stratagate-dsh 0.2.36 → 0.2.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -20
- package/LICENSE +21 -21
- package/README.md +336 -133
- package/README.zh-CN.md +400 -0
- package/dist/client.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +613 -140
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +244 -0
- package/docs/DSH.md +191 -0
- package/docs/{README.zh-CN.md → DSH.zh-CN.md} +30 -27
- package/docs/EVALUATION.md +179 -0
- package/docs/EXTERNAL_MEMORY_IMPORT.zh-CN.md +54 -0
- package/docs/assets/stratagate-avatar.png +0 -0
- package/docs/assets/stratagate-how-it-works.en.png +0 -0
- package/docs/assets/stratagate-how-it-works.zh-CN.png +0 -0
- package/docs/assets/stratagate-knowledge-graph.png +0 -0
- package/docs/assets/stratagate-memory-structure.png +0 -0
- package/docs/assets/stratagate-short-term-memory.png +0 -0
- package/package.json +176 -153
- package/screenshots.json +4 -0
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(
|
|
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 ??
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1264
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
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 = {
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
1578
|
+
// packages/core/src/sqlite.ts
|
|
1537
1579
|
import { DatabaseSync } from "node:sqlite";
|
|
1538
1580
|
|
|
1539
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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
|
-
|
|
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
|
|
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");
|
|
@@ -2510,9 +2607,13 @@ var SqliteStorage = class {
|
|
|
2510
2607
|
this.assertOpen();
|
|
2511
2608
|
return this.database.prepare("SELECT namespace FROM memory_spaces ORDER BY namespace").all().map(({ namespace }) => namespace);
|
|
2512
2609
|
}
|
|
2610
|
+
listNamespaceRevisions() {
|
|
2611
|
+
this.assertOpen();
|
|
2612
|
+
return this.database.prepare("SELECT namespace, revision FROM memory_spaces ORDER BY namespace").all();
|
|
2613
|
+
}
|
|
2513
2614
|
};
|
|
2514
2615
|
|
|
2515
|
-
//
|
|
2616
|
+
// packages/core/src/store.ts
|
|
2516
2617
|
function defaultIdFactory(prefix) {
|
|
2517
2618
|
return `${prefix}_${crypto.randomUUID()}`;
|
|
2518
2619
|
}
|
|
@@ -2522,21 +2623,13 @@ function defaultElementIdFactory(prefix) {
|
|
|
2522
2623
|
function defaultGraphIdFactory(prefix) {
|
|
2523
2624
|
return `${prefix}_${crypto.randomUUID()}`;
|
|
2524
2625
|
}
|
|
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
2626
|
function renderBlock(block, level) {
|
|
2627
|
+
if (block.processingStatus !== "ready" || !block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints) {
|
|
2628
|
+
throw new Error(`Block ${block.id} is not ready for rendering`);
|
|
2629
|
+
}
|
|
2537
2630
|
if (level === 0) return `${block.l0Title}
|
|
2538
2631
|
Tags: ${block.l0Tags.join(", ") || "none"}`;
|
|
2539
|
-
if (level === 1) return block.l1Summary
|
|
2632
|
+
if (level === 1) return block.l1Summary;
|
|
2540
2633
|
if (level === 2) return block.l2Keypoints.map((point) => `- ${point}`).join("\n") || block.l1Summary;
|
|
2541
2634
|
if (level === 3) return block.l3Condensed;
|
|
2542
2635
|
if (level === 4) return block.l4Readable;
|
|
@@ -2553,6 +2646,8 @@ function errorMessage(error) {
|
|
|
2553
2646
|
return error instanceof Error ? error.message : String(error);
|
|
2554
2647
|
}
|
|
2555
2648
|
var STRATAGATE_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol("StrataGate constructor");
|
|
2649
|
+
var DERIVATION_MAX_ATTEMPTS = 3;
|
|
2650
|
+
var DERIVATION_BACKOFF_MS = 1e3;
|
|
2556
2651
|
var StrataGate = class _StrataGate {
|
|
2557
2652
|
blockTurnSizeValue;
|
|
2558
2653
|
blockDecayLambdaValue;
|
|
@@ -2572,6 +2667,7 @@ var StrataGate = class _StrataGate {
|
|
|
2572
2667
|
graphNodes = [];
|
|
2573
2668
|
graphEdges = [];
|
|
2574
2669
|
extractionJobs = /* @__PURE__ */ new Map();
|
|
2670
|
+
summaryJobs = /* @__PURE__ */ new Map();
|
|
2575
2671
|
elementProjectionJobs = /* @__PURE__ */ new Map();
|
|
2576
2672
|
graphProjectionJobs = /* @__PURE__ */ new Map();
|
|
2577
2673
|
usageReceipts = /* @__PURE__ */ new Map();
|
|
@@ -2680,6 +2776,21 @@ var StrataGate = class _StrataGate {
|
|
|
2680
2776
|
if (loaded && loadedSnapshot) {
|
|
2681
2777
|
memory.restoreSnapshot(loadedSnapshot);
|
|
2682
2778
|
memory.revision = loadedRevision;
|
|
2779
|
+
const interruptedSummaries = [...memory.summaryJobs.values()].filter((job) => job.status === "running");
|
|
2780
|
+
if (interruptedSummaries.length > 0) {
|
|
2781
|
+
await memory.commitMutation(() => {
|
|
2782
|
+
const now = toUtc8Iso(memory.now());
|
|
2783
|
+
for (const job of interruptedSummaries) {
|
|
2784
|
+
memory.summaryJobs.set(job.blockId, {
|
|
2785
|
+
...job,
|
|
2786
|
+
status: "failed",
|
|
2787
|
+
lastError: "Block summarization was interrupted before completion.",
|
|
2788
|
+
nextRetryAt: now,
|
|
2789
|
+
updatedAt: now
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2683
2794
|
const interrupted = [...memory.extractionJobs.values()].filter((job) => job.status === "running");
|
|
2684
2795
|
if (interrupted.length > 0) {
|
|
2685
2796
|
await memory.commitMutation(() => {
|
|
@@ -2689,6 +2800,7 @@ var StrataGate = class _StrataGate {
|
|
|
2689
2800
|
...job,
|
|
2690
2801
|
status: "failed",
|
|
2691
2802
|
lastError: "Extraction was interrupted before completion.",
|
|
2803
|
+
nextRetryAt: now,
|
|
2692
2804
|
updatedAt: now
|
|
2693
2805
|
});
|
|
2694
2806
|
}
|
|
@@ -2780,6 +2892,9 @@ var StrataGate = class _StrataGate {
|
|
|
2780
2892
|
listExtractionJobs() {
|
|
2781
2893
|
return [...this.extractionJobs.values()];
|
|
2782
2894
|
}
|
|
2895
|
+
listSummaryJobs() {
|
|
2896
|
+
return [...this.summaryJobs.values()];
|
|
2897
|
+
}
|
|
2783
2898
|
listElementProjectionJobs() {
|
|
2784
2899
|
return [...this.elementProjectionJobs.values()];
|
|
2785
2900
|
}
|
|
@@ -2812,6 +2927,7 @@ var StrataGate = class _StrataGate {
|
|
|
2812
2927
|
blockDecayLambda: this.blockDecayLambda,
|
|
2813
2928
|
openTail: this.openTail,
|
|
2814
2929
|
blocks: this.blocks,
|
|
2930
|
+
summaryJobs: [...this.summaryJobs.values()],
|
|
2815
2931
|
events: this.events,
|
|
2816
2932
|
graphNodes: this.graphNodes,
|
|
2817
2933
|
graphEdges: this.graphEdges,
|
|
@@ -2860,57 +2976,55 @@ var StrataGate = class _StrataGate {
|
|
|
2860
2976
|
if (receiptId) this.ingestionReceipts.set(receiptId, { id: receiptId, createdAt });
|
|
2861
2977
|
return true;
|
|
2862
2978
|
});
|
|
2863
|
-
if (!appended) return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
|
|
2979
|
+
if (!appended) return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: [] };
|
|
2864
2980
|
if (options.deferProcessing === true) {
|
|
2865
|
-
return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
|
|
2981
|
+
return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: [] };
|
|
2866
2982
|
}
|
|
2867
2983
|
if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
2868
2984
|
const projectedElements2 = await this.projectEligibleElements() ?? [];
|
|
2869
2985
|
await this.projectEligibleGraph();
|
|
2870
|
-
return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
|
|
2986
|
+
return { sealedBlock: null, readyBlocks: [], extractedEvents: [], projectedElements: projectedElements2 };
|
|
2871
2987
|
}
|
|
2872
2988
|
const sealedBlock = await this.sealOpenTail(threadId);
|
|
2873
|
-
|
|
2989
|
+
if (options.deferDerivation === true) {
|
|
2990
|
+
return { sealedBlock, readyBlocks: [], extractedEvents: [], projectedElements: [] };
|
|
2991
|
+
}
|
|
2992
|
+
const beforeReady = sealedBlock.processingStatus === "ready";
|
|
2993
|
+
const extractedEvents = await this.processBlock(sealedBlock, { retryFailed: false });
|
|
2994
|
+
const readyBlocks = !beforeReady && sealedBlock.processingStatus === "ready" ? [sealedBlock] : [];
|
|
2874
2995
|
const projectedElements = await this.projectEligibleElements() ?? [];
|
|
2875
2996
|
await this.projectEligibleGraph();
|
|
2876
|
-
return { sealedBlock, extractedEvents, projectedElements };
|
|
2997
|
+
return { sealedBlock, readyBlocks, extractedEvents, projectedElements };
|
|
2877
2998
|
}
|
|
2878
2999
|
async resumePendingWork(options = {}) {
|
|
2879
3000
|
const sealedBlocks = [];
|
|
3001
|
+
const readyBlocks = [];
|
|
2880
3002
|
const extractedEvents = [];
|
|
2881
3003
|
const projectedElements = [];
|
|
2882
3004
|
while (true) {
|
|
2883
3005
|
const sealable = this.nextSealableThread();
|
|
2884
3006
|
if (sealable === null) break;
|
|
2885
3007
|
sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
|
|
2886
|
-
extractedEvents.push(...await this.extractEligibleBlock() ?? []);
|
|
2887
|
-
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2888
|
-
await this.projectEligibleGraph();
|
|
2889
3008
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
3009
|
+
if (options.deferDerivation === true) {
|
|
3010
|
+
return { sealedBlocks, readyBlocks, extractedEvents, projectedElements };
|
|
3011
|
+
}
|
|
3012
|
+
for (const block of this.blocks) {
|
|
3013
|
+
if (options.threadId !== void 0 && block.threadId !== options.threadId) continue;
|
|
3014
|
+
if (block.processingStatus === "ready") continue;
|
|
3015
|
+
const extracted = await this.processBlock(block, { retryFailed: options.retryFailed === true });
|
|
2893
3016
|
extractedEvents.push(...extracted);
|
|
3017
|
+
if (this.blocks.find((candidate) => candidate.id === block.id)?.processingStatus === "ready") readyBlocks.push(block);
|
|
2894
3018
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
2895
3019
|
await this.projectEligibleGraph();
|
|
2896
3020
|
}
|
|
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
3021
|
while (true) {
|
|
2908
3022
|
const projected = await this.projectEligibleElements();
|
|
2909
3023
|
if (projected === null) break;
|
|
2910
3024
|
projectedElements.push(...projected);
|
|
2911
3025
|
}
|
|
2912
3026
|
await this.projectEligibleGraph();
|
|
2913
|
-
return { sealedBlocks, extractedEvents, projectedElements };
|
|
3027
|
+
return { sealedBlocks, readyBlocks, extractedEvents, projectedElements };
|
|
2914
3028
|
}
|
|
2915
3029
|
async addEvent(input) {
|
|
2916
3030
|
return this.commitMutation(() => {
|
|
@@ -3272,9 +3386,9 @@ var StrataGate = class _StrataGate {
|
|
|
3272
3386
|
* StrataGate namespace (never another namespace).
|
|
3273
3387
|
*/
|
|
3274
3388
|
getBlockContext(threadId) {
|
|
3275
|
-
const blocks = threadId === void 0 ? this.blocks : this.blocks.filter((block) => block.threadId === threadId);
|
|
3389
|
+
const blocks = threadId === void 0 ? this.blocks.filter((block) => block.processingStatus === "ready") : this.blocks.filter((block) => block.threadId === threadId && block.processingStatus === "ready");
|
|
3276
3390
|
return blocks.map((block) => {
|
|
3277
|
-
const threadBlocks = this.threadBlocks(block.threadId);
|
|
3391
|
+
const threadBlocks = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
|
|
3278
3392
|
const latestBlockPosition = threadBlocks.length;
|
|
3279
3393
|
const blockPosition = threadBlocks.indexOf(block) + 1;
|
|
3280
3394
|
const age = Math.max(0, latestBlockPosition - blockPosition);
|
|
@@ -3300,8 +3414,10 @@ var StrataGate = class _StrataGate {
|
|
|
3300
3414
|
return this.commitMutation(() => {
|
|
3301
3415
|
const block = this.blocks.find((candidate) => candidate.id === id);
|
|
3302
3416
|
if (!block) throw new Error(`Unknown block: ${id}`);
|
|
3303
|
-
|
|
3304
|
-
const
|
|
3417
|
+
if (block.processingStatus !== "ready") throw new Error(`Block ${id} is not ready for decay or expansion`);
|
|
3418
|
+
const readyBlocks = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
|
|
3419
|
+
const latestBlockPosition = readyBlocks.length;
|
|
3420
|
+
const blockPosition = readyBlocks.indexOf(block) + 1;
|
|
3305
3421
|
const current = getDecayedBlockLevel(
|
|
3306
3422
|
block.pointerAnchorLevel,
|
|
3307
3423
|
block.pointerAnchorBlockPosition,
|
|
@@ -3420,6 +3536,7 @@ var StrataGate = class _StrataGate {
|
|
|
3420
3536
|
l1Summary: text3.replace(/\s+/gu, " ").trim().slice(0, 500),
|
|
3421
3537
|
l2Keypoints: text3.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).slice(0, 8),
|
|
3422
3538
|
shouldExtract: false,
|
|
3539
|
+
processingStatus: "ready",
|
|
3423
3540
|
...deterministicBlockLayers([message]),
|
|
3424
3541
|
pointerCurrentLevel: 5,
|
|
3425
3542
|
pointerAnchorLevel: 5,
|
|
@@ -3636,7 +3753,6 @@ var StrataGate = class _StrataGate {
|
|
|
3636
3753
|
if (raw.filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
3637
3754
|
throw new Error("Open tail does not contain enough turns to seal a block");
|
|
3638
3755
|
}
|
|
3639
|
-
const generated = this.summarizer ? await this.summarizer(raw) : defaultSummary(raw);
|
|
3640
3756
|
const deterministic = deterministicBlockLayers(raw);
|
|
3641
3757
|
const sequence = this.blocks.length + 1;
|
|
3642
3758
|
const threadBlocks = this.threadBlocks(threadId);
|
|
@@ -3656,11 +3772,7 @@ var StrataGate = class _StrataGate {
|
|
|
3656
3772
|
startTurn,
|
|
3657
3773
|
endTurn,
|
|
3658
3774
|
createdAt: raw.at(-1)?.createdAt ?? toUtc8Iso(this.now()),
|
|
3659
|
-
|
|
3660
|
-
l0Tags: generated.l0Tags,
|
|
3661
|
-
l1Summary: generated.l1Summary,
|
|
3662
|
-
l2Keypoints: generated.l2Keypoints,
|
|
3663
|
-
shouldExtract: generated.shouldExtract,
|
|
3775
|
+
processingStatus: "pending",
|
|
3664
3776
|
...deterministic,
|
|
3665
3777
|
pointerCurrentLevel: 5,
|
|
3666
3778
|
pointerAnchorLevel: 5,
|
|
@@ -3672,27 +3784,132 @@ var StrataGate = class _StrataGate {
|
|
|
3672
3784
|
const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
|
|
3673
3785
|
this.openTail.splice(0, this.openTail.length, ...remaining);
|
|
3674
3786
|
this.blocks.push(block);
|
|
3787
|
+
const updatedAt = toUtc8Iso(this.now());
|
|
3788
|
+
this.summaryJobs.set(block.id, {
|
|
3789
|
+
blockId: block.id,
|
|
3790
|
+
status: "pending",
|
|
3791
|
+
attempts: 0,
|
|
3792
|
+
lastError: null,
|
|
3793
|
+
nextRetryAt: null,
|
|
3794
|
+
updatedAt
|
|
3795
|
+
});
|
|
3675
3796
|
return block;
|
|
3676
3797
|
});
|
|
3677
3798
|
}
|
|
3799
|
+
async processBlock(block, options) {
|
|
3800
|
+
if (block.processingStatus === "ready") return [];
|
|
3801
|
+
const summary = this.summaryJobs.get(block.id);
|
|
3802
|
+
if (!summary || summary.status !== "succeeded") {
|
|
3803
|
+
if (!this.summarizer || !this.jobCanRun(summary, options.retryFailed)) return [];
|
|
3804
|
+
const claimed = await this.commitMutation(() => {
|
|
3805
|
+
const current = this.summaryJobs.get(block.id);
|
|
3806
|
+
if (!current || !this.jobCanRun(current, options.retryFailed)) return false;
|
|
3807
|
+
this.summaryJobs.set(block.id, {
|
|
3808
|
+
...current,
|
|
3809
|
+
status: "running",
|
|
3810
|
+
attempts: current.attempts + 1,
|
|
3811
|
+
lastError: null,
|
|
3812
|
+
nextRetryAt: null,
|
|
3813
|
+
updatedAt: toUtc8Iso(this.now())
|
|
3814
|
+
});
|
|
3815
|
+
return true;
|
|
3816
|
+
});
|
|
3817
|
+
if (!claimed) return [];
|
|
3818
|
+
try {
|
|
3819
|
+
const generated = await this.summarizer(block.l5Raw);
|
|
3820
|
+
if (!generated.l0Title.trim() || !generated.l1Summary.trim() || !Array.isArray(generated.l0Tags) || !Array.isArray(generated.l2Keypoints) || typeof generated.shouldExtract !== "boolean") {
|
|
3821
|
+
throw new Error("Block summarizer returned invalid L0-L2 layers");
|
|
3822
|
+
}
|
|
3823
|
+
await this.commitMutation(() => {
|
|
3824
|
+
block.l0Title = generated.l0Title;
|
|
3825
|
+
block.l0Tags = [...generated.l0Tags];
|
|
3826
|
+
block.l1Summary = generated.l1Summary;
|
|
3827
|
+
block.l2Keypoints = [...generated.l2Keypoints];
|
|
3828
|
+
block.shouldExtract = generated.shouldExtract;
|
|
3829
|
+
const current = this.summaryJobs.get(block.id);
|
|
3830
|
+
if (!current) throw new Error(`Missing summary job for block: ${block.id}`);
|
|
3831
|
+
this.summaryJobs.set(block.id, {
|
|
3832
|
+
...current,
|
|
3833
|
+
status: "succeeded",
|
|
3834
|
+
lastError: null,
|
|
3835
|
+
nextRetryAt: null,
|
|
3836
|
+
updatedAt: toUtc8Iso(this.now())
|
|
3837
|
+
});
|
|
3838
|
+
});
|
|
3839
|
+
} catch (error) {
|
|
3840
|
+
await this.failSummary(block.id, error);
|
|
3841
|
+
return [];
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
if (block.shouldExtract === false) {
|
|
3845
|
+
await this.commitMutation(() => {
|
|
3846
|
+
const now = toUtc8Iso(this.now());
|
|
3847
|
+
this.extractionJobs.set(block.id, {
|
|
3848
|
+
blockId: block.id,
|
|
3849
|
+
status: "skipped",
|
|
3850
|
+
attempts: this.extractionJobs.get(block.id)?.attempts ?? 0,
|
|
3851
|
+
lastError: null,
|
|
3852
|
+
nextRetryAt: null,
|
|
3853
|
+
updatedAt: now
|
|
3854
|
+
});
|
|
3855
|
+
this.markBlockReady(block);
|
|
3856
|
+
});
|
|
3857
|
+
return [];
|
|
3858
|
+
}
|
|
3859
|
+
try {
|
|
3860
|
+
const extracted = await this.extractEligibleBlock({ blockId: block.id, retryFailed: options.retryFailed });
|
|
3861
|
+
return extracted ?? [];
|
|
3862
|
+
} catch {
|
|
3863
|
+
return [];
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
jobCanRun(job, force) {
|
|
3867
|
+
if (!job || job.attempts >= DERIVATION_MAX_ATTEMPTS || job.status === "running" || job.status === "succeeded") return false;
|
|
3868
|
+
return force || job.nextRetryAt === null || Date.parse(job.nextRetryAt) <= this.now().getTime();
|
|
3869
|
+
}
|
|
3870
|
+
async failSummary(blockId, error) {
|
|
3871
|
+
await this.commitMutation(() => {
|
|
3872
|
+
const job = this.summaryJobs.get(blockId);
|
|
3873
|
+
if (!job) return;
|
|
3874
|
+
this.summaryJobs.set(blockId, {
|
|
3875
|
+
...job,
|
|
3876
|
+
status: "failed",
|
|
3877
|
+
lastError: errorMessage(error),
|
|
3878
|
+
nextRetryAt: this.retryAt(job.attempts),
|
|
3879
|
+
updatedAt: toUtc8Iso(this.now())
|
|
3880
|
+
});
|
|
3881
|
+
});
|
|
3882
|
+
}
|
|
3883
|
+
retryAt(attempts) {
|
|
3884
|
+
if (attempts >= DERIVATION_MAX_ATTEMPTS) return null;
|
|
3885
|
+
return toUtc8Iso(new Date(this.now().getTime() + DERIVATION_BACKOFF_MS * 2 ** Math.max(0, attempts - 1)));
|
|
3886
|
+
}
|
|
3887
|
+
markBlockReady(block) {
|
|
3888
|
+
if (!block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints || typeof block.shouldExtract !== "boolean") {
|
|
3889
|
+
throw new Error(`Block ${block.id} cannot become ready without validated L0-L2 layers`);
|
|
3890
|
+
}
|
|
3891
|
+
const ready = this.threadBlocks(block.threadId).filter((candidate) => candidate.processingStatus === "ready");
|
|
3892
|
+
block.processingStatus = "ready";
|
|
3893
|
+
block.pointerCurrentLevel = 5;
|
|
3894
|
+
block.pointerAnchorLevel = 5;
|
|
3895
|
+
block.pointerAnchorBlockPosition = ready.length + 1;
|
|
3896
|
+
}
|
|
3678
3897
|
async extractEligibleBlock(options = {}) {
|
|
3679
|
-
if (!this.extractor
|
|
3898
|
+
if (!this.extractor) return null;
|
|
3680
3899
|
const target = this.blocks.find((block) => {
|
|
3681
|
-
if (
|
|
3900
|
+
if (block.processingStatus === "ready" || block.shouldExtract !== true) return false;
|
|
3682
3901
|
if (options.blockId !== void 0 && block.id !== options.blockId) return false;
|
|
3683
|
-
const
|
|
3684
|
-
return
|
|
3902
|
+
const job = this.extractionJobs.get(block.id);
|
|
3903
|
+
return job === void 0 || this.jobCanRun(job, options.retryFailed === true);
|
|
3685
3904
|
});
|
|
3686
3905
|
if (!target) return null;
|
|
3687
3906
|
const threadBlocks = this.threadBlocks(target.threadId);
|
|
3688
3907
|
const targetIndex = threadBlocks.indexOf(target);
|
|
3689
|
-
const next = threadBlocks
|
|
3690
|
-
if (!next) return null;
|
|
3908
|
+
const next = threadBlocks.slice(targetIndex + 1).find((block) => block.l2Keypoints !== void 0) ?? null;
|
|
3691
3909
|
const existing = this.extractionJobs.get(target.id);
|
|
3692
3910
|
await this.commitMutation(() => {
|
|
3693
3911
|
const currentStatus = this.extractionJobs.get(target.id)?.status;
|
|
3694
|
-
|
|
3695
|
-
if (currentStatus !== void 0 && currentStatus !== "failed" && !canRetrySkipped) {
|
|
3912
|
+
if (currentStatus !== void 0 && currentStatus !== "failed") {
|
|
3696
3913
|
throw new Error(`Extraction block ${target.id} is already ${currentStatus}`);
|
|
3697
3914
|
}
|
|
3698
3915
|
this.extractionJobs.set(target.id, {
|
|
@@ -3700,13 +3917,14 @@ var StrataGate = class _StrataGate {
|
|
|
3700
3917
|
status: "running",
|
|
3701
3918
|
attempts: (existing?.attempts ?? 0) + 1,
|
|
3702
3919
|
lastError: null,
|
|
3920
|
+
nextRetryAt: null,
|
|
3703
3921
|
updatedAt: toUtc8Iso(this.now())
|
|
3704
3922
|
});
|
|
3705
3923
|
});
|
|
3706
3924
|
let result;
|
|
3707
3925
|
try {
|
|
3708
3926
|
result = await this.extractor({
|
|
3709
|
-
previous: threadBlocks
|
|
3927
|
+
previous: threadBlocks.slice(0, targetIndex).reverse().find((block) => block.l2Keypoints !== void 0) ?? null,
|
|
3710
3928
|
target,
|
|
3711
3929
|
next,
|
|
3712
3930
|
timeline: this.events.map((event) => ({ id: event.id, title: event.title, temporal: event.temporal }))
|
|
@@ -3719,6 +3937,7 @@ var StrataGate = class _StrataGate {
|
|
|
3719
3937
|
...job,
|
|
3720
3938
|
status: "failed",
|
|
3721
3939
|
lastError: errorMessage(error),
|
|
3940
|
+
nextRetryAt: this.retryAt(job.attempts),
|
|
3722
3941
|
updatedAt: toUtc8Iso(this.now())
|
|
3723
3942
|
});
|
|
3724
3943
|
});
|
|
@@ -3733,6 +3952,7 @@ var StrataGate = class _StrataGate {
|
|
|
3733
3952
|
...job,
|
|
3734
3953
|
status: "failed",
|
|
3735
3954
|
lastError: reason,
|
|
3955
|
+
nextRetryAt: this.retryAt(job.attempts),
|
|
3736
3956
|
updatedAt: toUtc8Iso(this.now())
|
|
3737
3957
|
});
|
|
3738
3958
|
});
|
|
@@ -3751,8 +3971,10 @@ var StrataGate = class _StrataGate {
|
|
|
3751
3971
|
...job,
|
|
3752
3972
|
status: result.shouldExtract ? "succeeded" : "skipped",
|
|
3753
3973
|
lastError: null,
|
|
3974
|
+
nextRetryAt: null,
|
|
3754
3975
|
updatedAt: toUtc8Iso(this.now())
|
|
3755
3976
|
});
|
|
3977
|
+
this.markBlockReady(target);
|
|
3756
3978
|
return extracted;
|
|
3757
3979
|
});
|
|
3758
3980
|
}
|
|
@@ -3821,6 +4043,8 @@ var StrataGate = class _StrataGate {
|
|
|
3821
4043
|
this.currentTurn = copy.currentTurn;
|
|
3822
4044
|
this.openTail.splice(0, this.openTail.length, ...copy.openTail);
|
|
3823
4045
|
this.blocks.splice(0, this.blocks.length, ...copy.blocks);
|
|
4046
|
+
this.summaryJobs.clear();
|
|
4047
|
+
for (const job of copy.summaryJobs) this.summaryJobs.set(job.blockId, job);
|
|
3824
4048
|
this.events.splice(0, this.events.length, ...copy.events);
|
|
3825
4049
|
this.graphNodes.splice(0, this.graphNodes.length, ...copy.graphNodes);
|
|
3826
4050
|
this.graphEdges.splice(0, this.graphEdges.length, ...copy.graphEdges);
|
|
@@ -3867,6 +4091,9 @@ var StrataGate = class _StrataGate {
|
|
|
3867
4091
|
for (const job of this.extractionJobs.values()) {
|
|
3868
4092
|
if (!blockIds.has(job.blockId)) throw new Error(`Unknown extraction job block in snapshot: ${job.blockId}`);
|
|
3869
4093
|
}
|
|
4094
|
+
for (const job of this.summaryJobs.values()) {
|
|
4095
|
+
if (!blockIds.has(job.blockId)) throw new Error(`Unknown summary job block in snapshot: ${job.blockId}`);
|
|
4096
|
+
}
|
|
3870
4097
|
const elementIds = /* @__PURE__ */ new Set();
|
|
3871
4098
|
for (const element of this.elements) {
|
|
3872
4099
|
if (elementIds.has(element.id)) throw new Error(`Duplicate element ID in snapshot: ${element.id}`);
|
|
@@ -4095,7 +4322,7 @@ function extractorPayload(context) {
|
|
|
4095
4322
|
}
|
|
4096
4323
|
var JSON_RESPONSE_ATTEMPTS = 2;
|
|
4097
4324
|
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
|
|
4325
|
+
var DEFAULT_STRUCTURED_TIMEOUT_MS = 45e3;
|
|
4099
4326
|
var STRUCTURED_FIELDS = {
|
|
4100
4327
|
summarizer: ["l0Title", "l0Tags", "l1Summary", "l2Keypoints", "shouldExtract"],
|
|
4101
4328
|
extractor: ["shouldExtract", "reason", "events"],
|
|
@@ -4246,11 +4473,15 @@ var DshModelBridge = class {
|
|
|
4246
4473
|
constructor(ctx, config) {
|
|
4247
4474
|
this.ctx = ctx;
|
|
4248
4475
|
this.config = config;
|
|
4476
|
+
this.ctx.on?.("llm/adapters-updated", () => {
|
|
4477
|
+
this.offCapabilities.clear();
|
|
4478
|
+
});
|
|
4249
4479
|
}
|
|
4250
4480
|
ctx;
|
|
4251
4481
|
config;
|
|
4252
4482
|
sessions = new AsyncLocalStorage();
|
|
4253
4483
|
successfulResponses = [];
|
|
4484
|
+
offCapabilities = /* @__PURE__ */ new Map();
|
|
4254
4485
|
run(session, operation) {
|
|
4255
4486
|
return this.sessions.run(session, operation);
|
|
4256
4487
|
}
|
|
@@ -4265,7 +4496,7 @@ var DshModelBridge = class {
|
|
|
4265
4496
|
{ messages }
|
|
4266
4497
|
));
|
|
4267
4498
|
return {
|
|
4268
|
-
l0Title: text2(raw.l0Title
|
|
4499
|
+
l0Title: text2(raw.l0Title).slice(0, 120),
|
|
4269
4500
|
l0Tags: strings2(raw.l0Tags).slice(0, 12),
|
|
4270
4501
|
l1Summary: text2(raw.l1Summary).slice(0, 2e3),
|
|
4271
4502
|
l2Keypoints: strings2(raw.l2Keypoints).slice(0, 20),
|
|
@@ -4397,10 +4628,11 @@ var DshModelBridge = class {
|
|
|
4397
4628
|
async callStructured(kind, system, payload) {
|
|
4398
4629
|
const session = this.sessions.getStore();
|
|
4399
4630
|
if (!session) throw new Error("StrataGate model callback ran without a DSH session");
|
|
4400
|
-
const
|
|
4631
|
+
const baseRoute = this.resolveRoute(session);
|
|
4632
|
+
const routeKey = `${baseRoute.provider}\0${baseRoute.model}`;
|
|
4633
|
+
let useOff = await this.shouldUseOff(baseRoute);
|
|
4401
4634
|
let lastError;
|
|
4402
4635
|
let lastResponse = "";
|
|
4403
|
-
let retryMaxTokens = this.config.maxOutputTokens;
|
|
4404
4636
|
for (let attempt = 1; attempt <= JSON_RESPONSE_ATTEMPTS; attempt += 1) {
|
|
4405
4637
|
const message = createUserMessage({
|
|
4406
4638
|
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
@@ -4408,7 +4640,8 @@ var DshModelBridge = class {
|
|
|
4408
4640
|
});
|
|
4409
4641
|
const assembler = new BlockAssembler();
|
|
4410
4642
|
const request = {
|
|
4411
|
-
...
|
|
4643
|
+
...baseRoute,
|
|
4644
|
+
...useOff ? { reasoningEffort: "off" } : {},
|
|
4412
4645
|
messages: [message],
|
|
4413
4646
|
system: attempt === 1 ? system : `${system}
|
|
4414
4647
|
|
|
@@ -4422,15 +4655,35 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4422
4655
|
type: "function",
|
|
4423
4656
|
function: { name: STRUCTURED_TOOLS[kind].name }
|
|
4424
4657
|
},
|
|
4425
|
-
maxTokens:
|
|
4658
|
+
maxTokens: this.config.maxOutputTokens,
|
|
4426
4659
|
sessionId: session.id,
|
|
4427
4660
|
purpose: "compaction"
|
|
4428
4661
|
};
|
|
4429
|
-
|
|
4662
|
+
try {
|
|
4663
|
+
await this.consumeStructuredStream(request, assembler);
|
|
4664
|
+
} catch (error) {
|
|
4665
|
+
if (useOff && isOffRejection(error)) {
|
|
4666
|
+
this.offCapabilities.set(routeKey, "unsupported");
|
|
4667
|
+
useOff = false;
|
|
4668
|
+
this.ctx.logger.warn(`stratagate-memory ${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
|
|
4669
|
+
attempt -= 1;
|
|
4670
|
+
continue;
|
|
4671
|
+
}
|
|
4672
|
+
throw error;
|
|
4673
|
+
}
|
|
4430
4674
|
const finish = assembler.finish;
|
|
4431
4675
|
if (finish.kind === "error" || finish.kind === "aborted") {
|
|
4432
|
-
|
|
4676
|
+
const failure = new Error(`StrataGate model call failed [${finish.failure.code}]: ${finish.failure.message}`);
|
|
4677
|
+
if (useOff && isOffRejection(finish.failure)) {
|
|
4678
|
+
this.offCapabilities.set(routeKey, "unsupported");
|
|
4679
|
+
useOff = false;
|
|
4680
|
+
this.ctx.logger.warn(`stratagate-memory ${baseRoute.provider}/${baseRoute.model} rejected reasoningEffort=off; retrying once without it`);
|
|
4681
|
+
attempt -= 1;
|
|
4682
|
+
continue;
|
|
4683
|
+
}
|
|
4684
|
+
throw failure;
|
|
4433
4685
|
}
|
|
4686
|
+
if (useOff) this.offCapabilities.set(routeKey, "supported");
|
|
4434
4687
|
const blocks = assembler.blocks();
|
|
4435
4688
|
const calls = blocks.filter((block) => block.type === "tool-call");
|
|
4436
4689
|
const responseForError = `${renderBlocksForDiagnostics(blocks, finish.kind)}
|
|
@@ -4466,6 +4719,15 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4466
4719
|
{ response: responseForError }
|
|
4467
4720
|
);
|
|
4468
4721
|
}
|
|
4722
|
+
if (kind === "summarizer") {
|
|
4723
|
+
const summary = object(parsed);
|
|
4724
|
+
if (!text2(summary.l0Title) || !text2(summary.l1Summary)) {
|
|
4725
|
+
throw new ModelJsonResponseError(
|
|
4726
|
+
`StrataGate ${expectedTool} arguments were invalid: l0Title and l1Summary must not be empty`,
|
|
4727
|
+
{ response: responseForError }
|
|
4728
|
+
);
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4469
4731
|
this.successfulResponses.push({
|
|
4470
4732
|
id: `model_response_${crypto.randomUUID()}`,
|
|
4471
4733
|
kind,
|
|
@@ -4480,7 +4742,6 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4480
4742
|
`StrataGate ${STRUCTURED_TOOLS[kind].name} call was truncated before valid arguments`,
|
|
4481
4743
|
{ cause: error, response: responseForError }
|
|
4482
4744
|
) : error;
|
|
4483
|
-
if (finish.kind === "max-tokens") retryMaxTokens = Math.max(this.config.maxOutputTokens, RETRY_MAX_TOKENS);
|
|
4484
4745
|
if (attempt < JSON_RESPONSE_ATTEMPTS) {
|
|
4485
4746
|
this.ctx.logger.warn(`stratagate-memory model returned an invalid structured tool call; retrying (${attempt}/${JSON_RESPONSE_ATTEMPTS})`);
|
|
4486
4747
|
}
|
|
@@ -4491,25 +4752,77 @@ ${JSON_RETRY_INSTRUCTION}`,
|
|
|
4491
4752
|
{ cause: lastError, response: lastResponse }
|
|
4492
4753
|
);
|
|
4493
4754
|
}
|
|
4494
|
-
|
|
4495
|
-
const
|
|
4496
|
-
const
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4755
|
+
async shouldUseOff(route) {
|
|
4756
|
+
const key = `${route.provider}\0${route.model}`;
|
|
4757
|
+
const cached = this.offCapabilities.get(key);
|
|
4758
|
+
if (cached) return cached === "supported";
|
|
4759
|
+
if (typeof this.ctx.llm.resolveModelInfo !== "function") return true;
|
|
4760
|
+
const controller = new AbortController();
|
|
4761
|
+
const lookupTimeoutMs = Math.min(5e3, this.config.structuredTaskTimeoutMs ?? DEFAULT_STRUCTURED_TIMEOUT_MS);
|
|
4762
|
+
let timer;
|
|
4763
|
+
try {
|
|
4764
|
+
const info = await Promise.race([
|
|
4765
|
+
this.ctx.llm.resolveModelInfo(route.provider, route.model, controller.signal),
|
|
4766
|
+
new Promise((_resolve, reject) => {
|
|
4767
|
+
timer = setTimeout(() => {
|
|
4768
|
+
controller.abort();
|
|
4769
|
+
reject(new Error(`Model capability lookup timed out after ${lookupTimeoutMs}ms`));
|
|
4770
|
+
}, lookupTimeoutMs);
|
|
4771
|
+
timer.unref?.();
|
|
4772
|
+
})
|
|
4773
|
+
]);
|
|
4774
|
+
if (!info.reasoning) return true;
|
|
4775
|
+
const supported = info.reasoning.efforts.some(({ id }) => String(id) === "off");
|
|
4776
|
+
this.offCapabilities.set(key, supported ? "supported" : "unsupported");
|
|
4777
|
+
return supported;
|
|
4778
|
+
} catch {
|
|
4779
|
+
return true;
|
|
4780
|
+
} finally {
|
|
4781
|
+
if (timer) clearTimeout(timer);
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
async consumeStructuredStream(request, assembler) {
|
|
4785
|
+
const timeoutMs = this.config.structuredTaskTimeoutMs ?? DEFAULT_STRUCTURED_TIMEOUT_MS;
|
|
4786
|
+
const controller = new AbortController();
|
|
4787
|
+
const timedRequest = { ...request, signal: controller.signal };
|
|
4788
|
+
let timer;
|
|
4789
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
4790
|
+
timer = setTimeout(() => {
|
|
4791
|
+
controller.abort(new Error(`StrataGate structured model task timed out after ${timeoutMs}ms`));
|
|
4792
|
+
reject(new Error(`StrataGate structured model task timed out after ${timeoutMs}ms`));
|
|
4793
|
+
}, timeoutMs);
|
|
4794
|
+
timer.unref?.();
|
|
4500
4795
|
});
|
|
4796
|
+
try {
|
|
4797
|
+
await Promise.race([
|
|
4798
|
+
(async () => {
|
|
4799
|
+
for await (const chunk of this.ctx.llm.stream(timedRequest)) assembler.push(chunk);
|
|
4800
|
+
})(),
|
|
4801
|
+
timeout
|
|
4802
|
+
]);
|
|
4803
|
+
} finally {
|
|
4804
|
+
if (timer) clearTimeout(timer);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
resolveRoute(session) {
|
|
4808
|
+
const request = session.requestHeader()?.config;
|
|
4501
4809
|
if (this.config.provider && this.config.model) {
|
|
4502
|
-
return
|
|
4810
|
+
return { provider: this.config.provider, model: this.config.model };
|
|
4503
4811
|
}
|
|
4504
|
-
if (request) return
|
|
4812
|
+
if (request) return { provider: request.provider, model: request.model };
|
|
4505
4813
|
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
|
-
};
|
|
4814
|
+
return { provider: fallback.provider, model: fallback.model };
|
|
4511
4815
|
}
|
|
4512
4816
|
};
|
|
4817
|
+
function isOffRejection(error) {
|
|
4818
|
+
let detail;
|
|
4819
|
+
try {
|
|
4820
|
+
detail = typeof error === "string" ? error : error && typeof error === "object" ? JSON.stringify(error, Object.getOwnPropertyNames(error)) : String(error);
|
|
4821
|
+
} catch {
|
|
4822
|
+
detail = error instanceof Error ? error.message : String(error);
|
|
4823
|
+
}
|
|
4824
|
+
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);
|
|
4825
|
+
}
|
|
4513
4826
|
|
|
4514
4827
|
// src/runtime.ts
|
|
4515
4828
|
import { createHash } from "node:crypto";
|
|
@@ -4736,6 +5049,9 @@ var StrataGateRuntime = class {
|
|
|
4736
5049
|
latestBatchIds = /* @__PURE__ */ new Map();
|
|
4737
5050
|
workspaceNames = /* @__PURE__ */ new Map();
|
|
4738
5051
|
migrationTimers = /* @__PURE__ */ new Map();
|
|
5052
|
+
derivationTimers = /* @__PURE__ */ new Map();
|
|
5053
|
+
derivationRuns = /* @__PURE__ */ new Map();
|
|
5054
|
+
adminSnapshotCache = /* @__PURE__ */ new Map();
|
|
4739
5055
|
ingestTail = Promise.resolve();
|
|
4740
5056
|
settingsTail = Promise.resolve();
|
|
4741
5057
|
batchSequence = 0;
|
|
@@ -4752,15 +5068,8 @@ var StrataGateRuntime = class {
|
|
|
4752
5068
|
}).then(async () => {
|
|
4753
5069
|
const memory = await this.space(session);
|
|
4754
5070
|
try {
|
|
4755
|
-
const result = await
|
|
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
|
-
}
|
|
5071
|
+
const result = await memory.appendTurn(turn, { deferDerivation: true });
|
|
5072
|
+
if (result.sealedBlock) this.scheduleBlockDerivation(session, memory);
|
|
4764
5073
|
} finally {
|
|
4765
5074
|
await this.persistSuccessfulResponses(memory);
|
|
4766
5075
|
}
|
|
@@ -5074,6 +5383,8 @@ var StrataGateRuntime = class {
|
|
|
5074
5383
|
this.closed = true;
|
|
5075
5384
|
for (const timer of this.migrationTimers.values()) clearTimeout(timer);
|
|
5076
5385
|
this.migrationTimers.clear();
|
|
5386
|
+
for (const timer of this.derivationTimers.values()) clearTimeout(timer);
|
|
5387
|
+
this.derivationTimers.clear();
|
|
5077
5388
|
let flushError;
|
|
5078
5389
|
try {
|
|
5079
5390
|
await this.flush();
|
|
@@ -5081,6 +5392,7 @@ var StrataGateRuntime = class {
|
|
|
5081
5392
|
flushError = error;
|
|
5082
5393
|
}
|
|
5083
5394
|
const settled = await Promise.allSettled(this.spaces.values());
|
|
5395
|
+
await Promise.allSettled(this.derivationRuns.values());
|
|
5084
5396
|
await Promise.all(settled.flatMap((result) => result.status === "fulfilled" ? [result.value.close()] : []));
|
|
5085
5397
|
if (flushError !== void 0) throw flushError;
|
|
5086
5398
|
}
|
|
@@ -5099,6 +5411,42 @@ var StrataGateRuntime = class {
|
|
|
5099
5411
|
await storage.close();
|
|
5100
5412
|
}
|
|
5101
5413
|
}
|
|
5414
|
+
async adminSnapshotEntries() {
|
|
5415
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return [];
|
|
5416
|
+
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
5417
|
+
try {
|
|
5418
|
+
const entries = [];
|
|
5419
|
+
for (const { namespace, revision } of storage.listNamespaceRevisions()) {
|
|
5420
|
+
const opening = this.spaces.get(namespace);
|
|
5421
|
+
if (opening) {
|
|
5422
|
+
const memory = await opening;
|
|
5423
|
+
const currentRevision = memory.storageRevision;
|
|
5424
|
+
const cached2 = this.adminSnapshotCache.get(namespace);
|
|
5425
|
+
const entry2 = cached2?.revision === currentRevision ? cached2 : { namespace, revision: currentRevision, snapshot: memory.exportSnapshot() };
|
|
5426
|
+
this.adminSnapshotCache.set(namespace, entry2);
|
|
5427
|
+
entries.push(entry2);
|
|
5428
|
+
continue;
|
|
5429
|
+
}
|
|
5430
|
+
const cached = this.adminSnapshotCache.get(namespace);
|
|
5431
|
+
if (cached?.revision === revision) {
|
|
5432
|
+
entries.push(cached);
|
|
5433
|
+
continue;
|
|
5434
|
+
}
|
|
5435
|
+
const loaded = await storage.load(namespace);
|
|
5436
|
+
if (!loaded) continue;
|
|
5437
|
+
const entry = { namespace, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
5438
|
+
this.adminSnapshotCache.set(namespace, entry);
|
|
5439
|
+
entries.push(entry);
|
|
5440
|
+
}
|
|
5441
|
+
const activeNamespaces = new Set(entries.map(({ namespace }) => namespace));
|
|
5442
|
+
for (const namespace of this.adminSnapshotCache.keys()) {
|
|
5443
|
+
if (!activeNamespaces.has(namespace)) this.adminSnapshotCache.delete(namespace);
|
|
5444
|
+
}
|
|
5445
|
+
return entries;
|
|
5446
|
+
} finally {
|
|
5447
|
+
await storage.close();
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5102
5450
|
async syncConfiguredSettings() {
|
|
5103
5451
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return;
|
|
5104
5452
|
const metadata = new DshMetadataStore(this.config.database);
|
|
@@ -5127,9 +5475,27 @@ var StrataGateRuntime = class {
|
|
|
5127
5475
|
const key = namespace.trim();
|
|
5128
5476
|
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
5129
5477
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
5478
|
+
const opening = this.spaces.get(key);
|
|
5479
|
+
if (opening) {
|
|
5480
|
+
const memory = await opening;
|
|
5481
|
+
const revision = memory.storageRevision;
|
|
5482
|
+
const cached = this.adminSnapshotCache.get(key);
|
|
5483
|
+
if (cached?.revision === revision) return cached.snapshot;
|
|
5484
|
+
const entry = { namespace: key, revision, snapshot: memory.exportSnapshot() };
|
|
5485
|
+
this.adminSnapshotCache.set(key, entry);
|
|
5486
|
+
return entry.snapshot;
|
|
5487
|
+
}
|
|
5130
5488
|
const storage = new SqliteStorage({ filename: this.config.database, readonly: true });
|
|
5131
5489
|
try {
|
|
5132
|
-
|
|
5490
|
+
const head = storage.listNamespaceRevisions().find(({ namespace: namespace2 }) => namespace2 === key);
|
|
5491
|
+
if (!head) return null;
|
|
5492
|
+
const cached = this.adminSnapshotCache.get(key);
|
|
5493
|
+
if (cached?.revision === head.revision) return cached.snapshot;
|
|
5494
|
+
const loaded = await storage.load(key);
|
|
5495
|
+
if (!loaded) return null;
|
|
5496
|
+
const entry = { namespace: key, revision: loaded.revision, snapshot: loaded.snapshot };
|
|
5497
|
+
this.adminSnapshotCache.set(key, entry);
|
|
5498
|
+
return entry.snapshot;
|
|
5133
5499
|
} finally {
|
|
5134
5500
|
await storage.close();
|
|
5135
5501
|
}
|
|
@@ -5319,23 +5685,14 @@ var StrataGateRuntime = class {
|
|
|
5319
5685
|
}).then(async (memory) => {
|
|
5320
5686
|
try {
|
|
5321
5687
|
try {
|
|
5322
|
-
|
|
5688
|
+
await memory.resumePendingWork({ deferDerivation: true, threadId: String(session.id) });
|
|
5323
5689
|
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
5690
|
this.syncDecayedBlockSurface(session, contexts);
|
|
5332
|
-
if (resumed.sealedBlocks.some((block) => block.threadId === String(session.id))) {
|
|
5333
|
-
await this.flushNativeSession(session);
|
|
5334
|
-
}
|
|
5335
5691
|
} finally {
|
|
5336
5692
|
await this.persistSuccessfulResponses(memory);
|
|
5337
5693
|
}
|
|
5338
5694
|
this.scheduleGraphMigration(session, memory);
|
|
5695
|
+
this.scheduleBlockDerivation(session, memory);
|
|
5339
5696
|
return memory;
|
|
5340
5697
|
} catch (error) {
|
|
5341
5698
|
await memory.close().catch(() => {
|
|
@@ -5369,6 +5726,43 @@ var StrataGateRuntime = class {
|
|
|
5369
5726
|
target: { eventIds: [.../* @__PURE__ */ new Set([...node.sourceEventIds, ...edges.flatMap(({ sourceEventIds }) => sourceEventIds)])], elementIds: [] }
|
|
5370
5727
|
}], { node, edges });
|
|
5371
5728
|
}
|
|
5729
|
+
scheduleBlockDerivation(session, memory) {
|
|
5730
|
+
const threadId = String(session.id);
|
|
5731
|
+
const key = `${this.namespaceFor(session)}\0${threadId}`;
|
|
5732
|
+
if (this.closed || this.derivationTimers.has(key) || this.derivationRuns.has(key)) return;
|
|
5733
|
+
const pendingBlockIds = new Set(memory.listBlocks().filter((block) => block.threadId === threadId && block.processingStatus === "pending").map((block) => block.id));
|
|
5734
|
+
if (pendingBlockIds.size === 0) return;
|
|
5735
|
+
const retryTimes = [
|
|
5736
|
+
...memory.listSummaryJobs(),
|
|
5737
|
+
...memory.listExtractionJobs()
|
|
5738
|
+
].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);
|
|
5739
|
+
if (retryTimes.length === 0) return;
|
|
5740
|
+
const delay = Math.max(0, Math.min(...retryTimes) - Date.now());
|
|
5741
|
+
const timer = setTimeout(() => {
|
|
5742
|
+
this.derivationTimers.delete(key);
|
|
5743
|
+
if (this.closed) return;
|
|
5744
|
+
const run = this.models.run(session, () => memory.resumePendingWork({ threadId })).then(async (resumed) => {
|
|
5745
|
+
await this.persistSuccessfulResponses(memory);
|
|
5746
|
+
const contexts = memory.getBlockContext(threadId);
|
|
5747
|
+
for (const block of resumed.readyBlocks) {
|
|
5748
|
+
if (block.threadId !== threadId) continue;
|
|
5749
|
+
const context = contexts.find(({ id }) => id === block.id);
|
|
5750
|
+
if (!context) throw new Error(`Missing context for ready StrataGate block ${block.id}`);
|
|
5751
|
+
this.replaceSealedSurface(session, block, context, dshTurnAtBlockEnd(session, block));
|
|
5752
|
+
}
|
|
5753
|
+
const changed = this.syncDecayedBlockSurface(session, contexts);
|
|
5754
|
+
if (resumed.readyBlocks.length > 0 || changed) await this.flushNativeSession(session);
|
|
5755
|
+
}).catch((error) => {
|
|
5756
|
+
this.onIngestError(error);
|
|
5757
|
+
}).finally(() => {
|
|
5758
|
+
this.derivationRuns.delete(key);
|
|
5759
|
+
this.scheduleBlockDerivation(session, memory);
|
|
5760
|
+
});
|
|
5761
|
+
this.derivationRuns.set(key, run);
|
|
5762
|
+
}, delay);
|
|
5763
|
+
timer.unref?.();
|
|
5764
|
+
this.derivationTimers.set(key, timer);
|
|
5765
|
+
}
|
|
5372
5766
|
scheduleGraphMigration(session, memory) {
|
|
5373
5767
|
const namespace = this.namespaceFor(session);
|
|
5374
5768
|
if (this.closed || this.migrationTimers.has(namespace)) return;
|
|
@@ -5895,6 +6289,7 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
5895
6289
|
}
|
|
5896
6290
|
|
|
5897
6291
|
// src/web.ts
|
|
6292
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5898
6293
|
import { createRequire } from "node:module";
|
|
5899
6294
|
|
|
5900
6295
|
// src/graph-clustering.ts
|
|
@@ -6064,6 +6459,13 @@ function sourceMessages(snapshot, ids) {
|
|
|
6064
6459
|
return output;
|
|
6065
6460
|
}
|
|
6066
6461
|
function blockLayers(block) {
|
|
6462
|
+
if (block.processingStatus !== "ready" || !block.l0Title || !block.l0Tags || !block.l1Summary || !block.l2Keypoints) {
|
|
6463
|
+
return [
|
|
6464
|
+
{ level: 3, content: block.l3Condensed },
|
|
6465
|
+
{ level: 4, content: block.l4Readable },
|
|
6466
|
+
{ level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
6467
|
+
];
|
|
6468
|
+
}
|
|
6067
6469
|
return [
|
|
6068
6470
|
{ level: 0, content: `${block.l0Title}
|
|
6069
6471
|
\u6807\u7B7E\uFF1A${block.l0Tags.join("\u3001") || "\u65E0"}` },
|
|
@@ -6125,14 +6527,17 @@ var AdminHttpError = class extends Error {
|
|
|
6125
6527
|
}
|
|
6126
6528
|
status;
|
|
6127
6529
|
};
|
|
6128
|
-
async function overview(runtime) {
|
|
6129
|
-
const
|
|
6530
|
+
async function overview(runtime, cachedEntries) {
|
|
6531
|
+
const entries = cachedEntries ?? await Promise.all((await runtime.adminNamespaces()).map(async (namespace) => ({
|
|
6532
|
+
namespace,
|
|
6533
|
+
revision: 0,
|
|
6534
|
+
snapshot: await runtime.adminSnapshot(namespace)
|
|
6535
|
+
})));
|
|
6130
6536
|
const rows = [];
|
|
6131
|
-
for (const namespace of
|
|
6132
|
-
const snapshot = await runtime.adminSnapshot(namespace);
|
|
6537
|
+
for (const { namespace, snapshot } of entries) {
|
|
6133
6538
|
if (!snapshot) continue;
|
|
6134
6539
|
const failedJobs = snapshot.extractionJobs.filter(({ status }) => status === "failed").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "failed").length;
|
|
6135
|
-
const processingJobs = snapshot.extractionJobs.filter(({ status }) => status === "running").length + snapshot.graphProjectionJobs.filter(({ status }) => status === "pending" || status === "running").length;
|
|
6540
|
+
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;
|
|
6136
6541
|
const failedJobDetails = [
|
|
6137
6542
|
...snapshot.extractionJobs.filter(({ status }) => status === "failed").map((job) => ({
|
|
6138
6543
|
id: job.blockId,
|
|
@@ -6601,6 +7006,73 @@ async function audit(runtime, url) {
|
|
|
6601
7006
|
items: receipts.slice(offset, offset + limit).map((receipt) => receiptSources(snapshot, receipt))
|
|
6602
7007
|
};
|
|
6603
7008
|
}
|
|
7009
|
+
function requestHeader(req, name2) {
|
|
7010
|
+
const headers = req.headers ?? {};
|
|
7011
|
+
const key = Object.keys(headers).find((candidate) => candidate.toLocaleLowerCase() === name2.toLocaleLowerCase());
|
|
7012
|
+
const value = key ? headers[key] : void 0;
|
|
7013
|
+
return Array.isArray(value) ? value.join(", ") : value ?? "";
|
|
7014
|
+
}
|
|
7015
|
+
async function dashboard(runtime, url, ifNoneMatch) {
|
|
7016
|
+
const entries = await runtime.adminSnapshotEntries();
|
|
7017
|
+
const requestedNamespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
7018
|
+
const selected = entries.find(({ namespace }) => namespace === requestedNamespace) ?? entries[0];
|
|
7019
|
+
const threadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
7020
|
+
const revisionKey = entries.map(({ namespace, revision }) => `${namespace}:${revision}`).join("|");
|
|
7021
|
+
const etag = `"${createHash2("sha256").update(`${revisionKey}\0${selected?.namespace ?? ""}\0${threadId}`).digest("base64url").slice(0, 24)}"`;
|
|
7022
|
+
if (ifNoneMatch.split(",").map((value) => value.trim()).includes(etag)) return { etag, notModified: true };
|
|
7023
|
+
const overviewValue = await overview(runtime, entries);
|
|
7024
|
+
if (!selected) {
|
|
7025
|
+
return { etag, notModified: false, body: { namespace: null, overview: overviewValue, data: null, processing: false } };
|
|
7026
|
+
}
|
|
7027
|
+
const snapshotRuntime = {
|
|
7028
|
+
adminSnapshot: async (namespace) => namespace === selected.namespace ? selected.snapshot : null
|
|
7029
|
+
};
|
|
7030
|
+
const memoryUrl = (kind, limit) => {
|
|
7031
|
+
const target = new URL(url);
|
|
7032
|
+
target.searchParams.set("namespace", selected.namespace);
|
|
7033
|
+
target.searchParams.set("kind", kind);
|
|
7034
|
+
if (limit) target.searchParams.set("limit", limit);
|
|
7035
|
+
return target;
|
|
7036
|
+
};
|
|
7037
|
+
const [eventResult, graphResult, blockResult, auditResult] = await Promise.all([
|
|
7038
|
+
memories(snapshotRuntime, memoryUrl("events", "200")),
|
|
7039
|
+
memories(snapshotRuntime, memoryUrl("graph")),
|
|
7040
|
+
memories(snapshotRuntime, memoryUrl("blocks", "200")),
|
|
7041
|
+
audit(snapshotRuntime, memoryUrl("audit", "100"))
|
|
7042
|
+
]);
|
|
7043
|
+
const selectedOverview = overviewValue.namespaces?.find(({ namespace }) => namespace === selected.namespace);
|
|
7044
|
+
return {
|
|
7045
|
+
etag,
|
|
7046
|
+
notModified: false,
|
|
7047
|
+
body: {
|
|
7048
|
+
namespace: selected.namespace,
|
|
7049
|
+
revision: selected.revision,
|
|
7050
|
+
overview: overviewValue,
|
|
7051
|
+
processing: Number(selectedOverview?.processingJobs ?? 0) > 0,
|
|
7052
|
+
data: {
|
|
7053
|
+
events: eventResult.items ?? [],
|
|
7054
|
+
graph: graphResult,
|
|
7055
|
+
blocks: blockResult.items ?? [],
|
|
7056
|
+
openBlock: blockResult.openBlock ?? null,
|
|
7057
|
+
conversations: blockResult.conversations ?? [],
|
|
7058
|
+
activeThreadId: blockResult.activeThreadId ?? null,
|
|
7059
|
+
audit: auditResult.items ?? []
|
|
7060
|
+
}
|
|
7061
|
+
}
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
function sendDashboard(res, result) {
|
|
7065
|
+
res.setHeader("ETag", result.etag);
|
|
7066
|
+
res.setHeader("Cache-Control", "private, no-cache");
|
|
7067
|
+
if (result.notModified) {
|
|
7068
|
+
res.statusCode = 304;
|
|
7069
|
+
res.end("");
|
|
7070
|
+
return;
|
|
7071
|
+
}
|
|
7072
|
+
res.statusCode = 200;
|
|
7073
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
7074
|
+
res.end(JSON.stringify(redactValue(result.body)));
|
|
7075
|
+
}
|
|
6604
7076
|
async function handleAdminRequest(runtime, req, res) {
|
|
6605
7077
|
try {
|
|
6606
7078
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
@@ -6616,6 +7088,7 @@ async function handleAdminRequest(runtime, req, res) {
|
|
|
6616
7088
|
else if (req.method === "POST") sendJson(res, 200, await importExternalMemory(runtime, req));
|
|
6617
7089
|
else throw new AdminHttpError(405, "External memory import requires GET or POST");
|
|
6618
7090
|
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
7091
|
+
else if (path === "/api/stratagate/dashboard") sendDashboard(res, await dashboard(runtime, url, requestHeader(req, "if-none-match")));
|
|
6619
7092
|
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
6620
7093
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
6621
7094
|
else if (path === "/api/stratagate/sources") sendJson(res, 200, await sources(runtime, url));
|