agenr 0.8.14 → 0.8.17
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 +28 -0
- package/dist/cli-main.js +249 -24
- package/dist/openclaw-plugin/index.js +9 -4
- package/package.json +13 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.17] - 2026-02-23
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- chore: rebuild dist to include browse mode CLI flag inadvertently omitted from 0.8.16 publish
|
|
7
|
+
|
|
8
|
+
## [0.8.16] - 2026-02-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- feat(recall): new temporal browse mode for recall via `agenr recall --browse` and MCP `agenr_recall` with `context="browse"` (issue #190)
|
|
12
|
+
- docs(recall): added `docs/usage/recall.md` with browse-mode CLI and MCP usage examples
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- recall browse mode now uses a SQL-only path that requires no query and performs zero embedding/OpenAI API calls
|
|
16
|
+
- browse mode does not increment recall metadata (`recall_count`, `last_recalled_at`, `recall_intervals`)
|
|
17
|
+
- OpenClaw plugin tool wiring now maps `context="browse"` to the CLI `--browse` flag (and omits query/context positional args appropriately)
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
- test(recall): added browse-mode coverage in DB recall, CLI command recall, MCP server recall, and OpenClaw plugin recall tool argument wiring
|
|
21
|
+
|
|
22
|
+
## [0.8.15] - 2026-02-23
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- fix(consolidate): switch GROUP_CONCAT separator from comma to pipe in buildClusters to prevent silent tag corruption when tag values contain commas (issue #155)
|
|
26
|
+
- fix(consolidate): Tier 1 near-exact duplicate merge now preserves the highest importance across merged entries by raising the keeper's `importance` floor to the group max (issue #156)
|
|
27
|
+
- fix(consolidate): Tier 1 near-exact duplicate merge now preserves oldest provenance by inheriting the oldest `created_at` across the merge group into the keeper (issue #156)
|
|
28
|
+
- test(consolidate): new cluster.test.ts with pipe-separator roundtrip and comma-in-tag regression coverage (issue #155)
|
|
29
|
+
- test(consolidate): added merge coverage for tag union transfer, keeper importance floor, and keeper `created_at` inheritance in rules consolidation tests (issue #156)
|
|
30
|
+
|
|
3
31
|
## [0.8.13] - 2026-02-23
|
|
4
32
|
|
|
5
33
|
### 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;
|
|
@@ -6540,7 +6673,7 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
6540
6673
|
args.push(...projectSql.args);
|
|
6541
6674
|
const result = await db.execute({
|
|
6542
6675
|
sql: `
|
|
6543
|
-
SELECT id, type, subject, content, project, embedding, confirmations, recall_count, created_at
|
|
6676
|
+
SELECT id, type, subject, content, project, importance, embedding, confirmations, recall_count, created_at
|
|
6544
6677
|
FROM entries
|
|
6545
6678
|
WHERE superseded_by IS NULL
|
|
6546
6679
|
AND retired = 0
|
|
@@ -6556,6 +6689,7 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
6556
6689
|
subject: toStringValue2(row.subject),
|
|
6557
6690
|
content: toStringValue2(row.content),
|
|
6558
6691
|
project: toProjectValue(row.project),
|
|
6692
|
+
importance: Number.isFinite(toNumber3(row.importance)) ? toNumber3(row.importance) : 5,
|
|
6559
6693
|
embedding: mapBufferToVector3(row.embedding),
|
|
6560
6694
|
confirmations: Number.isFinite(toNumber3(row.confirmations)) ? toNumber3(row.confirmations) : 0,
|
|
6561
6695
|
recallCount: Number.isFinite(toNumber3(row.recall_count)) ? toNumber3(row.recall_count) : 0,
|
|
@@ -6628,6 +6762,12 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
6628
6762
|
continue;
|
|
6629
6763
|
}
|
|
6630
6764
|
const totalConfirmations = sorted.reduce((sum, entry) => sum + entry.confirmations, 0);
|
|
6765
|
+
const maxImportance = sorted.reduce((max, entry) => Math.max(max, entry.importance ?? 5), 0);
|
|
6766
|
+
const oldestCreatedAt = sorted.reduce((oldest, entry) => {
|
|
6767
|
+
const t = Date.parse(entry.createdAt);
|
|
6768
|
+
const o = Date.parse(oldest);
|
|
6769
|
+
return Number.isFinite(t) && t < (Number.isFinite(o) ? o : Infinity) ? entry.createdAt : oldest;
|
|
6770
|
+
}, keeper.createdAt);
|
|
6631
6771
|
for (const source of sources) {
|
|
6632
6772
|
await db.execute({
|
|
6633
6773
|
sql: `
|
|
@@ -6662,10 +6802,12 @@ async function mergeNearExactDuplicates(db, options) {
|
|
|
6662
6802
|
UPDATE entries
|
|
6663
6803
|
SET merged_from = ?,
|
|
6664
6804
|
consolidated_at = datetime('now'),
|
|
6665
|
-
confirmations =
|
|
6805
|
+
confirmations = ?,
|
|
6806
|
+
importance = CASE WHEN importance < ? THEN ? ELSE importance END,
|
|
6807
|
+
created_at = CASE WHEN created_at > ? THEN ? ELSE created_at END
|
|
6666
6808
|
WHERE id = ?
|
|
6667
6809
|
`,
|
|
6668
|
-
args: [sources.length, totalConfirmations, keeper.id]
|
|
6810
|
+
args: [sources.length, totalConfirmations, maxImportance, maxImportance, oldestCreatedAt, oldestCreatedAt, keeper.id]
|
|
6669
6811
|
});
|
|
6670
6812
|
}
|
|
6671
6813
|
return mergedCount;
|
|
@@ -6882,7 +7024,7 @@ function mapActiveEmbeddedEntry(row) {
|
|
|
6882
7024
|
const projectRaw = toStringValue3(row.project);
|
|
6883
7025
|
const project = projectRaw.trim().length > 0 ? projectRaw.trim().toLowerCase() : null;
|
|
6884
7026
|
const tagsRaw = toStringValue3(row.tags_csv);
|
|
6885
|
-
const tags = tagsRaw.length > 0 ? tagsRaw.split("
|
|
7027
|
+
const tags = tagsRaw.length > 0 ? tagsRaw.split("|").map((tag) => tag.trim()).filter(Boolean) : [];
|
|
6886
7028
|
return {
|
|
6887
7029
|
id,
|
|
6888
7030
|
type: toStringValue3(row.type),
|
|
@@ -6943,7 +7085,7 @@ async function buildClusters(db, options = {}) {
|
|
|
6943
7085
|
e.merged_from,
|
|
6944
7086
|
e.consolidated_at,
|
|
6945
7087
|
(
|
|
6946
|
-
SELECT GROUP_CONCAT(t.tag, '
|
|
7088
|
+
SELECT GROUP_CONCAT(t.tag, '|')
|
|
6947
7089
|
FROM tags t
|
|
6948
7090
|
WHERE t.entry_id = e.id
|
|
6949
7091
|
) AS tags_csv
|
|
@@ -15768,8 +15910,8 @@ var TOOL_DEFINITIONS = [
|
|
|
15768
15910
|
},
|
|
15769
15911
|
context: {
|
|
15770
15912
|
type: "string",
|
|
15771
|
-
description: "Recall context. Use 'session-start' for fast bootstrap without
|
|
15772
|
-
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"]
|
|
15773
15915
|
},
|
|
15774
15916
|
limit: {
|
|
15775
15917
|
type: "integer",
|
|
@@ -16152,6 +16294,27 @@ function formatRecallText(query, results) {
|
|
|
16152
16294
|
}
|
|
16153
16295
|
return lines.join("\n");
|
|
16154
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
|
+
}
|
|
16155
16318
|
function formatStoreSummary(result) {
|
|
16156
16319
|
const total = result.added + result.updated + result.skipped + result.superseded;
|
|
16157
16320
|
const parts = [`${result.added} new`, `${result.updated} updated`, `${result.skipped} duplicates skipped`];
|
|
@@ -16303,12 +16466,12 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
16303
16466
|
async function callRecallTool(args) {
|
|
16304
16467
|
const contextRaw = typeof args.context === "string" ? args.context.trim().toLowerCase() : "default";
|
|
16305
16468
|
const context = contextRaw || "default";
|
|
16306
|
-
if (context !== "default" && context !== "session-start") {
|
|
16307
|
-
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");
|
|
16308
16471
|
}
|
|
16309
16472
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
16310
|
-
if (!query && context
|
|
16311
|
-
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");
|
|
16312
16475
|
}
|
|
16313
16476
|
const limit = parsePositiveInt5(args.limit, 10, "limit");
|
|
16314
16477
|
const threshold = parseThreshold(args.threshold);
|
|
@@ -16359,7 +16522,26 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
16359
16522
|
}
|
|
16360
16523
|
const db = await ensureDb();
|
|
16361
16524
|
let results;
|
|
16362
|
-
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") {
|
|
16363
16545
|
const grouped = await sessionStartRecall(db, {
|
|
16364
16546
|
query: {
|
|
16365
16547
|
context,
|
|
@@ -16406,6 +16588,9 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
16406
16588
|
result.entry.last_recalled_at = nowIso;
|
|
16407
16589
|
}
|
|
16408
16590
|
}
|
|
16591
|
+
if (context === "browse") {
|
|
16592
|
+
return formatBrowseText(filtered);
|
|
16593
|
+
}
|
|
16409
16594
|
return formatRecallText(query || context, filtered);
|
|
16410
16595
|
}
|
|
16411
16596
|
async function callStoreTool(args) {
|
|
@@ -16874,6 +17059,24 @@ ${ui.bold(section.title)}`, clackOutput);
|
|
|
16874
17059
|
index += 1;
|
|
16875
17060
|
}
|
|
16876
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
|
+
}
|
|
16877
17080
|
async function runRecallCommand(queryInput, options, deps) {
|
|
16878
17081
|
const resolvedDeps = {
|
|
16879
17082
|
readConfigFn: deps?.readConfigFn ?? readConfig,
|
|
@@ -16891,12 +17094,24 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
16891
17094
|
const queryText = queryInput?.trim() ?? "";
|
|
16892
17095
|
const context = options.context?.trim() || "default";
|
|
16893
17096
|
const isSessionStart = context === "session-start";
|
|
16894
|
-
|
|
16895
|
-
|
|
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.");
|
|
16896
17106
|
}
|
|
16897
17107
|
if (options.noBoost && !queryText) {
|
|
16898
17108
|
throw new Error("--no-boost requires a query.");
|
|
16899
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
|
+
}
|
|
16900
17115
|
const limit = parsePositiveInt6(options.limit, DEFAULT_LIMIT4, "--limit");
|
|
16901
17116
|
const budget = options.budget !== void 0 ? parsePositiveInt6(options.budget, DEFAULT_LIMIT4, "--budget") : void 0;
|
|
16902
17117
|
const parsedTypes = parseCsv(options.type);
|
|
@@ -16951,7 +17166,7 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
16951
17166
|
const sinceIso = parseSinceToIso(options.since, now, "Invalid --since value. Use 1h, 7d, 1m, 1y, or an ISO date.");
|
|
16952
17167
|
const untilIso = parseSinceToIso(options.until, now, "Invalid --until value. Use 1h, 7d, 1m, 1y, or an ISO date.");
|
|
16953
17168
|
const queryForRecall = {
|
|
16954
|
-
text: queryText ? shapeRecallText(queryText, context) : void 0,
|
|
17169
|
+
text: queryText && !isBrowse ? shapeRecallText(queryText, context) : void 0,
|
|
16955
17170
|
limit,
|
|
16956
17171
|
types: types.length > 0 ? types : void 0,
|
|
16957
17172
|
tags: tags.length > 0 ? tags : void 0,
|
|
@@ -16967,18 +17182,23 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
16967
17182
|
noBoost: options.noBoost === true,
|
|
16968
17183
|
noUpdate: true,
|
|
16969
17184
|
context,
|
|
16970
|
-
budget
|
|
17185
|
+
budget,
|
|
17186
|
+
browse: isBrowse
|
|
16971
17187
|
};
|
|
16972
17188
|
const config = resolvedDeps.readConfigFn(process.env);
|
|
16973
17189
|
const dbPath = options.db?.trim() || config?.db?.path;
|
|
16974
17190
|
const db = resolvedDeps.getDbFn(dbPath);
|
|
16975
17191
|
try {
|
|
16976
17192
|
await resolvedDeps.initDbFn(db);
|
|
16977
|
-
const apiKey = queryText ? resolvedDeps.resolveEmbeddingApiKeyFn(config, process.env) : "";
|
|
17193
|
+
const apiKey = queryText && !isBrowse ? resolvedDeps.resolveEmbeddingApiKeyFn(config, process.env) : "";
|
|
16978
17194
|
const startedAt = Date.now();
|
|
16979
17195
|
let finalResults = [];
|
|
16980
17196
|
let budgetUsed = 0;
|
|
16981
|
-
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) {
|
|
16982
17202
|
const grouped = await sessionStartRecall(db, {
|
|
16983
17203
|
query: queryForRecall,
|
|
16984
17204
|
apiKey,
|
|
@@ -16999,7 +17219,7 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
16999
17219
|
finalResults = consumed.selected.map((result) => ({ ...result }));
|
|
17000
17220
|
}
|
|
17001
17221
|
}
|
|
17002
|
-
if (!options.noUpdate && finalResults.length > 0) {
|
|
17222
|
+
if (!isBrowse && !options.noUpdate && finalResults.length > 0) {
|
|
17003
17223
|
const ids = finalResults.map((result) => result.entry.id);
|
|
17004
17224
|
await resolvedDeps.updateRecallMetadataFn(db, ids, now);
|
|
17005
17225
|
const nowIso = now.toISOString();
|
|
@@ -17010,7 +17230,7 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
17010
17230
|
}
|
|
17011
17231
|
const stripped = finalResults.map((result) => stripEmbedding(result));
|
|
17012
17232
|
const payload = {
|
|
17013
|
-
query: queryText,
|
|
17233
|
+
query: isBrowse ? "[browse]" : queryText,
|
|
17014
17234
|
results: stripped,
|
|
17015
17235
|
total: stripped.length,
|
|
17016
17236
|
budget_used: budgetUsed,
|
|
@@ -17021,7 +17241,11 @@ async function runRecallCommand(queryInput, options, deps) {
|
|
|
17021
17241
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
17022
17242
|
`);
|
|
17023
17243
|
} else {
|
|
17024
|
-
|
|
17244
|
+
if (isBrowse) {
|
|
17245
|
+
printBrowseResults(stripped, elapsedMs);
|
|
17246
|
+
} else {
|
|
17247
|
+
printHumanResults(stripped, elapsedMs, now, isSessionStart);
|
|
17248
|
+
}
|
|
17025
17249
|
}
|
|
17026
17250
|
clack6.outro(void 0, clackOutput);
|
|
17027
17251
|
return {
|
|
@@ -19438,7 +19662,7 @@ function createProgram() {
|
|
|
19438
19662
|
process.exitCode = result.exitCode;
|
|
19439
19663
|
}
|
|
19440
19664
|
);
|
|
19441
|
-
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) => {
|
|
19442
19666
|
const result = await runRecallCommand(query, {
|
|
19443
19667
|
limit: opts.limit,
|
|
19444
19668
|
type: opts.type,
|
|
@@ -19456,6 +19680,7 @@ function createProgram() {
|
|
|
19456
19680
|
budget: opts.budget,
|
|
19457
19681
|
context: opts.context,
|
|
19458
19682
|
scope: opts.scope,
|
|
19683
|
+
browse: opts.browse === true,
|
|
19459
19684
|
noBoost: opts.noBoost === true,
|
|
19460
19685
|
noUpdate: opts.noUpdate === true
|
|
19461
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)." })),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenr",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.17",
|
|
4
4
|
"openclaw": {
|
|
5
5
|
"extensions": [
|
|
6
6
|
"dist/openclaw-plugin/index.js"
|
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"agenr": "dist/cli.js"
|
|
13
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
|
|
16
|
+
"dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit"
|
|
20
|
+
},
|
|
14
21
|
"dependencies": {
|
|
15
22
|
"@clack/prompts": "^1.0.1",
|
|
16
23
|
"@libsql/client": "^0.17.0",
|
|
@@ -54,11 +61,9 @@
|
|
|
54
61
|
"README.md"
|
|
55
62
|
],
|
|
56
63
|
"author": "agenr-ai",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"test:watch": "vitest",
|
|
62
|
-
"typecheck": "tsc --noEmit"
|
|
64
|
+
"pnpm": {
|
|
65
|
+
"overrides": {
|
|
66
|
+
"fast-xml-parser": "^5.3.6"
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
|
-
}
|
|
69
|
+
}
|