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 +44 -0
- package/dist/cli-main.js +235 -19
- package/dist/openclaw-plugin/index.js +9 -4
- package/package.json +1 -1
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
|
|
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
|
|
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 === "
|
|
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
|
-
|
|
16904
|
-
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
{
|
|
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)." })),
|