stratagate-dsh 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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 record2 = value;
173
- if (typeof record2.error === "string") return `failed: ${record2.error.replace(/\s+/g, " ").slice(0, 140)}`;
174
- if (typeof record2.summary === "string") return record2.summary.replace(/\s+/g, " ").slice(0, 160);
175
- if (typeof record2.message === "string") return record2.message.replace(/\s+/g, " ").slice(0, 160);
176
- const counts = Object.entries(record2).filter(([key, item]) => !["arguments", "params", "input", "request"].includes(key) && Array.isArray(item)).map(([key, item]) => `${key}: ${item.length}`);
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 (record2.ok === true) return "completed";
179
- if (record2.ok === false) return "not completed";
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, key = "", seen = /* @__PURE__ */ new WeakSet()) {
193
+ function omitLargeSourceFields(value, key2 = "", seen = /* @__PURE__ */ new WeakSet()) {
194
194
  if (typeof value === "string") {
195
- if (!sourceFieldName(key) || value.length <= DERIVATION_SOURCE_FIELD_MIN_CHARS) return value;
196
- return `[${key || "source"} omitted: ${value.length} chars; full value remains in L5]`;
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, key, seen));
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 record2 = item;
265
- if (typeof record2.name === "string" && ("arguments" in record2 || "parameters" in record2)) names.add(record2.name);
266
- if (typeof record2.tool === "string") names.add(record2.tool);
267
- if (record2.function && typeof record2.function === "object") {
268
- const name2 = record2.function.name;
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(record2).forEach(visit);
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 key = normalizedParagraph(part.text);
362
- if (!seen.has(key)) {
363
- seen.add(key);
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(ids, eventById) {
487
- return unique((ids ?? []).filter((id) => EXPOSED_EVENT_STATUSES.has(eventById.get(id)?.status ?? "forgotten")));
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(entries, allowedValues, eventById) {
489
+ function metadataEntries(entries2, allowedValues, eventById) {
490
490
  const allowed = new Set(allowedValues.map((value) => normalizeSearchText(value)));
491
- return (entries ?? []).flatMap((entry) => {
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 = (ids, statuses) => unique((ids ?? []).filter((id) => {
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 = (record2) => {
528
- if (record2.status === "archived") return {};
529
- const activeSources = sources2(record2.sourceEventIds, activeStatuses);
530
- const visibleSources = sources2(record2.sourceEventIds, visibleStatuses);
531
- if ((record2.status === "active" || record2.status === "disputed") && activeSources.length > 0) {
532
- return { current: { ...record2, sourceEventIds: activeSources } };
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 && record2.status === "superseded") {
535
- return { historical: { ...record2, sourceEventIds: visibleSources } };
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 = (record2) => {
572
- const sourceEventIds = record2.sourceEventIds.filter((id) => eventIds.has(id));
573
- return sourceEventIds.length > 0 ? { ...record2, sourceEventIds } : null;
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((record2) => boundRecord(record2) ?? []);
576
- const historicalFacts = view.historicalFacts.flatMap((record2) => boundRecord(record2) ?? []);
577
- const currentEdges = view.currentEdges.flatMap((record2) => boundRecord(record2) ?? []);
578
- const historicalEdges = view.historicalEdges.flatMap((record2) => boundRecord(record2) ?? []);
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, ids, fallback) {
634
- return ids.flatMap((id) => events.find((event) => event.id === id) ?? []).map((event) => event.temporal.happenedStart ?? event.temporal.happenedEnd ?? event.temporal.mentionedAt ?? event.createdAt).sort().at(-1) ?? fallback;
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 key = (value) => normalizeSearchText(value).replace(/[\s_-]+/g, "");
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(key));
641
- if (proposalNames.has(key(metadata.name))) return "canonical-name";
642
- if (metadata.aliases.some((value) => proposalNames.has(key(value)))) return "alias";
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 = (ids) => ids.filter((id) => eventById.get(id)?.status === "active");
656
- const metadataEntriesFromProjection = (entries, fallbackValues) => {
657
- const byValue = new Map((entries ?? []).map((entry) => [normalizeSearchText(entry.value), entry]));
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 key = text(rawFact.key, 160);
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 (!key || (Array.isArray(value) ? value.length === 0 : !value)) continue;
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 "${key}" because it lacks valid exposable Event provenance.`);
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 "${key}" because it has no active Event provenance.`);
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 === 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 key of ["mentionedAt", "happenedStart", "happenedEnd", "originalText"]) {
1523
- const parsed = optionalString(raw[key], 200);
1524
- if (parsed) temporal[key] = parsed;
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 key of ["openTail", "blocks", "summaryJobs", "events", "agentEvents", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts", "externalMemoryImportJobs"]) {
1913
- if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
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 [key, values] of sets) lines.push(`${key}: ${values.join(", ")}`);
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 key = compactText(rawChange.key, 160);
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) || !key || !FACT_MODES.has(mode) || !operationMatchesMode || sourceEventIds.length === 0 || sourceEventIds.length !== requestedSourceEventIds.length || (Array.isArray(value) ? value.length === 0 : !value)) continue;
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 === key && candidate.mode === mode)) {
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 === key).flatMap((fact) => Array.isArray(fact.value) ? fact.value : [fact.value]).map(normalizeSearchText));
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 key = nonEmptyNamespace(namespace);
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(key);
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(key);
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(key);
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(key).map((row) => ({
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(key);
2625
+ `).all(key2);
2626
2626
  const sourcesByEvent = /* @__PURE__ */ new Map();
2627
2627
  for (const row of sourceRows) {
2628
- const ids = sourcesByEvent.get(row.event_id) ?? [];
2629
- ids.push(row.message_id);
2630
- sourcesByEvent.set(row.event_id, ids);
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(key);
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(key);
2639
+ `).all(key2);
2640
2640
  const agentSourcesByEvent = /* @__PURE__ */ new Map();
2641
2641
  for (const row of agentSourceRows) {
2642
- const ids = agentSourcesByEvent.get(row.event_id) ?? [];
2643
- ids.push(row.message_id);
2644
- agentSourcesByEvent.set(row.event_id, ids);
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(key);
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(key);
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(key);
2665
+ `).all(key2);
2666
2666
  const sourcesByElement = /* @__PURE__ */ new Map();
2667
2667
  for (const row of elementSourceRows) {
2668
- const ids = sourcesByElement.get(row.element_id) ?? [];
2669
- ids.push(row.event_id);
2670
- sourcesByElement.set(row.element_id, ids);
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(key);
2675
+ `).all(key2);
2676
2676
  const sourcesByFact = /* @__PURE__ */ new Map();
2677
2677
  for (const row of elementFactSourceRows) {
2678
- const ids = sourcesByFact.get(row.fact_id) ?? [];
2679
- ids.push(row.event_id);
2680
- sourcesByFact.set(row.fact_id, ids);
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(key);
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(key);
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(key).map((row) => ({
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(key).map((row) => ({
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(key).map((row) => ({
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(key).map((row) => {
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(key).map((row) => ({
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(key).map((row) => parseJson(row.payload_json, "external_memory_import_jobs.payload_json"));
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(key);
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(key) : void 0;
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 key = nonEmptyNamespace(namespace);
2827
- return this.immediateTransaction(() => this.persistSnapshot(key, snapshot, expectedRevision, rawMessageIndexDelta));
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 key = nonEmptyNamespace(namespace);
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, key, boundedLimit] : [match, key, threadId, boundedLimit]);
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 key of [
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[key];
3825
- if (typeof number === "number" && Number.isSafeInteger(number) && number >= 0) result[key] = number;
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(([key, item]) => [key, canonical(item)]));
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 = (ids) => [...new Set(ids)];
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 key = normalizedLabel(title);
3892
- const section = sections.get(key) ?? { title, sourceEventIds: [] };
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(key, section);
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 ids = topic.sourceEventIds.filter((id) => events.has(id));
3915
- if (ids.length === 0) return null;
3916
- const valid = !topic.invalidated && topic.projectorVersion === MEMORY_TOPIC_PROJECTOR_VERSION && ids.length === topic.sourceEventIds.length && Object.entries(topic.dependencyVersions ?? topic.sourceVersions).every(([id, version]) => versions.get(id) === 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: ids,
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: ids.length, summarizedEvents: summarized.size, omittedEvents: ids.length - summarized.size },
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 [key, value] of Object.entries(temporal)) {
3937
- if (typeof value === "string") temporal[key] = bounded(value, key === "originalText" ? 200 : 120);
3938
- if (Array.isArray(value)) temporal[key] = value.slice(0, 12).map((item) => bounded(String(item), 120));
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 ids = topic.sourceEventIds.filter((id) => !assigned.has(id) && versions.has(id) && this.state.projectedVersions[id] === versions.get(id));
4003
- if (ids.length) missing[topic.id] = Object.fromEntries(ids.map((id) => [id, versions.get(id)]));
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 === bootstrap.failedEvents && bootstrap.status !== "completed") {
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
- bootstrap.completedAt = now;
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(ids, versions, mode, sectionBackfillTopicId) {
4127
- return mode === "all" || ids.some((id) => this.isBootstrap(id, versions.get(id), sectionBackfillTopicId) === (mode === "bootstrap"));
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 = (ids, label) => {
4303
- if (!Array.isArray(ids) || ids.length === 0 || unique2(ids).length !== ids.length || ids.some((eventId) => typeof eventId !== "string" || !allowed.has(eventId))) throw new TopicProjectionError("validation-failed", `Invalid topic ${label} source events.`);
4304
- return ids;
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 ids = checkIds(proposal.sourceEventIds, "membership");
4308
- const matching = this.state.topics.filter((topic) => candidates.has(topic.id) && topic.sourceEventIds.some((eventId) => ids.includes(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 key = label(proposal.title);
4321
- if (labels.has(key)) throw new TopicProjectionError("validation-failed", "Invalid duplicate chapter label in one projection.");
4322
- labels.add(key);
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 ids) if (batchIds.has(eventId)) covered.add(eventId);
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) || typeof part.text !== "string" || !part.text.trim() || [...part.text].length > 600) throw new TopicProjectionError("validation-failed", "Invalid topic overview section.");
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) => !ids.includes(eventId))) throw new TopicProjectionError("validation-failed", "Overview references must belong to returned topic membership.");
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) => !ids.includes(eventId))) throw new TopicProjectionError("validation-failed", "Section references must belong to returned topic membership.");
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 && ids.some((eventId) => batchIds.has(eventId) && !assignments.some((section) => section.sourceEventIds.includes(eventId)))) throw new TopicProjectionError("validation-failed", "Topic sections omitted batch events.");
4352
- const membership = unique2([...(existing?.sourceEventIds ?? []).filter((eventId) => sources2.has(eventId)), ...ids]);
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 = unique2(proposal.overview.filter((part) => memoryTopicOverviewMatchesSection(part, titles)).map((part) => JSON.stringify(part))).map((part) => JSON.parse(part));
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 ids of [job.sourceEventIds.slice(0, middle), job.sourceEventIds.slice(middle)]) {
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: ids,
4442
- sourceVersions: Object.fromEntries(ids.map((id2) => [id2, job.sourceVersions[id2]])),
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
- retry(id, events, now) {
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 ids = Array.isArray(values) ? [...new Set(values.filter((id) => typeof id === "string" && allowedIds.has(id)))] : [];
4583
- if (ids.length > 0) result[field] = ids;
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(key, operation) {
4893
- const existing = this.manualRetryRuns.get(key);
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(key) === run) this.manualRetryRuns.delete(key);
5100
+ if (this.manualRetryRuns.get(key2) === run) this.manualRetryRuns.delete(key2);
4897
5101
  });
4898
- this.manualRetryRuns.set(key, run);
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 ids = addedEvents.map(({ id }) => id);
5455
- this.queueElementProjection(ids);
5456
- this.queueGraphProjection(ids, 2e3);
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 }) => key)).filter((key) => queryParts.includes(normalizeSearchText(key)));
6429
- const knownFactKeyTokens = new Set(explicitFactKeys.flatMap((key) => [...tokenSet(key)]));
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 }) => 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(key)).flatMap(({ sourceEventIds }) => sourceEventIds),
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(key));
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 = (record2) => {
6565
- const sourceEventIds = record2.sourceEventIds.filter((id) => evidenceIds.has(id));
6566
- return sourceEventIds.length > 0 ? { ...record2, sourceEventIds } : null;
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((record2) => bounded2(record2) ?? []),
6576
- historicalFacts: details.historicalFacts.flatMap((record2) => bounded2(record2) ?? []),
6577
- currentEdges: dedupe([...details.currentEdges, ...currentContextEdges]).flatMap((record2) => bounded2(record2) ?? []),
6578
- historicalEdges: details.historicalEdges.flatMap((record2) => bounded2(record2) ?? []),
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 ids = [...new Set(sourceEventIds.filter((id) => this.findEvent(id) && !completed.has(id) && !queued.has(id)))];
6595
- if (ids.length === 0) return null;
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: ids,
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 ids = [...new Set(sourceEventIds.filter((id) => Boolean(this.findEvent(id))))];
6643
- if (ids.length === 0) return null;
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: ids,
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(ids) {
6671
- for (const id of ids) {
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 ids = extracted.map(({ id }) => id);
6966
- this.queueElementProjection(ids);
6967
- this.queueGraphProjection(ids, 1e3);
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((key) => Object.prototype.hasOwnProperty.call(value, key));
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 entries = metadata.name;
7637
- if (typeof node.name !== "string" || !Array.isArray(entries) || entries.length === 0) continue;
7638
- if (!entries.every((candidateEntry) => {
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((key) => key === "value" || key === "sourceEventIds") && Array.isArray(entry.sourceEventIds) && entry.sourceEventIds.length > 0 && entry.sourceEventIds.every((id) => typeof id === "string");
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(entries.flatMap((entry) => entry.sourceEventIds))];
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\u3002overview \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
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((key) => key !== "topics") || !Array.isArray(raw.topics) || raw.topics.length > 12) {
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((key) => !["topicId", "title", "description", "sourceEventIds", "overview", "sections"].includes(key))) fail("unexpected topic field");
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((key2) => !["title", "sourceEventIds"].includes(key2))) fail("unexpected section field");
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 key = title2.normalize("NFKC").replace(/\s+/g, " ").toLowerCase();
7976
- if (sectionLabels.has(key)) fail("duplicate section title");
7977
- sectionLabels.add(key);
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((key) => !["kind", "title", "text", "sourceEventIds"].includes(key))) fail("unexpected overview field");
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
- const paragraph = boundedText(entry.text, "overview.text", 600);
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 key = exactKeys.get(event);
8135
- const prior = unique3.get(key);
8341
+ const key2 = exactKeys.get(event);
8342
+ const prior = unique3.get(key2);
8136
8343
  if (!prior) {
8137
- unique3.set(key, event);
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(payload) }],
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
- ...payload,
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) throw topicError("schema-invalid", "structured topic schema mismatch");
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 topicError("validation-failed", error instanceof TopicProjectionError ? error.diagnostics.reason : "topic semantic validation failed");
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: error, response: responseForError }
8524
- ) : error;
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 key = `${route.provider}\0${route.model}`;
8540
- const cached = this.offCapabilities.get(key);
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(key, `${route.provider}/${route.model} does not support reasoningEffort=off; using the model default`);
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(key, "unsupported");
8777
+ this.offCapabilities.set(key2, "unsupported");
8549
8778
  if (this.currentStructuredReasoningEffort() === "force-off") {
8550
- this.warnOffFallbackOnce(key, `${route.provider}/${route.model} capabilities are unavailable; using the model default`);
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(key, info.context.contextWindow);
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(key, supported ? "supported" : "unsupported");
8800
+ this.offCapabilities.set(key2, supported ? "supported" : "unsupported");
8572
8801
  if (!supported && this.currentStructuredReasoningEffort() === "force-off") {
8573
- this.warnOffFallbackOnce(key, `${route.provider}/${route.model} does not support reasoningEffort=off; using the model default`);
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(key, "unsupported");
8806
+ this.offCapabilities.set(key2, "unsupported");
8578
8807
  if (this.currentStructuredReasoningEffort() === "force-off") {
8579
- this.warnOffFallbackOnce(key, `${route.provider}/${route.model} capability lookup failed; using the model default`);
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(key, value) {
8756
- this.setSettingValue(key, String(value));
8984
+ setSetting(key2, value) {
8985
+ this.setSettingValue(key2, String(value));
8757
8986
  }
8758
- setSettingValue(key, value) {
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(key, value, (/* @__PURE__ */ new Date()).toISOString());
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 key = memoryTopicSectionKey(membership.title);
8940
- const section = sections.get(key) ?? { key, title: membership.title, paragraphs: [], sourceEventIds: [] };
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(key, section);
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 entries = sortMemoryTopics(topics).map((topic) => ({
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: entries.filter((entry) => entry.category === category.id).length
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 entries = options.category ? navigation.entries.filter(({ category }) => category === options.category) : navigation.entries;
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) entries = bm25Rank(entries, query, (entry) => weightedSearchTokens([[entry.title, 4], [entry.description, 2]])).map(({ item }) => item);
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 = entries.slice(offset, offset + limit);
9264
+ const page = entries2.slice(offset, offset + limit);
9035
9265
  return {
9036
9266
  navigationOnly: true,
9037
- total: entries.length,
9267
+ total: entries2.length,
9038
9268
  offset,
9039
- nextOffset: offset + page.length < entries.length ? offset + page.length : null,
9269
+ nextOffset: offset + page.length < entries2.length ? offset + page.length : null,
9040
9270
  categories: navigation.categories,
9041
9271
  topics: page
9042
9272
  };
@@ -9045,27 +9275,58 @@ function lineText(value, maximum) {
9045
9275
  return value.replace(/\s+/g, " ").trim().slice(0, maximum);
9046
9276
  }
9047
9277
  function renderMemoryDirectory(topics, events) {
9048
- const { entries, categories: categories2 } = topicNavigation(topics, events);
9049
- if (entries.length === 0) return "";
9050
- const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u6982\u89C8\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
- const lines = entries.map((entry) => `- ${entry.id}\uFF1A${lineText(entry.title, 64)}${entry.description ? `\uFF1B${lineText(entry.description, 48)}` : ""}`);
9052
- const complete = `${header}
9053
- ${lines.join("\n")}`;
9278
+ const { entries: entries2, categories: categories2 } = topicNavigation(topics, events);
9279
+ if (entries2.length === 0) return "";
9280
+ const header = "[StrataGate \u8BB0\u5FC6\u76EE\u5F55]\n\u4EC5\u4F9B\u5BFC\u822A\uFF0C\u4E0D\u662F\u4E8B\u5B9E\u8BC1\u636E\u3002\u9700\u8981\u4E3B\u9898\u8109\u7EDC\u65F6\u7528 memory_expand_topic\uFF1B\u9700\u8981\u4E8B\u5B9E\u8BC1\u636E\u53EF\u76F4\u63A5 memory_search_events(topic_id)\uFF0C\u4E5F\u53EF\u76F4\u63A5\u641C\u7D22\u4E8B\u4EF6/\u56FE\u8C31\u3002";
9281
+ const byId = new Map(topics.map((topic) => [topic.id, topic]));
9282
+ const chapters = entries2.map((entry) => ({
9283
+ ...entry,
9284
+ line: `- ${entry.id}\uFF1A${lineText(entry.title, 64)}${entry.description ? `\uFF1B${lineText(entry.description, 48)}` : ""}`,
9285
+ // Reuse the book's stable section order, including summary-free and legacy sections.
9286
+ sections: memoryTopicSections(byId.get(entry.id)).map(({ title }) => ` - ${lineText(title, 64)}`)
9287
+ }));
9288
+ const render = (prefix, selected2) => `${prefix}
9289
+ ${selected2.flatMap(({ line, sections }) => [line, ...sections]).join("\n")}`.trim();
9290
+ const complete = render(header, chapters);
9054
9291
  if (estimateTokens(complete) <= MEMORY_DIRECTORY_TOKEN_BUDGET) return complete;
9055
9292
  const branches = categories2.map(({ id, label, count }) => `${id}\uFF08${label} ${count}\uFF09`).join("\uFF1B");
9056
9293
  const navigation = `${header}
9057
- \u5171 ${entries.length} \u9879\uFF1B\u5206\u7C7B\uFF1A${branches}\u3002\u5B8C\u6574\u76EE\u5F55\u7528 memory_list_topics(category, offset) \u5206\u9875\uFF1Bquery \u53EF\u67E5\u4E3B\u9898\u3002\u4E0B\u5217\u4E3A\u90E8\u5206\u5165\u53E3\uFF1A`;
9058
- const selected = [];
9059
- for (const category of categories2) {
9060
- const entry = entries.find((item) => item.category === category.id);
9061
- const line = `- ${entry.id}\uFF1A${lineText(entry.title, 40)}`;
9062
- if (estimateTokens(`${navigation}
9063
- ${[...selected.map(({ line: line2 }) => line2), line].join("\n")}`) <= MEMORY_DIRECTORY_TOKEN_BUDGET) selected.push({ id: entry.id, line });
9064
- }
9065
- const order = new Map(entries.map(({ id }, index) => [id, index]));
9294
+ \u5171 ${entries2.length} \u9879\uFF1B\u5206\u7C7B\uFF1A${branches}\u3002\u76EE\u5F55\u5DF2\u88C1\u526A\uFF1B\u5B8C\u6574\u76EE\u5F55\u7528 memory_list_topics(category, offset) \u5206\u9875\uFF1Bquery \u53EF\u67E5\u4E3B\u9898\uFF0C\u5C0F\u8282\u7528 memory_expand_topic(id)\u3002`;
9295
+ let selected = chapters.map((chapter) => ({ ...chapter, sections: [] }));
9296
+ if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
9297
+ selected = [];
9298
+ const admit = (chapter) => {
9299
+ for (const line of [chapter.line, `- ${chapter.id}\uFF1A${lineText(chapter.title, 64)}`]) {
9300
+ const candidate = { ...chapter, line, sections: [] };
9301
+ if (estimateTokens(render(navigation, [...selected, candidate])) <= MEMORY_DIRECTORY_TOKEN_BUDGET) {
9302
+ selected.push(candidate);
9303
+ return;
9304
+ }
9305
+ }
9306
+ };
9307
+ for (const category of categories2) admit(chapters.find((chapter) => chapter.category === category.id));
9308
+ for (const chapter of chapters) if (!selected.some(({ id }) => id === chapter.id)) admit(chapter);
9309
+ }
9310
+ const order = new Map(entries2.map(({ id }, index) => [id, index]));
9066
9311
  selected.sort((a, b) => order.get(a.id) - order.get(b.id));
9067
- return `${navigation}
9068
- ${selected.map(({ line }) => line).join("\n")}`.trim();
9312
+ const sectionLines = new Map(chapters.map(({ id, sections }) => [id, sections]));
9313
+ const exhausted = /* @__PURE__ */ new Set();
9314
+ for (let index = 0; exhausted.size < selected.length; index++) {
9315
+ for (const chapter of selected) {
9316
+ if (exhausted.has(chapter.id)) continue;
9317
+ const line = sectionLines.get(chapter.id)[index];
9318
+ if (line === void 0) {
9319
+ exhausted.add(chapter.id);
9320
+ continue;
9321
+ }
9322
+ chapter.sections.push(line);
9323
+ if (estimateTokens(render(navigation, selected)) > MEMORY_DIRECTORY_TOKEN_BUDGET) {
9324
+ chapter.sections.pop();
9325
+ exhausted.add(chapter.id);
9326
+ }
9327
+ }
9328
+ }
9329
+ return render(navigation, selected);
9069
9330
  }
9070
9331
  function boundedTopic(topic, envelope = {}) {
9071
9332
  const { sections: _sections, ...summaryTopic } = topic;
@@ -9128,13 +9389,13 @@ var FEEDBACK_MARKDOWN_HEADINGS = {
9128
9389
  };
9129
9390
  function patchFeedbackMarkdown(markdown, input) {
9130
9391
  let result = markdown.trim();
9131
- for (const [key, heading] of Object.entries(FEEDBACK_MARKDOWN_HEADINGS)) {
9132
- if (!Object.prototype.hasOwnProperty.call(input, key)) continue;
9133
- const raw = input[key];
9392
+ for (const [key2, heading] of Object.entries(FEEDBACK_MARKDOWN_HEADINGS)) {
9393
+ if (!Object.prototype.hasOwnProperty.call(input, key2)) continue;
9394
+ const raw = input[key2];
9134
9395
  const text3 = Array.isArray(raw) ? raw.map((item, index) => {
9135
9396
  const value = feedbackText(item, 2e3);
9136
9397
  return value ? `${index + 1}. ${value}` : "";
9137
- }).filter(Boolean).join("\n") : feedbackText(raw, key === "description" ? 2e4 : key === "expected" || key === "actual" ? 1e4 : 2e4);
9398
+ }).filter(Boolean).join("\n") : feedbackText(raw, key2 === "description" ? 2e4 : key2 === "expected" || key2 === "actual" ? 1e4 : 2e4);
9138
9399
  const marker = `## ${heading}`;
9139
9400
  const lines = result.split("\n");
9140
9401
  let fence = null;
@@ -9176,9 +9437,9 @@ ${text3}`;
9176
9437
  return result.trim();
9177
9438
  }
9178
9439
  function normalizeFeedbackDraft(input, previous) {
9179
- const has = (key) => Object.prototype.hasOwnProperty.call(input, key);
9440
+ const has = (key2) => Object.prototype.hasOwnProperty.call(input, key2);
9180
9441
  const replacesStructuredBody = has("bodyMarkdown");
9181
- const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((key) => has(key));
9442
+ const updatesStructuredBody = ["description", "reproduction", "expected", "actual", "errorContext"].some((key2) => has(key2));
9182
9443
  const reproduction = replacesStructuredBody ? [] : has("reproduction") ? (Array.isArray(input.reproduction) ? input.reproduction : []).map((value) => feedbackText(value, 2e3)).filter(Boolean).slice(0, 20) : previous?.reproduction ?? [];
9183
9444
  const bodyMarkdown = has("bodyMarkdown") ? feedbackText(input.bodyMarkdown, 5e4) : updatesStructuredBody && previous?.bodyMarkdown ? patchFeedbackMarkdown(previous.bodyMarkdown, input) : previous?.bodyMarkdown;
9184
9445
  return {
@@ -9361,7 +9622,7 @@ var StrataGateRuntime = class {
9361
9622
  ...this.derivationRuns.keys(),
9362
9623
  ...this.migrationTimers.keys(),
9363
9624
  ...this.migrationRuns.keys()
9364
- ].some((key) => key.startsWith(`${namespace}\0`) || key === namespace);
9625
+ ].some((key2) => key2.startsWith(`${namespace}\0`) || key2 === namespace);
9365
9626
  if (hasActiveSessionWork) return;
9366
9627
  if (!this.models.isReady()) return;
9367
9628
  const resumed = await this.models.runDetached(
@@ -9460,24 +9721,24 @@ var StrataGateRuntime = class {
9460
9721
  if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
9461
9722
  const turn = this.folder.accept(session, event);
9462
9723
  if (!turn) return;
9463
- const key = String(session.id);
9464
- this.sessionsById.set(key, session);
9465
- const queued = this.pendingTurns.get(key) ?? [];
9724
+ const key2 = String(session.id);
9725
+ this.sessionsById.set(key2, session);
9726
+ const queued = this.pendingTurns.get(key2) ?? [];
9466
9727
  queued.push(turn);
9467
- this.pendingTurns.set(key, queued);
9728
+ this.pendingTurns.set(key2, queued);
9468
9729
  this.scheduleDrain(session);
9469
9730
  }
9470
9731
  /** Serialize drains per session while allowing unrelated sessions to settle independently. */
9471
9732
  enqueueDrain(session) {
9472
- const key = String(session.id);
9473
- const prior = this.drainTails.get(key) ?? Promise.resolve();
9733
+ const key2 = String(session.id);
9734
+ const prior = this.drainTails.get(key2) ?? Promise.resolve();
9474
9735
  const next = prior.catch(() => {
9475
9736
  }).then(() => this.drain(session));
9476
- this.drainTails.set(key, next);
9737
+ this.drainTails.set(key2, next);
9477
9738
  void next.then(() => {
9478
- if ((this.pendingTurns.get(key)?.length ?? 0) === 0) this.drainErrors.delete(key);
9739
+ if ((this.pendingTurns.get(key2)?.length ?? 0) === 0) this.drainErrors.delete(key2);
9479
9740
  }, (error) => {
9480
- this.drainErrors.set(key, error);
9741
+ this.drainErrors.set(key2, error);
9481
9742
  this.notePluginError(session, error);
9482
9743
  this.onIngestError(error);
9483
9744
  });
@@ -9485,46 +9746,46 @@ var StrataGateRuntime = class {
9485
9746
  }
9486
9747
  /** Batch short bursts and retry failed drains with bounded exponential backoff. */
9487
9748
  scheduleDrain(session, overrideDelayMs) {
9488
- const key = String(session.id);
9749
+ const key2 = String(session.id);
9489
9750
  if (this.closed) return;
9490
- const queued = this.pendingTurns.get(key)?.length ?? 0;
9751
+ const queued = this.pendingTurns.get(key2)?.length ?? 0;
9491
9752
  if (queued === 0) return;
9492
- const delay = overrideDelayMs ?? (queued >= DRAIN_THRESHOLD ? DRAIN_EAGER_MS : Math.min(this.drainBackoffMs.get(key) ?? DRAIN_BASE_BACKOFF_MS, DRAIN_MAX_BACKOFF_MS));
9493
- const pending = this.drainTimers.get(key);
9753
+ const delay = overrideDelayMs ?? (queued >= DRAIN_THRESHOLD ? DRAIN_EAGER_MS : Math.min(this.drainBackoffMs.get(key2) ?? DRAIN_BASE_BACKOFF_MS, DRAIN_MAX_BACKOFF_MS));
9754
+ const pending = this.drainTimers.get(key2);
9494
9755
  if (pending) {
9495
9756
  if (delay >= DRAIN_BASE_BACKOFF_MS) return;
9496
9757
  clearTimeout(pending);
9497
- this.drainTimers.delete(key);
9758
+ this.drainTimers.delete(key2);
9498
9759
  }
9499
9760
  const timer = setTimeout(() => {
9500
- this.drainTimers.delete(key);
9761
+ this.drainTimers.delete(key2);
9501
9762
  if (this.closed) return;
9502
9763
  void this.enqueueDrain(session).then(() => {
9503
- this.drainBackoffMs.delete(key);
9504
- if ((this.pendingTurns.get(key)?.length ?? 0) > 0) {
9764
+ this.drainBackoffMs.delete(key2);
9765
+ if ((this.pendingTurns.get(key2)?.length ?? 0) > 0) {
9505
9766
  this.scheduleDrain(session, DRAIN_BASE_BACKOFF_MS);
9506
9767
  }
9507
9768
  }, () => {
9508
- const backoff = this.drainBackoffMs.get(key) ?? DRAIN_BASE_BACKOFF_MS;
9509
- this.drainBackoffMs.set(key, Math.min(backoff * 2, DRAIN_MAX_BACKOFF_MS));
9510
- const pendingRetry = this.drainTimers.get(key);
9769
+ const backoff = this.drainBackoffMs.get(key2) ?? DRAIN_BASE_BACKOFF_MS;
9770
+ this.drainBackoffMs.set(key2, Math.min(backoff * 2, DRAIN_MAX_BACKOFF_MS));
9771
+ const pendingRetry = this.drainTimers.get(key2);
9511
9772
  if (pendingRetry) clearTimeout(pendingRetry);
9512
- this.drainTimers.delete(key);
9773
+ this.drainTimers.delete(key2);
9513
9774
  this.scheduleDrain(session);
9514
9775
  });
9515
9776
  }, delay);
9516
9777
  timer.unref?.();
9517
- this.drainTimers.set(key, timer);
9778
+ this.drainTimers.set(key2, timer);
9518
9779
  }
9519
9780
  /**
9520
9781
  * Persist one detached batch. On failure, the failed and unvisited turns are
9521
9782
  * restored ahead of turns that arrived meanwhile, preserving original order.
9522
9783
  */
9523
9784
  async drain(session) {
9524
- const key = String(session.id);
9525
- const turns = this.pendingTurns.get(key);
9785
+ const key2 = String(session.id);
9786
+ const turns = this.pendingTurns.get(key2);
9526
9787
  if (!turns || turns.length === 0) return;
9527
- this.pendingTurns.delete(key);
9788
+ this.pendingTurns.delete(key2);
9528
9789
  let completed = 0;
9529
9790
  let memory;
9530
9791
  try {
@@ -9537,7 +9798,7 @@ var StrataGateRuntime = class {
9537
9798
  } catch (error) {
9538
9799
  const remaining = turns.slice(completed);
9539
9800
  if (remaining.length > 0) {
9540
- this.pendingTurns.set(key, [...remaining, ...this.pendingTurns.get(key) ?? []]);
9801
+ this.pendingTurns.set(key2, [...remaining, ...this.pendingTurns.get(key2) ?? []]);
9541
9802
  }
9542
9803
  throw error;
9543
9804
  } finally {
@@ -9611,9 +9872,21 @@ var StrataGateRuntime = class {
9611
9872
  }
9612
9873
  async buildMemoryDirectory(session) {
9613
9874
  await this.flush();
9614
- const memory = await this.space(session);
9615
- await this.refreshExternalImportMemory(this.namespaceFor(session), memory);
9616
- return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
9875
+ const namespace = this.namespaceFor(session);
9876
+ const active = this.spaces.get(namespace);
9877
+ if (active) {
9878
+ const memory = await active;
9879
+ await this.refreshExternalImportMemory(namespace, memory);
9880
+ return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
9881
+ }
9882
+ if (this.config.database === ":memory:" || !existsSync(this.config.database)) return "";
9883
+ const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
9884
+ try {
9885
+ const memory = await StrataGate.openWithStorage({ storage, namespace });
9886
+ return renderMemoryDirectory(this.visibleTopics(memory), memory.listAllEvents());
9887
+ } finally {
9888
+ await storage.close();
9889
+ }
9617
9890
  }
9618
9891
  visibleTopics(memory) {
9619
9892
  if (this.agentMemoryRetrievalWeight > 0) return memory.listMemoryTopics();
@@ -9760,7 +10033,7 @@ var StrataGateRuntime = class {
9760
10033
  };
9761
10034
  }
9762
10035
  async recordUse(session, receiptId, evidenceRefs, batchId) {
9763
- const key = String(session.id);
10036
+ const key2 = String(session.id);
9764
10037
  const batch = this.requireBatch(session, batchId, "memory_record_use");
9765
10038
  if (batch.status !== "unresolved") {
9766
10039
  throw new Error(this.batchError(
@@ -9839,9 +10112,9 @@ var StrataGateRuntime = class {
9839
10112
  eventIds: [...eventIds],
9840
10113
  elementIds: [...elementIds]
9841
10114
  }, {
9842
- receiptId: `dsh:${key}:tool:${receiptId}`,
10115
+ receiptId: `dsh:${key2}:tool:${receiptId}`,
9843
10116
  audit: {
9844
- sessionId: key,
10117
+ sessionId: key2,
9845
10118
  ...turn === void 0 ? {} : { turn },
9846
10119
  batchId: batch.id,
9847
10120
  evidenceRefs: selectedRefs,
@@ -9890,6 +10163,7 @@ var StrataGateRuntime = class {
9890
10163
  async buildAutoContext(session) {
9891
10164
  await this.flush();
9892
10165
  const memory = await this.space(session);
10166
+ await this.refreshExternalImportMemory(this.namespaceFor(session), memory);
9893
10167
  const threadId = String(session.id);
9894
10168
  const blockContexts = memory.getBlockContext(threadId);
9895
10169
  if (this.syncDecayedBlockSurface(session, memory, blockContexts)) {
@@ -10004,9 +10278,9 @@ var StrataGateRuntime = class {
10004
10278
  const contexts = new Map(memory.getBlockContext(threadId).map((context) => [context.id, context]));
10005
10279
  const existing = currentBlockSurfaceMessages(session);
10006
10280
  let changed = false;
10007
- for (const [key, { blockId }] of pending) {
10281
+ for (const [key2, { blockId }] of pending) {
10008
10282
  if (existing.has(blockId)) {
10009
- this.pendingSurfaceSync.delete(key);
10283
+ this.pendingSurfaceSync.delete(key2);
10010
10284
  continue;
10011
10285
  }
10012
10286
  const block = memory.listBlocks().find((candidate) => candidate.id === blockId && candidate.threadId === threadId);
@@ -10014,7 +10288,7 @@ var StrataGateRuntime = class {
10014
10288
  if (!block || block.processingStatus !== "ready" || !context) continue;
10015
10289
  try {
10016
10290
  changed = this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block)) || changed;
10017
- this.pendingSurfaceSync.delete(key);
10291
+ this.pendingSurfaceSync.delete(key2);
10018
10292
  } catch (error) {
10019
10293
  this.onIngestError(error);
10020
10294
  }
@@ -10024,11 +10298,11 @@ var StrataGateRuntime = class {
10024
10298
  // Keep the ingestion error for callers that explicitly require a flushed run.
10025
10299
  async settleIngestion() {
10026
10300
  do {
10027
- for (const [key, session] of this.sessionsById) {
10028
- if ((this.pendingTurns.get(key)?.length ?? 0) === 0) continue;
10029
- const timer = this.drainTimers.get(key);
10301
+ for (const [key2, session] of this.sessionsById) {
10302
+ if ((this.pendingTurns.get(key2)?.length ?? 0) === 0) continue;
10303
+ const timer = this.drainTimers.get(key2);
10030
10304
  if (timer) clearTimeout(timer);
10031
- this.drainTimers.delete(key);
10305
+ this.drainTimers.delete(key2);
10032
10306
  this.enqueueDrain(session);
10033
10307
  }
10034
10308
  await Promise.allSettled(this.drainTails.values());
@@ -10091,16 +10365,16 @@ var StrataGateRuntime = class {
10091
10365
  };
10092
10366
  }
10093
10367
  adminFeedbackDraft(namespace) {
10094
- const key = namespace.trim();
10095
- if (!key) throw new TypeError("StrataGate feedback namespace must not be empty");
10096
- return { namespace: key, draft: this.loadFeedbackDraft(key) };
10368
+ const key2 = namespace.trim();
10369
+ if (!key2) throw new TypeError("StrataGate feedback namespace must not be empty");
10370
+ return { namespace: key2, draft: this.loadFeedbackDraft(key2) };
10097
10371
  }
10098
10372
  adminSaveFeedbackDraft(namespace, input) {
10099
- const key = namespace.trim();
10100
- if (!key) throw new TypeError("StrataGate feedback namespace must not be empty");
10101
- const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(key));
10102
- this.saveFeedbackDraft(key, draft);
10103
- return { namespace: key, draft };
10373
+ const key2 = namespace.trim();
10374
+ if (!key2) throw new TypeError("StrataGate feedback namespace must not be empty");
10375
+ const draft = normalizeFeedbackDraft(input, this.loadFeedbackDraft(key2));
10376
+ this.saveFeedbackDraft(key2, draft);
10377
+ return { namespace: key2, draft };
10104
10378
  }
10105
10379
  /**
10106
10380
  * Record one agent-authored fact through the core long-term Event pipeline.
@@ -10268,7 +10542,7 @@ var StrataGateRuntime = class {
10268
10542
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
10269
10543
  const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
10270
10544
  try {
10271
- const entries = [];
10545
+ const entries2 = [];
10272
10546
  for (const { namespace, revision } of storage.listNamespaceRevisions()) {
10273
10547
  const opening = this.spaces.get(namespace);
10274
10548
  if (opening) {
@@ -10278,25 +10552,25 @@ var StrataGateRuntime = class {
10278
10552
  const cached2 = this.adminSnapshotCache.get(namespace);
10279
10553
  const entry2 = cached2?.revision === currentRevision ? cached2 : { namespace, revision: currentRevision, snapshot: memory.exportSnapshot() };
10280
10554
  this.adminSnapshotCache.set(namespace, entry2);
10281
- entries.push(entry2);
10555
+ entries2.push(entry2);
10282
10556
  continue;
10283
10557
  }
10284
10558
  const cached = this.adminSnapshotCache.get(namespace);
10285
10559
  if (cached?.revision === revision) {
10286
- entries.push(cached);
10560
+ entries2.push(cached);
10287
10561
  continue;
10288
10562
  }
10289
10563
  const loaded = await storage.load(namespace);
10290
10564
  if (!loaded) continue;
10291
10565
  const entry = { namespace, revision: loaded.revision, snapshot: loaded.snapshot };
10292
10566
  this.adminSnapshotCache.set(namespace, entry);
10293
- entries.push(entry);
10567
+ entries2.push(entry);
10294
10568
  }
10295
- const activeNamespaces = new Set(entries.map(({ namespace }) => namespace));
10569
+ const activeNamespaces = new Set(entries2.map(({ namespace }) => namespace));
10296
10570
  for (const namespace of this.adminSnapshotCache.keys()) {
10297
10571
  if (!activeNamespaces.has(namespace)) this.adminSnapshotCache.delete(namespace);
10298
10572
  }
10299
- return entries;
10573
+ return entries2;
10300
10574
  } finally {
10301
10575
  await storage.close();
10302
10576
  }
@@ -10327,30 +10601,30 @@ var StrataGateRuntime = class {
10327
10601
  }
10328
10602
  }
10329
10603
  async adminSnapshot(namespace) {
10330
- const key = namespace.trim();
10331
- if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
10604
+ const key2 = namespace.trim();
10605
+ if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
10332
10606
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
10333
- const opening = this.spaces.get(key);
10607
+ const opening = this.spaces.get(key2);
10334
10608
  if (opening) {
10335
10609
  const memory = await opening;
10336
- await this.refreshExternalImportMemory(key, memory);
10610
+ await this.refreshExternalImportMemory(key2, memory);
10337
10611
  const revision = memory.storageRevision;
10338
- const cached = this.adminSnapshotCache.get(key);
10612
+ const cached = this.adminSnapshotCache.get(key2);
10339
10613
  if (cached?.revision === revision) return cached.snapshot;
10340
- const entry = { namespace: key, revision, snapshot: memory.exportSnapshot() };
10341
- this.adminSnapshotCache.set(key, entry);
10614
+ const entry = { namespace: key2, revision, snapshot: memory.exportSnapshot() };
10615
+ this.adminSnapshotCache.set(key2, entry);
10342
10616
  return entry.snapshot;
10343
10617
  }
10344
10618
  const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
10345
10619
  try {
10346
- const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key);
10620
+ const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key2);
10347
10621
  if (!head) return null;
10348
- const cached = this.adminSnapshotCache.get(key);
10622
+ const cached = this.adminSnapshotCache.get(key2);
10349
10623
  if (cached?.revision === head.revision) return cached.snapshot;
10350
- const loaded = await storage.load(key);
10624
+ const loaded = await storage.load(key2);
10351
10625
  if (!loaded) return null;
10352
- const entry = { namespace: key, revision: loaded.revision, snapshot: loaded.snapshot };
10353
- this.adminSnapshotCache.set(key, entry);
10626
+ const entry = { namespace: key2, revision: loaded.revision, snapshot: loaded.snapshot };
10627
+ this.adminSnapshotCache.set(key2, entry);
10354
10628
  return entry.snapshot;
10355
10629
  } finally {
10356
10630
  await storage.close();
@@ -10479,44 +10753,44 @@ var StrataGateRuntime = class {
10479
10753
  }
10480
10754
  /** Create a durable analysis job and return before model-backed work begins. */
10481
10755
  async adminPreviewExternalMemory(namespace, text3) {
10482
- const key = namespace.trim();
10483
- if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
10756
+ const key2 = namespace.trim();
10757
+ if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
10484
10758
  if (!text3.trim()) throw new TypeError("External memory text must not be empty");
10485
10759
  await this.flush();
10486
- const { memory, owned } = await this.openAdminMemory(key);
10760
+ const { memory, owned } = await this.openAdminMemory(key2);
10487
10761
  try {
10488
- await this.refreshExternalImportMemory(key, memory);
10762
+ await this.refreshExternalImportMemory(key2, memory);
10489
10763
  const job = await memory.createExternalMemoryImportJob(text3);
10490
- this.scheduleExternalMemoryImport(key, job.id);
10764
+ this.scheduleExternalMemoryImport(key2, job.id);
10491
10765
  return this.externalImportView(job);
10492
10766
  } finally {
10493
10767
  if (owned) await memory.close();
10494
10768
  }
10495
10769
  }
10496
10770
  async adminExternalMemoryStatus(namespace, jobId) {
10497
- const key = namespace.trim();
10498
- if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
10499
- const { memory, owned } = await this.openAdminMemory(key);
10771
+ const key2 = namespace.trim();
10772
+ if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
10773
+ const { memory, owned } = await this.openAdminMemory(key2);
10500
10774
  try {
10501
- await this.refreshExternalImportMemory(key, memory);
10775
+ await this.refreshExternalImportMemory(key2, memory);
10502
10776
  const jobs = memory.listExternalMemoryImportJobs();
10503
10777
  const job = jobId ? jobs.find(({ id }) => id === jobId) : [...jobs].sort((left, right) => right.createdAt.localeCompare(left.createdAt))[0];
10504
10778
  if (!job) return { job: null };
10505
- if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(key, job.id);
10779
+ if (job.status === "extracting" || job.status === "processing") this.scheduleExternalMemoryImport(key2, job.id);
10506
10780
  return this.externalImportView(job);
10507
10781
  } finally {
10508
10782
  if (owned) await memory.close();
10509
10783
  }
10510
10784
  }
10511
10785
  async adminRetryExternalMemory(namespace, jobId) {
10512
- const key = namespace.trim();
10786
+ const key2 = namespace.trim();
10513
10787
  let lastConflict;
10514
10788
  for (let attempt = 0; attempt < 4; attempt += 1) {
10515
- const { memory, owned } = await this.openAdminMemory(key);
10789
+ const { memory, owned } = await this.openAdminMemory(key2);
10516
10790
  try {
10517
- await this.refreshExternalImportMemory(key, memory);
10791
+ await this.refreshExternalImportMemory(key2, memory);
10518
10792
  const job = await memory.retryExternalMemoryImportJob(jobId);
10519
- this.scheduleExternalMemoryImport(key, job.id);
10793
+ this.scheduleExternalMemoryImport(key2, job.id);
10520
10794
  return this.externalImportView(job);
10521
10795
  } catch (error) {
10522
10796
  if (!(error instanceof StorageConflictError)) throw error;
@@ -10529,10 +10803,10 @@ var StrataGateRuntime = class {
10529
10803
  throw lastConflict;
10530
10804
  }
10531
10805
  async adminCommitExternalMemory(namespace, jobId, choices) {
10532
- const key = namespace.trim();
10533
- const { memory, owned } = await this.openAdminMemory(key);
10806
+ const key2 = namespace.trim();
10807
+ const { memory, owned } = await this.openAdminMemory(key2);
10534
10808
  try {
10535
- await this.refreshExternalImportMemory(key, memory);
10809
+ await this.refreshExternalImportMemory(key2, memory);
10536
10810
  const job = memory.getExternalMemoryImportJob(jobId);
10537
10811
  if (!job) throw new Error("\u627E\u4E0D\u5230\u5BFC\u5165\u4EFB\u52A1");
10538
10812
  if (job.status !== "ready" && job.status !== "awaiting_confirmation") {
@@ -10573,10 +10847,10 @@ var StrataGateRuntime = class {
10573
10847
  }
10574
10848
  }
10575
10849
  async adminUndoExternalMemory(namespace, sourceBlockId) {
10576
- const key = namespace.trim();
10577
- const { memory, owned } = await this.openAdminMemory(key);
10850
+ const key2 = namespace.trim();
10851
+ const { memory, owned } = await this.openAdminMemory(key2);
10578
10852
  try {
10579
- await this.refreshExternalImportMemory(key, memory);
10853
+ await this.refreshExternalImportMemory(key2, memory);
10580
10854
  const result = await memory.undoExternalMemoryImport(sourceBlockId);
10581
10855
  const job = memory.listExternalMemoryImportJobs().find((candidate) => candidate.sourceBlockId === sourceBlockId);
10582
10856
  if (job) await memory.markExternalMemoryImportUndone(job.id);
@@ -10658,19 +10932,19 @@ var StrataGateRuntime = class {
10658
10932
  return { opened: true, path };
10659
10933
  }
10660
10934
  async adminExpandBlock(namespace, id, target) {
10661
- const key = namespace.trim();
10662
- if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
10935
+ const key2 = namespace.trim();
10936
+ if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
10663
10937
  const update = this.settingsTail.catch(() => {
10664
10938
  }).then(async () => {
10665
10939
  await this.flush();
10666
- const active = this.spaces.get(key);
10940
+ const active = this.spaces.get(key2);
10667
10941
  if (active) return (await active).expandBlock(id, target, "user");
10668
10942
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
10669
- throw new Error(`Unknown StrataGate namespace: ${key}`);
10943
+ throw new Error(`Unknown StrataGate namespace: ${key2}`);
10670
10944
  }
10671
10945
  const memory = await StrataGate.open({
10672
10946
  database: this.config.database,
10673
- namespace: key,
10947
+ namespace: key2,
10674
10948
  blockTurnSize: this.blockTurnSize,
10675
10949
  blockDecayLambda: this.blockDecayLambda,
10676
10950
  summarizer: this.models.summarizer,
@@ -10690,20 +10964,20 @@ var StrataGateRuntime = class {
10690
10964
  return update;
10691
10965
  }
10692
10966
  async adminRetryJob(namespace, kind, id) {
10693
- const key = namespace.trim();
10967
+ const key2 = namespace.trim();
10694
10968
  const jobId = id.trim();
10695
- if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
10969
+ if (!key2) throw new TypeError("StrataGate admin namespace must not be empty");
10696
10970
  if (!jobId) throw new TypeError("StrataGate job id must not be empty");
10697
- if (kind === "topic-projection") return this.adminRetryTopicProjection(key, jobId);
10698
- const retryKey = `${key}\0${kind}\0${jobId}`;
10971
+ if (kind === "topic-projection") return this.adminRetryTopicProjection(key2, jobId);
10972
+ const retryKey = `${key2}\0${kind}\0${jobId}`;
10699
10973
  const existing = this.adminJobRetryRuns.get(retryKey);
10700
10974
  if (existing) return existing;
10701
10975
  const update = this.settingsTail.catch(() => {
10702
10976
  }).then(async () => {
10703
10977
  await this.flush();
10704
- const { memory, owned } = await this.openAdminMemory(key, { derivation: true });
10978
+ const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
10705
10979
  try {
10706
- await this.refreshExternalImportMemory(key, memory);
10980
+ await this.refreshExternalImportMemory(key2, memory);
10707
10981
  const graphJob = kind === "graph-projection" ? memory.listGraphProjectionJobs().find((candidate) => candidate.id === jobId) : void 0;
10708
10982
  const blockId = kind === "graph-projection" ? graphJob?.sourceEventIds.flatMap((eventId) => memory.listAllEvents().find(({ id: eventCandidateId }) => eventCandidateId === eventId)?.sourceBlockId ?? [])[0] : jobId;
10709
10983
  const block = blockId ? memory.listBlocks().find((candidate) => candidate.id === blockId) : void 0;
@@ -10719,12 +10993,12 @@ var StrataGateRuntime = class {
10719
10993
  if (!this.models.isReady(session)) {
10720
10994
  throw new Error("StrataGate model adapter is not ready; retry after DSH finishes registering adapters");
10721
10995
  }
10722
- const result = session && this.namespaceFor(session) === key ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
10996
+ const result = session && this.namespaceFor(session) === key2 ? await this.models.run(session, retry) : await this.models.runDetached(threadId, retry);
10723
10997
  await this.persistSuccessfulResponses(memory);
10724
10998
  const currentBlock = blockId ? memory.listBlocks().find(({ id: candidateId }) => candidateId === blockId) : void 0;
10725
10999
  if (kind !== "graph-projection" && currentBlock?.processingStatus === "ready" && currentBlock.threadId) {
10726
- this.pendingSurfaceSync.set(`${key}\0${currentBlock.id}`, { namespace: key, blockId: currentBlock.id });
10727
- if (session && this.namespaceFor(session) === key) await this.syncPendingRetrySurface(session, memory);
11000
+ this.pendingSurfaceSync.set(`${key2}\0${currentBlock.id}`, { namespace: key2, blockId: currentBlock.id });
11001
+ if (session && this.namespaceFor(session) === key2) await this.syncPendingRetrySurface(session, memory);
10728
11002
  }
10729
11003
  const job = kind === "block-summary" ? memory.listSummaryJobs().find(({ blockId: candidateId }) => candidateId === jobId) : kind === "event-extraction" ? memory.listExtractionJobs().find(({ blockId: candidateId }) => candidateId === jobId) : memory.listGraphProjectionJobs().find(({ id: candidateId }) => candidateId === jobId);
10730
11004
  const succeeded = kind === "graph-projection" ? job?.status === "completed" : job?.status === "succeeded" || job?.status === "skipped";
@@ -10739,7 +11013,7 @@ var StrataGateRuntime = class {
10739
11013
  result,
10740
11014
  processingStatus: currentBlock?.processingStatus ?? null,
10741
11015
  ready: currentBlock?.processingStatus === "ready",
10742
- surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${key}\0${blockId}`)
11016
+ surfaceUpdated: !blockId || !this.pendingSurfaceSync.has(`${key2}\0${blockId}`)
10743
11017
  };
10744
11018
  } finally {
10745
11019
  if (owned) await memory.close();
@@ -10755,14 +11029,14 @@ var StrataGateRuntime = class {
10755
11029
  return tracked;
10756
11030
  }
10757
11031
  async adminRetryTopicProjection(namespace, id) {
10758
- const key = namespace.trim();
11032
+ const key2 = namespace.trim();
10759
11033
  const jobId = id.trim();
10760
- if (!key || !jobId) throw new TypeError("Topic retry requires namespace and job id");
11034
+ if (!key2 || !jobId) throw new TypeError("Topic retry requires namespace and job id");
10761
11035
  const update = this.settingsTail.catch(() => {
10762
11036
  }).then(async () => {
10763
- const { memory, owned } = await this.openAdminMemory(key, { derivation: true });
11037
+ const { memory, owned } = await this.openAdminMemory(key2, { derivation: true });
10764
11038
  try {
10765
- return await this.retryTopicWrite(key, memory, () => memory.retryTopicProjection(jobId));
11039
+ return await this.retryTopicWrite(key2, memory, () => memory.retryTopicProjection(jobId));
10766
11040
  } finally {
10767
11041
  if (owned) await memory.close();
10768
11042
  }
@@ -10939,8 +11213,8 @@ var StrataGateRuntime = class {
10939
11213
  }
10940
11214
  scheduleBlockDerivation(session, memory) {
10941
11215
  const threadId = String(session.id);
10942
- const key = `${this.namespaceFor(session)}\0${threadId}`;
10943
- if (this.closed || !this.models.isReady(session) || this.derivationTimers.has(key) || this.derivationRuns.has(key)) return;
11216
+ const key2 = `${this.namespaceFor(session)}\0${threadId}`;
11217
+ if (this.closed || !this.models.isReady(session) || this.derivationTimers.has(key2) || this.derivationRuns.has(key2)) return;
10944
11218
  const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
10945
11219
  if (pendingBlockIds.size === 0) return;
10946
11220
  const retryTimes = [
@@ -10950,7 +11224,7 @@ var StrataGateRuntime = class {
10950
11224
  if (retryTimes.length === 0) return;
10951
11225
  const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
10952
11226
  const timer = setTimeout(() => {
10953
- this.derivationTimers.delete(key);
11227
+ this.derivationTimers.delete(key2);
10954
11228
  if (this.closed || !this.models.isReady(session)) return;
10955
11229
  const failedBefore = this.failedCoreJobs(memory);
10956
11230
  const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
@@ -10977,13 +11251,13 @@ var StrataGateRuntime = class {
10977
11251
  this.notePluginError(session, error);
10978
11252
  this.onIngestError(error);
10979
11253
  }).finally(() => {
10980
- this.derivationRuns.delete(key);
11254
+ this.derivationRuns.delete(key2);
10981
11255
  this.scheduleBlockDerivation(session, memory);
10982
11256
  });
10983
- this.derivationRuns.set(key, run);
11257
+ this.derivationRuns.set(key2, run);
10984
11258
  }, delay);
10985
11259
  timer.unref?.();
10986
- this.derivationTimers.set(key, timer);
11260
+ this.derivationTimers.set(key2, timer);
10987
11261
  }
10988
11262
  scheduleGraphMigration(session, memory) {
10989
11263
  const namespace = this.namespaceFor(session);
@@ -11068,23 +11342,28 @@ var StrataGateRuntime = class {
11068
11342
  if (responses.length > 0) await memory.recordSuccessfulModelResponses(responses);
11069
11343
  }
11070
11344
  batch(session, evidence, results, metadata = {}) {
11071
- const sequence = ++this.batchSequence;
11345
+ const sequence = this.batchSequence + 1;
11072
11346
  const id = `batch_${sequence}`;
11073
11347
  const refs = new Map(evidence.map(({ ref, target }) => [ref, target]));
11074
- const key = String(session.id);
11075
- let sessionBatches = this.batches.get(key);
11348
+ const output = { batchId: id, evidenceRefs: [...refs.keys()], results, ...metadata };
11349
+ if (validateArgs2({ retrieval: { type: "json", required: true } }, { retrieval: output }).length) {
11350
+ throw new TypeError("StrataGate retrieval output is not lossless JSON");
11351
+ }
11352
+ this.batchSequence = sequence;
11353
+ const key2 = String(session.id);
11354
+ let sessionBatches = this.batches.get(key2);
11076
11355
  if (!sessionBatches) {
11077
11356
  sessionBatches = /* @__PURE__ */ new Map();
11078
- this.batches.set(key, sessionBatches);
11357
+ this.batches.set(key2, sessionBatches);
11079
11358
  }
11080
11359
  sessionBatches.set(id, { id, sequence, refs, status: "unresolved" });
11081
- this.latestBatchIds.set(key, id);
11082
- return { batchId: id, evidenceRefs: [...refs.keys()], results, ...metadata };
11360
+ this.latestBatchIds.set(key2, id);
11361
+ return output;
11083
11362
  }
11084
11363
  requireBatch(session, batchId, operation) {
11085
- const key = String(session.id);
11086
- const selectedId = batchId?.trim() || this.latestBatchIds.get(key);
11087
- const sessionBatches = this.batches.get(key);
11364
+ const key2 = String(session.id);
11365
+ const selectedId = batchId?.trim() || this.latestBatchIds.get(key2);
11366
+ const sessionBatches = this.batches.get(key2);
11088
11367
  const batch = selectedId ? sessionBatches?.get(selectedId) : void 0;
11089
11368
  if (batch) return batch;
11090
11369
  const availableBatchIds = [...sessionBatches?.keys() ?? []];
@@ -11092,7 +11371,7 @@ var StrataGateRuntime = class {
11092
11371
  const requested = batchId?.trim() ? `Unknown retrieval batch: ${batchId.trim()}.` : "No StrataGate retrieval batch exists for this session.";
11093
11372
  throw new Error([
11094
11373
  `${operation} could not select a retrieval batch. ${requested}`,
11095
- `Latest batch: ${this.latestBatchIds.get(key) ?? "none"}.`,
11374
+ `Latest batch: ${this.latestBatchIds.get(key2) ?? "none"}.`,
11096
11375
  `Unresolved batches: ${JSON.stringify(unresolvedBatchIds)}.`,
11097
11376
  `Available batches: ${JSON.stringify(availableBatchIds)}.`
11098
11377
  ].join(" "));
@@ -11103,10 +11382,10 @@ var StrataGateRuntime = class {
11103
11382
  return [...sessionBatches.values()].filter(({ status }) => status === "unresolved").map(({ id }) => id);
11104
11383
  }
11105
11384
  batchError(session, batch, summary, invalidEvidenceRefs = []) {
11106
- const key = String(session.id);
11385
+ const key2 = String(session.id);
11107
11386
  const assessmentStatus = batch.assessment?.verdict ?? "not_assessed";
11108
- const latestBatch = this.latestBatchIds.get(key);
11109
- const latestState = latestBatch ? this.batches.get(key)?.get(latestBatch) : void 0;
11387
+ const latestBatch = this.latestBatchIds.get(key2);
11388
+ const latestState = latestBatch ? this.batches.get(key2)?.get(latestBatch) : void 0;
11110
11389
  return [
11111
11390
  summary,
11112
11391
  `Invalid evidence refs: ${JSON.stringify(invalidEvidenceRefs)}.`,
@@ -11350,7 +11629,7 @@ function compactEvent2(event, score) {
11350
11629
  summary: compactText2(event.summary),
11351
11630
  sourceTime: event.temporal.happenedStart ?? event.temporal.mentionedAt ?? event.createdAt,
11352
11631
  temporal: compactTemporal(event),
11353
- sourceBlockId: event.sourceBlockId,
11632
+ ...event.sourceBlockId !== void 0 ? { sourceBlockId: event.sourceBlockId } : {},
11354
11633
  status: event.status,
11355
11634
  scope: event.scope,
11356
11635
  criticality: event.criticality,
@@ -11495,9 +11774,9 @@ Events:
11495
11774
  metadataEvidenceStatus: result.metadataEvidenceStatus,
11496
11775
  matchType: result.matchType,
11497
11776
  currentState: node.currentState,
11498
- facts: node.facts.map(({ key, value, status, validFrom, validTo }) => ({ key, value, status, validFrom, validTo })),
11777
+ facts: node.facts.map(({ key: key2, value, status, validFrom, validTo }) => ({ key: key2, value, status, validFrom, validTo })),
11499
11778
  currentRelations: result.currentEdges?.map(({ relation, status }) => ({ relation, status })),
11500
- historicalMatches: result.historicalFacts?.map(({ key, value, status, validFrom, validTo }) => ({ key, value, status, validFrom, validTo })),
11779
+ historicalMatches: result.historicalFacts?.map(({ key: key2, value, status, validFrom, validTo }) => ({ key: key2, value, status, validFrom, validTo })),
11501
11780
  historicalRelations: result.historicalEdges?.map(({ relation, status, validFrom, validTo }) => ({ relation, status, validFrom, validTo })),
11502
11781
  timeline: result.timeline
11503
11782
  });
@@ -11557,7 +11836,7 @@ function registerMemoryTools(ctx, runtime) {
11557
11836
  },
11558
11837
  output: jsonOutput,
11559
11838
  execute: async (args, exec) => {
11560
- if (Object.keys(args).some((key) => key !== "field" && key !== "value")) throw new TypeError("Unknown Profile update argument");
11839
+ if (Object.keys(args).some((key2) => key2 !== "field" && key2 !== "value")) throw new TypeError("Unknown Profile update argument");
11561
11840
  sessionOf(exec);
11562
11841
  return runtime.updatePersistentProfileFromTool(args.field, args.value);
11563
11842
  }
@@ -11771,9 +12050,9 @@ var LEIDEN_RESOLUTION = 0.55;
11771
12050
  function pairKey(left, right) {
11772
12051
  return left < right ? `${left}\0${right}` : `${right}\0${left}`;
11773
12052
  }
11774
- function pairIds(key) {
11775
- const separator = key.indexOf("\0");
11776
- return [key.slice(0, separator), key.slice(separator + 1)];
12053
+ function pairIds(key2) {
12054
+ const separator = key2.indexOf("\0");
12055
+ return [key2.slice(0, separator), key2.slice(separator + 1)];
11777
12056
  }
11778
12057
  function semanticTags(node) {
11779
12058
  return Array.isArray(node.tags) ? [...new Set(node.tags.map((tag) => tag.trim()).filter(Boolean))] : [];
@@ -11804,8 +12083,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
11804
12083
  const structuralWeights = /* @__PURE__ */ new Map();
11805
12084
  const addStructuralWeight = (left, right, weight) => {
11806
12085
  if (left === right || !nodeMap.has(left) || !nodeMap.has(right) || weight <= 0) return;
11807
- const key = pairKey(left, right);
11808
- structuralWeights.set(key, (structuralWeights.get(key) ?? 0) + weight);
12086
+ const key2 = pairKey(left, right);
12087
+ structuralWeights.set(key2, (structuralWeights.get(key2) ?? 0) + weight);
11809
12088
  };
11810
12089
  for (const edge of rawEdges.filter((edge2) => edge2.status === "active")) {
11811
12090
  addStructuralWeight(edge.fromNodeId, edge.toNodeId, 3 * Math.max(0.25, Number(edge.confidence) || 0.8));
@@ -11825,8 +12104,8 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
11825
12104
  const graph = createGraph({ multigraph: false });
11826
12105
  for (const node of nodes) graph.addNode(node.id);
11827
12106
  const weightedDegree = new Map(nodes.map((node) => [node.id, 0]));
11828
- for (const [key, structuralWeight] of structuralWeights) {
11829
- const [leftId, rightId] = pairIds(key);
12107
+ for (const [key2, structuralWeight] of structuralWeights) {
12108
+ const [leftId, rightId] = pairIds(key2);
11830
12109
  const left = nodeMap.get(leftId);
11831
12110
  const right = nodeMap.get(rightId);
11832
12111
  const leftTags = new Set(semanticTags(left));
@@ -11851,9 +12130,9 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
11851
12130
  const connectedGroups = [];
11852
12131
  const isolatedIds = [];
11853
12132
  for (const community of partition.getCommunities().values()) {
11854
- const ids = community.map(String).sort();
11855
- const connected = ids.filter((id) => (weightedDegree.get(id) ?? 0) > 0);
11856
- const isolated = ids.filter((id) => (weightedDegree.get(id) ?? 0) === 0);
12133
+ const ids2 = community.map(String).sort();
12134
+ const connected = ids2.filter((id) => (weightedDegree.get(id) ?? 0) > 0);
12135
+ const isolated = ids2.filter((id) => (weightedDegree.get(id) ?? 0) === 0);
11857
12136
  if (connected.length > 0) connectedGroups.push(connected);
11858
12137
  isolatedIds.push(...isolated);
11859
12138
  }
@@ -11885,7 +12164,7 @@ function currentPluginVersion() {
11885
12164
  return "unknown";
11886
12165
  }
11887
12166
  var STRATAGATE_DSH_VERSION = currentPluginVersion();
11888
- var TOPIC_DIRECTORY_VIEW_VERSION = 5;
12167
+ var TOPIC_DIRECTORY_VIEW_VERSION = 7;
11889
12168
  function graphProjectionIsProcessing(job) {
11890
12169
  return derivationJobState(job, false) === "processing" || derivationJobState(job, false) === "retryable";
11891
12170
  }
@@ -11940,7 +12219,7 @@ function redactValue(value) {
11940
12219
  if (typeof value === "string") return redact(value);
11941
12220
  if (Array.isArray(value)) return value.map(redactValue);
11942
12221
  if (value && typeof value === "object") {
11943
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)]));
12222
+ return Object.fromEntries(Object.entries(value).map(([key2, item]) => [key2, redactValue(item)]));
11944
12223
  }
11945
12224
  return value;
11946
12225
  }
@@ -11949,17 +12228,17 @@ function redactedMessage(message, blockId) {
11949
12228
  const common = { ...base, content: redact(message.content), blockId };
11950
12229
  return toolCalls ? { ...common, toolCalls: redactValue(toolCalls) } : common;
11951
12230
  }
11952
- function sourceMessages(snapshot, ids) {
12231
+ function sourceMessages(snapshot, ids2) {
11953
12232
  const output = [];
11954
12233
  for (const block of snapshot.blocks) {
11955
12234
  for (const message of block.l5Raw) {
11956
- if (!ids || ids.has(message.id)) {
12235
+ if (!ids2 || ids2.has(message.id)) {
11957
12236
  output.push(redactedMessage(message, block.id));
11958
12237
  }
11959
12238
  }
11960
12239
  }
11961
12240
  for (const message of snapshot.openTail) {
11962
- if (!ids || ids.has(message.id)) {
12241
+ if (!ids2 || ids2.has(message.id)) {
11963
12242
  output.push(redactedMessage(message, null));
11964
12243
  }
11965
12244
  }
@@ -12205,15 +12484,15 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
12205
12484
  const failures = state.jobs.flatMap((job) => {
12206
12485
  if (job.superseded || job.projectorVersion !== MEMORY_TOPIC_PROJECTOR_VERSION || job.status !== "failed" || job.attempts < TOPIC_MAX_ATTEMPTS || !Object.entries({ ...job.sourceVersions, ...job.dependencyVersions }).every(([id, version]) => versions.get(id) === version)) return [];
12207
12486
  const diagnostics = safeTopicDiagnostics(job.diagnostics);
12208
- const ids = job.sourceEventIds.filter((id) => {
12209
- const key = relationKey(job.sectionBackfillTopicId, id, job.sourceVersions[id]);
12210
- if (!outstandingWork.has(key)) return false;
12211
- failedWork.add(key);
12487
+ const ids2 = memoryTopicUnfinishedJobEventIds(job, state, versions).filter((id) => {
12488
+ const key2 = relationKey(job.sectionBackfillTopicId, id, job.sourceVersions[id]);
12489
+ if (!outstandingWork.has(key2)) return false;
12490
+ failedWork.add(key2);
12212
12491
  return true;
12213
12492
  });
12214
- return ids.length > 0 ? [{
12493
+ return ids2.length > 0 ? [{
12215
12494
  jobId: job.id,
12216
- eventIds: ids,
12495
+ eventIds: ids2,
12217
12496
  attempts: job.attempts,
12218
12497
  // Topic failures store reason codes, never raw model output.
12219
12498
  diagnostics,
@@ -12224,7 +12503,7 @@ function topicDirectoryProjection(snapshot, agentMemoryWeight) {
12224
12503
  const total = (/* @__PURE__ */ new Set([...history.map(([id]) => id), ...backfillRelations.map(({ id }) => id)])).size;
12225
12504
  const failedRelations = backfillRelations.filter(({ topicId, id, version }) => failedWork.has(relationKey(topicId, id, version))).length;
12226
12505
  const bootstrap = frozen ? {
12227
- status: backfillRelations.length > 0 && outstandingWork.size === failedWork.size ? "failed" : Object.keys(state.rebuildVersions ?? {}).length > 0 || backfillRelations.length > 0 ? outstandingWork.size === failedWork.size ? "completed" : "running" : frozen.status,
12506
+ status: outstandingWork.size === 0 ? "completed" : outstandingWork.size === failedWork.size ? "failed" : frozen.status === "pending" ? "pending" : "running",
12228
12507
  total,
12229
12508
  completed: total - outstanding.size,
12230
12509
  failedEvents: new Set(failures.flatMap(({ eventIds }) => eventIds)).size,
@@ -12293,8 +12572,8 @@ function topicEventPageNumber(url, name2, fallback) {
12293
12572
  }
12294
12573
  async function topicEvents(runtime, url) {
12295
12574
  const allowed = /* @__PURE__ */ new Set(["namespace", "topicId", "sectionKey", "offset", "limit", "expectedRevision"]);
12296
- for (const key of url.searchParams.keys()) {
12297
- if (!allowed.has(key) || url.searchParams.getAll(key).length !== 1) {
12575
+ for (const key2 of url.searchParams.keys()) {
12576
+ if (!allowed.has(key2) || url.searchParams.getAll(key2).length !== 1) {
12298
12577
  throw new AdminHttpError(400, "Unknown or repeated topic Event parameter");
12299
12578
  }
12300
12579
  }
@@ -12321,29 +12600,29 @@ async function topicEvents(runtime, url) {
12321
12600
  revision: data.revision
12322
12601
  });
12323
12602
  }
12324
- let ids;
12603
+ let ids2;
12325
12604
  if (topicId === "pending") {
12326
- if (sectionKey === null) ids = projection.pending.map(({ id }) => id);
12605
+ if (sectionKey === null) ids2 = projection.pending.map(({ id }) => id);
12327
12606
  else if (sectionKey.startsWith("failure:")) {
12328
12607
  const failure = projection.failures.find(({ jobId }) => jobId === sectionKey.slice("failure:".length));
12329
12608
  if (!failure) throw new AdminHttpError(404, "Unknown or unavailable Topic failure");
12330
- ids = failure.eventIds;
12609
+ ids2 = failure.eventIds;
12331
12610
  } else throw new AdminHttpError(404, "Unknown or unavailable pending Topic section");
12332
12611
  } else {
12333
12612
  const topic = projection.chapters.find(({ id }) => id === topicId);
12334
12613
  if (!topic) throw new AdminHttpError(404, "Unknown or unavailable memory topic");
12335
- if (sectionKey === null) ids = topic.sourceEventIds;
12614
+ if (sectionKey === null) ids2 = topic.sourceEventIds;
12336
12615
  else if (sectionKey === "uncovered") {
12337
12616
  const covered = new Set(memoryTopicSections(topic).flatMap((section) => section.sourceEventIds));
12338
- ids = topic.sourceEventIds.filter((id) => !covered.has(id));
12617
+ ids2 = topic.sourceEventIds.filter((id) => !covered.has(id));
12339
12618
  } else {
12340
12619
  const section = memoryTopicSections(topic).find((candidate) => candidate.key === sectionKey);
12341
12620
  if (!section) throw new AdminHttpError(404, "Unknown or unavailable memory topic section");
12342
- ids = section.sourceEventIds;
12621
+ ids2 = section.sourceEventIds;
12343
12622
  }
12344
12623
  }
12345
12624
  const byId = new Map(projection.visibleEvents.map((event) => [event.id, event]));
12346
- const sources2 = [...new Set(ids)].flatMap((id) => byId.get(id) ?? []);
12625
+ const sources2 = [...new Set(ids2)].flatMap((id) => byId.get(id) ?? []);
12347
12626
  const page = sources2.slice(offset, offset + limit);
12348
12627
  return {
12349
12628
  namespace,
@@ -12358,13 +12637,13 @@ async function topicEvents(runtime, url) {
12358
12637
  };
12359
12638
  }
12360
12639
  async function overview(runtime, cachedEntries) {
12361
- const entries = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
12640
+ const entries2 = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
12362
12641
  namespace,
12363
12642
  revision: 0,
12364
12643
  snapshot: await runtime.adminSnapshot(namespace)
12365
12644
  })));
12366
12645
  const rows = [];
12367
- for (const { namespace, snapshot } of entries) {
12646
+ for (const { namespace, snapshot } of entries2) {
12368
12647
  if (!snapshot) continue;
12369
12648
  const recovered = recoverSnapshotView(snapshot);
12370
12649
  const blockDetails = (blockIds) => [...new Set(blockIds)].flatMap((blockId) => {
@@ -12433,7 +12712,12 @@ async function overview(runtime, cachedEntries) {
12433
12712
  sourceEventIds: job.sourceEventIds
12434
12713
  };
12435
12714
  };
12436
- const topicJobs = (snapshot.memoryTopicState?.jobs ?? []).filter((job) => !job.superseded);
12715
+ const topicEvents2 = [...snapshot.events, ...(runtime.adminAgentMemoryRetrievalWeight?.() ?? 1) > 0 ? snapshot.agentEvents : []].filter((event) => event.status === "active" || event.status === "superseded");
12716
+ const topicDirectory2 = new MemoryTopicDirectory();
12717
+ topicDirectory2.restore(snapshot.memoryTopicState);
12718
+ const topicState = topicDirectory2.snapshot(topicEvents2);
12719
+ const topicVersions = new Map(topicEvents2.map((event) => [event.id, memoryTopicEventFingerprint(event)]));
12720
+ const topicJobs = topicState.jobs.filter((job) => !job.superseded && (job.status === "completed" || memoryTopicUnfinishedJobEventIds(job, topicState, topicVersions).length > 0));
12437
12721
  const describeTopicJob = (job) => {
12438
12722
  const diagnostics = safeTopicDiagnostics(job.diagnostics);
12439
12723
  const lastError = diagnostics ? `${diagnostics.category}: ${diagnostics.reason}` : ["timeout", "source-changed", "invalid-output", "worker-failed"].includes(job.lastError ?? "") ? job.lastError : "worker-failed";
@@ -12448,7 +12732,7 @@ async function overview(runtime, cachedEntries) {
12448
12732
  lastError,
12449
12733
  lastErrorFull: lastError,
12450
12734
  diagnostics,
12451
- eventCount: job.sourceEventIds.length,
12735
+ eventCount: job.status === "completed" ? job.sourceEventIds.length : memoryTopicUnfinishedJobEventIds(job, topicState, topicVersions).length,
12452
12736
  blockIds: [],
12453
12737
  threadIds: [],
12454
12738
  blockDetails: []
@@ -12752,12 +13036,12 @@ function recoverSnapshotView(snapshot) {
12752
13036
  if (turn !== null) receiptTurns.set(receiptTurnKey(threadId, receipt.createdAt), turn);
12753
13037
  const currentActivity = receiptActivity.get(threadId);
12754
13038
  if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
12755
- const key = timestampKey(receipt.createdAt);
12756
- const candidates = receiptCandidates.get(key) ?? /* @__PURE__ */ new Set();
13039
+ const key2 = timestampKey(receipt.createdAt);
13040
+ const candidates = receiptCandidates.get(key2) ?? /* @__PURE__ */ new Set();
12757
13041
  candidates.add(threadId);
12758
- receiptCandidates.set(key, candidates);
13042
+ receiptCandidates.set(key2, candidates);
12759
13043
  }
12760
- const exactThreadAt = new Map([...receiptCandidates].filter(([, ids]) => ids.size === 1).map(([createdAt, ids]) => [createdAt, [...ids][0]]));
13044
+ const exactThreadAt = new Map([...receiptCandidates].filter(([, ids2]) => ids2.size === 1).map(([createdAt, ids2]) => [createdAt, [...ids2][0]]));
12761
13045
  const recoverMessages = (messages) => {
12762
13046
  let precedingThreadId = null;
12763
13047
  return messages.map((message) => {
@@ -12778,11 +13062,11 @@ function recoverSnapshotView(snapshot) {
12778
13062
  messages.push(item.message);
12779
13063
  groups.set(item.threadId, messages);
12780
13064
  }
12781
- const entries = [...groups];
12782
- for (const [threadId, messages] of entries) {
12783
- const virtual = !source.threadId && (entries.length > 1 || threadId !== LEGACY_THREAD_ID);
13065
+ const entries2 = [...groups];
13066
+ for (const [threadId, messages] of entries2) {
13067
+ const virtual = !source.threadId && (entries2.length > 1 || threadId !== LEGACY_THREAD_ID);
12784
13068
  blocks.push({
12785
- id: entries.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
13069
+ id: entries2.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
12786
13070
  source,
12787
13071
  threadId,
12788
13072
  messages,
@@ -12831,12 +13115,12 @@ function virtualBlockLayers(block) {
12831
13115
  ]);
12832
13116
  }
12833
13117
  function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
12834
- const ids = /* @__PURE__ */ new Set([
13118
+ const ids2 = /* @__PURE__ */ new Set([
12835
13119
  ...view.blocks.map((block) => block.threadId),
12836
13120
  ...view.openMessages.map(({ threadId }) => threadId),
12837
13121
  ...view.receiptThreads.values()
12838
13122
  ]);
12839
- return [...ids].map((id) => {
13123
+ return [...ids2].map((id) => {
12840
13124
  const blocks = view.blocks.filter((block) => block.threadId === id);
12841
13125
  const messages = [
12842
13126
  ...blocks.flatMap((block) => block.messages),
@@ -13035,12 +13319,12 @@ async function sources(runtime, url) {
13035
13319
  const blockId = url.searchParams.get("blockId");
13036
13320
  let events = [];
13037
13321
  let elements = [];
13038
- let ids = /* @__PURE__ */ new Set();
13322
+ let ids2 = /* @__PURE__ */ new Set();
13039
13323
  if (eventId) {
13040
13324
  const event = snapshot.events.find(({ id }) => id === eventId) ?? ((runtime.adminAgentMemoryRetrievalWeight?.() ?? 1) > 0 ? snapshot.agentEvents.find(({ id, status }) => id === eventId && (status === "active" || status === "superseded")) : void 0);
13041
13325
  if (!event) throw new AdminHttpError(404, `Unknown event: ${eventId}`);
13042
13326
  events = [event];
13043
- ids = new Set(event.sourceMessageIds);
13327
+ ids2 = new Set(event.sourceMessageIds);
13044
13328
  } else if (nodeId) {
13045
13329
  const rawNode = snapshot.graphNodes.find(({ id }) => id === nodeId);
13046
13330
  if (!rawNode) throw new AdminHttpError(404, `Unknown graph node: ${nodeId}`);
@@ -13062,7 +13346,7 @@ async function sources(runtime, url) {
13062
13346
  ...edges.flatMap(({ sourceEventIds }) => sourceEventIds)
13063
13347
  ]);
13064
13348
  events = snapshot.events.filter(({ id }) => eventIds.has(id));
13065
- ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
13349
+ ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
13066
13350
  return {
13067
13351
  namespace,
13068
13352
  node: view.node,
@@ -13073,28 +13357,28 @@ async function sources(runtime, url) {
13073
13357
  currentEdges: view.currentEdges,
13074
13358
  historicalEdges: view.historicalEdges,
13075
13359
  events: events.map((event) => eventSummary(event)),
13076
- messages: sourceMessages(snapshot, ids)
13360
+ messages: sourceMessages(snapshot, ids2)
13077
13361
  };
13078
13362
  } else if (elementId) {
13079
13363
  const element = snapshot.elements.find(({ id }) => id === elementId);
13080
13364
  if (!element) throw new AdminHttpError(404, `Unknown element: ${elementId}`);
13081
13365
  elements = [element];
13082
13366
  events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
13083
- ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
13367
+ ids2 = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
13084
13368
  } else if (blockId) {
13085
13369
  const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
13086
13370
  const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
13087
13371
  if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
13088
13372
  const messages = displayBlock?.messages ?? block.l5Raw;
13089
- ids = new Set(messages.map(({ id }) => id));
13090
- events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids.has(id))));
13373
+ ids2 = new Set(messages.map(({ id }) => id));
13374
+ events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids2.has(id))));
13091
13375
  const eventIds = new Set(events.map(({ id }) => id));
13092
13376
  elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
13093
13377
  return {
13094
13378
  namespace,
13095
13379
  events: events.map((event) => eventSummary(event)),
13096
13380
  elements: elements.map(elementSummary),
13097
- messages: sourceMessages(snapshot, ids),
13381
+ messages: sourceMessages(snapshot, ids2),
13098
13382
  layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
13099
13383
  virtual: displayBlock?.virtual ?? false
13100
13384
  };
@@ -13109,7 +13393,7 @@ async function sources(runtime, url) {
13109
13393
  } : {},
13110
13394
  elements: elements.map(elementSummary),
13111
13395
  blocks: events.map((event) => snapshot.blocks.find(({ id }) => id === event.sourceBlockId)).filter((block) => Boolean(block)).map((block) => ({ id: block.id, title: block.l0Title, createdAt: block.createdAt })),
13112
- messages: sourceMessages(snapshot, ids)
13396
+ messages: sourceMessages(snapshot, ids2)
13113
13397
  };
13114
13398
  }
13115
13399
  async function expandBlock(runtime, url) {
@@ -13207,20 +13491,20 @@ async function audit(runtime, url) {
13207
13491
  }
13208
13492
  function requestHeader(req, name2) {
13209
13493
  const headers = req.headers ?? {};
13210
- const key = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
13211
- const value = key ? headers[key] : void 0;
13494
+ const key2 = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
13495
+ const value = key2 ? headers[key2] : void 0;
13212
13496
  return Array.isArray(value) ? value.join(", ") : value ?? "";
13213
13497
  }
13214
13498
  async function dashboard(runtime, url, ifNoneMatch) {
13215
- const entries = await runtime.adminSnapshotEntries();
13499
+ const entries2 = await runtime.adminSnapshotEntries();
13216
13500
  const requestedNamespace = url.searchParams.get("namespace")?.trim() ?? "";
13217
- const selected = entries.find(({ namespace }) => namespace === requestedNamespace) ?? entries[0];
13501
+ const selected = entries2.find(({ namespace }) => namespace === requestedNamespace) ?? entries2[0];
13218
13502
  const threadId = url.searchParams.get("threadId")?.trim() ?? "";
13219
- const revisionKey = entries.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
13503
+ const revisionKey = entries2.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
13220
13504
  const agentMemoryWeight = runtime.adminAgentMemoryRetrievalWeight?.() ?? 1;
13221
13505
  const etag = `"${createHash4("sha256").update(`${STRATAGATE_DSH_VERSION}\0${TOPIC_DIRECTORY_VIEW_VERSION}\0${revisionKey}\0${selected?.namespace ?? ""}\0${threadId}\0${agentMemoryWeight}`).digest("base64url").slice(0, 24)}"`;
13222
13506
  if (ifNoneMatch.split(",").map((value) => value.trim()).includes(etag)) return { etag, notModified: true };
13223
- const overviewValue = await overview(runtime, entries);
13507
+ const overviewValue = await overview(runtime, entries2);
13224
13508
  if (!selected) {
13225
13509
  return { etag, notModified: false, body: { namespace: null, overview: overviewValue, data: null, processing: false } };
13226
13510
  }
@@ -13358,8 +13642,8 @@ function isCompleteAnswer(data) {
13358
13642
  if (!data.message.content.some((block) => block.type === "text" && block.text.trim().length > 0)) return false;
13359
13643
  const stream = data.stream ?? [];
13360
13644
  for (let index = stream.length - 1; index >= 0; index--) {
13361
- const record2 = stream[index];
13362
- if (record2.type === "chunk" && record2.chunk.type === "finish") return record2.chunk.reason.kind === "stop";
13645
+ const record3 = stream[index];
13646
+ if (record3.type === "chunk" && record3.chunk.type === "finish") return record3.chunk.reason.kind === "stop";
13363
13647
  }
13364
13648
  return true;
13365
13649
  }
@@ -13863,20 +14147,22 @@ async function apply(ctx, config) {
13863
14147
  }
13864
14148
  const session = context.agent?.session;
13865
14149
  if (!session) return { ...assembled, contexts };
14150
+ let autoMemory;
13866
14151
  try {
13867
- const directory = await runtime.buildMemoryDirectory(session);
13868
- if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
14152
+ const text3 = await runtime.buildAutoContext(session);
14153
+ autoMemory = { name: "stratagate:auto-memory", text: text3 };
13869
14154
  } catch (error) {
13870
- ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
14155
+ ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
13871
14156
  runtime.notePluginError(session, error);
13872
14157
  }
13873
14158
  try {
13874
- const text3 = await runtime.buildAutoContext(session);
13875
- contexts.push({ name: "stratagate:auto-memory", text: text3 });
14159
+ const directory = await runtime.buildMemoryDirectory(session);
14160
+ if (directory) contexts.push({ name: "stratagate:memory-directory", text: directory });
13876
14161
  } catch (error) {
13877
- ctx.logger.warn(`stratagate-memory auto-context failed: ${renderError(error)}`);
14162
+ ctx.logger.warn(`stratagate-memory directory failed: ${renderError(error)}`);
13878
14163
  runtime.notePluginError(session, error);
13879
14164
  }
14165
+ if (autoMemory) contexts.push(autoMemory);
13880
14166
  const feedbackSuggestion = runtime.takeFeedbackSuggestion(session);
13881
14167
  if (feedbackSuggestion) contexts.push({ name: "stratagate:feedback-suggestion", text: feedbackSuggestion });
13882
14168
  return { ...assembled, contexts };