@agenr/openclaw-plugin 0.13.4 → 0.14.1

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
@@ -827,6 +827,9 @@ function formatShadowMismatch(stats) {
827
827
  }
828
828
  return details.join(", ");
829
829
  }
830
+ function formatIntegrityDetail(embeddingCount, shadowCount, missingRowids) {
831
+ return `active=${embeddingCount}, shadow=${shadowCount}, missing=${missingRowids}`;
832
+ }
830
833
  async function rollbackQuietly(db) {
831
834
  try {
832
835
  await db.execute("ROLLBACK");
@@ -919,6 +922,18 @@ async function getVectorIndexShadowStats(db) {
919
922
  missingRowids
920
923
  };
921
924
  }
925
+ async function checkVectorIntegrity(db) {
926
+ const stats = await getVectorIndexShadowStats(db);
927
+ const missingRowids = stats.missingRowids.length;
928
+ return {
929
+ drifted: stats.embeddingCount !== stats.shadowCount || missingRowids > 0,
930
+ detail: formatIntegrityDetail(stats.embeddingCount, stats.shadowCount, missingRowids),
931
+ activeEntries: stats.embeddingCount,
932
+ shadowRows: stats.shadowCount,
933
+ missingRowids,
934
+ missingRowidValues: stats.missingRowids
935
+ };
936
+ }
922
937
  async function rebuildVectorIndex(db, options) {
923
938
  const start = Date.now();
924
939
  const onLog = options?.onLog ?? (() => void 0);
@@ -1088,6 +1103,287 @@ var init_version = __esm({
1088
1103
  }
1089
1104
  });
1090
1105
 
1106
+ // src/shared/utils/entry-utils.ts
1107
+ function toNumber2(value) {
1108
+ if (typeof value === "number") {
1109
+ return value;
1110
+ }
1111
+ if (typeof value === "bigint") {
1112
+ return Number(value);
1113
+ }
1114
+ if (typeof value === "string" && value.trim()) {
1115
+ return Number(value);
1116
+ }
1117
+ return Number.NaN;
1118
+ }
1119
+ function toStringValue(value) {
1120
+ if (typeof value === "string") {
1121
+ return value;
1122
+ }
1123
+ if (typeof value === "number" || typeof value === "bigint") {
1124
+ return String(value);
1125
+ }
1126
+ return "";
1127
+ }
1128
+ function toRowsAffected(value) {
1129
+ if (typeof value === "number") {
1130
+ return value;
1131
+ }
1132
+ if (typeof value === "bigint") {
1133
+ return Number(value);
1134
+ }
1135
+ if (typeof value === "string" && value.trim().length > 0) {
1136
+ return Number(value);
1137
+ }
1138
+ return 0;
1139
+ }
1140
+ function parseDaysBetween(now, pastIso) {
1141
+ if (!pastIso) {
1142
+ return 0;
1143
+ }
1144
+ const parsed = new Date(pastIso);
1145
+ if (Number.isNaN(parsed.getTime())) {
1146
+ return 0;
1147
+ }
1148
+ const delta = (now.getTime() - parsed.getTime()) / MILLISECONDS_PER_DAY;
1149
+ if (!Number.isFinite(delta)) {
1150
+ return 0;
1151
+ }
1152
+ return Math.max(delta, 0);
1153
+ }
1154
+ var MILLISECONDS_PER_DAY;
1155
+ var init_entry_utils = __esm({
1156
+ "src/shared/utils/entry-utils.ts"() {
1157
+ "use strict";
1158
+ MILLISECONDS_PER_DAY = 1e3 * 60 * 60 * 24;
1159
+ }
1160
+ });
1161
+
1162
+ // src/shared/infrastructure/db/co-recall.ts
1163
+ function normalizePair(a, b) {
1164
+ return a < b ? [a, b] : [b, a];
1165
+ }
1166
+ async function strengthenEdges(db, usedEntryIds, timestamp2, edgeType) {
1167
+ const uniqueIds = Array.from(
1168
+ new Set(
1169
+ usedEntryIds.map((id) => id.trim()).filter((id) => id.length > 0)
1170
+ )
1171
+ ).slice(0, MAX_USED_ENTRIES);
1172
+ if (uniqueIds.length < 2) {
1173
+ return;
1174
+ }
1175
+ const now = timestamp2 || (/* @__PURE__ */ new Date()).toISOString();
1176
+ const pairs = [];
1177
+ for (let i = 0; i < uniqueIds.length; i += 1) {
1178
+ const a = uniqueIds[i];
1179
+ if (!a) {
1180
+ continue;
1181
+ }
1182
+ for (let j = i + 1; j < uniqueIds.length; j += 1) {
1183
+ const b = uniqueIds[j];
1184
+ if (!b || a === b) {
1185
+ continue;
1186
+ }
1187
+ pairs.push(normalizePair(a, b));
1188
+ }
1189
+ }
1190
+ if (pairs.length === 0) {
1191
+ return;
1192
+ }
1193
+ await db.execute("BEGIN");
1194
+ try {
1195
+ for (const [entryA, entryB] of pairs) {
1196
+ await db.execute({
1197
+ sql: `
1198
+ INSERT INTO co_recall_edges (
1199
+ entry_a, entry_b, edge_type, weight, session_count, last_co_recalled, created_at
1200
+ )
1201
+ VALUES (?, ?, ?, ?, 1, ?, ?)
1202
+ ON CONFLICT
1203
+ DO UPDATE SET
1204
+ weight = MIN(co_recall_edges.weight + excluded.weight, 1.0),
1205
+ session_count = co_recall_edges.session_count + 1,
1206
+ last_co_recalled = excluded.last_co_recalled
1207
+ `,
1208
+ args: [
1209
+ entryA,
1210
+ entryB,
1211
+ edgeType,
1212
+ DEFAULT_EDGE_INCREMENT,
1213
+ now,
1214
+ now
1215
+ ]
1216
+ });
1217
+ }
1218
+ await db.execute("COMMIT");
1219
+ } catch (error) {
1220
+ try {
1221
+ await db.execute("ROLLBACK");
1222
+ } catch {
1223
+ }
1224
+ throw error;
1225
+ }
1226
+ }
1227
+ async function strengthenCoRecallEdges(db, usedEntryIds, timestamp2) {
1228
+ await strengthenEdges(db, usedEntryIds, timestamp2, CO_RECALL_EDGE_TYPE);
1229
+ }
1230
+ async function getCoRecallNeighbors(db, entryId, minWeight = 0.1, limit = 10, edgeType = CO_RECALL_EDGE_TYPE) {
1231
+ const normalizedId = entryId.trim();
1232
+ if (!normalizedId) {
1233
+ return [];
1234
+ }
1235
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
1236
+ const safeMinWeight = Number.isFinite(minWeight) ? Math.max(minWeight, 0) : 0.1;
1237
+ const result = await db.execute({
1238
+ sql: `
1239
+ SELECT
1240
+ CASE WHEN entry_a = ? THEN entry_b ELSE entry_a END AS neighbor_id,
1241
+ weight,
1242
+ session_count,
1243
+ last_co_recalled
1244
+ FROM co_recall_edges
1245
+ WHERE edge_type = ?
1246
+ AND (entry_a = ? OR entry_b = ?)
1247
+ AND weight >= ?
1248
+ ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
1249
+ LIMIT ?
1250
+ `,
1251
+ args: [normalizedId, edgeType, normalizedId, normalizedId, safeMinWeight, safeLimit]
1252
+ });
1253
+ return result.rows.map((row) => ({
1254
+ entryId: toStringValue(row.neighbor_id),
1255
+ weight: toNumber2(row.weight),
1256
+ sessionCount: toNumber2(row.session_count),
1257
+ lastCoRecalled: toStringValue(row.last_co_recalled)
1258
+ }));
1259
+ }
1260
+ async function getCoRecallEdgeCounts(db, edgeType = CO_RECALL_EDGE_TYPE) {
1261
+ const result = await db.execute({
1262
+ sql: `
1263
+ SELECT entry_id, COUNT(*) AS edge_count
1264
+ FROM (
1265
+ SELECT entry_a AS entry_id
1266
+ FROM co_recall_edges
1267
+ WHERE edge_type = ?
1268
+ UNION ALL
1269
+ SELECT entry_b AS entry_id
1270
+ FROM co_recall_edges
1271
+ WHERE edge_type = ?
1272
+ )
1273
+ GROUP BY entry_id
1274
+ `,
1275
+ args: [edgeType, edgeType]
1276
+ });
1277
+ const counts = /* @__PURE__ */ new Map();
1278
+ for (const row of result.rows) {
1279
+ const entryId = toStringValue(row.entry_id).trim();
1280
+ if (!entryId) {
1281
+ continue;
1282
+ }
1283
+ counts.set(entryId, Math.max(0, toNumber2(row.edge_count)));
1284
+ }
1285
+ return counts;
1286
+ }
1287
+ async function getTopCoRecallEdges(db, limit = 20, edgeType = CO_RECALL_EDGE_TYPE) {
1288
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
1289
+ const result = await db.execute({
1290
+ sql: `
1291
+ SELECT entry_a, entry_b, weight, session_count, last_co_recalled
1292
+ FROM co_recall_edges
1293
+ WHERE edge_type = ?
1294
+ ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
1295
+ LIMIT ?
1296
+ `,
1297
+ args: [edgeType, safeLimit]
1298
+ });
1299
+ return result.rows.map((row) => ({
1300
+ entryA: toStringValue(row.entry_a),
1301
+ entryB: toStringValue(row.entry_b),
1302
+ weight: toNumber2(row.weight),
1303
+ sessionCount: toNumber2(row.session_count),
1304
+ lastCoRecalled: toStringValue(row.last_co_recalled)
1305
+ }));
1306
+ }
1307
+ var DEFAULT_EDGE_INCREMENT, MAX_USED_ENTRIES, CO_RECALL_EDGE_TYPE, CO_INGEST_EDGE_TYPE, ALL_EDGE_TYPES, ALL_EDGE_TYPES_PLACEHOLDERS;
1308
+ var init_co_recall = __esm({
1309
+ "src/shared/infrastructure/db/co-recall.ts"() {
1310
+ "use strict";
1311
+ init_entry_utils();
1312
+ DEFAULT_EDGE_INCREMENT = 0.1;
1313
+ MAX_USED_ENTRIES = 20;
1314
+ CO_RECALL_EDGE_TYPE = "co_recalled";
1315
+ CO_INGEST_EDGE_TYPE = "co_ingested";
1316
+ ALL_EDGE_TYPES = [CO_RECALL_EDGE_TYPE, CO_INGEST_EDGE_TYPE];
1317
+ ALL_EDGE_TYPES_PLACEHOLDERS = ALL_EDGE_TYPES.map(() => "?").join(", ");
1318
+ }
1319
+ });
1320
+
1321
+ // src/shared/infrastructure/db/meta.ts
1322
+ function normalizeIsoTimestamp(value) {
1323
+ if (value instanceof Date) {
1324
+ return value.toISOString();
1325
+ }
1326
+ const parsed = Date.parse(value);
1327
+ if (!Number.isFinite(parsed)) {
1328
+ throw new Error(`Invalid ISO timestamp: ${value}`);
1329
+ }
1330
+ return new Date(parsed).toISOString();
1331
+ }
1332
+ async function getMetaValue(db, key) {
1333
+ const result = await db.execute({
1334
+ sql: "SELECT value FROM _meta WHERE key = ? LIMIT 1",
1335
+ args: [key]
1336
+ });
1337
+ const raw = result.rows[0];
1338
+ const value = raw?.value ?? (raw ? Object.values(raw)[0] : void 0);
1339
+ if (typeof value !== "string") {
1340
+ return null;
1341
+ }
1342
+ const trimmed = value.trim();
1343
+ return trimmed.length > 0 ? trimmed : null;
1344
+ }
1345
+ async function setMetaValue(db, input) {
1346
+ const updatedAt = normalizeIsoTimestamp(input.updatedAt ?? /* @__PURE__ */ new Date());
1347
+ await db.execute({
1348
+ sql: `
1349
+ INSERT INTO _meta (key, value, updated_at)
1350
+ VALUES (?, ?, ?)
1351
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
1352
+ `,
1353
+ args: [input.key, input.value, updatedAt]
1354
+ });
1355
+ }
1356
+ async function getLastCorpusRebuildAt(db) {
1357
+ return getMetaValue(db, LAST_CORPUS_REBUILD_AT_META_KEY);
1358
+ }
1359
+ async function setLastCorpusRebuildAt(db, value) {
1360
+ const normalized = normalizeIsoTimestamp(value);
1361
+ await setMetaValue(db, {
1362
+ key: LAST_CORPUS_REBUILD_AT_META_KEY,
1363
+ value: normalized,
1364
+ updatedAt: normalized
1365
+ });
1366
+ }
1367
+ async function initializeLastCorpusRebuildAtForEmptyCorpus(db, at = /* @__PURE__ */ new Date()) {
1368
+ const existing = await getLastCorpusRebuildAt(db);
1369
+ if (existing) {
1370
+ return;
1371
+ }
1372
+ const result = await db.execute("SELECT COUNT(*) AS count FROM entries");
1373
+ const count = Number(result.rows[0]?.count ?? 0);
1374
+ if (count > 0) {
1375
+ return;
1376
+ }
1377
+ await setLastCorpusRebuildAt(db, at);
1378
+ }
1379
+ var LAST_CORPUS_REBUILD_AT_META_KEY;
1380
+ var init_meta = __esm({
1381
+ "src/shared/infrastructure/db/meta.ts"() {
1382
+ "use strict";
1383
+ LAST_CORPUS_REBUILD_AT_META_KEY = "last_corpus_rebuild_at";
1384
+ }
1385
+ });
1386
+
1091
1387
  // src/shared/infrastructure/db/migrations/reflection-importance-cap.ts
1092
1388
  async function applyReflectionImportanceCapMigration(db) {
1093
1389
  await db.execute("BEGIN IMMEDIATE");
@@ -1602,25 +1898,55 @@ async function backfillLegacyImportanceFromConfidence(client) {
1602
1898
  } catch {
1603
1899
  }
1604
1900
  }
1605
- async function stampSchemaMetadata(client) {
1901
+ async function retypeLegacyCoRecallEdges(client) {
1902
+ const edgeInfo = await client.execute("PRAGMA table_info(co_recall_edges)");
1903
+ const edgeColumns = readColumnNames(edgeInfo.rows);
1904
+ if (!edgeColumns.has("edge_type")) {
1905
+ return;
1906
+ }
1907
+ const sentinel = await client.execute({
1908
+ sql: "SELECT 1 AS found FROM _meta WHERE key = ? LIMIT 1",
1909
+ args: [CO_INGEST_EDGE_RETYPE_META_KEY]
1910
+ });
1911
+ if (sentinel.rows.length > 0) {
1912
+ return;
1913
+ }
1606
1914
  await client.execute({
1607
1915
  sql: `
1608
- INSERT INTO _meta (key, value, updated_at)
1609
- VALUES ('db_created_at', datetime('now'), datetime('now'))
1610
- ON CONFLICT(key) DO NOTHING
1916
+ UPDATE co_recall_edges
1917
+ SET edge_type = ?
1918
+ WHERE edge_type = ?
1611
1919
  `,
1612
- args: []
1920
+ args: [CO_INGEST_EDGE_TYPE, CO_RECALL_EDGE_TYPE]
1613
1921
  });
1614
1922
  await client.execute({
1615
1923
  sql: `
1616
1924
  INSERT INTO _meta (key, value, updated_at)
1617
- VALUES ('schema_version', ?, datetime('now'))
1618
- ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
1925
+ VALUES (?, datetime('now'), datetime('now'))
1926
+ ON CONFLICT(key) DO NOTHING
1619
1927
  `,
1620
- args: [APP_VERSION]
1928
+ args: [CO_INGEST_EDGE_RETYPE_META_KEY]
1621
1929
  });
1622
1930
  }
1623
- async function repairVectorIndexIfNeeded(client) {
1931
+ async function stampSchemaMetadata(client) {
1932
+ await client.execute({
1933
+ sql: `
1934
+ INSERT INTO _meta (key, value, updated_at)
1935
+ VALUES ('db_created_at', datetime('now'), datetime('now'))
1936
+ ON CONFLICT(key) DO NOTHING
1937
+ `,
1938
+ args: []
1939
+ });
1940
+ await client.execute({
1941
+ sql: `
1942
+ INSERT INTO _meta (key, value, updated_at)
1943
+ VALUES ('schema_version', ?, datetime('now'))
1944
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
1945
+ `,
1946
+ args: [APP_VERSION]
1947
+ });
1948
+ }
1949
+ async function repairVectorIndexIfNeeded(client) {
1624
1950
  try {
1625
1951
  const hasEntries = await client.execute(
1626
1952
  "SELECT 1 FROM entries WHERE embedding IS NOT NULL AND retired = 0 AND superseded_by IS NULL LIMIT 1"
@@ -1658,17 +1984,21 @@ async function initSchema(client) {
1658
1984
  await applyReflectionImportanceCapMigration(client);
1659
1985
  await applyReflectionRemovalMigration(client);
1660
1986
  await backfillLegacyImportanceFromConfidence(client);
1987
+ await retypeLegacyCoRecallEdges(client);
1661
1988
  for (const statement of CREATE_INDEX_STATEMENTS) {
1662
1989
  await client.execute(statement);
1663
1990
  }
1664
1991
  await repairVectorIndexIfNeeded(client);
1665
1992
  await stampSchemaMetadata(client);
1993
+ await initializeLastCorpusRebuildAtForEmptyCorpus(client);
1666
1994
  }
1667
- var LEGACY_IMPORTANCE_BACKFILL_META_KEY;
1995
+ var LEGACY_IMPORTANCE_BACKFILL_META_KEY, CO_INGEST_EDGE_RETYPE_META_KEY;
1668
1996
  var init_init = __esm({
1669
1997
  "src/shared/infrastructure/db/schema/init.ts"() {
1670
1998
  "use strict";
1671
1999
  init_version();
2000
+ init_co_recall();
2001
+ init_meta();
1672
2002
  init_reflection_importance_cap();
1673
2003
  init_reflection_removal();
1674
2004
  init_vector_index();
@@ -1677,6 +2007,7 @@ var init_init = __esm({
1677
2007
  init_fts();
1678
2008
  init_recall_events();
1679
2009
  LEGACY_IMPORTANCE_BACKFILL_META_KEY = "legacy_importance_backfill_from_confidence_v1";
2010
+ CO_INGEST_EDGE_RETYPE_META_KEY = "co_recall_edges_retyped_to_co_ingested_v1";
1680
2011
  }
1681
2012
  });
1682
2013
 
@@ -2039,6 +2370,52 @@ var init_client = __esm({
2039
2370
  }
2040
2371
  });
2041
2372
 
2373
+ // src/shared/domain/types.ts
2374
+ var KNOWLEDGE_TYPES, IMPORTANCE_MIN, IMPORTANCE_MAX, EXPIRY_LEVELS, SCOPE_LEVELS, KNOWLEDGE_PLATFORMS, REJECTED_CONFLICT_ENTRY_ID;
2375
+ var init_types = __esm({
2376
+ "src/shared/domain/types.ts"() {
2377
+ "use strict";
2378
+ KNOWLEDGE_TYPES = [
2379
+ "fact",
2380
+ "decision",
2381
+ "preference",
2382
+ "todo",
2383
+ "relationship",
2384
+ "event",
2385
+ "lesson",
2386
+ "reflection"
2387
+ ];
2388
+ IMPORTANCE_MIN = 1;
2389
+ IMPORTANCE_MAX = 10;
2390
+ EXPIRY_LEVELS = ["core", "permanent", "temporary"];
2391
+ SCOPE_LEVELS = ["private", "personal", "public"];
2392
+ KNOWLEDGE_PLATFORMS = ["openclaw", "claude-code", "codex", "plaud"];
2393
+ REJECTED_CONFLICT_ENTRY_ID = "rejected";
2394
+ }
2395
+ });
2396
+
2397
+ // src/shared/domain/subject-key.ts
2398
+ function isGenericSubjectKey(subjectKey2, entryType) {
2399
+ const normalizedSubjectKey = subjectKey2?.trim().toLowerCase();
2400
+ const normalizedEntryType = entryType.trim().toLowerCase();
2401
+ if (!normalizedSubjectKey || !normalizedEntryType || !ENTRY_TYPE_ATTRIBUTES.has(normalizedEntryType)) {
2402
+ return false;
2403
+ }
2404
+ const slashIndex = normalizedSubjectKey.lastIndexOf("/");
2405
+ if (slashIndex < 0 || slashIndex === normalizedSubjectKey.length - 1) {
2406
+ return false;
2407
+ }
2408
+ return normalizedSubjectKey.slice(slashIndex + 1) === normalizedEntryType;
2409
+ }
2410
+ var ENTRY_TYPE_ATTRIBUTES;
2411
+ var init_subject_key = __esm({
2412
+ "src/shared/domain/subject-key.ts"() {
2413
+ "use strict";
2414
+ init_types();
2415
+ ENTRY_TYPE_ATTRIBUTES = new Set(KNOWLEDGE_TYPES);
2416
+ }
2417
+ });
2418
+
2042
2419
  // src/modules/store/domain/structured-claim.ts
2043
2420
  function normalizeLower(value) {
2044
2421
  return value?.trim().toLowerCase() ?? "";
@@ -2256,6 +2633,34 @@ function buildSubjectKey(subjectEntity, subjectAttribute, fallback) {
2256
2633
  }
2257
2634
  return fallback;
2258
2635
  }
2636
+ function extractTopicSlug(text) {
2637
+ if (!text || text.trim().length === 0) {
2638
+ return null;
2639
+ }
2640
+ const tokens = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((token) => token.length > 1 && !TOPIC_SLUG_STOP_WORDS.has(token));
2641
+ const slug = tokens.slice(0, TOPIC_SLUG_MAX_TOKENS).join("_").slice(0, TOPIC_SLUG_MAX_LENGTH);
2642
+ return slug.length >= TOPIC_SLUG_MIN_LENGTH ? slug : null;
2643
+ }
2644
+ function deriveTopicAttribute(entry, fallback) {
2645
+ const normalizedFallback = normalizeLower(fallback);
2646
+ const normalizedType = normalizeLower(entry.type);
2647
+ const objectSlug = extractTopicSlug(entry.claimObject);
2648
+ if (objectSlug && objectSlug !== normalizedFallback && objectSlug !== normalizedType) {
2649
+ return objectSlug;
2650
+ }
2651
+ const subjectSlug = extractTopicSlug(entry.subject);
2652
+ if (subjectSlug && subjectSlug !== normalizedFallback && subjectSlug !== normalizedType) {
2653
+ return subjectSlug;
2654
+ }
2655
+ return fallback;
2656
+ }
2657
+ function resolveSubjectAttribute(entry, subjectEntity, subjectAttribute, fallbackSubjectKey) {
2658
+ const subjectKey2 = buildSubjectKey(subjectEntity, subjectAttribute, fallbackSubjectKey);
2659
+ if (!isGenericSubjectKey(subjectKey2, entry.type)) {
2660
+ return subjectAttribute;
2661
+ }
2662
+ return deriveTopicAttribute(entry, subjectAttribute);
2663
+ }
2259
2664
  function normalizeStructuredClaimFields(entry) {
2260
2665
  const normalizedSubjectEntity = normalizeLower(entry.subjectEntity);
2261
2666
  const normalizedSubjectKey = normalizeLower(entry.subjectKey);
@@ -2272,6 +2677,16 @@ function normalizeStructuredClaimFields(entry) {
2272
2677
  claimRole: entry.claimRole
2273
2678
  });
2274
2679
  const normalizedAttribute = detectedState.normalizedAttribute ?? subjectAttribute;
2680
+ const resolvedAttribute = resolveSubjectAttribute(
2681
+ {
2682
+ type: entry.type,
2683
+ subject: entry.subject,
2684
+ claimObject: entry.claimObject
2685
+ },
2686
+ subjectEntity,
2687
+ normalizedAttribute,
2688
+ normalizedSubjectKey
2689
+ );
2275
2690
  const transitionClaim = detectedState.role ? void 0 : detectTransitionClaim({
2276
2691
  content: entry.content,
2277
2692
  claimObject: entry.claimObject,
@@ -2280,11 +2695,11 @@ function normalizeStructuredClaimFields(entry) {
2280
2695
  if (subjectEntity) {
2281
2696
  entry.subjectEntity = subjectEntity;
2282
2697
  }
2283
- if (normalizedAttribute) {
2284
- entry.subjectAttribute = normalizedAttribute;
2698
+ if (resolvedAttribute) {
2699
+ entry.subjectAttribute = resolvedAttribute;
2285
2700
  }
2286
- if (subjectEntity || normalizedAttribute) {
2287
- entry.subjectKey = buildSubjectKey(subjectEntity, normalizedAttribute, normalizedSubjectKey);
2701
+ if (subjectEntity || resolvedAttribute) {
2702
+ entry.subjectKey = buildSubjectKey(subjectEntity, resolvedAttribute, normalizedSubjectKey);
2288
2703
  }
2289
2704
  if (transitionClaim) {
2290
2705
  entry.claimPredicate = STATE_TRANSITION_PREDICATE;
@@ -2330,10 +2745,11 @@ function buildStructuredClaimIdentity(entry) {
2330
2745
  cardinality: "exact_value_only"
2331
2746
  };
2332
2747
  }
2333
- var STRUCTURED_CLAIM_IDENTITY_TYPES, STRUCTURED_CLAIM_CONFIDENCE_FLOOR, STATE_ANCHOR_PREDICATE, STATE_TRANSITION_PREDICATE, DECISION_COMMITMENT_MARKERS, DECISION_NON_COMMITMENT_MARKERS, EXPLICIT_STATE_ROLE_SUFFIXES, CURRENT_STATE_SUBJECT_MARKERS, PRIOR_STATE_SUBJECT_MARKERS, CURRENT_STATE_CONTENT_MARKERS, PRIOR_STATE_CONTENT_MARKERS, STATE_NON_COMMITMENT_MARKERS, TRANSITION_PATTERNS, TRANSITION_MULTI_STEP_MARKERS;
2748
+ var STRUCTURED_CLAIM_IDENTITY_TYPES, STRUCTURED_CLAIM_CONFIDENCE_FLOOR, STATE_ANCHOR_PREDICATE, STATE_TRANSITION_PREDICATE, DECISION_COMMITMENT_MARKERS, DECISION_NON_COMMITMENT_MARKERS, EXPLICIT_STATE_ROLE_SUFFIXES, CURRENT_STATE_SUBJECT_MARKERS, PRIOR_STATE_SUBJECT_MARKERS, CURRENT_STATE_CONTENT_MARKERS, PRIOR_STATE_CONTENT_MARKERS, STATE_NON_COMMITMENT_MARKERS, TRANSITION_PATTERNS, TRANSITION_MULTI_STEP_MARKERS, TOPIC_SLUG_STOP_WORDS, TOPIC_SLUG_MAX_TOKENS, TOPIC_SLUG_MAX_LENGTH, TOPIC_SLUG_MIN_LENGTH;
2334
2749
  var init_structured_claim = __esm({
2335
2750
  "src/modules/store/domain/structured-claim.ts"() {
2336
2751
  "use strict";
2752
+ init_subject_key();
2337
2753
  STRUCTURED_CLAIM_IDENTITY_TYPES = /* @__PURE__ */ new Set(["fact", "preference", "decision"]);
2338
2754
  STRUCTURED_CLAIM_CONFIDENCE_FLOOR = 0.8;
2339
2755
  STATE_ANCHOR_PREDICATE = "state_is";
@@ -2427,6 +2843,48 @@ var init_structured_claim = __esm({
2427
2843
  /\bafter that\b/i,
2428
2844
  /\bthen\b.{0,24}\b(?:move|moved|switch|switched|replace|replaced|migrate|migrated|transition|transitioned)\b/i
2429
2845
  ];
2846
+ TOPIC_SLUG_STOP_WORDS = /* @__PURE__ */ new Set([
2847
+ "the",
2848
+ "a",
2849
+ "an",
2850
+ "for",
2851
+ "and",
2852
+ "or",
2853
+ "to",
2854
+ "in",
2855
+ "of",
2856
+ "on",
2857
+ "at",
2858
+ "by",
2859
+ "with",
2860
+ "from",
2861
+ "that",
2862
+ "this",
2863
+ "is",
2864
+ "was",
2865
+ "are",
2866
+ "were",
2867
+ "be",
2868
+ "been",
2869
+ "has",
2870
+ "have",
2871
+ "had",
2872
+ "it",
2873
+ "its",
2874
+ "create",
2875
+ "creating",
2876
+ "document",
2877
+ "documenting",
2878
+ "turn",
2879
+ "turning",
2880
+ "into",
2881
+ "concrete",
2882
+ "investigate",
2883
+ "investigating"
2884
+ ]);
2885
+ TOPIC_SLUG_MAX_TOKENS = 6;
2886
+ TOPIC_SLUG_MAX_LENGTH = 60;
2887
+ TOPIC_SLUG_MIN_LENGTH = 3;
2430
2888
  }
2431
2889
  });
2432
2890
 
@@ -2706,212 +3164,274 @@ var init_client2 = __esm({
2706
3164
  }
2707
3165
  });
2708
3166
 
2709
- // src/shared/utils/entry-utils.ts
2710
- function toNumber2(value) {
2711
- if (typeof value === "number") {
2712
- return value;
2713
- }
2714
- if (typeof value === "bigint") {
2715
- return Number(value);
2716
- }
2717
- if (typeof value === "string" && value.trim()) {
2718
- return Number(value);
2719
- }
2720
- return Number.NaN;
2721
- }
2722
- function toStringValue(value) {
2723
- if (typeof value === "string") {
2724
- return value;
2725
- }
2726
- if (typeof value === "number" || typeof value === "bigint") {
2727
- return String(value);
3167
+ // src/shared/infrastructure/config/schema.ts
3168
+ import os3 from "os";
3169
+ import path3 from "path";
3170
+ function resolveClaimExtractionBatchSize(value) {
3171
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3172
+ return DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE;
2728
3173
  }
2729
- return "";
3174
+ return Math.min(Math.floor(value), MAX_CLAIM_EXTRACTION_BATCH_SIZE);
2730
3175
  }
2731
- function toRowsAffected(value) {
2732
- if (typeof value === "number") {
2733
- return value;
2734
- }
2735
- if (typeof value === "bigint") {
2736
- return Number(value);
2737
- }
2738
- if (typeof value === "string" && value.trim().length > 0) {
2739
- return Number(value);
3176
+ function resolveClaimExtractionConcurrency(value) {
3177
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3178
+ return DEFAULT_CLAIM_EXTRACTION_CONCURRENCY;
2740
3179
  }
2741
- return 0;
3180
+ return Math.min(Math.floor(value), MAX_CLAIM_EXTRACTION_CONCURRENCY);
2742
3181
  }
2743
- function parseDaysBetween(now, pastIso) {
2744
- if (!pastIso) {
2745
- return 0;
2746
- }
2747
- const parsed = new Date(pastIso);
2748
- if (Number.isNaN(parsed.getTime())) {
2749
- return 0;
2750
- }
2751
- const delta = (now.getTime() - parsed.getTime()) / MILLISECONDS_PER_DAY;
2752
- if (!Number.isFinite(delta)) {
2753
- return 0;
2754
- }
2755
- return Math.max(delta, 0);
3182
+ function isModelTask(value) {
3183
+ return MODEL_TASK_KEYS.includes(value);
2756
3184
  }
2757
- var MILLISECONDS_PER_DAY;
2758
- var init_entry_utils = __esm({
2759
- "src/shared/utils/entry-utils.ts"() {
2760
- "use strict";
2761
- MILLISECONDS_PER_DAY = 1e3 * 60 * 60 * 24;
2762
- }
2763
- });
2764
-
2765
- // src/shared/infrastructure/db/co-recall.ts
2766
- function normalizePair(a, b) {
2767
- return a < b ? [a, b] : [b, a];
3185
+ function isScoringSetKey(value) {
3186
+ return SCORING_SET_KEYS.includes(value);
2768
3187
  }
2769
- async function strengthenCoRecallEdges(db, usedEntryIds, timestamp2) {
2770
- const uniqueIds = Array.from(
2771
- new Set(
2772
- usedEntryIds.map((id) => id.trim()).filter((id) => id.length > 0)
2773
- )
2774
- ).slice(0, MAX_USED_ENTRIES);
2775
- if (uniqueIds.length < 2) {
2776
- return;
2777
- }
2778
- const now = timestamp2 || (/* @__PURE__ */ new Date()).toISOString();
2779
- const pairs = [];
2780
- for (let i = 0; i < uniqueIds.length; i += 1) {
2781
- const a = uniqueIds[i];
2782
- if (!a) {
2783
- continue;
2784
- }
2785
- for (let j = i + 1; j < uniqueIds.length; j += 1) {
2786
- const b = uniqueIds[j];
2787
- if (!b || a === b) {
2788
- continue;
2789
- }
2790
- pairs.push(normalizePair(a, b));
2791
- }
2792
- }
2793
- if (pairs.length === 0) {
2794
- return;
2795
- }
2796
- await db.execute("BEGIN");
2797
- try {
2798
- for (const [entryA, entryB] of pairs) {
2799
- await db.execute({
2800
- sql: `
2801
- INSERT INTO co_recall_edges (
2802
- entry_a, entry_b, edge_type, weight, session_count, last_co_recalled, created_at
2803
- )
2804
- VALUES (?, ?, ?, ?, 1, ?, ?)
2805
- ON CONFLICT
2806
- DO UPDATE SET
2807
- weight = MIN(co_recall_edges.weight + excluded.weight, 1.0),
2808
- session_count = co_recall_edges.session_count + 1,
2809
- last_co_recalled = excluded.last_co_recalled
2810
- `,
2811
- args: [
2812
- entryA,
2813
- entryB,
2814
- CO_RECALL_EDGE_TYPE,
2815
- DEFAULT_EDGE_INCREMENT,
2816
- now,
2817
- now
2818
- ]
2819
- });
2820
- }
2821
- await db.execute("COMMIT");
2822
- } catch (error) {
2823
- try {
2824
- await db.execute("ROLLBACK");
2825
- } catch {
2826
- }
2827
- throw error;
2828
- }
3188
+ function isAgenrAuthMethod(value) {
3189
+ return AUTH_METHOD_SET.has(value);
2829
3190
  }
2830
- async function getCoRecallNeighbors(db, entryId, minWeight = 0.1, limit = 10) {
2831
- const normalizedId = entryId.trim();
2832
- if (!normalizedId) {
2833
- return [];
2834
- }
2835
- const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 10;
2836
- const safeMinWeight = Number.isFinite(minWeight) ? Math.max(minWeight, 0) : 0.1;
2837
- const result = await db.execute({
2838
- sql: `
2839
- SELECT
2840
- CASE WHEN entry_a = ? THEN entry_b ELSE entry_a END AS neighbor_id,
2841
- weight,
2842
- session_count,
2843
- last_co_recalled
2844
- FROM co_recall_edges
2845
- WHERE edge_type = ?
2846
- AND (entry_a = ? OR entry_b = ?)
2847
- AND weight >= ?
2848
- ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
2849
- LIMIT ?
2850
- `,
2851
- args: [normalizedId, CO_RECALL_EDGE_TYPE, normalizedId, normalizedId, safeMinWeight, safeLimit]
2852
- });
2853
- return result.rows.map((row) => ({
2854
- entryId: toStringValue(row.neighbor_id),
2855
- weight: toNumber2(row.weight),
2856
- sessionCount: toNumber2(row.session_count),
2857
- lastCoRecalled: toStringValue(row.last_co_recalled)
2858
- }));
3191
+ function resolveDefaultKnowledgeDbPath() {
3192
+ return path3.join(os3.homedir(), ".agenr", "knowledge.db");
2859
3193
  }
2860
- async function getCoRecallEdgeCounts(db) {
2861
- const result = await db.execute({
2862
- sql: `
2863
- SELECT entry_id, COUNT(*) AS edge_count
2864
- FROM (
2865
- SELECT entry_a AS entry_id
2866
- FROM co_recall_edges
2867
- WHERE edge_type = ?
2868
- UNION ALL
2869
- SELECT entry_b AS entry_id
2870
- FROM co_recall_edges
2871
- WHERE edge_type = ?
2872
- )
2873
- GROUP BY entry_id
2874
- `,
2875
- args: [CO_RECALL_EDGE_TYPE, CO_RECALL_EDGE_TYPE]
2876
- });
2877
- const counts = /* @__PURE__ */ new Map();
2878
- for (const row of result.rows) {
2879
- const entryId = toStringValue(row.entry_id).trim();
2880
- if (!entryId) {
2881
- continue;
2882
- }
2883
- counts.set(entryId, Math.max(0, toNumber2(row.edge_count)));
3194
+ var SCORING_SET_KEYS, AUTH_METHOD_DEFINITIONS, AUTH_METHOD_SET, MODEL_TASK_KEYS, DEFAULT_EMBEDDING_PROVIDER, DEFAULT_EMBEDDING_MODEL, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_FORGETTING_SCORE_THRESHOLD, DEFAULT_FORGETTING_MAX_AGE_DAYS, DEFAULT_SCORING_FTS_BOOST, DEFAULT_SCORING_VECTOR_EXPONENT, DEFAULT_SCORING_IMPORTANCE_FLOOR, DEFAULT_SCORING_FRESHNESS_HIGH_HOURS, DEFAULT_SCORING_FRESHNESS_MEDIUM_HOURS, DEFAULT_SCORING_FRESHNESS_LOW_HOURS, DEFAULT_SCORING_RECENCY_HALFLIFE_PERMANENT, DEFAULT_SCORING_RECENCY_HALFLIFE_TEMPORARY, DEFAULT_SCORING_FRESHNESS_MIN_IMPORTANCE, DEFAULT_SCORING_CONTRADICTION_PENALTY, DEFAULT_SCORING_QUALITY_MIN, DEFAULT_SCORING_QUALITY_MAX, DEFAULT_SCORING_IMPORTANCE_BLEND, DEFAULT_SCORING_RECALL_BLEND, DEFAULT_SCORING_TODO_HALFLIFE_DAYS, DEFAULT_SCORING_TODO_FLOOR_HIGH, DEFAULT_SCORING_TODO_FLOOR_LOW, DEFAULT_QUALITY_EVOLUTION_RECALL_SATURATION, DEFAULT_QUALITY_EVOLUTION_EDGE_SATURATION, DEFAULT_QUALITY_EVOLUTION_RECENCY_HALF_LIFE_DAYS, DEFAULT_QUALITY_EVOLUTION_NEUTRAL_FLOOR, DEFAULT_QUALITY_EVOLUTION_GRACE_MULTIPLIER, DEFAULT_QUALITY_EVOLUTION_DECAY_STEEPNESS, DEFAULT_QUALITY_EVOLUTION_EMA_ALPHA, DEFAULT_CONTRADICTION_ENABLED, DEFAULT_TASK_MODEL, DEFAULT_SURGEON_MODEL, DEFAULT_AUTO_SUPERSEDE_CONFIDENCE, DEFAULT_CONTRADICTION_BLOCKING_THRESHOLD, DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE, MAX_CLAIM_EXTRACTION_BATCH_SIZE, DEFAULT_CLAIM_EXTRACTION_CONCURRENCY, MAX_CLAIM_EXTRACTION_CONCURRENCY, DEFAULT_SURGEON_CONTEXT_LIMIT, DEFAULT_SURGEON_COST_CAP, DEFAULT_SURGEON_DAILY_COST_CAP, DEFAULT_SURGEON_RETIREMENT_PROTECT_RECALLED_DAYS, DEFAULT_SURGEON_RETIREMENT_PROTECT_MIN_IMPORTANCE, DEFAULT_SURGEON_RETIREMENT_MAX_ACTIONS_PER_RUN, DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS, DEFAULT_SURGEON_DEDUP_MAX_MERGES_PER_RUN, DEFAULT_SURGEON_CONTRADICTIONS_MAX_ACTIONS_PER_RUN;
3195
+ var init_schema2 = __esm({
3196
+ "src/shared/infrastructure/config/schema.ts"() {
3197
+ "use strict";
3198
+ SCORING_SET_KEYS = [
3199
+ "scoring.ftsBoost",
3200
+ "scoring.vectorExponent",
3201
+ "scoring.importanceFloor",
3202
+ "scoring.freshnessWindow.high",
3203
+ "scoring.freshnessWindow.medium",
3204
+ "scoring.freshnessWindow.low",
3205
+ "scoring.recencyHalfLife.permanent",
3206
+ "scoring.recencyHalfLife.temporary",
3207
+ "scoring.freshnessMinImportance",
3208
+ "scoring.contradictionPenalty",
3209
+ "scoring.qualityRange.min",
3210
+ "scoring.qualityRange.max",
3211
+ "scoring.importanceBlend.importance",
3212
+ "scoring.importanceBlend.recall",
3213
+ "scoring.todoPenalty.halfLifeDays",
3214
+ "scoring.todoPenalty.floorHigh",
3215
+ "scoring.todoPenalty.floorLow"
3216
+ ];
3217
+ AUTH_METHOD_DEFINITIONS = [
3218
+ {
3219
+ id: "anthropic-oauth",
3220
+ provider: "anthropic",
3221
+ title: "Anthropic - Claude subscription (OAuth)",
3222
+ setupDescription: "Uses your Claude Pro/Team subscription via Claude CLI credentials. No per-token cost. Requires Claude Code CLI.",
3223
+ preferredModels: ["claude-opus-4-6", "claude-sonnet-4-20250514", "claude-haiku-3-5-20241022"]
3224
+ },
3225
+ {
3226
+ id: "anthropic-token",
3227
+ provider: "anthropic",
3228
+ title: "Anthropic - Claude subscription (long-lived token)",
3229
+ setupDescription: "Uses a long-lived token from `claude setup-token`. No per-token cost. Simpler setup.",
3230
+ preferredModels: ["claude-opus-4-6", "claude-sonnet-4-20250514", "claude-haiku-3-5-20241022"]
3231
+ },
3232
+ {
3233
+ id: "anthropic-api-key",
3234
+ provider: "anthropic",
3235
+ title: "Anthropic - API key",
3236
+ setupDescription: "Standard API key from console.anthropic.com. Pay per token.",
3237
+ preferredModels: ["claude-sonnet-4-20250514", "claude-opus-4-6", "claude-haiku-3-5-20241022"]
3238
+ },
3239
+ {
3240
+ id: "openai-subscription",
3241
+ provider: "openai-codex",
3242
+ title: "OpenAI - Subscription (via Codex CLI)",
3243
+ setupDescription: "Uses your ChatGPT Plus subscription via Codex CLI credentials. No per-token cost. Requires Codex CLI.",
3244
+ preferredModels: ["gpt-5.3-codex", "o3-codex"]
3245
+ },
3246
+ {
3247
+ id: "openai-api-key",
3248
+ provider: "openai",
3249
+ title: "OpenAI - API key",
3250
+ setupDescription: "Standard API key from https://platform.openai.com/api-keys. Pay per token.",
3251
+ preferredModels: ["gpt-5.4-mini", "gpt-5.4", "gpt-4.1-mini"]
3252
+ }
3253
+ ];
3254
+ AUTH_METHOD_SET = new Set(AUTH_METHOD_DEFINITIONS.map((item) => item.id));
3255
+ MODEL_TASK_KEYS = [
3256
+ "extraction",
3257
+ "claimExtraction",
3258
+ "contradictionJudge",
3259
+ "handoffSummary"
3260
+ ];
3261
+ DEFAULT_EMBEDDING_PROVIDER = "openai";
3262
+ DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
3263
+ DEFAULT_EMBEDDING_DIMENSIONS = 1024;
3264
+ DEFAULT_FORGETTING_SCORE_THRESHOLD = 0.05;
3265
+ DEFAULT_FORGETTING_MAX_AGE_DAYS = 60;
3266
+ DEFAULT_SCORING_FTS_BOOST = 1.3;
3267
+ DEFAULT_SCORING_VECTOR_EXPONENT = 0.7;
3268
+ DEFAULT_SCORING_IMPORTANCE_FLOOR = 0.55;
3269
+ DEFAULT_SCORING_FRESHNESS_HIGH_HOURS = 1;
3270
+ DEFAULT_SCORING_FRESHNESS_MEDIUM_HOURS = 6;
3271
+ DEFAULT_SCORING_FRESHNESS_LOW_HOURS = 24;
3272
+ DEFAULT_SCORING_RECENCY_HALFLIFE_PERMANENT = 365;
3273
+ DEFAULT_SCORING_RECENCY_HALFLIFE_TEMPORARY = 30;
3274
+ DEFAULT_SCORING_FRESHNESS_MIN_IMPORTANCE = 6;
3275
+ DEFAULT_SCORING_CONTRADICTION_PENALTY = 0.8;
3276
+ DEFAULT_SCORING_QUALITY_MIN = 0.7;
3277
+ DEFAULT_SCORING_QUALITY_MAX = 1.3;
3278
+ DEFAULT_SCORING_IMPORTANCE_BLEND = 0.7;
3279
+ DEFAULT_SCORING_RECALL_BLEND = 0.3;
3280
+ DEFAULT_SCORING_TODO_HALFLIFE_DAYS = 7;
3281
+ DEFAULT_SCORING_TODO_FLOOR_HIGH = 0.4;
3282
+ DEFAULT_SCORING_TODO_FLOOR_LOW = 0.1;
3283
+ DEFAULT_QUALITY_EVOLUTION_RECALL_SATURATION = 10;
3284
+ DEFAULT_QUALITY_EVOLUTION_EDGE_SATURATION = 8;
3285
+ DEFAULT_QUALITY_EVOLUTION_RECENCY_HALF_LIFE_DAYS = 30;
3286
+ DEFAULT_QUALITY_EVOLUTION_NEUTRAL_FLOOR = 0.35;
3287
+ DEFAULT_QUALITY_EVOLUTION_GRACE_MULTIPLIER = 1;
3288
+ DEFAULT_QUALITY_EVOLUTION_DECAY_STEEPNESS = 0.1;
3289
+ DEFAULT_QUALITY_EVOLUTION_EMA_ALPHA = 0.3;
3290
+ DEFAULT_CONTRADICTION_ENABLED = true;
3291
+ DEFAULT_TASK_MODEL = "gpt-5.4-mini";
3292
+ DEFAULT_SURGEON_MODEL = "gpt-5.4";
3293
+ DEFAULT_AUTO_SUPERSEDE_CONFIDENCE = 0.85;
3294
+ DEFAULT_CONTRADICTION_BLOCKING_THRESHOLD = 0.85;
3295
+ DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE = 10;
3296
+ MAX_CLAIM_EXTRACTION_BATCH_SIZE = 25;
3297
+ DEFAULT_CLAIM_EXTRACTION_CONCURRENCY = 5;
3298
+ MAX_CLAIM_EXTRACTION_CONCURRENCY = 50;
3299
+ DEFAULT_SURGEON_CONTEXT_LIMIT = null;
3300
+ DEFAULT_SURGEON_COST_CAP = 5;
3301
+ DEFAULT_SURGEON_DAILY_COST_CAP = 15;
3302
+ DEFAULT_SURGEON_RETIREMENT_PROTECT_RECALLED_DAYS = 14;
3303
+ DEFAULT_SURGEON_RETIREMENT_PROTECT_MIN_IMPORTANCE = 8;
3304
+ DEFAULT_SURGEON_RETIREMENT_MAX_ACTIONS_PER_RUN = 100;
3305
+ DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS = 7;
3306
+ DEFAULT_SURGEON_DEDUP_MAX_MERGES_PER_RUN = 100;
3307
+ DEFAULT_SURGEON_CONTRADICTIONS_MAX_ACTIONS_PER_RUN = 100;
2884
3308
  }
2885
- return counts;
3309
+ });
3310
+
3311
+ // src/shared/utils/source-classification.ts
3312
+ function normalizeSourceToken(sourceFile) {
3313
+ return sourceFile?.trim().toLowerCase() ?? "";
2886
3314
  }
2887
- async function getTopCoRecallEdges(db, limit = 20) {
2888
- const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.floor(limit) : 20;
2889
- const result = await db.execute({
2890
- sql: `
2891
- SELECT entry_a, entry_b, weight, session_count, last_co_recalled
2892
- FROM co_recall_edges
2893
- WHERE edge_type = ?
2894
- ORDER BY weight DESC, session_count DESC, last_co_recalled DESC
2895
- LIMIT ?
2896
- `,
2897
- args: [CO_RECALL_EDGE_TYPE, safeLimit]
2898
- });
2899
- return result.rows.map((row) => ({
2900
- entryA: toStringValue(row.entry_a),
2901
- entryB: toStringValue(row.entry_b),
2902
- weight: toNumber2(row.weight),
2903
- sessionCount: toNumber2(row.session_count),
2904
- lastCoRecalled: toStringValue(row.last_co_recalled)
2905
- }));
3315
+ function normalizeSourceClass(value) {
3316
+ const normalized = value?.trim().toLowerCase() ?? "";
3317
+ if (!normalized) {
3318
+ return null;
3319
+ }
3320
+ if (normalized === "agent") {
3321
+ return "tool";
3322
+ }
3323
+ return SOURCE_CLASS_SET.has(normalized) ? normalized : null;
2906
3324
  }
2907
- var DEFAULT_EDGE_INCREMENT, MAX_USED_ENTRIES, CO_RECALL_EDGE_TYPE;
2908
- var init_co_recall = __esm({
2909
- "src/shared/infrastructure/db/co-recall.ts"() {
3325
+ function isToolSourceFile(sourceFile) {
3326
+ return normalizeSourceToken(sourceFile).startsWith(TOOL_SOURCE_PREFIX);
3327
+ }
3328
+ function classifyEntrySource(sourceFile) {
3329
+ const normalized = normalizeSourceToken(sourceFile);
3330
+ if (!normalized) {
3331
+ return "system";
3332
+ }
3333
+ if (isToolSourceFile(normalized)) {
3334
+ return "tool";
3335
+ }
3336
+ if (SYSTEM_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
3337
+ return "system";
3338
+ }
3339
+ if (IMPORT_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
3340
+ return "import";
3341
+ }
3342
+ if (CLI_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
3343
+ return "cli";
3344
+ }
3345
+ if (WATCHER_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix)) || normalized.includes("/") || normalized.includes("\\")) {
3346
+ return "watcher";
3347
+ }
3348
+ return "system";
3349
+ }
3350
+ function sourceClassTrustLevel(sourceClass) {
3351
+ return sourceClass === "tool" ? "high" : "low";
3352
+ }
3353
+ var TOOL_SOURCE_PREFIX, SOURCE_CLASSES, SOURCE_CLASS_SET, SYSTEM_SOURCE_PREFIXES, CLI_SOURCE_PREFIXES, WATCHER_SOURCE_PREFIXES, IMPORT_SOURCE_PREFIXES;
3354
+ var init_source_classification = __esm({
3355
+ "src/shared/utils/source-classification.ts"() {
2910
3356
  "use strict";
2911
- init_entry_utils();
2912
- DEFAULT_EDGE_INCREMENT = 0.1;
2913
- MAX_USED_ENTRIES = 20;
2914
- CO_RECALL_EDGE_TYPE = "co_recalled";
3357
+ TOOL_SOURCE_PREFIX = "stdin:tool";
3358
+ SOURCE_CLASSES = [
3359
+ "tool",
3360
+ "watcher",
3361
+ "cli",
3362
+ "import",
3363
+ "system"
3364
+ ];
3365
+ SOURCE_CLASS_SET = new Set(SOURCE_CLASSES);
3366
+ SYSTEM_SOURCE_PREFIXES = [
3367
+ "stdin:migration-",
3368
+ "stdin:system-",
3369
+ "migration:",
3370
+ "system:",
3371
+ "_meta:"
3372
+ ];
3373
+ CLI_SOURCE_PREFIXES = [
3374
+ "stdin:",
3375
+ "cli:"
3376
+ ];
3377
+ WATCHER_SOURCE_PREFIXES = [
3378
+ "file:",
3379
+ "session:",
3380
+ "conversation"
3381
+ ];
3382
+ IMPORT_SOURCE_PREFIXES = [
3383
+ "import:"
3384
+ ];
3385
+ }
3386
+ });
3387
+
3388
+ // src/shared/domain/retirement-thresholds.ts
3389
+ function parseIsoTime(value) {
3390
+ if (typeof value !== "string" || value.trim().length === 0) {
3391
+ return null;
3392
+ }
3393
+ const parsed = Date.parse(value);
3394
+ return Number.isFinite(parsed) ? parsed : null;
3395
+ }
3396
+ function computeCorpusExposureAgeDays(input) {
3397
+ const createdAtMs = parseIsoTime(input.createdAt);
3398
+ if (createdAtMs === null) {
3399
+ return null;
3400
+ }
3401
+ const rebuildAtMs = parseIsoTime(input.lastCorpusRebuildAt);
3402
+ const exposureStartMs = rebuildAtMs === null ? createdAtMs : Math.max(createdAtMs, rebuildAtMs);
3403
+ const ageDays = (input.now.getTime() - exposureStartMs) / DAY_MS;
3404
+ return Number.isFinite(ageDays) ? Math.max(0, ageDays) : null;
3405
+ }
3406
+ var DAY_MS, DEFAULT_TEMPORARY_ENTRY_TTL_ENABLED, DEFAULT_TEMPORARY_ENTRY_TTL_DAYS, DEFAULT_TEMPORARY_ENTRY_TTL_MAX_IMPORTANCE, DEFAULT_TEMPORARY_ENTRY_TTL_REQUIRE_ZERO_RECALL;
3407
+ var init_retirement_thresholds = __esm({
3408
+ "src/shared/domain/retirement-thresholds.ts"() {
3409
+ "use strict";
3410
+ init_source_classification();
3411
+ DAY_MS = 24 * 60 * 60 * 1e3;
3412
+ DEFAULT_TEMPORARY_ENTRY_TTL_ENABLED = true;
3413
+ DEFAULT_TEMPORARY_ENTRY_TTL_DAYS = 21;
3414
+ DEFAULT_TEMPORARY_ENTRY_TTL_MAX_IMPORTANCE = 6;
3415
+ DEFAULT_TEMPORARY_ENTRY_TTL_REQUIRE_ZERO_RECALL = true;
3416
+ }
3417
+ });
3418
+
3419
+ // src/shared/domain/quality-tiers.ts
3420
+ function qualityTier(score) {
3421
+ if (score >= QUALITY_TIER_HIGH) {
3422
+ return "high";
3423
+ }
3424
+ if (score >= QUALITY_TIER_MEDIUM) {
3425
+ return "medium";
3426
+ }
3427
+ return "low";
3428
+ }
3429
+ var QUALITY_TIER_HIGH, QUALITY_TIER_MEDIUM;
3430
+ var init_quality_tiers = __esm({
3431
+ "src/shared/domain/quality-tiers.ts"() {
3432
+ "use strict";
3433
+ QUALITY_TIER_HIGH = 0.7;
3434
+ QUALITY_TIER_MEDIUM = 0.4;
2915
3435
  }
2916
3436
  });
2917
3437
 
@@ -3134,18 +3654,39 @@ function qualityFloorByType(type) {
3134
3654
  return 0.35;
3135
3655
  }
3136
3656
  if (type === "decision" || type === "lesson") {
3657
+ return 0.3;
3658
+ }
3659
+ if (type === "reflection") {
3660
+ return 0.3;
3661
+ }
3662
+ if (type === "relationship") {
3137
3663
  return 0.25;
3138
3664
  }
3139
- if (type === "todo" || type === "event" || type === "relationship") {
3140
- return 0.15;
3665
+ if (type === "todo" || type === "event") {
3666
+ return 0.2;
3141
3667
  }
3142
- return 0.1;
3668
+ return 0.2;
3143
3669
  }
3144
- function computeDecayPenalty(daysSinceLastRecall) {
3145
- if (daysSinceLastRecall <= 60) {
3146
- return 1;
3670
+ function expectedRecallCycleDays(type) {
3671
+ switch (type) {
3672
+ case "preference":
3673
+ return 14;
3674
+ case "fact":
3675
+ return 21;
3676
+ case "decision":
3677
+ case "lesson":
3678
+ return 60;
3679
+ case "event":
3680
+ return 14;
3681
+ case "todo":
3682
+ return 7;
3683
+ case "relationship":
3684
+ return 30;
3685
+ case "reflection":
3686
+ return 90;
3687
+ default:
3688
+ return 30;
3147
3689
  }
3148
- return Math.max(0.3, 1 - (daysSinceLastRecall - 60) / 180);
3149
3690
  }
3150
3691
  function extractTextFromContent(content, separator) {
3151
3692
  if (typeof content === "string") {
@@ -3378,23 +3919,23 @@ async function evolveQualityScores(db, options = {}, config) {
3378
3919
  };
3379
3920
  }
3380
3921
  const qualityEvolutionConfig = config?.scoring?.qualityEvolution;
3381
- const recallSaturationRaw = qualityEvolutionConfig?.recallSaturation ?? 10;
3382
- const edgeSaturationRaw = qualityEvolutionConfig?.edgeSaturation ?? 8;
3383
- const recallSaturation = Number.isFinite(recallSaturationRaw) && recallSaturationRaw > 0 ? recallSaturationRaw : 10;
3384
- const edgeSaturation = Number.isFinite(edgeSaturationRaw) && edgeSaturationRaw > 0 ? edgeSaturationRaw : 8;
3385
- const recallWeight = Number.isFinite(qualityEvolutionConfig?.recallWeight) ? Number(qualityEvolutionConfig?.recallWeight) : 0.25;
3386
- const edgeWeight = Number.isFinite(qualityEvolutionConfig?.edgeWeight) ? Number(qualityEvolutionConfig?.edgeWeight) : 0.15;
3387
- const confirmWeight = Number.isFinite(qualityEvolutionConfig?.confirmWeight) ? Number(qualityEvolutionConfig?.confirmWeight) : 0.15;
3388
- const importanceWeight = Number.isFinite(qualityEvolutionConfig?.importanceWeight) ? Number(qualityEvolutionConfig?.importanceWeight) : 0.2;
3389
- const maxEvolveWeightRaw = qualityEvolutionConfig?.maxEvolveWeight ?? 0.6;
3390
- const minEvolveWeightRaw = qualityEvolutionConfig?.minEvolveWeight ?? 0.2;
3391
- const maxEvolveWeight = Number.isFinite(maxEvolveWeightRaw) ? clamp(maxEvolveWeightRaw, 0, 1) : 0.6;
3392
- const minEvolveWeightCandidate = Number.isFinite(minEvolveWeightRaw) ? minEvolveWeightRaw : 0.2;
3393
- const minEvolveWeight = clamp(minEvolveWeightCandidate, 0, maxEvolveWeight);
3394
- const neverRecalledDecayFloorRaw = qualityEvolutionConfig?.neverRecalledDecayFloor ?? 0.7;
3395
- const neverRecalledDecayFloor = Number.isFinite(neverRecalledDecayFloorRaw) ? clamp(neverRecalledDecayFloorRaw, 0, 1) : 0.7;
3396
- const edgeCounts = await getCoRecallEdgeCounts(db);
3922
+ const recallSaturationRaw = qualityEvolutionConfig?.recallSaturation ?? DEFAULT_QUALITY_EVOLUTION_RECALL_SATURATION;
3923
+ const edgeSaturationRaw = qualityEvolutionConfig?.edgeSaturation ?? DEFAULT_QUALITY_EVOLUTION_EDGE_SATURATION;
3924
+ const recallSaturation = Number.isFinite(recallSaturationRaw) && recallSaturationRaw > 0 ? recallSaturationRaw : DEFAULT_QUALITY_EVOLUTION_RECALL_SATURATION;
3925
+ const edgeSaturation = Number.isFinite(edgeSaturationRaw) && edgeSaturationRaw > 0 ? edgeSaturationRaw : DEFAULT_QUALITY_EVOLUTION_EDGE_SATURATION;
3926
+ const recencyHalfLifeDaysRaw = qualityEvolutionConfig?.recencyHalfLifeDays ?? DEFAULT_QUALITY_EVOLUTION_RECENCY_HALF_LIFE_DAYS;
3927
+ const recencyHalfLifeDays = Number.isFinite(recencyHalfLifeDaysRaw) && recencyHalfLifeDaysRaw > 0 ? recencyHalfLifeDaysRaw : DEFAULT_QUALITY_EVOLUTION_RECENCY_HALF_LIFE_DAYS;
3928
+ const neutralFloorRaw = qualityEvolutionConfig?.neutralFloor ?? DEFAULT_QUALITY_EVOLUTION_NEUTRAL_FLOOR;
3929
+ const neutralFloor = Number.isFinite(neutralFloorRaw) ? clamp(neutralFloorRaw, 0, 1) : DEFAULT_QUALITY_EVOLUTION_NEUTRAL_FLOOR;
3930
+ const graceMultiplierRaw = qualityEvolutionConfig?.graceMultiplier ?? DEFAULT_QUALITY_EVOLUTION_GRACE_MULTIPLIER;
3931
+ const graceMultiplier = Number.isFinite(graceMultiplierRaw) && graceMultiplierRaw > 0 ? graceMultiplierRaw : DEFAULT_QUALITY_EVOLUTION_GRACE_MULTIPLIER;
3932
+ const decaySteepnessRaw = qualityEvolutionConfig?.decaySteepness ?? DEFAULT_QUALITY_EVOLUTION_DECAY_STEEPNESS;
3933
+ const decaySteepness = Number.isFinite(decaySteepnessRaw) && decaySteepnessRaw >= 0 ? decaySteepnessRaw : DEFAULT_QUALITY_EVOLUTION_DECAY_STEEPNESS;
3934
+ const emaAlphaRaw = qualityEvolutionConfig?.emaAlpha ?? DEFAULT_QUALITY_EVOLUTION_EMA_ALPHA;
3935
+ const emaAlpha = Number.isFinite(emaAlphaRaw) ? clamp(emaAlphaRaw, 0, 1) : DEFAULT_QUALITY_EVOLUTION_EMA_ALPHA;
3936
+ const edgeCounts = await getCoRecallEdgeCounts(db, CO_RECALL_EDGE_TYPE);
3397
3937
  const now = options.now ?? /* @__PURE__ */ new Date();
3938
+ const lastCorpusRebuildAt = await getLastCorpusRebuildAt(db);
3398
3939
  let high = 0;
3399
3940
  let medium = 0;
3400
3941
  let low = 0;
@@ -3402,34 +3943,61 @@ async function evolveQualityScores(db, options = {}, config) {
3402
3943
  let totalAfter = 0;
3403
3944
  const updates = [];
3404
3945
  for (const row of rows) {
3405
- const recallSignal = row.recallCount > 0 ? clamp(Math.log1p(row.recallCount) / Math.log1p(recallSaturation), 0, 1) : 0;
3406
3946
  const edgeCount = edgeCounts.get(row.id) ?? 0;
3407
- const edgeSignal = edgeCount > 0 ? clamp(Math.log1p(edgeCount) / Math.log1p(edgeSaturation), 0, 1) : 0;
3408
- const confirmSignal = clamp(row.confirmations / 3, 0, 1);
3409
- const importanceNormalized = clamp(row.importance / 10, 0, 1);
3410
- const sourceDate = row.lastRecalledAt || row.createdAt;
3411
- const daysSinceLastRecall = parseDaysBetween(now, sourceDate || void 0);
3412
- const decayPenalty = row.recallCount > 0 ? computeDecayPenalty(daysSinceLastRecall) : Math.max(neverRecalledDecayFloor, computeDecayPenalty(daysSinceLastRecall));
3413
- const rawQuality = (recallWeight * recallSignal + edgeWeight * edgeSignal + confirmWeight * confirmSignal + importanceWeight * importanceNormalized) * decayPenalty;
3414
- const evolved = clamp(rawQuality, 0.05, 1);
3415
3947
  const existingQuality = clamp(Number.isFinite(row.qualityScore) ? row.qualityScore : 0.5, 0, 1);
3416
- const hasRecallData = row.recallCount > 0;
3417
- const hasEdgeData = edgeCount > 0;
3418
- const hasConfirmData = row.confirmations > 0;
3419
- const signalCount = (hasRecallData ? 1 : 0) + (hasEdgeData ? 1 : 0) + (hasConfirmData ? 1 : 0);
3420
- const evolveWeight = minEvolveWeight + signalCount / 3 * (maxEvolveWeight - minEvolveWeight);
3421
- const blendedQuality = evolveWeight * evolved + (1 - evolveWeight) * existingQuality;
3422
- const finalQuality = clamp(Math.max(blendedQuality, qualityFloorByType(row.type)), 0.05, 1);
3423
- totalBefore += existingQuality;
3424
- totalAfter += finalQuality;
3425
- if (finalQuality >= 0.7) {
3426
- high += 1;
3427
- } else if (finalQuality >= 0.3) {
3428
- medium += 1;
3948
+ const effectiveObservationDays = computeCorpusExposureAgeDays({
3949
+ createdAt: row.createdAt,
3950
+ now,
3951
+ lastCorpusRebuildAt
3952
+ }) ?? 0;
3953
+ const expectedCycleDays = expectedRecallCycleDays(row.type);
3954
+ const graceWindowDays = expectedCycleDays * graceMultiplier;
3955
+ const inGracePeriod = effectiveObservationDays < graceWindowDays;
3956
+ const hasAnyPositiveSignal = row.recallCount > 0 || row.confirmations > 0 || edgeCount > 0;
3957
+ let finalQuality = existingQuality;
3958
+ if (inGracePeriod && !hasAnyPositiveSignal) {
3959
+ finalQuality = existingQuality;
3960
+ } else if (!hasAnyPositiveSignal) {
3961
+ const importanceDampener = 1 + clamp(row.importance / 10, 0, 1) * 2;
3962
+ const excessDays = Math.max(0, effectiveObservationDays - graceWindowDays);
3963
+ const baselineQuality = Math.max(existingQuality, neutralFloor);
3964
+ const decayRate = excessDays / (expectedCycleDays * importanceDampener);
3965
+ const decayFactor = Math.exp(-decayRate * decaySteepness);
3966
+ finalQuality = neutralFloor + (baselineQuality - neutralFloor) * decayFactor;
3429
3967
  } else {
3430
- low += 1;
3431
- }
3432
- updates.push({ id: row.id, qualityScore: finalQuality });
3968
+ const recallSignal = row.recallCount > 0 ? clamp(Math.log1p(row.recallCount) / Math.log1p(recallSaturation), 0, 1) : 0;
3969
+ const daysSinceLastRecall = row.lastRecalledAt ? parseDaysBetween(now, row.lastRecalledAt) : Number.POSITIVE_INFINITY;
3970
+ const recencySignal = Number.isFinite(daysSinceLastRecall) ? Math.exp(-daysSinceLastRecall / recencyHalfLifeDays) : 0;
3971
+ const confirmSignal = clamp(row.confirmations / 3, 0, 1);
3972
+ const edgeSignal = edgeCount > 0 ? clamp(Math.log1p(edgeCount) / Math.log1p(edgeSaturation), 0, 1) : 0;
3973
+ const rawEvidence = clamp(
3974
+ EVIDENCE_WEIGHTS.recall * recallSignal + EVIDENCE_WEIGHTS.recency * recencySignal + EVIDENCE_WEIGHTS.confirm * confirmSignal + EVIDENCE_WEIGHTS.edge * edgeSignal,
3975
+ 0,
3976
+ 1
3977
+ );
3978
+ const evidenceQuality = clamp(
3979
+ EVIDENCE_QUALITY_BASELINE + rawEvidence * EVIDENCE_QUALITY_RANGE,
3980
+ EVIDENCE_QUALITY_BASELINE,
3981
+ 0.95
3982
+ );
3983
+ const blendedEvidence = emaAlpha * evidenceQuality + (1 - emaAlpha) * existingQuality;
3984
+ finalQuality = Math.max(existingQuality, blendedEvidence);
3985
+ }
3986
+ finalQuality = clamp(Math.max(finalQuality, qualityFloorByType(row.type)), 0.1, 0.98);
3987
+ totalBefore += existingQuality;
3988
+ totalAfter += finalQuality;
3989
+ switch (qualityTier(finalQuality)) {
3990
+ case "high":
3991
+ high += 1;
3992
+ break;
3993
+ case "medium":
3994
+ medium += 1;
3995
+ break;
3996
+ default:
3997
+ low += 1;
3998
+ break;
3999
+ }
4000
+ updates.push({ id: row.id, qualityScore: finalQuality });
3433
4001
  }
3434
4002
  if (updates.length > 0) {
3435
4003
  await db.execute("BEGIN");
@@ -3565,12 +4133,16 @@ async function computeRecallFeedback(db, sessionKey, messages, recalledEntryIds,
3565
4133
  updatedIds: updates.map((update) => update.id)
3566
4134
  };
3567
4135
  }
3568
- var RESPONSE_MIN_CHARS, RESPONSE_MAX_CHARS;
4136
+ var RESPONSE_MIN_CHARS, RESPONSE_MAX_CHARS, EVIDENCE_QUALITY_BASELINE, EVIDENCE_QUALITY_RANGE, EVIDENCE_WEIGHTS;
3569
4137
  var init_feedback = __esm({
3570
4138
  "src/shared/infrastructure/db/feedback.ts"() {
3571
4139
  "use strict";
3572
4140
  init_client2();
4141
+ init_schema2();
4142
+ init_retirement_thresholds();
4143
+ init_quality_tiers();
3573
4144
  init_co_recall();
4145
+ init_meta();
3574
4146
  init_metrics();
3575
4147
  init_entry_utils();
3576
4148
  init_errors();
@@ -3578,6 +4150,14 @@ var init_feedback = __esm({
3578
4150
  init_vector();
3579
4151
  RESPONSE_MIN_CHARS = 50;
3580
4152
  RESPONSE_MAX_CHARS = 8e3;
4153
+ EVIDENCE_QUALITY_BASELINE = 0.45;
4154
+ EVIDENCE_QUALITY_RANGE = 0.5;
4155
+ EVIDENCE_WEIGHTS = {
4156
+ recall: 0.35,
4157
+ recency: 0.25,
4158
+ confirm: 0.2,
4159
+ edge: 0.2
4160
+ };
3581
4161
  }
3582
4162
  });
3583
4163
 
@@ -3867,144 +4447,6 @@ var init_stream = __esm({
3867
4447
  }
3868
4448
  });
3869
4449
 
3870
- // src/shared/infrastructure/config/schema.ts
3871
- import os3 from "os";
3872
- import path3 from "path";
3873
- function resolveClaimExtractionBatchSize(value) {
3874
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3875
- return DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE;
3876
- }
3877
- return Math.min(Math.floor(value), MAX_CLAIM_EXTRACTION_BATCH_SIZE);
3878
- }
3879
- function resolveClaimExtractionConcurrency(value) {
3880
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
3881
- return DEFAULT_CLAIM_EXTRACTION_CONCURRENCY;
3882
- }
3883
- return Math.min(Math.floor(value), MAX_CLAIM_EXTRACTION_CONCURRENCY);
3884
- }
3885
- function isModelTask(value) {
3886
- return MODEL_TASK_KEYS.includes(value);
3887
- }
3888
- function isScoringSetKey(value) {
3889
- return SCORING_SET_KEYS.includes(value);
3890
- }
3891
- function isAgenrAuthMethod(value) {
3892
- return AUTH_METHOD_SET.has(value);
3893
- }
3894
- function resolveDefaultKnowledgeDbPath() {
3895
- return path3.join(os3.homedir(), ".agenr", "knowledge.db");
3896
- }
3897
- var SCORING_SET_KEYS, AUTH_METHOD_DEFINITIONS, AUTH_METHOD_SET, MODEL_TASK_KEYS, DEFAULT_EMBEDDING_PROVIDER, DEFAULT_EMBEDDING_MODEL, DEFAULT_EMBEDDING_DIMENSIONS, DEFAULT_FORGETTING_SCORE_THRESHOLD, DEFAULT_FORGETTING_MAX_AGE_DAYS, DEFAULT_FORGETTING_LOW_SCORE_TRIGGER_COUNT, DEFAULT_SCORING_FTS_BOOST, DEFAULT_SCORING_VECTOR_EXPONENT, DEFAULT_SCORING_IMPORTANCE_FLOOR, DEFAULT_SCORING_FRESHNESS_HIGH_HOURS, DEFAULT_SCORING_FRESHNESS_MEDIUM_HOURS, DEFAULT_SCORING_FRESHNESS_LOW_HOURS, DEFAULT_SCORING_RECENCY_HALFLIFE_PERMANENT, DEFAULT_SCORING_RECENCY_HALFLIFE_TEMPORARY, DEFAULT_SCORING_FRESHNESS_MIN_IMPORTANCE, DEFAULT_SCORING_CONTRADICTION_PENALTY, DEFAULT_SCORING_QUALITY_MIN, DEFAULT_SCORING_QUALITY_MAX, DEFAULT_SCORING_IMPORTANCE_BLEND, DEFAULT_SCORING_RECALL_BLEND, DEFAULT_SCORING_TODO_HALFLIFE_DAYS, DEFAULT_SCORING_TODO_FLOOR_HIGH, DEFAULT_SCORING_TODO_FLOOR_LOW, DEFAULT_CONTRADICTION_ENABLED, DEFAULT_TASK_MODEL, DEFAULT_SURGEON_MODEL, DEFAULT_AUTO_SUPERSEDE_CONFIDENCE, DEFAULT_CONTRADICTION_BLOCKING_THRESHOLD, DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE, MAX_CLAIM_EXTRACTION_BATCH_SIZE, DEFAULT_CLAIM_EXTRACTION_CONCURRENCY, MAX_CLAIM_EXTRACTION_CONCURRENCY, DEFAULT_SURGEON_BUDGET, DEFAULT_SURGEON_COST_CAP, DEFAULT_SURGEON_DAILY_COST_CAP, DEFAULT_SURGEON_RETIREMENT_PROTECT_RECALLED_DAYS, DEFAULT_SURGEON_RETIREMENT_PROTECT_MIN_IMPORTANCE, DEFAULT_SURGEON_RETIREMENT_MAX_ACTIONS_PER_RUN, DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS, DEFAULT_SURGEON_DEDUP_MAX_MERGES_PER_RUN, DEFAULT_SURGEON_CONTRADICTIONS_MAX_ACTIONS_PER_RUN;
3898
- var init_schema2 = __esm({
3899
- "src/shared/infrastructure/config/schema.ts"() {
3900
- "use strict";
3901
- SCORING_SET_KEYS = [
3902
- "scoring.ftsBoost",
3903
- "scoring.vectorExponent",
3904
- "scoring.importanceFloor",
3905
- "scoring.freshnessWindow.high",
3906
- "scoring.freshnessWindow.medium",
3907
- "scoring.freshnessWindow.low",
3908
- "scoring.recencyHalfLife.permanent",
3909
- "scoring.recencyHalfLife.temporary",
3910
- "scoring.freshnessMinImportance",
3911
- "scoring.contradictionPenalty",
3912
- "scoring.qualityRange.min",
3913
- "scoring.qualityRange.max",
3914
- "scoring.importanceBlend.importance",
3915
- "scoring.importanceBlend.recall",
3916
- "scoring.todoPenalty.halfLifeDays",
3917
- "scoring.todoPenalty.floorHigh",
3918
- "scoring.todoPenalty.floorLow"
3919
- ];
3920
- AUTH_METHOD_DEFINITIONS = [
3921
- {
3922
- id: "anthropic-oauth",
3923
- provider: "anthropic",
3924
- title: "Anthropic - Claude subscription (OAuth)",
3925
- setupDescription: "Uses your Claude Pro/Team subscription via Claude CLI credentials. No per-token cost. Requires Claude Code CLI.",
3926
- preferredModels: ["claude-opus-4-6", "claude-sonnet-4-20250514", "claude-haiku-3-5-20241022"]
3927
- },
3928
- {
3929
- id: "anthropic-token",
3930
- provider: "anthropic",
3931
- title: "Anthropic - Claude subscription (long-lived token)",
3932
- setupDescription: "Uses a long-lived token from `claude setup-token`. No per-token cost. Simpler setup.",
3933
- preferredModels: ["claude-opus-4-6", "claude-sonnet-4-20250514", "claude-haiku-3-5-20241022"]
3934
- },
3935
- {
3936
- id: "anthropic-api-key",
3937
- provider: "anthropic",
3938
- title: "Anthropic - API key",
3939
- setupDescription: "Standard API key from console.anthropic.com. Pay per token.",
3940
- preferredModels: ["claude-sonnet-4-20250514", "claude-opus-4-6", "claude-haiku-3-5-20241022"]
3941
- },
3942
- {
3943
- id: "openai-subscription",
3944
- provider: "openai-codex",
3945
- title: "OpenAI - Subscription (via Codex CLI)",
3946
- setupDescription: "Uses your ChatGPT Plus subscription via Codex CLI credentials. No per-token cost. Requires Codex CLI.",
3947
- preferredModels: ["gpt-5.3-codex", "o3-codex"]
3948
- },
3949
- {
3950
- id: "openai-api-key",
3951
- provider: "openai",
3952
- title: "OpenAI - API key",
3953
- setupDescription: "Standard API key from https://platform.openai.com/api-keys. Pay per token.",
3954
- preferredModels: ["gpt-5.4-mini", "gpt-5.4", "gpt-4.1-mini"]
3955
- }
3956
- ];
3957
- AUTH_METHOD_SET = new Set(AUTH_METHOD_DEFINITIONS.map((item) => item.id));
3958
- MODEL_TASK_KEYS = [
3959
- "extraction",
3960
- "claimExtraction",
3961
- "contradictionJudge",
3962
- "handoffSummary"
3963
- ];
3964
- DEFAULT_EMBEDDING_PROVIDER = "openai";
3965
- DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
3966
- DEFAULT_EMBEDDING_DIMENSIONS = 1024;
3967
- DEFAULT_FORGETTING_SCORE_THRESHOLD = 0.05;
3968
- DEFAULT_FORGETTING_MAX_AGE_DAYS = 60;
3969
- DEFAULT_FORGETTING_LOW_SCORE_TRIGGER_COUNT = 200;
3970
- DEFAULT_SCORING_FTS_BOOST = 1.3;
3971
- DEFAULT_SCORING_VECTOR_EXPONENT = 0.7;
3972
- DEFAULT_SCORING_IMPORTANCE_FLOOR = 0.55;
3973
- DEFAULT_SCORING_FRESHNESS_HIGH_HOURS = 1;
3974
- DEFAULT_SCORING_FRESHNESS_MEDIUM_HOURS = 6;
3975
- DEFAULT_SCORING_FRESHNESS_LOW_HOURS = 24;
3976
- DEFAULT_SCORING_RECENCY_HALFLIFE_PERMANENT = 365;
3977
- DEFAULT_SCORING_RECENCY_HALFLIFE_TEMPORARY = 30;
3978
- DEFAULT_SCORING_FRESHNESS_MIN_IMPORTANCE = 6;
3979
- DEFAULT_SCORING_CONTRADICTION_PENALTY = 0.8;
3980
- DEFAULT_SCORING_QUALITY_MIN = 0.7;
3981
- DEFAULT_SCORING_QUALITY_MAX = 1.3;
3982
- DEFAULT_SCORING_IMPORTANCE_BLEND = 0.7;
3983
- DEFAULT_SCORING_RECALL_BLEND = 0.3;
3984
- DEFAULT_SCORING_TODO_HALFLIFE_DAYS = 7;
3985
- DEFAULT_SCORING_TODO_FLOOR_HIGH = 0.4;
3986
- DEFAULT_SCORING_TODO_FLOOR_LOW = 0.1;
3987
- DEFAULT_CONTRADICTION_ENABLED = true;
3988
- DEFAULT_TASK_MODEL = "gpt-5.4-mini";
3989
- DEFAULT_SURGEON_MODEL = "gpt-5.4";
3990
- DEFAULT_AUTO_SUPERSEDE_CONFIDENCE = 0.85;
3991
- DEFAULT_CONTRADICTION_BLOCKING_THRESHOLD = 0.85;
3992
- DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE = 10;
3993
- MAX_CLAIM_EXTRACTION_BATCH_SIZE = 25;
3994
- DEFAULT_CLAIM_EXTRACTION_CONCURRENCY = 5;
3995
- MAX_CLAIM_EXTRACTION_CONCURRENCY = 50;
3996
- DEFAULT_SURGEON_BUDGET = 2e5;
3997
- DEFAULT_SURGEON_COST_CAP = 5;
3998
- DEFAULT_SURGEON_DAILY_COST_CAP = 15;
3999
- DEFAULT_SURGEON_RETIREMENT_PROTECT_RECALLED_DAYS = 14;
4000
- DEFAULT_SURGEON_RETIREMENT_PROTECT_MIN_IMPORTANCE = 8;
4001
- DEFAULT_SURGEON_RETIREMENT_MAX_ACTIONS_PER_RUN = 100;
4002
- DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS = 7;
4003
- DEFAULT_SURGEON_DEDUP_MAX_MERGES_PER_RUN = 100;
4004
- DEFAULT_SURGEON_CONTRADICTIONS_MAX_ACTIONS_PER_RUN = 100;
4005
- }
4006
- });
4007
-
4008
4450
  // src/shared/infrastructure/llm/models.ts
4009
4451
  import { getModel } from "@mariozechner/pi-ai";
4010
4452
  function isAgenrProvider(value) {
@@ -4489,8 +4931,7 @@ function normalizeForgettingConfig(input) {
4489
4931
  protect: [],
4490
4932
  scoreThreshold: DEFAULT_FORGETTING_SCORE_THRESHOLD,
4491
4933
  maxAgeDays: DEFAULT_FORGETTING_MAX_AGE_DAYS,
4492
- enabled: true,
4493
- lowScoreTriggerCount: DEFAULT_FORGETTING_LOW_SCORE_TRIGGER_COUNT
4934
+ enabled: true
4494
4935
  };
4495
4936
  if (!input || typeof input !== "object") {
4496
4937
  return normalized;
@@ -4508,8 +4949,33 @@ function normalizeForgettingConfig(input) {
4508
4949
  if (typeof record.enabled === "boolean") {
4509
4950
  normalized.enabled = record.enabled;
4510
4951
  }
4511
- if (typeof record.lowScoreTriggerCount === "number" && Number.isFinite(record.lowScoreTriggerCount) && record.lowScoreTriggerCount > 0) {
4512
- normalized.lowScoreTriggerCount = Math.floor(record.lowScoreTriggerCount);
4952
+ if (typeof record.qualityPenaltyWeight === "number" && Number.isFinite(record.qualityPenaltyWeight) && record.qualityPenaltyWeight >= 0) {
4953
+ normalized.qualityPenaltyWeight = record.qualityPenaltyWeight;
4954
+ }
4955
+ return normalized;
4956
+ }
4957
+ function normalizeTtlConfig(input) {
4958
+ const normalized = {
4959
+ enabled: DEFAULT_TEMPORARY_ENTRY_TTL_ENABLED,
4960
+ temporaryMaxDays: DEFAULT_TEMPORARY_ENTRY_TTL_DAYS,
4961
+ temporaryMaxImportance: DEFAULT_TEMPORARY_ENTRY_TTL_MAX_IMPORTANCE,
4962
+ requireZeroRecall: DEFAULT_TEMPORARY_ENTRY_TTL_REQUIRE_ZERO_RECALL
4963
+ };
4964
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
4965
+ return normalized;
4966
+ }
4967
+ const record = input;
4968
+ if (typeof record.enabled === "boolean") {
4969
+ normalized.enabled = record.enabled;
4970
+ }
4971
+ if (typeof record.temporaryMaxDays === "number" && Number.isFinite(record.temporaryMaxDays) && record.temporaryMaxDays > 0) {
4972
+ normalized.temporaryMaxDays = Math.floor(record.temporaryMaxDays);
4973
+ }
4974
+ if (typeof record.temporaryMaxImportance === "number" && Number.isFinite(record.temporaryMaxImportance) && record.temporaryMaxImportance >= 1 && record.temporaryMaxImportance <= 10) {
4975
+ normalized.temporaryMaxImportance = Math.floor(record.temporaryMaxImportance);
4976
+ }
4977
+ if (typeof record.requireZeroRecall === "boolean") {
4978
+ normalized.requireZeroRecall = record.requireZeroRecall;
4513
4979
  }
4514
4980
  return normalized;
4515
4981
  }
@@ -4650,7 +5116,7 @@ function normalizeContradictionConfig(input) {
4650
5116
  }
4651
5117
  function normalizeSurgeonConfig(input) {
4652
5118
  const normalized = {
4653
- budget: DEFAULT_SURGEON_BUDGET,
5119
+ contextLimit: DEFAULT_SURGEON_CONTEXT_LIMIT,
4654
5120
  costCap: DEFAULT_SURGEON_COST_CAP,
4655
5121
  dailyCostCap: DEFAULT_SURGEON_DAILY_COST_CAP,
4656
5122
  passes: {
@@ -4676,8 +5142,12 @@ function normalizeSurgeonConfig(input) {
4676
5142
  if (typeof record.model === "string" && record.model.trim()) {
4677
5143
  normalized.model = record.model.trim();
4678
5144
  }
4679
- if (typeof record.budget === "number" && Number.isFinite(record.budget) && record.budget > 0) {
4680
- normalized.budget = Math.floor(record.budget);
5145
+ if (typeof record.contextLimit === "number" && Number.isFinite(record.contextLimit) && record.contextLimit > 0) {
5146
+ normalized.contextLimit = Math.floor(record.contextLimit);
5147
+ } else if (record.contextLimit === null) {
5148
+ normalized.contextLimit = null;
5149
+ } else if (typeof record.budget === "number" && Number.isFinite(record.budget) && record.budget > 0) {
5150
+ normalized.contextLimit = Math.floor(record.budget);
4681
5151
  }
4682
5152
  if (typeof record.costCap === "number" && Number.isFinite(record.costCap) && record.costCap > 0) {
4683
5153
  normalized.costCap = record.costCap;
@@ -4845,6 +5315,7 @@ function normalizeConfig(input) {
4845
5315
  embedding: normalizeEmbeddingConfig(record.embedding),
4846
5316
  db: normalizeDbConfig(record.db),
4847
5317
  forgetting: normalizeForgettingConfig(record.forgetting),
5318
+ ttl: normalizeTtlConfig(record.ttl),
4848
5319
  scoring: normalizeScoringConfig(record.scoring),
4849
5320
  contradiction: normalizeContradictionConfig(record.contradiction),
4850
5321
  surgeon: normalizeSurgeonConfig(record.surgeon)
@@ -4890,6 +5361,7 @@ function normalizeConfig(input) {
4890
5361
  var init_normalize = __esm({
4891
5362
  "src/shared/infrastructure/config/normalize.ts"() {
4892
5363
  "use strict";
5364
+ init_retirement_thresholds();
4893
5365
  init_fs();
4894
5366
  init_string();
4895
5367
  init_models();
@@ -4932,6 +5404,12 @@ function mergeConfigPatch(current, patch) {
4932
5404
  ...patch.forgetting ?? {}
4933
5405
  };
4934
5406
  }
5407
+ if (current?.ttl || patch.ttl) {
5408
+ merged.ttl = {
5409
+ ...current?.ttl ?? {},
5410
+ ...patch.ttl ?? {}
5411
+ };
5412
+ }
4935
5413
  if (current?.scoring || patch.scoring) {
4936
5414
  merged.scoring = {
4937
5415
  ...current?.scoring ?? {},
@@ -5407,6 +5885,7 @@ __export(config_exports, {
5407
5885
  AUTH_METHOD_DEFINITIONS: () => AUTH_METHOD_DEFINITIONS,
5408
5886
  DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE: () => DEFAULT_CLAIM_EXTRACTION_BATCH_SIZE,
5409
5887
  DEFAULT_CLAIM_EXTRACTION_CONCURRENCY: () => DEFAULT_CLAIM_EXTRACTION_CONCURRENCY,
5888
+ DEFAULT_SURGEON_CONTEXT_LIMIT: () => DEFAULT_SURGEON_CONTEXT_LIMIT,
5410
5889
  DEFAULT_SURGEON_MODEL: () => DEFAULT_SURGEON_MODEL,
5411
5890
  DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS: () => DEFAULT_SURGEON_SKIP_RECENTLY_EVALUATED_DAYS,
5412
5891
  DEFAULT_TASK_MODEL: () => DEFAULT_TASK_MODEL,
@@ -5888,83 +6367,6 @@ var init_openclaw = __esm({
5888
6367
  }
5889
6368
  });
5890
6369
 
5891
- // src/shared/utils/source-classification.ts
5892
- function normalizeSourceToken(sourceFile) {
5893
- return sourceFile?.trim().toLowerCase() ?? "";
5894
- }
5895
- function normalizeSourceClass(value) {
5896
- const normalized = value?.trim().toLowerCase() ?? "";
5897
- if (!normalized) {
5898
- return null;
5899
- }
5900
- if (normalized === "agent") {
5901
- return "tool";
5902
- }
5903
- return SOURCE_CLASS_SET.has(normalized) ? normalized : null;
5904
- }
5905
- function isToolSourceFile(sourceFile) {
5906
- return normalizeSourceToken(sourceFile).startsWith(TOOL_SOURCE_PREFIX);
5907
- }
5908
- function classifyEntrySource(sourceFile) {
5909
- const normalized = normalizeSourceToken(sourceFile);
5910
- if (!normalized) {
5911
- return "system";
5912
- }
5913
- if (isToolSourceFile(normalized)) {
5914
- return "tool";
5915
- }
5916
- if (SYSTEM_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
5917
- return "system";
5918
- }
5919
- if (IMPORT_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
5920
- return "import";
5921
- }
5922
- if (CLI_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
5923
- return "cli";
5924
- }
5925
- if (WATCHER_SOURCE_PREFIXES.some((prefix) => normalized.startsWith(prefix)) || normalized.includes("/") || normalized.includes("\\")) {
5926
- return "watcher";
5927
- }
5928
- return "system";
5929
- }
5930
- function sourceClassTrustLevel(sourceClass) {
5931
- return sourceClass === "tool" ? "high" : "low";
5932
- }
5933
- var TOOL_SOURCE_PREFIX, SOURCE_CLASSES, SOURCE_CLASS_SET, SYSTEM_SOURCE_PREFIXES, CLI_SOURCE_PREFIXES, WATCHER_SOURCE_PREFIXES, IMPORT_SOURCE_PREFIXES;
5934
- var init_source_classification = __esm({
5935
- "src/shared/utils/source-classification.ts"() {
5936
- "use strict";
5937
- TOOL_SOURCE_PREFIX = "stdin:tool";
5938
- SOURCE_CLASSES = [
5939
- "tool",
5940
- "watcher",
5941
- "cli",
5942
- "import",
5943
- "system"
5944
- ];
5945
- SOURCE_CLASS_SET = new Set(SOURCE_CLASSES);
5946
- SYSTEM_SOURCE_PREFIXES = [
5947
- "stdin:migration-",
5948
- "stdin:system-",
5949
- "migration:",
5950
- "system:",
5951
- "_meta:"
5952
- ];
5953
- CLI_SOURCE_PREFIXES = [
5954
- "stdin:",
5955
- "cli:"
5956
- ];
5957
- WATCHER_SOURCE_PREFIXES = [
5958
- "file:",
5959
- "session:",
5960
- "conversation"
5961
- ];
5962
- IMPORT_SOURCE_PREFIXES = [
5963
- "import:"
5964
- ];
5965
- }
5966
- });
5967
-
5968
6370
  // src/shared/infrastructure/db/stored-entry.ts
5969
6371
  function mapRawStoredEntry(row, options) {
5970
6372
  const canonicalKey = toStringValue(row.canonical_key).trim();
@@ -6207,9 +6609,9 @@ async function updateRecallMetadata(db, ids, now, options) {
6207
6609
  const previous = recallMetadataQueue.get(db) ?? Promise.resolve();
6208
6610
  const current = previous.catch(() => void 0).then(async () => {
6209
6611
  const nowIso = now.toISOString();
6210
- const sessionId = options?.sessionId ?? null;
6211
- const platform = options?.platform ?? null;
6212
- const project = options?.project ?? null;
6612
+ const sessionId = options?.sessionId?.trim() || null;
6613
+ const platform = options?.platform?.trim() || null;
6614
+ const project = options?.project?.trim() || null;
6213
6615
  const runUpdates = async () => {
6214
6616
  for (const id of ids) {
6215
6617
  const entryRow = await db.execute({
@@ -6963,7 +7365,7 @@ var init_entry_utils2 = __esm({
6963
7365
 
6964
7366
  // src/modules/recall/domain/types.ts
6965
7367
  var DEFAULT_VECTOR_CANDIDATE_LIMIT, DEFAULT_LEXICAL_CANDIDATE_LIMIT, DEFAULT_LIMIT, DEFAULT_AROUND_RADIUS_DAYS, DEFAULT_RERANK_WINDOW;
6966
- var init_types = __esm({
7368
+ var init_types2 = __esm({
6967
7369
  "src/modules/recall/domain/types.ts"() {
6968
7370
  "use strict";
6969
7371
  DEFAULT_VECTOR_CANDIDATE_LIMIT = 50;
@@ -7132,7 +7534,7 @@ var init_score = __esm({
7132
7534
  init_project2();
7133
7535
  init_source_classification2();
7134
7536
  init_entry_utils2();
7135
- init_types();
7537
+ init_types2();
7136
7538
  IMPORTANCE_FLOOR = 0.55;
7137
7539
  RECENCY_HALFLIFE_PERMANENT_DAYS = 365;
7138
7540
  RECENCY_HALFLIFE_TEMPORARY_DAYS = 30;
@@ -7968,7 +8370,7 @@ var init_preparation = __esm({
7968
8370
  init_query_normalization();
7969
8371
  init_scope();
7970
8372
  init_score();
7971
- init_types();
8373
+ init_types2();
7972
8374
  }
7973
8375
  });
7974
8376
 
@@ -8355,7 +8757,7 @@ var init_rerank = __esm({
8355
8757
  init_lexical();
8356
8758
  init_rerank_intent();
8357
8759
  init_text();
8358
- init_types();
8760
+ init_types2();
8359
8761
  QUERY_PHRASE_WINDOW = 3;
8360
8762
  }
8361
8763
  });
@@ -8459,7 +8861,7 @@ async function finalizeRecallResults(params) {
8459
8861
  var init_finalize = __esm({
8460
8862
  "src/modules/recall/adapters/pipeline/finalize.ts"() {
8461
8863
  "use strict";
8462
- init_types();
8864
+ init_types2();
8463
8865
  }
8464
8866
  });
8465
8867
 
@@ -9125,7 +9527,7 @@ var init_retrieval = __esm({
9125
9527
  init_candidate_scoring();
9126
9528
  init_entry_filters();
9127
9529
  init_lexical();
9128
- init_types();
9530
+ init_types2();
9129
9531
  init_session_start();
9130
9532
  init_candidates();
9131
9533
  init_helpers();
@@ -9197,7 +9599,7 @@ var init_outbound = __esm({
9197
9599
  init_retrieval();
9198
9600
  init_helpers();
9199
9601
  init_score();
9200
- init_types();
9602
+ init_types2();
9201
9603
  init_score();
9202
9604
  init_score();
9203
9605
  init_session_start();
@@ -9328,7 +9730,7 @@ var init_recall = __esm({
9328
9730
  init_rerank();
9329
9731
  init_trace();
9330
9732
  init_outbound();
9331
- init_types();
9733
+ init_types2();
9332
9734
  }
9333
9735
  });
9334
9736
 
@@ -12644,30 +13046,6 @@ var init_jsonl_generic = __esm({
12644
13046
  }
12645
13047
  });
12646
13048
 
12647
- // src/shared/domain/types.ts
12648
- var KNOWLEDGE_TYPES, IMPORTANCE_MIN, IMPORTANCE_MAX, EXPIRY_LEVELS, SCOPE_LEVELS, KNOWLEDGE_PLATFORMS, REJECTED_CONFLICT_ENTRY_ID;
12649
- var init_types2 = __esm({
12650
- "src/shared/domain/types.ts"() {
12651
- "use strict";
12652
- KNOWLEDGE_TYPES = [
12653
- "fact",
12654
- "decision",
12655
- "preference",
12656
- "todo",
12657
- "relationship",
12658
- "event",
12659
- "lesson",
12660
- "reflection"
12661
- ];
12662
- IMPORTANCE_MIN = 1;
12663
- IMPORTANCE_MAX = 10;
12664
- EXPIRY_LEVELS = ["core", "permanent", "temporary"];
12665
- SCOPE_LEVELS = ["private", "personal", "public"];
12666
- KNOWLEDGE_PLATFORMS = ["openclaw", "claude-code", "codex", "plaud"];
12667
- REJECTED_CONFLICT_ENTRY_ID = "rejected";
12668
- }
12669
- });
12670
-
12671
13049
  // src/shared/utils/expiry.ts
12672
13050
  function normalizeExpiry(value) {
12673
13051
  if (typeof value !== "string") {
@@ -12686,7 +13064,7 @@ var EXPIRY_SET, EXPIRY_PRIORITY;
12686
13064
  var init_expiry = __esm({
12687
13065
  "src/shared/utils/expiry.ts"() {
12688
13066
  "use strict";
12689
- init_types2();
13067
+ init_types();
12690
13068
  EXPIRY_SET = new Set(EXPIRY_LEVELS);
12691
13069
  EXPIRY_PRIORITY = {
12692
13070
  temporary: 0,
@@ -15138,7 +15516,7 @@ var MIN_CONTENT_LENGTH, MAX_CONTENT_LENGTH, MAX_SUBJECT_LENGTH, MIN_IMPORTANCE_F
15138
15516
  var init_entry_validator = __esm({
15139
15517
  "src/modules/ingestion/domain/validation/entry-validator.ts"() {
15140
15518
  "use strict";
15141
- init_types2();
15519
+ init_types();
15142
15520
  init_content_patterns();
15143
15521
  init_extraction_guards();
15144
15522
  MIN_CONTENT_LENGTH = 20;
@@ -18778,7 +19156,7 @@ var init_write_queue = __esm({
18778
19156
  var init_types3 = __esm({
18779
19157
  "src/types.ts"() {
18780
19158
  "use strict";
18781
- init_types2();
19159
+ init_types();
18782
19160
  }
18783
19161
  });
18784
19162
 
@@ -23749,6 +24127,7 @@ var init_ingestion = __esm({
23749
24127
  init_lockfile();
23750
24128
  init_minhash();
23751
24129
  init_schema();
24130
+ init_meta();
23752
24131
  init_queries();
23753
24132
  init_cache();
23754
24133
  init_client2();
@@ -24272,6 +24651,7 @@ function resolveDbCommandDefaults() {
24272
24651
  initDb: shared.initDbFn,
24273
24652
  closeDb: shared.closeDbFn,
24274
24653
  backupDb,
24654
+ checkVectorIntegrity,
24275
24655
  getConflictShadowSummary,
24276
24656
  resetDb,
24277
24657
  walCheckpoint,
@@ -26154,1170 +26534,1186 @@ function formatPerfLog(fields) {
26154
26534
  init_inbound();
26155
26535
  init_project();
26156
26536
 
26157
- // src/edge/openclaw/memory-index.ts
26158
- init_inbound();
26537
+ // src/edge/openclaw/session-state.ts
26159
26538
  init_errors();
26160
- var MEMORY_INDEX_TIMEOUT_MS = 1e4;
26161
- var MEMORY_INDEX_TIMEOUT = /* @__PURE__ */ Symbol("memory-index-timeout");
26162
- function normalizeDate(value) {
26163
- const raw = typeof value === "string" ? value.trim() : "";
26164
- if (!raw) {
26165
- return "";
26539
+ init_logger();
26540
+
26541
+ // src/edge/openclaw/surfaced-memory-ledger.ts
26542
+ var MAX_RECALLED_SESSIONS = 200;
26543
+ var SESSION_SURFACED_MEMORY_INTENTS = {
26544
+ startupPromptVisibleRecall: {
26545
+ writeProfile: {
26546
+ surface: "session-start",
26547
+ source: "startup-prompt-visible",
26548
+ ownership: "before-reset-owned"
26549
+ },
26550
+ read: {
26551
+ sharedSelectors: ["dedupe-relevant", "before-reset-owned"]
26552
+ },
26553
+ lifecycle: {
26554
+ ownership: "before-reset-owned",
26555
+ commandReset: "preserve",
26556
+ beforeResetFeedback: "owned"
26557
+ }
26558
+ },
26559
+ midSessionPluginInjection: {
26560
+ writeProfile: {
26561
+ surface: "mid-session-recall",
26562
+ source: "plugin-injection",
26563
+ ownership: "dedupe-only"
26564
+ },
26565
+ read: {
26566
+ sharedSelectors: ["dedupe-relevant"]
26567
+ },
26568
+ lifecycle: {
26569
+ ownership: "dedupe-only",
26570
+ commandReset: "clear",
26571
+ beforeResetFeedback: "excluded"
26572
+ }
26573
+ },
26574
+ agentToolRecallOutput: {
26575
+ writeProfile: {
26576
+ surface: "agent-tool-recall",
26577
+ source: "agent-tool-output",
26578
+ ownership: "dedupe-only"
26579
+ },
26580
+ read: {
26581
+ sharedSelectors: ["dedupe-relevant"]
26582
+ },
26583
+ lifecycle: {
26584
+ ownership: "dedupe-only",
26585
+ commandReset: "clear",
26586
+ beforeResetFeedback: "excluded"
26587
+ }
26166
26588
  }
26167
- const parsed = new Date(raw);
26168
- if (Number.isNaN(parsed.getTime())) {
26169
- return raw;
26589
+ };
26590
+ function buildSessionSurfacedMemoryRecordProfiles() {
26591
+ const profiles = {};
26592
+ for (const [intent, definition] of Object.entries(SESSION_SURFACED_MEMORY_INTENTS)) {
26593
+ profiles[intent] = definition.writeProfile;
26170
26594
  }
26171
- return parsed.toISOString();
26595
+ return profiles;
26172
26596
  }
26173
- function toProjectName(value) {
26174
- if (value === null || value === void 0) {
26175
- return null;
26597
+ var SESSION_SURFACED_MEMORY_RECORD_PROFILES = buildSessionSurfacedMemoryRecordProfiles();
26598
+ var SESSION_SURFACED_MEMORY_SELECTOR_FILTERS = {
26599
+ "dedupe-relevant": {},
26600
+ "before-reset-owned": { ownership: "before-reset-owned" }
26601
+ };
26602
+ var SESSION_SURFACED_MEMORY_SELECTORS = {
26603
+ dedupeRelevant: "dedupe-relevant",
26604
+ beforeResetOwned: "before-reset-owned",
26605
+ intent(intent) {
26606
+ return { kind: "intent", intent };
26607
+ },
26608
+ profile(profile) {
26609
+ return { kind: "profile", profile };
26610
+ },
26611
+ source(source) {
26612
+ return { kind: "source", source };
26176
26613
  }
26177
- const normalized = String(value).trim();
26178
- return normalized.length > 0 ? normalized : null;
26614
+ };
26615
+ function normalizeSessionKey(value) {
26616
+ return value.trim();
26179
26617
  }
26180
- function toCount(value) {
26181
- const parsed = Number(value);
26182
- if (!Number.isFinite(parsed) || parsed <= 0) {
26183
- return 0;
26618
+ function trimSurfacedMemoryLedgers(store) {
26619
+ while (store.ledgers.size > MAX_RECALLED_SESSIONS) {
26620
+ const oldestKey = store.ledgers.keys().next().value;
26621
+ if (oldestKey === void 0) {
26622
+ break;
26623
+ }
26624
+ store.ledgers.delete(oldestKey);
26625
+ for (const [alias, canonicalKey] of store.aliases) {
26626
+ if (canonicalKey === oldestKey) {
26627
+ store.aliases.delete(alias);
26628
+ }
26629
+ }
26184
26630
  }
26185
- return Math.floor(parsed);
26186
26631
  }
26187
- function normalizeProjectIndexEntry(value) {
26188
- if (!value || typeof value !== "object") {
26189
- return null;
26632
+ function touchSurfacedMemoryLedger(store, canonicalKey) {
26633
+ const existing = store.ledgers.get(canonicalKey);
26634
+ if (!existing) {
26635
+ return;
26190
26636
  }
26191
- const row = value;
26192
- return {
26193
- project: toProjectName(row.project),
26194
- count: toCount(row.count),
26195
- lastUpdated: normalizeDate(row.lastUpdated ?? row.last_updated)
26196
- };
26637
+ store.ledgers.delete(canonicalKey);
26638
+ store.ledgers.set(canonicalKey, existing);
26197
26639
  }
26198
- function parseMemoryIndex(value) {
26199
- if (!value || typeof value !== "object") {
26200
- return null;
26201
- }
26202
- const response = value;
26203
- if (!Array.isArray(response.projects)) {
26204
- return null;
26640
+ function resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey) {
26641
+ const normalizedKey = normalizeSessionKey(sessionKey);
26642
+ if (!normalizedKey) {
26643
+ return "";
26205
26644
  }
26206
- const projects = response.projects.map((entry) => normalizeProjectIndexEntry(entry)).filter((entry) => entry !== null);
26207
- const totalFromResponse = Number(response.totalEntries);
26208
- const totalEntries = Number.isFinite(totalFromResponse) && totalFromResponse >= 0 ? Math.floor(totalFromResponse) : projects.reduce((sum, project) => sum + project.count, 0);
26209
- return { projects, totalEntries };
26645
+ return store.aliases.get(normalizedKey) ?? normalizedKey;
26210
26646
  }
26211
- async function runMemoryIndex(dbPath, executionOptions) {
26212
- try {
26213
- const timeoutMs = executionOptions?.timeoutMs ?? MEMORY_INDEX_TIMEOUT_MS;
26214
- const result = await withTimeout(
26215
- loadMemoryIndex(dbPath, executionOptions?.recallServiceDeps),
26216
- timeoutMs
26217
- );
26218
- if (result === MEMORY_INDEX_TIMEOUT) {
26219
- return { status: "timeout" };
26220
- }
26221
- return result;
26222
- } catch (error) {
26223
- return {
26224
- status: "error",
26225
- error: toErrorMessage(error)
26226
- };
26647
+ function getOrCreateSurfacedMemoryLedger(store, sessionKey) {
26648
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26649
+ if (!canonicalKey) {
26650
+ return null;
26227
26651
  }
26228
- }
26229
- async function loadMemoryIndex(dbPath, recallServiceDeps) {
26230
- const index = parseMemoryIndex(
26231
- await buildRecallIndex(
26232
- { dbPath },
26233
- { runtime: adaptLegacyRecallRuntimeDeps(recallServiceDeps) }
26234
- )
26235
- );
26236
- if (!index) {
26237
- return { status: "invalid" };
26652
+ const existing = store.ledgers.get(canonicalKey);
26653
+ if (existing) {
26654
+ touchSurfacedMemoryLedger(store, canonicalKey);
26655
+ return existing;
26238
26656
  }
26239
- return {
26240
- status: "ok",
26241
- index
26657
+ const ledger = {
26658
+ nextSequence: 1,
26659
+ records: []
26242
26660
  };
26661
+ store.ledgers.set(canonicalKey, ledger);
26662
+ trimSurfacedMemoryLedgers(store);
26663
+ return ledger;
26243
26664
  }
26244
- async function withTimeout(promise, timeoutMs) {
26245
- let timer;
26246
- try {
26247
- return await new Promise((resolve, reject) => {
26248
- timer = setTimeout(() => {
26249
- resolve(MEMORY_INDEX_TIMEOUT);
26250
- }, timeoutMs);
26251
- promise.then(resolve).catch(reject);
26252
- });
26253
- } finally {
26254
- if (timer) {
26255
- clearTimeout(timer);
26256
- }
26665
+ function matchesSurfacedMemoryLedgerFilter(record, filter) {
26666
+ if (filter.surface && record.surface !== filter.surface) {
26667
+ return false;
26257
26668
  }
26258
- }
26259
- function formatMemoryIndex(index) {
26260
- if (index.projects.length === 0) {
26261
- return "";
26669
+ if (filter.source && record.source !== filter.source) {
26670
+ return false;
26262
26671
  }
26263
- const lines = ["## Memory Index", ""];
26264
- for (const project of index.projects) {
26265
- const name = project.project ?? "(universal)";
26266
- const date = project.lastUpdated ? project.lastUpdated.slice(0, 10) : "unknown";
26267
- lines.push(`- ${name}: ${project.count} entries (last updated ${date})`);
26672
+ if (filter.ownership && record.ownership !== filter.ownership) {
26673
+ return false;
26268
26674
  }
26269
- lines.push("");
26270
- lines.push(`Total: ${index.totalEntries} entries`);
26271
- return lines.join("\n");
26272
- }
26273
-
26274
- // src/edge/openclaw/recall.ts
26275
- var RECALL_QUERY_MAX_CHARS = 500;
26276
- var DEFAULT_LIMIT3 = 10;
26277
- function resolveUpdateMetadata(isBrowse, isSessionStart, requestOverrides) {
26278
- const metadataMode = requestOverrides?.metadataMode ?? "non-browse";
26279
- return requestOverrides?.noUpdate !== true && !isBrowse && (metadataMode === "non-browse" || metadataMode === "session-start" && isSessionStart);
26675
+ return true;
26280
26676
  }
26281
- function resolveRuntimeDeps(executionOptions) {
26282
- return adaptLegacyRecallRuntimeDeps(executionOptions?.recallServiceDeps);
26677
+ function normalizeSurfacedMemoryLedgerInputs(inputs) {
26678
+ const normalized = [];
26679
+ const seenSignatures = /* @__PURE__ */ new Set();
26680
+ for (const input of inputs) {
26681
+ const entryId = normalizeSessionKey(input.entryId);
26682
+ if (!entryId) {
26683
+ continue;
26684
+ }
26685
+ const signature = [
26686
+ entryId,
26687
+ input.surface,
26688
+ input.source,
26689
+ input.ownership
26690
+ ].join("\0");
26691
+ if (seenSignatures.has(signature)) {
26692
+ continue;
26693
+ }
26694
+ seenSignatures.add(signature);
26695
+ normalized.push({
26696
+ entryId,
26697
+ surface: input.surface,
26698
+ source: input.source,
26699
+ ownership: input.ownership
26700
+ });
26701
+ }
26702
+ return normalized;
26283
26703
  }
26284
- function buildRecallRequest(budget, project, query, options) {
26285
- const isBrowse = options?.context === "browse";
26286
- const isSessionStart = options?.context === "session-start";
26287
- const trimmedQuery = query?.trim() ?? "";
26288
- const queryText = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
26289
- const inferredSessionStart = isSessionStart || queryText.length === 0;
26290
- const universalOnly = options?.nullProjectOnly === true || options?.universalOnly === true;
26291
- const explicitWildcardProject = (options?.project ?? []).some(
26292
- (candidate) => candidate.split(",").some((part) => part.trim() === "*")
26293
- );
26294
- const normalizedProjects = Array.from(new Set(
26295
- (options?.project ?? []).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0)
26296
- ));
26297
- const explicitProjectSelection = explicitWildcardProject || normalizedProjects.length > 0;
26298
- const normalizedProjectHints = parseProjectList(options?.projectHints).filter((projectHint) => projectHint !== "*");
26299
- const resolvedProject = universalOnly ? void 0 : explicitProjectSelection ? normalizedProjects.length > 0 ? normalizedProjects : void 0 : !project ? void 0 : [project];
26300
- const resolvedProjectStrict = universalOnly ? void 0 : explicitProjectSelection ? options?.projectStrict ? true : void 0 : project && !universalOnly ? true : void 0;
26301
- const resolvedProjectHints = universalOnly || explicitProjectSelection || explicitWildcardProject ? void 0 : normalizedProjectHints.length > 0 ? normalizedProjectHints : void 0;
26704
+ function normalizeSessionSurfacedMemoryWrites(writes) {
26705
+ const inputs = [];
26706
+ for (const write of writes) {
26707
+ for (const entryId of write.entryIds) {
26708
+ inputs.push({
26709
+ entryId,
26710
+ surface: write.surface,
26711
+ source: write.source,
26712
+ ownership: write.ownership
26713
+ });
26714
+ }
26715
+ }
26716
+ return normalizeSurfacedMemoryLedgerInputs(inputs);
26717
+ }
26718
+ function resolveSessionSurfacedMemoryRecordProfile(profile) {
26719
+ return { ...SESSION_SURFACED_MEMORY_RECORD_PROFILES[profile] };
26720
+ }
26721
+ function resolveSessionSurfacedMemoryIntent(intent) {
26722
+ const definition = SESSION_SURFACED_MEMORY_INTENTS[intent];
26302
26723
  return {
26303
- isBrowse,
26304
- isSessionStart: !isBrowse && inferredSessionStart,
26305
- limit: options?.limit ?? DEFAULT_LIMIT3,
26306
- queryText,
26307
- since: options?.since,
26308
- types: options?.types,
26309
- until: options?.until,
26310
- around: options?.around,
26311
- aroundRadius: options?.aroundRadius,
26312
- platform: options?.platform,
26313
- minImportance: options?.minImportance,
26314
- expiry: options?.expiry,
26315
- universalOnly,
26316
- project: resolvedProject,
26317
- projectStrict: resolvedProjectStrict,
26318
- projectHints: resolvedProjectHints,
26319
- budget: isBrowse ? void 0 : options?.budget === null ? void 0 : options?.budget ?? budget
26724
+ intent,
26725
+ writeProfile: { ...definition.writeProfile },
26726
+ readSelector: SESSION_SURFACED_MEMORY_SELECTORS.intent(intent),
26727
+ sharedSelectors: [...definition.read.sharedSelectors],
26728
+ lifecycle: { ...definition.lifecycle }
26320
26729
  };
26321
26730
  }
26322
- function shapeRecallResult(result) {
26731
+ function resolveSessionSurfacedMemorySelectorFilter(selector) {
26732
+ if (selector === "dedupe-relevant" || selector === "before-reset-owned") {
26733
+ return { ...SESSION_SURFACED_MEMORY_SELECTOR_FILTERS[selector] };
26734
+ }
26735
+ if (selector.kind === "intent") {
26736
+ return resolveSessionSurfacedMemoryIntent(selector.intent).writeProfile;
26737
+ }
26738
+ if (selector.kind === "profile") {
26739
+ return resolveSessionSurfacedMemoryRecordProfile(selector.profile);
26740
+ }
26323
26741
  return {
26324
- query: result.payload.query,
26325
- decision: result.payload.decision,
26326
- rollupSummary: result.payload.rollup_summary,
26327
- surfaceSelection: result.surfaceSelection,
26328
- results: result.payload.results.map((item) => ({
26329
- entry: item.entry,
26330
- score: item.score,
26331
- category: item.category
26332
- }))
26742
+ source: selector.source
26333
26743
  };
26334
26744
  }
26335
- async function runRecallStrict(budget, project, query, options, dbPath, executionOptions) {
26336
- return executeRecallRequest(
26337
- budget,
26338
- project,
26339
- query,
26340
- options,
26341
- dbPath,
26342
- executionOptions
26343
- );
26745
+ function normalizeSessionSurfacedMemoryIntentWrites(writes) {
26746
+ const normalized = [];
26747
+ for (const write of writes) {
26748
+ normalized.push({
26749
+ entryIds: write.entryIds,
26750
+ ...resolveSessionSurfacedMemoryIntent(write.intent).writeProfile
26751
+ });
26752
+ }
26753
+ return normalized;
26344
26754
  }
26345
- async function executeRecallRequest(budget, project, query, options, dbPath, executionOptions) {
26346
- const request = buildRecallRequest(budget, project, query, options);
26347
- const runtime = resolveRuntimeDeps(executionOptions);
26348
- const updateMetadata = resolveUpdateMetadata(
26349
- request.isBrowse,
26350
- request.isSessionStart,
26351
- executionOptions?.requestOverrides
26352
- );
26353
- const result = request.isBrowse ? await browseRecall(
26354
- {
26355
- dbPath,
26356
- limit: request.limit,
26357
- threshold: 0,
26358
- types: request.types,
26359
- since: request.since,
26360
- until: request.until,
26361
- around: request.around,
26362
- aroundRadius: request.aroundRadius,
26363
- platform: request.platform,
26364
- minImportance: request.minImportance,
26365
- expiry: request.expiry,
26366
- project: request.project,
26367
- projectStrict: request.projectStrict,
26368
- projectHints: request.projectHints,
26369
- universalOnly: request.universalOnly,
26370
- includeRecallCounts: false
26371
- },
26372
- { runtime }
26373
- ) : request.isSessionStart ? await sessionStartRecall(
26374
- {
26375
- dbPath,
26376
- limit: request.limit,
26377
- budget: request.budget,
26378
- threshold: 0,
26379
- types: request.types,
26380
- since: request.since,
26381
- until: request.until,
26382
- around: request.around,
26383
- aroundRadius: request.aroundRadius,
26384
- platform: request.platform,
26385
- minImportance: request.minImportance,
26386
- expiry: request.expiry,
26387
- project: request.project,
26388
- projectStrict: request.projectStrict,
26389
- projectHints: request.projectHints,
26390
- universalOnly: request.universalOnly,
26391
- updateMetadata,
26392
- includeRecallCounts: false
26393
- },
26394
- { runtime }
26395
- ) : await searchRecall(
26396
- {
26397
- dbPath,
26398
- queryText: request.queryText,
26399
- queryContext: "default",
26400
- limit: request.limit,
26401
- budget: request.budget,
26402
- threshold: 0,
26403
- types: request.types,
26404
- since: request.since,
26405
- until: request.until,
26406
- around: request.around,
26407
- aroundRadius: request.aroundRadius,
26408
- platform: request.platform,
26409
- minImportance: request.minImportance,
26410
- expiry: request.expiry,
26411
- project: request.project,
26412
- projectStrict: request.projectStrict,
26413
- projectHints: request.projectHints,
26414
- universalOnly: request.universalOnly,
26415
- updateMetadata,
26416
- includeRecallCounts: false
26417
- },
26418
- { runtime }
26419
- );
26420
- return shapeRecallResult(result);
26755
+ function toSessionSurfacedMemoryWrites(writes) {
26756
+ if (Symbol.iterator in Object(writes) && !("entryIds" in Object(writes))) {
26757
+ return writes;
26758
+ }
26759
+ return [writes];
26421
26760
  }
26422
- async function runRecall(budget, project, query, options, dbPath, executionOptions) {
26423
- try {
26424
- return await executeRecallRequest(budget, project, query, options, dbPath, executionOptions);
26425
- } catch {
26426
- return null;
26761
+ function toSessionSurfacedMemoryIntentWrites(writes) {
26762
+ if (Symbol.iterator in Object(writes) && !("entryIds" in Object(writes))) {
26763
+ return writes;
26427
26764
  }
26765
+ return [writes];
26428
26766
  }
26429
- async function fetchCoreEntries(coreProjects, dbPath, executionOptions) {
26430
- let result;
26431
- try {
26432
- result = shapeRecallResult(
26433
- await loadCoreRecall(
26434
- { dbPath },
26435
- { runtime: resolveRuntimeDeps(executionOptions) }
26436
- )
26437
- );
26438
- } catch {
26439
- return null;
26767
+ function registerSessionSurfacedMemoryAlias(store, aliasKey, sessionKey) {
26768
+ const normalizedAlias = normalizeSessionKey(aliasKey);
26769
+ const normalizedSessionKey = normalizeSessionKey(sessionKey);
26770
+ if (!normalizedAlias || !normalizedSessionKey) {
26771
+ return;
26440
26772
  }
26441
- const allowedProjects = new Set(
26442
- coreProjects.map((project) => project.trim()).filter((project) => project.length > 0)
26773
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, normalizedSessionKey);
26774
+ store.aliases.set(normalizedAlias, canonicalKey);
26775
+ if (normalizedAlias === canonicalKey) {
26776
+ return;
26777
+ }
26778
+ const aliasLedger = store.ledgers.get(normalizedAlias);
26779
+ if (!aliasLedger) {
26780
+ return;
26781
+ }
26782
+ store.ledgers.delete(normalizedAlias);
26783
+ const targetLedger = getOrCreateSurfacedMemoryLedger(store, canonicalKey);
26784
+ if (!targetLedger) {
26785
+ return;
26786
+ }
26787
+ const existingSignatures = new Set(
26788
+ targetLedger.records.map((record) => [record.entryId, record.surface, record.source, record.ownership].join("\0"))
26443
26789
  );
26444
- const filteredResults = result.results.filter((item) => {
26445
- const rawProject = item.entry?.project;
26446
- const project = typeof rawProject === "string" ? rawProject.trim() : "";
26447
- if (!project) {
26448
- return true;
26449
- }
26450
- return allowedProjects.has(project);
26451
- });
26452
- return {
26453
- ...result,
26454
- results: filteredResults.slice(0, 30)
26455
- };
26456
- }
26457
-
26458
- // src/edge/openclaw/session-query.ts
26459
- init_errors();
26460
- init_guards();
26461
- import { createReadStream as createReadStream2 } from "fs";
26462
- import { readdir, stat } from "fs/promises";
26463
- import path10 from "path";
26464
- import { createInterface as createInterface2 } from "readline";
26465
- var EXCHANGE_TEXT_MAX_CHARS = 200;
26466
- var EXCHANGE_USER_TURN_LIMIT = 5;
26467
- var RECENT_TURN_MAX_CHARS = 300;
26468
- var DEFAULT_RECENT_TURN_LIMIT = 7;
26469
- var SESSION_TRANSCRIPT_ARTIFACT_MARKERS = [".jsonl.reset.", ".jsonl.deleted."];
26470
- function extractTextFromRoleMessage(message, role, separator) {
26471
- if (!isRecord(message) || message["role"] !== role) {
26472
- return "";
26473
- }
26474
- const content = message["content"];
26475
- if (typeof content === "string") {
26476
- return content.trim();
26477
- }
26478
- if (!Array.isArray(content)) {
26479
- return "";
26480
- }
26481
- const textParts = [];
26482
- for (const part of content) {
26483
- if (!isRecord(part) || part["type"] !== "text") {
26790
+ for (const record of aliasLedger.records) {
26791
+ const signature = [
26792
+ record.entryId,
26793
+ record.surface,
26794
+ record.source,
26795
+ record.ownership
26796
+ ].join("\0");
26797
+ if (existingSignatures.has(signature)) {
26484
26798
  continue;
26485
26799
  }
26486
- const partText = part["text"];
26487
- if (typeof partText !== "string") {
26800
+ existingSignatures.add(signature);
26801
+ targetLedger.records.push({
26802
+ ...record,
26803
+ sequence: targetLedger.nextSequence++
26804
+ });
26805
+ }
26806
+ touchSurfacedMemoryLedger(store, canonicalKey);
26807
+ }
26808
+ function appendSessionSurfacedMemoryLedgerEntries(store, sessionKey, inputs) {
26809
+ const ledger = getOrCreateSurfacedMemoryLedger(store, sessionKey);
26810
+ if (!ledger) {
26811
+ return 0;
26812
+ }
26813
+ const normalizedInputs = normalizeSurfacedMemoryLedgerInputs(inputs);
26814
+ const existingSignatures = new Set(
26815
+ ledger.records.map((record) => [record.entryId, record.surface, record.source, record.ownership].join("\0"))
26816
+ );
26817
+ let addedCount = 0;
26818
+ for (const input of normalizedInputs) {
26819
+ const signature = [
26820
+ input.entryId,
26821
+ input.surface,
26822
+ input.source,
26823
+ input.ownership
26824
+ ].join("\0");
26825
+ if (existingSignatures.has(signature)) {
26488
26826
  continue;
26489
26827
  }
26490
- const trimmed = partText.trim();
26491
- if (trimmed) {
26492
- textParts.push(trimmed);
26493
- }
26828
+ existingSignatures.add(signature);
26829
+ ledger.records.push({
26830
+ ...input,
26831
+ sequence: ledger.nextSequence++
26832
+ });
26833
+ addedCount += 1;
26494
26834
  }
26495
- return textParts.join(separator).trim();
26835
+ if (addedCount > 0) {
26836
+ touchSurfacedMemoryLedger(store, resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey));
26837
+ }
26838
+ return addedCount;
26496
26839
  }
26497
- function extractTextFromUserMessage(message) {
26498
- return extractTextFromRoleMessage(message, "user", " ");
26840
+ function replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, inputs) {
26841
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26842
+ if (!canonicalKey) {
26843
+ return;
26844
+ }
26845
+ const normalizedInputs = normalizeSurfacedMemoryLedgerInputs(inputs);
26846
+ const ledger = {
26847
+ nextSequence: 1,
26848
+ records: normalizedInputs.map((input, index) => ({
26849
+ ...input,
26850
+ sequence: index + 1
26851
+ }))
26852
+ };
26853
+ ledger.nextSequence = ledger.records.length + 1;
26854
+ store.ledgers.delete(canonicalKey);
26855
+ store.ledgers.set(canonicalKey, ledger);
26856
+ trimSurfacedMemoryLedgers(store);
26499
26857
  }
26500
- function extractTextFromAssistantMessage(message) {
26501
- return extractTextFromRoleMessage(message, "assistant", " ");
26858
+ function recordSessionSurfacedMemory(store, sessionKey, writes, options = {}) {
26859
+ const normalizedInputs = normalizeSessionSurfacedMemoryWrites(
26860
+ toSessionSurfacedMemoryWrites(writes)
26861
+ );
26862
+ if (normalizedInputs.length === 0) {
26863
+ if (options.mode === "replace") {
26864
+ replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, []);
26865
+ }
26866
+ return 0;
26867
+ }
26868
+ if (options.mode === "replace") {
26869
+ replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, normalizedInputs);
26870
+ return normalizedInputs.length;
26871
+ }
26872
+ return appendSessionSurfacedMemoryLedgerEntries(store, sessionKey, normalizedInputs);
26502
26873
  }
26503
- function truncateMessageText(text, maxChars = EXCHANGE_TEXT_MAX_CHARS) {
26504
- return text.length > maxChars ? text.slice(0, maxChars) : text;
26874
+ function recordSessionSurfacedMemoryByIntent(store, sessionKey, writes, options = {}) {
26875
+ return recordSessionSurfacedMemory(
26876
+ store,
26877
+ sessionKey,
26878
+ normalizeSessionSurfacedMemoryIntentWrites(
26879
+ toSessionSurfacedMemoryIntentWrites(writes)
26880
+ ),
26881
+ options
26882
+ );
26505
26883
  }
26506
- function stripPromptMetadata(raw) {
26507
- if (!raw) {
26508
- return "";
26884
+ function getSessionSurfacedMemoryLedger(store, sessionKey) {
26885
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26886
+ if (!canonicalKey) {
26887
+ return [];
26509
26888
  }
26510
- try {
26511
- const pattern = /\[\w{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2} (?:[A-Z]{2,5}|GMT[+-]\d{1,2})\] /g;
26512
- let lastMatchEnd = -1;
26513
- let match = null;
26514
- while ((match = pattern.exec(raw)) !== null) {
26515
- lastMatchEnd = match.index + match[0].length;
26516
- }
26517
- if (lastMatchEnd >= 0) {
26518
- return raw.slice(lastMatchEnd).trim();
26889
+ const ledger = store.ledgers.get(canonicalKey);
26890
+ return ledger ? [...ledger.records] : [];
26891
+ }
26892
+ function getSessionSurfacedMemoryLedgerRecords(store, sessionKey, filter = {}) {
26893
+ return getSessionSurfacedMemoryLedger(store, sessionKey).filter((record) => matchesSurfacedMemoryLedgerFilter(record, filter));
26894
+ }
26895
+ function collectSessionSurfacedMemoryEntryIds(records) {
26896
+ const surfacedEntryIds = [];
26897
+ const seenEntryIds = /* @__PURE__ */ new Set();
26898
+ for (const record of records) {
26899
+ if (seenEntryIds.has(record.entryId)) {
26900
+ continue;
26519
26901
  }
26520
- return raw.trim();
26521
- } catch {
26522
- return "";
26902
+ seenEntryIds.add(record.entryId);
26903
+ surfacedEntryIds.push(record.entryId);
26523
26904
  }
26905
+ return surfacedEntryIds;
26524
26906
  }
26525
- function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT) {
26526
- try {
26527
- if (!Array.isArray(messages) || messages.length === 0) {
26528
- return "";
26529
- }
26530
- if (maxTurns <= 0) {
26531
- return "";
26532
- }
26533
- const collected = [];
26534
- let collectedUserTurns = 0;
26535
- for (let index = messages.length - 1; index >= 0; index -= 1) {
26536
- const message = messages[index];
26537
- const userText = sanitizeTranscriptTurnText(extractTextFromUserMessage(message));
26538
- if (userText) {
26539
- collected.push({
26540
- role: "user",
26541
- text: truncateMessageText(userText.replace(/\s+/g, " ").trim())
26542
- });
26543
- collectedUserTurns += 1;
26544
- if (collectedUserTurns >= maxTurns) {
26545
- break;
26546
- }
26547
- continue;
26548
- }
26549
- const assistantText = sanitizeTranscriptTurnText(extractTextFromAssistantMessage(message));
26550
- if (assistantText) {
26551
- collected.push({
26552
- role: "assistant",
26553
- text: truncateMessageText(assistantText.replace(/\s+/g, " ").trim())
26554
- });
26555
- }
26556
- }
26557
- if (collected.length === 0) {
26558
- return "";
26907
+ function removeSessionSurfacedMemoryLedgerEntries(store, sessionKey, filter = {}) {
26908
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26909
+ if (!canonicalKey) {
26910
+ return 0;
26911
+ }
26912
+ const ledger = store.ledgers.get(canonicalKey);
26913
+ if (!ledger) {
26914
+ return 0;
26915
+ }
26916
+ const keptRecords = ledger.records.filter((record) => !matchesSurfacedMemoryLedgerFilter(record, filter));
26917
+ const removedCount = ledger.records.length - keptRecords.length;
26918
+ if (removedCount === 0) {
26919
+ return 0;
26920
+ }
26921
+ if (keptRecords.length === 0) {
26922
+ clearSessionSurfacedMemoryLedger(store, sessionKey);
26923
+ return removedCount;
26924
+ }
26925
+ ledger.records = keptRecords;
26926
+ touchSurfacedMemoryLedger(store, canonicalKey);
26927
+ return removedCount;
26928
+ }
26929
+ function clearSessionSurfacedMemoryLedger(store, sessionKey) {
26930
+ const normalizedKey = normalizeSessionKey(sessionKey);
26931
+ if (!normalizedKey) {
26932
+ return;
26933
+ }
26934
+ const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, normalizedKey);
26935
+ if (canonicalKey) {
26936
+ store.ledgers.delete(canonicalKey);
26937
+ }
26938
+ for (const [alias, targetKey] of store.aliases) {
26939
+ if (alias === normalizedKey || targetKey === canonicalKey) {
26940
+ store.aliases.delete(alias);
26559
26941
  }
26560
- return collected.reverse().map((turn) => `${turn.role === "user" ? "U" : "A"}: ${turn.text}`).join(" | ");
26561
- } catch {
26562
- return "";
26563
26942
  }
26564
26943
  }
26565
- async function listSessionFileCandidates(sessionsDir, currentSessionId, logger) {
26566
- try {
26567
- const normalizedSessionId = currentSessionId?.trim();
26568
- const entries = await readdir(sessionsDir, { withFileTypes: true });
26569
- const candidatePaths = [];
26570
- for (const entry of entries) {
26571
- if (!entry.isFile()) {
26572
- continue;
26573
- }
26574
- const isPlainJsonl = entry.name.endsWith(".jsonl");
26575
- const isTranscriptArtifact = SESSION_TRANSCRIPT_ARTIFACT_MARKERS.some((marker) => entry.name.includes(marker));
26576
- if (!isPlainJsonl && !isTranscriptArtifact) {
26577
- continue;
26578
- }
26579
- if (entry.name.includes(".deleted.") && !entry.name.includes(".jsonl.deleted.")) {
26580
- continue;
26581
- }
26582
- if (normalizedSessionId && entry.name.startsWith(normalizedSessionId)) {
26583
- continue;
26584
- }
26585
- candidatePaths.push(path10.join(sessionsDir, entry.name));
26586
- }
26587
- if (candidatePaths.length === 0) {
26588
- return [];
26589
- }
26590
- const statResults = await Promise.all(
26591
- candidatePaths.map(async (filePath) => {
26592
- try {
26593
- const fileStats = await stat(filePath);
26594
- return { filePath, mtimeMs: fileStats.mtimeMs };
26595
- } catch {
26596
- return null;
26597
- }
26598
- })
26599
- );
26600
- const candidates = statResults.filter(
26601
- (result) => result !== null
26602
- );
26603
- if (candidates.length === 0) {
26604
- return [];
26605
- }
26606
- candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
26607
- return candidates;
26608
- } catch (err) {
26609
- logger?.debug?.(
26610
- `listSessionFileCandidates: failed to read sessions dir "${sessionsDir}": ${toErrorMessage(err)}`
26611
- );
26612
- return [];
26944
+ var SURFACED_MEMORY_LIFECYCLE_FILTERS = {
26945
+ commandResetCleared: {
26946
+ ownership: "dedupe-only"
26613
26947
  }
26948
+ };
26949
+ function applySessionSurfacedMemoryCommandResetPolicy(store, sessionKey) {
26950
+ return removeSessionSurfacedMemoryLedgerEntries(
26951
+ store,
26952
+ sessionKey,
26953
+ SURFACED_MEMORY_LIFECYCLE_FILTERS.commandResetCleared
26954
+ );
26614
26955
  }
26615
- async function extractRecentTurnObjects(filePath, maxTurns = DEFAULT_RECENT_TURN_LIMIT) {
26616
- try {
26617
- if (!filePath) {
26618
- return [];
26619
- }
26620
- const parsedMaxTurns = Number.isFinite(maxTurns) ? Math.max(0, Math.trunc(maxTurns)) : 0;
26621
- if (parsedMaxTurns === 0) {
26622
- return [];
26623
- }
26624
- const allTurns = [];
26625
- await new Promise((resolve, reject) => {
26626
- const rl = createInterface2({
26627
- input: createReadStream2(filePath, { encoding: "utf8" }),
26628
- crlfDelay: Infinity
26629
- });
26630
- rl.on("line", (line) => {
26631
- const trimmed = line.trim();
26632
- if (!trimmed) {
26633
- return;
26634
- }
26635
- let record;
26636
- try {
26637
- record = JSON.parse(trimmed);
26638
- } catch {
26639
- return;
26640
- }
26641
- if (!isRecord(record) || record["type"] !== "message") {
26642
- return;
26643
- }
26644
- const message = record["message"];
26645
- const messageTimestamp = isRecord(message) && typeof message["timestamp"] === "string" ? message["timestamp"] : void 0;
26646
- const timestamp2 = typeof record["timestamp"] === "string" ? record["timestamp"] : messageTimestamp;
26647
- const userText = extractTextFromRoleMessage(message, "user", "\n");
26648
- if (userText) {
26649
- allTurns.push({ role: "user", text: userText, timestamp: timestamp2 });
26650
- return;
26651
- }
26652
- const assistantText = extractTextFromRoleMessage(message, "assistant", "\n");
26653
- if (assistantText) {
26654
- allTurns.push({ role: "assistant", text: assistantText, timestamp: timestamp2 });
26655
- }
26656
- });
26657
- rl.on("close", resolve);
26658
- rl.on("error", reject);
26659
- });
26660
- const sanitizedTurns = sanitizeTranscriptTurns(allTurns, {
26661
- maxCharsPerTurn: RECENT_TURN_MAX_CHARS
26662
- });
26663
- return sanitizedTurns.slice(-parsedMaxTurns);
26664
- } catch {
26665
- return [];
26666
- }
26956
+ function applySessionSurfacedMemoryBeforeResetPolicy(store, sessionKey) {
26957
+ const beforeResetOwnedEntryIds = querySessionSurfacedMemory(store, sessionKey).select(SESSION_SURFACED_MEMORY_SELECTORS.beforeResetOwned).entryIds();
26958
+ const clearedCount = getSessionSurfacedMemoryLedger(store, sessionKey).length;
26959
+ clearSessionSurfacedMemoryLedger(store, sessionKey);
26960
+ return {
26961
+ beforeResetOwnedEntryIds,
26962
+ clearedCount
26963
+ };
26667
26964
  }
26668
-
26669
- // src/edge/openclaw/session-state.ts
26670
- init_errors();
26671
- init_logger();
26672
-
26673
- // src/edge/openclaw/surfaced-memory-ledger.ts
26674
- var MAX_RECALLED_SESSIONS = 200;
26675
- var SESSION_SURFACED_MEMORY_INTENTS = {
26676
- startupPromptVisibleRecall: {
26677
- writeProfile: {
26678
- surface: "session-start",
26679
- source: "startup-prompt-visible",
26680
- ownership: "before-reset-owned"
26681
- },
26682
- read: {
26683
- sharedSelectors: ["dedupe-relevant", "before-reset-owned"]
26684
- },
26685
- lifecycle: {
26686
- ownership: "before-reset-owned",
26687
- commandReset: "preserve",
26688
- beforeResetFeedback: "owned"
26965
+ function querySessionSurfacedMemory(store, sessionKey) {
26966
+ const records = (filter = {}) => getSessionSurfacedMemoryLedgerRecords(store, sessionKey, filter);
26967
+ const entryIds = (filter = {}) => collectSessionSurfacedMemoryEntryIds(records(filter));
26968
+ const hasEntry = (entryId, filter = {}) => {
26969
+ const normalizedEntryId = normalizeSessionKey(entryId);
26970
+ if (!normalizedEntryId) {
26971
+ return false;
26689
26972
  }
26690
- },
26691
- midSessionPluginInjection: {
26692
- writeProfile: {
26693
- surface: "mid-session-recall",
26694
- source: "plugin-injection",
26695
- ownership: "dedupe-only"
26696
- },
26697
- read: {
26698
- sharedSelectors: ["dedupe-relevant"]
26973
+ return records(filter).some((record) => record.entryId === normalizedEntryId);
26974
+ };
26975
+ const select = (selector) => {
26976
+ const filter = resolveSessionSurfacedMemorySelectorFilter(selector);
26977
+ return {
26978
+ records: () => records(filter),
26979
+ entryIds: () => entryIds(filter),
26980
+ hasEntry: (entryId) => hasEntry(entryId, filter)
26981
+ };
26982
+ };
26983
+ return {
26984
+ records,
26985
+ entryIds,
26986
+ hasEntry,
26987
+ select,
26988
+ dedupeEntryIds() {
26989
+ return select(SESSION_SURFACED_MEMORY_SELECTORS.dedupeRelevant).entryIds();
26699
26990
  },
26700
- lifecycle: {
26701
- ownership: "dedupe-only",
26702
- commandReset: "clear",
26703
- beforeResetFeedback: "excluded"
26704
- }
26705
- },
26706
- agentToolRecallOutput: {
26707
- writeProfile: {
26708
- surface: "agent-tool-recall",
26709
- source: "agent-tool-output",
26710
- ownership: "dedupe-only"
26991
+ hasDedupeEntry(entryId) {
26992
+ return select(SESSION_SURFACED_MEMORY_SELECTORS.dedupeRelevant).hasEntry(entryId);
26711
26993
  },
26712
- read: {
26713
- sharedSelectors: ["dedupe-relevant"]
26994
+ beforeResetOwnedEntryIds() {
26995
+ return select(SESSION_SURFACED_MEMORY_SELECTORS.beforeResetOwned).entryIds();
26714
26996
  },
26715
- lifecycle: {
26716
- ownership: "dedupe-only",
26717
- commandReset: "clear",
26718
- beforeResetFeedback: "excluded"
26997
+ clearDedupeOnly() {
26998
+ return removeSessionSurfacedMemoryLedgerEntries(store, sessionKey, {
26999
+ ownership: "dedupe-only"
27000
+ });
26719
27001
  }
27002
+ };
27003
+ }
27004
+
27005
+ // src/edge/openclaw/session-state.ts
27006
+ var SKIP_SESSION_PATTERNS = [":subagent:", ":cron:"];
27007
+ var DEFAULT_MAX_SEEN_SESSIONS = 1e3;
27008
+ var DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD = 0.85;
27009
+ var SEEN_SESSION_RETENTION_MS = 24 * 60 * 60 * 1e3;
27010
+ var CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
27011
+ var sessionStartLog = createLogger("session-start");
27012
+ var SESSION_STATE_GLOBALS_KEY = "__agenrOpenClawSessionState";
27013
+ function getSessionStateGlobals() {
27014
+ const globalState = globalThis;
27015
+ globalState[SESSION_STATE_GLOBALS_KEY] ??= {
27016
+ seenSessions: /* @__PURE__ */ new Map(),
27017
+ lastCleanupMs: 0,
27018
+ surfacedMemoryLedgerStore: {
27019
+ ledgers: /* @__PURE__ */ new Map(),
27020
+ aliases: /* @__PURE__ */ new Map()
27021
+ },
27022
+ sessionSignalState: /* @__PURE__ */ new Map(),
27023
+ handoffSeenSessionIds: /* @__PURE__ */ new Set(),
27024
+ sessionRef: { current: "" }
27025
+ };
27026
+ return globalState[SESSION_STATE_GLOBALS_KEY];
27027
+ }
27028
+ var sessionStateGlobals = getSessionStateGlobals();
27029
+ var seenSessions = sessionStateGlobals.seenSessions;
27030
+ var ledgerStore = sessionStateGlobals.surfacedMemoryLedgerStore;
27031
+ var sessionRef = sessionStateGlobals.sessionRef;
27032
+ var sessionSignalState = sessionStateGlobals.sessionSignalState;
27033
+ var handoffSeenSessionIds = sessionStateGlobals.handoffSeenSessionIds;
27034
+ function registerSessionSurfacedMemoryAlias2(aliasKey, sessionKey) {
27035
+ registerSessionSurfacedMemoryAlias(ledgerStore, aliasKey, sessionKey);
27036
+ }
27037
+ function recordSessionSurfacedMemoryByIntent2(sessionKey, writes, options = {}) {
27038
+ return recordSessionSurfacedMemoryByIntent(ledgerStore, sessionKey, writes, options);
27039
+ }
27040
+ function querySessionSurfacedMemory2(sessionKey) {
27041
+ return querySessionSurfacedMemory(ledgerStore, sessionKey);
27042
+ }
27043
+ function applySessionSurfacedMemoryCommandResetPolicy2(sessionKey) {
27044
+ return applySessionSurfacedMemoryCommandResetPolicy(ledgerStore, sessionKey);
27045
+ }
27046
+ function applySessionSurfacedMemoryBeforeResetPolicy2(sessionKey) {
27047
+ return applySessionSurfacedMemoryBeforeResetPolicy(ledgerStore, sessionKey);
27048
+ }
27049
+ function clearSessionSurfacedMemoryLedger2(sessionKey) {
27050
+ clearSessionSurfacedMemoryLedger(ledgerStore, sessionKey);
27051
+ }
27052
+ function hasHandoffSession(sessionId) {
27053
+ return handoffSeenSessionIds.has(sessionId);
27054
+ }
27055
+ function markHandoffSession(sessionId) {
27056
+ handoffSeenSessionIds.add(sessionId);
27057
+ }
27058
+ function deleteHandoffSession(sessionId) {
27059
+ handoffSeenSessionIds.delete(sessionId);
27060
+ }
27061
+ function resolveMaxSeenSessions() {
27062
+ const raw = process.env.AGENR_OPENCLAW_MAX_SEEN_SESSIONS;
27063
+ if (!raw) {
27064
+ return DEFAULT_MAX_SEEN_SESSIONS;
26720
27065
  }
26721
- };
26722
- function buildSessionSurfacedMemoryRecordProfiles() {
26723
- const profiles = {};
26724
- for (const [intent, definition] of Object.entries(SESSION_SURFACED_MEMORY_INTENTS)) {
26725
- profiles[intent] = definition.writeProfile;
27066
+ const parsed = Number.parseInt(raw, 10);
27067
+ if (Number.isFinite(parsed) && parsed > 0) {
27068
+ return parsed;
26726
27069
  }
26727
- return profiles;
27070
+ return DEFAULT_MAX_SEEN_SESSIONS;
26728
27071
  }
26729
- var SESSION_SURFACED_MEMORY_RECORD_PROFILES = buildSessionSurfacedMemoryRecordProfiles();
26730
- var SESSION_SURFACED_MEMORY_SELECTOR_FILTERS = {
26731
- "dedupe-relevant": {},
26732
- "before-reset-owned": { ownership: "before-reset-owned" }
26733
- };
26734
- var SESSION_SURFACED_MEMORY_SELECTORS = {
26735
- dedupeRelevant: "dedupe-relevant",
26736
- beforeResetOwned: "before-reset-owned",
26737
- intent(intent) {
26738
- return { kind: "intent", intent };
26739
- },
26740
- profile(profile) {
26741
- return { kind: "profile", profile };
26742
- },
26743
- source(source) {
26744
- return { kind: "source", source };
27072
+ function canExecuteSql(db) {
27073
+ return typeof db?.execute === "function";
27074
+ }
27075
+ function trimSeenSessionCache() {
27076
+ const maxSeenSessions = resolveMaxSeenSessions();
27077
+ while (seenSessions.size > maxSeenSessions) {
27078
+ const oldestKey = seenSessions.keys().next().value;
27079
+ if (oldestKey === void 0) {
27080
+ break;
27081
+ }
27082
+ seenSessions.delete(oldestKey);
27083
+ }
27084
+ }
27085
+ function touchSeenSessionCache(sessionKey) {
27086
+ seenSessions.delete(sessionKey);
27087
+ seenSessions.set(sessionKey, true);
27088
+ trimSeenSessionCache();
27089
+ }
27090
+ async function hasSeenSessionInDb(db, sessionKey) {
27091
+ const result = await db.execute({
27092
+ sql: "SELECT 1 FROM seen_sessions WHERE dedupe_key = ? AND seen_at >= ? LIMIT 1",
27093
+ args: [sessionKey, Date.now() - SEEN_SESSION_RETENTION_MS]
27094
+ });
27095
+ return result.rows.length > 0;
27096
+ }
27097
+ async function cleanupSeenSessionsInDb(db, nowMs) {
27098
+ const maxSeenSessions = resolveMaxSeenSessions();
27099
+ await db.execute({
27100
+ sql: "DELETE FROM seen_sessions WHERE seen_at < ?",
27101
+ args: [nowMs - SEEN_SESSION_RETENTION_MS]
27102
+ });
27103
+ await db.execute({
27104
+ sql: `
27105
+ DELETE FROM seen_sessions
27106
+ WHERE dedupe_key IN (
27107
+ SELECT dedupe_key
27108
+ FROM seen_sessions
27109
+ ORDER BY seen_at DESC, dedupe_key DESC
27110
+ LIMIT -1 OFFSET ?
27111
+ )
27112
+ `,
27113
+ args: [maxSeenSessions]
27114
+ });
27115
+ }
27116
+ function shouldSkipSession(sessionKey) {
27117
+ return SKIP_SESSION_PATTERNS.some((pattern) => sessionKey.includes(pattern));
27118
+ }
27119
+ async function hasSeenSession(sessionKey, db) {
27120
+ let seen = seenSessions.has(sessionKey);
27121
+ if (seen) {
27122
+ touchSeenSessionCache(sessionKey);
27123
+ sessionStartLog.debug(
27124
+ `hasSeenSession key=${sessionKey.slice(0, 12)} found=${seen} mapSize=${seenSessions.size}`
27125
+ );
27126
+ return true;
26745
27127
  }
26746
- };
26747
- function normalizeSessionKey(value) {
26748
- return value.trim();
26749
- }
26750
- function trimSurfacedMemoryLedgers(store) {
26751
- while (store.ledgers.size > MAX_RECALLED_SESSIONS) {
26752
- const oldestKey = store.ledgers.keys().next().value;
26753
- if (oldestKey === void 0) {
26754
- break;
26755
- }
26756
- store.ledgers.delete(oldestKey);
26757
- for (const [alias, canonicalKey] of store.aliases) {
26758
- if (canonicalKey === oldestKey) {
26759
- store.aliases.delete(alias);
27128
+ if (canExecuteSql(db)) {
27129
+ try {
27130
+ seen = await hasSeenSessionInDb(db, sessionKey);
27131
+ if (seen) {
27132
+ touchSeenSessionCache(sessionKey);
26760
27133
  }
27134
+ } catch (err) {
27135
+ sessionStartLog.warn(`hasSeenSession db lookup failed: ${toErrorMessage(err)}`);
26761
27136
  }
26762
27137
  }
27138
+ sessionStartLog.debug(
27139
+ `hasSeenSession key=${sessionKey.slice(0, 12)} found=${seen} mapSize=${seenSessions.size}`
27140
+ );
27141
+ return seen;
26763
27142
  }
26764
- function touchSurfacedMemoryLedger(store, canonicalKey) {
26765
- const existing = store.ledgers.get(canonicalKey);
26766
- if (!existing) {
27143
+ async function markSessionSeen(sessionKey, db) {
27144
+ touchSeenSessionCache(sessionKey);
27145
+ if (!canExecuteSql(db)) {
26767
27146
  return;
26768
27147
  }
26769
- store.ledgers.delete(canonicalKey);
26770
- store.ledgers.set(canonicalKey, existing);
26771
- }
26772
- function resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey) {
26773
- const normalizedKey = normalizeSessionKey(sessionKey);
26774
- if (!normalizedKey) {
26775
- return "";
26776
- }
26777
- return store.aliases.get(normalizedKey) ?? normalizedKey;
26778
- }
26779
- function getOrCreateSurfacedMemoryLedger(store, sessionKey) {
26780
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26781
- if (!canonicalKey) {
26782
- return null;
26783
- }
26784
- const existing = store.ledgers.get(canonicalKey);
26785
- if (existing) {
26786
- touchSurfacedMemoryLedger(store, canonicalKey);
26787
- return existing;
27148
+ const nowMs = Date.now();
27149
+ try {
27150
+ await db.execute({
27151
+ sql: "INSERT OR REPLACE INTO seen_sessions (dedupe_key, seen_at) VALUES (?, ?)",
27152
+ args: [sessionKey, nowMs]
27153
+ });
27154
+ if (nowMs - sessionStateGlobals.lastCleanupMs > CLEANUP_INTERVAL_MS) {
27155
+ await cleanupSeenSessionsInDb(db, nowMs);
27156
+ sessionStateGlobals.lastCleanupMs = nowMs;
27157
+ }
27158
+ } catch (err) {
27159
+ sessionStartLog.warn(`markSessionSeen db write failed: ${toErrorMessage(err)}`);
26788
27160
  }
26789
- const ledger = {
26790
- nextSequence: 1,
26791
- records: []
26792
- };
26793
- store.ledgers.set(canonicalKey, ledger);
26794
- trimSurfacedMemoryLedgers(store);
26795
- return ledger;
26796
27161
  }
26797
- function matchesSurfacedMemoryLedgerFilter(record, filter) {
26798
- if (filter.surface && record.surface !== filter.surface) {
26799
- return false;
26800
- }
26801
- if (filter.source && record.source !== filter.source) {
26802
- return false;
26803
- }
26804
- if (filter.ownership && record.ownership !== filter.ownership) {
26805
- return false;
27162
+ function resolveMidSessionLimit(raw, fallback) {
27163
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
27164
+ return fallback;
26806
27165
  }
26807
- return true;
26808
- }
26809
- function normalizeSurfacedMemoryLedgerInputs(inputs) {
26810
- const normalized = [];
26811
- const seenSignatures = /* @__PURE__ */ new Set();
26812
- for (const input of inputs) {
26813
- const entryId = normalizeSessionKey(input.entryId);
26814
- if (!entryId) {
26815
- continue;
26816
- }
26817
- const signature = [
26818
- entryId,
26819
- input.surface,
26820
- input.source,
26821
- input.ownership
26822
- ].join("\0");
26823
- if (seenSignatures.has(signature)) {
26824
- continue;
26825
- }
26826
- seenSignatures.add(signature);
26827
- normalized.push({
26828
- entryId,
26829
- surface: input.surface,
26830
- source: input.source,
26831
- ownership: input.ownership
26832
- });
27166
+ const normalized = Math.trunc(raw);
27167
+ if (normalized <= 0) {
27168
+ return fallback;
26833
27169
  }
26834
27170
  return normalized;
26835
27171
  }
26836
- function normalizeSessionSurfacedMemoryWrites(writes) {
26837
- const inputs = [];
26838
- for (const write of writes) {
26839
- for (const entryId of write.entryIds) {
26840
- inputs.push({
26841
- entryId,
26842
- surface: write.surface,
26843
- source: write.source,
26844
- ownership: write.ownership
26845
- });
26846
- }
27172
+ function resolveMidSessionSimilarityThreshold(raw) {
27173
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
27174
+ return DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD;
26847
27175
  }
26848
- return normalizeSurfacedMemoryLedgerInputs(inputs);
26849
- }
26850
- function resolveSessionSurfacedMemoryRecordProfile(profile) {
26851
- return { ...SESSION_SURFACED_MEMORY_RECORD_PROFILES[profile] };
27176
+ if (raw < 0 || raw > 1) {
27177
+ return DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD;
27178
+ }
27179
+ return raw;
26852
27180
  }
26853
- function resolveSessionSurfacedMemoryIntent(intent) {
26854
- const definition = SESSION_SURFACED_MEMORY_INTENTS[intent];
26855
- return {
26856
- intent,
26857
- writeProfile: { ...definition.writeProfile },
26858
- readSelector: SESSION_SURFACED_MEMORY_SELECTORS.intent(intent),
26859
- sharedSelectors: [...definition.read.sharedSelectors],
26860
- lifecycle: { ...definition.lifecycle }
26861
- };
27181
+ function clearSessionState() {
27182
+ seenSessions.clear();
27183
+ sessionStateGlobals.lastCleanupMs = 0;
27184
+ sessionSignalState.clear();
27185
+ handoffSeenSessionIds.clear();
27186
+ ledgerStore.ledgers.clear();
27187
+ ledgerStore.aliases.clear();
27188
+ sessionRef.current = "";
26862
27189
  }
26863
- function resolveSessionSurfacedMemorySelectorFilter(selector) {
26864
- if (selector === "dedupe-relevant" || selector === "before-reset-owned") {
26865
- return { ...SESSION_SURFACED_MEMORY_SELECTOR_FILTERS[selector] };
26866
- }
26867
- if (selector.kind === "intent") {
26868
- return resolveSessionSurfacedMemoryIntent(selector.intent).writeProfile;
27190
+
27191
+ // src/edge/openclaw/memory-index.ts
27192
+ init_inbound();
27193
+ init_errors();
27194
+ var MEMORY_INDEX_TIMEOUT_MS = 1e4;
27195
+ var MEMORY_INDEX_TIMEOUT = /* @__PURE__ */ Symbol("memory-index-timeout");
27196
+ function normalizeDate(value) {
27197
+ const raw = typeof value === "string" ? value.trim() : "";
27198
+ if (!raw) {
27199
+ return "";
26869
27200
  }
26870
- if (selector.kind === "profile") {
26871
- return resolveSessionSurfacedMemoryRecordProfile(selector.profile);
27201
+ const parsed = new Date(raw);
27202
+ if (Number.isNaN(parsed.getTime())) {
27203
+ return raw;
26872
27204
  }
26873
- return {
26874
- source: selector.source
26875
- };
27205
+ return parsed.toISOString();
26876
27206
  }
26877
- function normalizeSessionSurfacedMemoryIntentWrites(writes) {
26878
- const normalized = [];
26879
- for (const write of writes) {
26880
- normalized.push({
26881
- entryIds: write.entryIds,
26882
- ...resolveSessionSurfacedMemoryIntent(write.intent).writeProfile
26883
- });
27207
+ function toProjectName(value) {
27208
+ if (value === null || value === void 0) {
27209
+ return null;
26884
27210
  }
26885
- return normalized;
27211
+ const normalized = String(value).trim();
27212
+ return normalized.length > 0 ? normalized : null;
26886
27213
  }
26887
- function toSessionSurfacedMemoryWrites(writes) {
26888
- if (Symbol.iterator in Object(writes) && !("entryIds" in Object(writes))) {
26889
- return writes;
27214
+ function toCount(value) {
27215
+ const parsed = Number(value);
27216
+ if (!Number.isFinite(parsed) || parsed <= 0) {
27217
+ return 0;
26890
27218
  }
26891
- return [writes];
27219
+ return Math.floor(parsed);
26892
27220
  }
26893
- function toSessionSurfacedMemoryIntentWrites(writes) {
26894
- if (Symbol.iterator in Object(writes) && !("entryIds" in Object(writes))) {
26895
- return writes;
27221
+ function normalizeProjectIndexEntry(value) {
27222
+ if (!value || typeof value !== "object") {
27223
+ return null;
26896
27224
  }
26897
- return [writes];
27225
+ const row = value;
27226
+ return {
27227
+ project: toProjectName(row.project),
27228
+ count: toCount(row.count),
27229
+ lastUpdated: normalizeDate(row.lastUpdated ?? row.last_updated)
27230
+ };
26898
27231
  }
26899
- function registerSessionSurfacedMemoryAlias(store, aliasKey, sessionKey) {
26900
- const normalizedAlias = normalizeSessionKey(aliasKey);
26901
- const normalizedSessionKey = normalizeSessionKey(sessionKey);
26902
- if (!normalizedAlias || !normalizedSessionKey) {
26903
- return;
26904
- }
26905
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, normalizedSessionKey);
26906
- store.aliases.set(normalizedAlias, canonicalKey);
26907
- if (normalizedAlias === canonicalKey) {
26908
- return;
26909
- }
26910
- const aliasLedger = store.ledgers.get(normalizedAlias);
26911
- if (!aliasLedger) {
26912
- return;
27232
+ function parseMemoryIndex(value) {
27233
+ if (!value || typeof value !== "object") {
27234
+ return null;
26913
27235
  }
26914
- store.ledgers.delete(normalizedAlias);
26915
- const targetLedger = getOrCreateSurfacedMemoryLedger(store, canonicalKey);
26916
- if (!targetLedger) {
26917
- return;
27236
+ const response = value;
27237
+ if (!Array.isArray(response.projects)) {
27238
+ return null;
26918
27239
  }
26919
- const existingSignatures = new Set(
26920
- targetLedger.records.map((record) => [record.entryId, record.surface, record.source, record.ownership].join("\0"))
26921
- );
26922
- for (const record of aliasLedger.records) {
26923
- const signature = [
26924
- record.entryId,
26925
- record.surface,
26926
- record.source,
26927
- record.ownership
26928
- ].join("\0");
26929
- if (existingSignatures.has(signature)) {
26930
- continue;
27240
+ const projects = response.projects.map((entry) => normalizeProjectIndexEntry(entry)).filter((entry) => entry !== null);
27241
+ const totalFromResponse = Number(response.totalEntries);
27242
+ const totalEntries = Number.isFinite(totalFromResponse) && totalFromResponse >= 0 ? Math.floor(totalFromResponse) : projects.reduce((sum, project) => sum + project.count, 0);
27243
+ return { projects, totalEntries };
27244
+ }
27245
+ async function runMemoryIndex(dbPath, executionOptions) {
27246
+ try {
27247
+ const timeoutMs = executionOptions?.timeoutMs ?? MEMORY_INDEX_TIMEOUT_MS;
27248
+ const result = await withTimeout(
27249
+ loadMemoryIndex(dbPath, executionOptions?.recallServiceDeps),
27250
+ timeoutMs
27251
+ );
27252
+ if (result === MEMORY_INDEX_TIMEOUT) {
27253
+ return { status: "timeout" };
26931
27254
  }
26932
- existingSignatures.add(signature);
26933
- targetLedger.records.push({
26934
- ...record,
26935
- sequence: targetLedger.nextSequence++
26936
- });
27255
+ return result;
27256
+ } catch (error) {
27257
+ return {
27258
+ status: "error",
27259
+ error: toErrorMessage(error)
27260
+ };
26937
27261
  }
26938
- touchSurfacedMemoryLedger(store, canonicalKey);
26939
27262
  }
26940
- function appendSessionSurfacedMemoryLedgerEntries(store, sessionKey, inputs) {
26941
- const ledger = getOrCreateSurfacedMemoryLedger(store, sessionKey);
26942
- if (!ledger) {
26943
- return 0;
26944
- }
26945
- const normalizedInputs = normalizeSurfacedMemoryLedgerInputs(inputs);
26946
- const existingSignatures = new Set(
26947
- ledger.records.map((record) => [record.entryId, record.surface, record.source, record.ownership].join("\0"))
27263
+ async function loadMemoryIndex(dbPath, recallServiceDeps) {
27264
+ const index = parseMemoryIndex(
27265
+ await buildRecallIndex(
27266
+ { dbPath },
27267
+ { runtime: adaptLegacyRecallRuntimeDeps(recallServiceDeps) }
27268
+ )
26948
27269
  );
26949
- let addedCount = 0;
26950
- for (const input of normalizedInputs) {
26951
- const signature = [
26952
- input.entryId,
26953
- input.surface,
26954
- input.source,
26955
- input.ownership
26956
- ].join("\0");
26957
- if (existingSignatures.has(signature)) {
26958
- continue;
26959
- }
26960
- existingSignatures.add(signature);
26961
- ledger.records.push({
26962
- ...input,
26963
- sequence: ledger.nextSequence++
27270
+ if (!index) {
27271
+ return { status: "invalid" };
27272
+ }
27273
+ return {
27274
+ status: "ok",
27275
+ index
27276
+ };
27277
+ }
27278
+ async function withTimeout(promise, timeoutMs) {
27279
+ let timer;
27280
+ try {
27281
+ return await new Promise((resolve, reject) => {
27282
+ timer = setTimeout(() => {
27283
+ resolve(MEMORY_INDEX_TIMEOUT);
27284
+ }, timeoutMs);
27285
+ promise.then(resolve).catch(reject);
26964
27286
  });
26965
- addedCount += 1;
27287
+ } finally {
27288
+ if (timer) {
27289
+ clearTimeout(timer);
27290
+ }
26966
27291
  }
26967
- if (addedCount > 0) {
26968
- touchSurfacedMemoryLedger(store, resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey));
27292
+ }
27293
+ function formatMemoryIndex(index) {
27294
+ if (index.projects.length === 0) {
27295
+ return "";
26969
27296
  }
26970
- return addedCount;
27297
+ const lines = ["## Memory Index", ""];
27298
+ for (const project of index.projects) {
27299
+ const name = project.project ?? "(universal)";
27300
+ const date = project.lastUpdated ? project.lastUpdated.slice(0, 10) : "unknown";
27301
+ lines.push(`- ${name}: ${project.count} entries (last updated ${date})`);
27302
+ }
27303
+ lines.push("");
27304
+ lines.push(`Total: ${index.totalEntries} entries`);
27305
+ return lines.join("\n");
26971
27306
  }
26972
- function replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, inputs) {
26973
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
26974
- if (!canonicalKey) {
26975
- return;
27307
+
27308
+ // src/edge/openclaw/recall.ts
27309
+ var RECALL_QUERY_MAX_CHARS = 500;
27310
+ var DEFAULT_LIMIT3 = 10;
27311
+ function resolveUpdateMetadata(isBrowse, isSessionStart, requestOverrides) {
27312
+ const metadataMode = requestOverrides?.metadataMode ?? "non-browse";
27313
+ return requestOverrides?.noUpdate !== true && !isBrowse && (metadataMode === "non-browse" || metadataMode === "session-start" && isSessionStart);
27314
+ }
27315
+ function resolveRuntimeDeps(executionOptions) {
27316
+ return adaptLegacyRecallRuntimeDeps(executionOptions?.recallServiceDeps);
27317
+ }
27318
+ function resolveRecallMetadata(requestOverrides) {
27319
+ if (requestOverrides?.metadata) {
27320
+ return requestOverrides.metadata;
26976
27321
  }
26977
- const normalizedInputs = normalizeSurfacedMemoryLedgerInputs(inputs);
26978
- const ledger = {
26979
- nextSequence: 1,
26980
- records: normalizedInputs.map((input, index) => ({
26981
- ...input,
26982
- sequence: index + 1
27322
+ const sessionId = sessionRef.current.trim();
27323
+ if (!sessionId) {
27324
+ return void 0;
27325
+ }
27326
+ return {
27327
+ sessionId,
27328
+ platform: "openclaw"
27329
+ };
27330
+ }
27331
+ function buildRecallRequest(budget, project, query, options) {
27332
+ const isBrowse = options?.context === "browse";
27333
+ const isSessionStart = options?.context === "session-start";
27334
+ const trimmedQuery = query?.trim() ?? "";
27335
+ const queryText = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
27336
+ const inferredSessionStart = isSessionStart || queryText.length === 0;
27337
+ const universalOnly = options?.nullProjectOnly === true || options?.universalOnly === true;
27338
+ const explicitWildcardProject = (options?.project ?? []).some(
27339
+ (candidate) => candidate.split(",").some((part) => part.trim() === "*")
27340
+ );
27341
+ const normalizedProjects = Array.from(new Set(
27342
+ (options?.project ?? []).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0)
27343
+ ));
27344
+ const explicitProjectSelection = explicitWildcardProject || normalizedProjects.length > 0;
27345
+ const normalizedProjectHints = parseProjectList(options?.projectHints).filter((projectHint) => projectHint !== "*");
27346
+ const resolvedProject = universalOnly ? void 0 : explicitProjectSelection ? normalizedProjects.length > 0 ? normalizedProjects : void 0 : !project ? void 0 : [project];
27347
+ const resolvedProjectStrict = universalOnly ? void 0 : explicitProjectSelection ? options?.projectStrict ? true : void 0 : project && !universalOnly ? true : void 0;
27348
+ const resolvedProjectHints = universalOnly || explicitProjectSelection || explicitWildcardProject ? void 0 : normalizedProjectHints.length > 0 ? normalizedProjectHints : void 0;
27349
+ return {
27350
+ isBrowse,
27351
+ isSessionStart: !isBrowse && inferredSessionStart,
27352
+ limit: options?.limit ?? DEFAULT_LIMIT3,
27353
+ queryText,
27354
+ since: options?.since,
27355
+ types: options?.types,
27356
+ until: options?.until,
27357
+ around: options?.around,
27358
+ aroundRadius: options?.aroundRadius,
27359
+ platform: options?.platform,
27360
+ minImportance: options?.minImportance,
27361
+ expiry: options?.expiry,
27362
+ universalOnly,
27363
+ project: resolvedProject,
27364
+ projectStrict: resolvedProjectStrict,
27365
+ projectHints: resolvedProjectHints,
27366
+ budget: isBrowse ? void 0 : options?.budget === null ? void 0 : options?.budget ?? budget
27367
+ };
27368
+ }
27369
+ function shapeRecallResult(result) {
27370
+ return {
27371
+ query: result.payload.query,
27372
+ decision: result.payload.decision,
27373
+ rollupSummary: result.payload.rollup_summary,
27374
+ surfaceSelection: result.surfaceSelection,
27375
+ results: result.payload.results.map((item) => ({
27376
+ entry: item.entry,
27377
+ score: item.score,
27378
+ category: item.category
26983
27379
  }))
26984
27380
  };
26985
- ledger.nextSequence = ledger.records.length + 1;
26986
- store.ledgers.delete(canonicalKey);
26987
- store.ledgers.set(canonicalKey, ledger);
26988
- trimSurfacedMemoryLedgers(store);
26989
27381
  }
26990
- function recordSessionSurfacedMemory(store, sessionKey, writes, options = {}) {
26991
- const normalizedInputs = normalizeSessionSurfacedMemoryWrites(
26992
- toSessionSurfacedMemoryWrites(writes)
27382
+ async function runRecallStrict(budget, project, query, options, dbPath, executionOptions) {
27383
+ return executeRecallRequest(
27384
+ budget,
27385
+ project,
27386
+ query,
27387
+ options,
27388
+ dbPath,
27389
+ executionOptions
26993
27390
  );
26994
- if (normalizedInputs.length === 0) {
26995
- if (options.mode === "replace") {
26996
- replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, []);
26997
- }
26998
- return 0;
26999
- }
27000
- if (options.mode === "replace") {
27001
- replaceSessionSurfacedMemoryLedgerEntries(store, sessionKey, normalizedInputs);
27002
- return normalizedInputs.length;
27003
- }
27004
- return appendSessionSurfacedMemoryLedgerEntries(store, sessionKey, normalizedInputs);
27005
27391
  }
27006
- function recordSessionSurfacedMemoryByIntent(store, sessionKey, writes, options = {}) {
27007
- return recordSessionSurfacedMemory(
27008
- store,
27009
- sessionKey,
27010
- normalizeSessionSurfacedMemoryIntentWrites(
27011
- toSessionSurfacedMemoryIntentWrites(writes)
27012
- ),
27013
- options
27392
+ async function executeRecallRequest(budget, project, query, options, dbPath, executionOptions) {
27393
+ const request = buildRecallRequest(budget, project, query, options);
27394
+ const runtime = resolveRuntimeDeps(executionOptions);
27395
+ const updateMetadata = resolveUpdateMetadata(
27396
+ request.isBrowse,
27397
+ request.isSessionStart,
27398
+ executionOptions?.requestOverrides
27399
+ );
27400
+ const metadata = resolveRecallMetadata(executionOptions?.requestOverrides);
27401
+ const result = request.isBrowse ? await browseRecall(
27402
+ {
27403
+ dbPath,
27404
+ limit: request.limit,
27405
+ threshold: 0,
27406
+ types: request.types,
27407
+ since: request.since,
27408
+ until: request.until,
27409
+ around: request.around,
27410
+ aroundRadius: request.aroundRadius,
27411
+ platform: request.platform,
27412
+ minImportance: request.minImportance,
27413
+ expiry: request.expiry,
27414
+ project: request.project,
27415
+ projectStrict: request.projectStrict,
27416
+ projectHints: request.projectHints,
27417
+ universalOnly: request.universalOnly,
27418
+ includeRecallCounts: false
27419
+ },
27420
+ { runtime }
27421
+ ) : request.isSessionStart ? await sessionStartRecall(
27422
+ {
27423
+ dbPath,
27424
+ limit: request.limit,
27425
+ budget: request.budget,
27426
+ threshold: 0,
27427
+ types: request.types,
27428
+ since: request.since,
27429
+ until: request.until,
27430
+ around: request.around,
27431
+ aroundRadius: request.aroundRadius,
27432
+ platform: request.platform,
27433
+ minImportance: request.minImportance,
27434
+ expiry: request.expiry,
27435
+ project: request.project,
27436
+ projectStrict: request.projectStrict,
27437
+ projectHints: request.projectHints,
27438
+ universalOnly: request.universalOnly,
27439
+ updateMetadata,
27440
+ metadata,
27441
+ includeRecallCounts: false
27442
+ },
27443
+ { runtime }
27444
+ ) : await searchRecall(
27445
+ {
27446
+ dbPath,
27447
+ queryText: request.queryText,
27448
+ queryContext: "default",
27449
+ limit: request.limit,
27450
+ budget: request.budget,
27451
+ threshold: 0,
27452
+ types: request.types,
27453
+ since: request.since,
27454
+ until: request.until,
27455
+ around: request.around,
27456
+ aroundRadius: request.aroundRadius,
27457
+ platform: request.platform,
27458
+ minImportance: request.minImportance,
27459
+ expiry: request.expiry,
27460
+ project: request.project,
27461
+ projectStrict: request.projectStrict,
27462
+ projectHints: request.projectHints,
27463
+ universalOnly: request.universalOnly,
27464
+ updateMetadata,
27465
+ metadata,
27466
+ includeRecallCounts: false
27467
+ },
27468
+ { runtime }
27014
27469
  );
27470
+ return shapeRecallResult(result);
27015
27471
  }
27016
- function getSessionSurfacedMemoryLedger(store, sessionKey) {
27017
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
27018
- if (!canonicalKey) {
27019
- return [];
27020
- }
27021
- const ledger = store.ledgers.get(canonicalKey);
27022
- return ledger ? [...ledger.records] : [];
27023
- }
27024
- function getSessionSurfacedMemoryLedgerRecords(store, sessionKey, filter = {}) {
27025
- return getSessionSurfacedMemoryLedger(store, sessionKey).filter((record) => matchesSurfacedMemoryLedgerFilter(record, filter));
27026
- }
27027
- function collectSessionSurfacedMemoryEntryIds(records) {
27028
- const surfacedEntryIds = [];
27029
- const seenEntryIds = /* @__PURE__ */ new Set();
27030
- for (const record of records) {
27031
- if (seenEntryIds.has(record.entryId)) {
27032
- continue;
27033
- }
27034
- seenEntryIds.add(record.entryId);
27035
- surfacedEntryIds.push(record.entryId);
27036
- }
27037
- return surfacedEntryIds;
27038
- }
27039
- function removeSessionSurfacedMemoryLedgerEntries(store, sessionKey, filter = {}) {
27040
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, sessionKey);
27041
- if (!canonicalKey) {
27042
- return 0;
27043
- }
27044
- const ledger = store.ledgers.get(canonicalKey);
27045
- if (!ledger) {
27046
- return 0;
27047
- }
27048
- const keptRecords = ledger.records.filter((record) => !matchesSurfacedMemoryLedgerFilter(record, filter));
27049
- const removedCount = ledger.records.length - keptRecords.length;
27050
- if (removedCount === 0) {
27051
- return 0;
27052
- }
27053
- if (keptRecords.length === 0) {
27054
- clearSessionSurfacedMemoryLedger(store, sessionKey);
27055
- return removedCount;
27056
- }
27057
- ledger.records = keptRecords;
27058
- touchSurfacedMemoryLedger(store, canonicalKey);
27059
- return removedCount;
27060
- }
27061
- function clearSessionSurfacedMemoryLedger(store, sessionKey) {
27062
- const normalizedKey = normalizeSessionKey(sessionKey);
27063
- if (!normalizedKey) {
27064
- return;
27065
- }
27066
- const canonicalKey = resolveCanonicalSurfacedMemoryLedgerKey(store, normalizedKey);
27067
- if (canonicalKey) {
27068
- store.ledgers.delete(canonicalKey);
27069
- }
27070
- for (const [alias, targetKey] of store.aliases) {
27071
- if (alias === normalizedKey || targetKey === canonicalKey) {
27072
- store.aliases.delete(alias);
27073
- }
27472
+ async function runRecall(budget, project, query, options, dbPath, executionOptions) {
27473
+ try {
27474
+ return await executeRecallRequest(budget, project, query, options, dbPath, executionOptions);
27475
+ } catch {
27476
+ return null;
27074
27477
  }
27075
27478
  }
27076
- var SURFACED_MEMORY_LIFECYCLE_FILTERS = {
27077
- commandResetCleared: {
27078
- ownership: "dedupe-only"
27479
+ async function fetchCoreEntries(coreProjects, dbPath, executionOptions) {
27480
+ let result;
27481
+ try {
27482
+ result = shapeRecallResult(
27483
+ await loadCoreRecall(
27484
+ { dbPath },
27485
+ { runtime: resolveRuntimeDeps(executionOptions) }
27486
+ )
27487
+ );
27488
+ } catch {
27489
+ return null;
27079
27490
  }
27080
- };
27081
- function applySessionSurfacedMemoryCommandResetPolicy(store, sessionKey) {
27082
- return removeSessionSurfacedMemoryLedgerEntries(
27083
- store,
27084
- sessionKey,
27085
- SURFACED_MEMORY_LIFECYCLE_FILTERS.commandResetCleared
27491
+ const allowedProjects = new Set(
27492
+ coreProjects.map((project) => project.trim()).filter((project) => project.length > 0)
27086
27493
  );
27087
- }
27088
- function applySessionSurfacedMemoryBeforeResetPolicy(store, sessionKey) {
27089
- const beforeResetOwnedEntryIds = querySessionSurfacedMemory(store, sessionKey).select(SESSION_SURFACED_MEMORY_SELECTORS.beforeResetOwned).entryIds();
27090
- const clearedCount = getSessionSurfacedMemoryLedger(store, sessionKey).length;
27091
- clearSessionSurfacedMemoryLedger(store, sessionKey);
27092
- return {
27093
- beforeResetOwnedEntryIds,
27094
- clearedCount
27095
- };
27096
- }
27097
- function querySessionSurfacedMemory(store, sessionKey) {
27098
- const records = (filter = {}) => getSessionSurfacedMemoryLedgerRecords(store, sessionKey, filter);
27099
- const entryIds = (filter = {}) => collectSessionSurfacedMemoryEntryIds(records(filter));
27100
- const hasEntry = (entryId, filter = {}) => {
27101
- const normalizedEntryId = normalizeSessionKey(entryId);
27102
- if (!normalizedEntryId) {
27103
- return false;
27494
+ const filteredResults = result.results.filter((item) => {
27495
+ const rawProject = item.entry?.project;
27496
+ const project = typeof rawProject === "string" ? rawProject.trim() : "";
27497
+ if (!project) {
27498
+ return true;
27104
27499
  }
27105
- return records(filter).some((record) => record.entryId === normalizedEntryId);
27106
- };
27107
- const select = (selector) => {
27108
- const filter = resolveSessionSurfacedMemorySelectorFilter(selector);
27109
- return {
27110
- records: () => records(filter),
27111
- entryIds: () => entryIds(filter),
27112
- hasEntry: (entryId) => hasEntry(entryId, filter)
27113
- };
27114
- };
27500
+ return allowedProjects.has(project);
27501
+ });
27115
27502
  return {
27116
- records,
27117
- entryIds,
27118
- hasEntry,
27119
- select,
27120
- dedupeEntryIds() {
27121
- return select(SESSION_SURFACED_MEMORY_SELECTORS.dedupeRelevant).entryIds();
27122
- },
27123
- hasDedupeEntry(entryId) {
27124
- return select(SESSION_SURFACED_MEMORY_SELECTORS.dedupeRelevant).hasEntry(entryId);
27125
- },
27126
- beforeResetOwnedEntryIds() {
27127
- return select(SESSION_SURFACED_MEMORY_SELECTORS.beforeResetOwned).entryIds();
27128
- },
27129
- clearDedupeOnly() {
27130
- return removeSessionSurfacedMemoryLedgerEntries(store, sessionKey, {
27131
- ownership: "dedupe-only"
27132
- });
27133
- }
27503
+ ...result,
27504
+ results: filteredResults.slice(0, 30)
27134
27505
  };
27135
27506
  }
27136
27507
 
27137
- // src/edge/openclaw/session-state.ts
27138
- var SKIP_SESSION_PATTERNS = [":subagent:", ":cron:"];
27139
- var DEFAULT_MAX_SEEN_SESSIONS = 1e3;
27140
- var DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD = 0.85;
27141
- var SEEN_SESSION_RETENTION_MS = 24 * 60 * 60 * 1e3;
27142
- var CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
27143
- var sessionStartLog = createLogger("session-start");
27144
- var SESSION_STATE_GLOBALS_KEY = "__agenrOpenClawSessionState";
27145
- function getSessionStateGlobals() {
27146
- const globalState = globalThis;
27147
- globalState[SESSION_STATE_GLOBALS_KEY] ??= {
27148
- seenSessions: /* @__PURE__ */ new Map(),
27149
- lastCleanupMs: 0,
27150
- surfacedMemoryLedgerStore: {
27151
- ledgers: /* @__PURE__ */ new Map(),
27152
- aliases: /* @__PURE__ */ new Map()
27153
- },
27154
- sessionSignalState: /* @__PURE__ */ new Map(),
27155
- handoffSeenSessionIds: /* @__PURE__ */ new Set(),
27156
- sessionRef: { current: "" }
27157
- };
27158
- return globalState[SESSION_STATE_GLOBALS_KEY];
27159
- }
27160
- var sessionStateGlobals = getSessionStateGlobals();
27161
- var seenSessions = sessionStateGlobals.seenSessions;
27162
- var ledgerStore = sessionStateGlobals.surfacedMemoryLedgerStore;
27163
- var sessionRef = sessionStateGlobals.sessionRef;
27164
- var sessionSignalState = sessionStateGlobals.sessionSignalState;
27165
- var handoffSeenSessionIds = sessionStateGlobals.handoffSeenSessionIds;
27166
- function registerSessionSurfacedMemoryAlias2(aliasKey, sessionKey) {
27167
- registerSessionSurfacedMemoryAlias(ledgerStore, aliasKey, sessionKey);
27168
- }
27169
- function recordSessionSurfacedMemoryByIntent2(sessionKey, writes, options = {}) {
27170
- return recordSessionSurfacedMemoryByIntent(ledgerStore, sessionKey, writes, options);
27171
- }
27172
- function querySessionSurfacedMemory2(sessionKey) {
27173
- return querySessionSurfacedMemory(ledgerStore, sessionKey);
27174
- }
27175
- function applySessionSurfacedMemoryCommandResetPolicy2(sessionKey) {
27176
- return applySessionSurfacedMemoryCommandResetPolicy(ledgerStore, sessionKey);
27177
- }
27178
- function applySessionSurfacedMemoryBeforeResetPolicy2(sessionKey) {
27179
- return applySessionSurfacedMemoryBeforeResetPolicy(ledgerStore, sessionKey);
27180
- }
27181
- function clearSessionSurfacedMemoryLedger2(sessionKey) {
27182
- clearSessionSurfacedMemoryLedger(ledgerStore, sessionKey);
27183
- }
27184
- function hasHandoffSession(sessionId) {
27185
- return handoffSeenSessionIds.has(sessionId);
27186
- }
27187
- function markHandoffSession(sessionId) {
27188
- handoffSeenSessionIds.add(sessionId);
27189
- }
27190
- function deleteHandoffSession(sessionId) {
27191
- handoffSeenSessionIds.delete(sessionId);
27192
- }
27193
- function resolveMaxSeenSessions() {
27194
- const raw = process.env.AGENR_OPENCLAW_MAX_SEEN_SESSIONS;
27195
- if (!raw) {
27196
- return DEFAULT_MAX_SEEN_SESSIONS;
27508
+ // src/edge/openclaw/session-query.ts
27509
+ init_errors();
27510
+ init_guards();
27511
+ import { createReadStream as createReadStream2 } from "fs";
27512
+ import { readdir, stat } from "fs/promises";
27513
+ import path10 from "path";
27514
+ import { createInterface as createInterface2 } from "readline";
27515
+ var EXCHANGE_TEXT_MAX_CHARS = 200;
27516
+ var EXCHANGE_USER_TURN_LIMIT = 5;
27517
+ var RECENT_TURN_MAX_CHARS = 300;
27518
+ var DEFAULT_RECENT_TURN_LIMIT = 7;
27519
+ var SESSION_TRANSCRIPT_ARTIFACT_MARKERS = [".jsonl.reset.", ".jsonl.deleted."];
27520
+ function extractTextFromRoleMessage(message, role, separator) {
27521
+ if (!isRecord(message) || message["role"] !== role) {
27522
+ return "";
27197
27523
  }
27198
- const parsed = Number.parseInt(raw, 10);
27199
- if (Number.isFinite(parsed) && parsed > 0) {
27200
- return parsed;
27524
+ const content = message["content"];
27525
+ if (typeof content === "string") {
27526
+ return content.trim();
27201
27527
  }
27202
- return DEFAULT_MAX_SEEN_SESSIONS;
27203
- }
27204
- function canExecuteSql(db) {
27205
- return typeof db?.execute === "function";
27206
- }
27207
- function trimSeenSessionCache() {
27208
- const maxSeenSessions = resolveMaxSeenSessions();
27209
- while (seenSessions.size > maxSeenSessions) {
27210
- const oldestKey = seenSessions.keys().next().value;
27211
- if (oldestKey === void 0) {
27212
- break;
27528
+ if (!Array.isArray(content)) {
27529
+ return "";
27530
+ }
27531
+ const textParts = [];
27532
+ for (const part of content) {
27533
+ if (!isRecord(part) || part["type"] !== "text") {
27534
+ continue;
27535
+ }
27536
+ const partText = part["text"];
27537
+ if (typeof partText !== "string") {
27538
+ continue;
27539
+ }
27540
+ const trimmed = partText.trim();
27541
+ if (trimmed) {
27542
+ textParts.push(trimmed);
27213
27543
  }
27214
- seenSessions.delete(oldestKey);
27215
27544
  }
27545
+ return textParts.join(separator).trim();
27216
27546
  }
27217
- function touchSeenSessionCache(sessionKey) {
27218
- seenSessions.delete(sessionKey);
27219
- seenSessions.set(sessionKey, true);
27220
- trimSeenSessionCache();
27221
- }
27222
- async function hasSeenSessionInDb(db, sessionKey) {
27223
- const result = await db.execute({
27224
- sql: "SELECT 1 FROM seen_sessions WHERE dedupe_key = ? AND seen_at >= ? LIMIT 1",
27225
- args: [sessionKey, Date.now() - SEEN_SESSION_RETENTION_MS]
27226
- });
27227
- return result.rows.length > 0;
27547
+ function extractTextFromUserMessage(message) {
27548
+ return extractTextFromRoleMessage(message, "user", " ");
27228
27549
  }
27229
- async function cleanupSeenSessionsInDb(db, nowMs) {
27230
- const maxSeenSessions = resolveMaxSeenSessions();
27231
- await db.execute({
27232
- sql: "DELETE FROM seen_sessions WHERE seen_at < ?",
27233
- args: [nowMs - SEEN_SESSION_RETENTION_MS]
27234
- });
27235
- await db.execute({
27236
- sql: `
27237
- DELETE FROM seen_sessions
27238
- WHERE dedupe_key IN (
27239
- SELECT dedupe_key
27240
- FROM seen_sessions
27241
- ORDER BY seen_at DESC, dedupe_key DESC
27242
- LIMIT -1 OFFSET ?
27243
- )
27244
- `,
27245
- args: [maxSeenSessions]
27246
- });
27550
+ function extractTextFromAssistantMessage(message) {
27551
+ return extractTextFromRoleMessage(message, "assistant", " ");
27247
27552
  }
27248
- function shouldSkipSession(sessionKey) {
27249
- return SKIP_SESSION_PATTERNS.some((pattern) => sessionKey.includes(pattern));
27553
+ function truncateMessageText(text, maxChars = EXCHANGE_TEXT_MAX_CHARS) {
27554
+ return text.length > maxChars ? text.slice(0, maxChars) : text;
27250
27555
  }
27251
- async function hasSeenSession(sessionKey, db) {
27252
- let seen = seenSessions.has(sessionKey);
27253
- if (seen) {
27254
- touchSeenSessionCache(sessionKey);
27255
- sessionStartLog.debug(
27256
- `hasSeenSession key=${sessionKey.slice(0, 12)} found=${seen} mapSize=${seenSessions.size}`
27257
- );
27258
- return true;
27556
+ function stripPromptMetadata(raw) {
27557
+ if (!raw) {
27558
+ return "";
27259
27559
  }
27260
- if (canExecuteSql(db)) {
27261
- try {
27262
- seen = await hasSeenSessionInDb(db, sessionKey);
27263
- if (seen) {
27264
- touchSeenSessionCache(sessionKey);
27265
- }
27266
- } catch (err) {
27267
- sessionStartLog.warn(`hasSeenSession db lookup failed: ${toErrorMessage(err)}`);
27560
+ try {
27561
+ const pattern = /\[\w{3} \d{4}-\d{2}-\d{2} \d{2}:\d{2} (?:[A-Z]{2,5}|GMT[+-]\d{1,2})\] /g;
27562
+ let lastMatchEnd = -1;
27563
+ let match = null;
27564
+ while ((match = pattern.exec(raw)) !== null) {
27565
+ lastMatchEnd = match.index + match[0].length;
27268
27566
  }
27567
+ if (lastMatchEnd >= 0) {
27568
+ return raw.slice(lastMatchEnd).trim();
27569
+ }
27570
+ return raw.trim();
27571
+ } catch {
27572
+ return "";
27269
27573
  }
27270
- sessionStartLog.debug(
27271
- `hasSeenSession key=${sessionKey.slice(0, 12)} found=${seen} mapSize=${seenSessions.size}`
27272
- );
27273
- return seen;
27274
27574
  }
27275
- async function markSessionSeen(sessionKey, db) {
27276
- touchSeenSessionCache(sessionKey);
27277
- if (!canExecuteSql(db)) {
27278
- return;
27279
- }
27280
- const nowMs = Date.now();
27575
+ function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT) {
27281
27576
  try {
27282
- await db.execute({
27283
- sql: "INSERT OR REPLACE INTO seen_sessions (dedupe_key, seen_at) VALUES (?, ?)",
27284
- args: [sessionKey, nowMs]
27285
- });
27286
- if (nowMs - sessionStateGlobals.lastCleanupMs > CLEANUP_INTERVAL_MS) {
27287
- await cleanupSeenSessionsInDb(db, nowMs);
27288
- sessionStateGlobals.lastCleanupMs = nowMs;
27577
+ if (!Array.isArray(messages) || messages.length === 0) {
27578
+ return "";
27289
27579
  }
27290
- } catch (err) {
27291
- sessionStartLog.warn(`markSessionSeen db write failed: ${toErrorMessage(err)}`);
27580
+ if (maxTurns <= 0) {
27581
+ return "";
27582
+ }
27583
+ const collected = [];
27584
+ let collectedUserTurns = 0;
27585
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
27586
+ const message = messages[index];
27587
+ const userText = sanitizeTranscriptTurnText(extractTextFromUserMessage(message));
27588
+ if (userText) {
27589
+ collected.push({
27590
+ role: "user",
27591
+ text: truncateMessageText(userText.replace(/\s+/g, " ").trim())
27592
+ });
27593
+ collectedUserTurns += 1;
27594
+ if (collectedUserTurns >= maxTurns) {
27595
+ break;
27596
+ }
27597
+ continue;
27598
+ }
27599
+ const assistantText = sanitizeTranscriptTurnText(extractTextFromAssistantMessage(message));
27600
+ if (assistantText) {
27601
+ collected.push({
27602
+ role: "assistant",
27603
+ text: truncateMessageText(assistantText.replace(/\s+/g, " ").trim())
27604
+ });
27605
+ }
27606
+ }
27607
+ if (collected.length === 0) {
27608
+ return "";
27609
+ }
27610
+ return collected.reverse().map((turn) => `${turn.role === "user" ? "U" : "A"}: ${turn.text}`).join(" | ");
27611
+ } catch {
27612
+ return "";
27292
27613
  }
27293
27614
  }
27294
- function resolveMidSessionLimit(raw, fallback) {
27295
- if (typeof raw !== "number" || !Number.isFinite(raw)) {
27296
- return fallback;
27297
- }
27298
- const normalized = Math.trunc(raw);
27299
- if (normalized <= 0) {
27300
- return fallback;
27615
+ async function listSessionFileCandidates(sessionsDir, currentSessionId, logger) {
27616
+ try {
27617
+ const normalizedSessionId = currentSessionId?.trim();
27618
+ const entries = await readdir(sessionsDir, { withFileTypes: true });
27619
+ const candidatePaths = [];
27620
+ for (const entry of entries) {
27621
+ if (!entry.isFile()) {
27622
+ continue;
27623
+ }
27624
+ const isPlainJsonl = entry.name.endsWith(".jsonl");
27625
+ const isTranscriptArtifact = SESSION_TRANSCRIPT_ARTIFACT_MARKERS.some((marker) => entry.name.includes(marker));
27626
+ if (!isPlainJsonl && !isTranscriptArtifact) {
27627
+ continue;
27628
+ }
27629
+ if (entry.name.includes(".deleted.") && !entry.name.includes(".jsonl.deleted.")) {
27630
+ continue;
27631
+ }
27632
+ if (normalizedSessionId && entry.name.startsWith(normalizedSessionId)) {
27633
+ continue;
27634
+ }
27635
+ candidatePaths.push(path10.join(sessionsDir, entry.name));
27636
+ }
27637
+ if (candidatePaths.length === 0) {
27638
+ return [];
27639
+ }
27640
+ const statResults = await Promise.all(
27641
+ candidatePaths.map(async (filePath) => {
27642
+ try {
27643
+ const fileStats = await stat(filePath);
27644
+ return { filePath, mtimeMs: fileStats.mtimeMs };
27645
+ } catch {
27646
+ return null;
27647
+ }
27648
+ })
27649
+ );
27650
+ const candidates = statResults.filter(
27651
+ (result) => result !== null
27652
+ );
27653
+ if (candidates.length === 0) {
27654
+ return [];
27655
+ }
27656
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
27657
+ return candidates;
27658
+ } catch (err) {
27659
+ logger?.debug?.(
27660
+ `listSessionFileCandidates: failed to read sessions dir "${sessionsDir}": ${toErrorMessage(err)}`
27661
+ );
27662
+ return [];
27301
27663
  }
27302
- return normalized;
27303
27664
  }
27304
- function resolveMidSessionSimilarityThreshold(raw) {
27305
- if (typeof raw !== "number" || !Number.isFinite(raw)) {
27306
- return DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD;
27307
- }
27308
- if (raw < 0 || raw > 1) {
27309
- return DEFAULT_MID_SESSION_QUERY_SIMILARITY_THRESHOLD;
27665
+ async function extractRecentTurnObjects(filePath, maxTurns = DEFAULT_RECENT_TURN_LIMIT) {
27666
+ try {
27667
+ if (!filePath) {
27668
+ return [];
27669
+ }
27670
+ const parsedMaxTurns = Number.isFinite(maxTurns) ? Math.max(0, Math.trunc(maxTurns)) : 0;
27671
+ if (parsedMaxTurns === 0) {
27672
+ return [];
27673
+ }
27674
+ const allTurns = [];
27675
+ await new Promise((resolve, reject) => {
27676
+ const rl = createInterface2({
27677
+ input: createReadStream2(filePath, { encoding: "utf8" }),
27678
+ crlfDelay: Infinity
27679
+ });
27680
+ rl.on("line", (line) => {
27681
+ const trimmed = line.trim();
27682
+ if (!trimmed) {
27683
+ return;
27684
+ }
27685
+ let record;
27686
+ try {
27687
+ record = JSON.parse(trimmed);
27688
+ } catch {
27689
+ return;
27690
+ }
27691
+ if (!isRecord(record) || record["type"] !== "message") {
27692
+ return;
27693
+ }
27694
+ const message = record["message"];
27695
+ const messageTimestamp = isRecord(message) && typeof message["timestamp"] === "string" ? message["timestamp"] : void 0;
27696
+ const timestamp2 = typeof record["timestamp"] === "string" ? record["timestamp"] : messageTimestamp;
27697
+ const userText = extractTextFromRoleMessage(message, "user", "\n");
27698
+ if (userText) {
27699
+ allTurns.push({ role: "user", text: userText, timestamp: timestamp2 });
27700
+ return;
27701
+ }
27702
+ const assistantText = extractTextFromRoleMessage(message, "assistant", "\n");
27703
+ if (assistantText) {
27704
+ allTurns.push({ role: "assistant", text: assistantText, timestamp: timestamp2 });
27705
+ }
27706
+ });
27707
+ rl.on("close", resolve);
27708
+ rl.on("error", reject);
27709
+ });
27710
+ const sanitizedTurns = sanitizeTranscriptTurns(allTurns, {
27711
+ maxCharsPerTurn: RECENT_TURN_MAX_CHARS
27712
+ });
27713
+ return sanitizedTurns.slice(-parsedMaxTurns);
27714
+ } catch {
27715
+ return [];
27310
27716
  }
27311
- return raw;
27312
- }
27313
- function clearSessionState() {
27314
- seenSessions.clear();
27315
- sessionStateGlobals.lastCleanupMs = 0;
27316
- sessionSignalState.clear();
27317
- handoffSeenSessionIds.clear();
27318
- ledgerStore.ledgers.clear();
27319
- ledgerStore.aliases.clear();
27320
- sessionRef.current = "";
27321
27717
  }
27322
27718
 
27323
27719
  // src/modules/ingestion/application/extract-text.ts
@@ -29338,6 +29734,16 @@ function resolveWarn(pluginConfig) {
29338
29734
  log8.warn(message);
29339
29735
  };
29340
29736
  }
29737
+ function resolveLiveRecallWriteMetadata() {
29738
+ const sessionId = sessionRef.current.trim();
29739
+ if (!sessionId) {
29740
+ return void 0;
29741
+ }
29742
+ return {
29743
+ sessionId,
29744
+ platform: "openclaw"
29745
+ };
29746
+ }
29341
29747
  function buildStoreToolReceipt(decisions, fallbackCounts, warningCount) {
29342
29748
  const useDecisionCounts = decisions.length > 0;
29343
29749
  const countByAction = (action) => decisions.filter((decision) => decision.action === action).length;
@@ -29431,7 +29837,12 @@ async function runRecallTool(params, defaultProject, dbPath) {
29431
29837
  const recalledEntryIds = result.results.map((item) => item.entry?.id).filter((entryId) => typeof entryId === "string" && entryId.length > 0);
29432
29838
  if (recalledEntryIds.length > 0) {
29433
29839
  try {
29434
- enqueueRecallEventWrite(recalledEntryIds, /* @__PURE__ */ new Date(), void 0, dbPath);
29840
+ enqueueRecallEventWrite(
29841
+ recalledEntryIds,
29842
+ /* @__PURE__ */ new Date(),
29843
+ resolveLiveRecallWriteMetadata(),
29844
+ dbPath
29845
+ );
29435
29846
  } catch (error) {
29436
29847
  log8.warn(`failed to enqueue recall event write: ${toErrorMessage(error)}`);
29437
29848
  }