agenr 0.7.5 → 0.7.6
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 +13 -0
- package/README.md +2 -2
- package/dist/cli-main.js +51 -11
- package/dist/openclaw-plugin/index.js +65 -34
- package/package.json +8 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.6] - 2026-02-20
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- fix(plugin): `agenr_recall` now passes query as a positional argument instead of unsupported `--query`
|
|
7
|
+
- fix(plugin): `agenr_recall` now uses `--type` (singular) instead of invalid `--types`
|
|
8
|
+
- fix(plugin): removed unsupported `--threshold` forwarding from `agenr_recall`; threshold has no direct CLI equivalent
|
|
9
|
+
- fix(plugin): `agenr_store` now sends entries array directly on stdin and passes `platform`/`project` as CLI flags
|
|
10
|
+
- fix(plugin): `agenr_store` now infers missing `subject` from `content` before CLI spawn, matching MCP server behavior
|
|
11
|
+
- fix(plugin): `agenr_retire` now calls `agenr retire --id <entry_id>` instead of subject matching with UUIDs
|
|
12
|
+
- fix(cli): `agenr retire` now supports `--id <id>` and enforces exactly one of subject or `--id`
|
|
13
|
+
- fix(plugin): `agenr_extract` now uses a two-step flow for `store=true` (`extract --json` then `store`) and injects source metadata before storing
|
|
14
|
+
- fix(cli): `agenr store` now accepts the `--aggressive` flag used by plugin dedup config forwarding
|
|
15
|
+
|
|
3
16
|
## [0.7.5] - 2026-02-20
|
|
4
17
|
|
|
5
18
|
### Changed
|
package/README.md
CHANGED
|
@@ -202,7 +202,7 @@ MCP settings.
|
|
|
202
202
|
| `agenr extract <files...>` | Extract knowledge entries from text files |
|
|
203
203
|
| `agenr store [files...]` | Store entries with semantic dedup |
|
|
204
204
|
| `agenr recall [query]` | Semantic + memory-aware recall |
|
|
205
|
-
| `agenr retire
|
|
205
|
+
| `agenr retire [subject]` | Retire a stale entry (hidden, not deleted). Match by subject text or use --id <id> to target by entry ID. |
|
|
206
206
|
| `agenr watch [file]` | Live-watch files/directories, auto-extract knowledge |
|
|
207
207
|
| `agenr daemon install` | Install background watch daemon (macOS launchd) |
|
|
208
208
|
| `agenr daemon status` | Show daemon status (running/stopped, pid, watched file, recent logs) |
|
|
@@ -227,7 +227,7 @@ Deep dive: [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md)
|
|
|
227
227
|
|
|
228
228
|
## Status
|
|
229
229
|
|
|
230
|
-
The core pipeline is stable and tested (
|
|
230
|
+
The core pipeline is stable and tested (797 tests). We use it daily managing
|
|
231
231
|
thousands of knowledge entries across OpenClaw sessions.
|
|
232
232
|
|
|
233
233
|
What works: extraction, storage, recall, MCP integration, online dedup, consolidation, smart filtering, live watching, daemon mode.
|
package/dist/cli-main.js
CHANGED
|
@@ -14968,6 +14968,23 @@ async function queryCandidates(db, subject, contains) {
|
|
|
14968
14968
|
content: toStringValue5(row.content)
|
|
14969
14969
|
}));
|
|
14970
14970
|
}
|
|
14971
|
+
async function queryById(db, id) {
|
|
14972
|
+
const result = await db.execute({
|
|
14973
|
+
sql: `
|
|
14974
|
+
SELECT id, type, subject, importance, content
|
|
14975
|
+
FROM entries
|
|
14976
|
+
WHERE retired = 0 AND id = ?
|
|
14977
|
+
`,
|
|
14978
|
+
args: [id]
|
|
14979
|
+
});
|
|
14980
|
+
return result.rows.map((row) => ({
|
|
14981
|
+
id: toStringValue5(row.id),
|
|
14982
|
+
type: toStringValue5(row.type),
|
|
14983
|
+
subject: toStringValue5(row.subject),
|
|
14984
|
+
importance: toNumber7(row.importance),
|
|
14985
|
+
content: toStringValue5(row.content)
|
|
14986
|
+
}));
|
|
14987
|
+
}
|
|
14971
14988
|
async function runRetireCommand(subject, options, deps) {
|
|
14972
14989
|
const resolvedDeps = {
|
|
14973
14990
|
readConfigFn: deps?.readConfigFn ?? readConfig,
|
|
@@ -14989,8 +15006,12 @@ async function runRetireCommand(subject, options, deps) {
|
|
|
14989
15006
|
logFn: deps?.logFn ?? ((line) => clack7.log.info(line, { output: process.stderr }))
|
|
14990
15007
|
};
|
|
14991
15008
|
const querySubject = subject.trim();
|
|
14992
|
-
|
|
14993
|
-
|
|
15009
|
+
const queryId = options.id?.trim() ?? "";
|
|
15010
|
+
if (!queryId && !querySubject) {
|
|
15011
|
+
throw new Error("subject or id is required");
|
|
15012
|
+
}
|
|
15013
|
+
if (queryId && querySubject) {
|
|
15014
|
+
throw new Error("subject and id are mutually exclusive");
|
|
14994
15015
|
}
|
|
14995
15016
|
const clackOutput = { output: process.stderr };
|
|
14996
15017
|
clack7.intro(banner(), clackOutput);
|
|
@@ -15000,9 +15021,15 @@ async function runRetireCommand(subject, options, deps) {
|
|
|
15000
15021
|
try {
|
|
15001
15022
|
await resolvedDeps.initDbFn(db);
|
|
15002
15023
|
const contains = options.contains === true;
|
|
15003
|
-
|
|
15024
|
+
let candidates;
|
|
15025
|
+
if (queryId) {
|
|
15026
|
+
candidates = await queryById(db, queryId);
|
|
15027
|
+
} else {
|
|
15028
|
+
candidates = await queryCandidates(db, querySubject, contains);
|
|
15029
|
+
}
|
|
15030
|
+
const queryDisplay = queryId || querySubject;
|
|
15004
15031
|
if (candidates.length === 0) {
|
|
15005
|
-
clack7.log.warn(`No active entries matching: ${
|
|
15032
|
+
clack7.log.warn(`No active entries matching: ${queryDisplay}`, clackOutput);
|
|
15006
15033
|
clack7.outro(void 0, clackOutput);
|
|
15007
15034
|
return { exitCode: 1 };
|
|
15008
15035
|
}
|
|
@@ -15038,16 +15065,17 @@ async function runRetireCommand(subject, options, deps) {
|
|
|
15038
15065
|
return { exitCode: 1 };
|
|
15039
15066
|
}
|
|
15040
15067
|
}
|
|
15068
|
+
const retireSubject = queryId ? candidates[0]?.subject ?? queryId : querySubject;
|
|
15041
15069
|
const retired = await resolvedDeps.retireEntriesFn({
|
|
15042
|
-
subjectPattern:
|
|
15043
|
-
matchType: contains ? "contains" : "exact",
|
|
15070
|
+
subjectPattern: retireSubject,
|
|
15071
|
+
matchType: queryId ? "exact" : contains ? "contains" : "exact",
|
|
15044
15072
|
reason: options.reason?.trim() || void 0,
|
|
15045
15073
|
writeLedger: options.persist === true,
|
|
15046
15074
|
db,
|
|
15047
15075
|
dbPath
|
|
15048
15076
|
});
|
|
15049
15077
|
if (retired.count === 0) {
|
|
15050
|
-
clack7.log.warn(`No active entries matching: ${
|
|
15078
|
+
clack7.log.warn(`No active entries matching: ${queryDisplay}`, clackOutput);
|
|
15051
15079
|
clack7.outro(void 0, clackOutput);
|
|
15052
15080
|
return { exitCode: 1 };
|
|
15053
15081
|
}
|
|
@@ -17249,7 +17277,7 @@ function createProgram() {
|
|
|
17249
17277
|
});
|
|
17250
17278
|
process.exitCode = result.exitCode;
|
|
17251
17279
|
});
|
|
17252
|
-
program.command("store").description("Store extracted knowledge entries in the local database").argument("[files...]", "One or more extraction JSON files").option("--db <path>", "Database path override").option("--dry-run", "Show what would be stored without writing", false).option("--verbose", "Show per-entry dedup decisions", false).option("--force", "Skip dedup and store all entries as new", false).option("--platform <name>", "Platform tag: openclaw, claude-code, codex").option("--project <name>", "Project tag (lowercase).", (val, prev) => [...prev, val], []).option("--online-dedup", "Enable online LLM dedup at write time", true).option("--no-online-dedup", "Disable online LLM dedup at write time").option("--dedup-threshold <n>", "Similarity threshold for online dedup (0.0-1.0)").action(
|
|
17280
|
+
program.command("store").description("Store extracted knowledge entries in the local database").argument("[files...]", "One or more extraction JSON files").option("--db <path>", "Database path override").option("--dry-run", "Show what would be stored without writing", false).option("--verbose", "Show per-entry dedup decisions", false).option("--force", "Skip dedup and store all entries as new", false).option("--aggressive", "Enable aggressive dedup (lower similarity threshold)", false).option("--platform <name>", "Platform tag: openclaw, claude-code, codex").option("--project <name>", "Project tag (lowercase).", (val, prev) => [...prev, val], []).option("--online-dedup", "Enable online LLM dedup at write time", true).option("--no-online-dedup", "Disable online LLM dedup at write time").option("--dedup-threshold <n>", "Similarity threshold for online dedup (0.0-1.0)").action(
|
|
17253
17281
|
async (files, opts) => {
|
|
17254
17282
|
const result = await runStoreCommand(files ?? [], opts);
|
|
17255
17283
|
process.exitCode = result.exitCode;
|
|
@@ -17277,13 +17305,25 @@ function createProgram() {
|
|
|
17277
17305
|
});
|
|
17278
17306
|
process.exitCode = result.exitCode;
|
|
17279
17307
|
});
|
|
17280
|
-
program.command("retire
|
|
17281
|
-
const
|
|
17308
|
+
program.command("retire [subject]").description("Retire a stale entry from active recall (entry is hidden, not deleted)").option("--persist", "Write to retirements ledger so retirement survives re-ingest").option("--contains", "Use substring matching instead of exact match").option("--dry-run", "Preview matches without retiring").option("--reason <text>", "Reason for retirement").option("--db <path>", "Path to database file").option("--id <id>", "Retire a specific entry by its ID (overrides subject matching)").action(async (subject, opts) => {
|
|
17309
|
+
const entryId = opts.id;
|
|
17310
|
+
if (!entryId && !subject) {
|
|
17311
|
+
console.error("Error: provide a subject or --id <id>");
|
|
17312
|
+
process.exitCode = 1;
|
|
17313
|
+
return;
|
|
17314
|
+
}
|
|
17315
|
+
if (entryId && subject) {
|
|
17316
|
+
console.error("Error: --id and subject are mutually exclusive");
|
|
17317
|
+
process.exitCode = 1;
|
|
17318
|
+
return;
|
|
17319
|
+
}
|
|
17320
|
+
const result = await runRetireCommand(subject ?? "", {
|
|
17282
17321
|
persist: opts.persist === true,
|
|
17283
17322
|
contains: opts.contains === true,
|
|
17284
17323
|
dryRun: opts.dryRun === true,
|
|
17285
17324
|
reason: opts.reason,
|
|
17286
|
-
db: opts.db
|
|
17325
|
+
db: opts.db,
|
|
17326
|
+
id: entryId
|
|
17287
17327
|
});
|
|
17288
17328
|
process.exitCode = result.exitCode;
|
|
17289
17329
|
});
|
|
@@ -313,11 +313,10 @@ async function runRecallTool(agenrPath, params) {
|
|
|
313
313
|
const limit = asNumber(params.limit);
|
|
314
314
|
const types = asString(params.types);
|
|
315
315
|
const since = asString(params.since);
|
|
316
|
-
const threshold = asNumber(params.threshold);
|
|
317
316
|
const platform = asString(params.platform);
|
|
318
317
|
const project = asString(params.project);
|
|
319
318
|
if (query) {
|
|
320
|
-
args.push(
|
|
319
|
+
args.push(query);
|
|
321
320
|
}
|
|
322
321
|
if (context) {
|
|
323
322
|
args.push("--context", context);
|
|
@@ -326,14 +325,11 @@ async function runRecallTool(agenrPath, params) {
|
|
|
326
325
|
args.push("--limit", String(limit));
|
|
327
326
|
}
|
|
328
327
|
if (types) {
|
|
329
|
-
args.push("--
|
|
328
|
+
args.push("--type", types);
|
|
330
329
|
}
|
|
331
330
|
if (since) {
|
|
332
331
|
args.push("--since", since);
|
|
333
332
|
}
|
|
334
|
-
if (threshold !== void 0) {
|
|
335
|
-
args.push("--threshold", String(threshold));
|
|
336
|
-
}
|
|
337
333
|
if (platform) {
|
|
338
334
|
args.push("--platform", platform);
|
|
339
335
|
}
|
|
@@ -381,14 +377,20 @@ async function runStoreTool(agenrPath, params, pluginConfig) {
|
|
|
381
377
|
const entries = Array.isArray(params.entries) ? params.entries : [];
|
|
382
378
|
const platform = asString(params.platform);
|
|
383
379
|
const project = asString(params.project);
|
|
384
|
-
const
|
|
380
|
+
const storeArgs = ["store"];
|
|
385
381
|
if (platform) {
|
|
386
|
-
|
|
382
|
+
storeArgs.push("--platform", platform);
|
|
387
383
|
}
|
|
388
384
|
if (project) {
|
|
389
|
-
|
|
385
|
+
storeArgs.push("--project", project);
|
|
390
386
|
}
|
|
391
|
-
const
|
|
387
|
+
const processedEntries = entries.map((e) => {
|
|
388
|
+
const entry = e && typeof e === "object" ? e : {};
|
|
389
|
+
if (!entry.subject && typeof entry.content === "string") {
|
|
390
|
+
entry.subject = entry.content.slice(0, 60).replace(/[.!?][\s\S]*$/, "").trim() || entry.content.slice(0, 40);
|
|
391
|
+
}
|
|
392
|
+
return entry;
|
|
393
|
+
});
|
|
392
394
|
const dedupConfig = pluginConfig?.dedup;
|
|
393
395
|
if (dedupConfig?.aggressive === true) {
|
|
394
396
|
storeArgs.push("--aggressive");
|
|
@@ -396,7 +398,7 @@ async function runStoreTool(agenrPath, params, pluginConfig) {
|
|
|
396
398
|
if (typeof dedupConfig?.threshold === "number") {
|
|
397
399
|
storeArgs.push("--dedup-threshold", String(dedupConfig.threshold));
|
|
398
400
|
}
|
|
399
|
-
const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(
|
|
401
|
+
const result = await runAgenrCommand(agenrPath, storeArgs, JSON.stringify(processedEntries));
|
|
400
402
|
if (result.timedOut) {
|
|
401
403
|
return {
|
|
402
404
|
content: [{ type: "text", text: "agenr_store timed out" }]
|
|
@@ -426,13 +428,6 @@ async function runExtractTool(agenrPath, params) {
|
|
|
426
428
|
};
|
|
427
429
|
}
|
|
428
430
|
const args = ["extract", "--json"];
|
|
429
|
-
if (params.store === true) {
|
|
430
|
-
args.push("--store");
|
|
431
|
-
}
|
|
432
|
-
const source = asString(params.source);
|
|
433
|
-
if (source) {
|
|
434
|
-
args.push("--source", source);
|
|
435
|
-
}
|
|
436
431
|
const tempFile = join(
|
|
437
432
|
tmpdir(),
|
|
438
433
|
`agenr-extract-${Date.now()}-${Math.random().toString(36).slice(2)}.txt`
|
|
@@ -440,37 +435,74 @@ async function runExtractTool(agenrPath, params) {
|
|
|
440
435
|
try {
|
|
441
436
|
writeFileSync(tempFile, text, "utf8");
|
|
442
437
|
args.push(tempFile);
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
438
|
+
const extractResult = await runAgenrCommand(agenrPath, args, void 0, EXTRACT_TIMEOUT_MS);
|
|
439
|
+
if (extractResult.timedOut) {
|
|
445
440
|
return {
|
|
446
441
|
content: [{ type: "text", text: "agenr_extract failed: command timed out" }]
|
|
447
442
|
};
|
|
448
443
|
}
|
|
449
|
-
if (
|
|
444
|
+
if (extractResult.error) {
|
|
450
445
|
return {
|
|
451
|
-
content: [{ type: "text", text: `agenr_extract failed: ${
|
|
446
|
+
content: [{ type: "text", text: `agenr_extract failed: ${extractResult.error.message}` }]
|
|
452
447
|
};
|
|
453
448
|
}
|
|
454
|
-
if (
|
|
455
|
-
const message =
|
|
449
|
+
if (extractResult.code !== 0) {
|
|
450
|
+
const message = extractResult.stderr.trim() || extractResult.stdout.trim() || "unknown error";
|
|
456
451
|
return {
|
|
457
452
|
content: [{ type: "text", text: `agenr_extract failed: ${message}` }]
|
|
458
453
|
};
|
|
459
454
|
}
|
|
455
|
+
if (params.store === true) {
|
|
456
|
+
let entries;
|
|
457
|
+
try {
|
|
458
|
+
entries = JSON.parse(extractResult.stdout);
|
|
459
|
+
if (!Array.isArray(entries)) {
|
|
460
|
+
throw new Error("extract did not return an array");
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return {
|
|
464
|
+
content: [
|
|
465
|
+
{
|
|
466
|
+
type: "text",
|
|
467
|
+
text: `agenr_extract failed: could not parse extract output for store: ${err instanceof Error ? err.message : String(err)}`
|
|
468
|
+
}
|
|
469
|
+
]
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const source = asString(params.source);
|
|
473
|
+
if (source) {
|
|
474
|
+
for (const e of entries) {
|
|
475
|
+
if (e && typeof e === "object") {
|
|
476
|
+
const entry = e;
|
|
477
|
+
if (!entry.source) {
|
|
478
|
+
entry.source = { file: source, context: "extracted via agenr_extract" };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const storeResult = await runAgenrCommand(agenrPath, ["store"], JSON.stringify(entries));
|
|
484
|
+
if (storeResult.timedOut) {
|
|
485
|
+
return { content: [{ type: "text", text: "agenr_extract store step timed out" }] };
|
|
486
|
+
}
|
|
487
|
+
if (storeResult.error) {
|
|
488
|
+
return { content: [{ type: "text", text: `agenr_extract store step failed: ${storeResult.error.message}` }] };
|
|
489
|
+
}
|
|
490
|
+
if (storeResult.code !== 0) {
|
|
491
|
+
const message = storeResult.stderr.trim() || storeResult.stdout.trim() || "unknown error";
|
|
492
|
+
return { content: [{ type: "text", text: `agenr_extract store step failed: ${message}` }] };
|
|
493
|
+
}
|
|
494
|
+
return { content: [{ type: "text", text: `Extracted and stored ${entries.length} entries.` }] };
|
|
495
|
+
}
|
|
460
496
|
try {
|
|
461
|
-
const parsed = JSON.parse(
|
|
497
|
+
const parsed = JSON.parse(extractResult.stdout);
|
|
462
498
|
return {
|
|
463
499
|
content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
|
|
464
500
|
details: parsed
|
|
465
501
|
};
|
|
466
|
-
} catch
|
|
502
|
+
} catch {
|
|
467
503
|
return {
|
|
468
|
-
content: [
|
|
469
|
-
|
|
470
|
-
type: "text",
|
|
471
|
-
text: `agenr_extract failed: ${error instanceof Error ? error.message : String(error)}`
|
|
472
|
-
}
|
|
473
|
-
]
|
|
504
|
+
content: [{ type: "text", text: "No results found." }],
|
|
505
|
+
details: { count: 0 }
|
|
474
506
|
};
|
|
475
507
|
}
|
|
476
508
|
} finally {
|
|
@@ -489,7 +521,7 @@ async function runRetireTool(agenrPath, params) {
|
|
|
489
521
|
}
|
|
490
522
|
const reason = asString(params.reason);
|
|
491
523
|
const persist = params.persist === true;
|
|
492
|
-
const args = ["retire", entryId];
|
|
524
|
+
const args = ["retire", "--id", entryId];
|
|
493
525
|
if (reason) {
|
|
494
526
|
args.push("--reason", reason);
|
|
495
527
|
}
|
|
@@ -700,7 +732,6 @@ var plugin = {
|
|
|
700
732
|
since: Type.Optional(
|
|
701
733
|
Type.String({ description: "Only entries newer than this (ISO date or relative, e.g. 7d)." })
|
|
702
734
|
),
|
|
703
|
-
threshold: Type.Optional(Type.Number({ description: "Minimum relevance score 0.0-1.0." })),
|
|
704
735
|
platform: Type.Optional(Type.String({ description: "Platform filter: openclaw, claude-code, codex." })),
|
|
705
736
|
project: Type.Optional(Type.String({ description: "Project scope. Pass * for all projects." }))
|
|
706
737
|
}),
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenr",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
|
|
3
|
+
"version": "0.7.6",
|
|
5
4
|
"openclaw": {
|
|
6
5
|
"extensions": [
|
|
7
6
|
"dist/openclaw-plugin/index.js"
|
|
@@ -12,13 +11,6 @@
|
|
|
12
11
|
"bin": {
|
|
13
12
|
"agenr": "dist/cli.js"
|
|
14
13
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
|
|
17
|
-
"dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
|
|
18
|
-
"test": "vitest run",
|
|
19
|
-
"test:watch": "vitest",
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
21
|
-
},
|
|
22
14
|
"dependencies": {
|
|
23
15
|
"@clack/prompts": "^1.0.1",
|
|
24
16
|
"@libsql/client": "^0.17.0",
|
|
@@ -62,9 +54,11 @@
|
|
|
62
54
|
"README.md"
|
|
63
55
|
],
|
|
64
56
|
"author": "agenr-ai",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup src/cli.ts src/cli-main.ts src/openclaw-plugin/index.ts --format esm --dts",
|
|
59
|
+
"dev": "tsup src/cli.ts src/cli-main.ts --format esm --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc --noEmit"
|
|
69
63
|
}
|
|
70
|
-
}
|
|
64
|
+
}
|