@agenr/openclaw-plugin 0.12.1 → 0.12.2

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
@@ -693,6 +693,202 @@ var init_definitions = __esm({
693
693
  }
694
694
  });
695
695
 
696
+ // src/db/vector-index.ts
697
+ function toNumber(value) {
698
+ if (typeof value === "number") {
699
+ return value;
700
+ }
701
+ if (typeof value === "bigint") {
702
+ return Number(value);
703
+ }
704
+ if (typeof value === "string" && value.trim()) {
705
+ return Number(value);
706
+ }
707
+ return Number.NaN;
708
+ }
709
+ function toFiniteCount(value) {
710
+ const numeric = toNumber(value);
711
+ if (!Number.isFinite(numeric)) {
712
+ return 0;
713
+ }
714
+ return Math.max(0, Math.trunc(numeric));
715
+ }
716
+ function normalizeRowid(value) {
717
+ const numeric = toNumber(value);
718
+ if (!Number.isFinite(numeric)) {
719
+ return null;
720
+ }
721
+ const rowid = Math.trunc(numeric);
722
+ return rowid > 0 ? rowid : null;
723
+ }
724
+ function formatShadowMismatch(stats) {
725
+ const details = [`active=${stats.embeddingCount}`, `shadow=${stats.shadowCount}`];
726
+ if (stats.missingRowids.length > 0) {
727
+ details.push(`missing=${stats.missingRowids.length}`);
728
+ }
729
+ return details.join(", ");
730
+ }
731
+ async function rollbackQuietly(db) {
732
+ try {
733
+ await db.execute("ROLLBACK");
734
+ } catch {
735
+ }
736
+ }
737
+ async function dropRepairTableQuietly(db) {
738
+ try {
739
+ await db.execute(`DROP TABLE IF EXISTS ${TEMP_REPAIR_TABLE}`);
740
+ } catch {
741
+ }
742
+ }
743
+ async function repairMissingRowids(db, missingRowids) {
744
+ if (missingRowids.length === 0) {
745
+ return;
746
+ }
747
+ await db.execute(`DROP TABLE IF EXISTS ${TEMP_REPAIR_TABLE}`);
748
+ await db.execute(`
749
+ CREATE TEMP TABLE ${TEMP_REPAIR_TABLE} (
750
+ rid INTEGER PRIMARY KEY,
751
+ emb BLOB NOT NULL
752
+ )
753
+ `);
754
+ try {
755
+ for (let index = 0; index < missingRowids.length; index += REPAIR_CHUNK_SIZE) {
756
+ const chunk = missingRowids.slice(index, index + REPAIR_CHUNK_SIZE);
757
+ if (chunk.length === 0) {
758
+ continue;
759
+ }
760
+ const placeholders = chunk.map(() => "?").join(", ");
761
+ await db.execute(`DELETE FROM ${TEMP_REPAIR_TABLE}`);
762
+ await db.execute({
763
+ sql: `
764
+ INSERT INTO ${TEMP_REPAIR_TABLE} (rid, emb)
765
+ SELECT rowid, embedding
766
+ FROM entries
767
+ WHERE rowid IN (${placeholders})
768
+ `,
769
+ args: chunk
770
+ });
771
+ await db.execute({
772
+ sql: `
773
+ UPDATE entries
774
+ SET embedding = NULL
775
+ WHERE rowid IN (${placeholders})
776
+ `,
777
+ args: chunk
778
+ });
779
+ await db.execute({
780
+ sql: `
781
+ UPDATE entries
782
+ SET embedding = (
783
+ SELECT emb
784
+ FROM ${TEMP_REPAIR_TABLE}
785
+ WHERE rid = entries.rowid
786
+ )
787
+ WHERE rowid IN (${placeholders})
788
+ `,
789
+ args: chunk
790
+ });
791
+ }
792
+ } finally {
793
+ await dropRepairTableQuietly(db);
794
+ }
795
+ }
796
+ async function getVectorIndexShadowStats(db) {
797
+ const countsResult = await db.execute(`
798
+ SELECT
799
+ (SELECT COUNT(*) FROM entries WHERE ${ACTIVE_EMBEDDED_WHERE_SQL}) AS embedding_count,
800
+ (SELECT COUNT(*) FROM idx_entries_embedding_shadow) AS shadow_count
801
+ `);
802
+ const countsRow = countsResult.rows[0];
803
+ const embeddingCount = toFiniteCount(countsRow?.embedding_count);
804
+ const shadowCount = toFiniteCount(countsRow?.shadow_count);
805
+ const missingResult = await db.execute(`
806
+ SELECT e.rowid AS rowid
807
+ FROM entries AS e
808
+ LEFT JOIN idx_entries_embedding_shadow AS s
809
+ ON s.index_key = e.rowid
810
+ WHERE e.embedding IS NOT NULL
811
+ AND e.retired = 0
812
+ AND e.superseded_by IS NULL
813
+ AND s.index_key IS NULL
814
+ ORDER BY e.rowid ASC
815
+ `);
816
+ const missingRowids = missingResult.rows.map((row) => normalizeRowid(row.rowid)).filter((rowid) => rowid !== null);
817
+ return {
818
+ embeddingCount,
819
+ shadowCount,
820
+ missingRowids
821
+ };
822
+ }
823
+ async function rebuildVectorIndex(db, options) {
824
+ const start = Date.now();
825
+ const onLog = options?.onLog ?? (() => void 0);
826
+ await db.execute("BEGIN IMMEDIATE");
827
+ try {
828
+ await db.execute("DROP INDEX IF EXISTS idx_entries_embedding");
829
+ await db.execute(CREATE_IDX_ENTRIES_EMBEDDING_SQL);
830
+ await db.execute(SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL);
831
+ const beforeRepair = await getVectorIndexShadowStats(db);
832
+ let repairedCount = 0;
833
+ if (beforeRepair.missingRowids.length > 0) {
834
+ repairedCount = beforeRepair.missingRowids.length;
835
+ onLog(`[vector-index] repairing ${repairedCount} entries missing from shadow table`);
836
+ await repairMissingRowids(db, beforeRepair.missingRowids);
837
+ }
838
+ const afterRepair = await getVectorIndexShadowStats(db);
839
+ if (afterRepair.missingRowids.length > 0 || afterRepair.shadowCount !== afterRepair.embeddingCount) {
840
+ throw new Error(`Vector index shadow table desynced after rebuild (${formatShadowMismatch(afterRepair)})`);
841
+ }
842
+ if (afterRepair.embeddingCount > 0) {
843
+ const verify = await db.execute(`
844
+ SELECT count(*) AS count
845
+ FROM vector_top_k(
846
+ 'idx_entries_embedding',
847
+ (SELECT embedding FROM entries WHERE ${ACTIVE_EMBEDDED_WHERE_SQL} LIMIT 1),
848
+ 1
849
+ )
850
+ `);
851
+ const verifyCount = toFiniteCount(verify.rows[0]?.count);
852
+ if (verifyCount !== 1) {
853
+ throw new Error(`Vector index rebuild verification failed (expected 1, got ${verifyCount})`);
854
+ }
855
+ }
856
+ await db.execute("COMMIT");
857
+ const durationMs = Date.now() - start;
858
+ onLog(
859
+ `[vector-index] rebuilt for ${afterRepair.embeddingCount} entries (${afterRepair.shadowCount} shadow rows, ${repairedCount} repaired, ${durationMs}ms)`
860
+ );
861
+ return {
862
+ embeddingCount: afterRepair.embeddingCount,
863
+ shadowCount: afterRepair.shadowCount,
864
+ repairedCount,
865
+ durationMs
866
+ };
867
+ } catch (error) {
868
+ await rollbackQuietly(db);
869
+ throw error;
870
+ }
871
+ }
872
+ var ACTIVE_EMBEDDED_WHERE_SQL, SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL, TEMP_REPAIR_TABLE, REPAIR_CHUNK_SIZE;
873
+ var init_vector_index = __esm({
874
+ "src/db/vector-index.ts"() {
875
+ "use strict";
876
+ init_definitions();
877
+ ACTIVE_EMBEDDED_WHERE_SQL = `
878
+ embedding IS NOT NULL
879
+ AND retired = 0
880
+ AND superseded_by IS NULL
881
+ `;
882
+ SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL = `
883
+ UPDATE entries
884
+ SET embedding = embedding
885
+ WHERE ${ACTIVE_EMBEDDED_WHERE_SQL}
886
+ `;
887
+ TEMP_REPAIR_TABLE = "_vec_repair";
888
+ REPAIR_CHUNK_SIZE = 100;
889
+ }
890
+ });
891
+
696
892
  // src/db/schema/fts.ts
697
893
  async function rebuildEntriesFts(db) {
698
894
  await db.execute("INSERT INTO entries_fts(entries_fts) VALUES('delete-all')");
@@ -727,31 +923,16 @@ async function rebuildFtsAndTriggers(db) {
727
923
  throw error;
728
924
  }
729
925
  }
730
- async function rebuildVectorIndex(db) {
926
+ async function rebuildVectorIndex2(db) {
731
927
  try {
732
928
  await db.execute("REINDEX idx_entries_embedding");
733
- return;
734
- } catch {
735
- }
736
- await db.execute("BEGIN IMMEDIATE");
737
- try {
738
- await db.execute("DROP INDEX IF EXISTS idx_entries_embedding");
739
- await db.execute(CREATE_IDX_ENTRIES_EMBEDDING_SQL);
740
- await db.execute(`
741
- UPDATE entries
742
- SET embedding = embedding
743
- WHERE embedding IS NOT NULL
744
- AND retired = 0
745
- AND superseded_by IS NULL
746
- `);
747
- await db.execute("COMMIT");
748
- } catch (fallbackError) {
749
- try {
750
- await db.execute("ROLLBACK");
751
- } catch {
929
+ const stats = await getVectorIndexShadowStats(db);
930
+ if (stats.shadowCount === stats.embeddingCount && stats.missingRowids.length === 0) {
931
+ return;
752
932
  }
753
- throw fallbackError;
933
+ } catch {
754
934
  }
935
+ await rebuildVectorIndex(db);
755
936
  }
756
937
  async function clearBulkIngestMeta(db) {
757
938
  await db.execute({ sql: "DELETE FROM _meta WHERE key = ?", args: [BULK_INGEST_META_KEY] });
@@ -777,6 +958,7 @@ var BULK_INGEST_META_KEY;
777
958
  var init_bulk_ingest = __esm({
778
959
  "src/db/schema/bulk-ingest.ts"() {
779
960
  "use strict";
961
+ init_vector_index();
780
962
  init_definitions();
781
963
  init_fts();
782
964
  BULK_INGEST_META_KEY = "bulk_ingest_state";
@@ -1358,15 +1540,10 @@ async function repairVectorIndexIfNeeded(client) {
1358
1540
  if (probeCount > 0) {
1359
1541
  return;
1360
1542
  }
1361
- await client.execute("DROP INDEX IF EXISTS idx_entries_embedding");
1362
- await client.execute(CREATE_IDX_ENTRIES_EMBEDDING_SQL);
1363
- await client.execute(`
1364
- UPDATE entries
1365
- SET embedding = embedding
1366
- WHERE embedding IS NOT NULL
1367
- AND retired = 0
1368
- AND superseded_by IS NULL
1369
- `);
1543
+ } catch {
1544
+ }
1545
+ try {
1546
+ await rebuildVectorIndex(client);
1370
1547
  } catch {
1371
1548
  }
1372
1549
  }
@@ -1395,6 +1572,7 @@ var init_init = __esm({
1395
1572
  init_version();
1396
1573
  init_reflection_importance_cap();
1397
1574
  init_reflection_removal();
1575
+ init_vector_index();
1398
1576
  init_definitions();
1399
1577
  init_entry_provenance();
1400
1578
  init_fts();
@@ -1596,7 +1774,7 @@ async function checkAndRecoverBulkIngest(client) {
1596
1774
  }
1597
1775
  }
1598
1776
  if (indexCount < 1) {
1599
- await rebuildVectorIndex(client);
1777
+ await rebuildVectorIndex2(client);
1600
1778
  }
1601
1779
  try {
1602
1780
  const integrityResult = await client.execute("PRAGMA integrity_check");
@@ -4428,7 +4606,7 @@ var init_source_classification = __esm({
4428
4606
  });
4429
4607
 
4430
4608
  // src/utils/entry-utils.ts
4431
- function toNumber(value) {
4609
+ function toNumber2(value) {
4432
4610
  if (typeof value === "number") {
4433
4611
  return value;
4434
4612
  }
@@ -4537,10 +4715,10 @@ function mapRawStoredEntry(row, options) {
4537
4715
  const entryKind = toStringValue(row.entry_kind).trim();
4538
4716
  const temporalClass = toStringValue(row.temporal_class).trim();
4539
4717
  const scopeRaw = toStringValue(row.scope).trim();
4540
- const importanceRaw = toNumber(row.importance);
4541
- const confirmationsRaw = toNumber(row.confirmations);
4542
- const contradictionsRaw = toNumber(row.contradictions);
4543
- const qualityScoreRaw = toNumber(row.quality_score);
4718
+ const importanceRaw = toNumber2(row.importance);
4719
+ const confirmationsRaw = toNumber2(row.confirmations);
4720
+ const contradictionsRaw = toNumber2(row.contradictions);
4721
+ const qualityScoreRaw = toNumber2(row.quality_score);
4544
4722
  const subjectEntity = toStringValue(row.subject_entity).trim();
4545
4723
  const subjectAttribute = toStringValue(row.subject_attribute).trim();
4546
4724
  const subjectKey2 = toStringValue(row.subject_key).trim();
@@ -4548,8 +4726,8 @@ function mapRawStoredEntry(row, options) {
4548
4726
  const claimObject = toStringValue(row.claim_object).trim();
4549
4727
  const claimRole = toStringValue(row.claim_role).trim();
4550
4728
  const normalizedClaimRole = normalizeClaimRole2(claimRole);
4551
- const claimConfidenceRaw = toNumber(row.claim_confidence);
4552
- const retiredRaw = toNumber(row.retired);
4729
+ const claimConfidenceRaw = toNumber2(row.claim_confidence);
4730
+ const retiredRaw = toNumber2(row.retired);
4553
4731
  const clusterId = toStringValue(row.cluster_id).trim();
4554
4732
  const sourceClass = normalizeSourceClass(toStringValue(row.source_class));
4555
4733
  const suppressedContextsRaw = toStringValue(row.suppressed_contexts);
@@ -4752,14 +4930,14 @@ function normalizeEntryIds(entryIds) {
4752
4930
  );
4753
4931
  }
4754
4932
  function parseCount(value) {
4755
- const parsed = toNumber(value);
4933
+ const parsed = toNumber2(value);
4756
4934
  if (!Number.isFinite(parsed) || parsed <= 0) {
4757
4935
  return 0;
4758
4936
  }
4759
4937
  return Math.floor(parsed);
4760
4938
  }
4761
4939
  function parseSignalTotal(value) {
4762
- const parsed = toNumber(value);
4940
+ const parsed = toNumber2(value);
4763
4941
  if (!Number.isFinite(parsed) || parsed <= 0) {
4764
4942
  return 0;
4765
4943
  }
@@ -4773,7 +4951,7 @@ function parseIsoTimestamp(value) {
4773
4951
  return normalized.length > 0 ? normalized : null;
4774
4952
  }
4775
4953
  function parseGapDays(value) {
4776
- const parsed = toNumber(value);
4954
+ const parsed = toNumber2(value);
4777
4955
  if (!Number.isFinite(parsed) || parsed <= 0) {
4778
4956
  return 0;
4779
4957
  }
@@ -4979,7 +5157,7 @@ async function getStoredEntryById(db, id) {
4979
5157
  }
4980
5158
  async function getTotalEntries(db) {
4981
5159
  const result = await db.execute("SELECT COUNT(*) AS count FROM entries");
4982
- const count = toNumber(result.rows[0]?.count);
5160
+ const count = toNumber2(result.rows[0]?.count);
4983
5161
  return Number.isFinite(count) ? count : 0;
4984
5162
  }
4985
5163
  async function hasContentHash(db, contentHash) {
@@ -5344,7 +5522,7 @@ async function findRecentEntriesBySubjectTypeAndSourceFile(db, requests) {
5344
5522
  normalizedSubject: toStringValue(row.lookup_normalized_subject),
5345
5523
  type: toStringValue(row.lookup_type),
5346
5524
  sourceFile: toStringValue(row.lookup_source_file),
5347
- withinHours: toNumber(row.lookup_within_hours),
5525
+ withinHours: toNumber2(row.lookup_within_hours),
5348
5526
  entry: mapEntryRow(row, { tags: tagsById.get(entryId) ?? [], includeEmbedding: true })
5349
5527
  });
5350
5528
  }
@@ -5879,6 +6057,10 @@ var init_similarity = __esm({
5879
6057
 
5880
6058
  // src/utils/content-fingerprint.ts
5881
6059
  import { createHash as createHash3 } from "crypto";
6060
+ function computeNormContentHash(content) {
6061
+ const normalized = content.toLowerCase().replace(/\s+/g, " ").trim().replace(/[^\w\s]/g, "");
6062
+ return createHash3("sha256").update(normalized).digest("hex");
6063
+ }
5882
6064
  var init_content_fingerprint = __esm({
5883
6065
  "src/utils/content-fingerprint.ts"() {
5884
6066
  "use strict";
@@ -6278,12 +6460,12 @@ async function getConflictShadowSummary(db) {
6278
6460
  continue;
6279
6461
  }
6280
6462
  const relationSummary = {
6281
- totalEvaluated: Number.isFinite(toNumber(record.total)) ? toNumber(record.total) : 0,
6282
- blockedAtCurrent: Number.isFinite(toNumber(record.blocked_current)) ? toNumber(record.blocked_current) : 0,
6283
- currentOnlyBand: Number.isFinite(toNumber(record.current_only)) ? toNumber(record.current_only) : 0,
6284
- wouldStillBlockAt090: Number.isFinite(toNumber(record.still_block_090)) ? toNumber(record.still_block_090) : 0,
6285
- wouldStillBlockAt095: Number.isFinite(toNumber(record.still_block_095)) ? toNumber(record.still_block_095) : 0,
6286
- silentCoexistBand: Number.isFinite(toNumber(record.silent_coexist)) ? toNumber(record.silent_coexist) : 0
6463
+ totalEvaluated: Number.isFinite(toNumber2(record.total)) ? toNumber2(record.total) : 0,
6464
+ blockedAtCurrent: Number.isFinite(toNumber2(record.blocked_current)) ? toNumber2(record.blocked_current) : 0,
6465
+ currentOnlyBand: Number.isFinite(toNumber2(record.current_only)) ? toNumber2(record.current_only) : 0,
6466
+ wouldStillBlockAt090: Number.isFinite(toNumber2(record.still_block_090)) ? toNumber2(record.still_block_090) : 0,
6467
+ wouldStillBlockAt095: Number.isFinite(toNumber2(record.still_block_095)) ? toNumber2(record.still_block_095) : 0,
6468
+ silentCoexistBand: Number.isFinite(toNumber2(record.silent_coexist)) ? toNumber2(record.silent_coexist) : 0
6287
6469
  };
6288
6470
  byRelation[relation] = relationSummary;
6289
6471
  totalEvaluated += relationSummary.totalEvaluated;
@@ -6732,7 +6914,7 @@ async function getCoRecallEdgeCounts(db) {
6732
6914
  if (!entryId) {
6733
6915
  continue;
6734
6916
  }
6735
- counts.set(entryId, Math.max(0, toNumber(row.edge_count)));
6917
+ counts.set(entryId, Math.max(0, toNumber2(row.edge_count)));
6736
6918
  }
6737
6919
  return counts;
6738
6920
  }
@@ -7042,10 +7224,10 @@ async function evolveQualityScores(db, options = {}, config) {
7042
7224
  return {
7043
7225
  id,
7044
7226
  type: toStringValue(row.type),
7045
- importance: Math.max(0, toNumber(row.importance)),
7227
+ importance: Math.max(0, toNumber2(row.importance)),
7046
7228
  recallCount: Math.max(0, metrics?.totalCount ?? 0),
7047
- confirmations: Math.max(0, toNumber(row.confirmations)),
7048
- qualityScore: toNumber(row.quality_score),
7229
+ confirmations: Math.max(0, toNumber2(row.confirmations)),
7230
+ qualityScore: toNumber2(row.quality_score),
7049
7231
  lastRecalledAt: metrics?.lastLiveRecalledAt ?? metrics?.lastAnyRecalledAt ?? "",
7050
7232
  createdAt: toStringValue(row.created_at)
7051
7233
  };
@@ -7402,8 +7584,8 @@ async function fetchRecalledEntryMetrics(db, entryIds) {
7402
7584
  });
7403
7585
  return result.rows.map((row) => ({
7404
7586
  id: toStringValue(row.id),
7405
- qualityScore: toNumber(row.quality_score),
7406
- recallEventsCount: toNumber(row.recall_events_count)
7587
+ qualityScore: toNumber2(row.quality_score),
7588
+ recallEventsCount: toNumber2(row.recall_events_count)
7407
7589
  }));
7408
7590
  }
7409
7591
  async function processBeforeResetRecallFeedback(request, deps) {
@@ -7481,66 +7663,6 @@ var init_watcher_demotion = __esm({
7481
7663
  }
7482
7664
  });
7483
7665
 
7484
- // src/db/vector-index.ts
7485
- function toNumber2(value) {
7486
- if (typeof value === "number") {
7487
- return value;
7488
- }
7489
- if (typeof value === "bigint") {
7490
- return Number(value);
7491
- }
7492
- if (typeof value === "string" && value.trim()) {
7493
- return Number(value);
7494
- }
7495
- return Number.NaN;
7496
- }
7497
- async function rebuildVectorIndex2(db, options) {
7498
- const start = Date.now();
7499
- const onLog = options?.onLog ?? (() => void 0);
7500
- await db.execute("DROP INDEX IF EXISTS idx_entries_embedding");
7501
- await db.execute(CREATE_IDX_ENTRIES_EMBEDDING_SQL);
7502
- await db.execute(`
7503
- UPDATE entries
7504
- SET embedding = embedding
7505
- WHERE embedding IS NOT NULL
7506
- AND retired = 0
7507
- AND superseded_by IS NULL
7508
- `);
7509
- const result = await db.execute(`
7510
- SELECT COUNT(*) AS count
7511
- FROM entries
7512
- WHERE embedding IS NOT NULL
7513
- AND retired = 0
7514
- AND superseded_by IS NULL
7515
- `);
7516
- const embeddingCountRaw = result.rows[0]?.count;
7517
- const embeddingCount = Number.isFinite(toNumber2(embeddingCountRaw)) ? toNumber2(embeddingCountRaw) : 0;
7518
- if (embeddingCount > 0) {
7519
- const verify = await db.execute(`
7520
- SELECT count(*) AS count
7521
- FROM vector_top_k(
7522
- 'idx_entries_embedding',
7523
- (SELECT embedding FROM entries WHERE embedding IS NOT NULL AND retired = 0 AND superseded_by IS NULL LIMIT 1),
7524
- 1
7525
- )
7526
- `);
7527
- const verifyCountRaw = verify.rows[0]?.count;
7528
- const verifyCount = Number.isFinite(toNumber2(verifyCountRaw)) ? toNumber2(verifyCountRaw) : 0;
7529
- if (verifyCount !== 1) {
7530
- throw new Error(`Vector index rebuild verification failed (expected 1, got ${verifyCount})`);
7531
- }
7532
- }
7533
- const durationMs = Date.now() - start;
7534
- onLog(`[vector-index] rebuilt for ${embeddingCount} entries (${durationMs}ms)`);
7535
- return { embeddingCount, durationMs };
7536
- }
7537
- var init_vector_index = __esm({
7538
- "src/db/vector-index.ts"() {
7539
- "use strict";
7540
- init_schema();
7541
- }
7542
- });
7543
-
7544
7666
  // src/runtime/db-command-defaults.ts
7545
7667
  function resolveDbCommandDefaults() {
7546
7668
  const shared = resolveDefaultAppRuntimeDeps();
@@ -7553,7 +7675,7 @@ function resolveDbCommandDefaults() {
7553
7675
  getConflictShadowSummary,
7554
7676
  resetDb,
7555
7677
  walCheckpoint,
7556
- rebuildVectorIndex: rebuildVectorIndex2,
7678
+ rebuildVectorIndex,
7557
7679
  evolveQualityScores
7558
7680
  };
7559
7681
  }
@@ -8882,6 +9004,47 @@ async function finalizeRecallResults(params) {
8882
9004
  return results;
8883
9005
  }
8884
9006
 
9007
+ // src/domain/recall/browse-selection.ts
9008
+ var DEFAULT_BROWSE_DIVERSITY_TARGET = 4;
9009
+ function normalizeEntryType(result) {
9010
+ const rawType = result.entry.type;
9011
+ return typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
9012
+ }
9013
+ function diversifyBrowseResults(results, limit, diversityTarget = DEFAULT_BROWSE_DIVERSITY_TARGET) {
9014
+ const normalizedLimit = Math.max(0, Math.floor(limit));
9015
+ if (normalizedLimit === 0 || results.length === 0) {
9016
+ return [];
9017
+ }
9018
+ const normalizedDiversityTarget = Math.max(0, Math.floor(diversityTarget));
9019
+ const seedTarget = Math.min(normalizedLimit, normalizedDiversityTarget);
9020
+ const selected = [];
9021
+ const selectedIds = /* @__PURE__ */ new Set();
9022
+ const admittedTypes = /* @__PURE__ */ new Set();
9023
+ for (const result of results) {
9024
+ if (selected.length >= seedTarget) {
9025
+ break;
9026
+ }
9027
+ const normalizedType = normalizeEntryType(result);
9028
+ if (!normalizedType || admittedTypes.has(normalizedType)) {
9029
+ continue;
9030
+ }
9031
+ selected.push(result);
9032
+ selectedIds.add(result.entry.id);
9033
+ admittedTypes.add(normalizedType);
9034
+ }
9035
+ for (const result of results) {
9036
+ if (selected.length >= normalizedLimit) {
9037
+ break;
9038
+ }
9039
+ if (selectedIds.has(result.entry.id)) {
9040
+ continue;
9041
+ }
9042
+ selected.push(result);
9043
+ selectedIds.add(result.entry.id);
9044
+ }
9045
+ return selected;
9046
+ }
9047
+
8885
9048
  // src/db/recall/pipeline/retrieval.ts
8886
9049
  init_client2();
8887
9050
 
@@ -9045,6 +9208,8 @@ var DEFAULT_RECALL_SATURATION = 10;
9045
9208
  var DEFAULT_WARM_START_THRESHOLD = 3;
9046
9209
  var DEFAULT_SYNTHETIC_FLOOR = 0.1;
9047
9210
  var DEFAULT_AGENT_SOURCE_BONUS = 0.05;
9211
+ var BROWSE_RECENCY_WEIGHT = 0.8;
9212
+ var BROWSE_IMPORTANCE_WEIGHT = 0.2;
9048
9213
  var MISSING_RECALL_DAYS = 99999;
9049
9214
  var EMPTY_RECALL_METRICS = {
9050
9215
  totalCount: 0,
@@ -9166,7 +9331,9 @@ function browseRecencyFactor(entry, now, aroundDate, aroundRadius = DEFAULT_AROU
9166
9331
  return resolveRecallRecency(entry, now, aroundDate, aroundRadius);
9167
9332
  }
9168
9333
  function scoreBrowseEntry(entry, now, aroundDate, aroundRadius = DEFAULT_AROUND_RADIUS_DAYS) {
9169
- return clamp01((browseRecencyFactor(entry, now, aroundDate, aroundRadius) + importanceScore(entry.importance)) / 2);
9334
+ const recency2 = browseRecencyFactor(entry, now, aroundDate, aroundRadius);
9335
+ const importance = importanceScore(entry.importance);
9336
+ return clamp01(recency2 * BROWSE_RECENCY_WEIGHT + importance * BROWSE_IMPORTANCE_WEIGHT);
9170
9337
  }
9171
9338
  function scoreSessionEntry(entry, effectiveNow, freshnessNow, metricsMap, aroundDate, aroundRadius = DEFAULT_AROUND_RADIUS_DAYS, config) {
9172
9339
  return scoreEntryWithBreakdown(
@@ -9702,8 +9869,21 @@ async function fetchBrowseCandidates(params) {
9702
9869
  whereClauses.push("platform = ?");
9703
9870
  args.push(params.query.platform);
9704
9871
  }
9705
- args.push(params.limit);
9706
9872
  const whereClause = whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : "";
9873
+ const orderBy = params.aroundDate ? `
9874
+ ORDER BY
9875
+ ABS(julianday(created_at) - julianday(?)) ASC,
9876
+ importance DESC,
9877
+ created_at DESC
9878
+ ` : `
9879
+ ORDER BY
9880
+ created_at DESC,
9881
+ importance DESC
9882
+ `;
9883
+ if (params.aroundDate) {
9884
+ args.push(params.aroundDate.toISOString());
9885
+ }
9886
+ args.push(params.limit);
9707
9887
  const result = await params.db.execute({
9708
9888
  sql: `
9709
9889
  SELECT
@@ -9711,10 +9891,10 @@ async function fetchBrowseCandidates(params) {
9711
9891
  FROM entries
9712
9892
  ${whereClause}
9713
9893
  -- SQL pre-sort is a best-effort approximation only.
9714
- -- Final order is determined by scoreBrowseEntry() (importance * recency decay)
9715
- -- which re-sorts post-fetch. The over-fetch buffer (limit*3, min 50)
9894
+ -- Final order is determined by recency-first browse scoring and a post-score
9895
+ -- diversity pass. The over-fetch buffer (limit*3, min 50)
9716
9896
  -- ensures the final top-N are present in the candidate pool.
9717
- ORDER BY importance DESC, created_at DESC
9897
+ ${orderBy}
9718
9898
  LIMIT ?
9719
9899
  `,
9720
9900
  args
@@ -9947,6 +10127,7 @@ async function recallBrowseMode(params) {
9947
10127
  query: browseQuery,
9948
10128
  limit: browseLimit,
9949
10129
  now: params.now,
10130
+ aroundDate: params.prepared.temporal.aroundDate,
9950
10131
  projectFilter: params.prepared.scope.resolution.primaryFilter
9951
10132
  });
9952
10133
  const filtered = browseCandidates.filter(
@@ -9967,7 +10148,11 @@ async function recallBrowseMode(params) {
9967
10148
  aroundRadiusDays: params.prepared.temporal.aroundRadiusDays
9968
10149
  });
9969
10150
  scored.sort((left, right) => right.score - left.score);
9970
- return scored.slice(0, requestedLimit);
10151
+ const results = diversifyBrowseResults(scored, requestedLimit, DEFAULT_BROWSE_DIVERSITY_TARGET);
10152
+ if (params.trace) {
10153
+ params.trace.finalResultIds = results.map((result) => result.entry.subject ?? result.entry.id);
10154
+ }
10155
+ return results;
9971
10156
  }
9972
10157
  async function retrieveCandidates(params) {
9973
10158
  if (!params.prepared.retrievalSearchText) {
@@ -10377,11 +10562,12 @@ function resolvePreparedRecallScope(query, normalizedQuery) {
10377
10562
  const explicitWildcardProject = hasWildcardProjectOverride(query.project);
10378
10563
  const explicitProjects = explicitWildcardProject ? [] : parseProjectList(query.project);
10379
10564
  const explicitExcludedProjects = parseProjectList(query.excludeProject);
10380
- const inferredProjectHints = deriveRecallProjectScopeHints({
10565
+ const inferredProjectHints = explicitWildcardProject ? [] : deriveRecallProjectScopeHints({
10381
10566
  queryText: normalizedQuery?.searchText ?? query.text ?? "",
10382
10567
  explicitProjects
10383
10568
  });
10384
- const projectHints = explicitProjects.length > 0 ? explicitProjects : inferredProjectHints;
10569
+ const callerProjectHints = explicitProjects.length > 0 || explicitWildcardProject ? [] : parseProjectList(query.projectHints).filter((projectHint) => projectHint !== "*");
10570
+ const projectHints = explicitProjects.length > 0 ? explicitProjects : Array.from(/* @__PURE__ */ new Set([...inferredProjectHints, ...callerProjectHints]));
10385
10571
  const intent = projectHints.length > 0 ? "project" : normalizedQuery?.scopeIntent ?? "ambiguous";
10386
10572
  if (query.universalOnly === true) {
10387
10573
  return {
@@ -12126,6 +12312,7 @@ function buildRecallQuery(request, options) {
12126
12312
  scope: request.scope ?? "private",
12127
12313
  platform: request.platform,
12128
12314
  project: request.project,
12315
+ projectHints: request.projectHints,
12129
12316
  excludeProject: request.excludeProject,
12130
12317
  projectStrict: request.projectStrict,
12131
12318
  universalOnly: request.universalOnly,
@@ -12454,6 +12641,9 @@ async function sessionStartRecall2(request, deps) {
12454
12641
  });
12455
12642
  }
12456
12643
 
12644
+ // src/openclaw-plugin/recall.ts
12645
+ init_project();
12646
+
12457
12647
  // src/openclaw-plugin/memory-index.ts
12458
12648
  init_errors();
12459
12649
  var MEMORY_INDEX_TIMEOUT_MS = 1e4;
@@ -12587,18 +12777,23 @@ function buildRecallRequest(budget, project, query, options) {
12587
12777
  const queryText = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
12588
12778
  const inferredSessionStart = isSessionStart || queryText.length === 0;
12589
12779
  const universalOnly = options?.nullProjectOnly === true || options?.universalOnly === true;
12590
- const explicitProjectsProvided = options?.project !== void 0;
12780
+ const explicitWildcardProject = (options?.project ?? []).some(
12781
+ (candidate) => candidate.split(",").some((part) => part.trim() === "*")
12782
+ );
12591
12783
  const normalizedProjects = Array.from(new Set(
12592
12784
  (options?.project ?? []).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0)
12593
12785
  ));
12594
- const resolvedProject = universalOnly ? void 0 : explicitProjectsProvided ? normalizedProjects.length > 0 ? normalizedProjects : void 0 : !project ? void 0 : [project];
12595
- const resolvedProjectStrict = universalOnly ? void 0 : explicitProjectsProvided ? options?.projectStrict ? true : void 0 : project && !universalOnly ? true : void 0;
12786
+ const explicitProjectSelection = explicitWildcardProject || normalizedProjects.length > 0;
12787
+ const normalizedProjectHints = parseProjectList(options?.projectHints).filter((projectHint) => projectHint !== "*");
12788
+ const resolvedProject = universalOnly ? void 0 : explicitProjectSelection ? normalizedProjects.length > 0 ? normalizedProjects : void 0 : !project ? void 0 : [project];
12789
+ const resolvedProjectStrict = universalOnly ? void 0 : explicitProjectSelection ? options?.projectStrict ? true : void 0 : project && !universalOnly ? true : void 0;
12790
+ const resolvedProjectHints = universalOnly || explicitProjectSelection || explicitWildcardProject ? void 0 : normalizedProjectHints.length > 0 ? normalizedProjectHints : void 0;
12596
12791
  return {
12597
12792
  isBrowse,
12598
12793
  isSessionStart: !isBrowse && inferredSessionStart,
12599
12794
  limit: options?.limit ?? DEFAULT_LIMIT2,
12600
12795
  queryText,
12601
- since: isBrowse ? options?.since ?? "1d" : options?.since,
12796
+ since: options?.since,
12602
12797
  types: options?.types,
12603
12798
  until: options?.until,
12604
12799
  around: options?.around,
@@ -12609,6 +12804,7 @@ function buildRecallRequest(budget, project, query, options) {
12609
12804
  universalOnly,
12610
12805
  project: resolvedProject,
12611
12806
  projectStrict: resolvedProjectStrict,
12807
+ projectHints: resolvedProjectHints,
12612
12808
  budget: isBrowse ? void 0 : options?.budget === null ? void 0 : options?.budget ?? budget
12613
12809
  };
12614
12810
  }
@@ -12658,6 +12854,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12658
12854
  expiry: request.expiry,
12659
12855
  project: request.project,
12660
12856
  projectStrict: request.projectStrict,
12857
+ projectHints: request.projectHints,
12661
12858
  universalOnly: request.universalOnly,
12662
12859
  includeRecallCounts: false
12663
12860
  },
@@ -12678,6 +12875,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12678
12875
  expiry: request.expiry,
12679
12876
  project: request.project,
12680
12877
  projectStrict: request.projectStrict,
12878
+ projectHints: request.projectHints,
12681
12879
  universalOnly: request.universalOnly,
12682
12880
  updateMetadata,
12683
12881
  includeRecallCounts: false
@@ -12701,6 +12899,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12701
12899
  expiry: request.expiry,
12702
12900
  project: request.project,
12703
12901
  projectStrict: request.projectStrict,
12902
+ projectHints: request.projectHints,
12704
12903
  universalOnly: request.universalOnly,
12705
12904
  updateMetadata,
12706
12905
  includeRecallCounts: false
@@ -14923,6 +15122,31 @@ var genericJsonlAdapter = {
14923
15122
 
14924
15123
  // src/adapters/openclaw.ts
14925
15124
  import fs13 from "fs/promises";
15125
+ init_content_fingerprint();
15126
+
15127
+ // src/utils/expiry.ts
15128
+ init_types();
15129
+ var EXPIRY_SET = new Set(EXPIRY_LEVELS);
15130
+ var EXPIRY_PRIORITY = {
15131
+ temporary: 0,
15132
+ permanent: 1,
15133
+ core: 2
15134
+ };
15135
+ function normalizeExpiry(value) {
15136
+ if (typeof value !== "string") {
15137
+ return void 0;
15138
+ }
15139
+ const normalized = value.trim().toLowerCase();
15140
+ if (!normalized) {
15141
+ return void 0;
15142
+ }
15143
+ return EXPIRY_SET.has(normalized) ? normalized : void 0;
15144
+ }
15145
+ function resolveHigherExpiry(a, b) {
15146
+ return EXPIRY_PRIORITY[a] >= EXPIRY_PRIORITY[b] ? a : b;
15147
+ }
15148
+
15149
+ // src/adapters/openclaw.ts
14926
15150
  init_string();
14927
15151
  function normalizeOpenClawRole(value) {
14928
15152
  if (typeof value !== "string") {
@@ -14968,10 +15192,120 @@ function getStringArray(value) {
14968
15192
  const values = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
14969
15193
  return values.length > 0 ? values : void 0;
14970
15194
  }
15195
+ function parseJsonRecord(value) {
15196
+ try {
15197
+ return asRecord4(JSON.parse(value));
15198
+ } catch {
15199
+ return null;
15200
+ }
15201
+ }
15202
+ function normalizeProjectValue(value) {
15203
+ const trimmed = value?.trim();
15204
+ return trimmed ? trimmed.toLowerCase() : void 0;
15205
+ }
14971
15206
  function normalizeSessionLabel(value) {
14972
15207
  const normalized = normalizeLabel(value);
14973
15208
  return normalized.length > 0 ? normalized : void 0;
14974
15209
  }
15210
+ function normalizeHintSubject(subject) {
15211
+ const trimmed = subject?.trim().toLowerCase();
15212
+ return trimmed ? trimmed : void 0;
15213
+ }
15214
+ function extractRecallResultsPayload(message, toolText) {
15215
+ const details = asRecord4(message.details);
15216
+ if (Array.isArray(details?.results)) {
15217
+ return details.results;
15218
+ }
15219
+ const parsedPayload = parseJsonRecord(toolText);
15220
+ if (Array.isArray(parsedPayload?.results)) {
15221
+ return parsedPayload.results;
15222
+ }
15223
+ return [];
15224
+ }
15225
+ function extractRecallTargetHints(message, toolText) {
15226
+ const hints = [];
15227
+ for (const result of extractRecallResultsPayload(message, toolText)) {
15228
+ const resultRecord = asRecord4(result);
15229
+ const entryRecord = asRecord4(resultRecord?.entry);
15230
+ const recalledEntryId = getString4(entryRecord?.id);
15231
+ const type = getString4(entryRecord?.type);
15232
+ const subject = getString4(entryRecord?.subject);
15233
+ const content = getString4(entryRecord?.content);
15234
+ if (!recalledEntryId || !type || !subject || !content) {
15235
+ continue;
15236
+ }
15237
+ const source = asRecord4(entryRecord?.source);
15238
+ hints.push({
15239
+ recalledEntryId,
15240
+ type,
15241
+ subject,
15242
+ normContentHash: computeNormContentHash(content),
15243
+ project: normalizeProjectValue(getString4(entryRecord?.project)),
15244
+ sourceFile: getString4(source?.file),
15245
+ sourceContext: getString4(source?.context)
15246
+ });
15247
+ }
15248
+ return hints;
15249
+ }
15250
+ function isRetireSuccessText(toolText) {
15251
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15252
+ return new RegExp(`^Retired entry\\s+${entryLabelPattern}\\.\\s*$`, "i").test(toolText.trim());
15253
+ }
15254
+ function isUpdateSuccessText(toolText) {
15255
+ const trimmed = toolText.trim();
15256
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15257
+ const updatePart = "(?:importance\\s+\\d+\\s+(?:->|\u2192)\\s+\\d+|expiry\\s+(?:core|permanent|temporary)\\s+(?:->|\u2192)\\s+(?:core|permanent|temporary))";
15258
+ return new RegExp(`^Updated entry\\s+${entryLabelPattern}\\s+importance:\\s+\\d+\\s+->\\s+\\d+\\.\\s*$`, "i").test(trimmed) || new RegExp(`^Entry\\s+${entryLabelPattern}\\s+already has importance\\s+\\d+\\.\\s*$`, "i").test(trimmed) || new RegExp(`^Updated entry\\s+${entryLabelPattern}:\\s+importance\\s+\u2192\\s+\\d+\\.\\s*$`, "i").test(trimmed) || new RegExp(`^Updated entry\\s+${entryLabelPattern}:\\s+${updatePart}(?:,\\s+${updatePart})*\\.\\s*$`, "i").test(trimmed) || new RegExp(`^Entry\\s+${entryLabelPattern}\\s+already has expiry\\s+(?:core|permanent|temporary)\\.\\s*$`, "i").test(trimmed) || new RegExp(`^Entry\\s+${entryLabelPattern}\\s+already has importance\\s+\\d+\\s+and\\s+expiry\\s+(?:core|permanent|temporary)\\.\\s*$`, "i").test(trimmed);
15259
+ }
15260
+ function isInactiveUpdateText(toolText) {
15261
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15262
+ return new RegExp(
15263
+ `^Entry\\s+${entryLabelPattern}\\s+was not updated because it is no longer active\\.\\s*$`,
15264
+ "i"
15265
+ ).test(toolText.trim());
15266
+ }
15267
+ function parseQuotedSubject(subjectToken) {
15268
+ if (!subjectToken) {
15269
+ return void 0;
15270
+ }
15271
+ try {
15272
+ const parsed = JSON.parse(subjectToken);
15273
+ return typeof parsed === "string" ? parsed : void 0;
15274
+ } catch {
15275
+ return void 0;
15276
+ }
15277
+ }
15278
+ function parseRetireSuccessReceipt(toolText) {
15279
+ const match = toolText.trim().match(
15280
+ /^Retired entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\.\s*$/i
15281
+ );
15282
+ if (!match) {
15283
+ return null;
15284
+ }
15285
+ return {
15286
+ entryId: match[1],
15287
+ subject: parseQuotedSubject(match[2])
15288
+ };
15289
+ }
15290
+ function parseUpdateReceiptEntry(toolText) {
15291
+ const matchers = [
15292
+ /^Updated entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+importance:/i,
15293
+ /^Updated entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?:/i,
15294
+ /^Entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+already has/i,
15295
+ /^Entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+was not updated because/i
15296
+ ];
15297
+ for (const matcher of matchers) {
15298
+ const match = toolText.trim().match(matcher);
15299
+ if (!match) {
15300
+ continue;
15301
+ }
15302
+ return {
15303
+ entryId: match[1],
15304
+ subject: parseQuotedSubject(match[2])
15305
+ };
15306
+ }
15307
+ return null;
15308
+ }
14975
15309
  function parsePendingAgentStoreEntry(value, defaultProject) {
14976
15310
  const record = asRecord4(value);
14977
15311
  if (!record) {
@@ -15125,11 +15459,43 @@ var openClawAdapter = {
15125
15459
  const modelsUsed = [];
15126
15460
  const modelsUsedSet = /* @__PURE__ */ new Set();
15127
15461
  const successfulAgentStoredEntries = [];
15462
+ const successfulAgentRetires = [];
15463
+ const successfulAgentUpdates = [];
15128
15464
  const agentStoreProjects = /* @__PURE__ */ new Set();
15465
+ const recalledEntryHintsById = /* @__PURE__ */ new Map();
15466
+ const recalledEntryHintsBySubject = /* @__PURE__ */ new Map();
15129
15467
  const pendingToolCalls = [];
15130
15468
  const pendingToolCallsById = /* @__PURE__ */ new Map();
15131
15469
  const pendingAgentStores = /* @__PURE__ */ new Map();
15132
15470
  const pendingAgentStoreQueue = [];
15471
+ const pendingAgentRetires = /* @__PURE__ */ new Map();
15472
+ const pendingAgentRetireQueue = [];
15473
+ const pendingAgentUpdates = /* @__PURE__ */ new Map();
15474
+ const pendingAgentUpdateQueue = [];
15475
+ const appendRecalledEntryHint = (hint) => {
15476
+ recalledEntryHintsById.set(hint.recalledEntryId, hint);
15477
+ const subjectKey2 = normalizeHintSubject(hint.subject);
15478
+ if (!subjectKey2) {
15479
+ return;
15480
+ }
15481
+ const existing = recalledEntryHintsBySubject.get(subjectKey2) ?? [];
15482
+ existing.push(hint);
15483
+ recalledEntryHintsBySubject.set(subjectKey2, existing);
15484
+ };
15485
+ const resolveRecalledEntryHint = (entryId, subject) => {
15486
+ if (entryId) {
15487
+ const byId = recalledEntryHintsById.get(entryId);
15488
+ if (byId) {
15489
+ return byId;
15490
+ }
15491
+ }
15492
+ const subjectKey2 = normalizeHintSubject(subject);
15493
+ if (!subjectKey2) {
15494
+ return void 0;
15495
+ }
15496
+ const bySubject = recalledEntryHintsBySubject.get(subjectKey2) ?? [];
15497
+ return bySubject.length === 1 ? bySubject[0] : void 0;
15498
+ };
15133
15499
  const addModelUsed = (value) => {
15134
15500
  const modelId = typeof value === "string" ? value.trim() : "";
15135
15501
  if (!modelId || modelsUsedSet.has(modelId)) {
@@ -15167,7 +15533,7 @@ var openClawAdapter = {
15167
15533
  }
15168
15534
  return pendingToolCalls.shift() ?? null;
15169
15535
  };
15170
- const resolvePendingAgentStore = (message, toolCallIdHint) => {
15536
+ const resolvePendingAction = (message, byId, queue, toolCallIdHint) => {
15171
15537
  const candidateIds = [
15172
15538
  toolCallIdHint,
15173
15539
  getString4(message.toolCallId),
@@ -15176,14 +15542,23 @@ var openClawAdapter = {
15176
15542
  getString4(message.id)
15177
15543
  ].filter((value, index, all) => Boolean(value) && all.indexOf(value) === index);
15178
15544
  for (const id of candidateIds) {
15179
- const pending = pendingAgentStores.get(id);
15545
+ const pending = byId.get(id);
15180
15546
  if (!pending) {
15181
15547
  continue;
15182
15548
  }
15183
- pendingAgentStores.delete(id);
15549
+ byId.delete(id);
15184
15550
  return pending;
15185
15551
  }
15186
- return pendingAgentStoreQueue.shift() ?? null;
15552
+ return queue.shift() ?? null;
15553
+ };
15554
+ const resolvePendingAgentStore = (message, toolCallIdHint) => {
15555
+ return resolvePendingAction(message, pendingAgentStores, pendingAgentStoreQueue, toolCallIdHint);
15556
+ };
15557
+ const resolvePendingAgentRetire = (message, toolCallIdHint) => {
15558
+ return resolvePendingAction(message, pendingAgentRetires, pendingAgentRetireQueue, toolCallIdHint);
15559
+ };
15560
+ const resolvePendingAgentUpdate = (message, toolCallIdHint) => {
15561
+ return resolvePendingAction(message, pendingAgentUpdates, pendingAgentUpdateQueue, toolCallIdHint);
15187
15562
  };
15188
15563
  parseJsonlLines(raw, warnings, (record) => {
15189
15564
  if (record.type === "session") {
@@ -15249,28 +15624,75 @@ var openClawAdapter = {
15249
15624
  const toolCalls = extractToolCallBlocks(content);
15250
15625
  const assistantMessageIndex = messages.length;
15251
15626
  for (const call of toolCalls) {
15627
+ const normalizedToolName = call.name.trim().toLowerCase();
15252
15628
  pendingToolCalls.push(call);
15253
15629
  if (call.id) {
15254
15630
  pendingToolCallsById.set(call.id, call);
15255
15631
  }
15256
- if (call.name.trim().toLowerCase() !== "agenr_store" || !Array.isArray(call.args.entries)) {
15632
+ if (normalizedToolName === "agenr_store" && Array.isArray(call.args.entries)) {
15633
+ const defaultProject = getString4(call.args.project);
15634
+ const entries = call.args.entries.map((entry) => parsePendingAgentStoreEntry(entry, defaultProject)).filter((entry) => entry !== null);
15635
+ if (entries.length === 0) {
15636
+ continue;
15637
+ }
15638
+ const pending = {
15639
+ entries,
15640
+ toolCallId: call.id,
15641
+ assistantMessageIndex,
15642
+ timestamp: timestamp2
15643
+ };
15644
+ if (call.id) {
15645
+ pendingAgentStores.set(call.id, pending);
15646
+ } else {
15647
+ pendingAgentStoreQueue.push(pending);
15648
+ }
15257
15649
  continue;
15258
15650
  }
15259
- const defaultProject = getString4(call.args.project);
15260
- const entries = call.args.entries.map((entry) => parsePendingAgentStoreEntry(entry, defaultProject)).filter((entry) => entry !== null);
15261
- if (entries.length === 0) {
15651
+ if (normalizedToolName === "agenr_retire") {
15652
+ const entryId = getString4(call.args.entry_id);
15653
+ const subject = getString4(call.args.subject);
15654
+ if (!entryId && !subject || entryId && subject) {
15655
+ continue;
15656
+ }
15657
+ const pending = {
15658
+ ...entryId ? { entryId } : {},
15659
+ ...subject ? { subject } : {},
15660
+ reason: getString4(call.args.reason),
15661
+ toolCallId: call.id,
15662
+ assistantMessageIndex,
15663
+ timestamp: timestamp2,
15664
+ targetHint: resolveRecalledEntryHint(entryId, subject)
15665
+ };
15666
+ if (call.id) {
15667
+ pendingAgentRetires.set(call.id, pending);
15668
+ } else {
15669
+ pendingAgentRetireQueue.push(pending);
15670
+ }
15262
15671
  continue;
15263
15672
  }
15264
- const pending = {
15265
- entries,
15266
- toolCallId: call.id,
15267
- assistantMessageIndex,
15268
- timestamp: timestamp2
15269
- };
15270
- if (call.id) {
15271
- pendingAgentStores.set(call.id, pending);
15272
- } else {
15273
- pendingAgentStoreQueue.push(pending);
15673
+ if (normalizedToolName === "agenr_update") {
15674
+ const entryId = getString4(call.args.entry_id);
15675
+ const subject = getString4(call.args.subject);
15676
+ const importance = getNumber(call.args.importance);
15677
+ const expiry = normalizeExpiry(getString4(call.args.expiry));
15678
+ if (!entryId && !subject || entryId && subject || importance === void 0 && expiry === void 0) {
15679
+ continue;
15680
+ }
15681
+ const pending = {
15682
+ ...entryId ? { entryId } : {},
15683
+ ...subject ? { subject } : {},
15684
+ ...importance !== void 0 ? { importance } : {},
15685
+ ...expiry !== void 0 ? { expiry } : {},
15686
+ toolCallId: call.id,
15687
+ assistantMessageIndex,
15688
+ timestamp: timestamp2,
15689
+ targetHint: resolveRecalledEntryHint(entryId, subject)
15690
+ };
15691
+ if (call.id) {
15692
+ pendingAgentUpdates.set(call.id, pending);
15693
+ } else {
15694
+ pendingAgentUpdateQueue.push(pending);
15695
+ }
15274
15696
  }
15275
15697
  }
15276
15698
  let text;
@@ -15308,8 +15730,17 @@ var openClawAdapter = {
15308
15730
  const normalizedToolName = toolName?.trim().toLowerCase();
15309
15731
  const args = ctx?.args ?? {};
15310
15732
  const toolText = normalizeMessageText(content);
15733
+ const trimmedToolText = toolText.trim();
15734
+ if (normalizedToolName === "agenr_recall") {
15735
+ const recallFailed = message.isError === true || message.is_error === true || /^agenr_recall failed:/i.test(trimmedToolText);
15736
+ if (!recallFailed) {
15737
+ for (const hint of extractRecallTargetHints(message, toolText)) {
15738
+ appendRecalledEntryHint(hint);
15739
+ }
15740
+ }
15741
+ }
15311
15742
  if (normalizedToolName === "agenr_store") {
15312
- const storeFailed = message.isError === true || message.is_error === true || /^validation failed\b/i.test(toolText.trim());
15743
+ const storeFailed = message.isError === true || message.is_error === true || /^validation failed\b/i.test(trimmedToolText);
15313
15744
  const pending = resolvePendingAgentStore(message, ctx?.id);
15314
15745
  if (pending && !storeFailed) {
15315
15746
  const toolResultMessageIndex = messages.length;
@@ -15331,6 +15762,39 @@ var openClawAdapter = {
15331
15762
  }
15332
15763
  }
15333
15764
  }
15765
+ if (normalizedToolName === "agenr_retire") {
15766
+ const retireFailed = message.isError === true || message.is_error === true || /^agenr_retire failed:/i.test(trimmedToolText);
15767
+ const pending = resolvePendingAgentRetire(message, ctx?.id);
15768
+ const receipt = parseRetireSuccessReceipt(toolText);
15769
+ if (pending && !retireFailed && receipt && isRetireSuccessText(toolText)) {
15770
+ const targetHint = pending.targetHint ?? resolveRecalledEntryHint(void 0, receipt.subject);
15771
+ successfulAgentRetires.push({
15772
+ entryId: receipt.entryId,
15773
+ reason: pending.reason,
15774
+ toolCallId: pending.toolCallId,
15775
+ assistantMessageIndex: pending.assistantMessageIndex,
15776
+ timestamp: pending.timestamp,
15777
+ ...targetHint ? { targetHint } : {}
15778
+ });
15779
+ }
15780
+ }
15781
+ if (normalizedToolName === "agenr_update") {
15782
+ const updateFailed = message.isError === true || message.is_error === true || /^agenr_update failed:/i.test(trimmedToolText) || isInactiveUpdateText(trimmedToolText);
15783
+ const pending = resolvePendingAgentUpdate(message, ctx?.id);
15784
+ const receipt = parseUpdateReceiptEntry(toolText);
15785
+ if (pending && !updateFailed && receipt && isUpdateSuccessText(toolText)) {
15786
+ const targetHint = pending.targetHint ?? resolveRecalledEntryHint(void 0, receipt.subject);
15787
+ successfulAgentUpdates.push({
15788
+ entryId: receipt.entryId,
15789
+ ...pending.importance !== void 0 ? { importance: pending.importance } : {},
15790
+ ...pending.expiry !== void 0 ? { expiry: pending.expiry } : {},
15791
+ toolCallId: pending.toolCallId,
15792
+ assistantMessageIndex: pending.assistantMessageIndex,
15793
+ timestamp: pending.timestamp,
15794
+ ...targetHint ? { targetHint } : {}
15795
+ });
15796
+ }
15797
+ }
15334
15798
  if (!toolText) {
15335
15799
  return;
15336
15800
  }
@@ -15388,7 +15852,9 @@ var openClawAdapter = {
15388
15852
  startedAt: sessionTimestamp ?? messages[0]?.timestamp ?? fallbackTimestamp,
15389
15853
  agentStoreProjects: agentStoreProjects.size > 0 ? Array.from(agentStoreProjects) : void 0
15390
15854
  },
15391
- successfulAgentStoredEntries: successfulAgentStoredEntries.length > 0 ? successfulAgentStoredEntries : void 0
15855
+ successfulAgentStoredEntries: successfulAgentStoredEntries.length > 0 ? successfulAgentStoredEntries : void 0,
15856
+ successfulAgentRetires: successfulAgentRetires.length > 0 ? successfulAgentRetires : void 0,
15857
+ successfulAgentUpdates: successfulAgentUpdates.length > 0 ? successfulAgentUpdates : void 0
15392
15858
  };
15393
15859
  }
15394
15860
  };
@@ -15853,7 +16319,9 @@ async function parseTranscriptFile(filePath, options) {
15853
16319
  chunks,
15854
16320
  warnings: result.warnings,
15855
16321
  metadata: result.metadata,
15856
- successfulAgentStoredEntries: result.successfulAgentStoredEntries
16322
+ successfulAgentStoredEntries: result.successfulAgentStoredEntries,
16323
+ successfulAgentRetires: result.successfulAgentRetires,
16324
+ successfulAgentUpdates: result.successfulAgentUpdates
15857
16325
  };
15858
16326
  }
15859
16327
 
@@ -20281,18 +20749,6 @@ async function extractKnowledgeFromChunks(params) {
20281
20749
  };
20282
20750
  }
20283
20751
 
20284
- // src/utils/expiry.ts
20285
- init_types();
20286
- var EXPIRY_SET = new Set(EXPIRY_LEVELS);
20287
- var EXPIRY_PRIORITY = {
20288
- temporary: 0,
20289
- permanent: 1,
20290
- core: 2
20291
- };
20292
- function resolveHigherExpiry(a, b) {
20293
- return EXPIRY_PRIORITY[a] >= EXPIRY_PRIORITY[b] ? a : b;
20294
- }
20295
-
20296
20752
  // src/app/ingest/trigram-dedup.ts
20297
20753
  function normalize3(value) {
20298
20754
  return value.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
@@ -20660,8 +21116,8 @@ function createContradictionRepository(db) {
20660
21116
  });
20661
21117
  return result.rows.map((row) => {
20662
21118
  const record = row;
20663
- const importance = toNumber(record.importance);
20664
- const claimConfidence = toNumber(record.claim_confidence);
21119
+ const importance = toNumber2(record.importance);
21120
+ const claimConfidence = toNumber2(record.claim_confidence);
20665
21121
  const claimRole = toStringValue(record.claim_role);
20666
21122
  return {
20667
21123
  id: toStringValue(record.id),
@@ -25108,7 +25564,7 @@ async function queryCandidates(db, subject, contains) {
25108
25564
  id: toStringValue(row.id),
25109
25565
  type: toStringValue(row.type),
25110
25566
  subject: toStringValue(row.subject),
25111
- importance: toNumber(row.importance),
25567
+ importance: toNumber2(row.importance),
25112
25568
  content: toStringValue(row.content)
25113
25569
  }));
25114
25570
  }
@@ -25125,7 +25581,7 @@ async function queryById(db, id) {
25125
25581
  id: toStringValue(row.id),
25126
25582
  type: toStringValue(row.type),
25127
25583
  subject: toStringValue(row.subject),
25128
- importance: toNumber(row.importance),
25584
+ importance: toNumber2(row.importance),
25129
25585
  content: toStringValue(row.content)
25130
25586
  }));
25131
25587
  }
@@ -25201,12 +25657,16 @@ init_runtime2();
25201
25657
  function resolveUpdateServiceDeps(deps) {
25202
25658
  return resolveAppRuntimeDeps(deps);
25203
25659
  }
25204
- function validateEntryId(id) {
25205
- const entryId = id?.trim() ?? "";
25206
- if (!entryId) {
25207
- throw new Error("id is required");
25660
+ function validateSelector2(request) {
25661
+ const queryId = request.id?.trim() ?? "";
25662
+ const querySubject = request.subject?.trim() ?? "";
25663
+ if (!queryId && !querySubject) {
25664
+ throw new Error("entry_id or subject is required");
25665
+ }
25666
+ if (queryId && querySubject) {
25667
+ throw new Error("entry_id and subject are mutually exclusive");
25208
25668
  }
25209
- return entryId;
25669
+ return { queryId, querySubject };
25210
25670
  }
25211
25671
  function normalizeImportance3(value) {
25212
25672
  const parsed = typeof value === "number" ? value : Number(value);
@@ -25215,10 +25675,26 @@ function normalizeImportance3(value) {
25215
25675
  }
25216
25676
  return parsed;
25217
25677
  }
25678
+ function normalizeOptionalImportance(value) {
25679
+ if (value === void 0) {
25680
+ return void 0;
25681
+ }
25682
+ return normalizeImportance3(value);
25683
+ }
25684
+ function normalizeOptionalExpiry(value) {
25685
+ if (value === void 0) {
25686
+ return void 0;
25687
+ }
25688
+ const normalized = normalizeExpiry(value);
25689
+ if (!normalized) {
25690
+ throw new Error(`expiry must be one of: ${EXPIRY_LEVELS.join(", ")}`);
25691
+ }
25692
+ return normalized;
25693
+ }
25218
25694
  async function queryById2(db, id) {
25219
25695
  const result = await db.execute({
25220
25696
  sql: `
25221
- SELECT id, type, subject, importance, content
25697
+ SELECT id, type, subject, importance, expiry, content
25222
25698
  FROM entries
25223
25699
  WHERE retired = 0 AND id = ?
25224
25700
  LIMIT 1
@@ -25229,11 +25705,39 @@ async function queryById2(db, id) {
25229
25705
  if (!row) {
25230
25706
  return null;
25231
25707
  }
25708
+ const expiry = normalizeExpiry(row.expiry) ?? "temporary";
25232
25709
  return {
25233
25710
  id: toStringValue(row.id),
25234
25711
  type: toStringValue(row.type),
25235
25712
  subject: toStringValue(row.subject),
25236
- importance: toNumber(row.importance),
25713
+ importance: toNumber2(row.importance),
25714
+ expiry,
25715
+ content: toStringValue(row.content)
25716
+ };
25717
+ }
25718
+ async function queryBySubject(db, subject) {
25719
+ const result = await db.execute({
25720
+ sql: `
25721
+ SELECT id, type, subject, importance, expiry, content
25722
+ FROM entries
25723
+ WHERE retired = 0
25724
+ AND LOWER(subject) = LOWER(?)
25725
+ ORDER BY updated_at DESC
25726
+ LIMIT 1
25727
+ `,
25728
+ args: [subject]
25729
+ });
25730
+ const row = result.rows[0];
25731
+ if (!row) {
25732
+ return null;
25733
+ }
25734
+ const expiry = normalizeExpiry(row.expiry) ?? "temporary";
25735
+ return {
25736
+ id: toStringValue(row.id),
25737
+ type: toStringValue(row.type),
25738
+ subject: toStringValue(row.subject),
25739
+ importance: toNumber2(row.importance),
25740
+ expiry,
25237
25741
  content: toStringValue(row.content)
25238
25742
  };
25239
25743
  }
@@ -25245,7 +25749,7 @@ async function lookupUpdateCandidate(request, deps) {
25245
25749
  }
25246
25750
  async function runUpdateService(request, deps) {
25247
25751
  const resolvedDeps = resolveUpdateServiceDeps(deps);
25248
- const id = validateEntryId(request.id);
25752
+ const { queryId, querySubject } = validateSelector2(request);
25249
25753
  return withAppDb(
25250
25754
  {
25251
25755
  env: request.env,
@@ -25256,53 +25760,81 @@ async function runUpdateService(request, deps) {
25256
25760
  if (request.action === "lookup") {
25257
25761
  return {
25258
25762
  action: "lookup",
25259
- candidate: await queryById2(db, id)
25763
+ candidate: queryId ? await queryById2(db, queryId) : await queryBySubject(db, querySubject)
25260
25764
  };
25261
25765
  }
25262
- const importance = normalizeImportance3(request.importance);
25263
- const existing = await queryById2(db, id);
25766
+ const importance = normalizeOptionalImportance(request.importance);
25767
+ const expiry = normalizeOptionalExpiry(request.expiry);
25768
+ if (importance === void 0 && expiry === void 0) {
25769
+ throw new Error("at least one of importance or expiry is required");
25770
+ }
25771
+ const existing = queryId ? await queryById2(db, queryId) : await queryBySubject(db, querySubject);
25772
+ const targetId = existing?.id ?? queryId ?? querySubject;
25264
25773
  if (!existing) {
25265
25774
  return {
25266
25775
  action: "execute",
25267
- id,
25268
- previousImportance: importance,
25269
- importance,
25776
+ id: targetId,
25777
+ previousImportance: importance ?? IMPORTANCE_MIN,
25778
+ importance: importance ?? IMPORTANCE_MIN,
25779
+ previousExpiry: expiry ?? "temporary",
25780
+ expiry: expiry ?? "temporary",
25270
25781
  updated: false
25271
25782
  };
25272
25783
  }
25273
- if (existing.importance === importance) {
25784
+ const nextImportance = importance ?? existing.importance;
25785
+ const nextExpiry = expiry ?? existing.expiry;
25786
+ const importanceChanged = existing.importance !== nextImportance;
25787
+ const expiryChanged = existing.expiry !== nextExpiry;
25788
+ if (!importanceChanged && !expiryChanged) {
25274
25789
  return {
25275
25790
  action: "execute",
25276
- id,
25791
+ id: existing.id,
25277
25792
  previousImportance: existing.importance,
25278
- importance,
25793
+ importance: existing.importance,
25794
+ previousExpiry: existing.expiry,
25795
+ expiry: existing.expiry,
25279
25796
  updated: true
25280
25797
  };
25281
25798
  }
25799
+ const setClauses = [];
25800
+ const setArgs = [];
25801
+ if (importanceChanged) {
25802
+ setClauses.push("importance = ?");
25803
+ setArgs.push(nextImportance);
25804
+ }
25805
+ if (expiryChanged) {
25806
+ setClauses.push("expiry = ?");
25807
+ setArgs.push(nextExpiry);
25808
+ }
25809
+ setClauses.push("updated_at = ?");
25810
+ setArgs.push(runtime.now.toISOString());
25282
25811
  const result = await db.execute({
25283
25812
  sql: `
25284
25813
  UPDATE entries
25285
- SET importance = ?,
25286
- updated_at = ?
25814
+ SET ${setClauses.join(", ")}
25287
25815
  WHERE id = ?
25288
25816
  AND retired = 0
25289
25817
  `,
25290
- args: [importance, runtime.now.toISOString(), id]
25818
+ args: [...setArgs, existing.id]
25291
25819
  });
25292
25820
  if (toRowsAffected(result.rowsAffected) === 0) {
25293
25821
  return {
25294
25822
  action: "execute",
25295
- id,
25823
+ id: existing.id,
25296
25824
  previousImportance: existing.importance,
25297
25825
  importance: existing.importance,
25826
+ previousExpiry: existing.expiry,
25827
+ expiry: existing.expiry,
25298
25828
  updated: false
25299
25829
  };
25300
25830
  }
25301
25831
  return {
25302
25832
  action: "execute",
25303
- id,
25833
+ id: existing.id,
25304
25834
  previousImportance: existing.importance,
25305
- importance,
25835
+ importance: nextImportance,
25836
+ previousExpiry: existing.expiry,
25837
+ expiry: nextExpiry,
25306
25838
  updated: true
25307
25839
  };
25308
25840
  }
@@ -25314,6 +25846,8 @@ async function executeUpdate(request, deps) {
25314
25846
  id: result.id,
25315
25847
  previousImportance: result.previousImportance,
25316
25848
  importance: result.importance,
25849
+ previousExpiry: result.previousExpiry,
25850
+ expiry: result.expiry,
25317
25851
  updated: result.updated
25318
25852
  };
25319
25853
  }
@@ -25325,8 +25859,8 @@ init_client();
25325
25859
  // src/db/provenance.ts
25326
25860
  init_entry_utils();
25327
25861
  function mapProvenanceEntry(row, tags) {
25328
- const importance = toNumber(row.importance);
25329
- const retired = toNumber(row.retired);
25862
+ const importance = toNumber2(row.importance);
25863
+ const retired = toNumber2(row.retired);
25330
25864
  return {
25331
25865
  id: toStringValue(row.id),
25332
25866
  subject: toStringValue(row.subject),
@@ -25349,7 +25883,7 @@ function mapProvenanceRelated(row, tags) {
25349
25883
  };
25350
25884
  }
25351
25885
  function mapProvenanceSupport(row) {
25352
- const claimConfidence = toNumber(row.claim_confidence);
25886
+ const claimConfidence = toNumber2(row.claim_confidence);
25353
25887
  const sourceFile = toStringValue(row.source_file).trim();
25354
25888
  const sourceContext = toStringValue(row.source_context).trim();
25355
25889
  const sourceCreatedAt = toStringValue(row.source_created_at).trim();
@@ -26643,6 +27177,7 @@ function resolveOpenClawBridgeStoreProjectAttribution(params) {
26643
27177
 
26644
27178
  // src/openclaw-plugin/tools.ts
26645
27179
  init_types();
27180
+ init_project();
26646
27181
  var KNOWN_PLATFORMS = /* @__PURE__ */ new Set(["openclaw", "codex", "claude-code", "plaud"]);
26647
27182
  var log7 = createLogger("tools");
26648
27183
  var SUCCESSFUL_STORE_ACTIONS = /* @__PURE__ */ new Set([
@@ -26657,10 +27192,31 @@ function asString2(value) {
26657
27192
  const trimmed = value.trim();
26658
27193
  return trimmed.length > 0 ? trimmed : void 0;
26659
27194
  }
27195
+ function validateExclusiveSelector(params) {
27196
+ const entryId = asString2(params.entry_id);
27197
+ const subject = asString2(params.subject);
27198
+ if (entryId && subject || !entryId && !subject) {
27199
+ throw new Error("exactly one of entry_id or subject is required");
27200
+ }
27201
+ return { entryId, subject };
27202
+ }
27203
+ function formatQuotedSubject(subject) {
27204
+ return JSON.stringify(subject);
27205
+ }
27206
+ function formatEntryLabel(id, subject) {
27207
+ return subject ? `${id} (${formatQuotedSubject(subject)})` : id;
27208
+ }
27209
+ function formatMissingEntryError(selector) {
27210
+ if (selector.subject) {
27211
+ return `No active entry found matching subject: ${formatQuotedSubject(selector.subject)}`;
27212
+ }
27213
+ return `No active entry found with id: ${selector.entryId}`;
27214
+ }
26660
27215
  function resolveStoreCandidateProject(explicitProject, sessionProject, defaultProject) {
26661
27216
  return asString2(explicitProject) ?? asString2(sessionProject) ?? asString2(defaultProject);
26662
27217
  }
26663
27218
  function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
27219
+ const diagnosticProjects = scope.projects.length > 0 ? scope.projects : scope.projectHints ?? [];
26664
27220
  return createProjectScopeResolutionDiagnostic({
26665
27221
  surface: "agent-tool-recall",
26666
27222
  inputs: {
@@ -26674,8 +27230,8 @@ function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
26674
27230
  outcome: {
26675
27231
  projectSource: scope.projectSource,
26676
27232
  reason: scope.reason,
26677
- projects: scope.projects,
26678
- primaryProject: scope.projects[0],
27233
+ projects: diagnosticProjects,
27234
+ primaryProject: diagnosticProjects[0],
26679
27235
  recallProjectArg: scope.recallProjectArg,
26680
27236
  strictScope: scope.projectStrict,
26681
27237
  dependencyExpansionApplied: false,
@@ -26685,7 +27241,9 @@ function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
26685
27241
  }
26686
27242
  function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26687
27243
  const normalizedExplicitProject = explicitProject?.trim();
26688
- const defaultProjectPresent = Boolean(defaultProject?.trim());
27244
+ const normalizedDefaultProjects = parseProjectList(defaultProject).filter((project) => project !== "*");
27245
+ const normalizedDefaultProject = normalizedDefaultProjects[0];
27246
+ const defaultProjectPresent = normalizedDefaultProjects.length > 0;
26689
27247
  if (normalizedExplicitProject === "*") {
26690
27248
  const scope2 = {
26691
27249
  surface: "agent-tool-recall",
@@ -26693,8 +27251,8 @@ function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26693
27251
  projectSource: "unscoped",
26694
27252
  projectArgumentState: "wildcard",
26695
27253
  reason: "explicit-tool-project-bypass",
26696
- projects: [],
26697
- recallProjectArg: void 0,
27254
+ projects: ["*"],
27255
+ recallProjectArg: "*",
26698
27256
  projectStrict: false
26699
27257
  };
26700
27258
  return {
@@ -26706,14 +27264,34 @@ function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26706
27264
  };
26707
27265
  }
26708
27266
  if (!normalizedExplicitProject) {
27267
+ if (normalizedDefaultProject) {
27268
+ const scope3 = {
27269
+ surface: "agent-tool-recall",
27270
+ projectResolutionMode: "explicit-tool-project-only",
27271
+ projectSource: "plugin-default-project",
27272
+ projectArgumentState: "omitted",
27273
+ reason: "plugin-default-project",
27274
+ projects: [],
27275
+ projectHints: [normalizedDefaultProject],
27276
+ recallProjectArg: void 0,
27277
+ projectStrict: false
27278
+ };
27279
+ return {
27280
+ ...scope3,
27281
+ memoryReliabilityDiagnostic: createAgentToolRecallScopeDiagnostic(
27282
+ scope3,
27283
+ defaultProjectPresent
27284
+ )
27285
+ };
27286
+ }
26709
27287
  const scope2 = {
26710
27288
  surface: "agent-tool-recall",
26711
27289
  projectResolutionMode: "explicit-tool-project-only",
26712
27290
  projectSource: "unscoped",
26713
27291
  projectArgumentState: "omitted",
26714
27292
  reason: "no-project-candidate",
26715
- projects: [],
26716
- recallProjectArg: void 0,
27293
+ projects: ["*"],
27294
+ recallProjectArg: "*",
26717
27295
  projectStrict: false
26718
27296
  };
26719
27297
  return {
@@ -26755,6 +27333,36 @@ function asNumber(value) {
26755
27333
  }
26756
27334
  return void 0;
26757
27335
  }
27336
+ function normalizeOptionalUpdateExpiry(value) {
27337
+ if (value === void 0) {
27338
+ return void 0;
27339
+ }
27340
+ const expiry = normalizeExpiry(value);
27341
+ if (!expiry) {
27342
+ throw new Error(`expiry must be one of: ${EXPIRY_LEVELS.join(", ")}`);
27343
+ }
27344
+ return expiry;
27345
+ }
27346
+ function buildChangedUpdateParts(request, result) {
27347
+ const parts = [];
27348
+ if (request.importance !== void 0 && result.previousImportance !== result.importance) {
27349
+ parts.push(`importance ${result.previousImportance} \u2192 ${result.importance}`);
27350
+ }
27351
+ if (request.expiry !== void 0 && result.previousExpiry !== result.expiry) {
27352
+ parts.push(`expiry ${result.previousExpiry} \u2192 ${result.expiry}`);
27353
+ }
27354
+ return parts;
27355
+ }
27356
+ function buildRequestedUpdateParts(request, result) {
27357
+ const parts = [];
27358
+ if (request.importance !== void 0) {
27359
+ parts.push(`importance ${result.importance}`);
27360
+ }
27361
+ if (request.expiry !== void 0) {
27362
+ parts.push(`expiry ${result.expiry}`);
27363
+ }
27364
+ return parts;
27365
+ }
26758
27366
  function parseOptionalPositiveInt(value, label) {
26759
27367
  if (value === void 0) {
26760
27368
  return void 0;
@@ -26879,6 +27487,7 @@ async function runRecallTool(params, defaultProject, dbPath) {
26879
27487
  platform: normalizeRecallPlatform(asString2(params.platform)),
26880
27488
  project: recallScope.projects,
26881
27489
  projectStrict: recallScope.projectStrict,
27490
+ projectHints: recallScope.projectHints,
26882
27491
  budget: null
26883
27492
  },
26884
27493
  dbPath,
@@ -27069,34 +27678,34 @@ async function runExtractTool(params, dbPath) {
27069
27678
  }
27070
27679
  async function runRetireTool(params, dbPath) {
27071
27680
  try {
27072
- const entryId = asString2(params.entry_id);
27073
- if (!entryId) {
27074
- return {
27075
- content: [{ type: "text", text: "agenr_retire failed: entry_id is required" }]
27076
- };
27077
- }
27681
+ const selector = validateExclusiveSelector(params);
27078
27682
  const lookup = await lookupRetireCandidates({
27079
- id: entryId,
27683
+ id: selector.entryId,
27684
+ subject: selector.subject,
27080
27685
  dbPath
27081
27686
  });
27082
- if (lookup.candidates.length === 0) {
27687
+ const candidate = lookup.candidates[0];
27688
+ if (!candidate) {
27083
27689
  return {
27084
- content: [{ type: "text", text: `agenr_retire failed: No active entry found with id: ${entryId}` }]
27690
+ content: [{ type: "text", text: `agenr_retire failed: ${formatMissingEntryError(selector)}` }]
27085
27691
  };
27086
27692
  }
27087
27693
  const retired = await executeRetire({
27088
- id: entryId,
27694
+ id: candidate.id,
27089
27695
  reason: asString2(params.reason),
27090
27696
  persist: params.persist === true,
27091
27697
  dbPath
27092
27698
  });
27093
27699
  if (retired.retiredCount === 0) {
27094
27700
  return {
27095
- content: [{ type: "text", text: `agenr_retire failed: No active entry found with id: ${entryId}` }]
27701
+ content: [{ type: "text", text: `agenr_retire failed: ${formatMissingEntryError(selector)}` }]
27096
27702
  };
27097
27703
  }
27098
27704
  return {
27099
- content: [{ type: "text", text: `Retired entry ${entryId}.` }]
27705
+ content: [{
27706
+ type: "text",
27707
+ text: `Retired entry ${formatEntryLabel(candidate.id, selector.subject ? candidate.subject : void 0)}.`
27708
+ }]
27100
27709
  };
27101
27710
  } catch (error) {
27102
27711
  return {
@@ -27106,42 +27715,60 @@ async function runRetireTool(params, dbPath) {
27106
27715
  }
27107
27716
  async function runUpdateTool(params, dbPath) {
27108
27717
  try {
27109
- const entryId = asString2(params.entry_id);
27718
+ const selector = validateExclusiveSelector(params);
27719
+ const importanceProvided = Object.prototype.hasOwnProperty.call(params, "importance");
27110
27720
  const importance = asNumber(params.importance);
27111
- if (!entryId) {
27721
+ if (importanceProvided && (importance === void 0 || !Number.isInteger(importance) || importance < 1 || importance > 10)) {
27112
27722
  return {
27113
- content: [{ type: "text", text: "agenr_update failed: entry_id is required" }]
27723
+ content: [{ type: "text", text: "agenr_update failed: importance must be an integer between 1 and 10" }]
27114
27724
  };
27115
27725
  }
27116
- if (typeof importance !== "number" || !Number.isInteger(importance) || importance < 1 || importance > 10) {
27726
+ const expiry = normalizeOptionalUpdateExpiry(params.expiry);
27727
+ if (importance === void 0 && expiry === void 0) {
27117
27728
  return {
27118
- content: [{ type: "text", text: "agenr_update failed: importance must be an integer between 1 and 10" }]
27729
+ content: [{ type: "text", text: "agenr_update failed: at least one of importance or expiry is required" }]
27119
27730
  };
27120
27731
  }
27121
27732
  const lookup = await lookupUpdateCandidate({
27122
- id: entryId,
27733
+ id: selector.entryId,
27734
+ subject: selector.subject,
27123
27735
  dbPath
27124
27736
  });
27125
- if (!lookup.candidate) {
27737
+ const candidate = lookup.candidate;
27738
+ if (!candidate) {
27126
27739
  return {
27127
- content: [{ type: "text", text: `agenr_update failed: No active entry found with id: ${entryId}` }]
27740
+ content: [{ type: "text", text: `agenr_update failed: ${formatMissingEntryError(selector)}` }]
27128
27741
  };
27129
27742
  }
27130
27743
  const updated = await executeUpdate({
27131
- id: entryId,
27132
- importance,
27744
+ id: candidate.id,
27745
+ ...importance !== void 0 ? { importance } : {},
27746
+ ...expiry !== void 0 ? { expiry } : {},
27133
27747
  dbPath
27134
27748
  });
27749
+ const entryLabel = formatEntryLabel(candidate.id, selector.subject ? candidate.subject : void 0);
27135
27750
  if (!updated.updated) {
27136
27751
  return {
27137
- content: [{ type: "text", text: `Entry ${entryId} was not updated because it is no longer active.` }]
27752
+ content: [{ type: "text", text: `Entry ${entryLabel} was not updated because it is no longer active.` }]
27138
27753
  };
27139
27754
  }
27755
+ const changedParts = buildChangedUpdateParts({ importance, expiry }, updated);
27756
+ if (changedParts.length > 0) {
27757
+ return {
27758
+ content: [
27759
+ {
27760
+ type: "text",
27761
+ text: `Updated entry ${entryLabel}: ${changedParts.join(", ")}.`
27762
+ }
27763
+ ]
27764
+ };
27765
+ }
27766
+ const requestedParts = buildRequestedUpdateParts({ importance, expiry }, updated);
27140
27767
  return {
27141
27768
  content: [
27142
27769
  {
27143
27770
  type: "text",
27144
- text: updated.previousImportance === updated.importance ? `Entry ${entryId} already has importance ${updated.importance}.` : `Updated entry ${entryId} importance: ${updated.previousImportance} -> ${updated.importance}.`
27771
+ text: requestedParts.length === 1 ? `Entry ${entryLabel} already has ${requestedParts[0]}.` : `Entry ${entryLabel} already has ${requestedParts.join(" and ")}.`
27145
27772
  }
27146
27773
  ]
27147
27774
  };
@@ -32774,7 +33401,7 @@ function registerAgenrTools(api, params) {
32774
33401
  Type6.Union(
32775
33402
  [Type6.Literal("default"), Type6.Literal("session-start"), Type6.Literal("browse")],
32776
33403
  {
32777
- description: "Use session-start for fast bootstrap without embedding. Use browse for temporal browsing (date+importance, no query needed, no semantic search)."
33404
+ description: "Use session-start for fast bootstrap without embedding. Use browse for recency-dominant temporal browsing with secondary importance tie-breaking (no query needed, no semantic search)."
32778
33405
  }
32779
33406
  )
32780
33407
  ),
@@ -32803,7 +33430,8 @@ function registerAgenrTools(api, params) {
32803
33430
  return makeDisabledToolResult();
32804
33431
  }
32805
33432
  const dbPath = runtimeConfig?.dbPath;
32806
- const result = await runRecallTool(toolParams, void 0, dbPath);
33433
+ const defaultProject = runtimeConfig?.project?.trim() || void 0;
33434
+ const result = await runRecallTool(toolParams, defaultProject, dbPath);
32807
33435
  const sessionKey = sessionRef.current;
32808
33436
  if (sessionKey && result.agentToolRecallSurface) {
32809
33437
  const addedCount = createSessionSurfacedMemoryService(sessionKey).recordByIntent(
@@ -32992,11 +33620,22 @@ function registerAgenrTools(api, params) {
32992
33620
  name: "agenr_retire",
32993
33621
  label: "Agenr Retire",
32994
33622
  description: "Mark a memory entry as retired (soft delete). Retired entries are excluded from all recall.",
32995
- parameters: Type6.Object({
32996
- entry_id: Type6.String({ description: "Entry ID to retire." }),
32997
- reason: Type6.Optional(Type6.String({ description: "Retirement reason." })),
32998
- persist: Type6.Optional(Type6.Boolean({ description: "Persist retirement to ledger." }))
32999
- }),
33623
+ parameters: Type6.Object(
33624
+ {
33625
+ entry_id: Type6.Optional(Type6.String({ description: "Entry ID to retire." })),
33626
+ subject: Type6.Optional(
33627
+ Type6.String({ description: "Subject string to match. Mutually exclusive with entry_id." })
33628
+ ),
33629
+ reason: Type6.Optional(Type6.String({ description: "Retirement reason." })),
33630
+ persist: Type6.Optional(Type6.Boolean({ description: "Persist retirement to ledger." }))
33631
+ },
33632
+ {
33633
+ anyOf: [
33634
+ { required: ["entry_id"] },
33635
+ { required: ["subject"] }
33636
+ ]
33637
+ }
33638
+ ),
33000
33639
  async execute(_toolCallId, toolParams) {
33001
33640
  const runtimeConfig = api.pluginConfig;
33002
33641
  if (runtimeConfig?.enabled === false) {
@@ -33011,11 +33650,37 @@ function registerAgenrTools(api, params) {
33011
33650
  {
33012
33651
  name: "agenr_update",
33013
33652
  label: "Agenr Update",
33014
- description: "Update an existing memory entry in place. Currently supports importance only.",
33015
- parameters: Type6.Object({
33016
- entry_id: Type6.String({ description: "Entry ID to update." }),
33017
- importance: Type6.Integer({ minimum: 1, maximum: 10, description: "New importance score." })
33018
- }),
33653
+ description: "Update an existing memory entry in place. Currently supports importance and expiry.",
33654
+ parameters: Type6.Object(
33655
+ {
33656
+ entry_id: Type6.Optional(Type6.String({ description: "Entry ID to update." })),
33657
+ subject: Type6.Optional(
33658
+ Type6.String({
33659
+ description: "Subject string to match. Mutually exclusive with entry_id."
33660
+ })
33661
+ ),
33662
+ importance: Type6.Optional(
33663
+ Type6.Integer({ minimum: 1, maximum: 10, description: "New importance score." })
33664
+ ),
33665
+ expiry: optionalExpirySchema("New expiry tier: core, permanent, or temporary.")
33666
+ },
33667
+ {
33668
+ allOf: [
33669
+ {
33670
+ anyOf: [
33671
+ { required: ["entry_id"] },
33672
+ { required: ["subject"] }
33673
+ ]
33674
+ },
33675
+ {
33676
+ anyOf: [
33677
+ { required: ["importance"] },
33678
+ { required: ["expiry"] }
33679
+ ]
33680
+ }
33681
+ ]
33682
+ }
33683
+ ),
33019
33684
  async execute(_toolCallId, toolParams) {
33020
33685
  const runtimeConfig = api.pluginConfig;
33021
33686
  if (runtimeConfig?.enabled === false) {