stratagate-dsh 0.2.16 → 0.2.21
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 +31 -0
- package/README.md +19 -4
- package/cordis.patch.yml +1 -0
- package/dist/client.js +399 -95
- package/dist/index.d.ts +1 -0
- package/dist/index.js +618 -89
- package/dist/index.js.map +1 -1
- package/docs/README.zh-CN.md +19 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ var Config = z.object({
|
|
|
11
11
|
namespacePrefix: z.string().default("dsh"),
|
|
12
12
|
globalNamespace: z.string().default("global"),
|
|
13
13
|
blockTurnSize: z.natural().min(1).default(6),
|
|
14
|
+
blockDecayLambda: z.number().step(0.05).min(0).default(0.3).description("Block \u8870\u51CF\u7CFB\u6570 \u03BB").comment("\u9ED8\u8BA4 0.3\uFF1B\u6570\u5B57\u8D8A\u5C0F\uFF0C\u8BB0\u5FC6\u9057\u5FD8\u8D8A\u6162\uFF0C\u6D88\u8017 token \u8D8A\u591A\uFF0C\u4E0D\u5EFA\u8BAE\u5927\u4E8E 0.4\u3002"),
|
|
14
15
|
ingestSubagents: z.boolean().default(false),
|
|
15
16
|
provider: z.string(),
|
|
16
17
|
model: z.string(),
|
|
@@ -32,6 +33,7 @@ function resolveConfig(config) {
|
|
|
32
33
|
namespacePrefix,
|
|
33
34
|
globalNamespace,
|
|
34
35
|
blockTurnSize: Math.max(1, Math.floor(config.blockTurnSize ?? 6)),
|
|
36
|
+
blockDecayLambda: Math.max(0, config.blockDecayLambda ?? 0.3),
|
|
35
37
|
ingestSubagents: config.ingestSubagents ?? false,
|
|
36
38
|
...provider && model ? { provider, model } : {},
|
|
37
39
|
maxOutputTokens: Math.max(256, Math.floor(config.maxOutputTokens ?? 1e4))
|
|
@@ -46,7 +48,7 @@ import { parameterSchemaSpecToJsonSchema, validateArgs } from "@deepseek-ai/dsh-
|
|
|
46
48
|
// ../../src/blocks.ts
|
|
47
49
|
var DEFAULT_BLOCK_TURN_SIZE = 12;
|
|
48
50
|
var BLOCK_MAX_LEVEL = 5;
|
|
49
|
-
var BLOCK_DECAY_LAMBDA = 0.
|
|
51
|
+
var BLOCK_DECAY_LAMBDA = 0.3;
|
|
50
52
|
var FILLER_ONLY = /* @__PURE__ */ new Set([
|
|
51
53
|
"ok",
|
|
52
54
|
"okay",
|
|
@@ -80,11 +82,11 @@ var REPEATED_PASTE_MARKER = "[repeated paste omitted; original remains in L5]";
|
|
|
80
82
|
function asBlockLevel(value) {
|
|
81
83
|
return Math.max(0, Math.min(BLOCK_MAX_LEVEL, Math.round(value)));
|
|
82
84
|
}
|
|
83
|
-
function getBlockWeight(
|
|
84
|
-
return Math.exp(-
|
|
85
|
+
function getBlockWeight(anchorBlockPosition, latestBlockPosition, lambda = BLOCK_DECAY_LAMBDA) {
|
|
86
|
+
return Math.exp(-lambda * Math.max(0, latestBlockPosition - anchorBlockPosition));
|
|
85
87
|
}
|
|
86
|
-
function getDecayedBlockLevel(anchorLevel,
|
|
87
|
-
const weight = getBlockWeight(
|
|
88
|
+
function getDecayedBlockLevel(anchorLevel, anchorBlockPosition, latestBlockPosition, lambda = BLOCK_DECAY_LAMBDA) {
|
|
89
|
+
const weight = getBlockWeight(anchorBlockPosition, latestBlockPosition, lambda);
|
|
88
90
|
const droppedLevels = weight > 0.7 ? 0 : weight > 0.5 ? 1 : weight > 0.3 ? 2 : weight > 0.15 ? 3 : weight > 0.08 ? 4 : 5;
|
|
89
91
|
return asBlockLevel(anchorLevel - droppedLevels);
|
|
90
92
|
}
|
|
@@ -358,7 +360,7 @@ function rrfRank(rankings) {
|
|
|
358
360
|
}
|
|
359
361
|
|
|
360
362
|
// ../../src/storage.ts
|
|
361
|
-
var STRATAGATE_STORAGE_SCHEMA_VERSION =
|
|
363
|
+
var STRATAGATE_STORAGE_SCHEMA_VERSION = 7;
|
|
362
364
|
var StorageConflictError = class extends Error {
|
|
363
365
|
constructor(namespace, expectedRevision, actualRevision) {
|
|
364
366
|
super(`Storage revision conflict for ${namespace}: expected ${expectedRevision}, found ${actualRevision ?? "missing"}`);
|
|
@@ -374,6 +376,13 @@ var StorageConflictError = class extends Error {
|
|
|
374
376
|
function cloneSnapshot(snapshot) {
|
|
375
377
|
return structuredClone(snapshot);
|
|
376
378
|
}
|
|
379
|
+
function migrateLegacyBlocks(blocks) {
|
|
380
|
+
return blocks.map((block) => {
|
|
381
|
+
const { pointerAnchorTurn, ...current } = block;
|
|
382
|
+
const position = blocks.filter((candidate) => candidate.threadId === block.threadId && candidate.endTurn <= pointerAnchorTurn).length;
|
|
383
|
+
return { ...current, pointerAnchorBlockPosition: Math.max(1, position), lastLiftedBy: null };
|
|
384
|
+
});
|
|
385
|
+
}
|
|
377
386
|
function normalizeSnapshot(value) {
|
|
378
387
|
if (!value || typeof value !== "object") throw new TypeError("Invalid StrataGate snapshot: expected an object");
|
|
379
388
|
const schemaVersion = value.schemaVersion;
|
|
@@ -383,26 +392,52 @@ function normalizeSnapshot(value) {
|
|
|
383
392
|
snapshot = {
|
|
384
393
|
...structuredClone(legacy),
|
|
385
394
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
395
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
396
|
+
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
386
397
|
elements: [],
|
|
387
398
|
elementProjectionJobs: [],
|
|
388
399
|
usageReceipts: Array.isArray(legacy.usageReceipts) ? legacy.usageReceipts.map((receipt) => ({ ...receipt, elementIds: [] })) : [],
|
|
389
400
|
ingestionReceipts: []
|
|
390
401
|
};
|
|
391
402
|
} else if (schemaVersion === 2) {
|
|
403
|
+
const legacy = value;
|
|
392
404
|
snapshot = {
|
|
393
|
-
...structuredClone(
|
|
405
|
+
...structuredClone(legacy),
|
|
394
406
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
407
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
408
|
+
blocks: migrateLegacyBlocks(legacy.blocks),
|
|
395
409
|
ingestionReceipts: []
|
|
396
410
|
};
|
|
397
411
|
} else if (schemaVersion === 3) {
|
|
412
|
+
const legacy = value;
|
|
398
413
|
snapshot = {
|
|
399
|
-
...structuredClone(
|
|
400
|
-
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
|
|
414
|
+
...structuredClone(legacy),
|
|
415
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
416
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
417
|
+
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
401
418
|
};
|
|
402
419
|
} else if (schemaVersion === 4) {
|
|
420
|
+
const legacy = value;
|
|
403
421
|
snapshot = {
|
|
404
|
-
...structuredClone(
|
|
405
|
-
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
|
|
422
|
+
...structuredClone(legacy),
|
|
423
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
424
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
425
|
+
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
426
|
+
};
|
|
427
|
+
} else if (schemaVersion === 5) {
|
|
428
|
+
const legacy = value;
|
|
429
|
+
snapshot = {
|
|
430
|
+
...structuredClone(legacy),
|
|
431
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
432
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
433
|
+
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
434
|
+
};
|
|
435
|
+
} else if (schemaVersion === 6) {
|
|
436
|
+
const legacy = value;
|
|
437
|
+
snapshot = {
|
|
438
|
+
...structuredClone(legacy),
|
|
439
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
440
|
+
blocks: legacy.blocks.map((block) => ({ ...structuredClone(block), lastLiftedBy: null }))
|
|
406
441
|
};
|
|
407
442
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
408
443
|
snapshot = structuredClone(value);
|
|
@@ -415,10 +450,21 @@ function normalizeSnapshot(value) {
|
|
|
415
450
|
if (!Number.isSafeInteger(snapshot.blockTurnSize) || (snapshot.blockTurnSize ?? 0) < 1) {
|
|
416
451
|
throw new TypeError("Invalid StrataGate snapshot: blockTurnSize must be a positive integer");
|
|
417
452
|
}
|
|
453
|
+
if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
|
|
454
|
+
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
455
|
+
}
|
|
418
456
|
for (const key of ["openTail", "blocks", "events", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
|
|
419
457
|
if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
|
|
420
458
|
}
|
|
421
459
|
if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
|
|
460
|
+
for (const block of snapshot.blocks) {
|
|
461
|
+
if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
|
|
462
|
+
throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
|
|
463
|
+
}
|
|
464
|
+
if (block.lastLiftedBy !== null && block.lastLiftedBy !== "user" && block.lastLiftedBy !== "agent") {
|
|
465
|
+
throw new TypeError("Invalid StrataGate snapshot: lastLiftedBy must be user, agent, or null");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
422
468
|
if (snapshot.successfulModelResponses.length > 5) {
|
|
423
469
|
snapshot.successfulModelResponses = snapshot.successfulModelResponses.slice(-5);
|
|
424
470
|
}
|
|
@@ -592,6 +638,7 @@ CREATE TABLE IF NOT EXISTS memory_spaces (
|
|
|
592
638
|
revision INTEGER NOT NULL,
|
|
593
639
|
current_turn INTEGER NOT NULL,
|
|
594
640
|
block_turn_size INTEGER NOT NULL,
|
|
641
|
+
block_decay_lambda REAL NOT NULL,
|
|
595
642
|
created_at TEXT NOT NULL,
|
|
596
643
|
updated_at TEXT NOT NULL
|
|
597
644
|
) STRICT;
|
|
@@ -613,8 +660,9 @@ CREATE TABLE IF NOT EXISTS blocks (
|
|
|
613
660
|
l4_readable TEXT NOT NULL,
|
|
614
661
|
pointer_current_level INTEGER NOT NULL,
|
|
615
662
|
pointer_anchor_level INTEGER NOT NULL,
|
|
616
|
-
|
|
663
|
+
pointer_anchor_block_position INTEGER NOT NULL,
|
|
617
664
|
last_lifted_at TEXT,
|
|
665
|
+
last_lifted_by TEXT CHECK (last_lifted_by IS NULL OR last_lifted_by IN ('user', 'agent')),
|
|
618
666
|
PRIMARY KEY (namespace, id),
|
|
619
667
|
UNIQUE (namespace, sequence),
|
|
620
668
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
@@ -831,7 +879,7 @@ var SqliteStorage = class {
|
|
|
831
879
|
this.assertOpen();
|
|
832
880
|
const key = nonEmptyNamespace(namespace);
|
|
833
881
|
const space = this.database.prepare(`
|
|
834
|
-
SELECT schema_version, revision, current_turn, block_turn_size
|
|
882
|
+
SELECT schema_version, revision, current_turn, block_turn_size, block_decay_lambda
|
|
835
883
|
FROM memory_spaces WHERE namespace = ?
|
|
836
884
|
`).get(key);
|
|
837
885
|
if (!space) return null;
|
|
@@ -880,8 +928,9 @@ var SqliteStorage = class {
|
|
|
880
928
|
l5Raw: messagesByBlock.get(row.id) ?? [],
|
|
881
929
|
pointerCurrentLevel: row.pointer_current_level,
|
|
882
930
|
pointerAnchorLevel: row.pointer_anchor_level,
|
|
883
|
-
|
|
884
|
-
lastLiftedAt: row.last_lifted_at
|
|
931
|
+
pointerAnchorBlockPosition: row.pointer_anchor_block_position,
|
|
932
|
+
lastLiftedAt: row.last_lifted_at,
|
|
933
|
+
lastLiftedBy: row.last_lifted_by
|
|
885
934
|
}));
|
|
886
935
|
const sourceRows = this.database.prepare(`
|
|
887
936
|
SELECT event_id, message_id, position FROM event_sources
|
|
@@ -1047,6 +1096,7 @@ var SqliteStorage = class {
|
|
|
1047
1096
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1048
1097
|
currentTurn: space.current_turn,
|
|
1049
1098
|
blockTurnSize: space.block_turn_size,
|
|
1099
|
+
blockDecayLambda: space.block_decay_lambda,
|
|
1050
1100
|
openTail,
|
|
1051
1101
|
blocks,
|
|
1052
1102
|
events,
|
|
@@ -1085,27 +1135,29 @@ var SqliteStorage = class {
|
|
|
1085
1135
|
if (current) {
|
|
1086
1136
|
this.database.prepare(`
|
|
1087
1137
|
UPDATE memory_spaces
|
|
1088
|
-
SET schema_version = ?, revision = ?, current_turn = ?, block_turn_size = ?, updated_at = ?
|
|
1138
|
+
SET schema_version = ?, revision = ?, current_turn = ?, block_turn_size = ?, block_decay_lambda = ?, updated_at = ?
|
|
1089
1139
|
WHERE namespace = ?
|
|
1090
1140
|
`).run(
|
|
1091
1141
|
snapshot.schemaVersion,
|
|
1092
1142
|
nextRevision,
|
|
1093
1143
|
snapshot.currentTurn,
|
|
1094
1144
|
snapshot.blockTurnSize,
|
|
1145
|
+
snapshot.blockDecayLambda,
|
|
1095
1146
|
updatedAt,
|
|
1096
1147
|
namespace
|
|
1097
1148
|
);
|
|
1098
1149
|
} else {
|
|
1099
1150
|
this.database.prepare(`
|
|
1100
1151
|
INSERT INTO memory_spaces (
|
|
1101
|
-
namespace, schema_version, revision, current_turn, block_turn_size, created_at, updated_at
|
|
1102
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1152
|
+
namespace, schema_version, revision, current_turn, block_turn_size, block_decay_lambda, created_at, updated_at
|
|
1153
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1103
1154
|
`).run(
|
|
1104
1155
|
namespace,
|
|
1105
1156
|
snapshot.schemaVersion,
|
|
1106
1157
|
nextRevision,
|
|
1107
1158
|
snapshot.currentTurn,
|
|
1108
1159
|
snapshot.blockTurnSize,
|
|
1160
|
+
snapshot.blockDecayLambda,
|
|
1109
1161
|
updatedAt,
|
|
1110
1162
|
updatedAt
|
|
1111
1163
|
);
|
|
@@ -1114,8 +1166,8 @@ var SqliteStorage = class {
|
|
|
1114
1166
|
INSERT INTO blocks (
|
|
1115
1167
|
namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
|
|
1116
1168
|
l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
|
|
1117
|
-
pointer_current_level, pointer_anchor_level,
|
|
1118
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1169
|
+
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at, last_lifted_by
|
|
1170
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1119
1171
|
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
1120
1172
|
thread_id = excluded.thread_id,
|
|
1121
1173
|
sequence = excluded.sequence,
|
|
@@ -1131,8 +1183,9 @@ var SqliteStorage = class {
|
|
|
1131
1183
|
l4_readable = excluded.l4_readable,
|
|
1132
1184
|
pointer_current_level = excluded.pointer_current_level,
|
|
1133
1185
|
pointer_anchor_level = excluded.pointer_anchor_level,
|
|
1134
|
-
|
|
1135
|
-
last_lifted_at = excluded.last_lifted_at
|
|
1186
|
+
pointer_anchor_block_position = excluded.pointer_anchor_block_position,
|
|
1187
|
+
last_lifted_at = excluded.last_lifted_at,
|
|
1188
|
+
last_lifted_by = excluded.last_lifted_by
|
|
1136
1189
|
`);
|
|
1137
1190
|
for (const block of snapshot.blocks) {
|
|
1138
1191
|
insertBlock.run(
|
|
@@ -1152,8 +1205,9 @@ var SqliteStorage = class {
|
|
|
1152
1205
|
block.l4Readable,
|
|
1153
1206
|
block.pointerCurrentLevel,
|
|
1154
1207
|
block.pointerAnchorLevel,
|
|
1155
|
-
block.
|
|
1156
|
-
block.lastLiftedAt
|
|
1208
|
+
block.pointerAnchorBlockPosition,
|
|
1209
|
+
block.lastLiftedAt,
|
|
1210
|
+
block.lastLiftedBy
|
|
1157
1211
|
);
|
|
1158
1212
|
}
|
|
1159
1213
|
const insertMessage = this.database.prepare(`
|
|
@@ -1422,8 +1476,9 @@ var SqliteStorage = class {
|
|
|
1422
1476
|
this.database.exec(THREAD_INDEXES);
|
|
1423
1477
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1424
1478
|
});
|
|
1425
|
-
} else if (version === 1 || version === 2 || version === 3 || version === 4) {
|
|
1479
|
+
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5 || version === 6) {
|
|
1426
1480
|
this.immediateTransaction(() => {
|
|
1481
|
+
this.database.exec(SCHEMA);
|
|
1427
1482
|
if (version === 1) {
|
|
1428
1483
|
const receiptColumns2 = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
|
|
1429
1484
|
if (!receiptColumns2.some(({ name: name2 }) => name2 === "element_ids_json")) {
|
|
@@ -1434,11 +1489,29 @@ var SqliteStorage = class {
|
|
|
1434
1489
|
if (!receiptColumns.some(({ name: name2 }) => name2 === "audit_json")) {
|
|
1435
1490
|
this.database.exec("ALTER TABLE usage_receipts ADD COLUMN audit_json TEXT NOT NULL DEFAULT '{}'");
|
|
1436
1491
|
}
|
|
1437
|
-
this.database.
|
|
1492
|
+
const spaceColumns = this.database.prepare("PRAGMA table_info('memory_spaces')").all();
|
|
1493
|
+
if (!spaceColumns.some(({ name: name2 }) => name2 === "block_decay_lambda")) {
|
|
1494
|
+
this.database.exec("ALTER TABLE memory_spaces ADD COLUMN block_decay_lambda REAL NOT NULL DEFAULT 0.3");
|
|
1495
|
+
}
|
|
1438
1496
|
const blockColumns = this.database.prepare("PRAGMA table_info('blocks')").all();
|
|
1439
1497
|
if (!blockColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1440
1498
|
this.database.exec("ALTER TABLE blocks ADD COLUMN thread_id TEXT");
|
|
1441
1499
|
}
|
|
1500
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "pointer_anchor_block_position")) {
|
|
1501
|
+
this.database.exec("ALTER TABLE blocks RENAME COLUMN pointer_anchor_turn TO pointer_anchor_block_position");
|
|
1502
|
+
this.database.exec(`
|
|
1503
|
+
UPDATE blocks AS target
|
|
1504
|
+
SET pointer_anchor_block_position = MAX(1, (
|
|
1505
|
+
SELECT COUNT(*) FROM blocks AS candidate
|
|
1506
|
+
WHERE candidate.namespace = target.namespace
|
|
1507
|
+
AND candidate.thread_id IS target.thread_id
|
|
1508
|
+
AND candidate.end_turn <= target.pointer_anchor_block_position
|
|
1509
|
+
))
|
|
1510
|
+
`);
|
|
1511
|
+
}
|
|
1512
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "last_lifted_by")) {
|
|
1513
|
+
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'))");
|
|
1514
|
+
}
|
|
1442
1515
|
const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
|
|
1443
1516
|
if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1444
1517
|
this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
|
|
@@ -1526,6 +1599,7 @@ function errorMessage(error) {
|
|
|
1526
1599
|
var STRATAGATE_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol("StrataGate constructor");
|
|
1527
1600
|
var StrataGate = class _StrataGate {
|
|
1528
1601
|
blockTurnSize;
|
|
1602
|
+
blockDecayLambdaValue;
|
|
1529
1603
|
summarizer;
|
|
1530
1604
|
extractor;
|
|
1531
1605
|
elementProjector;
|
|
@@ -1551,6 +1625,11 @@ var StrataGate = class _StrataGate {
|
|
|
1551
1625
|
throw new TypeError("Use StrataGate.open() for SQLite or StrataGate.inMemory() for explicit ephemeral storage");
|
|
1552
1626
|
}
|
|
1553
1627
|
this.blockTurnSize = Math.max(1, Math.floor(options.blockTurnSize ?? DEFAULT_BLOCK_TURN_SIZE));
|
|
1628
|
+
const blockDecayLambda = options.blockDecayLambda ?? BLOCK_DECAY_LAMBDA;
|
|
1629
|
+
if (!Number.isFinite(blockDecayLambda) || blockDecayLambda < 0) {
|
|
1630
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1631
|
+
}
|
|
1632
|
+
this.blockDecayLambdaValue = blockDecayLambda;
|
|
1554
1633
|
this.summarizer = options.summarizer;
|
|
1555
1634
|
this.extractor = options.extractor;
|
|
1556
1635
|
this.elementProjector = options.elementProjector;
|
|
@@ -1573,6 +1652,7 @@ var StrataGate = class _StrataGate {
|
|
|
1573
1652
|
storage,
|
|
1574
1653
|
namespace: options.namespace,
|
|
1575
1654
|
...options.blockTurnSize !== void 0 ? { blockTurnSize: options.blockTurnSize } : {},
|
|
1655
|
+
...options.blockDecayLambda !== void 0 ? { blockDecayLambda: options.blockDecayLambda } : {},
|
|
1576
1656
|
...options.summarizer ? { summarizer: options.summarizer } : {},
|
|
1577
1657
|
...options.extractor ? { extractor: options.extractor } : {},
|
|
1578
1658
|
...options.elementProjector ? { elementProjector: options.elementProjector } : {},
|
|
@@ -1591,17 +1671,32 @@ var StrataGate = class _StrataGate {
|
|
|
1591
1671
|
const loaded = await options.storage.load(namespace);
|
|
1592
1672
|
const loadedSnapshot = loaded ? normalizeSnapshot(loaded.snapshot) : null;
|
|
1593
1673
|
let loadedRevision = loaded?.revision ?? 0;
|
|
1594
|
-
if (loaded &&
|
|
1595
|
-
|
|
1596
|
-
if (
|
|
1597
|
-
|
|
1598
|
-
loadedSnapshot.blockTurnSize
|
|
1599
|
-
|
|
1674
|
+
if (loaded && loadedSnapshot) {
|
|
1675
|
+
let settingsChanged = false;
|
|
1676
|
+
if (options.blockTurnSize !== void 0) {
|
|
1677
|
+
const requested = Math.max(1, Math.floor(options.blockTurnSize));
|
|
1678
|
+
if (requested !== loadedSnapshot.blockTurnSize) {
|
|
1679
|
+
loadedSnapshot.blockTurnSize = requested;
|
|
1680
|
+
settingsChanged = true;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
if (options.blockDecayLambda !== void 0) {
|
|
1684
|
+
const requested = options.blockDecayLambda;
|
|
1685
|
+
if (!Number.isFinite(requested) || requested < 0) {
|
|
1686
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1687
|
+
}
|
|
1688
|
+
if (requested !== loadedSnapshot.blockDecayLambda) {
|
|
1689
|
+
loadedSnapshot.blockDecayLambda = requested;
|
|
1690
|
+
settingsChanged = true;
|
|
1691
|
+
}
|
|
1600
1692
|
}
|
|
1693
|
+
if (settingsChanged) loadedRevision = await options.storage.save(namespace, loadedSnapshot, loadedRevision);
|
|
1601
1694
|
}
|
|
1602
1695
|
const memoryOptions = {};
|
|
1603
1696
|
if (loadedSnapshot) memoryOptions.blockTurnSize = loadedSnapshot.blockTurnSize;
|
|
1604
1697
|
else if (options.blockTurnSize !== void 0) memoryOptions.blockTurnSize = options.blockTurnSize;
|
|
1698
|
+
if (loadedSnapshot) memoryOptions.blockDecayLambda = loadedSnapshot.blockDecayLambda;
|
|
1699
|
+
else if (options.blockDecayLambda !== void 0) memoryOptions.blockDecayLambda = options.blockDecayLambda;
|
|
1605
1700
|
if (options.summarizer) memoryOptions.summarizer = options.summarizer;
|
|
1606
1701
|
if (options.extractor) memoryOptions.extractor = options.extractor;
|
|
1607
1702
|
if (options.elementProjector) memoryOptions.elementProjector = options.elementProjector;
|
|
@@ -1653,6 +1748,18 @@ var StrataGate = class _StrataGate {
|
|
|
1653
1748
|
get storageRevision() {
|
|
1654
1749
|
return this.revision;
|
|
1655
1750
|
}
|
|
1751
|
+
get blockDecayLambda() {
|
|
1752
|
+
return this.blockDecayLambdaValue;
|
|
1753
|
+
}
|
|
1754
|
+
async setBlockDecayLambda(value) {
|
|
1755
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1756
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1757
|
+
}
|
|
1758
|
+
if (value === this.blockDecayLambdaValue) return;
|
|
1759
|
+
await this.commitMutation(() => {
|
|
1760
|
+
this.blockDecayLambdaValue = value;
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1656
1763
|
listBlocks() {
|
|
1657
1764
|
return this.blocks;
|
|
1658
1765
|
}
|
|
@@ -1695,6 +1802,7 @@ var StrataGate = class _StrataGate {
|
|
|
1695
1802
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1696
1803
|
currentTurn: this.currentTurn,
|
|
1697
1804
|
blockTurnSize: this.blockTurnSize,
|
|
1805
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
1698
1806
|
openTail: this.openTail,
|
|
1699
1807
|
blocks: this.blocks,
|
|
1700
1808
|
events: this.events,
|
|
@@ -1995,34 +2103,51 @@ var StrataGate = class _StrataGate {
|
|
|
1995
2103
|
getBlockContext(threadId) {
|
|
1996
2104
|
const blocks = threadId === void 0 ? this.blocks : this.blocks.filter((block) => block.threadId === threadId);
|
|
1997
2105
|
return blocks.map((block) => {
|
|
1998
|
-
const
|
|
1999
|
-
const
|
|
2106
|
+
const threadBlocks = this.threadBlocks(block.threadId);
|
|
2107
|
+
const latestBlockPosition = threadBlocks.length;
|
|
2108
|
+
const blockPosition = threadBlocks.indexOf(block) + 1;
|
|
2109
|
+
const age = Math.max(0, latestBlockPosition - blockPosition);
|
|
2110
|
+
const level = getDecayedBlockLevel(
|
|
2111
|
+
block.pointerAnchorLevel,
|
|
2112
|
+
block.pointerAnchorBlockPosition,
|
|
2113
|
+
latestBlockPosition,
|
|
2114
|
+
this.blockDecayLambda
|
|
2115
|
+
);
|
|
2000
2116
|
block.pointerCurrentLevel = level;
|
|
2001
2117
|
return {
|
|
2002
2118
|
id: block.id,
|
|
2003
2119
|
...block.threadId ? { threadId: block.threadId } : {},
|
|
2004
2120
|
turnRange: [block.startTurn, block.endTurn],
|
|
2121
|
+
age,
|
|
2005
2122
|
level,
|
|
2006
2123
|
label: blockLevelLabel(level),
|
|
2007
2124
|
content: renderBlock(block, level)
|
|
2008
2125
|
};
|
|
2009
2126
|
});
|
|
2010
2127
|
}
|
|
2011
|
-
async expandBlock(id, target = "next") {
|
|
2128
|
+
async expandBlock(id, target = "next", source = "agent") {
|
|
2012
2129
|
return this.commitMutation(() => {
|
|
2013
2130
|
const block = this.blocks.find((candidate) => candidate.id === id);
|
|
2014
2131
|
if (!block) throw new Error(`Unknown block: ${id}`);
|
|
2015
|
-
const
|
|
2016
|
-
const
|
|
2132
|
+
const latestBlockPosition = this.threadBlocks(block.threadId).length;
|
|
2133
|
+
const blockPosition = this.threadBlocks(block.threadId).indexOf(block) + 1;
|
|
2134
|
+
const current = getDecayedBlockLevel(
|
|
2135
|
+
block.pointerAnchorLevel,
|
|
2136
|
+
block.pointerAnchorBlockPosition,
|
|
2137
|
+
latestBlockPosition,
|
|
2138
|
+
this.blockDecayLambda
|
|
2139
|
+
);
|
|
2017
2140
|
const level = normalizeBlockLevel(target, current);
|
|
2018
2141
|
block.pointerCurrentLevel = level;
|
|
2019
2142
|
block.pointerAnchorLevel = level;
|
|
2020
|
-
block.
|
|
2143
|
+
block.pointerAnchorBlockPosition = latestBlockPosition;
|
|
2021
2144
|
block.lastLiftedAt = toUtc8Iso(this.now());
|
|
2145
|
+
block.lastLiftedBy = source;
|
|
2022
2146
|
return {
|
|
2023
2147
|
id: block.id,
|
|
2024
2148
|
...block.threadId ? { threadId: block.threadId } : {},
|
|
2025
2149
|
turnRange: [block.startTurn, block.endTurn],
|
|
2150
|
+
age: Math.max(0, latestBlockPosition - blockPosition),
|
|
2026
2151
|
level,
|
|
2027
2152
|
label: blockLevelLabel(level),
|
|
2028
2153
|
content: renderBlock(block, level)
|
|
@@ -2221,7 +2346,9 @@ var StrataGate = class _StrataGate {
|
|
|
2221
2346
|
const generated = this.summarizer ? await this.summarizer(raw) : defaultSummary(raw);
|
|
2222
2347
|
const deterministic = deterministicBlockLayers(raw);
|
|
2223
2348
|
const sequence = this.blocks.length + 1;
|
|
2224
|
-
const
|
|
2349
|
+
const threadBlocks = this.threadBlocks(threadId);
|
|
2350
|
+
const previous = threadBlocks.at(-1);
|
|
2351
|
+
const blockPosition = threadBlocks.length + 1;
|
|
2225
2352
|
const startTurn = previous ? previous.endTurn + 1 : 1;
|
|
2226
2353
|
const endTurn = startTurn + this.blockTurnSize - 1;
|
|
2227
2354
|
return this.commitMutation(() => {
|
|
@@ -2244,8 +2371,9 @@ var StrataGate = class _StrataGate {
|
|
|
2244
2371
|
...deterministic,
|
|
2245
2372
|
pointerCurrentLevel: 5,
|
|
2246
2373
|
pointerAnchorLevel: 5,
|
|
2247
|
-
|
|
2248
|
-
lastLiftedAt: null
|
|
2374
|
+
pointerAnchorBlockPosition: blockPosition,
|
|
2375
|
+
lastLiftedAt: null,
|
|
2376
|
+
lastLiftedBy: null
|
|
2249
2377
|
};
|
|
2250
2378
|
const sealedIds = new Set(raw.map((message) => message.id));
|
|
2251
2379
|
const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
|
|
@@ -2381,6 +2509,7 @@ var StrataGate = class _StrataGate {
|
|
|
2381
2509
|
throw new Error(`Snapshot blockTurnSize ${normalized.blockTurnSize} does not match ${this.blockTurnSize}`);
|
|
2382
2510
|
}
|
|
2383
2511
|
const copy = cloneSnapshot(normalized);
|
|
2512
|
+
this.blockDecayLambdaValue = copy.blockDecayLambda;
|
|
2384
2513
|
this.currentTurn = copy.currentTurn;
|
|
2385
2514
|
this.openTail.splice(0, this.openTail.length, ...copy.openTail);
|
|
2386
2515
|
this.blocks.splice(0, this.blocks.length, ...copy.blocks);
|
|
@@ -3051,6 +3180,60 @@ var TurnFolder = class {
|
|
|
3051
3180
|
}
|
|
3052
3181
|
};
|
|
3053
3182
|
|
|
3183
|
+
// src/metadata.ts
|
|
3184
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
3185
|
+
var METADATA_SCHEMA = `
|
|
3186
|
+
CREATE TABLE IF NOT EXISTS stratagate_dsh_settings (
|
|
3187
|
+
key TEXT PRIMARY KEY,
|
|
3188
|
+
value TEXT NOT NULL,
|
|
3189
|
+
updated_at TEXT NOT NULL
|
|
3190
|
+
) STRICT;
|
|
3191
|
+
|
|
3192
|
+
CREATE TABLE IF NOT EXISTS stratagate_dsh_workspaces (
|
|
3193
|
+
namespace TEXT PRIMARY KEY,
|
|
3194
|
+
display_name TEXT NOT NULL,
|
|
3195
|
+
updated_at TEXT NOT NULL
|
|
3196
|
+
) STRICT;
|
|
3197
|
+
`;
|
|
3198
|
+
var DshMetadataStore = class {
|
|
3199
|
+
database;
|
|
3200
|
+
constructor(filename) {
|
|
3201
|
+
this.database = new DatabaseSync2(filename);
|
|
3202
|
+
this.database.exec(METADATA_SCHEMA);
|
|
3203
|
+
}
|
|
3204
|
+
blockDecayLambda() {
|
|
3205
|
+
const row = this.database.prepare("SELECT value FROM stratagate_dsh_settings WHERE key = 'blockDecayLambda'").get();
|
|
3206
|
+
const value = Number(row?.value);
|
|
3207
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
3208
|
+
}
|
|
3209
|
+
setBlockDecayLambda(value) {
|
|
3210
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3211
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
3212
|
+
}
|
|
3213
|
+
this.database.prepare(`
|
|
3214
|
+
INSERT INTO stratagate_dsh_settings (key, value, updated_at)
|
|
3215
|
+
VALUES ('blockDecayLambda', ?, ?)
|
|
3216
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
3217
|
+
`).run(String(value), (/* @__PURE__ */ new Date()).toISOString());
|
|
3218
|
+
}
|
|
3219
|
+
workspaceName(namespace) {
|
|
3220
|
+
const row = this.database.prepare("SELECT display_name FROM stratagate_dsh_workspaces WHERE namespace = ?").get(namespace);
|
|
3221
|
+
return row?.display_name ?? null;
|
|
3222
|
+
}
|
|
3223
|
+
rememberWorkspace(namespace, displayName) {
|
|
3224
|
+
const name2 = displayName.trim();
|
|
3225
|
+
if (!namespace.trim() || !name2) return;
|
|
3226
|
+
this.database.prepare(`
|
|
3227
|
+
INSERT INTO stratagate_dsh_workspaces (namespace, display_name, updated_at)
|
|
3228
|
+
VALUES (?, ?, ?)
|
|
3229
|
+
ON CONFLICT (namespace) DO UPDATE SET display_name = excluded.display_name, updated_at = excluded.updated_at
|
|
3230
|
+
`).run(namespace, name2, (/* @__PURE__ */ new Date()).toISOString());
|
|
3231
|
+
}
|
|
3232
|
+
close() {
|
|
3233
|
+
this.database.close();
|
|
3234
|
+
}
|
|
3235
|
+
};
|
|
3236
|
+
|
|
3054
3237
|
// src/runtime.ts
|
|
3055
3238
|
var AUTO_EVENT_LIMIT = 4;
|
|
3056
3239
|
var AUTO_ELEMENT_LIMIT = 4;
|
|
@@ -3059,12 +3242,17 @@ function projectKey(cwd) {
|
|
|
3059
3242
|
const canonical = resolve(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
|
|
3060
3243
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
3061
3244
|
}
|
|
3245
|
+
function workspaceDisplayName(cwd) {
|
|
3246
|
+
const canonical = (cwd ?? process.cwd()).replace(/[\\/]+$/, "");
|
|
3247
|
+
return canonical.split(/[\\/]/).at(-1) || "\u5F53\u524D\u5DE5\u4F5C\u533A";
|
|
3248
|
+
}
|
|
3062
3249
|
var StrataGateRuntime = class {
|
|
3063
3250
|
constructor(config, models, onIngestError = () => {
|
|
3064
3251
|
}) {
|
|
3065
3252
|
this.config = config;
|
|
3066
3253
|
this.models = models;
|
|
3067
3254
|
this.onIngestError = onIngestError;
|
|
3255
|
+
this.blockDecayLambda = config.blockDecayLambda;
|
|
3068
3256
|
}
|
|
3069
3257
|
config;
|
|
3070
3258
|
models;
|
|
@@ -3074,10 +3262,13 @@ var StrataGateRuntime = class {
|
|
|
3074
3262
|
batches = /* @__PURE__ */ new Map();
|
|
3075
3263
|
adopted = /* @__PURE__ */ new Map();
|
|
3076
3264
|
pendingUse = /* @__PURE__ */ new Set();
|
|
3265
|
+
workspaceNames = /* @__PURE__ */ new Map();
|
|
3077
3266
|
ingestTail = Promise.resolve();
|
|
3267
|
+
settingsTail = Promise.resolve();
|
|
3078
3268
|
batchSequence = 0;
|
|
3079
3269
|
closed = false;
|
|
3080
3270
|
ingestError;
|
|
3271
|
+
blockDecayLambda;
|
|
3081
3272
|
acceptEvent(session, event) {
|
|
3082
3273
|
if (this.closed) return;
|
|
3083
3274
|
if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
|
|
@@ -3130,7 +3321,7 @@ var StrataGateRuntime = class {
|
|
|
3130
3321
|
}
|
|
3131
3322
|
async expandBlock(session, id, target) {
|
|
3132
3323
|
await this.flush();
|
|
3133
|
-
const result = await (await this.space(session)).expandBlock(id, target);
|
|
3324
|
+
const result = await (await this.space(session)).expandBlock(id, target, "agent");
|
|
3134
3325
|
return this.batch(session, [{
|
|
3135
3326
|
ref: `block:${result.id}:level:${result.level}`,
|
|
3136
3327
|
target: { eventIds: [], elementIds: [] }
|
|
@@ -3298,16 +3489,23 @@ var StrataGateRuntime = class {
|
|
|
3298
3489
|
await storage.close();
|
|
3299
3490
|
}
|
|
3300
3491
|
}
|
|
3301
|
-
async
|
|
3492
|
+
async syncConfiguredSettings() {
|
|
3302
3493
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return;
|
|
3494
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3495
|
+
try {
|
|
3496
|
+
this.blockDecayLambda = metadata.blockDecayLambda() ?? this.config.blockDecayLambda;
|
|
3497
|
+
} finally {
|
|
3498
|
+
metadata.close();
|
|
3499
|
+
}
|
|
3303
3500
|
const storage = new SqliteStorage({ filename: this.config.database });
|
|
3304
3501
|
try {
|
|
3305
3502
|
for (const namespace of storage.listNamespaces()) {
|
|
3306
3503
|
const loaded = await storage.load(namespace);
|
|
3307
|
-
if (!loaded || loaded.snapshot.blockTurnSize === this.config.blockTurnSize) continue;
|
|
3504
|
+
if (!loaded || loaded.snapshot.blockTurnSize === this.config.blockTurnSize && loaded.snapshot.blockDecayLambda === this.blockDecayLambda) continue;
|
|
3308
3505
|
await storage.save(namespace, {
|
|
3309
3506
|
...loaded.snapshot,
|
|
3310
|
-
blockTurnSize: this.config.blockTurnSize
|
|
3507
|
+
blockTurnSize: this.config.blockTurnSize,
|
|
3508
|
+
blockDecayLambda: this.blockDecayLambda
|
|
3311
3509
|
}, loaded.revision);
|
|
3312
3510
|
}
|
|
3313
3511
|
} finally {
|
|
@@ -3325,14 +3523,101 @@ var StrataGateRuntime = class {
|
|
|
3325
3523
|
await storage.close();
|
|
3326
3524
|
}
|
|
3327
3525
|
}
|
|
3526
|
+
adminWorkspaceName(namespace) {
|
|
3527
|
+
const remembered = this.workspaceNames.get(namespace);
|
|
3528
|
+
if (remembered) return remembered;
|
|
3529
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
3530
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3531
|
+
try {
|
|
3532
|
+
return metadata.workspaceName(namespace);
|
|
3533
|
+
} finally {
|
|
3534
|
+
metadata.close();
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
async adminSetBlockDecayLambda(value) {
|
|
3538
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3539
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
3540
|
+
}
|
|
3541
|
+
const update = this.settingsTail.catch(() => {
|
|
3542
|
+
}).then(() => this.applyBlockDecayLambda(value));
|
|
3543
|
+
this.settingsTail = update.then(() => {
|
|
3544
|
+
}, () => {
|
|
3545
|
+
});
|
|
3546
|
+
await update;
|
|
3547
|
+
return value;
|
|
3548
|
+
}
|
|
3549
|
+
async adminExpandBlock(namespace, id, target) {
|
|
3550
|
+
const key = namespace.trim();
|
|
3551
|
+
if (!key) throw new TypeError("StrataGate admin namespace must not be empty");
|
|
3552
|
+
const update = this.settingsTail.catch(() => {
|
|
3553
|
+
}).then(async () => {
|
|
3554
|
+
await this.flush();
|
|
3555
|
+
const active = this.spaces.get(key);
|
|
3556
|
+
if (active) return (await active).expandBlock(id, target, "user");
|
|
3557
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) {
|
|
3558
|
+
throw new Error(`Unknown StrataGate namespace: ${key}`);
|
|
3559
|
+
}
|
|
3560
|
+
const memory = await StrataGate.open({
|
|
3561
|
+
database: this.config.database,
|
|
3562
|
+
namespace: key,
|
|
3563
|
+
blockTurnSize: this.config.blockTurnSize,
|
|
3564
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
3565
|
+
summarizer: this.models.summarizer,
|
|
3566
|
+
extractor: this.models.extractor,
|
|
3567
|
+
elementProjector: this.models.projector
|
|
3568
|
+
});
|
|
3569
|
+
try {
|
|
3570
|
+
return await memory.expandBlock(id, target, "user");
|
|
3571
|
+
} finally {
|
|
3572
|
+
await memory.close();
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
this.settingsTail = update.then(() => {
|
|
3576
|
+
}, () => {
|
|
3577
|
+
});
|
|
3578
|
+
return update;
|
|
3579
|
+
}
|
|
3580
|
+
async applyBlockDecayLambda(value) {
|
|
3581
|
+
await this.flush();
|
|
3582
|
+
this.blockDecayLambda = value;
|
|
3583
|
+
if (this.config.database !== ":memory:") {
|
|
3584
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3585
|
+
try {
|
|
3586
|
+
metadata.setBlockDecayLambda(value);
|
|
3587
|
+
} finally {
|
|
3588
|
+
metadata.close();
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
const openNamespaces = /* @__PURE__ */ new Set();
|
|
3592
|
+
for (const [namespace, opening] of this.spaces) {
|
|
3593
|
+
const memory = await opening;
|
|
3594
|
+
await memory.setBlockDecayLambda(value);
|
|
3595
|
+
openNamespaces.add(namespace);
|
|
3596
|
+
}
|
|
3597
|
+
if (this.config.database !== ":memory:" && existsSync(this.config.database)) {
|
|
3598
|
+
const storage = new SqliteStorage({ filename: this.config.database });
|
|
3599
|
+
try {
|
|
3600
|
+
for (const namespace of storage.listNamespaces()) {
|
|
3601
|
+
if (openNamespaces.has(namespace)) continue;
|
|
3602
|
+
const loaded = await storage.load(namespace);
|
|
3603
|
+
if (!loaded || loaded.snapshot.blockDecayLambda === value) continue;
|
|
3604
|
+
await storage.save(namespace, { ...loaded.snapshot, blockDecayLambda: value }, loaded.revision);
|
|
3605
|
+
}
|
|
3606
|
+
} finally {
|
|
3607
|
+
await storage.close();
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3328
3611
|
space(session) {
|
|
3329
3612
|
const namespace = this.namespaceFor(session);
|
|
3613
|
+
this.rememberWorkspace(namespace, session.header.cwd);
|
|
3330
3614
|
let opening = this.spaces.get(namespace);
|
|
3331
3615
|
if (!opening) {
|
|
3332
3616
|
opening = StrataGate.open({
|
|
3333
3617
|
database: this.config.database,
|
|
3334
3618
|
namespace,
|
|
3335
3619
|
blockTurnSize: this.config.blockTurnSize,
|
|
3620
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
3336
3621
|
summarizer: this.models.summarizer,
|
|
3337
3622
|
extractor: this.models.extractor,
|
|
3338
3623
|
elementProjector: this.models.projector
|
|
@@ -3357,6 +3642,21 @@ var StrataGateRuntime = class {
|
|
|
3357
3642
|
}
|
|
3358
3643
|
return opening;
|
|
3359
3644
|
}
|
|
3645
|
+
rememberWorkspace(namespace, cwd) {
|
|
3646
|
+
const name2 = workspaceDisplayName(cwd);
|
|
3647
|
+
this.workspaceNames.set(namespace, name2);
|
|
3648
|
+
if (this.config.database === ":memory:") return;
|
|
3649
|
+
try {
|
|
3650
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3651
|
+
try {
|
|
3652
|
+
metadata.rememberWorkspace(namespace, name2);
|
|
3653
|
+
} finally {
|
|
3654
|
+
metadata.close();
|
|
3655
|
+
}
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
this.onIngestError(error);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3360
3660
|
async persistSuccessfulResponses(memory) {
|
|
3361
3661
|
if (typeof this.models.takeSuccessfulResponses !== "function") return;
|
|
3362
3662
|
const responses = this.models.takeSuccessfulResponses();
|
|
@@ -3413,7 +3713,7 @@ function renderMessages(messages) {
|
|
|
3413
3713
|
function renderBlocks2(blocks) {
|
|
3414
3714
|
if (blocks.length === 0) return "(no sealed blocks)";
|
|
3415
3715
|
return blocks.map((block) => [
|
|
3416
|
-
`block ${block.id} | turns ${block.turnRange[0]}-${block.turnRange[1]} | L${block.level}`,
|
|
3716
|
+
`block ${block.id} | turns ${block.turnRange[0]}-${block.turnRange[1]} | age ${block.age} | L${block.level}`,
|
|
3417
3717
|
block.content
|
|
3418
3718
|
].join("\n")).join("\n\n");
|
|
3419
3719
|
}
|
|
@@ -3661,6 +3961,20 @@ function registerMemoryTools(ctx, runtime) {
|
|
|
3661
3961
|
}
|
|
3662
3962
|
|
|
3663
3963
|
// src/web.ts
|
|
3964
|
+
import { createRequire } from "node:module";
|
|
3965
|
+
var STRATAGATE_DSH_VERSION = "0.2.21";
|
|
3966
|
+
var LEGACY_THREAD_ID = "__legacy__";
|
|
3967
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
3968
|
+
function installedPackageVersion(names) {
|
|
3969
|
+
for (const name2 of names) {
|
|
3970
|
+
try {
|
|
3971
|
+
const value = nodeRequire(`${name2}/package.json`);
|
|
3972
|
+
if (typeof value.version === "string" && value.version.trim()) return value.version;
|
|
3973
|
+
} catch {
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
return "unknown";
|
|
3977
|
+
}
|
|
3664
3978
|
function sendJson(res, status, body) {
|
|
3665
3979
|
res.statusCode = status;
|
|
3666
3980
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
@@ -3703,6 +4017,17 @@ function sourceMessages(snapshot, ids) {
|
|
|
3703
4017
|
}
|
|
3704
4018
|
return output;
|
|
3705
4019
|
}
|
|
4020
|
+
function blockLayers(block) {
|
|
4021
|
+
return [
|
|
4022
|
+
{ level: 0, content: `${block.l0Title}
|
|
4023
|
+
\u6807\u7B7E\uFF1A${block.l0Tags.join("\u3001") || "\u65E0"}` },
|
|
4024
|
+
{ level: 1, content: block.l1Summary || block.l0Title },
|
|
4025
|
+
{ level: 2, content: block.l2Keypoints.map((point) => `\u2022 ${point}`).join("\n") || block.l1Summary || block.l0Title },
|
|
4026
|
+
{ level: 3, content: block.l3Condensed || block.l2Keypoints.join("\n") || block.l1Summary },
|
|
4027
|
+
{ level: 4, content: block.l4Readable || block.l3Condensed },
|
|
4028
|
+
{ level: 5, content: block.l5Raw.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
4029
|
+
];
|
|
4030
|
+
}
|
|
3706
4031
|
function eventSummary(event) {
|
|
3707
4032
|
return {
|
|
3708
4033
|
id: event.id,
|
|
@@ -3787,15 +4112,18 @@ async function overview(runtime) {
|
|
|
3787
4112
|
].sort();
|
|
3788
4113
|
rows.push({
|
|
3789
4114
|
namespace,
|
|
4115
|
+
workspaceName: runtime.adminWorkspaceName(namespace) ?? "\u5F53\u524D\u5DE5\u4F5C\u533A",
|
|
3790
4116
|
schemaVersion: snapshot.schemaVersion,
|
|
3791
4117
|
currentTurn: snapshot.currentTurn,
|
|
3792
4118
|
blockTurnSize: snapshot.blockTurnSize,
|
|
4119
|
+
blockDecayLambda: snapshot.blockDecayLambda,
|
|
3793
4120
|
blocks: snapshot.blocks.length,
|
|
3794
4121
|
openTailMessages: snapshot.openTail.length,
|
|
3795
4122
|
events: snapshot.events.length,
|
|
3796
4123
|
activeEvents: snapshot.events.filter(({ status }) => status === "active").length,
|
|
3797
4124
|
elements: snapshot.elements.length,
|
|
3798
4125
|
usageReceipts: snapshot.usageReceipts.length,
|
|
4126
|
+
memoryUseCount: snapshot.usageReceipts.filter((receipt) => receipt.eventIds.length > 0 || receipt.elementIds.length > 0).length,
|
|
3799
4127
|
failedJobs,
|
|
3800
4128
|
processingJobs,
|
|
3801
4129
|
failedJobDetails,
|
|
@@ -3803,7 +4131,141 @@ async function overview(runtime) {
|
|
|
3803
4131
|
lastActivityAt: timestamps.at(-1) ?? null
|
|
3804
4132
|
});
|
|
3805
4133
|
}
|
|
3806
|
-
return {
|
|
4134
|
+
return {
|
|
4135
|
+
readonly: true,
|
|
4136
|
+
settingsWritable: true,
|
|
4137
|
+
pluginVersion: STRATAGATE_DSH_VERSION,
|
|
4138
|
+
harnessVersion: installedPackageVersion(["@deepseek-ai/dsh", "@deepseek-ai/dsh-session"]),
|
|
4139
|
+
namespaces: rows
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
async function updateSettings(runtime, url) {
|
|
4143
|
+
const raw = url.searchParams.get("blockDecayLambda")?.trim() ?? "";
|
|
4144
|
+
const value = Number(raw);
|
|
4145
|
+
if (!raw || !Number.isFinite(value) || value < 0) {
|
|
4146
|
+
throw new AdminHttpError(400, "blockDecayLambda must be a non-negative finite number");
|
|
4147
|
+
}
|
|
4148
|
+
return { blockDecayLambda: await runtime.adminSetBlockDecayLambda(value) };
|
|
4149
|
+
}
|
|
4150
|
+
function receiptThreadId(id) {
|
|
4151
|
+
const match = /^dsh:(.+):turn:\d+$/.exec(id);
|
|
4152
|
+
return match?.[1]?.trim() || null;
|
|
4153
|
+
}
|
|
4154
|
+
function timestampKey(value) {
|
|
4155
|
+
const parsed = Date.parse(value);
|
|
4156
|
+
return Number.isFinite(parsed) ? String(parsed) : value;
|
|
4157
|
+
}
|
|
4158
|
+
function recoverSnapshotView(snapshot) {
|
|
4159
|
+
const receiptThreads = /* @__PURE__ */ new Map();
|
|
4160
|
+
const receiptActivity = /* @__PURE__ */ new Map();
|
|
4161
|
+
const receiptCandidates = /* @__PURE__ */ new Map();
|
|
4162
|
+
for (const receipt of snapshot.ingestionReceipts) {
|
|
4163
|
+
const threadId = receiptThreadId(receipt.id);
|
|
4164
|
+
if (!threadId) continue;
|
|
4165
|
+
receiptThreads.set(receipt.id, threadId);
|
|
4166
|
+
const currentActivity = receiptActivity.get(threadId);
|
|
4167
|
+
if (!currentActivity || receipt.createdAt > currentActivity) receiptActivity.set(threadId, receipt.createdAt);
|
|
4168
|
+
const key = timestampKey(receipt.createdAt);
|
|
4169
|
+
const candidates = receiptCandidates.get(key) ?? /* @__PURE__ */ new Set();
|
|
4170
|
+
candidates.add(threadId);
|
|
4171
|
+
receiptCandidates.set(key, candidates);
|
|
4172
|
+
}
|
|
4173
|
+
const exactThreadAt = new Map([...receiptCandidates].filter(([, ids]) => ids.size === 1).map(([createdAt, ids]) => [createdAt, [...ids][0]]));
|
|
4174
|
+
const recoverMessages = (messages) => {
|
|
4175
|
+
let precedingThreadId = null;
|
|
4176
|
+
return messages.map((message) => {
|
|
4177
|
+
const explicit = message.threadId?.trim();
|
|
4178
|
+
const exact = exactThreadAt.get(timestampKey(message.createdAt));
|
|
4179
|
+
const recovered = explicit || exact || (message.role === "assistant" ? precedingThreadId : null);
|
|
4180
|
+
const threadId = recovered || LEGACY_THREAD_ID;
|
|
4181
|
+
if (message.role === "user" || explicit || exact) precedingThreadId = threadId;
|
|
4182
|
+
return { message, threadId };
|
|
4183
|
+
});
|
|
4184
|
+
};
|
|
4185
|
+
const blocks = [];
|
|
4186
|
+
for (const source of snapshot.blocks) {
|
|
4187
|
+
const recovered = recoverMessages(source.l5Raw);
|
|
4188
|
+
const groups = /* @__PURE__ */ new Map();
|
|
4189
|
+
for (const item of recovered) {
|
|
4190
|
+
const messages = groups.get(item.threadId) ?? [];
|
|
4191
|
+
messages.push(item.message);
|
|
4192
|
+
groups.set(item.threadId, messages);
|
|
4193
|
+
}
|
|
4194
|
+
const entries = [...groups];
|
|
4195
|
+
for (const [threadId, messages] of entries) {
|
|
4196
|
+
const virtual = !source.threadId && (entries.length > 1 || threadId !== LEGACY_THREAD_ID);
|
|
4197
|
+
blocks.push({
|
|
4198
|
+
id: entries.length > 1 ? `virtual:${source.id}:${encodeURIComponent(threadId)}` : source.id,
|
|
4199
|
+
source,
|
|
4200
|
+
threadId,
|
|
4201
|
+
messages,
|
|
4202
|
+
virtual,
|
|
4203
|
+
turnRange: [0, 0]
|
|
4204
|
+
});
|
|
4205
|
+
}
|
|
4206
|
+
}
|
|
4207
|
+
const turnCounters = /* @__PURE__ */ new Map();
|
|
4208
|
+
for (const block of blocks) {
|
|
4209
|
+
if (block.source.threadId) {
|
|
4210
|
+
block.turnRange = [block.source.startTurn, block.source.endTurn];
|
|
4211
|
+
turnCounters.set(block.threadId, Math.max(turnCounters.get(block.threadId) ?? 0, block.source.endTurn));
|
|
4212
|
+
continue;
|
|
4213
|
+
}
|
|
4214
|
+
const turns = Math.max(1, block.messages.filter(({ role }) => role === "user").length);
|
|
4215
|
+
const start = (turnCounters.get(block.threadId) ?? 0) + 1;
|
|
4216
|
+
block.turnRange = [start, start + turns - 1];
|
|
4217
|
+
turnCounters.set(block.threadId, start + turns - 1);
|
|
4218
|
+
}
|
|
4219
|
+
return {
|
|
4220
|
+
blocks,
|
|
4221
|
+
openMessages: recoverMessages(snapshot.openTail),
|
|
4222
|
+
receiptThreads,
|
|
4223
|
+
receiptActivity
|
|
4224
|
+
};
|
|
4225
|
+
}
|
|
4226
|
+
function virtualBlockLayers(block) {
|
|
4227
|
+
if (!block.virtual || block.messages.length === block.source.l5Raw.length) return blockLayers(block.source);
|
|
4228
|
+
const deterministic = deterministicBlockLayers(block.messages);
|
|
4229
|
+
const natural = block.messages.filter(({ role }) => role === "user" || role === "assistant");
|
|
4230
|
+
const firstUser = natural.find(({ role, content }) => role === "user" && content.trim());
|
|
4231
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5";
|
|
4232
|
+
const summary = natural.map(({ content }) => content.replace(/\s+/g, " ").trim()).filter(Boolean).join(" ").slice(0, 500);
|
|
4233
|
+
const keypoints = natural.filter(({ role }) => role === "user").map(({ content }) => content.replace(/\s+/g, " ").trim().slice(0, 160));
|
|
4234
|
+
return [
|
|
4235
|
+
{ level: 0, content: title },
|
|
4236
|
+
{ level: 1, content: summary || title },
|
|
4237
|
+
{ level: 2, content: keypoints.map((point) => `\u2022 ${point}`).join("\n") || summary || title },
|
|
4238
|
+
{ level: 3, content: deterministic.l3Condensed },
|
|
4239
|
+
{ level: 4, content: deterministic.l4Readable },
|
|
4240
|
+
{ level: 5, content: block.messages.map((message) => `${message.role}: ${message.content}`).join("\n\n") }
|
|
4241
|
+
];
|
|
4242
|
+
}
|
|
4243
|
+
function conversationRows(snapshot, view = recoverSnapshotView(snapshot)) {
|
|
4244
|
+
const ids = /* @__PURE__ */ new Set([
|
|
4245
|
+
...view.blocks.map((block) => block.threadId),
|
|
4246
|
+
...view.openMessages.map(({ threadId }) => threadId),
|
|
4247
|
+
...view.receiptThreads.values()
|
|
4248
|
+
]);
|
|
4249
|
+
return [...ids].map((id) => {
|
|
4250
|
+
const blocks = view.blocks.filter((block) => block.threadId === id);
|
|
4251
|
+
const messages = [
|
|
4252
|
+
...blocks.flatMap((block) => block.messages),
|
|
4253
|
+
...view.openMessages.filter((message) => message.threadId === id).map(({ message }) => message)
|
|
4254
|
+
];
|
|
4255
|
+
const firstUser = messages.find(({ role, content }) => role === "user" && content.trim());
|
|
4256
|
+
const title = firstUser?.content.replace(/\s+/g, " ").trim().slice(0, 28);
|
|
4257
|
+
const timestamps = [
|
|
4258
|
+
...blocks.map(({ source }) => source.createdAt),
|
|
4259
|
+
...messages.map(({ createdAt }) => createdAt),
|
|
4260
|
+
...view.receiptActivity.get(id) ? [view.receiptActivity.get(id)] : []
|
|
4261
|
+
].sort();
|
|
4262
|
+
return {
|
|
4263
|
+
id,
|
|
4264
|
+
label: id === LEGACY_THREAD_ID ? "\u5386\u53F2\u5BF9\u8BDD" : title || `\u5BF9\u8BDD ${id.slice(0, 8)}`,
|
|
4265
|
+
blocks: blocks.length,
|
|
4266
|
+
lastActivityAt: timestamps.at(-1) ?? null
|
|
4267
|
+
};
|
|
4268
|
+
}).sort((left, right) => String(right.lastActivityAt).localeCompare(String(left.lastActivityAt)));
|
|
3807
4269
|
}
|
|
3808
4270
|
async function memories(runtime, url) {
|
|
3809
4271
|
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
@@ -3819,44 +4281,85 @@ async function memories(runtime, url) {
|
|
|
3819
4281
|
relatedElements: snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.includes(event.id)).map(({ id, name: name2 }) => ({ id, name: name2 }))
|
|
3820
4282
|
}));
|
|
3821
4283
|
else if (kind === "elements") values = snapshot.elements.map(elementSummary);
|
|
3822
|
-
else if (kind === "blocks")
|
|
3823
|
-
const
|
|
3824
|
-
const
|
|
3825
|
-
const
|
|
3826
|
-
const
|
|
3827
|
-
const
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
4284
|
+
else if (kind === "blocks") {
|
|
4285
|
+
const recovered = recoverSnapshotView(snapshot);
|
|
4286
|
+
const conversations = conversationRows(snapshot, recovered);
|
|
4287
|
+
const requestedThreadId = url.searchParams.get("threadId")?.trim() ?? "";
|
|
4288
|
+
const activeThreadId = requestedThreadId || conversations[0]?.id || null;
|
|
4289
|
+
const scopedBlocks = activeThreadId ? recovered.blocks.filter((block) => block.threadId === activeThreadId) : [];
|
|
4290
|
+
values = scopedBlocks.map((block) => {
|
|
4291
|
+
const source = block.source;
|
|
4292
|
+
const extraction = snapshot.extractionJobs.find(({ blockId }) => blockId === source.id);
|
|
4293
|
+
const blockMessageIds = new Set(block.messages.map(({ id }) => id));
|
|
4294
|
+
const relatedEvents = snapshot.events.filter((event) => event.sourceBlockId === source.id && (!block.virtual || event.sourceMessageIds.some((id) => blockMessageIds.has(id))));
|
|
4295
|
+
const eventIds = new Set(relatedEvents.map(({ id }) => id));
|
|
4296
|
+
const projections = snapshot.elementProjectionJobs.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
4297
|
+
const relatedElements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id))).map(({ id, name: name2 }) => ({ id, name: name2 }));
|
|
4298
|
+
const failedProjection = projections.find(({ status: status2 }) => status2 === "failed");
|
|
4299
|
+
const pendingProjection = projections.some(({ status: status2 }) => status2 === "pending" || status2 === "running");
|
|
4300
|
+
const needsExtraction = source.shouldExtract === true;
|
|
4301
|
+
const status = extraction?.status === "failed" || failedProjection ? "failed" : extraction?.status === "succeeded" || extraction?.status === "skipped" ? pendingProjection ? "processing" : "organized" : needsExtraction ? "waiting" : "organized";
|
|
4302
|
+
const blockPosition = scopedBlocks.findIndex(({ id }) => id === block.id) + 1;
|
|
4303
|
+
const latestBlockPosition = scopedBlocks.length;
|
|
4304
|
+
const currentLevel = getDecayedBlockLevel(
|
|
4305
|
+
source.pointerAnchorLevel,
|
|
4306
|
+
source.threadId ? source.pointerAnchorBlockPosition : Math.min(source.pointerAnchorBlockPosition, blockPosition),
|
|
4307
|
+
latestBlockPosition,
|
|
4308
|
+
snapshot.blockDecayLambda
|
|
4309
|
+
);
|
|
4310
|
+
return {
|
|
4311
|
+
id: block.id,
|
|
4312
|
+
sourceBlockId: source.id,
|
|
4313
|
+
threadId: block.threadId,
|
|
4314
|
+
sequence: source.sequence,
|
|
4315
|
+
turnRange: block.turnRange,
|
|
4316
|
+
title: block.virtual && block.messages.length !== source.l5Raw.length ? block.messages.find(({ role }) => role === "user")?.content.replace(/\s+/g, " ").trim().slice(0, 80) || "\u65E7\u4F1A\u8BDD\u7247\u6BB5" : source.l0Title,
|
|
4317
|
+
tags: source.l0Tags,
|
|
4318
|
+
summary: source.l1Summary,
|
|
4319
|
+
keypoints: source.l2Keypoints,
|
|
4320
|
+
currentLevel,
|
|
4321
|
+
distanceFromLatest: Math.max(0, latestBlockPosition - blockPosition),
|
|
4322
|
+
expansionSource: source.lastLiftedAt ? source.lastLiftedBy ?? "legacy" : null,
|
|
4323
|
+
lastLiftedAt: source.lastLiftedAt,
|
|
4324
|
+
sourceMessages: block.messages.length,
|
|
4325
|
+
createdAt: source.createdAt,
|
|
4326
|
+
virtual: block.virtual,
|
|
4327
|
+
status,
|
|
4328
|
+
eventExtraction: extraction ? {
|
|
4329
|
+
status: extraction.status,
|
|
4330
|
+
attempts: extraction.attempts,
|
|
4331
|
+
updatedAt: extraction.updatedAt,
|
|
4332
|
+
lastError: extraction.lastError
|
|
4333
|
+
} : null,
|
|
4334
|
+
elementProjection: projections.length ? {
|
|
4335
|
+
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
4336
|
+
jobs: projections.length,
|
|
4337
|
+
lastError: failedProjection?.lastError ?? null
|
|
4338
|
+
} : null,
|
|
4339
|
+
relatedEvents: relatedEvents.map(eventSummary),
|
|
4340
|
+
relatedElements
|
|
4341
|
+
};
|
|
4342
|
+
});
|
|
4343
|
+
const filtered2 = values.filter((value) => matchesQuery(value, query));
|
|
4344
|
+
const latestSealedTurn = scopedBlocks.reduce((latest, block) => Math.max(latest, block.turnRange[1]), 0);
|
|
4345
|
+
const openMessages = activeThreadId ? recovered.openMessages.filter((message) => message.threadId === activeThreadId).map(({ message }) => message) : [];
|
|
4346
|
+
const openTurns = openMessages.filter(({ role }) => role === "user").length;
|
|
3832
4347
|
return {
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
attempts: extraction.attempts,
|
|
3847
|
-
updatedAt: extraction.updatedAt,
|
|
3848
|
-
lastError: extraction.lastError
|
|
3849
|
-
} : null,
|
|
3850
|
-
elementProjection: projections.length ? {
|
|
3851
|
-
status: failedProjection ? "failed" : pendingProjection ? "processing" : "completed",
|
|
3852
|
-
jobs: projections.length,
|
|
3853
|
-
lastError: failedProjection?.lastError ?? null
|
|
3854
|
-
} : null,
|
|
3855
|
-
relatedEvents: relatedEvents.map(eventSummary),
|
|
3856
|
-
relatedElements
|
|
4348
|
+
namespace,
|
|
4349
|
+
kind,
|
|
4350
|
+
total: filtered2.length,
|
|
4351
|
+
offset,
|
|
4352
|
+
limit,
|
|
4353
|
+
items: filtered2.slice(offset, offset + limit),
|
|
4354
|
+
openBlock: {
|
|
4355
|
+
turnRange: openTurns > 0 ? [latestSealedTurn + 1, latestSealedTurn + openTurns] : null,
|
|
4356
|
+
messages: openMessages.length,
|
|
4357
|
+
status: "open"
|
|
4358
|
+
},
|
|
4359
|
+
conversations,
|
|
4360
|
+
activeThreadId
|
|
3857
4361
|
};
|
|
3858
|
-
});
|
|
3859
|
-
else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
4362
|
+
} else throw new AdminHttpError(400, `Unsupported memory kind: ${kind}`);
|
|
3860
4363
|
const filtered = values.filter((value) => matchesQuery(value, query));
|
|
3861
4364
|
return { namespace, kind, total: filtered.length, offset, limit, items: filtered.slice(offset, offset + limit) };
|
|
3862
4365
|
}
|
|
@@ -3882,12 +4385,22 @@ async function sources(runtime, url) {
|
|
|
3882
4385
|
events = snapshot.events.filter(({ id }) => element.sourceEventIds.includes(id));
|
|
3883
4386
|
ids = new Set(events.flatMap(({ sourceMessageIds }) => sourceMessageIds));
|
|
3884
4387
|
} else if (blockId) {
|
|
3885
|
-
const
|
|
4388
|
+
const displayBlock = recoverSnapshotView(snapshot).blocks.find(({ id }) => id === blockId);
|
|
4389
|
+
const block = displayBlock?.source ?? snapshot.blocks.find(({ id }) => id === blockId);
|
|
3886
4390
|
if (!block) throw new AdminHttpError(404, `Unknown block: ${blockId}`);
|
|
3887
|
-
|
|
3888
|
-
|
|
4391
|
+
const messages = displayBlock?.messages ?? block.l5Raw;
|
|
4392
|
+
ids = new Set(messages.map(({ id }) => id));
|
|
4393
|
+
events = snapshot.events.filter((event) => event.sourceBlockId === block.id && (!displayBlock?.virtual || event.sourceMessageIds.some((id) => ids.has(id))));
|
|
3889
4394
|
const eventIds = new Set(events.map(({ id }) => id));
|
|
3890
4395
|
elements = snapshot.elements.filter(({ sourceEventIds }) => sourceEventIds.some((id) => eventIds.has(id)));
|
|
4396
|
+
return {
|
|
4397
|
+
namespace,
|
|
4398
|
+
events: events.map(eventSummary),
|
|
4399
|
+
elements: elements.map(elementSummary),
|
|
4400
|
+
messages: sourceMessages(snapshot, ids),
|
|
4401
|
+
layers: displayBlock ? virtualBlockLayers(displayBlock) : blockLayers(block),
|
|
4402
|
+
virtual: displayBlock?.virtual ?? false
|
|
4403
|
+
};
|
|
3891
4404
|
} else {
|
|
3892
4405
|
throw new AdminHttpError(400, "eventId, elementId, or blockId is required");
|
|
3893
4406
|
}
|
|
@@ -3898,6 +4411,16 @@ async function sources(runtime, url) {
|
|
|
3898
4411
|
messages: sourceMessages(snapshot, ids)
|
|
3899
4412
|
};
|
|
3900
4413
|
}
|
|
4414
|
+
async function expandBlock(runtime, url) {
|
|
4415
|
+
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
4416
|
+
const blockId = url.searchParams.get("blockId")?.trim() ?? "";
|
|
4417
|
+
const target = url.searchParams.get("level")?.trim() ?? "";
|
|
4418
|
+
if (!namespace) throw new AdminHttpError(400, "namespace is required");
|
|
4419
|
+
if (!blockId) throw new AdminHttpError(400, "blockId is required");
|
|
4420
|
+
if (blockId.startsWith("virtual:")) throw new AdminHttpError(409, "Recovered legacy fragments are read-only display data");
|
|
4421
|
+
if (!/^L?[0-5]$/i.test(target)) throw new AdminHttpError(400, "level must be L0 through L5");
|
|
4422
|
+
return runtime.adminExpandBlock(namespace, blockId, target);
|
|
4423
|
+
}
|
|
3901
4424
|
function receiptSources(snapshot, receipt) {
|
|
3902
4425
|
const events = snapshot.events.filter(({ id }) => receipt.eventIds.includes(id));
|
|
3903
4426
|
const elements = snapshot.elements.filter(({ id }) => receipt.elementIds.includes(id));
|
|
@@ -3928,10 +4451,16 @@ async function audit(runtime, url) {
|
|
|
3928
4451
|
}
|
|
3929
4452
|
async function handleAdminRequest(runtime, req, res) {
|
|
3930
4453
|
try {
|
|
3931
|
-
if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate Memory UI is read-only");
|
|
3932
4454
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3933
4455
|
const path = url.pathname.replace(/\/$/, "");
|
|
3934
|
-
if (path === "/api/stratagate/
|
|
4456
|
+
if (path === "/api/stratagate/settings") {
|
|
4457
|
+
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate settings require PATCH");
|
|
4458
|
+
sendJson(res, 200, await updateSettings(runtime, url));
|
|
4459
|
+
} else if (path === "/api/stratagate/blocks/expand") {
|
|
4460
|
+
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate Block expansion requires PATCH");
|
|
4461
|
+
sendJson(res, 200, await expandBlock(runtime, url));
|
|
4462
|
+
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
4463
|
+
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
3935
4464
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
3936
4465
|
else if (path === "/api/stratagate/sources") sendJson(res, 200, await sources(runtime, url));
|
|
3937
4466
|
else if (path === "/api/stratagate/audit") sendJson(res, 200, await audit(runtime, url));
|
|
@@ -3974,7 +4503,7 @@ async function apply(ctx, config) {
|
|
|
3974
4503
|
const runtime = new StrataGateRuntime(resolved, models, (error) => {
|
|
3975
4504
|
ctx.logger.error(`stratagate-memory ingestion failed: ${renderError(error)}`);
|
|
3976
4505
|
});
|
|
3977
|
-
await runtime.
|
|
4506
|
+
await runtime.syncConfiguredSettings();
|
|
3978
4507
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
3979
4508
|
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
3980
4509
|
const assembled = await next();
|