@agenr/openclaw-plugin 0.12.1 → 0.12.3

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
@@ -107,7 +107,7 @@ var init_logger = __esm({
107
107
  });
108
108
 
109
109
  // src/db/schema/definitions.ts
110
- var CREATE_IDX_ENTRIES_EMBEDDING_SQL, CREATE_ENTRIES_FTS_TABLE_SQL, CREATE_ENTRIES_FTS_TRIGGER_AI_SQL, CREATE_ENTRIES_FTS_TRIGGER_AD_SQL, CREATE_ENTRIES_FTS_TRIGGER_AU_SQL, CREATE_TABLE_AND_TRIGGER_STATEMENTS, COLUMN_MIGRATIONS, CREATE_INDEX_STATEMENTS;
110
+ var CREATE_IDX_ENTRIES_EMBEDDING_SQL, CREATE_ENTRIES_FTS_TABLE_SQL, CREATE_ENTRIES_FTS_TRIGGER_AI_SQL, CREATE_ENTRIES_FTS_TRIGGER_AD_SQL, CREATE_ENTRIES_FTS_TRIGGER_AU_SQL, CREATE_SEEN_SESSIONS_TABLE_SQL, CREATE_SEEN_SESSIONS_SEEN_AT_INDEX_SQL, CREATE_SESSION_PROJECTS_TABLE_SQL, CREATE_SESSION_IDENTITY_BREADCRUMBS_TABLE_SQL, CREATE_SESSION_IDENTITY_BREADCRUMBS_UPDATED_AT_INDEX_SQL, ADD_SESSION_PROJECT_STATE_COLUMN_SQL, OPENCLAW_PLUGIN_SCHEMA_STATEMENTS, OPENCLAW_PLUGIN_COLUMN_MIGRATIONS, CREATE_TABLE_AND_TRIGGER_STATEMENTS, COLUMN_MIGRATIONS, CREATE_INDEX_STATEMENTS;
111
111
  var init_definitions = __esm({
112
112
  "src/db/schema/definitions.ts"() {
113
113
  "use strict";
@@ -146,6 +146,60 @@ var init_definitions = __esm({
146
146
  WHERE new.retired = 0 AND new.superseded_by IS NULL;
147
147
  END
148
148
  `;
149
+ CREATE_SEEN_SESSIONS_TABLE_SQL = `
150
+ CREATE TABLE IF NOT EXISTS seen_sessions (
151
+ dedupe_key TEXT PRIMARY KEY,
152
+ seen_at INTEGER NOT NULL
153
+ )
154
+ `;
155
+ CREATE_SEEN_SESSIONS_SEEN_AT_INDEX_SQL = `
156
+ CREATE INDEX IF NOT EXISTS idx_seen_sessions_seen_at
157
+ ON seen_sessions(seen_at)
158
+ `;
159
+ CREATE_SESSION_PROJECTS_TABLE_SQL = `
160
+ CREATE TABLE IF NOT EXISTS session_projects (
161
+ session_key TEXT PRIMARY KEY,
162
+ project TEXT NOT NULL,
163
+ state TEXT NOT NULL DEFAULT 'set',
164
+ updated_at TEXT NOT NULL
165
+ )
166
+ `;
167
+ CREATE_SESSION_IDENTITY_BREADCRUMBS_TABLE_SQL = `
168
+ CREATE TABLE IF NOT EXISTS session_identity_breadcrumbs (
169
+ session_id TEXT PRIMARY KEY,
170
+ session_key TEXT,
171
+ family TEXT NOT NULL,
172
+ lane_id TEXT,
173
+ origin_surface TEXT,
174
+ delivery_channel TEXT,
175
+ family_source TEXT,
176
+ lane_source TEXT,
177
+ created_at TEXT NOT NULL,
178
+ updated_at TEXT NOT NULL
179
+ )
180
+ `;
181
+ CREATE_SESSION_IDENTITY_BREADCRUMBS_UPDATED_AT_INDEX_SQL = `
182
+ CREATE INDEX IF NOT EXISTS idx_session_identity_breadcrumbs_updated_at
183
+ ON session_identity_breadcrumbs(updated_at)
184
+ `;
185
+ ADD_SESSION_PROJECT_STATE_COLUMN_SQL = `
186
+ ALTER TABLE session_projects
187
+ ADD COLUMN state TEXT NOT NULL DEFAULT 'set'
188
+ `;
189
+ OPENCLAW_PLUGIN_SCHEMA_STATEMENTS = [
190
+ CREATE_SEEN_SESSIONS_TABLE_SQL,
191
+ CREATE_SEEN_SESSIONS_SEEN_AT_INDEX_SQL,
192
+ CREATE_SESSION_PROJECTS_TABLE_SQL,
193
+ CREATE_SESSION_IDENTITY_BREADCRUMBS_TABLE_SQL,
194
+ CREATE_SESSION_IDENTITY_BREADCRUMBS_UPDATED_AT_INDEX_SQL
195
+ ];
196
+ OPENCLAW_PLUGIN_COLUMN_MIGRATIONS = [
197
+ {
198
+ table: "session_projects",
199
+ column: "state",
200
+ sql: ADD_SESSION_PROJECT_STATE_COLUMN_SQL
201
+ }
202
+ ];
149
203
  CREATE_TABLE_AND_TRIGGER_STATEMENTS = [
150
204
  `
151
205
  CREATE TABLE IF NOT EXISTS _meta (
@@ -419,9 +473,11 @@ var init_definitions = __esm({
419
473
  CREATE_ENTRIES_FTS_TABLE_SQL,
420
474
  CREATE_ENTRIES_FTS_TRIGGER_AI_SQL,
421
475
  CREATE_ENTRIES_FTS_TRIGGER_AD_SQL,
422
- CREATE_ENTRIES_FTS_TRIGGER_AU_SQL
476
+ CREATE_ENTRIES_FTS_TRIGGER_AU_SQL,
477
+ ...OPENCLAW_PLUGIN_SCHEMA_STATEMENTS
423
478
  ];
424
479
  COLUMN_MIGRATIONS = [
480
+ ...OPENCLAW_PLUGIN_COLUMN_MIGRATIONS,
425
481
  {
426
482
  table: "co_recall_edges",
427
483
  column: "edge_type",
@@ -693,6 +749,202 @@ var init_definitions = __esm({
693
749
  }
694
750
  });
695
751
 
752
+ // src/db/vector-index.ts
753
+ function toNumber(value) {
754
+ if (typeof value === "number") {
755
+ return value;
756
+ }
757
+ if (typeof value === "bigint") {
758
+ return Number(value);
759
+ }
760
+ if (typeof value === "string" && value.trim()) {
761
+ return Number(value);
762
+ }
763
+ return Number.NaN;
764
+ }
765
+ function toFiniteCount(value) {
766
+ const numeric = toNumber(value);
767
+ if (!Number.isFinite(numeric)) {
768
+ return 0;
769
+ }
770
+ return Math.max(0, Math.trunc(numeric));
771
+ }
772
+ function normalizeRowid(value) {
773
+ const numeric = toNumber(value);
774
+ if (!Number.isFinite(numeric)) {
775
+ return null;
776
+ }
777
+ const rowid = Math.trunc(numeric);
778
+ return rowid > 0 ? rowid : null;
779
+ }
780
+ function formatShadowMismatch(stats) {
781
+ const details = [`active=${stats.embeddingCount}`, `shadow=${stats.shadowCount}`];
782
+ if (stats.missingRowids.length > 0) {
783
+ details.push(`missing=${stats.missingRowids.length}`);
784
+ }
785
+ return details.join(", ");
786
+ }
787
+ async function rollbackQuietly(db) {
788
+ try {
789
+ await db.execute("ROLLBACK");
790
+ } catch {
791
+ }
792
+ }
793
+ async function dropRepairTableQuietly(db) {
794
+ try {
795
+ await db.execute(`DROP TABLE IF EXISTS ${TEMP_REPAIR_TABLE}`);
796
+ } catch {
797
+ }
798
+ }
799
+ async function repairMissingRowids(db, missingRowids) {
800
+ if (missingRowids.length === 0) {
801
+ return;
802
+ }
803
+ await db.execute(`DROP TABLE IF EXISTS ${TEMP_REPAIR_TABLE}`);
804
+ await db.execute(`
805
+ CREATE TEMP TABLE ${TEMP_REPAIR_TABLE} (
806
+ rid INTEGER PRIMARY KEY,
807
+ emb BLOB NOT NULL
808
+ )
809
+ `);
810
+ try {
811
+ for (let index = 0; index < missingRowids.length; index += REPAIR_CHUNK_SIZE) {
812
+ const chunk = missingRowids.slice(index, index + REPAIR_CHUNK_SIZE);
813
+ if (chunk.length === 0) {
814
+ continue;
815
+ }
816
+ const placeholders = chunk.map(() => "?").join(", ");
817
+ await db.execute(`DELETE FROM ${TEMP_REPAIR_TABLE}`);
818
+ await db.execute({
819
+ sql: `
820
+ INSERT INTO ${TEMP_REPAIR_TABLE} (rid, emb)
821
+ SELECT rowid, embedding
822
+ FROM entries
823
+ WHERE rowid IN (${placeholders})
824
+ `,
825
+ args: chunk
826
+ });
827
+ await db.execute({
828
+ sql: `
829
+ UPDATE entries
830
+ SET embedding = NULL
831
+ WHERE rowid IN (${placeholders})
832
+ `,
833
+ args: chunk
834
+ });
835
+ await db.execute({
836
+ sql: `
837
+ UPDATE entries
838
+ SET embedding = (
839
+ SELECT emb
840
+ FROM ${TEMP_REPAIR_TABLE}
841
+ WHERE rid = entries.rowid
842
+ )
843
+ WHERE rowid IN (${placeholders})
844
+ `,
845
+ args: chunk
846
+ });
847
+ }
848
+ } finally {
849
+ await dropRepairTableQuietly(db);
850
+ }
851
+ }
852
+ async function getVectorIndexShadowStats(db) {
853
+ const countsResult = await db.execute(`
854
+ SELECT
855
+ (SELECT COUNT(*) FROM entries WHERE ${ACTIVE_EMBEDDED_WHERE_SQL}) AS embedding_count,
856
+ (SELECT COUNT(*) FROM idx_entries_embedding_shadow) AS shadow_count
857
+ `);
858
+ const countsRow = countsResult.rows[0];
859
+ const embeddingCount = toFiniteCount(countsRow?.embedding_count);
860
+ const shadowCount = toFiniteCount(countsRow?.shadow_count);
861
+ const missingResult = await db.execute(`
862
+ SELECT e.rowid AS rowid
863
+ FROM entries AS e
864
+ LEFT JOIN idx_entries_embedding_shadow AS s
865
+ ON s.index_key = e.rowid
866
+ WHERE e.embedding IS NOT NULL
867
+ AND e.retired = 0
868
+ AND e.superseded_by IS NULL
869
+ AND s.index_key IS NULL
870
+ ORDER BY e.rowid ASC
871
+ `);
872
+ const missingRowids = missingResult.rows.map((row) => normalizeRowid(row.rowid)).filter((rowid) => rowid !== null);
873
+ return {
874
+ embeddingCount,
875
+ shadowCount,
876
+ missingRowids
877
+ };
878
+ }
879
+ async function rebuildVectorIndex(db, options) {
880
+ const start = Date.now();
881
+ const onLog = options?.onLog ?? (() => void 0);
882
+ await db.execute("BEGIN IMMEDIATE");
883
+ try {
884
+ await db.execute("DROP INDEX IF EXISTS idx_entries_embedding");
885
+ await db.execute(CREATE_IDX_ENTRIES_EMBEDDING_SQL);
886
+ await db.execute(SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL);
887
+ const beforeRepair = await getVectorIndexShadowStats(db);
888
+ let repairedCount = 0;
889
+ if (beforeRepair.missingRowids.length > 0) {
890
+ repairedCount = beforeRepair.missingRowids.length;
891
+ onLog(`[vector-index] repairing ${repairedCount} entries missing from shadow table`);
892
+ await repairMissingRowids(db, beforeRepair.missingRowids);
893
+ }
894
+ const afterRepair = await getVectorIndexShadowStats(db);
895
+ if (afterRepair.missingRowids.length > 0 || afterRepair.shadowCount !== afterRepair.embeddingCount) {
896
+ throw new Error(`Vector index shadow table desynced after rebuild (${formatShadowMismatch(afterRepair)})`);
897
+ }
898
+ if (afterRepair.embeddingCount > 0) {
899
+ const verify = await db.execute(`
900
+ SELECT count(*) AS count
901
+ FROM vector_top_k(
902
+ 'idx_entries_embedding',
903
+ (SELECT embedding FROM entries WHERE ${ACTIVE_EMBEDDED_WHERE_SQL} LIMIT 1),
904
+ 1
905
+ )
906
+ `);
907
+ const verifyCount = toFiniteCount(verify.rows[0]?.count);
908
+ if (verifyCount !== 1) {
909
+ throw new Error(`Vector index rebuild verification failed (expected 1, got ${verifyCount})`);
910
+ }
911
+ }
912
+ await db.execute("COMMIT");
913
+ const durationMs = Date.now() - start;
914
+ onLog(
915
+ `[vector-index] rebuilt for ${afterRepair.embeddingCount} entries (${afterRepair.shadowCount} shadow rows, ${repairedCount} repaired, ${durationMs}ms)`
916
+ );
917
+ return {
918
+ embeddingCount: afterRepair.embeddingCount,
919
+ shadowCount: afterRepair.shadowCount,
920
+ repairedCount,
921
+ durationMs
922
+ };
923
+ } catch (error) {
924
+ await rollbackQuietly(db);
925
+ throw error;
926
+ }
927
+ }
928
+ var ACTIVE_EMBEDDED_WHERE_SQL, SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL, TEMP_REPAIR_TABLE, REPAIR_CHUNK_SIZE;
929
+ var init_vector_index = __esm({
930
+ "src/db/vector-index.ts"() {
931
+ "use strict";
932
+ init_definitions();
933
+ ACTIVE_EMBEDDED_WHERE_SQL = `
934
+ embedding IS NOT NULL
935
+ AND retired = 0
936
+ AND superseded_by IS NULL
937
+ `;
938
+ SELF_UPDATE_ACTIVE_EMBEDDINGS_SQL = `
939
+ UPDATE entries
940
+ SET embedding = embedding
941
+ WHERE ${ACTIVE_EMBEDDED_WHERE_SQL}
942
+ `;
943
+ TEMP_REPAIR_TABLE = "_vec_repair";
944
+ REPAIR_CHUNK_SIZE = 100;
945
+ }
946
+ });
947
+
696
948
  // src/db/schema/fts.ts
697
949
  async function rebuildEntriesFts(db) {
698
950
  await db.execute("INSERT INTO entries_fts(entries_fts) VALUES('delete-all')");
@@ -727,31 +979,16 @@ async function rebuildFtsAndTriggers(db) {
727
979
  throw error;
728
980
  }
729
981
  }
730
- async function rebuildVectorIndex(db) {
982
+ async function rebuildVectorIndex2(db) {
731
983
  try {
732
984
  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 {
985
+ const stats = await getVectorIndexShadowStats(db);
986
+ if (stats.shadowCount === stats.embeddingCount && stats.missingRowids.length === 0) {
987
+ return;
752
988
  }
753
- throw fallbackError;
989
+ } catch {
754
990
  }
991
+ await rebuildVectorIndex(db);
755
992
  }
756
993
  async function clearBulkIngestMeta(db) {
757
994
  await db.execute({ sql: "DELETE FROM _meta WHERE key = ?", args: [BULK_INGEST_META_KEY] });
@@ -777,6 +1014,7 @@ var BULK_INGEST_META_KEY;
777
1014
  var init_bulk_ingest = __esm({
778
1015
  "src/db/schema/bulk-ingest.ts"() {
779
1016
  "use strict";
1017
+ init_vector_index();
780
1018
  init_definitions();
781
1019
  init_fts();
782
1020
  BULK_INGEST_META_KEY = "bulk_ingest_state";
@@ -1358,15 +1596,10 @@ async function repairVectorIndexIfNeeded(client) {
1358
1596
  if (probeCount > 0) {
1359
1597
  return;
1360
1598
  }
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
- `);
1599
+ } catch {
1600
+ }
1601
+ try {
1602
+ await rebuildVectorIndex(client);
1370
1603
  } catch {
1371
1604
  }
1372
1605
  }
@@ -1395,6 +1628,7 @@ var init_init = __esm({
1395
1628
  init_version();
1396
1629
  init_reflection_importance_cap();
1397
1630
  init_reflection_removal();
1631
+ init_vector_index();
1398
1632
  init_definitions();
1399
1633
  init_entry_provenance();
1400
1634
  init_fts();
@@ -1596,7 +1830,7 @@ async function checkAndRecoverBulkIngest(client) {
1596
1830
  }
1597
1831
  }
1598
1832
  if (indexCount < 1) {
1599
- await rebuildVectorIndex(client);
1833
+ await rebuildVectorIndex2(client);
1600
1834
  }
1601
1835
  try {
1602
1836
  const integrityResult = await client.execute("PRAGMA integrity_check");
@@ -4428,7 +4662,7 @@ var init_source_classification = __esm({
4428
4662
  });
4429
4663
 
4430
4664
  // src/utils/entry-utils.ts
4431
- function toNumber(value) {
4665
+ function toNumber2(value) {
4432
4666
  if (typeof value === "number") {
4433
4667
  return value;
4434
4668
  }
@@ -4537,10 +4771,10 @@ function mapRawStoredEntry(row, options) {
4537
4771
  const entryKind = toStringValue(row.entry_kind).trim();
4538
4772
  const temporalClass = toStringValue(row.temporal_class).trim();
4539
4773
  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);
4774
+ const importanceRaw = toNumber2(row.importance);
4775
+ const confirmationsRaw = toNumber2(row.confirmations);
4776
+ const contradictionsRaw = toNumber2(row.contradictions);
4777
+ const qualityScoreRaw = toNumber2(row.quality_score);
4544
4778
  const subjectEntity = toStringValue(row.subject_entity).trim();
4545
4779
  const subjectAttribute = toStringValue(row.subject_attribute).trim();
4546
4780
  const subjectKey2 = toStringValue(row.subject_key).trim();
@@ -4548,8 +4782,8 @@ function mapRawStoredEntry(row, options) {
4548
4782
  const claimObject = toStringValue(row.claim_object).trim();
4549
4783
  const claimRole = toStringValue(row.claim_role).trim();
4550
4784
  const normalizedClaimRole = normalizeClaimRole2(claimRole);
4551
- const claimConfidenceRaw = toNumber(row.claim_confidence);
4552
- const retiredRaw = toNumber(row.retired);
4785
+ const claimConfidenceRaw = toNumber2(row.claim_confidence);
4786
+ const retiredRaw = toNumber2(row.retired);
4553
4787
  const clusterId = toStringValue(row.cluster_id).trim();
4554
4788
  const sourceClass = normalizeSourceClass(toStringValue(row.source_class));
4555
4789
  const suppressedContextsRaw = toStringValue(row.suppressed_contexts);
@@ -4752,14 +4986,14 @@ function normalizeEntryIds(entryIds) {
4752
4986
  );
4753
4987
  }
4754
4988
  function parseCount(value) {
4755
- const parsed = toNumber(value);
4989
+ const parsed = toNumber2(value);
4756
4990
  if (!Number.isFinite(parsed) || parsed <= 0) {
4757
4991
  return 0;
4758
4992
  }
4759
4993
  return Math.floor(parsed);
4760
4994
  }
4761
4995
  function parseSignalTotal(value) {
4762
- const parsed = toNumber(value);
4996
+ const parsed = toNumber2(value);
4763
4997
  if (!Number.isFinite(parsed) || parsed <= 0) {
4764
4998
  return 0;
4765
4999
  }
@@ -4773,7 +5007,7 @@ function parseIsoTimestamp(value) {
4773
5007
  return normalized.length > 0 ? normalized : null;
4774
5008
  }
4775
5009
  function parseGapDays(value) {
4776
- const parsed = toNumber(value);
5010
+ const parsed = toNumber2(value);
4777
5011
  if (!Number.isFinite(parsed) || parsed <= 0) {
4778
5012
  return 0;
4779
5013
  }
@@ -4979,7 +5213,7 @@ async function getStoredEntryById(db, id) {
4979
5213
  }
4980
5214
  async function getTotalEntries(db) {
4981
5215
  const result = await db.execute("SELECT COUNT(*) AS count FROM entries");
4982
- const count = toNumber(result.rows[0]?.count);
5216
+ const count = toNumber2(result.rows[0]?.count);
4983
5217
  return Number.isFinite(count) ? count : 0;
4984
5218
  }
4985
5219
  async function hasContentHash(db, contentHash) {
@@ -5344,7 +5578,7 @@ async function findRecentEntriesBySubjectTypeAndSourceFile(db, requests) {
5344
5578
  normalizedSubject: toStringValue(row.lookup_normalized_subject),
5345
5579
  type: toStringValue(row.lookup_type),
5346
5580
  sourceFile: toStringValue(row.lookup_source_file),
5347
- withinHours: toNumber(row.lookup_within_hours),
5581
+ withinHours: toNumber2(row.lookup_within_hours),
5348
5582
  entry: mapEntryRow(row, { tags: tagsById.get(entryId) ?? [], includeEmbedding: true })
5349
5583
  });
5350
5584
  }
@@ -5879,6 +6113,10 @@ var init_similarity = __esm({
5879
6113
 
5880
6114
  // src/utils/content-fingerprint.ts
5881
6115
  import { createHash as createHash3 } from "crypto";
6116
+ function computeNormContentHash(content) {
6117
+ const normalized = content.toLowerCase().replace(/\s+/g, " ").trim().replace(/[^\w\s]/g, "");
6118
+ return createHash3("sha256").update(normalized).digest("hex");
6119
+ }
5882
6120
  var init_content_fingerprint = __esm({
5883
6121
  "src/utils/content-fingerprint.ts"() {
5884
6122
  "use strict";
@@ -6278,12 +6516,12 @@ async function getConflictShadowSummary(db) {
6278
6516
  continue;
6279
6517
  }
6280
6518
  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
6519
+ totalEvaluated: Number.isFinite(toNumber2(record.total)) ? toNumber2(record.total) : 0,
6520
+ blockedAtCurrent: Number.isFinite(toNumber2(record.blocked_current)) ? toNumber2(record.blocked_current) : 0,
6521
+ currentOnlyBand: Number.isFinite(toNumber2(record.current_only)) ? toNumber2(record.current_only) : 0,
6522
+ wouldStillBlockAt090: Number.isFinite(toNumber2(record.still_block_090)) ? toNumber2(record.still_block_090) : 0,
6523
+ wouldStillBlockAt095: Number.isFinite(toNumber2(record.still_block_095)) ? toNumber2(record.still_block_095) : 0,
6524
+ silentCoexistBand: Number.isFinite(toNumber2(record.silent_coexist)) ? toNumber2(record.silent_coexist) : 0
6287
6525
  };
6288
6526
  byRelation[relation] = relationSummary;
6289
6527
  totalEvaluated += relationSummary.totalEvaluated;
@@ -6732,7 +6970,7 @@ async function getCoRecallEdgeCounts(db) {
6732
6970
  if (!entryId) {
6733
6971
  continue;
6734
6972
  }
6735
- counts.set(entryId, Math.max(0, toNumber(row.edge_count)));
6973
+ counts.set(entryId, Math.max(0, toNumber2(row.edge_count)));
6736
6974
  }
6737
6975
  return counts;
6738
6976
  }
@@ -7042,10 +7280,10 @@ async function evolveQualityScores(db, options = {}, config) {
7042
7280
  return {
7043
7281
  id,
7044
7282
  type: toStringValue(row.type),
7045
- importance: Math.max(0, toNumber(row.importance)),
7283
+ importance: Math.max(0, toNumber2(row.importance)),
7046
7284
  recallCount: Math.max(0, metrics?.totalCount ?? 0),
7047
- confirmations: Math.max(0, toNumber(row.confirmations)),
7048
- qualityScore: toNumber(row.quality_score),
7285
+ confirmations: Math.max(0, toNumber2(row.confirmations)),
7286
+ qualityScore: toNumber2(row.quality_score),
7049
7287
  lastRecalledAt: metrics?.lastLiveRecalledAt ?? metrics?.lastAnyRecalledAt ?? "",
7050
7288
  createdAt: toStringValue(row.created_at)
7051
7289
  };
@@ -7402,8 +7640,8 @@ async function fetchRecalledEntryMetrics(db, entryIds) {
7402
7640
  });
7403
7641
  return result.rows.map((row) => ({
7404
7642
  id: toStringValue(row.id),
7405
- qualityScore: toNumber(row.quality_score),
7406
- recallEventsCount: toNumber(row.recall_events_count)
7643
+ qualityScore: toNumber2(row.quality_score),
7644
+ recallEventsCount: toNumber2(row.recall_events_count)
7407
7645
  }));
7408
7646
  }
7409
7647
  async function processBeforeResetRecallFeedback(request, deps) {
@@ -7481,66 +7719,6 @@ var init_watcher_demotion = __esm({
7481
7719
  }
7482
7720
  });
7483
7721
 
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
7722
  // src/runtime/db-command-defaults.ts
7545
7723
  function resolveDbCommandDefaults() {
7546
7724
  const shared = resolveDefaultAppRuntimeDeps();
@@ -7553,7 +7731,7 @@ function resolveDbCommandDefaults() {
7553
7731
  getConflictShadowSummary,
7554
7732
  resetDb,
7555
7733
  walCheckpoint,
7556
- rebuildVectorIndex: rebuildVectorIndex2,
7734
+ rebuildVectorIndex,
7557
7735
  evolveQualityScores
7558
7736
  };
7559
7737
  }
@@ -8430,6 +8608,7 @@ function formatMidSessionRecall(results, options = {}) {
8430
8608
 
8431
8609
  // src/runtime/openclaw-defaults.ts
8432
8610
  init_client();
8611
+ init_schema();
8433
8612
 
8434
8613
  // src/db/signals.ts
8435
8614
  async function fetchNewSignalEntries(db, sinceSeq, minImportance, limit, maxAgeSec = 0) {
@@ -8546,7 +8725,9 @@ function resolveOpenClawPluginDbDefaults() {
8546
8725
  return {
8547
8726
  getDb: shared.getDbFn,
8548
8727
  initDb: shared.initDbFn,
8549
- closeDb: shared.closeDbFn
8728
+ closeDb: shared.closeDbFn,
8729
+ pluginSchemaStatements: OPENCLAW_PLUGIN_SCHEMA_STATEMENTS,
8730
+ pluginColumnMigrations: OPENCLAW_PLUGIN_COLUMN_MIGRATIONS
8550
8731
  };
8551
8732
  }
8552
8733
  function resolveOpenClawSignalDefaults() {
@@ -8566,46 +8747,6 @@ function resolveOpenClawHandoffDefaults() {
8566
8747
  }
8567
8748
 
8568
8749
  // src/openclaw-plugin/plugin-db.ts
8569
- var CREATE_SEEN_SESSIONS_TABLE_SQL = `
8570
- CREATE TABLE IF NOT EXISTS seen_sessions (
8571
- dedupe_key TEXT PRIMARY KEY,
8572
- seen_at INTEGER NOT NULL
8573
- )
8574
- `;
8575
- var CREATE_SEEN_SESSIONS_SEEN_AT_INDEX_SQL = `
8576
- CREATE INDEX IF NOT EXISTS idx_seen_sessions_seen_at
8577
- ON seen_sessions(seen_at)
8578
- `;
8579
- var CREATE_SESSION_PROJECTS_TABLE_SQL = `
8580
- CREATE TABLE IF NOT EXISTS session_projects (
8581
- session_key TEXT PRIMARY KEY,
8582
- project TEXT NOT NULL,
8583
- state TEXT NOT NULL DEFAULT 'set',
8584
- updated_at TEXT NOT NULL
8585
- )
8586
- `;
8587
- var CREATE_SESSION_IDENTITY_BREADCRUMBS_TABLE_SQL = `
8588
- CREATE TABLE IF NOT EXISTS session_identity_breadcrumbs (
8589
- session_id TEXT PRIMARY KEY,
8590
- session_key TEXT,
8591
- family TEXT NOT NULL,
8592
- lane_id TEXT,
8593
- origin_surface TEXT,
8594
- delivery_channel TEXT,
8595
- family_source TEXT,
8596
- lane_source TEXT,
8597
- created_at TEXT NOT NULL,
8598
- updated_at TEXT NOT NULL
8599
- )
8600
- `;
8601
- var CREATE_SESSION_IDENTITY_BREADCRUMBS_UPDATED_AT_INDEX_SQL = `
8602
- CREATE INDEX IF NOT EXISTS idx_session_identity_breadcrumbs_updated_at
8603
- ON session_identity_breadcrumbs(updated_at)
8604
- `;
8605
- var ADD_SESSION_PROJECT_STATE_COLUMN_SQL = `
8606
- ALTER TABLE session_projects
8607
- ADD COLUMN state TEXT NOT NULL DEFAULT 'set'
8608
- `;
8609
8750
  var PLUGIN_DB_STATE_KEY = "__agenrOpenClawPluginDbState";
8610
8751
  function getPluginDbState() {
8611
8752
  const globalState = globalThis;
@@ -8625,18 +8766,19 @@ function closePluginDbState(state) {
8625
8766
  state.init = null;
8626
8767
  }
8627
8768
  async function ensurePluginDbTables(client) {
8628
- await resolveOpenClawPluginDbDefaults().initDb(client);
8629
- await client.execute(CREATE_SEEN_SESSIONS_TABLE_SQL);
8630
- await client.execute(CREATE_SEEN_SESSIONS_SEEN_AT_INDEX_SQL);
8631
- await client.execute(CREATE_SESSION_PROJECTS_TABLE_SQL);
8632
- await client.execute(CREATE_SESSION_IDENTITY_BREADCRUMBS_TABLE_SQL);
8633
- await client.execute(CREATE_SESSION_IDENTITY_BREADCRUMBS_UPDATED_AT_INDEX_SQL);
8634
- const sessionProjectsInfo = await client.execute("PRAGMA table_info(session_projects)");
8635
- const hasStateColumn = sessionProjectsInfo.rows.some(
8636
- (row) => String(row.name) === "state"
8637
- );
8638
- if (!hasStateColumn) {
8639
- await client.execute(ADD_SESSION_PROJECT_STATE_COLUMN_SQL);
8769
+ const defaults = resolveOpenClawPluginDbDefaults();
8770
+ await defaults.initDb(client);
8771
+ for (const statement of defaults.pluginSchemaStatements) {
8772
+ await client.execute(statement);
8773
+ }
8774
+ for (const migration of defaults.pluginColumnMigrations) {
8775
+ const tableInfo = await client.execute(`PRAGMA table_info(${migration.table})`);
8776
+ const hasColumn2 = tableInfo.rows.some(
8777
+ (row) => String(row.name) === migration.column
8778
+ );
8779
+ if (!hasColumn2) {
8780
+ await client.execute(migration.sql);
8781
+ }
8640
8782
  }
8641
8783
  }
8642
8784
  function registerDbShutdown(state) {
@@ -8882,6 +9024,47 @@ async function finalizeRecallResults(params) {
8882
9024
  return results;
8883
9025
  }
8884
9026
 
9027
+ // src/domain/recall/browse-selection.ts
9028
+ var DEFAULT_BROWSE_DIVERSITY_TARGET = 4;
9029
+ function normalizeEntryType(result) {
9030
+ const rawType = result.entry.type;
9031
+ return typeof rawType === "string" ? rawType.trim().toLowerCase() : "";
9032
+ }
9033
+ function diversifyBrowseResults(results, limit, diversityTarget = DEFAULT_BROWSE_DIVERSITY_TARGET) {
9034
+ const normalizedLimit = Math.max(0, Math.floor(limit));
9035
+ if (normalizedLimit === 0 || results.length === 0) {
9036
+ return [];
9037
+ }
9038
+ const normalizedDiversityTarget = Math.max(0, Math.floor(diversityTarget));
9039
+ const seedTarget = Math.min(normalizedLimit, normalizedDiversityTarget);
9040
+ const selected = [];
9041
+ const selectedIds = /* @__PURE__ */ new Set();
9042
+ const admittedTypes = /* @__PURE__ */ new Set();
9043
+ for (const result of results) {
9044
+ if (selected.length >= seedTarget) {
9045
+ break;
9046
+ }
9047
+ const normalizedType = normalizeEntryType(result);
9048
+ if (!normalizedType || admittedTypes.has(normalizedType)) {
9049
+ continue;
9050
+ }
9051
+ selected.push(result);
9052
+ selectedIds.add(result.entry.id);
9053
+ admittedTypes.add(normalizedType);
9054
+ }
9055
+ for (const result of results) {
9056
+ if (selected.length >= normalizedLimit) {
9057
+ break;
9058
+ }
9059
+ if (selectedIds.has(result.entry.id)) {
9060
+ continue;
9061
+ }
9062
+ selected.push(result);
9063
+ selectedIds.add(result.entry.id);
9064
+ }
9065
+ return selected;
9066
+ }
9067
+
8885
9068
  // src/db/recall/pipeline/retrieval.ts
8886
9069
  init_client2();
8887
9070
 
@@ -9045,6 +9228,8 @@ var DEFAULT_RECALL_SATURATION = 10;
9045
9228
  var DEFAULT_WARM_START_THRESHOLD = 3;
9046
9229
  var DEFAULT_SYNTHETIC_FLOOR = 0.1;
9047
9230
  var DEFAULT_AGENT_SOURCE_BONUS = 0.05;
9231
+ var BROWSE_RECENCY_WEIGHT = 0.8;
9232
+ var BROWSE_IMPORTANCE_WEIGHT = 0.2;
9048
9233
  var MISSING_RECALL_DAYS = 99999;
9049
9234
  var EMPTY_RECALL_METRICS = {
9050
9235
  totalCount: 0,
@@ -9166,7 +9351,9 @@ function browseRecencyFactor(entry, now, aroundDate, aroundRadius = DEFAULT_AROU
9166
9351
  return resolveRecallRecency(entry, now, aroundDate, aroundRadius);
9167
9352
  }
9168
9353
  function scoreBrowseEntry(entry, now, aroundDate, aroundRadius = DEFAULT_AROUND_RADIUS_DAYS) {
9169
- return clamp01((browseRecencyFactor(entry, now, aroundDate, aroundRadius) + importanceScore(entry.importance)) / 2);
9354
+ const recency2 = browseRecencyFactor(entry, now, aroundDate, aroundRadius);
9355
+ const importance = importanceScore(entry.importance);
9356
+ return clamp01(recency2 * BROWSE_RECENCY_WEIGHT + importance * BROWSE_IMPORTANCE_WEIGHT);
9170
9357
  }
9171
9358
  function scoreSessionEntry(entry, effectiveNow, freshnessNow, metricsMap, aroundDate, aroundRadius = DEFAULT_AROUND_RADIUS_DAYS, config) {
9172
9359
  return scoreEntryWithBreakdown(
@@ -9702,8 +9889,21 @@ async function fetchBrowseCandidates(params) {
9702
9889
  whereClauses.push("platform = ?");
9703
9890
  args.push(params.query.platform);
9704
9891
  }
9705
- args.push(params.limit);
9706
9892
  const whereClause = whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : "";
9893
+ const orderBy = params.aroundDate ? `
9894
+ ORDER BY
9895
+ ABS(julianday(created_at) - julianday(?)) ASC,
9896
+ importance DESC,
9897
+ created_at DESC
9898
+ ` : `
9899
+ ORDER BY
9900
+ created_at DESC,
9901
+ importance DESC
9902
+ `;
9903
+ if (params.aroundDate) {
9904
+ args.push(params.aroundDate.toISOString());
9905
+ }
9906
+ args.push(params.limit);
9707
9907
  const result = await params.db.execute({
9708
9908
  sql: `
9709
9909
  SELECT
@@ -9711,10 +9911,10 @@ async function fetchBrowseCandidates(params) {
9711
9911
  FROM entries
9712
9912
  ${whereClause}
9713
9913
  -- 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)
9914
+ -- Final order is determined by recency-first browse scoring and a post-score
9915
+ -- diversity pass. The over-fetch buffer (limit*3, min 50)
9716
9916
  -- ensures the final top-N are present in the candidate pool.
9717
- ORDER BY importance DESC, created_at DESC
9917
+ ${orderBy}
9718
9918
  LIMIT ?
9719
9919
  `,
9720
9920
  args
@@ -9947,6 +10147,7 @@ async function recallBrowseMode(params) {
9947
10147
  query: browseQuery,
9948
10148
  limit: browseLimit,
9949
10149
  now: params.now,
10150
+ aroundDate: params.prepared.temporal.aroundDate,
9950
10151
  projectFilter: params.prepared.scope.resolution.primaryFilter
9951
10152
  });
9952
10153
  const filtered = browseCandidates.filter(
@@ -9967,7 +10168,11 @@ async function recallBrowseMode(params) {
9967
10168
  aroundRadiusDays: params.prepared.temporal.aroundRadiusDays
9968
10169
  });
9969
10170
  scored.sort((left, right) => right.score - left.score);
9970
- return scored.slice(0, requestedLimit);
10171
+ const results = diversifyBrowseResults(scored, requestedLimit, DEFAULT_BROWSE_DIVERSITY_TARGET);
10172
+ if (params.trace) {
10173
+ params.trace.finalResultIds = results.map((result) => result.entry.subject ?? result.entry.id);
10174
+ }
10175
+ return results;
9971
10176
  }
9972
10177
  async function retrieveCandidates(params) {
9973
10178
  if (!params.prepared.retrievalSearchText) {
@@ -10377,11 +10582,12 @@ function resolvePreparedRecallScope(query, normalizedQuery) {
10377
10582
  const explicitWildcardProject = hasWildcardProjectOverride(query.project);
10378
10583
  const explicitProjects = explicitWildcardProject ? [] : parseProjectList(query.project);
10379
10584
  const explicitExcludedProjects = parseProjectList(query.excludeProject);
10380
- const inferredProjectHints = deriveRecallProjectScopeHints({
10585
+ const inferredProjectHints = explicitWildcardProject ? [] : deriveRecallProjectScopeHints({
10381
10586
  queryText: normalizedQuery?.searchText ?? query.text ?? "",
10382
10587
  explicitProjects
10383
10588
  });
10384
- const projectHints = explicitProjects.length > 0 ? explicitProjects : inferredProjectHints;
10589
+ const callerProjectHints = explicitProjects.length > 0 || explicitWildcardProject ? [] : parseProjectList(query.projectHints).filter((projectHint) => projectHint !== "*");
10590
+ const projectHints = explicitProjects.length > 0 ? explicitProjects : Array.from(/* @__PURE__ */ new Set([...inferredProjectHints, ...callerProjectHints]));
10385
10591
  const intent = projectHints.length > 0 ? "project" : normalizedQuery?.scopeIntent ?? "ambiguous";
10386
10592
  if (query.universalOnly === true) {
10387
10593
  return {
@@ -12126,6 +12332,7 @@ function buildRecallQuery(request, options) {
12126
12332
  scope: request.scope ?? "private",
12127
12333
  platform: request.platform,
12128
12334
  project: request.project,
12335
+ projectHints: request.projectHints,
12129
12336
  excludeProject: request.excludeProject,
12130
12337
  projectStrict: request.projectStrict,
12131
12338
  universalOnly: request.universalOnly,
@@ -12454,6 +12661,9 @@ async function sessionStartRecall2(request, deps) {
12454
12661
  });
12455
12662
  }
12456
12663
 
12664
+ // src/openclaw-plugin/recall.ts
12665
+ init_project();
12666
+
12457
12667
  // src/openclaw-plugin/memory-index.ts
12458
12668
  init_errors();
12459
12669
  var MEMORY_INDEX_TIMEOUT_MS = 1e4;
@@ -12587,18 +12797,23 @@ function buildRecallRequest(budget, project, query, options) {
12587
12797
  const queryText = trimmedQuery.length > RECALL_QUERY_MAX_CHARS ? trimmedQuery.slice(0, RECALL_QUERY_MAX_CHARS) : trimmedQuery;
12588
12798
  const inferredSessionStart = isSessionStart || queryText.length === 0;
12589
12799
  const universalOnly = options?.nullProjectOnly === true || options?.universalOnly === true;
12590
- const explicitProjectsProvided = options?.project !== void 0;
12800
+ const explicitWildcardProject = (options?.project ?? []).some(
12801
+ (candidate) => candidate.split(",").some((part) => part.trim() === "*")
12802
+ );
12591
12803
  const normalizedProjects = Array.from(new Set(
12592
12804
  (options?.project ?? []).map((candidate) => candidate.trim()).filter((candidate) => candidate.length > 0)
12593
12805
  ));
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;
12806
+ const explicitProjectSelection = explicitWildcardProject || normalizedProjects.length > 0;
12807
+ const normalizedProjectHints = parseProjectList(options?.projectHints).filter((projectHint) => projectHint !== "*");
12808
+ const resolvedProject = universalOnly ? void 0 : explicitProjectSelection ? normalizedProjects.length > 0 ? normalizedProjects : void 0 : !project ? void 0 : [project];
12809
+ const resolvedProjectStrict = universalOnly ? void 0 : explicitProjectSelection ? options?.projectStrict ? true : void 0 : project && !universalOnly ? true : void 0;
12810
+ const resolvedProjectHints = universalOnly || explicitProjectSelection || explicitWildcardProject ? void 0 : normalizedProjectHints.length > 0 ? normalizedProjectHints : void 0;
12596
12811
  return {
12597
12812
  isBrowse,
12598
12813
  isSessionStart: !isBrowse && inferredSessionStart,
12599
12814
  limit: options?.limit ?? DEFAULT_LIMIT2,
12600
12815
  queryText,
12601
- since: isBrowse ? options?.since ?? "1d" : options?.since,
12816
+ since: options?.since,
12602
12817
  types: options?.types,
12603
12818
  until: options?.until,
12604
12819
  around: options?.around,
@@ -12609,6 +12824,7 @@ function buildRecallRequest(budget, project, query, options) {
12609
12824
  universalOnly,
12610
12825
  project: resolvedProject,
12611
12826
  projectStrict: resolvedProjectStrict,
12827
+ projectHints: resolvedProjectHints,
12612
12828
  budget: isBrowse ? void 0 : options?.budget === null ? void 0 : options?.budget ?? budget
12613
12829
  };
12614
12830
  }
@@ -12658,6 +12874,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12658
12874
  expiry: request.expiry,
12659
12875
  project: request.project,
12660
12876
  projectStrict: request.projectStrict,
12877
+ projectHints: request.projectHints,
12661
12878
  universalOnly: request.universalOnly,
12662
12879
  includeRecallCounts: false
12663
12880
  },
@@ -12678,6 +12895,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12678
12895
  expiry: request.expiry,
12679
12896
  project: request.project,
12680
12897
  projectStrict: request.projectStrict,
12898
+ projectHints: request.projectHints,
12681
12899
  universalOnly: request.universalOnly,
12682
12900
  updateMetadata,
12683
12901
  includeRecallCounts: false
@@ -12701,6 +12919,7 @@ async function executeRecallRequest(budget, project, query, options, dbPath, exe
12701
12919
  expiry: request.expiry,
12702
12920
  project: request.project,
12703
12921
  projectStrict: request.projectStrict,
12922
+ projectHints: request.projectHints,
12704
12923
  universalOnly: request.universalOnly,
12705
12924
  updateMetadata,
12706
12925
  includeRecallCounts: false
@@ -14923,6 +15142,31 @@ var genericJsonlAdapter = {
14923
15142
 
14924
15143
  // src/adapters/openclaw.ts
14925
15144
  import fs13 from "fs/promises";
15145
+ init_content_fingerprint();
15146
+
15147
+ // src/utils/expiry.ts
15148
+ init_types();
15149
+ var EXPIRY_SET = new Set(EXPIRY_LEVELS);
15150
+ var EXPIRY_PRIORITY = {
15151
+ temporary: 0,
15152
+ permanent: 1,
15153
+ core: 2
15154
+ };
15155
+ function normalizeExpiry(value) {
15156
+ if (typeof value !== "string") {
15157
+ return void 0;
15158
+ }
15159
+ const normalized = value.trim().toLowerCase();
15160
+ if (!normalized) {
15161
+ return void 0;
15162
+ }
15163
+ return EXPIRY_SET.has(normalized) ? normalized : void 0;
15164
+ }
15165
+ function resolveHigherExpiry(a, b) {
15166
+ return EXPIRY_PRIORITY[a] >= EXPIRY_PRIORITY[b] ? a : b;
15167
+ }
15168
+
15169
+ // src/adapters/openclaw.ts
14926
15170
  init_string();
14927
15171
  function normalizeOpenClawRole(value) {
14928
15172
  if (typeof value !== "string") {
@@ -14968,10 +15212,120 @@ function getStringArray(value) {
14968
15212
  const values = value.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0);
14969
15213
  return values.length > 0 ? values : void 0;
14970
15214
  }
15215
+ function parseJsonRecord(value) {
15216
+ try {
15217
+ return asRecord4(JSON.parse(value));
15218
+ } catch {
15219
+ return null;
15220
+ }
15221
+ }
15222
+ function normalizeProjectValue(value) {
15223
+ const trimmed = value?.trim();
15224
+ return trimmed ? trimmed.toLowerCase() : void 0;
15225
+ }
14971
15226
  function normalizeSessionLabel(value) {
14972
15227
  const normalized = normalizeLabel(value);
14973
15228
  return normalized.length > 0 ? normalized : void 0;
14974
15229
  }
15230
+ function normalizeHintSubject(subject) {
15231
+ const trimmed = subject?.trim().toLowerCase();
15232
+ return trimmed ? trimmed : void 0;
15233
+ }
15234
+ function extractRecallResultsPayload(message, toolText) {
15235
+ const details = asRecord4(message.details);
15236
+ if (Array.isArray(details?.results)) {
15237
+ return details.results;
15238
+ }
15239
+ const parsedPayload = parseJsonRecord(toolText);
15240
+ if (Array.isArray(parsedPayload?.results)) {
15241
+ return parsedPayload.results;
15242
+ }
15243
+ return [];
15244
+ }
15245
+ function extractRecallTargetHints(message, toolText) {
15246
+ const hints = [];
15247
+ for (const result of extractRecallResultsPayload(message, toolText)) {
15248
+ const resultRecord = asRecord4(result);
15249
+ const entryRecord = asRecord4(resultRecord?.entry);
15250
+ const recalledEntryId = getString4(entryRecord?.id);
15251
+ const type = getString4(entryRecord?.type);
15252
+ const subject = getString4(entryRecord?.subject);
15253
+ const content = getString4(entryRecord?.content);
15254
+ if (!recalledEntryId || !type || !subject || !content) {
15255
+ continue;
15256
+ }
15257
+ const source = asRecord4(entryRecord?.source);
15258
+ hints.push({
15259
+ recalledEntryId,
15260
+ type,
15261
+ subject,
15262
+ normContentHash: computeNormContentHash(content),
15263
+ project: normalizeProjectValue(getString4(entryRecord?.project)),
15264
+ sourceFile: getString4(source?.file),
15265
+ sourceContext: getString4(source?.context)
15266
+ });
15267
+ }
15268
+ return hints;
15269
+ }
15270
+ function isRetireSuccessText(toolText) {
15271
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15272
+ return new RegExp(`^Retired entry\\s+${entryLabelPattern}\\.\\s*$`, "i").test(toolText.trim());
15273
+ }
15274
+ function isUpdateSuccessText(toolText) {
15275
+ const trimmed = toolText.trim();
15276
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15277
+ const updatePart = "(?:importance\\s+\\d+\\s+(?:->|\u2192)\\s+\\d+|expiry\\s+(?:core|permanent|temporary)\\s+(?:->|\u2192)\\s+(?:core|permanent|temporary))";
15278
+ 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);
15279
+ }
15280
+ function isInactiveUpdateText(toolText) {
15281
+ const entryLabelPattern = '\\S+(?:\\s+\\("(?:\\\\.|[^"])*"\\))?';
15282
+ return new RegExp(
15283
+ `^Entry\\s+${entryLabelPattern}\\s+was not updated because it is no longer active\\.\\s*$`,
15284
+ "i"
15285
+ ).test(toolText.trim());
15286
+ }
15287
+ function parseQuotedSubject(subjectToken) {
15288
+ if (!subjectToken) {
15289
+ return void 0;
15290
+ }
15291
+ try {
15292
+ const parsed = JSON.parse(subjectToken);
15293
+ return typeof parsed === "string" ? parsed : void 0;
15294
+ } catch {
15295
+ return void 0;
15296
+ }
15297
+ }
15298
+ function parseRetireSuccessReceipt(toolText) {
15299
+ const match = toolText.trim().match(
15300
+ /^Retired entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\.\s*$/i
15301
+ );
15302
+ if (!match) {
15303
+ return null;
15304
+ }
15305
+ return {
15306
+ entryId: match[1],
15307
+ subject: parseQuotedSubject(match[2])
15308
+ };
15309
+ }
15310
+ function parseUpdateReceiptEntry(toolText) {
15311
+ const matchers = [
15312
+ /^Updated entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+importance:/i,
15313
+ /^Updated entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?:/i,
15314
+ /^Entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+already has/i,
15315
+ /^Entry\s+(\S+)(?:\s+\(("(?:(?:\\.)|[^"])*")\))?\s+was not updated because/i
15316
+ ];
15317
+ for (const matcher of matchers) {
15318
+ const match = toolText.trim().match(matcher);
15319
+ if (!match) {
15320
+ continue;
15321
+ }
15322
+ return {
15323
+ entryId: match[1],
15324
+ subject: parseQuotedSubject(match[2])
15325
+ };
15326
+ }
15327
+ return null;
15328
+ }
14975
15329
  function parsePendingAgentStoreEntry(value, defaultProject) {
14976
15330
  const record = asRecord4(value);
14977
15331
  if (!record) {
@@ -15125,11 +15479,43 @@ var openClawAdapter = {
15125
15479
  const modelsUsed = [];
15126
15480
  const modelsUsedSet = /* @__PURE__ */ new Set();
15127
15481
  const successfulAgentStoredEntries = [];
15482
+ const successfulAgentRetires = [];
15483
+ const successfulAgentUpdates = [];
15128
15484
  const agentStoreProjects = /* @__PURE__ */ new Set();
15485
+ const recalledEntryHintsById = /* @__PURE__ */ new Map();
15486
+ const recalledEntryHintsBySubject = /* @__PURE__ */ new Map();
15129
15487
  const pendingToolCalls = [];
15130
15488
  const pendingToolCallsById = /* @__PURE__ */ new Map();
15131
15489
  const pendingAgentStores = /* @__PURE__ */ new Map();
15132
15490
  const pendingAgentStoreQueue = [];
15491
+ const pendingAgentRetires = /* @__PURE__ */ new Map();
15492
+ const pendingAgentRetireQueue = [];
15493
+ const pendingAgentUpdates = /* @__PURE__ */ new Map();
15494
+ const pendingAgentUpdateQueue = [];
15495
+ const appendRecalledEntryHint = (hint) => {
15496
+ recalledEntryHintsById.set(hint.recalledEntryId, hint);
15497
+ const subjectKey2 = normalizeHintSubject(hint.subject);
15498
+ if (!subjectKey2) {
15499
+ return;
15500
+ }
15501
+ const existing = recalledEntryHintsBySubject.get(subjectKey2) ?? [];
15502
+ existing.push(hint);
15503
+ recalledEntryHintsBySubject.set(subjectKey2, existing);
15504
+ };
15505
+ const resolveRecalledEntryHint = (entryId, subject) => {
15506
+ if (entryId) {
15507
+ const byId = recalledEntryHintsById.get(entryId);
15508
+ if (byId) {
15509
+ return byId;
15510
+ }
15511
+ }
15512
+ const subjectKey2 = normalizeHintSubject(subject);
15513
+ if (!subjectKey2) {
15514
+ return void 0;
15515
+ }
15516
+ const bySubject = recalledEntryHintsBySubject.get(subjectKey2) ?? [];
15517
+ return bySubject.length === 1 ? bySubject[0] : void 0;
15518
+ };
15133
15519
  const addModelUsed = (value) => {
15134
15520
  const modelId = typeof value === "string" ? value.trim() : "";
15135
15521
  if (!modelId || modelsUsedSet.has(modelId)) {
@@ -15167,7 +15553,7 @@ var openClawAdapter = {
15167
15553
  }
15168
15554
  return pendingToolCalls.shift() ?? null;
15169
15555
  };
15170
- const resolvePendingAgentStore = (message, toolCallIdHint) => {
15556
+ const resolvePendingAction = (message, byId, queue, toolCallIdHint) => {
15171
15557
  const candidateIds = [
15172
15558
  toolCallIdHint,
15173
15559
  getString4(message.toolCallId),
@@ -15176,14 +15562,23 @@ var openClawAdapter = {
15176
15562
  getString4(message.id)
15177
15563
  ].filter((value, index, all) => Boolean(value) && all.indexOf(value) === index);
15178
15564
  for (const id of candidateIds) {
15179
- const pending = pendingAgentStores.get(id);
15565
+ const pending = byId.get(id);
15180
15566
  if (!pending) {
15181
15567
  continue;
15182
15568
  }
15183
- pendingAgentStores.delete(id);
15569
+ byId.delete(id);
15184
15570
  return pending;
15185
15571
  }
15186
- return pendingAgentStoreQueue.shift() ?? null;
15572
+ return queue.shift() ?? null;
15573
+ };
15574
+ const resolvePendingAgentStore = (message, toolCallIdHint) => {
15575
+ return resolvePendingAction(message, pendingAgentStores, pendingAgentStoreQueue, toolCallIdHint);
15576
+ };
15577
+ const resolvePendingAgentRetire = (message, toolCallIdHint) => {
15578
+ return resolvePendingAction(message, pendingAgentRetires, pendingAgentRetireQueue, toolCallIdHint);
15579
+ };
15580
+ const resolvePendingAgentUpdate = (message, toolCallIdHint) => {
15581
+ return resolvePendingAction(message, pendingAgentUpdates, pendingAgentUpdateQueue, toolCallIdHint);
15187
15582
  };
15188
15583
  parseJsonlLines(raw, warnings, (record) => {
15189
15584
  if (record.type === "session") {
@@ -15249,28 +15644,75 @@ var openClawAdapter = {
15249
15644
  const toolCalls = extractToolCallBlocks(content);
15250
15645
  const assistantMessageIndex = messages.length;
15251
15646
  for (const call of toolCalls) {
15647
+ const normalizedToolName = call.name.trim().toLowerCase();
15252
15648
  pendingToolCalls.push(call);
15253
15649
  if (call.id) {
15254
15650
  pendingToolCallsById.set(call.id, call);
15255
15651
  }
15256
- if (call.name.trim().toLowerCase() !== "agenr_store" || !Array.isArray(call.args.entries)) {
15652
+ if (normalizedToolName === "agenr_store" && Array.isArray(call.args.entries)) {
15653
+ const defaultProject = getString4(call.args.project);
15654
+ const entries = call.args.entries.map((entry) => parsePendingAgentStoreEntry(entry, defaultProject)).filter((entry) => entry !== null);
15655
+ if (entries.length === 0) {
15656
+ continue;
15657
+ }
15658
+ const pending = {
15659
+ entries,
15660
+ toolCallId: call.id,
15661
+ assistantMessageIndex,
15662
+ timestamp: timestamp2
15663
+ };
15664
+ if (call.id) {
15665
+ pendingAgentStores.set(call.id, pending);
15666
+ } else {
15667
+ pendingAgentStoreQueue.push(pending);
15668
+ }
15257
15669
  continue;
15258
15670
  }
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) {
15671
+ if (normalizedToolName === "agenr_retire") {
15672
+ const entryId = getString4(call.args.entry_id);
15673
+ const subject = getString4(call.args.subject);
15674
+ if (!entryId && !subject || entryId && subject) {
15675
+ continue;
15676
+ }
15677
+ const pending = {
15678
+ ...entryId ? { entryId } : {},
15679
+ ...subject ? { subject } : {},
15680
+ reason: getString4(call.args.reason),
15681
+ toolCallId: call.id,
15682
+ assistantMessageIndex,
15683
+ timestamp: timestamp2,
15684
+ targetHint: resolveRecalledEntryHint(entryId, subject)
15685
+ };
15686
+ if (call.id) {
15687
+ pendingAgentRetires.set(call.id, pending);
15688
+ } else {
15689
+ pendingAgentRetireQueue.push(pending);
15690
+ }
15262
15691
  continue;
15263
15692
  }
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);
15693
+ if (normalizedToolName === "agenr_update") {
15694
+ const entryId = getString4(call.args.entry_id);
15695
+ const subject = getString4(call.args.subject);
15696
+ const importance = getNumber(call.args.importance);
15697
+ const expiry = normalizeExpiry(getString4(call.args.expiry));
15698
+ if (!entryId && !subject || entryId && subject || importance === void 0 && expiry === void 0) {
15699
+ continue;
15700
+ }
15701
+ const pending = {
15702
+ ...entryId ? { entryId } : {},
15703
+ ...subject ? { subject } : {},
15704
+ ...importance !== void 0 ? { importance } : {},
15705
+ ...expiry !== void 0 ? { expiry } : {},
15706
+ toolCallId: call.id,
15707
+ assistantMessageIndex,
15708
+ timestamp: timestamp2,
15709
+ targetHint: resolveRecalledEntryHint(entryId, subject)
15710
+ };
15711
+ if (call.id) {
15712
+ pendingAgentUpdates.set(call.id, pending);
15713
+ } else {
15714
+ pendingAgentUpdateQueue.push(pending);
15715
+ }
15274
15716
  }
15275
15717
  }
15276
15718
  let text;
@@ -15308,8 +15750,17 @@ var openClawAdapter = {
15308
15750
  const normalizedToolName = toolName?.trim().toLowerCase();
15309
15751
  const args = ctx?.args ?? {};
15310
15752
  const toolText = normalizeMessageText(content);
15753
+ const trimmedToolText = toolText.trim();
15754
+ if (normalizedToolName === "agenr_recall") {
15755
+ const recallFailed = message.isError === true || message.is_error === true || /^agenr_recall failed:/i.test(trimmedToolText);
15756
+ if (!recallFailed) {
15757
+ for (const hint of extractRecallTargetHints(message, toolText)) {
15758
+ appendRecalledEntryHint(hint);
15759
+ }
15760
+ }
15761
+ }
15311
15762
  if (normalizedToolName === "agenr_store") {
15312
- const storeFailed = message.isError === true || message.is_error === true || /^validation failed\b/i.test(toolText.trim());
15763
+ const storeFailed = message.isError === true || message.is_error === true || /^validation failed\b/i.test(trimmedToolText);
15313
15764
  const pending = resolvePendingAgentStore(message, ctx?.id);
15314
15765
  if (pending && !storeFailed) {
15315
15766
  const toolResultMessageIndex = messages.length;
@@ -15331,6 +15782,39 @@ var openClawAdapter = {
15331
15782
  }
15332
15783
  }
15333
15784
  }
15785
+ if (normalizedToolName === "agenr_retire") {
15786
+ const retireFailed = message.isError === true || message.is_error === true || /^agenr_retire failed:/i.test(trimmedToolText);
15787
+ const pending = resolvePendingAgentRetire(message, ctx?.id);
15788
+ const receipt = parseRetireSuccessReceipt(toolText);
15789
+ if (pending && !retireFailed && receipt && isRetireSuccessText(toolText)) {
15790
+ const targetHint = pending.targetHint ?? resolveRecalledEntryHint(void 0, receipt.subject);
15791
+ successfulAgentRetires.push({
15792
+ entryId: receipt.entryId,
15793
+ reason: pending.reason,
15794
+ toolCallId: pending.toolCallId,
15795
+ assistantMessageIndex: pending.assistantMessageIndex,
15796
+ timestamp: pending.timestamp,
15797
+ ...targetHint ? { targetHint } : {}
15798
+ });
15799
+ }
15800
+ }
15801
+ if (normalizedToolName === "agenr_update") {
15802
+ const updateFailed = message.isError === true || message.is_error === true || /^agenr_update failed:/i.test(trimmedToolText) || isInactiveUpdateText(trimmedToolText);
15803
+ const pending = resolvePendingAgentUpdate(message, ctx?.id);
15804
+ const receipt = parseUpdateReceiptEntry(toolText);
15805
+ if (pending && !updateFailed && receipt && isUpdateSuccessText(toolText)) {
15806
+ const targetHint = pending.targetHint ?? resolveRecalledEntryHint(void 0, receipt.subject);
15807
+ successfulAgentUpdates.push({
15808
+ entryId: receipt.entryId,
15809
+ ...pending.importance !== void 0 ? { importance: pending.importance } : {},
15810
+ ...pending.expiry !== void 0 ? { expiry: pending.expiry } : {},
15811
+ toolCallId: pending.toolCallId,
15812
+ assistantMessageIndex: pending.assistantMessageIndex,
15813
+ timestamp: pending.timestamp,
15814
+ ...targetHint ? { targetHint } : {}
15815
+ });
15816
+ }
15817
+ }
15334
15818
  if (!toolText) {
15335
15819
  return;
15336
15820
  }
@@ -15388,7 +15872,9 @@ var openClawAdapter = {
15388
15872
  startedAt: sessionTimestamp ?? messages[0]?.timestamp ?? fallbackTimestamp,
15389
15873
  agentStoreProjects: agentStoreProjects.size > 0 ? Array.from(agentStoreProjects) : void 0
15390
15874
  },
15391
- successfulAgentStoredEntries: successfulAgentStoredEntries.length > 0 ? successfulAgentStoredEntries : void 0
15875
+ successfulAgentStoredEntries: successfulAgentStoredEntries.length > 0 ? successfulAgentStoredEntries : void 0,
15876
+ successfulAgentRetires: successfulAgentRetires.length > 0 ? successfulAgentRetires : void 0,
15877
+ successfulAgentUpdates: successfulAgentUpdates.length > 0 ? successfulAgentUpdates : void 0
15392
15878
  };
15393
15879
  }
15394
15880
  };
@@ -15853,7 +16339,9 @@ async function parseTranscriptFile(filePath, options) {
15853
16339
  chunks,
15854
16340
  warnings: result.warnings,
15855
16341
  metadata: result.metadata,
15856
- successfulAgentStoredEntries: result.successfulAgentStoredEntries
16342
+ successfulAgentStoredEntries: result.successfulAgentStoredEntries,
16343
+ successfulAgentRetires: result.successfulAgentRetires,
16344
+ successfulAgentUpdates: result.successfulAgentUpdates
15857
16345
  };
15858
16346
  }
15859
16347
 
@@ -20281,18 +20769,6 @@ async function extractKnowledgeFromChunks(params) {
20281
20769
  };
20282
20770
  }
20283
20771
 
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
20772
  // src/app/ingest/trigram-dedup.ts
20297
20773
  function normalize3(value) {
20298
20774
  return value.toLowerCase().replace(/[^a-z0-9\s]/g, " ").replace(/\s+/g, " ").trim();
@@ -20660,8 +21136,8 @@ function createContradictionRepository(db) {
20660
21136
  });
20661
21137
  return result.rows.map((row) => {
20662
21138
  const record = row;
20663
- const importance = toNumber(record.importance);
20664
- const claimConfidence = toNumber(record.claim_confidence);
21139
+ const importance = toNumber2(record.importance);
21140
+ const claimConfidence = toNumber2(record.claim_confidence);
20665
21141
  const claimRole = toStringValue(record.claim_role);
20666
21142
  return {
20667
21143
  id: toStringValue(record.id),
@@ -25108,7 +25584,7 @@ async function queryCandidates(db, subject, contains) {
25108
25584
  id: toStringValue(row.id),
25109
25585
  type: toStringValue(row.type),
25110
25586
  subject: toStringValue(row.subject),
25111
- importance: toNumber(row.importance),
25587
+ importance: toNumber2(row.importance),
25112
25588
  content: toStringValue(row.content)
25113
25589
  }));
25114
25590
  }
@@ -25125,7 +25601,7 @@ async function queryById(db, id) {
25125
25601
  id: toStringValue(row.id),
25126
25602
  type: toStringValue(row.type),
25127
25603
  subject: toStringValue(row.subject),
25128
- importance: toNumber(row.importance),
25604
+ importance: toNumber2(row.importance),
25129
25605
  content: toStringValue(row.content)
25130
25606
  }));
25131
25607
  }
@@ -25201,12 +25677,16 @@ init_runtime2();
25201
25677
  function resolveUpdateServiceDeps(deps) {
25202
25678
  return resolveAppRuntimeDeps(deps);
25203
25679
  }
25204
- function validateEntryId(id) {
25205
- const entryId = id?.trim() ?? "";
25206
- if (!entryId) {
25207
- throw new Error("id is required");
25680
+ function validateSelector2(request) {
25681
+ const queryId = request.id?.trim() ?? "";
25682
+ const querySubject = request.subject?.trim() ?? "";
25683
+ if (!queryId && !querySubject) {
25684
+ throw new Error("entry_id or subject is required");
25208
25685
  }
25209
- return entryId;
25686
+ if (queryId && querySubject) {
25687
+ throw new Error("entry_id and subject are mutually exclusive");
25688
+ }
25689
+ return { queryId, querySubject };
25210
25690
  }
25211
25691
  function normalizeImportance3(value) {
25212
25692
  const parsed = typeof value === "number" ? value : Number(value);
@@ -25215,10 +25695,26 @@ function normalizeImportance3(value) {
25215
25695
  }
25216
25696
  return parsed;
25217
25697
  }
25698
+ function normalizeOptionalImportance(value) {
25699
+ if (value === void 0) {
25700
+ return void 0;
25701
+ }
25702
+ return normalizeImportance3(value);
25703
+ }
25704
+ function normalizeOptionalExpiry(value) {
25705
+ if (value === void 0) {
25706
+ return void 0;
25707
+ }
25708
+ const normalized = normalizeExpiry(value);
25709
+ if (!normalized) {
25710
+ throw new Error(`expiry must be one of: ${EXPIRY_LEVELS.join(", ")}`);
25711
+ }
25712
+ return normalized;
25713
+ }
25218
25714
  async function queryById2(db, id) {
25219
25715
  const result = await db.execute({
25220
25716
  sql: `
25221
- SELECT id, type, subject, importance, content
25717
+ SELECT id, type, subject, importance, expiry, content
25222
25718
  FROM entries
25223
25719
  WHERE retired = 0 AND id = ?
25224
25720
  LIMIT 1
@@ -25229,11 +25725,39 @@ async function queryById2(db, id) {
25229
25725
  if (!row) {
25230
25726
  return null;
25231
25727
  }
25728
+ const expiry = normalizeExpiry(row.expiry) ?? "temporary";
25232
25729
  return {
25233
25730
  id: toStringValue(row.id),
25234
25731
  type: toStringValue(row.type),
25235
25732
  subject: toStringValue(row.subject),
25236
- importance: toNumber(row.importance),
25733
+ importance: toNumber2(row.importance),
25734
+ expiry,
25735
+ content: toStringValue(row.content)
25736
+ };
25737
+ }
25738
+ async function queryBySubject(db, subject) {
25739
+ const result = await db.execute({
25740
+ sql: `
25741
+ SELECT id, type, subject, importance, expiry, content
25742
+ FROM entries
25743
+ WHERE retired = 0
25744
+ AND LOWER(subject) = LOWER(?)
25745
+ ORDER BY updated_at DESC
25746
+ LIMIT 1
25747
+ `,
25748
+ args: [subject]
25749
+ });
25750
+ const row = result.rows[0];
25751
+ if (!row) {
25752
+ return null;
25753
+ }
25754
+ const expiry = normalizeExpiry(row.expiry) ?? "temporary";
25755
+ return {
25756
+ id: toStringValue(row.id),
25757
+ type: toStringValue(row.type),
25758
+ subject: toStringValue(row.subject),
25759
+ importance: toNumber2(row.importance),
25760
+ expiry,
25237
25761
  content: toStringValue(row.content)
25238
25762
  };
25239
25763
  }
@@ -25245,7 +25769,7 @@ async function lookupUpdateCandidate(request, deps) {
25245
25769
  }
25246
25770
  async function runUpdateService(request, deps) {
25247
25771
  const resolvedDeps = resolveUpdateServiceDeps(deps);
25248
- const id = validateEntryId(request.id);
25772
+ const { queryId, querySubject } = validateSelector2(request);
25249
25773
  return withAppDb(
25250
25774
  {
25251
25775
  env: request.env,
@@ -25256,53 +25780,81 @@ async function runUpdateService(request, deps) {
25256
25780
  if (request.action === "lookup") {
25257
25781
  return {
25258
25782
  action: "lookup",
25259
- candidate: await queryById2(db, id)
25783
+ candidate: queryId ? await queryById2(db, queryId) : await queryBySubject(db, querySubject)
25260
25784
  };
25261
25785
  }
25262
- const importance = normalizeImportance3(request.importance);
25263
- const existing = await queryById2(db, id);
25786
+ const importance = normalizeOptionalImportance(request.importance);
25787
+ const expiry = normalizeOptionalExpiry(request.expiry);
25788
+ if (importance === void 0 && expiry === void 0) {
25789
+ throw new Error("at least one of importance or expiry is required");
25790
+ }
25791
+ const existing = queryId ? await queryById2(db, queryId) : await queryBySubject(db, querySubject);
25792
+ const targetId = existing?.id ?? queryId ?? querySubject;
25264
25793
  if (!existing) {
25265
25794
  return {
25266
25795
  action: "execute",
25267
- id,
25268
- previousImportance: importance,
25269
- importance,
25796
+ id: targetId,
25797
+ previousImportance: importance ?? IMPORTANCE_MIN,
25798
+ importance: importance ?? IMPORTANCE_MIN,
25799
+ previousExpiry: expiry ?? "temporary",
25800
+ expiry: expiry ?? "temporary",
25270
25801
  updated: false
25271
25802
  };
25272
25803
  }
25273
- if (existing.importance === importance) {
25804
+ const nextImportance = importance ?? existing.importance;
25805
+ const nextExpiry = expiry ?? existing.expiry;
25806
+ const importanceChanged = existing.importance !== nextImportance;
25807
+ const expiryChanged = existing.expiry !== nextExpiry;
25808
+ if (!importanceChanged && !expiryChanged) {
25274
25809
  return {
25275
25810
  action: "execute",
25276
- id,
25811
+ id: existing.id,
25277
25812
  previousImportance: existing.importance,
25278
- importance,
25813
+ importance: existing.importance,
25814
+ previousExpiry: existing.expiry,
25815
+ expiry: existing.expiry,
25279
25816
  updated: true
25280
25817
  };
25281
25818
  }
25819
+ const setClauses = [];
25820
+ const setArgs = [];
25821
+ if (importanceChanged) {
25822
+ setClauses.push("importance = ?");
25823
+ setArgs.push(nextImportance);
25824
+ }
25825
+ if (expiryChanged) {
25826
+ setClauses.push("expiry = ?");
25827
+ setArgs.push(nextExpiry);
25828
+ }
25829
+ setClauses.push("updated_at = ?");
25830
+ setArgs.push(runtime.now.toISOString());
25282
25831
  const result = await db.execute({
25283
25832
  sql: `
25284
25833
  UPDATE entries
25285
- SET importance = ?,
25286
- updated_at = ?
25834
+ SET ${setClauses.join(", ")}
25287
25835
  WHERE id = ?
25288
25836
  AND retired = 0
25289
25837
  `,
25290
- args: [importance, runtime.now.toISOString(), id]
25838
+ args: [...setArgs, existing.id]
25291
25839
  });
25292
25840
  if (toRowsAffected(result.rowsAffected) === 0) {
25293
25841
  return {
25294
25842
  action: "execute",
25295
- id,
25843
+ id: existing.id,
25296
25844
  previousImportance: existing.importance,
25297
25845
  importance: existing.importance,
25846
+ previousExpiry: existing.expiry,
25847
+ expiry: existing.expiry,
25298
25848
  updated: false
25299
25849
  };
25300
25850
  }
25301
25851
  return {
25302
25852
  action: "execute",
25303
- id,
25853
+ id: existing.id,
25304
25854
  previousImportance: existing.importance,
25305
- importance,
25855
+ importance: nextImportance,
25856
+ previousExpiry: existing.expiry,
25857
+ expiry: nextExpiry,
25306
25858
  updated: true
25307
25859
  };
25308
25860
  }
@@ -25314,6 +25866,8 @@ async function executeUpdate(request, deps) {
25314
25866
  id: result.id,
25315
25867
  previousImportance: result.previousImportance,
25316
25868
  importance: result.importance,
25869
+ previousExpiry: result.previousExpiry,
25870
+ expiry: result.expiry,
25317
25871
  updated: result.updated
25318
25872
  };
25319
25873
  }
@@ -25325,8 +25879,8 @@ init_client();
25325
25879
  // src/db/provenance.ts
25326
25880
  init_entry_utils();
25327
25881
  function mapProvenanceEntry(row, tags) {
25328
- const importance = toNumber(row.importance);
25329
- const retired = toNumber(row.retired);
25882
+ const importance = toNumber2(row.importance);
25883
+ const retired = toNumber2(row.retired);
25330
25884
  return {
25331
25885
  id: toStringValue(row.id),
25332
25886
  subject: toStringValue(row.subject),
@@ -25349,7 +25903,7 @@ function mapProvenanceRelated(row, tags) {
25349
25903
  };
25350
25904
  }
25351
25905
  function mapProvenanceSupport(row) {
25352
- const claimConfidence = toNumber(row.claim_confidence);
25906
+ const claimConfidence = toNumber2(row.claim_confidence);
25353
25907
  const sourceFile = toStringValue(row.source_file).trim();
25354
25908
  const sourceContext = toStringValue(row.source_context).trim();
25355
25909
  const sourceCreatedAt = toStringValue(row.source_created_at).trim();
@@ -26643,6 +27197,7 @@ function resolveOpenClawBridgeStoreProjectAttribution(params) {
26643
27197
 
26644
27198
  // src/openclaw-plugin/tools.ts
26645
27199
  init_types();
27200
+ init_project();
26646
27201
  var KNOWN_PLATFORMS = /* @__PURE__ */ new Set(["openclaw", "codex", "claude-code", "plaud"]);
26647
27202
  var log7 = createLogger("tools");
26648
27203
  var SUCCESSFUL_STORE_ACTIONS = /* @__PURE__ */ new Set([
@@ -26657,10 +27212,31 @@ function asString2(value) {
26657
27212
  const trimmed = value.trim();
26658
27213
  return trimmed.length > 0 ? trimmed : void 0;
26659
27214
  }
27215
+ function validateExclusiveSelector(params) {
27216
+ const entryId = asString2(params.entry_id);
27217
+ const subject = asString2(params.subject);
27218
+ if (entryId && subject || !entryId && !subject) {
27219
+ throw new Error("exactly one of entry_id or subject is required");
27220
+ }
27221
+ return { entryId, subject };
27222
+ }
27223
+ function formatQuotedSubject(subject) {
27224
+ return JSON.stringify(subject);
27225
+ }
27226
+ function formatEntryLabel(id, subject) {
27227
+ return subject ? `${id} (${formatQuotedSubject(subject)})` : id;
27228
+ }
27229
+ function formatMissingEntryError(selector) {
27230
+ if (selector.subject) {
27231
+ return `No active entry found matching subject: ${formatQuotedSubject(selector.subject)}`;
27232
+ }
27233
+ return `No active entry found with id: ${selector.entryId}`;
27234
+ }
26660
27235
  function resolveStoreCandidateProject(explicitProject, sessionProject, defaultProject) {
26661
27236
  return asString2(explicitProject) ?? asString2(sessionProject) ?? asString2(defaultProject);
26662
27237
  }
26663
27238
  function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
27239
+ const diagnosticProjects = scope.projects.length > 0 ? scope.projects : scope.projectHints ?? [];
26664
27240
  return createProjectScopeResolutionDiagnostic({
26665
27241
  surface: "agent-tool-recall",
26666
27242
  inputs: {
@@ -26674,8 +27250,8 @@ function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
26674
27250
  outcome: {
26675
27251
  projectSource: scope.projectSource,
26676
27252
  reason: scope.reason,
26677
- projects: scope.projects,
26678
- primaryProject: scope.projects[0],
27253
+ projects: diagnosticProjects,
27254
+ primaryProject: diagnosticProjects[0],
26679
27255
  recallProjectArg: scope.recallProjectArg,
26680
27256
  strictScope: scope.projectStrict,
26681
27257
  dependencyExpansionApplied: false,
@@ -26685,7 +27261,9 @@ function createAgentToolRecallScopeDiagnostic(scope, defaultProjectPresent) {
26685
27261
  }
26686
27262
  function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26687
27263
  const normalizedExplicitProject = explicitProject?.trim();
26688
- const defaultProjectPresent = Boolean(defaultProject?.trim());
27264
+ const normalizedDefaultProjects = parseProjectList(defaultProject).filter((project) => project !== "*");
27265
+ const normalizedDefaultProject = normalizedDefaultProjects[0];
27266
+ const defaultProjectPresent = normalizedDefaultProjects.length > 0;
26689
27267
  if (normalizedExplicitProject === "*") {
26690
27268
  const scope2 = {
26691
27269
  surface: "agent-tool-recall",
@@ -26693,8 +27271,8 @@ function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26693
27271
  projectSource: "unscoped",
26694
27272
  projectArgumentState: "wildcard",
26695
27273
  reason: "explicit-tool-project-bypass",
26696
- projects: [],
26697
- recallProjectArg: void 0,
27274
+ projects: ["*"],
27275
+ recallProjectArg: "*",
26698
27276
  projectStrict: false
26699
27277
  };
26700
27278
  return {
@@ -26706,14 +27284,34 @@ function resolveAgentToolRecallScope(explicitProject, defaultProject) {
26706
27284
  };
26707
27285
  }
26708
27286
  if (!normalizedExplicitProject) {
27287
+ if (normalizedDefaultProject) {
27288
+ const scope3 = {
27289
+ surface: "agent-tool-recall",
27290
+ projectResolutionMode: "explicit-tool-project-only",
27291
+ projectSource: "plugin-default-project",
27292
+ projectArgumentState: "omitted",
27293
+ reason: "plugin-default-project",
27294
+ projects: [],
27295
+ projectHints: [normalizedDefaultProject],
27296
+ recallProjectArg: void 0,
27297
+ projectStrict: false
27298
+ };
27299
+ return {
27300
+ ...scope3,
27301
+ memoryReliabilityDiagnostic: createAgentToolRecallScopeDiagnostic(
27302
+ scope3,
27303
+ defaultProjectPresent
27304
+ )
27305
+ };
27306
+ }
26709
27307
  const scope2 = {
26710
27308
  surface: "agent-tool-recall",
26711
27309
  projectResolutionMode: "explicit-tool-project-only",
26712
27310
  projectSource: "unscoped",
26713
27311
  projectArgumentState: "omitted",
26714
27312
  reason: "no-project-candidate",
26715
- projects: [],
26716
- recallProjectArg: void 0,
27313
+ projects: ["*"],
27314
+ recallProjectArg: "*",
26717
27315
  projectStrict: false
26718
27316
  };
26719
27317
  return {
@@ -26755,6 +27353,36 @@ function asNumber(value) {
26755
27353
  }
26756
27354
  return void 0;
26757
27355
  }
27356
+ function normalizeOptionalUpdateExpiry(value) {
27357
+ if (value === void 0) {
27358
+ return void 0;
27359
+ }
27360
+ const expiry = normalizeExpiry(value);
27361
+ if (!expiry) {
27362
+ throw new Error(`expiry must be one of: ${EXPIRY_LEVELS.join(", ")}`);
27363
+ }
27364
+ return expiry;
27365
+ }
27366
+ function buildChangedUpdateParts(request, result) {
27367
+ const parts = [];
27368
+ if (request.importance !== void 0 && result.previousImportance !== result.importance) {
27369
+ parts.push(`importance ${result.previousImportance} \u2192 ${result.importance}`);
27370
+ }
27371
+ if (request.expiry !== void 0 && result.previousExpiry !== result.expiry) {
27372
+ parts.push(`expiry ${result.previousExpiry} \u2192 ${result.expiry}`);
27373
+ }
27374
+ return parts;
27375
+ }
27376
+ function buildRequestedUpdateParts(request, result) {
27377
+ const parts = [];
27378
+ if (request.importance !== void 0) {
27379
+ parts.push(`importance ${result.importance}`);
27380
+ }
27381
+ if (request.expiry !== void 0) {
27382
+ parts.push(`expiry ${result.expiry}`);
27383
+ }
27384
+ return parts;
27385
+ }
26758
27386
  function parseOptionalPositiveInt(value, label) {
26759
27387
  if (value === void 0) {
26760
27388
  return void 0;
@@ -26879,6 +27507,7 @@ async function runRecallTool(params, defaultProject, dbPath) {
26879
27507
  platform: normalizeRecallPlatform(asString2(params.platform)),
26880
27508
  project: recallScope.projects,
26881
27509
  projectStrict: recallScope.projectStrict,
27510
+ projectHints: recallScope.projectHints,
26882
27511
  budget: null
26883
27512
  },
26884
27513
  dbPath,
@@ -27069,34 +27698,34 @@ async function runExtractTool(params, dbPath) {
27069
27698
  }
27070
27699
  async function runRetireTool(params, dbPath) {
27071
27700
  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
- }
27701
+ const selector = validateExclusiveSelector(params);
27078
27702
  const lookup = await lookupRetireCandidates({
27079
- id: entryId,
27703
+ id: selector.entryId,
27704
+ subject: selector.subject,
27080
27705
  dbPath
27081
27706
  });
27082
- if (lookup.candidates.length === 0) {
27707
+ const candidate = lookup.candidates[0];
27708
+ if (!candidate) {
27083
27709
  return {
27084
- content: [{ type: "text", text: `agenr_retire failed: No active entry found with id: ${entryId}` }]
27710
+ content: [{ type: "text", text: `agenr_retire failed: ${formatMissingEntryError(selector)}` }]
27085
27711
  };
27086
27712
  }
27087
27713
  const retired = await executeRetire({
27088
- id: entryId,
27714
+ id: candidate.id,
27089
27715
  reason: asString2(params.reason),
27090
27716
  persist: params.persist === true,
27091
27717
  dbPath
27092
27718
  });
27093
27719
  if (retired.retiredCount === 0) {
27094
27720
  return {
27095
- content: [{ type: "text", text: `agenr_retire failed: No active entry found with id: ${entryId}` }]
27721
+ content: [{ type: "text", text: `agenr_retire failed: ${formatMissingEntryError(selector)}` }]
27096
27722
  };
27097
27723
  }
27098
27724
  return {
27099
- content: [{ type: "text", text: `Retired entry ${entryId}.` }]
27725
+ content: [{
27726
+ type: "text",
27727
+ text: `Retired entry ${formatEntryLabel(candidate.id, selector.subject ? candidate.subject : void 0)}.`
27728
+ }]
27100
27729
  };
27101
27730
  } catch (error) {
27102
27731
  return {
@@ -27106,42 +27735,60 @@ async function runRetireTool(params, dbPath) {
27106
27735
  }
27107
27736
  async function runUpdateTool(params, dbPath) {
27108
27737
  try {
27109
- const entryId = asString2(params.entry_id);
27738
+ const selector = validateExclusiveSelector(params);
27739
+ const importanceProvided = Object.prototype.hasOwnProperty.call(params, "importance");
27110
27740
  const importance = asNumber(params.importance);
27111
- if (!entryId) {
27741
+ if (importanceProvided && (importance === void 0 || !Number.isInteger(importance) || importance < 1 || importance > 10)) {
27112
27742
  return {
27113
- content: [{ type: "text", text: "agenr_update failed: entry_id is required" }]
27743
+ content: [{ type: "text", text: "agenr_update failed: importance must be an integer between 1 and 10" }]
27114
27744
  };
27115
27745
  }
27116
- if (typeof importance !== "number" || !Number.isInteger(importance) || importance < 1 || importance > 10) {
27746
+ const expiry = normalizeOptionalUpdateExpiry(params.expiry);
27747
+ if (importance === void 0 && expiry === void 0) {
27117
27748
  return {
27118
- content: [{ type: "text", text: "agenr_update failed: importance must be an integer between 1 and 10" }]
27749
+ content: [{ type: "text", text: "agenr_update failed: at least one of importance or expiry is required" }]
27119
27750
  };
27120
27751
  }
27121
27752
  const lookup = await lookupUpdateCandidate({
27122
- id: entryId,
27753
+ id: selector.entryId,
27754
+ subject: selector.subject,
27123
27755
  dbPath
27124
27756
  });
27125
- if (!lookup.candidate) {
27757
+ const candidate = lookup.candidate;
27758
+ if (!candidate) {
27126
27759
  return {
27127
- content: [{ type: "text", text: `agenr_update failed: No active entry found with id: ${entryId}` }]
27760
+ content: [{ type: "text", text: `agenr_update failed: ${formatMissingEntryError(selector)}` }]
27128
27761
  };
27129
27762
  }
27130
27763
  const updated = await executeUpdate({
27131
- id: entryId,
27132
- importance,
27764
+ id: candidate.id,
27765
+ ...importance !== void 0 ? { importance } : {},
27766
+ ...expiry !== void 0 ? { expiry } : {},
27133
27767
  dbPath
27134
27768
  });
27769
+ const entryLabel = formatEntryLabel(candidate.id, selector.subject ? candidate.subject : void 0);
27135
27770
  if (!updated.updated) {
27136
27771
  return {
27137
- content: [{ type: "text", text: `Entry ${entryId} was not updated because it is no longer active.` }]
27772
+ content: [{ type: "text", text: `Entry ${entryLabel} was not updated because it is no longer active.` }]
27773
+ };
27774
+ }
27775
+ const changedParts = buildChangedUpdateParts({ importance, expiry }, updated);
27776
+ if (changedParts.length > 0) {
27777
+ return {
27778
+ content: [
27779
+ {
27780
+ type: "text",
27781
+ text: `Updated entry ${entryLabel}: ${changedParts.join(", ")}.`
27782
+ }
27783
+ ]
27138
27784
  };
27139
27785
  }
27786
+ const requestedParts = buildRequestedUpdateParts({ importance, expiry }, updated);
27140
27787
  return {
27141
27788
  content: [
27142
27789
  {
27143
27790
  type: "text",
27144
- text: updated.previousImportance === updated.importance ? `Entry ${entryId} already has importance ${updated.importance}.` : `Updated entry ${entryId} importance: ${updated.previousImportance} -> ${updated.importance}.`
27791
+ text: requestedParts.length === 1 ? `Entry ${entryLabel} already has ${requestedParts[0]}.` : `Entry ${entryLabel} already has ${requestedParts.join(" and ")}.`
27145
27792
  }
27146
27793
  ]
27147
27794
  };
@@ -32721,11 +33368,7 @@ async function runClearSessionProjectTool(sessionKey, pluginConfig) {
32721
33368
  }
32722
33369
 
32723
33370
  // src/openclaw-plugin/hooks/register-tools.ts
32724
- var EXPIRY_SCHEMA = Type6.Union([
32725
- Type6.Literal("core"),
32726
- Type6.Literal("permanent"),
32727
- Type6.Literal("temporary")
32728
- ]);
33371
+ var EXPIRY_SCHEMA = Type6.String({ enum: ["core", "permanent", "temporary"] });
32729
33372
  function optionalExpirySchema(description) {
32730
33373
  return Type6.Optional({
32731
33374
  ...EXPIRY_SCHEMA,
@@ -32771,12 +33414,10 @@ function registerAgenrTools(api, params) {
32771
33414
  parameters: Type6.Object({
32772
33415
  query: Type6.Optional(Type6.String({ description: "What to search for." })),
32773
33416
  context: Type6.Optional(
32774
- Type6.Union(
32775
- [Type6.Literal("default"), Type6.Literal("session-start"), Type6.Literal("browse")],
32776
- {
32777
- description: "Use session-start for fast bootstrap without embedding. Use browse for temporal browsing (date+importance, no query needed, no semantic search)."
32778
- }
32779
- )
33417
+ Type6.String({
33418
+ enum: ["default", "session-start", "browse"],
33419
+ 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)."
33420
+ })
32780
33421
  ),
32781
33422
  limit: Type6.Optional(Type6.Number({ description: "Max results (default: 10)." })),
32782
33423
  types: Type6.Optional(Type6.String({ description: "Comma-separated entry types to filter." })),
@@ -32803,7 +33444,8 @@ function registerAgenrTools(api, params) {
32803
33444
  return makeDisabledToolResult();
32804
33445
  }
32805
33446
  const dbPath = runtimeConfig?.dbPath;
32806
- const result = await runRecallTool(toolParams, void 0, dbPath);
33447
+ const defaultProject = runtimeConfig?.project?.trim() || void 0;
33448
+ const result = await runRecallTool(toolParams, defaultProject, dbPath);
32807
33449
  const sessionKey = sessionRef.current;
32808
33450
  if (sessionKey && result.agentToolRecallSurface) {
32809
33451
  const addedCount = createSessionSurfacedMemoryService(sessionKey).recordByIntent(
@@ -32992,11 +33634,22 @@ function registerAgenrTools(api, params) {
32992
33634
  name: "agenr_retire",
32993
33635
  label: "Agenr Retire",
32994
33636
  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
- }),
33637
+ parameters: Type6.Object(
33638
+ {
33639
+ entry_id: Type6.Optional(Type6.String({ description: "Entry ID to retire." })),
33640
+ subject: Type6.Optional(
33641
+ Type6.String({ description: "Subject string to match. Mutually exclusive with entry_id." })
33642
+ ),
33643
+ reason: Type6.Optional(Type6.String({ description: "Retirement reason." })),
33644
+ persist: Type6.Optional(Type6.Boolean({ description: "Persist retirement to ledger." }))
33645
+ },
33646
+ {
33647
+ anyOf: [
33648
+ { required: ["entry_id"] },
33649
+ { required: ["subject"] }
33650
+ ]
33651
+ }
33652
+ ),
33000
33653
  async execute(_toolCallId, toolParams) {
33001
33654
  const runtimeConfig = api.pluginConfig;
33002
33655
  if (runtimeConfig?.enabled === false) {
@@ -33011,11 +33664,37 @@ function registerAgenrTools(api, params) {
33011
33664
  {
33012
33665
  name: "agenr_update",
33013
33666
  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
- }),
33667
+ description: "Update an existing memory entry in place. Currently supports importance and expiry.",
33668
+ parameters: Type6.Object(
33669
+ {
33670
+ entry_id: Type6.Optional(Type6.String({ description: "Entry ID to update." })),
33671
+ subject: Type6.Optional(
33672
+ Type6.String({
33673
+ description: "Subject string to match. Mutually exclusive with entry_id."
33674
+ })
33675
+ ),
33676
+ importance: Type6.Optional(
33677
+ Type6.Integer({ minimum: 1, maximum: 10, description: "New importance score." })
33678
+ ),
33679
+ expiry: optionalExpirySchema("New expiry tier: core, permanent, or temporary.")
33680
+ },
33681
+ {
33682
+ allOf: [
33683
+ {
33684
+ anyOf: [
33685
+ { required: ["entry_id"] },
33686
+ { required: ["subject"] }
33687
+ ]
33688
+ },
33689
+ {
33690
+ anyOf: [
33691
+ { required: ["importance"] },
33692
+ { required: ["expiry"] }
33693
+ ]
33694
+ }
33695
+ ]
33696
+ }
33697
+ ),
33019
33698
  async execute(_toolCallId, toolParams) {
33020
33699
  const runtimeConfig = api.pluginConfig;
33021
33700
  if (runtimeConfig?.enabled === false) {