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/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.05;
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(anchorTurn, currentTurn) {
84
- return Math.exp(-BLOCK_DECAY_LAMBDA * Math.max(0, currentTurn - anchorTurn));
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, anchorTurn, currentTurn) {
87
- const weight = getBlockWeight(anchorTurn, currentTurn);
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 = 4;
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(value),
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(value),
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
- pointer_anchor_turn INTEGER NOT NULL,
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
- pointerAnchorTurn: row.pointer_anchor_turn,
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, pointer_anchor_turn, last_lifted_at
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
- pointer_anchor_turn = excluded.pointer_anchor_turn,
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.pointerAnchorTurn,
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.exec(SCHEMA);
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 && options.blockTurnSize !== void 0) {
1567
- const requested = Math.max(1, Math.floor(options.blockTurnSize));
1568
- if (requested !== loadedSnapshot?.blockTurnSize) {
1569
- if (!loadedSnapshot) throw new Error("Loaded StrataGate state did not contain a snapshot");
1570
- loadedSnapshot.blockTurnSize = requested;
1571
- loadedRevision = await options.storage.save(namespace, loadedSnapshot, loadedRevision);
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.openTail.filter((message) => message.role === "user").length < this.blockTurnSize) {
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 (this.openTail.filter((message) => message.role === "user").length >= this.blockTurnSize) {
1728
- sealedBlocks.push(await this.sealOpenTail());
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, index) => index < this.blocks.length - 1 && block.shouldExtract && this.extractionJobs.get(block.id)?.status === "skipped").map((block) => block.id);
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
- return this.blocks.map((block) => {
1960
- const level = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, this.currentTurn);
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 current = getDecayedBlockLevel(block.pointerAnchorLevel, block.pointerAnchorTurn, this.currentTurn);
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.pointerAnchorTurn = this.currentTurn;
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
- pendingBlockMessages() {
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 = this.openTail.length;
2135
- for (const [index, message] of this.openTail.entries()) {
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 = this.openTail.slice(index + 1).findIndex((candidate) => candidate.role === "user");
2140
- end = nextUserOffset === -1 ? this.openTail.length : index + 1 + nextUserOffset;
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 this.openTail.slice(0, end);
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 startTurn = this.blocks.at(-1)?.endTurn !== void 0 ? (this.blocks.at(-1)?.endTurn ?? 0) + 1 : 1;
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
- pointerAnchorTurn: endTurn,
2356
+ pointerAnchorBlockPosition: blockPosition,
2175
2357
  lastLiftedAt: null
2176
2358
  };
2177
- this.openTail.splice(0, raw.length);
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 targetIndex = this.blocks.findIndex((block, index) => {
2185
- if (index >= this.blocks.length - 1 || !block.shouldExtract) return false;
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 (targetIndex < 0) return null;
2191
- const target = this.blocks[targetIndex];
2192
- const next = this.blocks[targetIndex + 1];
2193
- if (!target || !next) return null;
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: this.blocks[targetIndex - 1] ?? null,
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 openTail = memory.listOpenTail();
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 syncConfiguredBlockTurnSize() {
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/overview") sendJson(res, 200, await overview(runtime));
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.syncConfiguredBlockTurnSize();
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();