stratagate-dsh 0.2.55 → 0.2.57

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/index.js CHANGED
@@ -2986,6 +2986,35 @@ var StrataGate = class _StrataGate {
2986
2986
  listSummaryJobs() {
2987
2987
  return [...this.summaryJobs.values()];
2988
2988
  }
2989
+ /** Give one terminally failed Block Summary job a fresh, user-requested retry budget. */
2990
+ async retryBlockSummary(id) {
2991
+ const blockId = id.trim();
2992
+ if (!blockId) throw new TypeError("Block id must not be empty");
2993
+ const block = this.blocks.find((candidate) => candidate.id === blockId);
2994
+ if (!block) throw new Error(`Unknown block: ${blockId}`);
2995
+ if (block.processingStatus !== "pending") throw new Error(`Block ${blockId} is already ready`);
2996
+ await this.commitMutation(() => {
2997
+ const job = this.summaryJobs.get(blockId);
2998
+ if (!job) throw new Error(`Missing summary job for block: ${blockId}`);
2999
+ if (job.status !== "failed" || job.nextRetryAt !== null) {
3000
+ throw new Error(`Block ${blockId} Summary has not exhausted automatic retries`);
3001
+ }
3002
+ this.summaryJobs.set(blockId, {
3003
+ ...job,
3004
+ status: "pending",
3005
+ attempts: 0,
3006
+ lastError: null,
3007
+ nextRetryAt: null,
3008
+ updatedAt: toUtc8Iso(this.now())
3009
+ });
3010
+ });
3011
+ const extractedEvents = await this.processBlock(block, { retryFailed: false });
3012
+ const processed = this.blocks.find((candidate) => candidate.id === blockId);
3013
+ const readyBlocks = processed?.processingStatus === "ready" ? [processed] : [];
3014
+ const projectedElements = await this.projectEligibleElements() ?? [];
3015
+ await this.projectEligibleGraph();
3016
+ return { sealedBlocks: [], readyBlocks, extractedEvents, projectedElements };
3017
+ }
2989
3018
  listElementProjectionJobs() {
2990
3019
  return [...this.elementProjectionJobs.values()];
2991
3020
  }
@@ -5566,6 +5595,8 @@ var StrataGateRuntime = class {
5566
5595
  migrationTimers = /* @__PURE__ */ new Map();
5567
5596
  derivationTimers = /* @__PURE__ */ new Map();
5568
5597
  derivationRuns = /* @__PURE__ */ new Map();
5598
+ knownSessions = /* @__PURE__ */ new Map();
5599
+ pendingSurfaceSync = /* @__PURE__ */ new Map();
5569
5600
  adminSnapshotCache = /* @__PURE__ */ new Map();
5570
5601
  externalImportRuns = /* @__PURE__ */ new Map();
5571
5602
  feedbackDrafts = /* @__PURE__ */ new Map();
@@ -5936,6 +5967,33 @@ var StrataGateRuntime = class {
5936
5967
  }
5937
5968
  return changed;
5938
5969
  }
5970
+ /** Finish native-surface replacement when an admin retry ran without the target session loaded. */
5971
+ async syncPendingRetrySurface(session, memory) {
5972
+ const namespace = this.namespaceFor(session);
5973
+ const threadId = String(session.id);
5974
+ const pending = [...this.pendingSurfaceSync.entries()].filter(([, item]) => item.namespace === namespace);
5975
+ if (pending.length === 0) return;
5976
+ const contexts = new Map(memory.getBlockContext(threadId).map((context) => [context.id, context]));
5977
+ const existing = currentBlockSurfaceMessages(session);
5978
+ let changed = false;
5979
+ for (const [key, { blockId }] of pending) {
5980
+ if (existing.has(blockId)) {
5981
+ this.pendingSurfaceSync.delete(key);
5982
+ continue;
5983
+ }
5984
+ const block = memory.listBlocks().find((candidate) => candidate.id === blockId && candidate.threadId === threadId);
5985
+ const context = contexts.get(blockId);
5986
+ if (!block || block.processingStatus !== "ready" || !context) continue;
5987
+ try {
5988
+ this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block));
5989
+ this.pendingSurfaceSync.delete(key);
5990
+ changed = true;
5991
+ } catch (error) {
5992
+ this.onIngestError(error);
5993
+ }
5994
+ }
5995
+ if (changed) await this.flushNativeSession(session);
5996
+ }
5939
5997
  // Keep the ingestion error for callers that explicitly require a flushed run.
5940
5998
  async settleIngestion() {
5941
5999
  await this.ingestTail;
@@ -6455,6 +6513,44 @@ var StrataGateRuntime = class {
6455
6513
  });
6456
6514
  return update;
6457
6515
  }
6516
+ async adminRetryBlockSummary(namespace, id) {
6517
+ const key = namespace.trim();
6518
+ const blockId = id.trim();
6519
+ if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
6520
+ if (!blockId) throw new TypeError("Block id must not be empty");
6521
+ const update = this.settingsTail.catch(() => {
6522
+ }).then(async () => {
6523
+ await this.flush();
6524
+ const { memory, owned } = await this.openAdminMemory(key, { derivation: true });
6525
+ try {
6526
+ await this.refreshExternalImportMemory(key, memory);
6527
+ const block = memory.listBlocks().find((candidate) => candidate.id === blockId);
6528
+ if (!block) throw new Error(`Unknown block: ${blockId}`);
6529
+ const threadId = block.threadId ?? `admin-summary-retry:${blockId}`;
6530
+ const session = block.threadId ? this.knownSessions.get(block.threadId)?.deref() : void 0;
6531
+ const resumed = session && this.namespaceFor(session) === key ? await this.models.run(session, () => memory.retryBlockSummary(blockId)) : await this.models.runDetached(threadId, () => memory.retryBlockSummary(blockId));
6532
+ await this.persistSuccessfulResponses(memory);
6533
+ if (resumed.readyBlocks.some(({ id: readyId }) => readyId === blockId) && block.threadId) {
6534
+ this.pendingSurfaceSync.set(`${key}\0${blockId}`, { namespace: key, blockId });
6535
+ if (session && this.namespaceFor(session) === key) await this.syncPendingRetrySurface(session, memory);
6536
+ }
6537
+ const summaryJob = memory.listSummaryJobs().find(({ blockId: candidateId }) => candidateId === blockId);
6538
+ return {
6539
+ blockId,
6540
+ processingStatus: block.processingStatus,
6541
+ ready: block.processingStatus === "ready",
6542
+ summaryJob: summaryJob ?? null,
6543
+ surfaceUpdated: !this.pendingSurfaceSync.has(`${key}\0${blockId}`)
6544
+ };
6545
+ } finally {
6546
+ if (owned) await memory.close();
6547
+ }
6548
+ });
6549
+ this.settingsTail = update.then(() => {
6550
+ }, () => {
6551
+ });
6552
+ return update;
6553
+ }
6458
6554
  async applyBlockDecayLambda(value) {
6459
6555
  await this.flush();
6460
6556
  this.blockDecayLambda = value;
@@ -6517,8 +6613,9 @@ var StrataGateRuntime = class {
6517
6613
  }
6518
6614
  }
6519
6615
  }
6520
- space(session) {
6616
+ async space(session) {
6521
6617
  const namespace = this.namespaceFor(session);
6618
+ this.knownSessions.set(String(session.id), new WeakRef(session));
6522
6619
  this.rememberWorkspace(namespace, session.header.cwd);
6523
6620
  let opening = this.spaces.get(namespace);
6524
6621
  if (!opening) {
@@ -6531,20 +6628,20 @@ var StrataGateRuntime = class {
6531
6628
  extractor: this.models.extractor,
6532
6629
  graphProjector: this.models.graphProjector,
6533
6630
  disableElementProjection: true
6534
- }).then(async (memory) => {
6631
+ }).then(async (memory2) => {
6535
6632
  try {
6536
6633
  try {
6537
- await memory.resumePendingWork({ deferDerivation: true, threadId: String(session.id) });
6538
- const contexts = memory.getBlockContext(String(session.id));
6634
+ await memory2.resumePendingWork({ deferDerivation: true, threadId: String(session.id) });
6635
+ const contexts = memory2.getBlockContext(String(session.id));
6539
6636
  this.syncDecayedBlockSurface(session, contexts);
6540
6637
  } finally {
6541
- await this.persistSuccessfulResponses(memory);
6638
+ await this.persistSuccessfulResponses(memory2);
6542
6639
  }
6543
- this.scheduleGraphMigration(session, memory);
6544
- this.scheduleBlockDerivation(session, memory);
6545
- return memory;
6640
+ this.scheduleGraphMigration(session, memory2);
6641
+ this.scheduleBlockDerivation(session, memory2);
6642
+ return memory2;
6546
6643
  } catch (error) {
6547
- await memory.close().catch(() => {
6644
+ await memory2.close().catch(() => {
6548
6645
  });
6549
6646
  throw error;
6550
6647
  }
@@ -6554,7 +6651,9 @@ var StrataGateRuntime = class {
6554
6651
  if (this.spaces.get(namespace) === opening) this.spaces.delete(namespace);
6555
6652
  });
6556
6653
  }
6557
- return opening;
6654
+ const memory = await opening;
6655
+ await this.syncPendingRetrySurface(session, memory);
6656
+ return memory;
6558
6657
  }
6559
6658
  async searchGraph(session, query, limit = 8) {
6560
6659
  await this.flush();
@@ -6658,7 +6757,7 @@ var StrataGateRuntime = class {
6658
6757
  noteNewCoreJobFailures(session, before, memory) {
6659
6758
  if ([...this.failedCoreJobs(memory)].some((failure) => !before.has(failure))) this.notePluginError(session);
6660
6759
  }
6661
- async openAdminMemory(namespace) {
6760
+ async openAdminMemory(namespace, options = {}) {
6662
6761
  const active = this.spaces.get(namespace);
6663
6762
  if (active) return { memory: await active, owned: false };
6664
6763
  if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
@@ -6670,6 +6769,10 @@ var StrataGateRuntime = class {
6670
6769
  namespace,
6671
6770
  blockTurnSize: this.blockTurnSize,
6672
6771
  blockDecayLambda: this.blockDecayLambda,
6772
+ ...options.derivation ? {
6773
+ summarizer: this.models.summarizer,
6774
+ extractor: this.models.extractor
6775
+ } : {},
6673
6776
  graphProjector: this.models.graphProjector,
6674
6777
  disableElementProjection: true
6675
6778
  }),
@@ -7315,7 +7418,7 @@ function clusterKnowledgeGraph(rawNodes, rawEdges) {
7315
7418
  }
7316
7419
 
7317
7420
  // src/web.ts
7318
- var STRATAGATE_DSH_VERSION = "0.2.52";
7421
+ var STRATAGATE_DSH_VERSION = "0.2.57";
7319
7422
  var LEGACY_THREAD_ID = "__legacy__";
7320
7423
  var nodeRequire = createRequire(import.meta.url);
7321
7424
  function installedPackageVersion(names) {
@@ -7458,9 +7561,24 @@ async function overview(runtime, cachedEntries) {
7458
7561
  const rows = [];
7459
7562
  for (const { namespace, snapshot } of entries) {
7460
7563
  if (!snapshot) continue;
7461
- const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
7564
+ const failedJobs = snapshot.summaryJobs.filter(({ status }) => status === "failed").length + snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
7462
7565
  const processingJobs = snapshot.summaryJobs.filter(({ status, nextRetryAt }) => status === "pending" || status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.extractionJobs.filter(({ status, nextRetryAt }) => status === "running" || status === "failed" && nextRetryAt !== null).length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
7463
7566
  const failedJobDetails = [
7567
+ ...snapshot.summaryJobs.filter(({ status }) => status === "failed").map((job) => {
7568
+ const block = snapshot.blocks.find(({ id }) => id === job.blockId);
7569
+ return {
7570
+ id: job.blockId,
7571
+ kind: "block-summary",
7572
+ attempts: job.attempts,
7573
+ nextRetryAt: job.nextRetryAt,
7574
+ lastError: job.lastError?.slice(0, 500) ?? null,
7575
+ lastErrorFull: job.lastError,
7576
+ updatedAt: job.updatedAt,
7577
+ threadId: block?.threadId ?? null,
7578
+ sequence: block?.sequence ?? null,
7579
+ turnRange: block ? [block.startTurn, block.endTurn] : null
7580
+ };
7581
+ }),
7464
7582
  ...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => ({
7465
7583
  id: job.blockId,
7466
7584
  kind: "event-extraction",
@@ -7855,7 +7973,7 @@ async function memories(runtime, url) {
7855
7973
  const failedProjection = projections.find(({ status: status2 }) => status2 === "failed");
7856
7974
  const pendingProjection = projections.some(({ status: status2 }) => status2 === "pending" || status2 === "running");
7857
7975
  const needsExtraction = source.shouldExtract === true;
7858
- const status = extraction?.status === "failed" || failedProjection ? "failed" : extraction?.status === "succeeded" || extraction?.status === "skipped" ? pendingProjection ? "processing" : "organized" : needsExtraction ? "waiting" : "organized";
7976
+ const status = summary?.status === "failed" ? "failed" : summary?.status === "pending" || summary?.status === "running" || !summary && source.processingStatus === "pending" ? "processing" : extraction?.status === "failed" || failedProjection ? "failed" : extraction?.status === "succeeded" || extraction?.status === "skipped" ? pendingProjection ? "processing" : "organized" : needsExtraction ? "waiting" : "organized";
7859
7977
  const blockPosition = scopedBlocks.findIndex(({ id }) => id === block.id) + 1;
7860
7978
  const latestBlockPosition = scopedBlocks.length;
7861
7979
  const currentLevel = getDecayedBlockLevel(
@@ -8013,6 +8131,20 @@ async function expandBlock(runtime, url) {
8013
8131
  if (!/^L?[0-5]$/i.test(target)) throw new AdminHttpError(400, "level must be L0 through L5");
8014
8132
  return runtime.adminExpandBlock(namespace, blockId, target);
8015
8133
  }
8134
+ async function retryBlockSummary(runtime, url) {
8135
+ const namespace = url.searchParams.get("namespace")?.trim() ?? "";
8136
+ const blockId = url.searchParams.get("blockId")?.trim() ?? "";
8137
+ if (!namespace) throw new AdminHttpError(400, "namespace is required");
8138
+ if (!blockId) throw new AdminHttpError(400, "blockId is required");
8139
+ if (blockId.startsWith("virtual:")) throw new AdminHttpError(409, "Recovered legacy fragments cannot be retried");
8140
+ const snapshot = await requiredSnapshot(runtime, namespace);
8141
+ const job = snapshot.summaryJobs.find(({ blockId: candidateId }) => candidateId === blockId);
8142
+ if (!job) throw new AdminHttpError(404, `Unknown Block Summary job: ${blockId}`);
8143
+ if (job.status !== "failed" || job.nextRetryAt !== null) {
8144
+ throw new AdminHttpError(409, "Block Summary is not in a terminal failed state");
8145
+ }
8146
+ return runtime.adminRetryBlockSummary(namespace, blockId);
8147
+ }
8016
8148
  function receiptSources(snapshot, receipt) {
8017
8149
  const events = snapshot.events.filter(({ id }) => receipt.eventIds.includes(id));
8018
8150
  const elements = snapshot.elements.filter(({ id }) => receipt.elementIds.includes(id));
@@ -8125,6 +8257,9 @@ async function handleAdminRequest(runtime, req, res) {
8125
8257
  } else if (path === "/api/stratagate/blocks/expand") {
8126
8258
  if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate Block expansion requires PATCH");
8127
8259
  sendJson(res, 200, await expandBlock(runtime, url));
8260
+ } else if (path === "/api/stratagate/blocks/retry-summary") {
8261
+ if (req.method !== "POST") throw new AdminHttpError(405, "StrataGate Block Summary retry requires POST");
8262
+ sendJson(res, 200, await retryBlockSummary(runtime, url));
8128
8263
  } else if (path === "/api/stratagate/import") {
8129
8264
  if (req.method === "GET") {
8130
8265
  const operation = url.searchParams.get("operation");