stratagate-dsh 0.2.15 → 0.2.19
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 +22 -0
- package/README.md +12 -7
- package/cordis.patch.yml +1 -0
- package/dist/client.js +89 -52
- package/dist/index.d.ts +1 -0
- package/dist/index.js +415 -73
- package/dist/index.js.map +1 -1
- package/{README.zh-CN.md → docs/README.zh-CN.md} +12 -7
- package/package.json +12 -2
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 = 6;
|
|
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) };
|
|
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,21 +392,45 @@ 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;
|
|
413
|
+
snapshot = {
|
|
414
|
+
...structuredClone(legacy),
|
|
415
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
416
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
417
|
+
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
418
|
+
};
|
|
419
|
+
} else if (schemaVersion === 4) {
|
|
420
|
+
const legacy = value;
|
|
421
|
+
snapshot = {
|
|
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;
|
|
398
429
|
snapshot = {
|
|
399
|
-
...structuredClone(
|
|
400
|
-
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION
|
|
430
|
+
...structuredClone(legacy),
|
|
431
|
+
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
432
|
+
blockDecayLambda: BLOCK_DECAY_LAMBDA,
|
|
433
|
+
blocks: migrateLegacyBlocks(legacy.blocks)
|
|
401
434
|
};
|
|
402
435
|
} else if (schemaVersion === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
403
436
|
snapshot = structuredClone(value);
|
|
@@ -410,10 +443,18 @@ function normalizeSnapshot(value) {
|
|
|
410
443
|
if (!Number.isSafeInteger(snapshot.blockTurnSize) || (snapshot.blockTurnSize ?? 0) < 1) {
|
|
411
444
|
throw new TypeError("Invalid StrataGate snapshot: blockTurnSize must be a positive integer");
|
|
412
445
|
}
|
|
446
|
+
if (!Number.isFinite(snapshot.blockDecayLambda) || snapshot.blockDecayLambda < 0) {
|
|
447
|
+
throw new TypeError("Invalid StrataGate snapshot: blockDecayLambda must be a non-negative finite number");
|
|
448
|
+
}
|
|
413
449
|
for (const key of ["openTail", "blocks", "events", "elements", "extractionJobs", "elementProjectionJobs", "usageReceipts", "ingestionReceipts"]) {
|
|
414
450
|
if (!Array.isArray(snapshot[key])) throw new TypeError(`Invalid StrataGate snapshot: ${key} must be an array`);
|
|
415
451
|
}
|
|
416
452
|
if (!Array.isArray(snapshot.successfulModelResponses)) snapshot.successfulModelResponses = [];
|
|
453
|
+
for (const block of snapshot.blocks) {
|
|
454
|
+
if (!Number.isSafeInteger(block.pointerAnchorBlockPosition) || block.pointerAnchorBlockPosition < 1) {
|
|
455
|
+
throw new TypeError("Invalid StrataGate snapshot: pointerAnchorBlockPosition must be a positive integer");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
417
458
|
if (snapshot.successfulModelResponses.length > 5) {
|
|
418
459
|
snapshot.successfulModelResponses = snapshot.successfulModelResponses.slice(-5);
|
|
419
460
|
}
|
|
@@ -587,6 +628,7 @@ CREATE TABLE IF NOT EXISTS memory_spaces (
|
|
|
587
628
|
revision INTEGER NOT NULL,
|
|
588
629
|
current_turn INTEGER NOT NULL,
|
|
589
630
|
block_turn_size INTEGER NOT NULL,
|
|
631
|
+
block_decay_lambda REAL NOT NULL,
|
|
590
632
|
created_at TEXT NOT NULL,
|
|
591
633
|
updated_at TEXT NOT NULL
|
|
592
634
|
) STRICT;
|
|
@@ -594,6 +636,7 @@ CREATE TABLE IF NOT EXISTS memory_spaces (
|
|
|
594
636
|
CREATE TABLE IF NOT EXISTS blocks (
|
|
595
637
|
namespace TEXT NOT NULL,
|
|
596
638
|
id TEXT NOT NULL,
|
|
639
|
+
thread_id TEXT,
|
|
597
640
|
sequence INTEGER NOT NULL,
|
|
598
641
|
start_turn INTEGER NOT NULL,
|
|
599
642
|
end_turn INTEGER NOT NULL,
|
|
@@ -607,7 +650,7 @@ CREATE TABLE IF NOT EXISTS blocks (
|
|
|
607
650
|
l4_readable TEXT NOT NULL,
|
|
608
651
|
pointer_current_level INTEGER NOT NULL,
|
|
609
652
|
pointer_anchor_level INTEGER NOT NULL,
|
|
610
|
-
|
|
653
|
+
pointer_anchor_block_position INTEGER NOT NULL,
|
|
611
654
|
last_lifted_at TEXT,
|
|
612
655
|
PRIMARY KEY (namespace, id),
|
|
613
656
|
UNIQUE (namespace, sequence),
|
|
@@ -618,6 +661,7 @@ CREATE TABLE IF NOT EXISTS messages (
|
|
|
618
661
|
namespace TEXT NOT NULL,
|
|
619
662
|
id TEXT NOT NULL,
|
|
620
663
|
block_id TEXT,
|
|
664
|
+
thread_id TEXT,
|
|
621
665
|
position INTEGER NOT NULL,
|
|
622
666
|
role TEXT NOT NULL,
|
|
623
667
|
content TEXT NOT NULL,
|
|
@@ -781,6 +825,10 @@ CREATE TABLE IF NOT EXISTS ingestion_receipts (
|
|
|
781
825
|
FOREIGN KEY (namespace) REFERENCES memory_spaces(namespace) ON DELETE CASCADE
|
|
782
826
|
) STRICT;
|
|
783
827
|
`;
|
|
828
|
+
var THREAD_INDEXES = `
|
|
829
|
+
CREATE INDEX IF NOT EXISTS messages_thread_idx ON messages(namespace, thread_id, position);
|
|
830
|
+
CREATE INDEX IF NOT EXISTS blocks_thread_idx ON blocks(namespace, thread_id, sequence);
|
|
831
|
+
`;
|
|
784
832
|
function parseJson(value, label) {
|
|
785
833
|
try {
|
|
786
834
|
return JSON.parse(value);
|
|
@@ -820,7 +868,7 @@ var SqliteStorage = class {
|
|
|
820
868
|
this.assertOpen();
|
|
821
869
|
const key = nonEmptyNamespace(namespace);
|
|
822
870
|
const space = this.database.prepare(`
|
|
823
|
-
SELECT schema_version, revision, current_turn, block_turn_size
|
|
871
|
+
SELECT schema_version, revision, current_turn, block_turn_size, block_decay_lambda
|
|
824
872
|
FROM memory_spaces WHERE namespace = ?
|
|
825
873
|
`).get(key);
|
|
826
874
|
if (!space) return null;
|
|
@@ -828,7 +876,7 @@ var SqliteStorage = class {
|
|
|
828
876
|
throw new Error(`Unsupported stored StrataGate schema: ${space.schema_version}`);
|
|
829
877
|
}
|
|
830
878
|
const messageRows = this.database.prepare(`
|
|
831
|
-
SELECT id, block_id, position, role, content, created_at, tool_calls_json
|
|
879
|
+
SELECT id, block_id, thread_id, position, role, content, created_at, tool_calls_json
|
|
832
880
|
FROM messages WHERE namespace = ? ORDER BY block_id, position
|
|
833
881
|
`).all(key);
|
|
834
882
|
const openTail = [];
|
|
@@ -839,6 +887,7 @@ var SqliteStorage = class {
|
|
|
839
887
|
role: row.role,
|
|
840
888
|
content: row.content,
|
|
841
889
|
createdAt: row.created_at,
|
|
890
|
+
...row.thread_id ? { threadId: row.thread_id } : {},
|
|
842
891
|
...row.tool_calls_json ? { toolCalls: parseJson(row.tool_calls_json, "messages.tool_calls_json") } : {}
|
|
843
892
|
};
|
|
844
893
|
if (row.block_id === null) openTail.push(message);
|
|
@@ -853,6 +902,7 @@ var SqliteStorage = class {
|
|
|
853
902
|
`).all(key);
|
|
854
903
|
const blocks = blockRows.map((row) => ({
|
|
855
904
|
id: row.id,
|
|
905
|
+
...row.thread_id ? { threadId: row.thread_id } : {},
|
|
856
906
|
sequence: row.sequence,
|
|
857
907
|
startTurn: row.start_turn,
|
|
858
908
|
endTurn: row.end_turn,
|
|
@@ -867,7 +917,7 @@ var SqliteStorage = class {
|
|
|
867
917
|
l5Raw: messagesByBlock.get(row.id) ?? [],
|
|
868
918
|
pointerCurrentLevel: row.pointer_current_level,
|
|
869
919
|
pointerAnchorLevel: row.pointer_anchor_level,
|
|
870
|
-
|
|
920
|
+
pointerAnchorBlockPosition: row.pointer_anchor_block_position,
|
|
871
921
|
lastLiftedAt: row.last_lifted_at
|
|
872
922
|
}));
|
|
873
923
|
const sourceRows = this.database.prepare(`
|
|
@@ -1034,6 +1084,7 @@ var SqliteStorage = class {
|
|
|
1034
1084
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1035
1085
|
currentTurn: space.current_turn,
|
|
1036
1086
|
blockTurnSize: space.block_turn_size,
|
|
1087
|
+
blockDecayLambda: space.block_decay_lambda,
|
|
1037
1088
|
openTail,
|
|
1038
1089
|
blocks,
|
|
1039
1090
|
events,
|
|
@@ -1072,38 +1123,41 @@ var SqliteStorage = class {
|
|
|
1072
1123
|
if (current) {
|
|
1073
1124
|
this.database.prepare(`
|
|
1074
1125
|
UPDATE memory_spaces
|
|
1075
|
-
SET schema_version = ?, revision = ?, current_turn = ?, block_turn_size = ?, updated_at = ?
|
|
1126
|
+
SET schema_version = ?, revision = ?, current_turn = ?, block_turn_size = ?, block_decay_lambda = ?, updated_at = ?
|
|
1076
1127
|
WHERE namespace = ?
|
|
1077
1128
|
`).run(
|
|
1078
1129
|
snapshot.schemaVersion,
|
|
1079
1130
|
nextRevision,
|
|
1080
1131
|
snapshot.currentTurn,
|
|
1081
1132
|
snapshot.blockTurnSize,
|
|
1133
|
+
snapshot.blockDecayLambda,
|
|
1082
1134
|
updatedAt,
|
|
1083
1135
|
namespace
|
|
1084
1136
|
);
|
|
1085
1137
|
} else {
|
|
1086
1138
|
this.database.prepare(`
|
|
1087
1139
|
INSERT INTO memory_spaces (
|
|
1088
|
-
namespace, schema_version, revision, current_turn, block_turn_size, created_at, updated_at
|
|
1089
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1140
|
+
namespace, schema_version, revision, current_turn, block_turn_size, block_decay_lambda, created_at, updated_at
|
|
1141
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1090
1142
|
`).run(
|
|
1091
1143
|
namespace,
|
|
1092
1144
|
snapshot.schemaVersion,
|
|
1093
1145
|
nextRevision,
|
|
1094
1146
|
snapshot.currentTurn,
|
|
1095
1147
|
snapshot.blockTurnSize,
|
|
1148
|
+
snapshot.blockDecayLambda,
|
|
1096
1149
|
updatedAt,
|
|
1097
1150
|
updatedAt
|
|
1098
1151
|
);
|
|
1099
1152
|
}
|
|
1100
1153
|
const insertBlock = this.database.prepare(`
|
|
1101
1154
|
INSERT INTO blocks (
|
|
1102
|
-
namespace, id, sequence, start_turn, end_turn, created_at, should_extract,
|
|
1155
|
+
namespace, id, thread_id, sequence, start_turn, end_turn, created_at, should_extract,
|
|
1103
1156
|
l0_title, l0_tags_json, l1_summary, l2_keypoints_json, l3_condensed, l4_readable,
|
|
1104
|
-
pointer_current_level, pointer_anchor_level,
|
|
1105
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1157
|
+
pointer_current_level, pointer_anchor_level, pointer_anchor_block_position, last_lifted_at
|
|
1158
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1106
1159
|
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
1160
|
+
thread_id = excluded.thread_id,
|
|
1107
1161
|
sequence = excluded.sequence,
|
|
1108
1162
|
start_turn = excluded.start_turn,
|
|
1109
1163
|
end_turn = excluded.end_turn,
|
|
@@ -1117,13 +1171,14 @@ var SqliteStorage = class {
|
|
|
1117
1171
|
l4_readable = excluded.l4_readable,
|
|
1118
1172
|
pointer_current_level = excluded.pointer_current_level,
|
|
1119
1173
|
pointer_anchor_level = excluded.pointer_anchor_level,
|
|
1120
|
-
|
|
1174
|
+
pointer_anchor_block_position = excluded.pointer_anchor_block_position,
|
|
1121
1175
|
last_lifted_at = excluded.last_lifted_at
|
|
1122
1176
|
`);
|
|
1123
1177
|
for (const block of snapshot.blocks) {
|
|
1124
1178
|
insertBlock.run(
|
|
1125
1179
|
namespace,
|
|
1126
1180
|
block.id,
|
|
1181
|
+
block.threadId ?? null,
|
|
1127
1182
|
block.sequence,
|
|
1128
1183
|
block.startTurn,
|
|
1129
1184
|
block.endTurn,
|
|
@@ -1137,16 +1192,17 @@ var SqliteStorage = class {
|
|
|
1137
1192
|
block.l4Readable,
|
|
1138
1193
|
block.pointerCurrentLevel,
|
|
1139
1194
|
block.pointerAnchorLevel,
|
|
1140
|
-
block.
|
|
1195
|
+
block.pointerAnchorBlockPosition,
|
|
1141
1196
|
block.lastLiftedAt
|
|
1142
1197
|
);
|
|
1143
1198
|
}
|
|
1144
1199
|
const insertMessage = this.database.prepare(`
|
|
1145
1200
|
INSERT INTO messages (
|
|
1146
|
-
namespace, id, block_id, position, role, content, created_at, tool_calls_json
|
|
1147
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1201
|
+
namespace, id, block_id, thread_id, position, role, content, created_at, tool_calls_json
|
|
1202
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1148
1203
|
ON CONFLICT (namespace, id) DO UPDATE SET
|
|
1149
1204
|
block_id = excluded.block_id,
|
|
1205
|
+
thread_id = excluded.thread_id,
|
|
1150
1206
|
position = excluded.position,
|
|
1151
1207
|
role = excluded.role,
|
|
1152
1208
|
content = excluded.content,
|
|
@@ -1159,6 +1215,7 @@ var SqliteStorage = class {
|
|
|
1159
1215
|
namespace,
|
|
1160
1216
|
message.id,
|
|
1161
1217
|
blockId,
|
|
1218
|
+
message.threadId ?? null,
|
|
1162
1219
|
position,
|
|
1163
1220
|
message.role,
|
|
1164
1221
|
message.content,
|
|
@@ -1402,10 +1459,12 @@ var SqliteStorage = class {
|
|
|
1402
1459
|
if (version === 0) {
|
|
1403
1460
|
this.immediateTransaction(() => {
|
|
1404
1461
|
this.database.exec(SCHEMA);
|
|
1462
|
+
this.database.exec(THREAD_INDEXES);
|
|
1405
1463
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1406
1464
|
});
|
|
1407
|
-
} else if (version === 1 || version === 2 || version === 3) {
|
|
1465
|
+
} else if (version === 1 || version === 2 || version === 3 || version === 4 || version === 5) {
|
|
1408
1466
|
this.immediateTransaction(() => {
|
|
1467
|
+
this.database.exec(SCHEMA);
|
|
1409
1468
|
if (version === 1) {
|
|
1410
1469
|
const receiptColumns2 = this.database.prepare("PRAGMA table_info('usage_receipts')").all();
|
|
1411
1470
|
if (!receiptColumns2.some(({ name: name2 }) => name2 === "element_ids_json")) {
|
|
@@ -1416,12 +1475,37 @@ var SqliteStorage = class {
|
|
|
1416
1475
|
if (!receiptColumns.some(({ name: name2 }) => name2 === "audit_json")) {
|
|
1417
1476
|
this.database.exec("ALTER TABLE usage_receipts ADD COLUMN audit_json TEXT NOT NULL DEFAULT '{}'");
|
|
1418
1477
|
}
|
|
1419
|
-
this.database.
|
|
1478
|
+
const spaceColumns = this.database.prepare("PRAGMA table_info('memory_spaces')").all();
|
|
1479
|
+
if (!spaceColumns.some(({ name: name2 }) => name2 === "block_decay_lambda")) {
|
|
1480
|
+
this.database.exec("ALTER TABLE memory_spaces ADD COLUMN block_decay_lambda REAL NOT NULL DEFAULT 0.3");
|
|
1481
|
+
}
|
|
1482
|
+
const blockColumns = this.database.prepare("PRAGMA table_info('blocks')").all();
|
|
1483
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1484
|
+
this.database.exec("ALTER TABLE blocks ADD COLUMN thread_id TEXT");
|
|
1485
|
+
}
|
|
1486
|
+
if (!blockColumns.some(({ name: name2 }) => name2 === "pointer_anchor_block_position")) {
|
|
1487
|
+
this.database.exec("ALTER TABLE blocks RENAME COLUMN pointer_anchor_turn TO pointer_anchor_block_position");
|
|
1488
|
+
this.database.exec(`
|
|
1489
|
+
UPDATE blocks AS target
|
|
1490
|
+
SET pointer_anchor_block_position = MAX(1, (
|
|
1491
|
+
SELECT COUNT(*) FROM blocks AS candidate
|
|
1492
|
+
WHERE candidate.namespace = target.namespace
|
|
1493
|
+
AND candidate.thread_id IS target.thread_id
|
|
1494
|
+
AND candidate.end_turn <= target.pointer_anchor_block_position
|
|
1495
|
+
))
|
|
1496
|
+
`);
|
|
1497
|
+
}
|
|
1498
|
+
const messageColumns = this.database.prepare("PRAGMA table_info('messages')").all();
|
|
1499
|
+
if (!messageColumns.some(({ name: name2 }) => name2 === "thread_id")) {
|
|
1500
|
+
this.database.exec("ALTER TABLE messages ADD COLUMN thread_id TEXT");
|
|
1501
|
+
}
|
|
1502
|
+
this.database.exec(THREAD_INDEXES);
|
|
1420
1503
|
this.database.prepare("UPDATE memory_spaces SET schema_version = ? WHERE schema_version < ?").run(STRATAGATE_STORAGE_SCHEMA_VERSION, STRATAGATE_STORAGE_SCHEMA_VERSION);
|
|
1421
1504
|
this.database.exec(`PRAGMA user_version = ${STRATAGATE_STORAGE_SCHEMA_VERSION}`);
|
|
1422
1505
|
});
|
|
1423
1506
|
} else if (version === STRATAGATE_STORAGE_SCHEMA_VERSION) {
|
|
1424
1507
|
this.database.exec(SCHEMA);
|
|
1508
|
+
this.database.exec(THREAD_INDEXES);
|
|
1425
1509
|
}
|
|
1426
1510
|
this.assertSchemaVersion();
|
|
1427
1511
|
}
|
|
@@ -1498,6 +1582,7 @@ function errorMessage(error) {
|
|
|
1498
1582
|
var STRATAGATE_CONSTRUCTOR_TOKEN = /* @__PURE__ */ Symbol("StrataGate constructor");
|
|
1499
1583
|
var StrataGate = class _StrataGate {
|
|
1500
1584
|
blockTurnSize;
|
|
1585
|
+
blockDecayLambdaValue;
|
|
1501
1586
|
summarizer;
|
|
1502
1587
|
extractor;
|
|
1503
1588
|
elementProjector;
|
|
@@ -1523,6 +1608,11 @@ var StrataGate = class _StrataGate {
|
|
|
1523
1608
|
throw new TypeError("Use StrataGate.open() for SQLite or StrataGate.inMemory() for explicit ephemeral storage");
|
|
1524
1609
|
}
|
|
1525
1610
|
this.blockTurnSize = Math.max(1, Math.floor(options.blockTurnSize ?? DEFAULT_BLOCK_TURN_SIZE));
|
|
1611
|
+
const blockDecayLambda = options.blockDecayLambda ?? BLOCK_DECAY_LAMBDA;
|
|
1612
|
+
if (!Number.isFinite(blockDecayLambda) || blockDecayLambda < 0) {
|
|
1613
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1614
|
+
}
|
|
1615
|
+
this.blockDecayLambdaValue = blockDecayLambda;
|
|
1526
1616
|
this.summarizer = options.summarizer;
|
|
1527
1617
|
this.extractor = options.extractor;
|
|
1528
1618
|
this.elementProjector = options.elementProjector;
|
|
@@ -1545,6 +1635,7 @@ var StrataGate = class _StrataGate {
|
|
|
1545
1635
|
storage,
|
|
1546
1636
|
namespace: options.namespace,
|
|
1547
1637
|
...options.blockTurnSize !== void 0 ? { blockTurnSize: options.blockTurnSize } : {},
|
|
1638
|
+
...options.blockDecayLambda !== void 0 ? { blockDecayLambda: options.blockDecayLambda } : {},
|
|
1548
1639
|
...options.summarizer ? { summarizer: options.summarizer } : {},
|
|
1549
1640
|
...options.extractor ? { extractor: options.extractor } : {},
|
|
1550
1641
|
...options.elementProjector ? { elementProjector: options.elementProjector } : {},
|
|
@@ -1563,17 +1654,32 @@ var StrataGate = class _StrataGate {
|
|
|
1563
1654
|
const loaded = await options.storage.load(namespace);
|
|
1564
1655
|
const loadedSnapshot = loaded ? normalizeSnapshot(loaded.snapshot) : null;
|
|
1565
1656
|
let loadedRevision = loaded?.revision ?? 0;
|
|
1566
|
-
if (loaded &&
|
|
1567
|
-
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1570
|
-
loadedSnapshot.blockTurnSize
|
|
1571
|
-
|
|
1657
|
+
if (loaded && loadedSnapshot) {
|
|
1658
|
+
let settingsChanged = false;
|
|
1659
|
+
if (options.blockTurnSize !== void 0) {
|
|
1660
|
+
const requested = Math.max(1, Math.floor(options.blockTurnSize));
|
|
1661
|
+
if (requested !== loadedSnapshot.blockTurnSize) {
|
|
1662
|
+
loadedSnapshot.blockTurnSize = requested;
|
|
1663
|
+
settingsChanged = true;
|
|
1664
|
+
}
|
|
1572
1665
|
}
|
|
1666
|
+
if (options.blockDecayLambda !== void 0) {
|
|
1667
|
+
const requested = options.blockDecayLambda;
|
|
1668
|
+
if (!Number.isFinite(requested) || requested < 0) {
|
|
1669
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1670
|
+
}
|
|
1671
|
+
if (requested !== loadedSnapshot.blockDecayLambda) {
|
|
1672
|
+
loadedSnapshot.blockDecayLambda = requested;
|
|
1673
|
+
settingsChanged = true;
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
if (settingsChanged) loadedRevision = await options.storage.save(namespace, loadedSnapshot, loadedRevision);
|
|
1573
1677
|
}
|
|
1574
1678
|
const memoryOptions = {};
|
|
1575
1679
|
if (loadedSnapshot) memoryOptions.blockTurnSize = loadedSnapshot.blockTurnSize;
|
|
1576
1680
|
else if (options.blockTurnSize !== void 0) memoryOptions.blockTurnSize = options.blockTurnSize;
|
|
1681
|
+
if (loadedSnapshot) memoryOptions.blockDecayLambda = loadedSnapshot.blockDecayLambda;
|
|
1682
|
+
else if (options.blockDecayLambda !== void 0) memoryOptions.blockDecayLambda = options.blockDecayLambda;
|
|
1577
1683
|
if (options.summarizer) memoryOptions.summarizer = options.summarizer;
|
|
1578
1684
|
if (options.extractor) memoryOptions.extractor = options.extractor;
|
|
1579
1685
|
if (options.elementProjector) memoryOptions.elementProjector = options.elementProjector;
|
|
@@ -1625,6 +1731,18 @@ var StrataGate = class _StrataGate {
|
|
|
1625
1731
|
get storageRevision() {
|
|
1626
1732
|
return this.revision;
|
|
1627
1733
|
}
|
|
1734
|
+
get blockDecayLambda() {
|
|
1735
|
+
return this.blockDecayLambdaValue;
|
|
1736
|
+
}
|
|
1737
|
+
async setBlockDecayLambda(value) {
|
|
1738
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1739
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
1740
|
+
}
|
|
1741
|
+
if (value === this.blockDecayLambdaValue) return;
|
|
1742
|
+
await this.commitMutation(() => {
|
|
1743
|
+
this.blockDecayLambdaValue = value;
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1628
1746
|
listBlocks() {
|
|
1629
1747
|
return this.blocks;
|
|
1630
1748
|
}
|
|
@@ -1634,8 +1752,9 @@ var StrataGate = class _StrataGate {
|
|
|
1634
1752
|
listElements() {
|
|
1635
1753
|
return this.elements;
|
|
1636
1754
|
}
|
|
1637
|
-
listOpenTail() {
|
|
1638
|
-
return this.openTail;
|
|
1755
|
+
listOpenTail(threadId) {
|
|
1756
|
+
if (threadId === void 0) return this.openTail;
|
|
1757
|
+
return this.openTail.filter((message) => message.threadId === threadId);
|
|
1639
1758
|
}
|
|
1640
1759
|
listExtractionJobs() {
|
|
1641
1760
|
return [...this.extractionJobs.values()];
|
|
@@ -1666,6 +1785,7 @@ var StrataGate = class _StrataGate {
|
|
|
1666
1785
|
schemaVersion: STRATAGATE_STORAGE_SCHEMA_VERSION,
|
|
1667
1786
|
currentTurn: this.currentTurn,
|
|
1668
1787
|
blockTurnSize: this.blockTurnSize,
|
|
1788
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
1669
1789
|
openTail: this.openTail,
|
|
1670
1790
|
blocks: this.blocks,
|
|
1671
1791
|
events: this.events,
|
|
@@ -1685,12 +1805,17 @@ var StrataGate = class _StrataGate {
|
|
|
1685
1805
|
if (input.receiptId !== void 0 && !receiptId) {
|
|
1686
1806
|
throw new TypeError("Turn receiptId must not be empty");
|
|
1687
1807
|
}
|
|
1808
|
+
const threadId = input.threadId?.trim();
|
|
1809
|
+
if (input.threadId !== void 0 && !threadId) {
|
|
1810
|
+
throw new TypeError("Turn threadId must not be empty");
|
|
1811
|
+
}
|
|
1688
1812
|
const createdAt = toUtc8Iso(input.createdAt ?? this.now());
|
|
1689
1813
|
const userMessage = {
|
|
1690
1814
|
id: this.idFactory("msg"),
|
|
1691
1815
|
role: "user",
|
|
1692
1816
|
content: input.user,
|
|
1693
1817
|
createdAt,
|
|
1818
|
+
...threadId ? { threadId } : {},
|
|
1694
1819
|
...input.userToolCalls ? { toolCalls: input.userToolCalls } : {}
|
|
1695
1820
|
};
|
|
1696
1821
|
const assistantMessage = {
|
|
@@ -1698,6 +1823,7 @@ var StrataGate = class _StrataGate {
|
|
|
1698
1823
|
role: "assistant",
|
|
1699
1824
|
content: input.assistant,
|
|
1700
1825
|
createdAt,
|
|
1826
|
+
...threadId ? { threadId } : {},
|
|
1701
1827
|
...input.assistantToolCalls ? { toolCalls: input.assistantToolCalls } : {}
|
|
1702
1828
|
};
|
|
1703
1829
|
const appended = await this.commitMutation(() => {
|
|
@@ -1711,11 +1837,11 @@ var StrataGate = class _StrataGate {
|
|
|
1711
1837
|
if (options.deferProcessing === true) {
|
|
1712
1838
|
return { sealedBlock: null, extractedEvents: [], projectedElements: [] };
|
|
1713
1839
|
}
|
|
1714
|
-
if (this.
|
|
1840
|
+
if (this.threadOpenTail(threadId).filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
1715
1841
|
const projectedElements2 = await this.projectEligibleElements() ?? [];
|
|
1716
1842
|
return { sealedBlock: null, extractedEvents: [], projectedElements: projectedElements2 };
|
|
1717
1843
|
}
|
|
1718
|
-
const sealedBlock = await this.sealOpenTail();
|
|
1844
|
+
const sealedBlock = await this.sealOpenTail(threadId);
|
|
1719
1845
|
const extractedEvents = await this.extractEligibleBlock() ?? [];
|
|
1720
1846
|
const projectedElements = await this.projectEligibleElements() ?? [];
|
|
1721
1847
|
return { sealedBlock, extractedEvents, projectedElements };
|
|
@@ -1724,8 +1850,10 @@ var StrataGate = class _StrataGate {
|
|
|
1724
1850
|
const sealedBlocks = [];
|
|
1725
1851
|
const extractedEvents = [];
|
|
1726
1852
|
const projectedElements = [];
|
|
1727
|
-
while (
|
|
1728
|
-
|
|
1853
|
+
while (true) {
|
|
1854
|
+
const sealable = this.nextSealableThread();
|
|
1855
|
+
if (sealable === null) break;
|
|
1856
|
+
sealedBlocks.push(await this.sealOpenTail(sealable.threadId));
|
|
1729
1857
|
extractedEvents.push(...await this.extractEligibleBlock() ?? []);
|
|
1730
1858
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
1731
1859
|
}
|
|
@@ -1736,7 +1864,7 @@ var StrataGate = class _StrataGate {
|
|
|
1736
1864
|
projectedElements.push(...await this.projectEligibleElements() ?? []);
|
|
1737
1865
|
}
|
|
1738
1866
|
if (options.retrySkipped === true) {
|
|
1739
|
-
const skippedBlockIds = this.blocks.filter((block
|
|
1867
|
+
const skippedBlockIds = this.blocks.filter((block) => this.nextBlockInThread(block) !== null && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
|
|
1740
1868
|
for (const blockId of skippedBlockIds) {
|
|
1741
1869
|
const extracted = await this.extractEligibleBlock({ blockId, includeSkipped: true });
|
|
1742
1870
|
if (extracted === null) continue;
|
|
@@ -1955,13 +2083,25 @@ var StrataGate = class _StrataGate {
|
|
|
1955
2083
|
}
|
|
1956
2084
|
return hits;
|
|
1957
2085
|
}
|
|
1958
|
-
getBlockContext() {
|
|
1959
|
-
|
|
1960
|
-
|
|
2086
|
+
getBlockContext(threadId) {
|
|
2087
|
+
const blocks = threadId === void 0 ? this.blocks : this.blocks.filter((block) => block.threadId === threadId);
|
|
2088
|
+
return blocks.map((block) => {
|
|
2089
|
+
const threadBlocks = this.threadBlocks(block.threadId);
|
|
2090
|
+
const latestBlockPosition = threadBlocks.length;
|
|
2091
|
+
const blockPosition = threadBlocks.indexOf(block) + 1;
|
|
2092
|
+
const age = Math.max(0, latestBlockPosition - blockPosition);
|
|
2093
|
+
const level = getDecayedBlockLevel(
|
|
2094
|
+
block.pointerAnchorLevel,
|
|
2095
|
+
block.pointerAnchorBlockPosition,
|
|
2096
|
+
latestBlockPosition,
|
|
2097
|
+
this.blockDecayLambda
|
|
2098
|
+
);
|
|
1961
2099
|
block.pointerCurrentLevel = level;
|
|
1962
2100
|
return {
|
|
1963
2101
|
id: block.id,
|
|
2102
|
+
...block.threadId ? { threadId: block.threadId } : {},
|
|
1964
2103
|
turnRange: [block.startTurn, block.endTurn],
|
|
2104
|
+
age,
|
|
1965
2105
|
level,
|
|
1966
2106
|
label: blockLevelLabel(level),
|
|
1967
2107
|
content: renderBlock(block, level)
|
|
@@ -1972,15 +2112,24 @@ var StrataGate = class _StrataGate {
|
|
|
1972
2112
|
return this.commitMutation(() => {
|
|
1973
2113
|
const block = this.blocks.find((candidate) => candidate.id === id);
|
|
1974
2114
|
if (!block) throw new Error(`Unknown block: ${id}`);
|
|
1975
|
-
const
|
|
2115
|
+
const latestBlockPosition = this.threadBlocks(block.threadId).length;
|
|
2116
|
+
const blockPosition = this.threadBlocks(block.threadId).indexOf(block) + 1;
|
|
2117
|
+
const current = getDecayedBlockLevel(
|
|
2118
|
+
block.pointerAnchorLevel,
|
|
2119
|
+
block.pointerAnchorBlockPosition,
|
|
2120
|
+
latestBlockPosition,
|
|
2121
|
+
this.blockDecayLambda
|
|
2122
|
+
);
|
|
1976
2123
|
const level = normalizeBlockLevel(target, current);
|
|
1977
2124
|
block.pointerCurrentLevel = level;
|
|
1978
2125
|
block.pointerAnchorLevel = level;
|
|
1979
|
-
block.
|
|
2126
|
+
block.pointerAnchorBlockPosition = latestBlockPosition;
|
|
1980
2127
|
block.lastLiftedAt = toUtc8Iso(this.now());
|
|
1981
2128
|
return {
|
|
1982
2129
|
id: block.id,
|
|
2130
|
+
...block.threadId ? { threadId: block.threadId } : {},
|
|
1983
2131
|
turnRange: [block.startTurn, block.endTurn],
|
|
2132
|
+
age: Math.max(0, latestBlockPosition - blockPosition),
|
|
1984
2133
|
level,
|
|
1985
2134
|
label: blockLevelLabel(level),
|
|
1986
2135
|
content: renderBlock(block, level)
|
|
@@ -2129,36 +2278,69 @@ var StrataGate = class _StrataGate {
|
|
|
2129
2278
|
this.elementProjectionJobs.set(job.id, job);
|
|
2130
2279
|
return job;
|
|
2131
2280
|
}
|
|
2132
|
-
|
|
2281
|
+
threadOpenTail(threadId) {
|
|
2282
|
+
return this.openTail.filter((message) => message.threadId === threadId);
|
|
2283
|
+
}
|
|
2284
|
+
threadBlocks(threadId) {
|
|
2285
|
+
return this.blocks.filter((block) => block.threadId === threadId);
|
|
2286
|
+
}
|
|
2287
|
+
threadTurn(threadId) {
|
|
2288
|
+
const sealedTurns = this.threadBlocks(threadId).reduce((total, block) => total + block.l5Raw.filter((message) => message.role === "user").length, 0);
|
|
2289
|
+
return sealedTurns + this.threadOpenTail(threadId).filter((message) => message.role === "user").length;
|
|
2290
|
+
}
|
|
2291
|
+
nextSealableThread() {
|
|
2292
|
+
const counts = [];
|
|
2293
|
+
for (const message of this.openTail) {
|
|
2294
|
+
if (message.role !== "user") continue;
|
|
2295
|
+
let entry = counts.find((candidate) => candidate.threadId === message.threadId);
|
|
2296
|
+
if (!entry) {
|
|
2297
|
+
entry = { threadId: message.threadId, users: 0 };
|
|
2298
|
+
counts.push(entry);
|
|
2299
|
+
}
|
|
2300
|
+
entry.users += 1;
|
|
2301
|
+
if (entry.users >= this.blockTurnSize) return { threadId: entry.threadId };
|
|
2302
|
+
}
|
|
2303
|
+
return null;
|
|
2304
|
+
}
|
|
2305
|
+
nextBlockInThread(block) {
|
|
2306
|
+
const index = this.blocks.indexOf(block);
|
|
2307
|
+
return this.blocks.slice(index + 1).find((candidate) => candidate.threadId === block.threadId) ?? null;
|
|
2308
|
+
}
|
|
2309
|
+
pendingBlockMessages(threadId) {
|
|
2310
|
+
const messages = this.threadOpenTail(threadId);
|
|
2133
2311
|
let users = 0;
|
|
2134
|
-
let end =
|
|
2135
|
-
for (const [index, message] of
|
|
2312
|
+
let end = messages.length;
|
|
2313
|
+
for (const [index, message] of messages.entries()) {
|
|
2136
2314
|
if (message.role !== "user") continue;
|
|
2137
2315
|
users += 1;
|
|
2138
2316
|
if (users !== this.blockTurnSize) continue;
|
|
2139
|
-
const nextUserOffset =
|
|
2140
|
-
end = nextUserOffset === -1 ?
|
|
2317
|
+
const nextUserOffset = messages.slice(index + 1).findIndex((candidate) => candidate.role === "user");
|
|
2318
|
+
end = nextUserOffset === -1 ? messages.length : index + 1 + nextUserOffset;
|
|
2141
2319
|
break;
|
|
2142
2320
|
}
|
|
2143
|
-
return
|
|
2321
|
+
return messages.slice(0, end);
|
|
2144
2322
|
}
|
|
2145
|
-
async sealOpenTail() {
|
|
2146
|
-
const raw = this.pendingBlockMessages();
|
|
2323
|
+
async sealOpenTail(threadId) {
|
|
2324
|
+
const raw = this.pendingBlockMessages(threadId);
|
|
2147
2325
|
if (raw.filter((message) => message.role === "user").length < this.blockTurnSize) {
|
|
2148
2326
|
throw new Error("Open tail does not contain enough turns to seal a block");
|
|
2149
2327
|
}
|
|
2150
2328
|
const generated = this.summarizer ? await this.summarizer(raw) : defaultSummary(raw);
|
|
2151
2329
|
const deterministic = deterministicBlockLayers(raw);
|
|
2152
2330
|
const sequence = this.blocks.length + 1;
|
|
2153
|
-
const
|
|
2331
|
+
const threadBlocks = this.threadBlocks(threadId);
|
|
2332
|
+
const previous = threadBlocks.at(-1);
|
|
2333
|
+
const blockPosition = threadBlocks.length + 1;
|
|
2334
|
+
const startTurn = previous ? previous.endTurn + 1 : 1;
|
|
2154
2335
|
const endTurn = startTurn + this.blockTurnSize - 1;
|
|
2155
2336
|
return this.commitMutation(() => {
|
|
2156
|
-
const currentRaw = this.pendingBlockMessages();
|
|
2337
|
+
const currentRaw = this.pendingBlockMessages(threadId);
|
|
2157
2338
|
if (!sameIds(currentRaw.map((message) => message.id), raw.map((message) => message.id))) {
|
|
2158
2339
|
throw new Error("Open tail changed while the block summary was being prepared");
|
|
2159
2340
|
}
|
|
2160
2341
|
const block = {
|
|
2161
2342
|
id: this.idFactory("blk"),
|
|
2343
|
+
...threadId ? { threadId } : {},
|
|
2162
2344
|
sequence,
|
|
2163
2345
|
startTurn,
|
|
2164
2346
|
endTurn,
|
|
@@ -2171,26 +2353,29 @@ var StrataGate = class _StrataGate {
|
|
|
2171
2353
|
...deterministic,
|
|
2172
2354
|
pointerCurrentLevel: 5,
|
|
2173
2355
|
pointerAnchorLevel: 5,
|
|
2174
|
-
|
|
2356
|
+
pointerAnchorBlockPosition: blockPosition,
|
|
2175
2357
|
lastLiftedAt: null
|
|
2176
2358
|
};
|
|
2177
|
-
|
|
2359
|
+
const sealedIds = new Set(raw.map((message) => message.id));
|
|
2360
|
+
const remaining = this.openTail.filter((message) => !sealedIds.has(message.id));
|
|
2361
|
+
this.openTail.splice(0, this.openTail.length, ...remaining);
|
|
2178
2362
|
this.blocks.push(block);
|
|
2179
2363
|
return block;
|
|
2180
2364
|
});
|
|
2181
2365
|
}
|
|
2182
2366
|
async extractEligibleBlock(options = {}) {
|
|
2183
2367
|
if (!this.extractor || this.blocks.length < 2) return null;
|
|
2184
|
-
const
|
|
2185
|
-
if (
|
|
2368
|
+
const target = this.blocks.find((block) => {
|
|
2369
|
+
if (this.nextBlockInThread(block) === null || !block.shouldExtract) return false;
|
|
2186
2370
|
if (options.blockId !== void 0 && block.id !== options.blockId) return false;
|
|
2187
2371
|
const status = this.extractionJobs.get(block.id)?.status;
|
|
2188
2372
|
return status === void 0 || status === "failed" || options.includeSkipped === true && status === "skipped";
|
|
2189
2373
|
});
|
|
2190
|
-
if (
|
|
2191
|
-
const
|
|
2192
|
-
const
|
|
2193
|
-
|
|
2374
|
+
if (!target) return null;
|
|
2375
|
+
const threadBlocks = this.threadBlocks(target.threadId);
|
|
2376
|
+
const targetIndex = threadBlocks.indexOf(target);
|
|
2377
|
+
const next = threadBlocks[targetIndex + 1];
|
|
2378
|
+
if (!next) return null;
|
|
2194
2379
|
const existing = this.extractionJobs.get(target.id);
|
|
2195
2380
|
await this.commitMutation(() => {
|
|
2196
2381
|
const currentStatus = this.extractionJobs.get(target.id)?.status;
|
|
@@ -2209,7 +2394,7 @@ var StrataGate = class _StrataGate {
|
|
|
2209
2394
|
let result;
|
|
2210
2395
|
try {
|
|
2211
2396
|
result = await this.extractor({
|
|
2212
|
-
previous:
|
|
2397
|
+
previous: threadBlocks[targetIndex - 1] ?? null,
|
|
2213
2398
|
target,
|
|
2214
2399
|
next,
|
|
2215
2400
|
timeline: this.events.map((event) => ({ id: event.id, title: event.title, temporal: event.temporal }))
|
|
@@ -2305,6 +2490,7 @@ var StrataGate = class _StrataGate {
|
|
|
2305
2490
|
throw new Error(`Snapshot blockTurnSize ${normalized.blockTurnSize} does not match ${this.blockTurnSize}`);
|
|
2306
2491
|
}
|
|
2307
2492
|
const copy = cloneSnapshot(normalized);
|
|
2493
|
+
this.blockDecayLambdaValue = copy.blockDecayLambda;
|
|
2308
2494
|
this.currentTurn = copy.currentTurn;
|
|
2309
2495
|
this.openTail.splice(0, this.openTail.length, ...copy.openTail);
|
|
2310
2496
|
this.blocks.splice(0, this.blocks.length, ...copy.blocks);
|
|
@@ -2950,6 +3136,7 @@ var TurnFolder = class {
|
|
|
2950
3136
|
return {
|
|
2951
3137
|
user: pending.user.join("\n\n"),
|
|
2952
3138
|
assistant: pending.assistant.join("\n\n") || reasonLabel(event.data.reason),
|
|
3139
|
+
threadId: sessionId,
|
|
2953
3140
|
assistantToolCalls: [...pending.tools.values()],
|
|
2954
3141
|
createdAt: toUtc8Iso(event.time),
|
|
2955
3142
|
receiptId: `dsh:${sessionId}:turn:${event.data.turn}`
|
|
@@ -2974,6 +3161,60 @@ var TurnFolder = class {
|
|
|
2974
3161
|
}
|
|
2975
3162
|
};
|
|
2976
3163
|
|
|
3164
|
+
// src/metadata.ts
|
|
3165
|
+
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
3166
|
+
var METADATA_SCHEMA = `
|
|
3167
|
+
CREATE TABLE IF NOT EXISTS stratagate_dsh_settings (
|
|
3168
|
+
key TEXT PRIMARY KEY,
|
|
3169
|
+
value TEXT NOT NULL,
|
|
3170
|
+
updated_at TEXT NOT NULL
|
|
3171
|
+
) STRICT;
|
|
3172
|
+
|
|
3173
|
+
CREATE TABLE IF NOT EXISTS stratagate_dsh_workspaces (
|
|
3174
|
+
namespace TEXT PRIMARY KEY,
|
|
3175
|
+
display_name TEXT NOT NULL,
|
|
3176
|
+
updated_at TEXT NOT NULL
|
|
3177
|
+
) STRICT;
|
|
3178
|
+
`;
|
|
3179
|
+
var DshMetadataStore = class {
|
|
3180
|
+
database;
|
|
3181
|
+
constructor(filename) {
|
|
3182
|
+
this.database = new DatabaseSync2(filename);
|
|
3183
|
+
this.database.exec(METADATA_SCHEMA);
|
|
3184
|
+
}
|
|
3185
|
+
blockDecayLambda() {
|
|
3186
|
+
const row = this.database.prepare("SELECT value FROM stratagate_dsh_settings WHERE key = 'blockDecayLambda'").get();
|
|
3187
|
+
const value = Number(row?.value);
|
|
3188
|
+
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
3189
|
+
}
|
|
3190
|
+
setBlockDecayLambda(value) {
|
|
3191
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3192
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
3193
|
+
}
|
|
3194
|
+
this.database.prepare(`
|
|
3195
|
+
INSERT INTO stratagate_dsh_settings (key, value, updated_at)
|
|
3196
|
+
VALUES ('blockDecayLambda', ?, ?)
|
|
3197
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
|
|
3198
|
+
`).run(String(value), (/* @__PURE__ */ new Date()).toISOString());
|
|
3199
|
+
}
|
|
3200
|
+
workspaceName(namespace) {
|
|
3201
|
+
const row = this.database.prepare("SELECT display_name FROM stratagate_dsh_workspaces WHERE namespace = ?").get(namespace);
|
|
3202
|
+
return row?.display_name ?? null;
|
|
3203
|
+
}
|
|
3204
|
+
rememberWorkspace(namespace, displayName) {
|
|
3205
|
+
const name2 = displayName.trim();
|
|
3206
|
+
if (!namespace.trim() || !name2) return;
|
|
3207
|
+
this.database.prepare(`
|
|
3208
|
+
INSERT INTO stratagate_dsh_workspaces (namespace, display_name, updated_at)
|
|
3209
|
+
VALUES (?, ?, ?)
|
|
3210
|
+
ON CONFLICT (namespace) DO UPDATE SET display_name = excluded.display_name, updated_at = excluded.updated_at
|
|
3211
|
+
`).run(namespace, name2, (/* @__PURE__ */ new Date()).toISOString());
|
|
3212
|
+
}
|
|
3213
|
+
close() {
|
|
3214
|
+
this.database.close();
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
|
|
2977
3218
|
// src/runtime.ts
|
|
2978
3219
|
var AUTO_EVENT_LIMIT = 4;
|
|
2979
3220
|
var AUTO_ELEMENT_LIMIT = 4;
|
|
@@ -2982,12 +3223,17 @@ function projectKey(cwd) {
|
|
|
2982
3223
|
const canonical = resolve(cwd ?? process.cwd()).replaceAll("\\", "/").toLowerCase();
|
|
2983
3224
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
2984
3225
|
}
|
|
3226
|
+
function workspaceDisplayName(cwd) {
|
|
3227
|
+
const canonical = (cwd ?? process.cwd()).replace(/[\\/]+$/, "");
|
|
3228
|
+
return canonical.split(/[\\/]/).at(-1) || "\u5F53\u524D\u5DE5\u4F5C\u533A";
|
|
3229
|
+
}
|
|
2985
3230
|
var StrataGateRuntime = class {
|
|
2986
3231
|
constructor(config, models, onIngestError = () => {
|
|
2987
3232
|
}) {
|
|
2988
3233
|
this.config = config;
|
|
2989
3234
|
this.models = models;
|
|
2990
3235
|
this.onIngestError = onIngestError;
|
|
3236
|
+
this.blockDecayLambda = config.blockDecayLambda;
|
|
2991
3237
|
}
|
|
2992
3238
|
config;
|
|
2993
3239
|
models;
|
|
@@ -2997,10 +3243,13 @@ var StrataGateRuntime = class {
|
|
|
2997
3243
|
batches = /* @__PURE__ */ new Map();
|
|
2998
3244
|
adopted = /* @__PURE__ */ new Map();
|
|
2999
3245
|
pendingUse = /* @__PURE__ */ new Set();
|
|
3246
|
+
workspaceNames = /* @__PURE__ */ new Map();
|
|
3000
3247
|
ingestTail = Promise.resolve();
|
|
3248
|
+
settingsTail = Promise.resolve();
|
|
3001
3249
|
batchSequence = 0;
|
|
3002
3250
|
closed = false;
|
|
3003
3251
|
ingestError;
|
|
3252
|
+
blockDecayLambda;
|
|
3004
3253
|
acceptEvent(session, event) {
|
|
3005
3254
|
if (this.closed) return;
|
|
3006
3255
|
if (!this.config.ingestSubagents && session.header.origin === "subagent") return;
|
|
@@ -3045,7 +3294,7 @@ var StrataGateRuntime = class {
|
|
|
3045
3294
|
}
|
|
3046
3295
|
async blocks(session) {
|
|
3047
3296
|
await this.flush();
|
|
3048
|
-
const results = (await this.space(session)).getBlockContext();
|
|
3297
|
+
const results = (await this.space(session)).getBlockContext(String(session.id));
|
|
3049
3298
|
return this.batch(session, results.map((result) => ({
|
|
3050
3299
|
ref: `block:${result.id}:level:${result.level}`,
|
|
3051
3300
|
target: { eventIds: [], elementIds: [] }
|
|
@@ -3167,7 +3416,8 @@ var StrataGateRuntime = class {
|
|
|
3167
3416
|
async buildAutoContext(session) {
|
|
3168
3417
|
await this.flush();
|
|
3169
3418
|
const memory = await this.space(session);
|
|
3170
|
-
const
|
|
3419
|
+
const threadId = String(session.id);
|
|
3420
|
+
const openTail = memory.listOpenTail(threadId);
|
|
3171
3421
|
const activationQuery = [currentUserMessage(session), renderMessages(recentTurns(openTail, 2))].filter(Boolean).join("\n\n");
|
|
3172
3422
|
const [eventHits, elementHits] = activationQuery ? await Promise.all([
|
|
3173
3423
|
memory.searchEvents(activationQuery, { limit: 20 }),
|
|
@@ -3180,7 +3430,7 @@ var StrataGateRuntime = class {
|
|
|
3180
3430
|
openTail.length > 0 ? renderMessages(openTail) : "(open tail is empty)",
|
|
3181
3431
|
"",
|
|
3182
3432
|
"[Decayed memory blocks]",
|
|
3183
|
-
renderBlocks2(memory.getBlockContext()),
|
|
3433
|
+
renderBlocks2(memory.getBlockContext(threadId)),
|
|
3184
3434
|
"",
|
|
3185
3435
|
renderActivatedMemory(events, elements)
|
|
3186
3436
|
].join("\n");
|
|
@@ -3220,16 +3470,23 @@ var StrataGateRuntime = class {
|
|
|
3220
3470
|
await storage.close();
|
|
3221
3471
|
}
|
|
3222
3472
|
}
|
|
3223
|
-
async
|
|
3473
|
+
async syncConfiguredSettings() {
|
|
3224
3474
|
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return;
|
|
3475
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3476
|
+
try {
|
|
3477
|
+
this.blockDecayLambda = metadata.blockDecayLambda() ?? this.config.blockDecayLambda;
|
|
3478
|
+
} finally {
|
|
3479
|
+
metadata.close();
|
|
3480
|
+
}
|
|
3225
3481
|
const storage = new SqliteStorage({ filename: this.config.database });
|
|
3226
3482
|
try {
|
|
3227
3483
|
for (const namespace of storage.listNamespaces()) {
|
|
3228
3484
|
const loaded = await storage.load(namespace);
|
|
3229
|
-
if (!loaded || loaded.snapshot.blockTurnSize === this.config.blockTurnSize) continue;
|
|
3485
|
+
if (!loaded || loaded.snapshot.blockTurnSize === this.config.blockTurnSize && loaded.snapshot.blockDecayLambda === this.blockDecayLambda) continue;
|
|
3230
3486
|
await storage.save(namespace, {
|
|
3231
3487
|
...loaded.snapshot,
|
|
3232
|
-
blockTurnSize: this.config.blockTurnSize
|
|
3488
|
+
blockTurnSize: this.config.blockTurnSize,
|
|
3489
|
+
blockDecayLambda: this.blockDecayLambda
|
|
3233
3490
|
}, loaded.revision);
|
|
3234
3491
|
}
|
|
3235
3492
|
} finally {
|
|
@@ -3247,14 +3504,70 @@ var StrataGateRuntime = class {
|
|
|
3247
3504
|
await storage.close();
|
|
3248
3505
|
}
|
|
3249
3506
|
}
|
|
3507
|
+
adminWorkspaceName(namespace) {
|
|
3508
|
+
const remembered = this.workspaceNames.get(namespace);
|
|
3509
|
+
if (remembered) return remembered;
|
|
3510
|
+
if (this.config.database === ":memory:" || !existsSync(this.config.database)) return null;
|
|
3511
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3512
|
+
try {
|
|
3513
|
+
return metadata.workspaceName(namespace);
|
|
3514
|
+
} finally {
|
|
3515
|
+
metadata.close();
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
async adminSetBlockDecayLambda(value) {
|
|
3519
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3520
|
+
throw new TypeError("blockDecayLambda must be a non-negative finite number");
|
|
3521
|
+
}
|
|
3522
|
+
const update = this.settingsTail.catch(() => {
|
|
3523
|
+
}).then(() => this.applyBlockDecayLambda(value));
|
|
3524
|
+
this.settingsTail = update.then(() => {
|
|
3525
|
+
}, () => {
|
|
3526
|
+
});
|
|
3527
|
+
await update;
|
|
3528
|
+
return value;
|
|
3529
|
+
}
|
|
3530
|
+
async applyBlockDecayLambda(value) {
|
|
3531
|
+
await this.flush();
|
|
3532
|
+
this.blockDecayLambda = value;
|
|
3533
|
+
if (this.config.database !== ":memory:") {
|
|
3534
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3535
|
+
try {
|
|
3536
|
+
metadata.setBlockDecayLambda(value);
|
|
3537
|
+
} finally {
|
|
3538
|
+
metadata.close();
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
const openNamespaces = /* @__PURE__ */ new Set();
|
|
3542
|
+
for (const [namespace, opening] of this.spaces) {
|
|
3543
|
+
const memory = await opening;
|
|
3544
|
+
await memory.setBlockDecayLambda(value);
|
|
3545
|
+
openNamespaces.add(namespace);
|
|
3546
|
+
}
|
|
3547
|
+
if (this.config.database !== ":memory:" && existsSync(this.config.database)) {
|
|
3548
|
+
const storage = new SqliteStorage({ filename: this.config.database });
|
|
3549
|
+
try {
|
|
3550
|
+
for (const namespace of storage.listNamespaces()) {
|
|
3551
|
+
if (openNamespaces.has(namespace)) continue;
|
|
3552
|
+
const loaded = await storage.load(namespace);
|
|
3553
|
+
if (!loaded || loaded.snapshot.blockDecayLambda === value) continue;
|
|
3554
|
+
await storage.save(namespace, { ...loaded.snapshot, blockDecayLambda: value }, loaded.revision);
|
|
3555
|
+
}
|
|
3556
|
+
} finally {
|
|
3557
|
+
await storage.close();
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3250
3561
|
space(session) {
|
|
3251
3562
|
const namespace = this.namespaceFor(session);
|
|
3563
|
+
this.rememberWorkspace(namespace, session.header.cwd);
|
|
3252
3564
|
let opening = this.spaces.get(namespace);
|
|
3253
3565
|
if (!opening) {
|
|
3254
3566
|
opening = StrataGate.open({
|
|
3255
3567
|
database: this.config.database,
|
|
3256
3568
|
namespace,
|
|
3257
3569
|
blockTurnSize: this.config.blockTurnSize,
|
|
3570
|
+
blockDecayLambda: this.blockDecayLambda,
|
|
3258
3571
|
summarizer: this.models.summarizer,
|
|
3259
3572
|
extractor: this.models.extractor,
|
|
3260
3573
|
elementProjector: this.models.projector
|
|
@@ -3279,6 +3592,21 @@ var StrataGateRuntime = class {
|
|
|
3279
3592
|
}
|
|
3280
3593
|
return opening;
|
|
3281
3594
|
}
|
|
3595
|
+
rememberWorkspace(namespace, cwd) {
|
|
3596
|
+
const name2 = workspaceDisplayName(cwd);
|
|
3597
|
+
this.workspaceNames.set(namespace, name2);
|
|
3598
|
+
if (this.config.database === ":memory:") return;
|
|
3599
|
+
try {
|
|
3600
|
+
const metadata = new DshMetadataStore(this.config.database);
|
|
3601
|
+
try {
|
|
3602
|
+
metadata.rememberWorkspace(namespace, name2);
|
|
3603
|
+
} finally {
|
|
3604
|
+
metadata.close();
|
|
3605
|
+
}
|
|
3606
|
+
} catch (error) {
|
|
3607
|
+
this.onIngestError(error);
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3282
3610
|
async persistSuccessfulResponses(memory) {
|
|
3283
3611
|
if (typeof this.models.takeSuccessfulResponses !== "function") return;
|
|
3284
3612
|
const responses = this.models.takeSuccessfulResponses();
|
|
@@ -3335,7 +3663,7 @@ function renderMessages(messages) {
|
|
|
3335
3663
|
function renderBlocks2(blocks) {
|
|
3336
3664
|
if (blocks.length === 0) return "(no sealed blocks)";
|
|
3337
3665
|
return blocks.map((block) => [
|
|
3338
|
-
`block ${block.id} | turns ${block.turnRange[0]}-${block.turnRange[1]} | L${block.level}`,
|
|
3666
|
+
`block ${block.id} | turns ${block.turnRange[0]}-${block.turnRange[1]} | age ${block.age} | L${block.level}`,
|
|
3339
3667
|
block.content
|
|
3340
3668
|
].join("\n")).join("\n\n");
|
|
3341
3669
|
}
|
|
@@ -3709,15 +4037,18 @@ async function overview(runtime) {
|
|
|
3709
4037
|
].sort();
|
|
3710
4038
|
rows.push({
|
|
3711
4039
|
namespace,
|
|
4040
|
+
workspaceName: runtime.adminWorkspaceName(namespace) ?? "\u5F53\u524D\u5DE5\u4F5C\u533A",
|
|
3712
4041
|
schemaVersion: snapshot.schemaVersion,
|
|
3713
4042
|
currentTurn: snapshot.currentTurn,
|
|
3714
4043
|
blockTurnSize: snapshot.blockTurnSize,
|
|
4044
|
+
blockDecayLambda: snapshot.blockDecayLambda,
|
|
3715
4045
|
blocks: snapshot.blocks.length,
|
|
3716
4046
|
openTailMessages: snapshot.openTail.length,
|
|
3717
4047
|
events: snapshot.events.length,
|
|
3718
4048
|
activeEvents: snapshot.events.filter(({ status }) => status === "active").length,
|
|
3719
4049
|
elements: snapshot.elements.length,
|
|
3720
4050
|
usageReceipts: snapshot.usageReceipts.length,
|
|
4051
|
+
memoryUseCount: snapshot.usageReceipts.filter((receipt) => receipt.eventIds.length > 0 || receipt.elementIds.length > 0).length,
|
|
3721
4052
|
failedJobs,
|
|
3722
4053
|
processingJobs,
|
|
3723
4054
|
failedJobDetails,
|
|
@@ -3725,7 +4056,15 @@ async function overview(runtime) {
|
|
|
3725
4056
|
lastActivityAt: timestamps.at(-1) ?? null
|
|
3726
4057
|
});
|
|
3727
4058
|
}
|
|
3728
|
-
return { readonly: true, namespaces: rows };
|
|
4059
|
+
return { readonly: true, settingsWritable: true, namespaces: rows };
|
|
4060
|
+
}
|
|
4061
|
+
async function updateSettings(runtime, url) {
|
|
4062
|
+
const raw = url.searchParams.get("blockDecayLambda")?.trim() ?? "";
|
|
4063
|
+
const value = Number(raw);
|
|
4064
|
+
if (!raw || !Number.isFinite(value) || value < 0) {
|
|
4065
|
+
throw new AdminHttpError(400, "blockDecayLambda must be a non-negative finite number");
|
|
4066
|
+
}
|
|
4067
|
+
return { blockDecayLambda: await runtime.adminSetBlockDecayLambda(value) };
|
|
3729
4068
|
}
|
|
3730
4069
|
async function memories(runtime, url) {
|
|
3731
4070
|
const namespace = url.searchParams.get("namespace")?.trim() ?? "";
|
|
@@ -3850,10 +4189,13 @@ async function audit(runtime, url) {
|
|
|
3850
4189
|
}
|
|
3851
4190
|
async function handleAdminRequest(runtime, req, res) {
|
|
3852
4191
|
try {
|
|
3853
|
-
if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate Memory UI is read-only");
|
|
3854
4192
|
const url = new URL(req.url ?? "/", "http://localhost");
|
|
3855
4193
|
const path = url.pathname.replace(/\/$/, "");
|
|
3856
|
-
if (path === "/api/stratagate/
|
|
4194
|
+
if (path === "/api/stratagate/settings") {
|
|
4195
|
+
if (req.method !== "PATCH") throw new AdminHttpError(405, "StrataGate settings require PATCH");
|
|
4196
|
+
sendJson(res, 200, await updateSettings(runtime, url));
|
|
4197
|
+
} else if (req.method !== "GET") throw new AdminHttpError(405, "StrataGate memory data is read-only");
|
|
4198
|
+
else if (path === "/api/stratagate/overview") sendJson(res, 200, await overview(runtime));
|
|
3857
4199
|
else if (path === "/api/stratagate/memories") sendJson(res, 200, await memories(runtime, url));
|
|
3858
4200
|
else if (path === "/api/stratagate/sources") sendJson(res, 200, await sources(runtime, url));
|
|
3859
4201
|
else if (path === "/api/stratagate/audit") sendJson(res, 200, await audit(runtime, url));
|
|
@@ -3896,7 +4238,7 @@ async function apply(ctx, config) {
|
|
|
3896
4238
|
const runtime = new StrataGateRuntime(resolved, models, (error) => {
|
|
3897
4239
|
ctx.logger.error(`stratagate-memory ingestion failed: ${renderError(error)}`);
|
|
3898
4240
|
});
|
|
3899
|
-
await runtime.
|
|
4241
|
+
await runtime.syncConfiguredSettings();
|
|
3900
4242
|
ctx.systemPrompt.section({ name: "tool:stratagate-memory", order: 113, text: MEMORY_PROTOCOL });
|
|
3901
4243
|
ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
|
|
3902
4244
|
const assembled = await next();
|