stratagate-dsh 0.2.36 → 0.2.37

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
@@ -15,7 +15,8 @@ var Config = z.object({
15
15
  ingestSubagents: z.boolean().default(false),
16
16
  provider: z.string(),
17
17
  model: z.string(),
18
- maxOutputTokens: z.natural().min(256).default(1e4)
18
+ maxOutputTokens: z.natural().min(256).default(2048),
19
+ structuredTaskTimeoutMs: z.natural().min(1e3).default(45e3)
19
20
  });
20
21
  function resolveConfig(config) {
21
22
  const database = config.database?.trim() ?? "";
@@ -36,7 +37,8 @@ function resolveConfig(config) {
36
37
  blockDecayLambda: Math.max(0, config.blockDecayLambda ?? 0.3),
37
38
  ingestSubagents: config.ingestSubagents ?? false,
38
39
  ...provider && model ? { provider, model } : {},
39
- maxOutputTokens: Math.max(256, Math.floor(config.maxOutputTokens ?? 1e4))
40
+ maxOutputTokens: Math.max(256, Math.floor(config.maxOutputTokens ?? 2048)),
41
+ structuredTaskTimeoutMs: Math.max(1e3, Math.floor(config.structuredTaskTimeoutMs ?? 45e3))
40
42
  };
41
43
  }
42
44
 
@@ -45,7 +47,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
45
47
  import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
46
48
  import { parameterSchemaSpecToJsonSchema, validateArgs } from "@deepseek-ai/dsh-tools";
47
49
 
48
- // ../../src/blocks.ts
50
+ // packages/core/src/blocks.ts
49
51
  var DEFAULT_BLOCK_TURN_SIZE = 12;
50
52
  var BLOCK_MAX_LEVEL = 5;
51
53
  var BLOCK_DECAY_LAMBDA = 0.3;
@@ -252,7 +254,7 @@ function deterministicBlockLayers(messages) {
252
254
  };
253
255
  }
254
256
 
255
- // ../../src/search.ts
257
+ // packages/core/src/search.ts
256
258
  var wordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
257
259
  function normalizeSearchText(value) {
258
260
  return value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/g, " ").trim();
@@ -329,7 +331,7 @@ function rrfRank(rankings) {
329
331
  return [...fused.values()].sort((left, right) => right.score - left.score || left.bestRank - right.bestRank || left.item.id.localeCompare(right.item.id)).map(({ item, score }) => ({ item, score }));
330
332
  }
331
333
 
332
- // ../../src/graph.ts
334
+ // packages/core/src/graph.ts
333
335
  var NODE_TYPES = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
334
336
  var STATUSES = /* @__PURE__ */ new Set(["active", "superseded", "disputed", "archived"]);
335
337
  function text(value, limit = 240) {
@@ -467,7 +469,7 @@ function applyGraphProjection(options) {
467
469
  return { nodeIds: [...touchedNodes], edgeIds: [...touchedEdges] };
468
470
  }
469
471
 
470
- // ../../src/events.ts
472
+ // packages/core/src/events.ts
471
473
  function normalizeStandardEventType(value) {
472
474
  const normalized = normalizeSearchText(value ?? "").replace(/[\s_-]+/g, "");
473
475
  const aliases = {
@@ -505,7 +507,7 @@ function normalizeStandardEventType(value) {
505
507
  return aliases[normalized] ?? "other";
506
508
  }
507
509
 
508
- // ../../src/external-memory.ts
510
+ // packages/core/src/external-memory.ts
509
511
  var EXTERNAL_MEMORY_EXPORT_SCHEMA = "stratagate.external-memory.v2";
510
512
  var EXTERNAL_MEMORY_DECIDER_PROMPT_ZH_CN = `
511
513
  \u4F60\u662F StrataGate \u7684\u5916\u90E8\u8BB0\u5FC6\u5408\u5E76\u88C1\u51B3\u5668\u3002\u8F93\u5165\u5305\u542B\u4E00\u4E2A\u5916\u90E8 candidate Event\uFF0C\u4EE5\u53CA\u672C\u5730\u68C0\u7D22\u5F97\u5230\u7684 Top-K existing Events\u3002\u53EA\u80FD\u4F9D\u636E candidate \u548C existing Events \u5224\u65AD\uFF0C\u4E0D\u5F97\u5F15\u5165\u5217\u8868\u4E4B\u5916\u7684\u672C\u5730\u4E8B\u4EF6\uFF0C\u4E5F\u4E0D\u5F97\u51ED\u5E38\u8BC6\u8865\u5168\u65F6\u95F4\u3002
@@ -1184,7 +1186,7 @@ function parseExternalMemoryExport(text3) {
1184
1186
  }
1185
1187
  var externalMemoryJsonExtractor = async ({ text: text3 }) => parseExternalMemoryExport(text3);
1186
1188
 
1187
- // ../../src/retrieval.ts
1189
+ // packages/core/src/retrieval.ts
1188
1190
  var RETRIEVAL_STRATEGIES = [
1189
1191
  "answer",
1190
1192
  "search_events",
@@ -1260,8 +1262,8 @@ function normalizeRetrievalAssessment(input, batchEvidenceRefs) {
1260
1262
  };
1261
1263
  }
1262
1264
 
1263
- // ../../src/storage.ts
1264
- var STRATAGATE_STORAGE_SCHEMA_VERSION = 8;
1265
+ // packages/core/src/storage.ts
1266
+ var STRATAGATE_STORAGE_SCHEMA_VERSION = 9;
1265
1267
  var KNOWLEDGE_GRAPH_PROJECTOR_VERSION = 1;
1266
1268
  var StorageConflictError = class extends Error {
1267
1269
  constructor(namespace, expectedRevision, actualRevision) {
@@ -1278,6 +1280,12 @@ var StorageConflictError = class extends Error {
1278
1280
  function cloneSnapshot(snapshot) {
1279
1281
  return structuredClone(snapshot);
1280
1282
  }
1283
+ function readyLegacyBlocks(blocks) {
1284
+ return blocks.map((block) => ({ ...structuredClone(block), processingStatus: "ready" }));
1285
+ }
1286
+ function legacyExtractionJobs(jobs) {
1287
+ return jobs.map((job) => ({ ...structuredClone(job), nextRetryAt: null }));
1288
+ }
1281
1289
  function emptyGraph() {
1282
1290
  return { graphNodes: [], graphEdges: [], graphProjectionJobs: [] };
1283
1291
  }
@@ -1285,7 +1293,7 @@ function migrateLegacyBlocks(blocks) {
1285
1293
  return blocks.map((block) => {
1286
1294
  const { pointerAnchorTurn, ...current } = block;
1287
1295
  const position = blocks.filter((candidate) => candidate.threadId === block.threadId && candidate.endTurn <= pointerAnchorTurn).length;
1288
- return { ...current, pointerAnchorBlockPosition: Math.max(1, position), lastLiftedBy: null };
1296
+ return { ...current, processingStatus: "ready", pointerAnchorBlockPosition: Math.max(1, position), lastLiftedBy: null };
1289
1297
  });
1290
1298
  }
1291
1299
  function normalizeSnapshot(value) {
@@ -1299,6 +1307,8 @@ function normalizeSnapshot(value) {
1299
1307
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1300
1308
  blockDecayLambda: BLOCK_DECAY_LAMBDA,
1301
1309
  blocks: migrateLegacyBlocks(legacy.blocks),
1310
+ summaryJobs: [],
1311
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1302
1312
  elements: [],
1303
1313
  elementProjectionJobs: [],
1304
1314
  usageReceipts: Array.isArray(legacy.usageReceipts) ? legacy.usageReceipts.map((receipt) => ({ ...receipt, elementIds: [] })) : [],
@@ -1312,6 +1322,8 @@ function normalizeSnapshot(value) {
1312
1322
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1313
1323
  blockDecayLambda: BLOCK_DECAY_LAMBDA,
1314
1324
  blocks: migrateLegacyBlocks(legacy.blocks),
1325
+ summaryJobs: [],
1326
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1315
1327
  ingestionReceipts: [],
1316
1328
  ...emptyGraph()
1317
1329
  };
@@ -1322,6 +1334,8 @@ function normalizeSnapshot(value) {
1322
1334
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1323
1335
  blockDecayLambda: BLOCK_DECAY_LAMBDA,
1324
1336
  blocks: migrateLegacyBlocks(legacy.blocks),
1337
+ summaryJobs: [],
1338
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1325
1339
  ...emptyGraph()
1326
1340
  };
1327
1341
  } else if (schemaVersion === 4) {
@@ -1331,6 +1345,8 @@ function normalizeSnapshot(value) {
1331
1345
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1332
1346
  blockDecayLambda: BLOCK_DECAY_LAMBDA,
1333
1347
  blocks: migrateLegacyBlocks(legacy.blocks),
1348
+ summaryJobs: [],
1349
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1334
1350
  ...emptyGraph()
1335
1351
  };
1336
1352
  } else if (schemaVersion === 5) {
@@ -1340,6 +1356,8 @@ function normalizeSnapshot(value) {
1340
1356
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1341
1357
  blockDecayLambda: BLOCK_DECAY_LAMBDA,
1342
1358
  blocks: migrateLegacyBlocks(legacy.blocks),
1359
+ summaryJobs: [],
1360
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1343
1361
  ...emptyGraph()
1344
1362
  };
1345
1363
  } else if (schemaVersion === 6) {
@@ -1347,12 +1365,30 @@ function normalizeSnapshot(value) {
1347
1365
  snapshot = {
1348
1366
  ...structuredClone(legacy),
1349
1367
  schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1350
- blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null })),
1368
+ blocks: readyLegacyBlocks(legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null }))),
1369
+ summaryJobs: [],
1370
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1351
1371
  ...emptyGraph()
1352
1372
  };
1353
1373
  } else if (schemaVersion === 7) {
1354
1374
  const legacy = value;
1355
- snapshot = { ...structuredClone(legacy), schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION, ...emptyGraph() };
1375
+ snapshot = {
1376
+ ...structuredClone(legacy),
1377
+ schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1378
+ blocks: readyLegacyBlocks(legacy.blocks),
1379
+ summaryJobs: [],
1380
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs),
1381
+ ...emptyGraph()
1382
+ };
1383
+ } else if (schemaVersion === 8) {
1384
+ const legacy = value;
1385
+ snapshot = {
1386
+ ...structuredClone(legacy),
1387
+ schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
1388
+ blocks: readyLegacyBlocks(legacy.blocks),
1389
+ summaryJobs: [],
1390
+ extractionJobs: legacyExtractionJobs(legacy.extractionJobs)
1391
+ };
1356
1392
  } else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
1357
1393
  snapshot = structuredClone(value);
1358
1394
  } else {
@@ -1367,7 +1403,7 @@ function normalizeSnapshot(value) {
1367
1403
  if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
1368
1404
  throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
1369
1405
  }
1370
- for (const key of ["openTail", "blocks", "events", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
1406
+ for (const key of ["openTail", "blocks", "summaryJobs", "events", "graphNodes", "graphEdges", "graphProjectionJobs", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
1371
1407
  if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
1372
1408
  }
1373
1409
  if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
@@ -1375,6 +1411,12 @@ function normalizeSnapshot(value) {
1375
1411
  event.temporal = { ...event.temporal, eventType: normalizeStandardEventType(event.temporal.eventType) };
1376
1412
  }
1377
1413
  for (const block of snapshot.blocks) {
1414
+ if (block.processingStatus !== "pending" && block.processingStatus !== "ready") {
1415
+ throw new TypeError("Invalid StrataGate snapshot: Block processingStatus must be pending or ready");
1416
+ }
1417
+ if (block.processingStatus === "ready" && (!block.l0Title || !block.l1Summary || !Array.isArray(block.l0Tags) || !Array.isArray(block.l2Keypoints) || typeof block.shouldExtract !== "boolean")) {
1418
+ throw new TypeError("Invalid StrataGate snapshot: ready Block must contain validated L0-L2 layers");
1419
+ }
1378
1420
  if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
1379
1421
  throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
1380
1422
  }
@@ -1391,7 +1433,7 @@ function assertValidSnapshot(value) {
1391
1433
  normalizeSnapshot(value);
1392
1434
  }
1393
1435
 
1394
- // ../../src/weights.ts
1436
+ // packages/core/src/weights.ts
1395
1437
  var BASE_DECAY = 0.15;
1396
1438
  var REHEARSAL_FACTOR = 1.5;
1397
1439
  function criticalityFloor(criticality) {
@@ -1410,7 +1452,7 @@ function memoryWeightAt(memory, currentTurn) {
1410
1452
  return memory.weight.pinned ? 1 : capped;
1411
1453
  }
1412
1454
 
1413
- // ../../src/elements.ts
1455
+ // packages/core/src/elements.ts
1414
1456
  var ELEMENT_TYPES = /* @__PURE__ */ new Set(["person", "project", "organization", "tool", "place"]);
1415
1457
  var FACT_MODES = /* @__PURE__ */ new Set(["state", "set", "relation"]);
1416
1458
  function compactText(value, limit) {
@@ -1533,10 +1575,10 @@ function elementViewAt(element, at) {
1533
1575
  return view;
1534
1576
  }
1535
1577
 
1536
- // ../../src/sqlite.ts
1578
+ // packages/core/src/sqlite.ts
1537
1579
  import { DatabaseSync } from "node:sqlite";
1538
1580
 
1539
- // ../../src/time.ts
1581
+ // packages/core/src/time.ts
1540
1582
  var UTC8_OFFSET_MS = 8 * 60 * 60 * 1e3;
1541
1583
  function toUtc8Iso(value = /* @__PURE__ */ new Date()) {
1542
1584
  const date = value instanceof Date ? new Date(value.getTime()) : new Date(value);
@@ -1547,7 +1589,7 @@ function nowUtc8() {
1547
1589
  return toUtc8Iso(/* @__PURE__ */ new Date());
1548
1590
  }
1549
1591
 
1550
- // ../../src/sqlite.ts
1592
+ // packages/core/src/sqlite.ts
1551
1593
  var SCHEMA = `
1552
1594
  CREATE TABLE IF NOT EXISTS memory_spaces (
1553
1595
  namespace TEXT PRIMARY KEY,
@@ -1580,6 +1622,7 @@ CREATE TABLE IF NOT EXISTS blocks (
1580
1622
  pointer_anchor_block_position INTEGER NOT NULL,
1581
1623
  last_lifted_at TEXT,
1582
1624
  last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent')),
1625
+ processing_status TEXT NOT NULL CHECK (processing_status IN ('pending', 'ready')),
1583
1626
  PRIMARY KEY (namespace, id),
1584
1627
  UNIQUE (namespace, sequence),
1585
1628
  FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
@@ -1704,6 +1747,19 @@ CREATE TABLE IF NOT EXISTS extraction_jobs (
1704
1747
  status TEXT NOT NULL,
1705
1748
  attempts INTEGER NOT NULL,
1706
1749
  last_error TEXT,
1750
+ next_retry_at TEXT,
1751
+ updated_at TEXT NOT NULL,
1752
+ PRIMARY KEY (namespace, block_id),
1753
+ FOREIGN KEY (namespace, block_id) REFERENCES blocks(namespace, id) ON DELETE CASCADE
1754
+ ) STRICT;
1755
+
1756
+ CREATE TABLE IF NOT EXISTS block_summary_jobs (
1757
+ namespace TEXT NOT NULL,
1758
+ block_id TEXT NOT NULL,
1759
+ status TEXT NOT NULL,
1760
+ attempts INTEGER NOT NULL,
1761
+ last_error TEXT,
1762
+ next_retry_at TEXT,
1707
1763
  updated_at TEXT NOT NULL,
1708
1764
  PRIMARY KEY (namespace, block_id),
1709
1765
  FOREIGN KEY (namespace, block_id) REFERENCES blocks(namespace, id) ON DELETE CASCADE
@@ -1844,11 +1900,14 @@ var SqliteStorage = class {
1844
1900
  startTurn: row.start_turn,
1845
1901
  endTurn: row.end_turn,
1846
1902
  createdAt: row.created_at,
1847
- shouldExtract: Boolean(row.should_extract),
1848
- l0Title: row.l0_title,
1849
- l0Tags: parseJson(row.l0_tags_json, "blocks.l0_tags_json"),
1850
- l1Summary: row.l1_summary,
1851
- l2Keypoints: parseJson(row.l2_keypoints_json, "blocks.l2_keypoints_json"),
1903
+ processingStatus: row.processing_status,
1904
+ ...row.l0_title ? {
1905
+ shouldExtract: Boolean(row.should_extract),
1906
+ l0Title: row.l0_title,
1907
+ l0Tags: parseJson(row.l0_tags_json, "blocks.l0_tags_json"),
1908
+ l1Summary: row.l1_summary,
1909
+ l2Keypoints: parseJson(row.l2_keypoints_json, "blocks.l2_keypoints_json")
1910
+ } : {},
1852
1911
  l3Condensed: row.l3_condensed,
1853
1912
  l4Readable: row.l4_readable,
1854
1913
  l5Raw: messagesByBlock.get(row.id) ?? [],
@@ -1858,6 +1917,17 @@ var SqliteStorage = class {
1858
1917
  lastLiftedAt: row.last_lifted_at,
1859
1918
  lastLiftedBy: row.last_lifted_by
1860
1919
  }));
1920
+ const summaryJobs = this.database.prepare(`
1921
+ SELECT block_id, status, attempts, last_error, next_retry_at, updated_at
1922
+ FROM block_summary_jobs WHERE namespace = ? ORDER BY block_id
1923
+ `).all(key).map((row) => ({
1924
+ blockId: row.block_id,
1925
+ status: row.status,
1926
+ attempts: row.attempts,
1927
+ lastError: row.last_error,
1928
+ nextRetryAt: row.next_retry_at,
1929
+ updatedAt: row.updated_at
1930
+ }));
1861
1931
  const sourceRows = this.database.prepare(`
1862
1932
  SELECT event_id, message_id, position FROM event_sources
1863
1933
  WHERE namespace = ? ORDER BY event_id, position
@@ -1969,13 +2039,14 @@ var SqliteStorage = class {
1969
2039
  };
1970
2040
  });
1971
2041
  const extractionJobs = this.database.prepare(`
1972
- SELECT block_id, status, attempts, last_error, updated_at
2042
+ SELECT block_id, status, attempts, last_error, next_retry_at, updated_at
1973
2043
  FROM extraction_jobs WHERE namespace = ? ORDER BY block_id
1974
2044
  `).all(key).map((row) => ({
1975
2045
  blockId: row.block_id,
1976
2046
  status: row.status,
1977
2047
  attempts: row.attempts,
1978
2048
  lastError: row.last_error,
2049
+ nextRetryAt: row.next_retry_at,
1979
2050
  updatedAt: row.updated_at
1980
2051
  }));
1981
2052
  const elementProjectionJobs = this.database.prepare(`
@@ -2034,6 +2105,7 @@ var SqliteStorage = class {
2034
2105
  blockDecayLambda: space.block_decay_lambda,
2035
2106
  openTail,
2036
2107
  blocks,
2108
+ summaryJobs,
2037
2109
  events,
2038
2110
  graphNodes,
2039
2111
  graphEdges,
@@ -2104,8 +2176,9 @@ var SqliteStorage = class {
2104
2176
  INSERT INTO blocks (
2105
2177
  namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
2106
2178
  l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
2107
- pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at, last_lifted_by
2108
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2179
+ pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at, last_lifted_by,
2180
+ processing_status
2181
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2109
2182
  ON CONFLICT (namespace, id) DO UPDATE SET
2110
2183
  thread_id = excluded.thread_id,
2111
2184
  sequence = excluded.sequence,
@@ -2123,7 +2196,8 @@ var SqliteStorage = class {
2123
2196
  pointer_anchor_level = excluded.pointer_anchor_level,
2124
2197
  pointer_anchor_block_position = excluded.pointer_anchor_block_position,
2125
2198
  last_lifted_at = excluded.last_lifted_at,
2126
- last_lifted_by = excluded.last_lifted_by
2199
+ last_lifted_by = excluded.last_lifted_by,
2200
+ processing_status = excluded.processing_status
2127
2201
  `);
2128
2202
  for (const block of snapshot.blocks) {
2129
2203
  insertBlock.run(
@@ -2134,18 +2208,19 @@ var SqliteStorage = class {
2134
2208
  block.startTurn,
2135
2209
  block.endTurn,
2136
2210
  block.createdAt,
2137
- Number(block.shouldExtract),
2138
- block.l0Title,
2139
- JSON.stringify(block.l0Tags),
2140
- block.l1Summary,
2141
- JSON.stringify(block.l2Keypoints),
2211
+ Number(block.shouldExtract ?? false),
2212
+ block.l0Title ?? "",
2213
+ JSON.stringify(block.l0Tags ?? []),
2214
+ block.l1Summary ?? "",
2215
+ JSON.stringify(block.l2Keypoints ?? []),
2142
2216
  block.l3Condensed,
2143
2217
  block.l4Readable,
2144
2218
  block.pointerCurrentLevel,
2145
2219
  block.pointerAnchorLevel,
2146
2220
  block.pointerAnchorBlockPosition,
2147
2221
  block.lastLiftedAt,
2148
- block.lastLiftedBy
2222
+ block.lastLiftedBy,
2223
+ block.processingStatus
2149
2224
  );
2150
2225
  }
2151
2226
  const insertMessage = this.database.prepare(`
@@ -2331,16 +2406,31 @@ var SqliteStorage = class {
2331
2406
  }
2332
2407
  const insertJob = this.database.prepare(`
2333
2408
  INSERT INTO extraction_jobs (
2334
- namespace, block_id, status, attempts, last_error, updated_at
2335
- ) VALUES (?, ?, ?, ?, ?, ?)
2409
+ namespace, block_id, status, attempts, last_error, next_retry_at, updated_at
2410
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
2336
2411
  ON CONFLICT (namespace, block_id) DO UPDATE SET
2337
2412
  status = excluded.status,
2338
2413
  attempts = excluded.attempts,
2339
2414
  last_error = excluded.last_error,
2415
+ next_retry_at = excluded.next_retry_at,
2340
2416
  updated_at = excluded.updated_at
2341
2417
  `);
2342
2418
  for (const job of snapshot.extractionJobs) {
2343
- insertJob.run(namespace, job.blockId, job.status, job.attempts, job.lastError, job.updatedAt);
2419
+ insertJob.run(namespace, job.blockId, job.status, job.attempts, job.lastError, job.nextRetryAt, job.updatedAt);
2420
+ }
2421
+ const insertSummaryJob = this.database.prepare(`
2422
+ INSERT INTO block_summary_jobs (
2423
+ namespace, block_id, status, attempts, last_error, next_retry_at, updated_at
2424
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
2425
+ ON CONFLICT (namespace, block_id) DO UPDATE SET
2426
+ status = excluded.status,
2427
+ attempts = excluded.attempts,
2428
+ last_error = excluded.last_error,
2429
+ next_retry_at = excluded.next_retry_at,
2430
+ updated_at = excluded.updated_at
2431
+ `);
2432
+ for (const job of snapshot.summaryJobs) {
2433
+ insertSummaryJob.run(namespace, job.blockId, job.status, job.attempts, job.lastError, job.nextRetryAt, job.updatedAt);
2344
2434
  }
2345
2435
  const insertElementProjectionJob = this.database.prepare(`
2346
2436
  INSERT INTO element_projection_jobs (
@@ -2429,7 +2519,7 @@ var SqliteStorage = class {
2429
2519
  this.database.exec(THREAD_INDEXES);
2430
2520
  this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
2431
2521
  });
2432
- } else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6 || version === 7) {
2522
+ } else if (version >= 1 && version < STRATAGATE_STORAGE_SCHEMA_VERSION) {
2433
2523
  this.immediateTransaction(() => {
2434
2524
  this.database.exec(SCHEMA);
2435
2525
  if (version === 1) {
@@ -2465,6 +2555,13 @@ var SqliteStorage = class {
2465
2555
  if (!blockColumns.some(({ name: name2 }) => name2 === "last_lifted_by")) {
2466
2556
  this.database.exec("ALTER TABLE blocks ADD COLUMN last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent'))");
2467
2557
  }
2558
+ if (!blockColumns.some(({ name: name2 }) => name2 === "processing_status")) {
2559
+ this.database.exec("ALTER TABLE blocks ADD COLUMN processing_status TEXT NOT NULL DEFAULT 'ready' CHECK (processing_status IN ('pending', 'ready'))");
2560
+ }
2561
+ const extractionColumns = this.database.prepare("PRAGMA table_info('extraction_jobs')").all();
2562
+ if (!extractionColumns.some(({ name: name2 }) => name2 === "next_retry_at")) {
2563
+ this.database.exec("ALTER TABLE extraction_jobs ADD COLUMN next_retry_at TEXT");
2564
+ }
2468
2565
  const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
2469
2566
  if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
2470
2567
  this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
@@ -2512,7 +2609,7 @@ var SqliteStorage = class {
2512
2609
  }
2513
2610
  };
2514
2611
 
2515
- // ../../src/store.ts
2612
+ // packages/core/src/store.ts
2516
2613
  function defaultIdFactory(prefix) {
2517
2614
  return `${prefix}_${crypto.randomUUID()}`;
2518
2615
  }
@@ -2522,21 +2619,13 @@ function defaultElementIdFactory(prefix) {
2522
2619
  function defaultGraphIdFactory(prefix) {
2523
2620
  return `${prefix}_${crypto.randomUUID()}`;
2524
2621
  }
2525
- function defaultSummary(messages) {
2526
- const natural = messages.filter((message) => message.role === "user" || message.role === "assistant");
2527
- const firstUser = natural.find((message) => message.role === "user");
2528
- return {
2529
- l0Title: (firstUser?.content ?? "Conversation block").replace(/\s+/g, " ").trim().slice(0, 80),
2530
- l0Tags: [],
2531
- l1Summary: natural.slice(0, 4).map((message) => message.content.replace(/\s+/g, " ").trim()).join(" ").slice(0, 500),
2532
- l2Keypoints: natural.slice(0, 8).map((message) => message.content.replace(/\s+/g, " ").trim().slice(0, 160)),
2533
- shouldExtract: false
2534
- };
2535
- }
2536
2622
  function renderBlock(block, level) {
2623
+ if (block.processingStatus !== "ready" || !block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints) {
2624
+ throw new Error(`Block ${block.id} is not ready for rendering`);
2625
+ }
2537
2626
  if (level === 0) return `${block.l0Title}
2538
2627
  Tags: ${block.l0Tags.join(", ") || "none"}`;
2539
- if (level === 1) return block.l1Summary || block.l0Title;
2628
+ if (level === 1) return block.l1Summary;
2540
2629
  if (level === 2) return block.l2Keypoints.map((point) => `- ${point}`).join("\n") || block.l1Summary;
2541
2630
  if (level === 3) return block.l3Condensed;
2542
2631
  if (level === 4) return block.l4Readable;
@@ -2553,6 +2642,8 @@ function errorMessage(error) {
2553
2642
  return error instanceof Error ? error.message : String(error);
2554
2643
  }
2555
2644
  var STRATAGATE_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol("StrataGate constructor");
2645
+ var DERIVATION_MAX_ATTEMPTS = 3;
2646
+ var DERIVATION_BACKOFF_MS = 1e3;
2556
2647
  var StrataGate = class _StrataGate {
2557
2648
  blockTurnSizeValue;
2558
2649
  blockDecayLambdaValue;
@@ -2572,6 +2663,7 @@ var StrataGate = class _StrataGate {
2572
2663
  graphNodes = [];
2573
2664
  graphEdges = [];
2574
2665
  extractionJobs = /* @__PURE__ */ new Map();
2666
+ summaryJobs = /* @__PURE__ */ new Map();
2575
2667
  elementProjectionJobs = /* @__PURE__ */ new Map();
2576
2668
  graphProjectionJobs = /* @__PURE__ */ new Map();
2577
2669
  usageReceipts = /* @__PURE__ */ new Map();
@@ -2680,6 +2772,21 @@ var StrataGate = class _StrataGate {
2680
2772
  if (loaded && loadedSnapshot) {
2681
2773
  memory.restoreSnapshot(loadedSnapshot);
2682
2774
  memory.revision = loadedRevision;
2775
+ const interruptedSummaries = [...memory.summaryJobs.values()].filter((job) => job.status === "running");
2776
+ if (interruptedSummaries.length > 0) {
2777
+ await memory.commitMutation(() => {
2778
+ const now = toUtc8Iso(memory.now());
2779
+ for (const job of interruptedSummaries) {
2780
+ memory.summaryJobs.set(job.blockId, {
2781
+ ...job,
2782
+ status: "failed",
2783
+ lastError: "Block summarization was interrupted before completion.",
2784
+ nextRetryAt: now,
2785
+ updatedAt: now
2786
+ });
2787
+ }
2788
+ });
2789
+ }
2683
2790
  const interrupted = [...memory.extractionJobs.values()].filter((job) => job.status === "running");
2684
2791
  if (interrupted.length > 0) {
2685
2792
  await memory.commitMutation(() => {
@@ -2689,6 +2796,7 @@ var StrataGate = class _StrataGate {
2689
2796
  ...job,
2690
2797
  status: "failed",
2691
2798
  lastError: "Extraction was interrupted before completion.",
2799
+ nextRetryAt: now,
2692
2800
  updatedAt: now
2693
2801
  });
2694
2802
  }
@@ -2780,6 +2888,9 @@ var StrataGate = class _StrataGate {
2780
2888
  listExtractionJobs() {
2781
2889
  return [...this.extractionJobs.values()];
2782
2890
  }
2891
+ listSummaryJobs() {
2892
+ return [...this.summaryJobs.values()];
2893
+ }
2783
2894
  listElementProjectionJobs() {
2784
2895
  return [...this.elementProjectionJobs.values()];
2785
2896
  }
@@ -2812,6 +2923,7 @@ var StrataGate = class _StrataGate {
2812
2923
  blockDecayLambda: this.blockDecayLambda,
2813
2924
  openTail: this.openTail,
2814
2925
  blocks: this.blocks,
2926
+ summaryJobs: [...this.summaryJobs.values()],
2815
2927
  events: this.events,
2816
2928
  graphNodes: this.graphNodes,
2817
2929
  graphEdges: this.graphEdges,
@@ -2860,57 +2972,55 @@ var StrataGate = class _StrataGate {
2860
2972
  if (receiptId) this.ingestionReceipts.set(receiptId, { id: receiptId, createdAt });
2861
2973
  return true;
2862
2974
  });
2863
- if (!appended) return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
2975
+ if (!appended) return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: [] };
2864
2976
  if (options.deferProcessing === true) {
2865
- return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
2977
+ return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: [] };
2866
2978
  }
2867
2979
  if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
2868
2980
  const projectedElements2 = await this.projectEligibleElements() ?? [];
2869
2981
  await this.projectEligibleGraph();
2870
- return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
2982
+ return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: projectedElements2 };
2871
2983
  }
2872
2984
  const sealedBlock = await this.sealOpenTail(threadId);
2873
- const extractedEvents = await this.extractEligibleBlock() ?? [];
2985
+ if (options.deferDerivation === true) {
2986
+ return { sealedBlock, readyBlocks: [], extractedEvents: [], projectedElements: [] };
2987
+ }
2988
+ const beforeReady = sealedBlock.processingStatus === "ready";
2989
+ const extractedEvents = await this.processBlock(sealedBlock, { retryFailed: false });
2990
+ const readyBlocks = !beforeReady && sealedBlock.processingStatus === "ready" ? [sealedBlock] : [];
2874
2991
  const projectedElements = await this.projectEligibleElements() ?? [];
2875
2992
  await this.projectEligibleGraph();
2876
- return { sealedBlock, extractedEvents, projectedElements };
2993
+ return { sealedBlock, readyBlocks, extractedEvents, projectedElements };
2877
2994
  }
2878
2995
  async resumePendingWork(options = {}) {
2879
2996
  const sealedBlocks = [];
2997
+ const readyBlocks = [];
2880
2998
  const extractedEvents = [];
2881
2999
  const projectedElements = [];
2882
3000
  while (true) {
2883
3001
  const sealable = this.nextSealableThread();
2884
3002
  if (sealable === null) break;
2885
3003
  sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
2886
- extractedEvents.push(...await this.extractEligibleBlock() ?? []);
2887
- projectedElements.push(...await this.projectEligibleElements() ?? []);
2888
- await this.projectEligibleGraph();
2889
3004
  }
2890
- while (true) {
2891
- const extracted = await this.extractEligibleBlock();
2892
- if (extracted === null) break;
3005
+ if (options.deferDerivation === true) {
3006
+ return { sealedBlocks, readyBlocks, extractedEvents, projectedElements };
3007
+ }
3008
+ for (const block of this.blocks) {
3009
+ if (options.threadId !== void 0 && block.threadId !== options.threadId) continue;
3010
+ if (block.processingStatus === "ready") continue;
3011
+ const extracted = await this.processBlock(block, { retryFailed: options.retryFailed === true });
2893
3012
  extractedEvents.push(...extracted);
3013
+ if (this.blocks.find((candidate) => candidate.id === block.id)?.processingStatus === "ready") readyBlocks.push(block);
2894
3014
  projectedElements.push(...await this.projectEligibleElements() ?? []);
2895
3015
  await this.projectEligibleGraph();
2896
3016
  }
2897
- if (options.retrySkipped === true) {
2898
- const skippedBlockIds = this.blocks.filter((block) => this.nextBlockInThread(block) !== null && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
2899
- for (const blockId of skippedBlockIds) {
2900
- const extracted = await this.extractEligibleBlock({ blockId, includeSkipped: true });
2901
- if (extracted === null) continue;
2902
- extractedEvents.push(...extracted);
2903
- projectedElements.push(...await this.projectEligibleElements() ?? []);
2904
- await this.projectEligibleGraph();
2905
- }
2906
- }
2907
3017
  while (true) {
2908
3018
  const projected = await this.projectEligibleElements();
2909
3019
  if (projected === null) break;
2910
3020
  projectedElements.push(...projected);
2911
3021
  }
2912
3022
  await this.projectEligibleGraph();
2913
- return { sealedBlocks, extractedEvents, projectedElements };
3023
+ return { sealedBlocks, readyBlocks, extractedEvents, projectedElements };
2914
3024
  }
2915
3025
  async addEvent(input) {
2916
3026
  return this.commitMutation(() => {
@@ -3272,9 +3382,9 @@ var StrataGate = class _StrataGate {
3272
3382
  * StrataGate namespace (never another namespace).
3273
3383
  */
3274
3384
  getBlockContext(threadId) {
3275
- const blocks = threadId === void 0 ? this.blocks : this.blocks.filter((block) => block.threadId === threadId);
3385
+ const blocks = threadId === void 0 ? this.blocks.filter((block) => block.processingStatus === "ready") : this.blocks.filter((block) => block.threadId === threadId && block.processingStatus === "ready");
3276
3386
  return blocks.map((block) => {
3277
- const threadBlocks = this.threadBlocks(block.threadId);
3387
+ const threadBlocks = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
3278
3388
  const latestBlockPosition = threadBlocks.length;
3279
3389
  const blockPosition = threadBlocks.indexOf(block) + 1;
3280
3390
  const age = Math.max(0, latestBlockPosition - blockPosition);
@@ -3300,8 +3410,10 @@ var StrataGate = class _StrataGate {
3300
3410
  return this.commitMutation(() => {
3301
3411
  const block = this.blocks.find((candidate) => candidate.id === id);
3302
3412
  if (!block) throw new Error(`Unknown block: ${id}`);
3303
- const latestBlockPosition = this.threadBlocks(block.threadId).length;
3304
- const blockPosition = this.threadBlocks(block.threadId).indexOf(block) + 1;
3413
+ if (block.processingStatus !== "ready") throw new Error(`Block ${id} is not ready for decay or expansion`);
3414
+ const readyBlocks = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
3415
+ const latestBlockPosition = readyBlocks.length;
3416
+ const blockPosition = readyBlocks.indexOf(block) + 1;
3305
3417
  const current = getDecayedBlockLevel(
3306
3418
  block.pointerAnchorLevel,
3307
3419
  block.pointerAnchorBlockPosition,
@@ -3420,6 +3532,7 @@ var StrataGate = class _StrataGate {
3420
3532
  l1Summary: text3.replace(/\s+/gu, " ").trim().slice(0, 500),
3421
3533
  l2Keypoints: text3.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).slice(0, 8),
3422
3534
  shouldExtract: false,
3535
+ processingStatus: "ready",
3423
3536
  ...deterministicBlockLayers([message]),
3424
3537
  pointerCurrentLevel: 5,
3425
3538
  pointerAnchorLevel: 5,
@@ -3636,7 +3749,6 @@ var StrataGate = class _StrataGate {
3636
3749
  if (raw.filter((message) => message.role === "user").length < this.blockTurnSize) {
3637
3750
  throw new Error("Open tail does not contain enough turns to seal a block");
3638
3751
  }
3639
- const generated = this.summarizer ? await this.summarizer(raw) : defaultSummary(raw);
3640
3752
  const deterministic = deterministicBlockLayers(raw);
3641
3753
  const sequence = this.blocks.length + 1;
3642
3754
  const threadBlocks = this.threadBlocks(threadId);
@@ -3656,11 +3768,7 @@ var StrataGate = class _StrataGate {
3656
3768
  startTurn,
3657
3769
  endTurn,
3658
3770
  createdAt: raw.at(-1)?.createdAt ?? toUtc8Iso(this.now()),
3659
- l0Title: generated.l0Title,
3660
- l0Tags: generated.l0Tags,
3661
- l1Summary: generated.l1Summary,
3662
- l2Keypoints: generated.l2Keypoints,
3663
- shouldExtract: generated.shouldExtract,
3771
+ processingStatus: "pending",
3664
3772
  ...deterministic,
3665
3773
  pointerCurrentLevel: 5,
3666
3774
  pointerAnchorLevel: 5,
@@ -3672,27 +3780,132 @@ var StrataGate = class _StrataGate {
3672
3780
  const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
3673
3781
  this.openTail.splice(0, this.openTail.length, ...remaining);
3674
3782
  this.blocks.push(block);
3783
+ const updatedAt = toUtc8Iso(this.now());
3784
+ this.summaryJobs.set(block.id, {
3785
+ blockId: block.id,
3786
+ status: "pending",
3787
+ attempts: 0,
3788
+ lastError: null,
3789
+ nextRetryAt: null,
3790
+ updatedAt
3791
+ });
3675
3792
  return block;
3676
3793
  });
3677
3794
  }
3795
+ async processBlock(block, options) {
3796
+ if (block.processingStatus === "ready") return [];
3797
+ const summary = this.summaryJobs.get(block.id);
3798
+ if (!summary || summary.status !== "succeeded") {
3799
+ if (!this.summarizer || !this.jobCanRun(summary, options.retryFailed)) return [];
3800
+ const claimed = await this.commitMutation(() => {
3801
+ const current = this.summaryJobs.get(block.id);
3802
+ if (!current || !this.jobCanRun(current, options.retryFailed)) return false;
3803
+ this.summaryJobs.set(block.id, {
3804
+ ...current,
3805
+ status: "running",
3806
+ attempts: current.attempts + 1,
3807
+ lastError: null,
3808
+ nextRetryAt: null,
3809
+ updatedAt: toUtc8Iso(this.now())
3810
+ });
3811
+ return true;
3812
+ });
3813
+ if (!claimed) return [];
3814
+ try {
3815
+ const generated = await this.summarizer(block.l5Raw);
3816
+ if (!generated.l0Title.trim() || !generated.l1Summary.trim() || !Array.isArray(generated.l0Tags) || !Array.isArray(generated.l2Keypoints) || typeof generated.shouldExtract !== "boolean") {
3817
+ throw new Error("Block summarizer returned invalid L0-L2 layers");
3818
+ }
3819
+ await this.commitMutation(() => {
3820
+ block.l0Title = generated.l0Title;
3821
+ block.l0Tags = [...generated.l0Tags];
3822
+ block.l1Summary = generated.l1Summary;
3823
+ block.l2Keypoints = [...generated.l2Keypoints];
3824
+ block.shouldExtract = generated.shouldExtract;
3825
+ const current = this.summaryJobs.get(block.id);
3826
+ if (!current) throw new Error(`Missing summary job for block: ${block.id}`);
3827
+ this.summaryJobs.set(block.id, {
3828
+ ...current,
3829
+ status: "succeeded",
3830
+ lastError: null,
3831
+ nextRetryAt: null,
3832
+ updatedAt: toUtc8Iso(this.now())
3833
+ });
3834
+ });
3835
+ } catch (error) {
3836
+ await this.failSummary(block.id, error);
3837
+ return [];
3838
+ }
3839
+ }
3840
+ if (block.shouldExtract === false) {
3841
+ await this.commitMutation(() => {
3842
+ const now = toUtc8Iso(this.now());
3843
+ this.extractionJobs.set(block.id, {
3844
+ blockId: block.id,
3845
+ status: "skipped",
3846
+ attempts: this.extractionJobs.get(block.id)?.attempts ?? 0,
3847
+ lastError: null,
3848
+ nextRetryAt: null,
3849
+ updatedAt: now
3850
+ });
3851
+ this.markBlockReady(block);
3852
+ });
3853
+ return [];
3854
+ }
3855
+ try {
3856
+ const extracted = await this.extractEligibleBlock({ blockId: block.id, retryFailed: options.retryFailed });
3857
+ return extracted ?? [];
3858
+ } catch {
3859
+ return [];
3860
+ }
3861
+ }
3862
+ jobCanRun(job, force) {
3863
+ if (!job || job.attempts >= DERIVATION_MAX_ATTEMPTS || job.status === "running" || job.status === "succeeded") return false;
3864
+ return force || job.nextRetryAt === null || Date.parse(job.nextRetryAt) <= this.now().getTime();
3865
+ }
3866
+ async failSummary(blockId, error) {
3867
+ await this.commitMutation(() => {
3868
+ const job = this.summaryJobs.get(blockId);
3869
+ if (!job) return;
3870
+ this.summaryJobs.set(blockId, {
3871
+ ...job,
3872
+ status: "failed",
3873
+ lastError: errorMessage(error),
3874
+ nextRetryAt: this.retryAt(job.attempts),
3875
+ updatedAt: toUtc8Iso(this.now())
3876
+ });
3877
+ });
3878
+ }
3879
+ retryAt(attempts) {
3880
+ if (attempts >= DERIVATION_MAX_ATTEMPTS) return null;
3881
+ return toUtc8Iso(new Date(this.now().getTime() + DERIVATION_BACKOFF_MS * 2 ** Math.max(0, attempts - 1)));
3882
+ }
3883
+ markBlockReady(block) {
3884
+ if (!block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints || typeof block.shouldExtract !== "boolean") {
3885
+ throw new Error(`Block ${block.id} cannot become ready without validated L0-L2 layers`);
3886
+ }
3887
+ const ready = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
3888
+ block.processingStatus = "ready";
3889
+ block.pointerCurrentLevel = 5;
3890
+ block.pointerAnchorLevel = 5;
3891
+ block.pointerAnchorBlockPosition = ready.length + 1;
3892
+ }
3678
3893
  async extractEligibleBlock(options = {}) {
3679
- if (!this.extractor || this.blocks.length < 2) return null;
3894
+ if (!this.extractor) return null;
3680
3895
  const target = this.blocks.find((block) => {
3681
- if (this.nextBlockInThread(block) === null || !block.shouldExtract) return false;
3896
+ if (block.processingStatus === "ready" || block.shouldExtract !== true) return false;
3682
3897
  if (options.blockId !== void 0 && block.id !== options.blockId) return false;
3683
- const status = this.extractionJobs.get(block.id)?.status;
3684
- return status === void 0 || status === "failed" || options.includeSkipped === true && status === "skipped";
3898
+ const job = this.extractionJobs.get(block.id);
3899
+ return job === void 0 || this.jobCanRun(job, options.retryFailed === true);
3685
3900
  });
3686
3901
  if (!target) return null;
3687
3902
  const threadBlocks = this.threadBlocks(target.threadId);
3688
3903
  const targetIndex = threadBlocks.indexOf(target);
3689
- const next = threadBlocks[targetIndex + 1];
3690
- if (!next) return null;
3904
+ const next = threadBlocks.slice(targetIndex + 1).find((block) => block.l2Keypoints !== void 0) ?? null;
3691
3905
  const existing = this.extractionJobs.get(target.id);
3692
3906
  await this.commitMutation(() => {
3693
3907
  const currentStatus = this.extractionJobs.get(target.id)?.status;
3694
- const canRetrySkipped = options.includeSkipped === true && currentStatus === "skipped";
3695
- if (currentStatus !== void 0 && currentStatus !== "failed" && !canRetrySkipped) {
3908
+ if (currentStatus !== void 0 && currentStatus !== "failed") {
3696
3909
  throw new Error(`Extraction block ${target.id} is already ${currentStatus}`);
3697
3910
  }
3698
3911
  this.extractionJobs.set(target.id, {
@@ -3700,13 +3913,14 @@ var StrataGate = class _StrataGate {
3700
3913
  status: "running",
3701
3914
  attempts: (existing?.attempts ?? 0) + 1,
3702
3915
  lastError: null,
3916
+ nextRetryAt: null,
3703
3917
  updatedAt: toUtc8Iso(this.now())
3704
3918
  });
3705
3919
  });
3706
3920
  let result;
3707
3921
  try {
3708
3922
  result = await this.extractor({
3709
- previous: threadBlocks[targetIndex - 1] ?? null,
3923
+ previous: threadBlocks.slice(0, targetIndex).reverse().find((block) => block.l2Keypoints !== void 0) ?? null,
3710
3924
  target,
3711
3925
  next,
3712
3926
  timeline: this.events.map((event) => ({ id: event.id, title: event.title, temporal: event.temporal }))
@@ -3719,6 +3933,7 @@ var StrataGate = class _StrataGate {
3719
3933
  ...job,
3720
3934
  status: "failed",
3721
3935
  lastError: errorMessage(error),
3936
+ nextRetryAt: this.retryAt(job.attempts),
3722
3937
  updatedAt: toUtc8Iso(this.now())
3723
3938
  });
3724
3939
  });
@@ -3733,6 +3948,7 @@ var StrataGate = class _StrataGate {
3733
3948
  ...job,
3734
3949
  status: "failed",
3735
3950
  lastError: reason,
3951
+ nextRetryAt: this.retryAt(job.attempts),
3736
3952
  updatedAt: toUtc8Iso(this.now())
3737
3953
  });
3738
3954
  });
@@ -3751,8 +3967,10 @@ var StrataGate = class _StrataGate {
3751
3967
  ...job,
3752
3968
  status: result.shouldExtract ? "succeeded" : "skipped",
3753
3969
  lastError: null,
3970
+ nextRetryAt: null,
3754
3971
  updatedAt: toUtc8Iso(this.now())
3755
3972
  });
3973
+ this.markBlockReady(target);
3756
3974
  return extracted;
3757
3975
  });
3758
3976
  }
@@ -3821,6 +4039,8 @@ var StrataGate = class _StrataGate {
3821
4039
  this.currentTurn = copy.currentTurn;
3822
4040
  this.openTail.splice(0, this.openTail.length, ...copy.openTail);
3823
4041
  this.blocks.splice(0, this.blocks.length, ...copy.blocks);
4042
+ this.summaryJobs.clear();
4043
+ for (const job of copy.summaryJobs) this.summaryJobs.set(job.blockId, job);
3824
4044
  this.events.splice(0, this.events.length, ...copy.events);
3825
4045
  this.graphNodes.splice(0, this.graphNodes.length, ...copy.graphNodes);
3826
4046
  this.graphEdges.splice(0, this.graphEdges.length, ...copy.graphEdges);
@@ -3867,6 +4087,9 @@ var StrataGate = class _StrataGate {
3867
4087
  for (const job of this.extractionJobs.values()) {
3868
4088
  if (!blockIds.has(job.blockId)) throw new Error(`Unknown extraction job block in snapshot: ${job.blockId}`);
3869
4089
  }
4090
+ for (const job of this.summaryJobs.values()) {
4091
+ if (!blockIds.has(job.blockId)) throw new Error(`Unknown summary job block in snapshot: ${job.blockId}`);
4092
+ }
3870
4093
  const elementIds = /* @__PURE__ */ new Set();
3871
4094
  for (const element of this.elements) {
3872
4095
  if (elementIds.has(element.id)) throw new Error(`Duplicate element ID in snapshot: ${element.id}`);
@@ -4095,7 +4318,7 @@ function extractorPayload(context) {
4095
4318
  }
4096
4319
  var JSON_RESPONSE_ATTEMPTS = 2;
4097
4320
  var JSON_RETRY_INSTRUCTION = "Your previous response did not make one valid call to the requested tool. Do not spend output on analysis or reasoning. Immediately call that tool exactly once with complete arguments. Do not return an answer as text or markdown.";
4098
- var RETRY_MAX_TOKENS = 1e4;
4321
+ var DEFAULT_STRUCTURED_TIMEOUT_MS = 45e3;
4099
4322
  var STRUCTURED_FIELDS = {
4100
4323
  summarizer: ["l0Title", "l0Tags", "l1Summary", "l2Keypoints", "shouldExtract"],
4101
4324
  extractor: ["shouldExtract", "reason", "events"],
@@ -4246,11 +4469,15 @@ var DshModelBridge = class {
4246
4469
  constructor(ctx, config) {
4247
4470
  this.ctx = ctx;
4248
4471
  this.config = config;
4472
+ this.ctx.on?.("llm/adapters-updated", () => {
4473
+ this.offCapabilities.clear();
4474
+ });
4249
4475
  }
4250
4476
  ctx;
4251
4477
  config;
4252
4478
  sessions = new AsyncLocalStorage();
4253
4479
  successfulResponses = [];
4480
+ offCapabilities = /* @__PURE__ */ new Map();
4254
4481
  run(session, operation) {
4255
4482
  return this.sessions.run(session, operation);
4256
4483
  }
@@ -4265,7 +4492,7 @@ var DshModelBridge = class {
4265
4492
  { messages }
4266
4493
  ));
4267
4494
  return {
4268
- l0Title: text2(raw.l0Title, "Conversation block").slice(0, 120),
4495
+ l0Title: text2(raw.l0Title).slice(0, 120),
4269
4496
  l0Tags: strings2(raw.l0Tags).slice(0, 12),
4270
4497
  l1Summary: text2(raw.l1Summary).slice(0, 2e3),
4271
4498
  l2Keypoints: strings2(raw.l2Keypoints).slice(0, 20),
@@ -4397,10 +4624,11 @@ var DshModelBridge = class {
4397
4624
  async callStructured(kind, system, payload) {
4398
4625
  const session = this.sessions.getStore();
4399
4626
  if (!session) throw new Error("StrataGate model callback ran without a DSH session");
4400
- const route = this.resolveRoute(session, true);
4627
+ const baseRoute = this.resolveRoute(session);
4628
+ const routeKey = `${baseRoute.provider}\0${baseRoute.model}`;
4629
+ let useOff = await this.shouldUseOff(baseRoute);
4401
4630
  let lastError;
4402
4631
  let lastResponse = "";
4403
- let retryMaxTokens = this.config.maxOutputTokens;
4404
4632
  for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
4405
4633
  const message = createUserMessage({
4406
4634
  content: [{ type: "text", text: JSON.stringify(payload) }],
@@ -4408,7 +4636,8 @@ var DshModelBridge = class {
4408
4636
  });
4409
4637
  const assembler = new BlockAssembler();
4410
4638
  const request = {
4411
- ...route,
4639
+ ...baseRoute,
4640
+ ...useOff ? { reasoningEffort: "off" } : {},
4412
4641
  messages: [message],
4413
4642
  system: attempt === 1 ? system : `${system}
4414
4643
 
@@ -4422,15 +4651,35 @@ ${JSON_RETRY_INSTRUCTION}`,
4422
4651
  type: "function",
4423
4652
  function: { name: STRUCTURED_TOOLS[kind].name }
4424
4653
  },
4425
- maxTokens: retryMaxTokens,
4654
+ maxTokens: this.config.maxOutputTokens,
4426
4655
  sessionId: session.id,
4427
4656
  purpose: "compaction"
4428
4657
  };
4429
- for await (const chunk of this.ctx.llm.stream(request)) assembler.push(chunk);
4658
+ try {
4659
+ await this.consumeStructuredStream(request, assembler);
4660
+ } catch (error) {
4661
+ if (useOff && isOffRejection(error)) {
4662
+ this.offCapabilities.set(routeKey, "unsupported");
4663
+ useOff = false;
4664
+ this.ctx.logger.warn(`stratagate-memory ${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
4665
+ attempt -= 1;
4666
+ continue;
4667
+ }
4668
+ throw error;
4669
+ }
4430
4670
  const finish = assembler.finish;
4431
4671
  if (finish.kind === "error" || finish.kind === "aborted") {
4432
- throw new Error(`StrataGate model call failed: ${finish.failure.message}`);
4672
+ const failure = new Error(`StrataGate model call failed [${finish.failure.code}]: ${finish.failure.message}`);
4673
+ if (useOff && isOffRejection(finish.failure)) {
4674
+ this.offCapabilities.set(routeKey, "unsupported");
4675
+ useOff = false;
4676
+ this.ctx.logger.warn(`stratagate-memory ${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
4677
+ attempt -= 1;
4678
+ continue;
4679
+ }
4680
+ throw failure;
4433
4681
  }
4682
+ if (useOff) this.offCapabilities.set(routeKey, "supported");
4434
4683
  const blocks = assembler.blocks();
4435
4684
  const calls = blocks.filter((block) => block.type === "tool-call");
4436
4685
  const responseForError = `${renderBlocksForDiagnostics(blocks, finish.kind)}
@@ -4466,6 +4715,15 @@ ${JSON_RETRY_INSTRUCTION}`,
4466
4715
  { response: responseForError }
4467
4716
  );
4468
4717
  }
4718
+ if (kind === "summarizer") {
4719
+ const summary = object(parsed);
4720
+ if (!text2(summary.l0Title) || !text2(summary.l1Summary)) {
4721
+ throw new ModelJsonResponseError(
4722
+ `StrataGate ${expectedTool} arguments were invalid: l0Title and l1Summary must not be empty`,
4723
+ { response: responseForError }
4724
+ );
4725
+ }
4726
+ }
4469
4727
  this.successfulResponses.push({
4470
4728
  id: `model_response_${crypto.randomUUID()}`,
4471
4729
  kind,
@@ -4480,7 +4738,6 @@ ${JSON_RETRY_INSTRUCTION}`,
4480
4738
  `StrataGate ${STRUCTURED_TOOLS[kind].name} call was truncated before valid arguments`,
4481
4739
  { cause: error, response: responseForError }
4482
4740
  ) : error;
4483
- if (finish.kind === "max-tokens") retryMaxTokens = Math.max(this.config.maxOutputTokens, RETRY_MAX_TOKENS);
4484
4741
  if (attempt < JSON_RESPONSE_ATTEMPTS) {
4485
4742
  this.ctx.logger.warn(`stratagate-memory model returned an invalid structured tool call; retrying (${attempt}/${JSON_RESPONSE_ATTEMPTS})`);
4486
4743
  }
@@ -4491,25 +4748,77 @@ ${JSON_RETRY_INSTRUCTION}`,
4491
4748
  { cause: lastError, response: lastResponse }
4492
4749
  );
4493
4750
  }
4494
- resolveRoute(session, structured = false) {
4495
- const request = session.requestHeader()?.config;
4496
- const requestedReasoningEffort = request?.reasoningEffort;
4497
- const withReasoningEffort = (route) => ({
4498
- ...route,
4499
- ...structured ? { reasoningEffort: "off" } : requestedReasoningEffort !== void 0 ? { reasoningEffort: requestedReasoningEffort } : {}
4751
+ async shouldUseOff(route) {
4752
+ const key = `${route.provider}\0${route.model}`;
4753
+ const cached = this.offCapabilities.get(key);
4754
+ if (cached) return cached === "supported";
4755
+ if (typeof this.ctx.llm.resolveModelInfo !== "function") return true;
4756
+ const controller = new AbortController();
4757
+ const lookupTimeoutMs = Math.min(5e3, this.config.structuredTaskTimeoutMs ?? DEFAULT_STRUCTURED_TIMEOUT_MS);
4758
+ let timer;
4759
+ try {
4760
+ const info = await Promise.race([
4761
+ this.ctx.llm.resolveModelInfo(route.provider, route.model, controller.signal),
4762
+ new Promise((_resolve, reject) => {
4763
+ timer = setTimeout(() => {
4764
+ controller.abort();
4765
+ reject(new Error(`Model capability lookup timed out after ${lookupTimeoutMs}ms`));
4766
+ }, lookupTimeoutMs);
4767
+ timer.unref?.();
4768
+ })
4769
+ ]);
4770
+ if (!info.reasoning) return true;
4771
+ const supported = info.reasoning.efforts.some(({ id }) => String(id) === "off");
4772
+ this.offCapabilities.set(key, supported ? "supported" : "unsupported");
4773
+ return supported;
4774
+ } catch {
4775
+ return true;
4776
+ } finally {
4777
+ if (timer) clearTimeout(timer);
4778
+ }
4779
+ }
4780
+ async consumeStructuredStream(request, assembler) {
4781
+ const timeoutMs = this.config.structuredTaskTimeoutMs ?? DEFAULT_STRUCTURED_TIMEOUT_MS;
4782
+ const controller = new AbortController();
4783
+ const timedRequest = { ...request, signal: controller.signal };
4784
+ let timer;
4785
+ const timeout = new Promise((_resolve, reject) => {
4786
+ timer = setTimeout(() => {
4787
+ controller.abort(new Error(`StrataGate structured model task timed out after ${timeoutMs}ms`));
4788
+ reject(new Error(`StrataGate structured model task timed out after ${timeoutMs}ms`));
4789
+ }, timeoutMs);
4790
+ timer.unref?.();
4500
4791
  });
4792
+ try {
4793
+ await Promise.race([
4794
+ (async () => {
4795
+ for await (const chunk of this.ctx.llm.stream(timedRequest)) assembler.push(chunk);
4796
+ })(),
4797
+ timeout
4798
+ ]);
4799
+ } finally {
4800
+ if (timer) clearTimeout(timer);
4801
+ }
4802
+ }
4803
+ resolveRoute(session) {
4804
+ const request = session.requestHeader()?.config;
4501
4805
  if (this.config.provider && this.config.model) {
4502
- return withReasoningEffort({ provider: this.config.provider, model: this.config.model });
4806
+ return { provider: this.config.provider, model: this.config.model };
4503
4807
  }
4504
- if (request) return withReasoningEffort({ provider: request.provider, model: request.model });
4808
+ if (request) return { provider: request.provider, model: request.model };
4505
4809
  const fallback = this.ctx.agentDefaultModel.currentSelection();
4506
- return {
4507
- provider: fallback.provider,
4508
- model: fallback.model,
4509
- ...structured ? { reasoningEffort: "off" } : fallback.reasoningEffort !== void 0 ? { reasoningEffort: fallback.reasoningEffort } : {}
4510
- };
4810
+ return { provider: fallback.provider, model: fallback.model };
4511
4811
  }
4512
4812
  };
4813
+ function isOffRejection(error) {
4814
+ let detail;
4815
+ try {
4816
+ detail = typeof error === "string" ? error : error && typeof error === "object" ? JSON.stringify(error, Object.getOwnPropertyNames(error)) : String(error);
4817
+ } catch {
4818
+ detail = error instanceof Error ? error.message : String(error);
4819
+ }
4820
+ return /(?:reasoning[_ -]?effort|reasoning).{0,100}\boff\b|\boff\b.{0,100}(?:reasoning[_ -]?effort|reasoning)/iu.test(detail) && /(?:unsupported|not supported|invalid|not allowed|unknown|unrecognized|reject|must be|expected)/iu.test(detail);
4821
+ }
4513
4822
 
4514
4823
  // src/runtime.ts
4515
4824
  import { createHash } from "node:crypto";
@@ -4736,6 +5045,8 @@ var StrataGateRuntime = class {
4736
5045
  latestBatchIds = /* @__PURE__ */ new Map();
4737
5046
  workspaceNames = /* @__PURE__ */ new Map();
4738
5047
  migrationTimers = /* @__PURE__ */ new Map();
5048
+ derivationTimers = /* @__PURE__ */ new Map();
5049
+ derivationRuns = /* @__PURE__ */ new Map();
4739
5050
  ingestTail = Promise.resolve();
4740
5051
  settingsTail = Promise.resolve();
4741
5052
  batchSequence = 0;
@@ -4752,15 +5063,8 @@ var StrataGateRuntime = class {
4752
5063
  }).then(async () => {
4753
5064
  const memory = await this.space(session);
4754
5065
  try {
4755
- const result = await this.models.run(session, () => memory.appendTurn(turn));
4756
- if (result.sealedBlock) {
4757
- const contexts = memory.getBlockContext(String(session.id));
4758
- const sealedContext = contexts.find(({ id }) => id === result.sealedBlock.id);
4759
- if (!sealedContext) throw new Error(`Missing context for sealed StrataGate block ${result.sealedBlock.id}`);
4760
- this.replaceSealedSurface(session, result.sealedBlock, sealedContext, turn.dshTurn);
4761
- this.syncDecayedBlockSurface(session, contexts);
4762
- await this.flushNativeSession(session);
4763
- }
5066
+ const result = await memory.appendTurn(turn, { deferDerivation: true });
5067
+ if (result.sealedBlock) this.scheduleBlockDerivation(session, memory);
4764
5068
  } finally {
4765
5069
  await this.persistSuccessfulResponses(memory);
4766
5070
  }
@@ -5074,6 +5378,8 @@ var StrataGateRuntime = class {
5074
5378
  this.closed = true;
5075
5379
  for (const timer of this.migrationTimers.values()) clearTimeout(timer);
5076
5380
  this.migrationTimers.clear();
5381
+ for (const timer of this.derivationTimers.values()) clearTimeout(timer);
5382
+ this.derivationTimers.clear();
5077
5383
  let flushError;
5078
5384
  try {
5079
5385
  await this.flush();
@@ -5081,6 +5387,7 @@ var StrataGateRuntime = class {
5081
5387
  flushError = error;
5082
5388
  }
5083
5389
  const settled = await Promise.allSettled(this.spaces.values());
5390
+ await Promise.allSettled(this.derivationRuns.values());
5084
5391
  await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
5085
5392
  if (flushError !== void 0) throw flushError;
5086
5393
  }
@@ -5319,23 +5626,14 @@ var StrataGateRuntime = class {
5319
5626
  }).then(async (memory) => {
5320
5627
  try {
5321
5628
  try {
5322
- const resumed = await this.models.run(session, () => memory.resumePendingWork({ retrySkipped: true }));
5629
+ await memory.resumePendingWork({ deferDerivation: true, threadId: String(session.id) });
5323
5630
  const contexts = memory.getBlockContext(String(session.id));
5324
- for (const block of resumed.sealedBlocks) {
5325
- if (block.threadId !== String(session.id)) continue;
5326
- const endTurn = dshTurnAtBlockEnd(session, block);
5327
- const context = contexts.find(({ id }) => id === block.id);
5328
- if (!context) throw new Error(`Missing context for recovered StrataGate block ${block.id}`);
5329
- this.replaceSealedSurface(session, block, context, endTurn);
5330
- }
5331
5631
  this.syncDecayedBlockSurface(session, contexts);
5332
- if (resumed.sealedBlocks.some((block) => block.threadId === String(session.id))) {
5333
- await this.flushNativeSession(session);
5334
- }
5335
5632
  } finally {
5336
5633
  await this.persistSuccessfulResponses(memory);
5337
5634
  }
5338
5635
  this.scheduleGraphMigration(session, memory);
5636
+ this.scheduleBlockDerivation(session, memory);
5339
5637
  return memory;
5340
5638
  } catch (error) {
5341
5639
  await memory.close().catch(() => {
@@ -5369,6 +5667,43 @@ var StrataGateRuntime = class {
5369
5667
  target: { eventIds: [.../* @__PURE__ */ new Set([...node.sourceEventIds, ...edges.flatMap(({ sourceEventIds }) => sourceEventIds)])], elementIds: [] }
5370
5668
  }], { node, edges });
5371
5669
  }
5670
+ scheduleBlockDerivation(session, memory) {
5671
+ const threadId = String(session.id);
5672
+ const key = `${this.namespaceFor(session)}\0${threadId}`;
5673
+ if (this.closed || this.derivationTimers.has(key) || this.derivationRuns.has(key)) return;
5674
+ const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
5675
+ if (pendingBlockIds.size === 0) return;
5676
+ const retryTimes = [
5677
+ ...memory.listSummaryJobs(),
5678
+ ...memory.listExtractionJobs()
5679
+ ].filter((job) => pendingBlockIds.has(job.blockId) && (job.status === "pending" || job.status === "failed" && job.nextRetryAt !== null)).map((job) => job.nextRetryAt ? Date.parse(job.nextRetryAt) : Date.now()).filter(Number.isFinite);
5680
+ if (retryTimes.length === 0) return;
5681
+ const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
5682
+ const timer = setTimeout(() => {
5683
+ this.derivationTimers.delete(key);
5684
+ if (this.closed) return;
5685
+ const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
5686
+ await this.persistSuccessfulResponses(memory);
5687
+ const contexts = memory.getBlockContext(threadId);
5688
+ for (const block of resumed.readyBlocks) {
5689
+ if (block.threadId !== threadId) continue;
5690
+ const context = contexts.find(({ id }) => id === block.id);
5691
+ if (!context) throw new Error(`Missing context for ready StrataGate block ${block.id}`);
5692
+ this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block));
5693
+ }
5694
+ const changed = this.syncDecayedBlockSurface(session, contexts);
5695
+ if (resumed.readyBlocks.length > 0 || changed) await this.flushNativeSession(session);
5696
+ }).catch((error) => {
5697
+ this.onIngestError(error);
5698
+ }).finally(() => {
5699
+ this.derivationRuns.delete(key);
5700
+ this.scheduleBlockDerivation(session, memory);
5701
+ });
5702
+ this.derivationRuns.set(key, run);
5703
+ }, delay);
5704
+ timer.unref?.();
5705
+ this.derivationTimers.set(key, timer);
5706
+ }
5372
5707
  scheduleGraphMigration(session, memory) {
5373
5708
  const namespace = this.namespaceFor(session);
5374
5709
  if (this.closed || this.migrationTimers.has(namespace)) return;
@@ -6064,6 +6399,13 @@ function sourceMessages(snapshot, ids) {
6064
6399
  return output;
6065
6400
  }
6066
6401
  function blockLayers(block) {
6402
+ if (block.processingStatus !== "ready" || !block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints) {
6403
+ return [
6404
+ { level: 3, content: block.l3Condensed },
6405
+ { level: 4, content: block.l4Readable },
6406
+ { level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
6407
+ ];
6408
+ }
6067
6409
  return [
6068
6410
  { level: 0, content: `${block.l0Title}
6069
6411
  \u6807\u7B7E\uFF1A${block.l0Tags.join("\u3001") || "\u65E0"}` },