agenr 0.8.15 → 0.8.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.19] - 2026-02-23
4
+
5
+ ### Changed
6
+ - feat(openclaw-plugin): `before_reset` handoff store content now uses a structured recent exchange summary (`U:`/`A:` turns) instead of user-only fragments, improving cross-session handoff clarity while keeping stash-based recall seeding unchanged (issue #196)
7
+ - feat(openclaw-plugin): added `extractLastExchangeText(messages, maxTurns?)` in `src/openclaw-plugin/session-query.ts` to collect the last 5 user-turn windows with interleaved assistant context, per-turn truncation (200 chars), and chronological `U:`/`A:` formatting
8
+ - chore(openclaw-plugin): exported `SESSION_QUERY_LOOKBACK` from `session-query.ts` for direct test assertions
9
+
10
+ ### Tests
11
+ - test(openclaw-plugin): added `extractLastExchangeText` coverage for empty input, U/A formatting, per-message truncation, 5-user-turn collection window, and no-extractable-content behavior
12
+ - test(openclaw-plugin): updated handoff-store integration assertion to verify stored content includes exchange context prefixes (`U:`/`A:`) rather than flattened user-only text
13
+
14
+ ## [0.8.18] - 2026-02-23
15
+
16
+ ### Changed
17
+ - feat(openclaw-plugin): `before_prompt_build` now uses browse-mode recall (`--browse --since 1d`) for cold session starts where no stash/query seed is available, and keeps embed/query recall for substantive or stash-seeded starts (issue #196)
18
+ - chore(openclaw-plugin): removed archived-session fallback query synthesis from session-start recall seeding, simplifying thin-prompt startup behavior to browse vs stash/embed paths only (issue #196)
19
+ - feat(openclaw-plugin): `before_reset` now stores a fire-and-forget `event` memory entry (`session handoff ...`) with the latest user context to support next-session handoff continuity (issue #196)
20
+ - feat(openclaw-plugin): session-start browse results now auto-retire surfaced handoff entries after context injection to avoid repeated carryover (`reason: consumed at session start`) (issue #196)
21
+ - feat(openclaw-plugin): `runRecall` in `src/openclaw-plugin/recall.ts` now accepts an optional context options object and maps browse context to CLI browse args while preserving existing default/session-start call behavior for unchanged callers (issue #196)
22
+
23
+ ### Tests
24
+ - test(openclaw-plugin): updated query-seeding coverage for new cold-start browse path and removed archive-fallback-specific expectations (issue #196)
25
+ - test(openclaw-plugin): added regression coverage for before-reset handoff storage and session-start handoff auto-retire success, non-handoff skip, missing-id skip, and retire-failure resilience (issue #196)
26
+ - test(openclaw-plugin): added plugin recall browse-args unit coverage to assert `runRecall` browse flag construction and query omission behavior (issue #196)
27
+
28
+ ## [0.8.17] - 2026-02-23
29
+
30
+ ### Changed
31
+ - chore: rebuild dist to include browse mode CLI flag inadvertently omitted from 0.8.16 publish
32
+
33
+ ## [0.8.16] - 2026-02-23
34
+
35
+ ### Added
36
+ - feat(recall): new temporal browse mode for recall via `agenr recall --browse` and MCP `agenr_recall` with `context="browse"` (issue #190)
37
+ - docs(recall): added `docs/usage/recall.md` with browse-mode CLI and MCP usage examples
38
+
39
+ ### Changed
40
+ - recall browse mode now uses a SQL-only path that requires no query and performs zero embedding/OpenAI API calls
41
+ - browse mode does not increment recall metadata (`recall_count`, `last_recalled_at`, `recall_intervals`)
42
+ - OpenClaw plugin tool wiring now maps `context="browse"` to the CLI `--browse` flag (and omits query/context positional args appropriately)
43
+
44
+ ### Tests
45
+ - test(recall): added browse-mode coverage in DB recall, CLI command recall, MCP server recall, and OpenClaw plugin recall tool argument wiring
46
+
3
47
  ## [0.8.15] - 2026-02-23
4
48
 
5
49
  ### Fixed
package/dist/cli-main.js CHANGED
@@ -3873,6 +3873,13 @@ function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now, freshnessNow =
3873
3873
  function scoreEntry(entry, vectorSim, ftsMatch, now) {
3874
3874
  return scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now).score;
3875
3875
  }
3876
+ function scoreBrowseEntry(entry, now) {
3877
+ const daysOld = parseDaysBetween(now, entry.created_at);
3878
+ const HALF_LIFE_DAYS = 30;
3879
+ const recencyFactor = Math.exp(-(daysOld / HALF_LIFE_DAYS) * Math.LN2);
3880
+ const impScore = importanceScore(entry.importance);
3881
+ return clamp01(impScore * recencyFactor);
3882
+ }
3876
3883
  function shapeRecallText(text2, context) {
3877
3884
  const trimmedText = text2.trim();
3878
3885
  if (!trimmedText) {
@@ -4020,6 +4027,97 @@ async function fetchSessionCandidates(db, limit, context, platform, project, exc
4020
4027
  };
4021
4028
  });
4022
4029
  }
4030
+ async function fetchBrowseCandidates(db, query, limit, now) {
4031
+ const normalizedProject = parseProjectList(query.project);
4032
+ const normalizedExclude = parseProjectList(query.excludeProject);
4033
+ const projectSql = buildProjectFilter({
4034
+ column: "project",
4035
+ strict: Boolean(query.projectStrict && normalizedProject.length > 0),
4036
+ project: normalizedProject.length > 0 ? normalizedProject : void 0,
4037
+ excludeProject: normalizedExclude.length > 0 ? normalizedExclude : void 0
4038
+ });
4039
+ const whereClauses = [
4040
+ "superseded_by IS NULL",
4041
+ "retired = 0"
4042
+ ];
4043
+ if (projectSql.clause.trim()) {
4044
+ whereClauses.push(projectSql.clause.trim().replace(/^AND\s+/i, ""));
4045
+ }
4046
+ const args = [...projectSql.args];
4047
+ const since = parseSince(query.since, now);
4048
+ if (since) {
4049
+ whereClauses.push("created_at >= ?");
4050
+ args.push(since.toISOString());
4051
+ }
4052
+ const until = parseSince(query.until, now);
4053
+ if (until) {
4054
+ whereClauses.push("created_at <= ?");
4055
+ args.push(until.toISOString());
4056
+ }
4057
+ if (query.minImportance !== void 0) {
4058
+ whereClauses.push("importance >= ?");
4059
+ args.push(Math.max(1, Math.min(10, Math.round(query.minImportance))));
4060
+ }
4061
+ if (query.types && query.types.length > 0) {
4062
+ const placeholders = query.types.map(() => "?").join(", ");
4063
+ whereClauses.push(`type IN (${placeholders})`);
4064
+ args.push(...query.types);
4065
+ }
4066
+ if (query.platform) {
4067
+ whereClauses.push("platform = ?");
4068
+ args.push(query.platform);
4069
+ }
4070
+ args.push(limit);
4071
+ const whereClause = whereClauses.length > 0 ? "WHERE " + whereClauses.join(" AND ") : "";
4072
+ const result = await db.execute({
4073
+ sql: `
4074
+ SELECT
4075
+ id,
4076
+ type,
4077
+ subject,
4078
+ canonical_key,
4079
+ content,
4080
+ importance,
4081
+ expiry,
4082
+ scope,
4083
+ platform,
4084
+ project,
4085
+ source_file,
4086
+ source_context,
4087
+ embedding,
4088
+ created_at,
4089
+ updated_at,
4090
+ last_recalled_at,
4091
+ recall_count,
4092
+ recall_intervals,
4093
+ confirmations,
4094
+ contradictions,
4095
+ superseded_by,
4096
+ retired,
4097
+ retired_at,
4098
+ retired_reason,
4099
+ suppressed_contexts
4100
+ FROM entries
4101
+ ${whereClause}
4102
+ -- SQL pre-sort is a best-effort approximation only.
4103
+ -- Final order is determined by scoreBrowseEntry() (importance * recency decay)
4104
+ -- which re-sorts post-fetch. The over-fetch buffer (limit*3, min 50)
4105
+ -- ensures the final top-N are present in the candidate pool.
4106
+ ORDER BY importance DESC, created_at DESC
4107
+ LIMIT ?
4108
+ `,
4109
+ args
4110
+ });
4111
+ const ids = result.rows.map((row) => toStringValue2(row.id)).filter((id) => id.length > 0);
4112
+ const tagsByEntryId = await getTagsForEntryIds(db, ids);
4113
+ return result.rows.map((row) => {
4114
+ const entryId = toStringValue2(row.id);
4115
+ return {
4116
+ entry: mapStoredEntry(row, tagsByEntryId.get(entryId) ?? []),
4117
+ vectorSim: 0
4118
+ };
4119
+ });
4120
+ }
4023
4121
  async function runFts(db, text2, platform, project, excludeProject, projectStrict) {
4024
4122
  if (!text2.trim()) {
4025
4123
  return /* @__PURE__ */ new Set();
@@ -4085,10 +4183,10 @@ async function recall(db, query, apiKey, options = {}) {
4085
4183
  const excludeProject = query.excludeProject;
4086
4184
  const projectStrict = query.projectStrict === true;
4087
4185
  const isSessionStart = context === "session-start";
4088
- if (!text2 && context !== "session-start") {
4186
+ if (!text2 && context !== "session-start" && query.browse !== true) {
4089
4187
  throw new Error("Query text is required unless --context session-start is used.");
4090
4188
  }
4091
- if (!text2 && query.noBoost) {
4189
+ if (!text2 && query.noBoost && query.browse !== true) {
4092
4190
  throw new Error("--no-boost requires query text.");
4093
4191
  }
4094
4192
  const normalizedTags = normalizeTags(query.tags);
@@ -4113,6 +4211,41 @@ async function recall(db, query, apiKey, options = {}) {
4113
4211
  );
4114
4212
  }
4115
4213
  const allowedScopes = resolveScopeSet(query.scope);
4214
+ if (query.browse === true) {
4215
+ if (text2) {
4216
+ process.stderr.write(
4217
+ "[agenr] Warning: --browse mode ignores query text. Remove the query or use agenr recall <query> for semantic search.\n"
4218
+ );
4219
+ }
4220
+ if (query.noBoost) {
4221
+ throw new Error("--no-boost is not applicable in browse mode.");
4222
+ }
4223
+ const requestedLimit = Number.isFinite(query.limit) && (query.limit ?? 0) > 0 ? Math.floor(query.limit) : DEFAULT_LIMIT;
4224
+ const browseLimit = Math.max(requestedLimit * 3, 50);
4225
+ const browseCandidates = await fetchBrowseCandidates(db, query, browseLimit, now);
4226
+ const filtered2 = browseCandidates.filter(
4227
+ (candidate) => passesFilters(candidate.entry, query, cutoff, ceiling, allowedScopes, normalizedTags, false)
4228
+ );
4229
+ const scored2 = filtered2.map((candidate) => {
4230
+ const score = scoreBrowseEntry(candidate.entry, now);
4231
+ return {
4232
+ entry: candidate.entry,
4233
+ score,
4234
+ scores: {
4235
+ vector: 0,
4236
+ recency: recency(parseDaysBetween(now, candidate.entry.created_at), candidate.entry.expiry),
4237
+ importance: importanceScore(candidate.entry.importance),
4238
+ recall: 0,
4239
+ freshness: 1,
4240
+ todoPenalty: 1,
4241
+ fts: 0,
4242
+ spacing: 1
4243
+ }
4244
+ };
4245
+ });
4246
+ scored2.sort((a, b) => b.score - a.score);
4247
+ return scored2.slice(0, requestedLimit);
4248
+ }
4116
4249
  const hasDateBounds = cutoff !== void 0 || ceiling !== void 0;
4117
4250
  let candidates;
4118
4251
  let effectiveText = text2;
@@ -15777,8 +15910,8 @@ var TOOL_DEFINITIONS = [
15777
15910
  },
15778
15911
  context: {
15779
15912
  type: "string",
15780
- description: "Recall context. Use 'session-start' for fast bootstrap without embedding (no query needed).",
15781
- enum: ["default", "session-start"]
15913
+ description: "Recall context. Use 'session-start' for fast bootstrap (no query needed). Use 'browse' for temporal browsing sorted by date+importance without semantic search (no query needed, zero API calls).",
15914
+ enum: ["default", "session-start", "browse"]
15782
15915
  },
15783
15916
  limit: {
15784
15917
  type: "integer",
@@ -16161,6 +16294,27 @@ function formatRecallText(query, results) {
16161
16294
  }
16162
16295
  return lines.join("\n");
16163
16296
  }
16297
+ function formatBrowseText(results) {
16298
+ if (results.length === 0) {
16299
+ return "No entries found in the specified time window.";
16300
+ }
16301
+ const lines = [`Found ${results.length} entries (browse mode):`, ""];
16302
+ for (let i = 0; i < results.length; i += 1) {
16303
+ const result = results[i];
16304
+ if (!result) {
16305
+ continue;
16306
+ }
16307
+ const dateStr = new Date(result.entry.created_at).toISOString().slice(0, 10);
16308
+ lines.push(
16309
+ `[${i + 1}] [id=${result.entry.id}] [${dateStr}] importance=${result.entry.importance} type=${result.entry.type}`
16310
+ );
16311
+ lines.push(result.entry.content);
16312
+ if (i < results.length - 1) {
16313
+ lines.push("");
16314
+ }
16315
+ }
16316
+ return lines.join("\n");
16317
+ }
16164
16318
  function formatStoreSummary(result) {
16165
16319
  const total = result.added + result.updated + result.skipped + result.superseded;
16166
16320
  const parts = [`${result.added} new`, `${result.updated} updated`, `${result.skipped} duplicates skipped`];
@@ -16312,12 +16466,12 @@ function createMcpServer(options = {}, deps = {}) {
16312
16466
  async function callRecallTool(args) {
16313
16467
  const contextRaw = typeof args.context === "string" ? args.context.trim().toLowerCase() : "default";
16314
16468
  const context = contextRaw || "default";
16315
- if (context !== "default" && context !== "session-start") {
16316
- throw new RpcError(JSON_RPC_INVALID_PARAMS, "context must be one of: default, session-start");
16469
+ if (context !== "default" && context !== "session-start" && context !== "browse") {
16470
+ throw new RpcError(JSON_RPC_INVALID_PARAMS, "context must be one of: default, session-start, browse");
16317
16471
  }
16318
16472
  const query = typeof args.query === "string" ? args.query.trim() : "";
16319
- if (!query && context !== "session-start") {
16320
- throw new RpcError(JSON_RPC_INVALID_PARAMS, "query is required unless context is session-start");
16473
+ if (!query && context === "default") {
16474
+ throw new RpcError(JSON_RPC_INVALID_PARAMS, "query is required unless context is session-start or browse");
16321
16475
  }
16322
16476
  const limit = parsePositiveInt5(args.limit, 10, "limit");
16323
16477
  const threshold = parseThreshold(args.threshold);
@@ -16368,7 +16522,26 @@ function createMcpServer(options = {}, deps = {}) {
16368
16522
  }
16369
16523
  const db = await ensureDb();
16370
16524
  let results;
16371
- if (context === "session-start") {
16525
+ if (context === "browse") {
16526
+ results = await resolvedDeps.recallFn(
16527
+ db,
16528
+ {
16529
+ text: void 0,
16530
+ context: "browse",
16531
+ browse: true,
16532
+ limit,
16533
+ types,
16534
+ since,
16535
+ until,
16536
+ platform: platform ?? void 0,
16537
+ project,
16538
+ projectStrict: projectStrict ? true : void 0,
16539
+ scope: "private",
16540
+ noUpdate: true
16541
+ },
16542
+ ""
16543
+ );
16544
+ } else if (context === "session-start") {
16372
16545
  const grouped = await sessionStartRecall(db, {
16373
16546
  query: {
16374
16547
  context,
@@ -16415,6 +16588,9 @@ function createMcpServer(options = {}, deps = {}) {
16415
16588
  result.entry.last_recalled_at = nowIso;
16416
16589
  }
16417
16590
  }
16591
+ if (context === "browse") {
16592
+ return formatBrowseText(filtered);
16593
+ }
16418
16594
  return formatRecallText(query || context, filtered);
16419
16595
  }
16420
16596
  async function callStoreTool(args) {
@@ -16883,6 +17059,24 @@ ${ui.bold(section.title)}`, clackOutput);
16883
17059
  index += 1;
16884
17060
  }
16885
17061
  }
17062
+ function printBrowseResults(results, elapsedMs) {
17063
+ const clackOutput = { output: process.stderr };
17064
+ clack6.log.info(`${ui.bold(String(results.length))} entries (browse mode, ${elapsedMs}ms)`, clackOutput);
17065
+ if (results.length === 0) {
17066
+ clack6.log.info("No entries found in the specified time window.", clackOutput);
17067
+ return;
17068
+ }
17069
+ let index = 1;
17070
+ for (const result of results) {
17071
+ const dateStr = new Date(result.entry.created_at).toISOString().slice(0, 10);
17072
+ clack6.log.info(
17073
+ `${index}. [${dateStr}] [importance=${result.entry.importance}] (${result.entry.type}) ${result.entry.subject}: ${result.entry.content}`,
17074
+ clackOutput
17075
+ );
17076
+ clack6.log.info(` tags: ${result.entry.tags.length > 0 ? result.entry.tags.join(", ") : "none"}`, clackOutput);
17077
+ index += 1;
17078
+ }
17079
+ }
16886
17080
  async function runRecallCommand(queryInput, options, deps) {
16887
17081
  const resolvedDeps = {
16888
17082
  readConfigFn: deps?.readConfigFn ?? readConfig,
@@ -16900,12 +17094,24 @@ async function runRecallCommand(queryInput, options, deps) {
16900
17094
  const queryText = queryInput?.trim() ?? "";
16901
17095
  const context = options.context?.trim() || "default";
16902
17096
  const isSessionStart = context === "session-start";
16903
- if (!queryText && !isSessionStart) {
16904
- throw new Error("Provide a query or use --context session-start.");
17097
+ const isBrowse = options.browse === true;
17098
+ if (isBrowse && isSessionStart) {
17099
+ throw new Error("--browse and --context session-start cannot be combined.");
17100
+ }
17101
+ if (!queryText && !isSessionStart && !isBrowse) {
17102
+ throw new Error("Provide a query, use --context session-start, or use --browse.");
17103
+ }
17104
+ if (options.noBoost && isBrowse) {
17105
+ throw new Error("--no-boost is not applicable in browse mode.");
16905
17106
  }
16906
17107
  if (options.noBoost && !queryText) {
16907
17108
  throw new Error("--no-boost requires a query.");
16908
17109
  }
17110
+ if (isBrowse && queryText) {
17111
+ process.stderr.write(
17112
+ "[agenr] Warning: --browse mode ignores query text. Remove the query or use agenr recall <query> for semantic search.\n"
17113
+ );
17114
+ }
16909
17115
  const limit = parsePositiveInt6(options.limit, DEFAULT_LIMIT4, "--limit");
16910
17116
  const budget = options.budget !== void 0 ? parsePositiveInt6(options.budget, DEFAULT_LIMIT4, "--budget") : void 0;
16911
17117
  const parsedTypes = parseCsv(options.type);
@@ -16960,7 +17166,7 @@ async function runRecallCommand(queryInput, options, deps) {
16960
17166
  const sinceIso = parseSinceToIso(options.since, now, "Invalid --since value. Use 1h, 7d, 1m, 1y, or an ISO date.");
16961
17167
  const untilIso = parseSinceToIso(options.until, now, "Invalid --until value. Use 1h, 7d, 1m, 1y, or an ISO date.");
16962
17168
  const queryForRecall = {
16963
- text: queryText ? shapeRecallText(queryText, context) : void 0,
17169
+ text: queryText && !isBrowse ? shapeRecallText(queryText, context) : void 0,
16964
17170
  limit,
16965
17171
  types: types.length > 0 ? types : void 0,
16966
17172
  tags: tags.length > 0 ? tags : void 0,
@@ -16976,18 +17182,23 @@ async function runRecallCommand(queryInput, options, deps) {
16976
17182
  noBoost: options.noBoost === true,
16977
17183
  noUpdate: true,
16978
17184
  context,
16979
- budget
17185
+ budget,
17186
+ browse: isBrowse
16980
17187
  };
16981
17188
  const config = resolvedDeps.readConfigFn(process.env);
16982
17189
  const dbPath = options.db?.trim() || config?.db?.path;
16983
17190
  const db = resolvedDeps.getDbFn(dbPath);
16984
17191
  try {
16985
17192
  await resolvedDeps.initDbFn(db);
16986
- const apiKey = queryText ? resolvedDeps.resolveEmbeddingApiKeyFn(config, process.env) : "";
17193
+ const apiKey = queryText && !isBrowse ? resolvedDeps.resolveEmbeddingApiKeyFn(config, process.env) : "";
16987
17194
  const startedAt = Date.now();
16988
17195
  let finalResults = [];
16989
17196
  let budgetUsed = 0;
16990
- if (isSessionStart) {
17197
+ if (isBrowse) {
17198
+ const baseResults = await resolvedDeps.recallFn(db, queryForRecall, apiKey);
17199
+ finalResults = baseResults.map((result) => ({ ...result }));
17200
+ budgetUsed = finalResults.reduce((sum, item) => sum + estimateEntryTokens(item), 0);
17201
+ } else if (isSessionStart) {
16991
17202
  const grouped = await sessionStartRecall(db, {
16992
17203
  query: queryForRecall,
16993
17204
  apiKey,
@@ -17008,7 +17219,7 @@ async function runRecallCommand(queryInput, options, deps) {
17008
17219
  finalResults = consumed.selected.map((result) => ({ ...result }));
17009
17220
  }
17010
17221
  }
17011
- if (!options.noUpdate && finalResults.length > 0) {
17222
+ if (!isBrowse && !options.noUpdate && finalResults.length > 0) {
17012
17223
  const ids = finalResults.map((result) => result.entry.id);
17013
17224
  await resolvedDeps.updateRecallMetadataFn(db, ids, now);
17014
17225
  const nowIso = now.toISOString();
@@ -17019,7 +17230,7 @@ async function runRecallCommand(queryInput, options, deps) {
17019
17230
  }
17020
17231
  const stripped = finalResults.map((result) => stripEmbedding(result));
17021
17232
  const payload = {
17022
- query: queryText,
17233
+ query: isBrowse ? "[browse]" : queryText,
17023
17234
  results: stripped,
17024
17235
  total: stripped.length,
17025
17236
  budget_used: budgetUsed,
@@ -17030,7 +17241,11 @@ async function runRecallCommand(queryInput, options, deps) {
17030
17241
  process.stdout.write(`${JSON.stringify(payload, null, 2)}
17031
17242
  `);
17032
17243
  } else {
17033
- printHumanResults(stripped, elapsedMs, now, isSessionStart);
17244
+ if (isBrowse) {
17245
+ printBrowseResults(stripped, elapsedMs);
17246
+ } else {
17247
+ printHumanResults(stripped, elapsedMs, now, isSessionStart);
17248
+ }
17034
17249
  }
17035
17250
  clack6.outro(void 0, clackOutput);
17036
17251
  return {
@@ -19447,7 +19662,7 @@ function createProgram() {
19447
19662
  process.exitCode = result.exitCode;
19448
19663
  }
19449
19664
  );
19450
- program.command("recall").description("Recall knowledge from the local database").argument("[query]", "Natural language query").option("--limit <n>", "Maximum number of results", parseIntOption, 10).option("--type <types>", "Filter by comma-separated entry types").option("--tags <tags>", "Filter by comma-separated tags").option("--min-importance <n>", "Minimum importance: 1-10").option("--since <duration>", "Filter by recency (1h, 7d, 30d, 1y) or ISO timestamp").option("--until <date>", "Only entries at or before this time (ISO date or relative, e.g. 7d, 1m)").option("--expiry <level>", "Filter by expiry: core|permanent|temporary").option("--platform <name>", "Filter by platform: openclaw, claude-code, codex, plaud").option("--project <name>", "Filter by project (repeatable)", (val, prev) => [...prev, val], []).option("--exclude-project <name>", "Exclude entries from project (repeatable)", (val, prev) => [...prev, val], []).option("--strict", "Exclude NULL-project entries from results", false).option("--json", "Output JSON", false).option("--db <path>", "Database path override").option("--budget <tokens>", "Approximate token budget", parseIntOption).option("--context <mode>", "Context mode: default|session-start|topic:<query>", "default").option("--scope <level>", "Visibility scope: private|personal|public", "private").option("--no-boost", "Disable scoring boosts and use raw vector similarity", false).option("--no-update", "Do not increment recall metadata", false).action(async (query, opts) => {
19665
+ program.command("recall").description("Recall knowledge from the local database").argument("[query]", "Natural language query").option("--limit <n>", "Maximum number of results", parseIntOption, 10).option("--type <types>", "Filter by comma-separated entry types").option("--tags <tags>", "Filter by comma-separated tags").option("--min-importance <n>", "Minimum importance: 1-10").option("--since <duration>", "Filter by recency (1h, 7d, 30d, 1y) or ISO timestamp").option("--until <date>", "Only entries at or before this time (ISO date or relative, e.g. 7d, 1m)").option("--expiry <level>", "Filter by expiry: core|permanent|temporary").option("--platform <name>", "Filter by platform: openclaw, claude-code, codex, plaud").option("--project <name>", "Filter by project (repeatable)", (val, prev) => [...prev, val], []).option("--exclude-project <name>", "Exclude entries from project (repeatable)", (val, prev) => [...prev, val], []).option("--strict", "Exclude NULL-project entries from results", false).option("--json", "Output JSON", false).option("--db <path>", "Database path override").option("--budget <tokens>", "Approximate token budget", parseIntOption).option("--context <mode>", "Context mode: default|session-start|topic:<query>", "default").option("--scope <level>", "Visibility scope: private|personal|public", "private").option("--browse", "Skip semantic search and return entries sorted by importance and date", false).option("--no-boost", "Disable scoring boosts and use raw vector similarity", false).option("--no-update", "Do not increment recall metadata", false).action(async (query, opts) => {
19451
19666
  const result = await runRecallCommand(query, {
19452
19667
  limit: opts.limit,
19453
19668
  type: opts.type,
@@ -19465,6 +19680,7 @@ function createProgram() {
19465
19680
  budget: opts.budget,
19466
19681
  context: opts.context,
19467
19682
  scope: opts.scope,
19683
+ browse: opts.browse === true,
19468
19684
  noBoost: opts.noBoost === true,
19469
19685
  noUpdate: opts.noUpdate === true
19470
19686
  });
@@ -573,16 +573,19 @@ async function runRecallTool(agenrPath, params, defaultProject) {
573
573
  const args = ["recall", "--json"];
574
574
  const query = asString(params.query);
575
575
  const context = asString(params.context);
576
+ const isBrowse = context === "browse";
576
577
  const limit = asNumber(params.limit);
577
578
  const types = asString(params.types);
578
579
  const since = asString(params.since);
579
580
  const until = asString(params.until);
580
581
  const platform = asString(params.platform);
581
582
  const project = asString(params.project) || defaultProject;
582
- if (query) {
583
+ if (isBrowse) {
584
+ args.push("--browse");
585
+ } else if (query) {
583
586
  args.push(query);
584
587
  }
585
- if (context) {
588
+ if (context && !isBrowse) {
586
589
  args.push("--context", context);
587
590
  }
588
591
  if (limit !== void 0) {
@@ -1045,8 +1048,10 @@ var plugin = {
1045
1048
  query: Type.Optional(Type.String({ description: "What to search for." })),
1046
1049
  context: Type.Optional(
1047
1050
  Type.Union(
1048
- [Type.Literal("default"), Type.Literal("session-start")],
1049
- { description: "Use session-start for fast bootstrap without embedding." }
1051
+ [Type.Literal("default"), Type.Literal("session-start"), Type.Literal("browse")],
1052
+ {
1053
+ description: "Use session-start for fast bootstrap without embedding. Use browse for temporal browsing (date+importance, no query needed, no semantic search)."
1054
+ }
1050
1055
  )
1051
1056
  ),
1052
1057
  limit: Type.Optional(Type.Number({ description: "Max results (default: 10)." })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenr",
3
- "version": "0.8.15",
3
+ "version": "0.8.19",
4
4
  "openclaw": {
5
5
  "extensions": [
6
6
  "dist/openclaw-plugin/index.js"