agenr 0.8.2 → 0.8.4
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 +35 -0
- package/README.md +1 -1
- package/dist/cli-main.js +121 -13
- package/dist/openclaw-plugin/index.js +118 -21
- package/openclaw.plugin.json +4 -1
- package/package.json +8 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.4]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- feat(openclaw-plugin): project scoping via config.project in openclaw.json; all session-start recall and store calls are scoped to the configured project when set (issue #71)
|
|
7
|
+
- feat(openclaw-plugin): optional subject field in agenr_store schema; agents can now pass an explicit subject per entry rather than always relying on inference (issue #86)
|
|
8
|
+
- feat(openclaw-plugin): platform normalization and source_file format warnings in runStoreTool; platform is inferred from source when missing, invalid values are warned and dropped, freeform source strings trigger a format hint (issue #145)
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- fix(recall): cap final recall scores at 1.0 after FTS bonus; Math.min(1.0) applied in scoreEntryWithBreakdown (issue #64)
|
|
12
|
+
- fix(mcp): correct misleading retire tool message; retired entries are hidden from all recall paths (issue #143)
|
|
13
|
+
- fix(mcp): inferSubject now splits on punctuation followed by whitespace only, preventing truncation on file path periods (e.g. .ts, .js)
|
|
14
|
+
- fix(openclaw-plugin): subject inference in runStoreTool processedEntries now uses the same safe regex as inferSubject
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- chore(openclaw-plugin): remove openclaw.plugin.json version field; package.json is now the single source of truth (issue #91)
|
|
18
|
+
- chore(openclaw-plugin): remove formatRecallAsSummary dead code; writeAgenrMd was already removed, this cleans up the last orphaned export (issue #77)
|
|
19
|
+
|
|
20
|
+
## [0.8.3]
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- setup: custom model aliases (gpt-4.1-nano, gpt-4.1-mini) now appear in
|
|
24
|
+
the model picker when using openai-api-key auth (issue #136)
|
|
25
|
+
- setup: revert hint null-normalization regression (details?.name ?? undefined)
|
|
26
|
+
- setup: warn user when empty credential is entered during key rotation
|
|
27
|
+
- setup: note that updated credential is saved but not re-validated
|
|
28
|
+
- setup: openai-api-key now prioritizes gpt-4.1-nano, gpt-4.1-mini, and
|
|
29
|
+
gpt-5-nano in preferred model selection, and adds gpt-5-nano alias
|
|
30
|
+
resolution for OpenAI model lookup
|
|
31
|
+
- setup: reconfigure now offers to update stored API key even when existing
|
|
32
|
+
credential is valid (issue #13)
|
|
33
|
+
- embeddings: EmbeddingCache is now bounded with LRU eviction (default
|
|
34
|
+
max 5000 entries) to prevent unbounded heap growth during large ingests
|
|
35
|
+
(issue #57)
|
|
36
|
+
- embeddings: EmbeddingCache constructor throws RangeError for maxSize < 1
|
|
37
|
+
|
|
3
38
|
## [0.8.2] - 2026-02-22
|
|
4
39
|
|
|
5
40
|
### Added
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ One local database. Your memory stays on your machine.
|
|
|
12
12
|
|
|
13
13
|
AGENR uses embeddings to make your memory searchable. The best setup we've found: an **OpenAI API key** with `text-embedding-3-small`. Embeddings cost fractions of a penny per operation - a full ingestion of 100+ session transcripts runs about $0.10 total.
|
|
14
14
|
|
|
15
|
-
AGENR also supports **OpenAI Pro subscriptions** and **Anthropic Claude subscriptions** (no API key needed) for the LLM extraction step. But for the best balance of speed, accuracy, and cost, we recommend `gpt-
|
|
15
|
+
AGENR also supports **OpenAI Pro subscriptions** and **Anthropic Claude subscriptions** (no API key needed) for the LLM extraction step. But for the best balance of speed, accuracy, and cost, we recommend `gpt-4.1-nano` with an API key. `agenr setup` walks you through all of this.
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
export OPENAI_API_KEY=sk-... # for embeddings + extraction
|
package/dist/cli-main.js
CHANGED
|
@@ -43,7 +43,8 @@ var MODEL_ALIASES = {
|
|
|
43
43
|
"gpt-codex": "gpt-5.2-codex",
|
|
44
44
|
"gpt-4.1-nano": "openai/gpt-4.1-nano",
|
|
45
45
|
"gpt-4.1-mini": "openai/gpt-4.1-mini",
|
|
46
|
-
"gpt-4.1": "openai/gpt-4.1"
|
|
46
|
+
"gpt-4.1": "openai/gpt-4.1",
|
|
47
|
+
"gpt-5-nano": "openai/gpt-5-nano"
|
|
47
48
|
},
|
|
48
49
|
"openai-codex": {
|
|
49
50
|
codex: "gpt-5.3-codex",
|
|
@@ -138,7 +139,7 @@ var AUTH_METHOD_DEFINITIONS = [
|
|
|
138
139
|
provider: "openai",
|
|
139
140
|
title: "OpenAI -- API key",
|
|
140
141
|
setupDescription: "Standard API key from platform.openai.com. Pay per token.",
|
|
141
|
-
preferredModels: ["gpt-
|
|
142
|
+
preferredModels: ["gpt-4.1-nano", "gpt-4.1-mini", "gpt-5-nano"]
|
|
142
143
|
}
|
|
143
144
|
];
|
|
144
145
|
var AUTH_METHOD_SET = new Set(AUTH_METHOD_DEFINITIONS.map((item) => item.id));
|
|
@@ -3847,7 +3848,8 @@ function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now, freshnessNow =
|
|
|
3847
3848
|
const memoryStrength = Math.min(Math.max(imp, spacedRecallBase) * fresh, 1);
|
|
3848
3849
|
const todoPenalty = entry.type === "todo" ? todoStaleness(entry, now) : 1;
|
|
3849
3850
|
const contradictionPenalty = entry.contradictions >= 2 ? 0.8 : 1;
|
|
3850
|
-
const
|
|
3851
|
+
const rawScore = sim * (0.3 + 0.7 * rec) * memoryStrength * todoPenalty * contradictionPenalty + fts;
|
|
3852
|
+
const score = Math.min(1, rawScore);
|
|
3851
3853
|
return {
|
|
3852
3854
|
score,
|
|
3853
3855
|
scores: {
|
|
@@ -3857,6 +3859,7 @@ function scoreEntryWithBreakdown(entry, vectorSim, ftsMatch, now, freshnessNow =
|
|
|
3857
3859
|
recall: recallBase,
|
|
3858
3860
|
freshness: fresh,
|
|
3859
3861
|
todoPenalty,
|
|
3862
|
+
// FTS bonus component before the final score is capped at 1.0.
|
|
3860
3863
|
fts,
|
|
3861
3864
|
spacing: spacingFactor
|
|
3862
3865
|
}
|
|
@@ -4533,12 +4536,75 @@ async function retireEntries(opts) {
|
|
|
4533
4536
|
|
|
4534
4537
|
// src/embeddings/cache.ts
|
|
4535
4538
|
var EmbeddingCache = class {
|
|
4536
|
-
|
|
4539
|
+
maxSize;
|
|
4540
|
+
map = /* @__PURE__ */ new Map();
|
|
4541
|
+
head = null;
|
|
4542
|
+
// most recently used key
|
|
4543
|
+
tail = null;
|
|
4544
|
+
// least recently used key
|
|
4545
|
+
constructor(maxSize = 5e3) {
|
|
4546
|
+
if (!(maxSize >= 1)) {
|
|
4547
|
+
throw new RangeError(`EmbeddingCache maxSize must be at least 1. Received: ${String(maxSize)}.`);
|
|
4548
|
+
}
|
|
4549
|
+
this.maxSize = maxSize;
|
|
4550
|
+
}
|
|
4537
4551
|
get(text2) {
|
|
4538
|
-
|
|
4552
|
+
const node = this.map.get(text2);
|
|
4553
|
+
if (!node) return void 0;
|
|
4554
|
+
this.moveToHead(text2, node);
|
|
4555
|
+
return node.value;
|
|
4539
4556
|
}
|
|
4540
4557
|
set(text2, embedding) {
|
|
4541
|
-
this.
|
|
4558
|
+
if (this.map.has(text2)) {
|
|
4559
|
+
const node2 = this.map.get(text2);
|
|
4560
|
+
node2.value = embedding;
|
|
4561
|
+
this.moveToHead(text2, node2);
|
|
4562
|
+
return;
|
|
4563
|
+
}
|
|
4564
|
+
if (this.map.size >= this.maxSize) {
|
|
4565
|
+
this.evictTail();
|
|
4566
|
+
}
|
|
4567
|
+
const node = { value: embedding, prev: null, next: this.head };
|
|
4568
|
+
this.map.set(text2, node);
|
|
4569
|
+
if (this.head !== null) {
|
|
4570
|
+
this.map.get(this.head).prev = text2;
|
|
4571
|
+
}
|
|
4572
|
+
this.head = text2;
|
|
4573
|
+
if (this.tail === null) {
|
|
4574
|
+
this.tail = text2;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
get size() {
|
|
4578
|
+
return this.map.size;
|
|
4579
|
+
}
|
|
4580
|
+
moveToHead(key, node) {
|
|
4581
|
+
if (this.head === key) return;
|
|
4582
|
+
if (node.prev !== null) {
|
|
4583
|
+
this.map.get(node.prev).next = node.next;
|
|
4584
|
+
}
|
|
4585
|
+
if (node.next !== null) {
|
|
4586
|
+
this.map.get(node.next).prev = node.prev;
|
|
4587
|
+
} else {
|
|
4588
|
+
this.tail = node.prev;
|
|
4589
|
+
}
|
|
4590
|
+
node.prev = null;
|
|
4591
|
+
node.next = this.head;
|
|
4592
|
+
if (this.head !== null) {
|
|
4593
|
+
this.map.get(this.head).prev = key;
|
|
4594
|
+
}
|
|
4595
|
+
this.head = key;
|
|
4596
|
+
}
|
|
4597
|
+
evictTail() {
|
|
4598
|
+
if (this.tail === null) return;
|
|
4599
|
+
const tailKey = this.tail;
|
|
4600
|
+
const tailNode = this.map.get(tailKey);
|
|
4601
|
+
this.tail = tailNode.prev;
|
|
4602
|
+
if (this.tail !== null) {
|
|
4603
|
+
this.map.get(this.tail).next = null;
|
|
4604
|
+
} else {
|
|
4605
|
+
this.head = null;
|
|
4606
|
+
}
|
|
4607
|
+
this.map.delete(tailKey);
|
|
4542
4608
|
}
|
|
4543
4609
|
};
|
|
4544
4610
|
|
|
@@ -15285,10 +15351,15 @@ function inferSubject(content) {
|
|
|
15285
15351
|
if (!trimmed) {
|
|
15286
15352
|
return "memory";
|
|
15287
15353
|
}
|
|
15288
|
-
const firstClause = trimmed.split(/[
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15354
|
+
const firstClause = trimmed.split(/[.!?:;]\s/)[0].trim();
|
|
15355
|
+
if (!firstClause) {
|
|
15356
|
+
return "memory";
|
|
15357
|
+
}
|
|
15358
|
+
if (firstClause.length <= 80) {
|
|
15359
|
+
return firstClause;
|
|
15360
|
+
}
|
|
15361
|
+
const cut = firstClause.lastIndexOf(" ", 80);
|
|
15362
|
+
return cut > 0 ? firstClause.slice(0, cut) : firstClause.slice(0, 80);
|
|
15292
15363
|
}
|
|
15293
15364
|
function parseStoreEntries(rawEntries) {
|
|
15294
15365
|
if (!Array.isArray(rawEntries)) {
|
|
@@ -15753,7 +15824,7 @@ function createMcpServer(options = {}, deps = {}) {
|
|
|
15753
15824
|
if (retired.count === 0) {
|
|
15754
15825
|
throw new RpcError(JSON_RPC_INVALID_PARAMS, `No active entry found with id: ${entryId}`);
|
|
15755
15826
|
}
|
|
15756
|
-
const messageBase = `Retired: ${subject} (type: ${type}). Entry is hidden from session-start
|
|
15827
|
+
const messageBase = `Retired: ${subject} (type: ${type}). Entry is hidden from all recall (session-start and explicit queries).`;
|
|
15757
15828
|
const text2 = persist ? `${messageBase} Retirement will survive database re-ingest.` : messageBase;
|
|
15758
15829
|
return text2;
|
|
15759
15830
|
}
|
|
@@ -18131,7 +18202,14 @@ function promptToEnterCredential(auth) {
|
|
|
18131
18202
|
function modelChoicesForAuth(auth, provider) {
|
|
18132
18203
|
const definition = getAuthMethodDefinition(auth);
|
|
18133
18204
|
const allModels = getModels(provider).map((model) => model.id);
|
|
18134
|
-
const preferred = definition.preferredModels.filter((modelId) =>
|
|
18205
|
+
const preferred = definition.preferredModels.filter((modelId) => {
|
|
18206
|
+
try {
|
|
18207
|
+
resolveModel(provider, modelId);
|
|
18208
|
+
return true;
|
|
18209
|
+
} catch {
|
|
18210
|
+
return false;
|
|
18211
|
+
}
|
|
18212
|
+
});
|
|
18135
18213
|
const fallback = allModels.filter((modelId) => !preferred.includes(modelId));
|
|
18136
18214
|
if (provider === "openai") {
|
|
18137
18215
|
const prioritizedFallback = fallback.filter(
|
|
@@ -18194,9 +18272,9 @@ async function runSetup(env = process.env) {
|
|
|
18194
18272
|
storedCredentials: working.credentials,
|
|
18195
18273
|
env
|
|
18196
18274
|
});
|
|
18275
|
+
const credentialKey = credentialKeyForAuth(auth);
|
|
18197
18276
|
if (!probe.available) {
|
|
18198
18277
|
clack11.log.warn(probe.guidance);
|
|
18199
|
-
const credentialKey = credentialKeyForAuth(auth);
|
|
18200
18278
|
if (credentialKey) {
|
|
18201
18279
|
const shouldEnterNow = await clack11.confirm({
|
|
18202
18280
|
message: "Enter the credential now?",
|
|
@@ -18225,6 +18303,36 @@ async function runSetup(env = process.env) {
|
|
|
18225
18303
|
}
|
|
18226
18304
|
}
|
|
18227
18305
|
}
|
|
18306
|
+
} else if (existing && credentialKey) {
|
|
18307
|
+
const updateKey = await clack11.confirm({
|
|
18308
|
+
message: "Update stored credential?",
|
|
18309
|
+
initialValue: false
|
|
18310
|
+
});
|
|
18311
|
+
if (clack11.isCancel(updateKey)) {
|
|
18312
|
+
clack11.cancel("Setup cancelled.");
|
|
18313
|
+
return;
|
|
18314
|
+
}
|
|
18315
|
+
if (updateKey) {
|
|
18316
|
+
const entered = await clack11.password({
|
|
18317
|
+
message: promptToEnterCredential(auth)
|
|
18318
|
+
});
|
|
18319
|
+
if (clack11.isCancel(entered)) {
|
|
18320
|
+
clack11.cancel("Setup cancelled.");
|
|
18321
|
+
return;
|
|
18322
|
+
}
|
|
18323
|
+
const normalized = entered.trim();
|
|
18324
|
+
if (normalized) {
|
|
18325
|
+
working = setStoredCredential(working, credentialKey, normalized);
|
|
18326
|
+
probe = probeCredentials({
|
|
18327
|
+
auth,
|
|
18328
|
+
storedCredentials: working.credentials,
|
|
18329
|
+
env
|
|
18330
|
+
});
|
|
18331
|
+
clack11.log.info("Credential updated.");
|
|
18332
|
+
} else {
|
|
18333
|
+
clack11.log.warn("Credential not updated - empty input.");
|
|
18334
|
+
}
|
|
18335
|
+
}
|
|
18228
18336
|
}
|
|
18229
18337
|
const modelChoices = modelChoicesForAuth(auth, provider);
|
|
18230
18338
|
if (modelChoices.length === 0) {
|
|
@@ -30,7 +30,7 @@ function buildSpawnArgs(agenrPath) {
|
|
|
30
30
|
}
|
|
31
31
|
return { cmd: agenrPath, args: [] };
|
|
32
32
|
}
|
|
33
|
-
async function runRecall(agenrPath, budget) {
|
|
33
|
+
async function runRecall(agenrPath, budget, project) {
|
|
34
34
|
return await new Promise((resolve) => {
|
|
35
35
|
let stdout = "";
|
|
36
36
|
let settled = false;
|
|
@@ -42,11 +42,13 @@ async function runRecall(agenrPath, budget) {
|
|
|
42
42
|
settled = true;
|
|
43
43
|
resolve(value);
|
|
44
44
|
}
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
const args = ["recall", "--context", "session-start", "--budget", String(budget), "--json"];
|
|
46
|
+
if (project) {
|
|
47
|
+
args.push("--project", project);
|
|
48
|
+
}
|
|
49
|
+
const child = spawn(spawnArgs.cmd, [...spawnArgs.args, ...args], {
|
|
50
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
51
|
+
});
|
|
50
52
|
const timer = setTimeout(() => {
|
|
51
53
|
child.kill("SIGTERM");
|
|
52
54
|
finish(null);
|
|
@@ -245,6 +247,9 @@ import { tmpdir } from "os";
|
|
|
245
247
|
import { join } from "path";
|
|
246
248
|
var TOOL_TIMEOUT_MS = 1e4;
|
|
247
249
|
var EXTRACT_TIMEOUT_MS = 6e4;
|
|
250
|
+
var KNOWN_PLATFORMS = /* @__PURE__ */ new Set(["openclaw", "codex", "claude-code", "plaud"]);
|
|
251
|
+
var RECOMMENDED_SOURCE_PREFIXES = ["mcp:", "file:", "cli:", "session:", "conversation"];
|
|
252
|
+
var SOURCE_FORMAT_WARNING = "agenr_store: source_file does not follow recommended format (mcp:, file:, cli:, session:, conversation). Storing as-is.";
|
|
248
253
|
function asString(value) {
|
|
249
254
|
if (typeof value !== "string") {
|
|
250
255
|
return void 0;
|
|
@@ -264,6 +269,54 @@ function asNumber(value) {
|
|
|
264
269
|
}
|
|
265
270
|
return void 0;
|
|
266
271
|
}
|
|
272
|
+
function resolveWarn(pluginConfig) {
|
|
273
|
+
const logger = pluginConfig?.logger;
|
|
274
|
+
if (logger && typeof logger === "object") {
|
|
275
|
+
const warn = logger.warn;
|
|
276
|
+
if (typeof warn === "function") {
|
|
277
|
+
return (message) => {
|
|
278
|
+
warn(message);
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return (message) => {
|
|
283
|
+
console.warn(message);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function sourceStringFromEntry(entry) {
|
|
287
|
+
if (typeof entry.source === "string") {
|
|
288
|
+
return entry.source;
|
|
289
|
+
}
|
|
290
|
+
if (entry.source && typeof entry.source === "object") {
|
|
291
|
+
const sourceFile = asString(entry.source.file);
|
|
292
|
+
if (sourceFile) {
|
|
293
|
+
return sourceFile;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return void 0;
|
|
297
|
+
}
|
|
298
|
+
function inferPlatformFromEntries(entries) {
|
|
299
|
+
for (const entry of entries) {
|
|
300
|
+
const source = sourceStringFromEntry(entry);
|
|
301
|
+
if (!source) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const normalizedSource = source.toLowerCase();
|
|
305
|
+
if (normalizedSource.includes("codex")) {
|
|
306
|
+
return "codex";
|
|
307
|
+
}
|
|
308
|
+
if (normalizedSource.includes("claude")) {
|
|
309
|
+
return "claude-code";
|
|
310
|
+
}
|
|
311
|
+
if (normalizedSource.includes("openclaw")) {
|
|
312
|
+
return "openclaw";
|
|
313
|
+
}
|
|
314
|
+
if (normalizedSource.includes("plaud")) {
|
|
315
|
+
return "plaud";
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
267
320
|
async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_TIMEOUT_MS) {
|
|
268
321
|
return await new Promise((resolve) => {
|
|
269
322
|
const resolvedAgenrPath = agenrPath.trim() || resolveAgenrPath();
|
|
@@ -306,7 +359,7 @@ async function runAgenrCommand(agenrPath, args, stdinPayload, timeoutMs = TOOL_T
|
|
|
306
359
|
child.stdin.end();
|
|
307
360
|
});
|
|
308
361
|
}
|
|
309
|
-
async function runRecallTool(agenrPath, params) {
|
|
362
|
+
async function runRecallTool(agenrPath, params, defaultProject) {
|
|
310
363
|
const args = ["recall", "--json"];
|
|
311
364
|
const query = asString(params.query);
|
|
312
365
|
const context = asString(params.context);
|
|
@@ -315,7 +368,7 @@ async function runRecallTool(agenrPath, params) {
|
|
|
315
368
|
const since = asString(params.since);
|
|
316
369
|
const until = asString(params.until);
|
|
317
370
|
const platform = asString(params.platform);
|
|
318
|
-
const project = asString(params.project);
|
|
371
|
+
const project = asString(params.project) || defaultProject;
|
|
319
372
|
if (query) {
|
|
320
373
|
args.push(query);
|
|
321
374
|
}
|
|
@@ -377,10 +430,44 @@ async function runRecallTool(agenrPath, params) {
|
|
|
377
430
|
};
|
|
378
431
|
}
|
|
379
432
|
}
|
|
380
|
-
async function runStoreTool(agenrPath, params, pluginConfig) {
|
|
433
|
+
async function runStoreTool(agenrPath, params, pluginConfig, defaultProject) {
|
|
381
434
|
const entries = Array.isArray(params.entries) ? params.entries : [];
|
|
382
|
-
const
|
|
383
|
-
const
|
|
435
|
+
const project = asString(params.project) || defaultProject;
|
|
436
|
+
const warn = resolveWarn(pluginConfig);
|
|
437
|
+
const processedEntries = entries.map((e) => {
|
|
438
|
+
const entry = e && typeof e === "object" ? e : {};
|
|
439
|
+
if (!entry.subject && typeof entry.content === "string") {
|
|
440
|
+
entry.subject = entry.content.slice(0, 60).replace(/[.!?:;]\s[\s\S]*$/, "").trim() || entry.content.slice(0, 40);
|
|
441
|
+
}
|
|
442
|
+
return entry;
|
|
443
|
+
});
|
|
444
|
+
const explicitPlatform = asString(params.platform);
|
|
445
|
+
const normalizedPlatform = explicitPlatform?.toLowerCase();
|
|
446
|
+
let platform;
|
|
447
|
+
if (explicitPlatform) {
|
|
448
|
+
if (normalizedPlatform && KNOWN_PLATFORMS.has(normalizedPlatform)) {
|
|
449
|
+
platform = normalizedPlatform;
|
|
450
|
+
} else {
|
|
451
|
+
warn(
|
|
452
|
+
`agenr_store: invalid platform "${explicitPlatform}". Expected one of: openclaw, codex, claude-code, plaud. Omitting --platform.`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
platform = inferPlatformFromEntries(processedEntries);
|
|
457
|
+
}
|
|
458
|
+
for (const entry of processedEntries) {
|
|
459
|
+
if (typeof entry.source !== "string") {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const normalizedSource = entry.source.toLowerCase();
|
|
463
|
+
const hasKnownPrefix = RECOMMENDED_SOURCE_PREFIXES.some(
|
|
464
|
+
(prefix) => normalizedSource.startsWith(prefix)
|
|
465
|
+
);
|
|
466
|
+
if (!hasKnownPrefix) {
|
|
467
|
+
warn(SOURCE_FORMAT_WARNING);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
384
471
|
const storeArgs = ["store"];
|
|
385
472
|
if (platform) {
|
|
386
473
|
storeArgs.push("--platform", platform);
|
|
@@ -388,13 +475,6 @@ async function runStoreTool(agenrPath, params, pluginConfig) {
|
|
|
388
475
|
if (project) {
|
|
389
476
|
storeArgs.push("--project", project);
|
|
390
477
|
}
|
|
391
|
-
const processedEntries = entries.map((e) => {
|
|
392
|
-
const entry = e && typeof e === "object" ? e : {};
|
|
393
|
-
if (!entry.subject && typeof entry.content === "string") {
|
|
394
|
-
entry.subject = entry.content.slice(0, 60).replace(/[.!?][\s\S]*$/, "").trim() || entry.content.slice(0, 40);
|
|
395
|
-
}
|
|
396
|
-
return entry;
|
|
397
|
-
});
|
|
398
478
|
const dedupConfig = pluginConfig?.dedup;
|
|
399
479
|
if (dedupConfig?.aggressive === true) {
|
|
400
480
|
storeArgs.push("--aggressive");
|
|
@@ -654,7 +734,8 @@ var plugin = {
|
|
|
654
734
|
}
|
|
655
735
|
const agenrPath = resolveAgenrPath(config);
|
|
656
736
|
const budget = resolveBudget(config);
|
|
657
|
-
const
|
|
737
|
+
const project = config?.project?.trim() || void 0;
|
|
738
|
+
const recallResult = await runRecall(agenrPath, budget, project);
|
|
658
739
|
if (recallResult) {
|
|
659
740
|
const formatted = formatRecallAsMarkdown(recallResult);
|
|
660
741
|
if (formatted.trim()) {
|
|
@@ -731,7 +812,8 @@ var plugin = {
|
|
|
731
812
|
return makeDisabledToolResult();
|
|
732
813
|
}
|
|
733
814
|
const agenrPath = resolveAgenrPath(runtimeConfig);
|
|
734
|
-
|
|
815
|
+
const defaultProject = runtimeConfig?.project?.trim() || void 0;
|
|
816
|
+
return runRecallTool(agenrPath, params, defaultProject);
|
|
735
817
|
}
|
|
736
818
|
}
|
|
737
819
|
);
|
|
@@ -744,6 +826,11 @@ var plugin = {
|
|
|
744
826
|
entries: Type.Array(
|
|
745
827
|
Type.Object({
|
|
746
828
|
content: Type.String({ description: "What to remember." }),
|
|
829
|
+
subject: Type.Optional(
|
|
830
|
+
Type.String({
|
|
831
|
+
description: "Short subject label. Inferred from content if omitted."
|
|
832
|
+
})
|
|
833
|
+
),
|
|
747
834
|
type: Type.Unsafe({
|
|
748
835
|
type: "string",
|
|
749
836
|
enum: [...KNOWLEDGE_TYPES],
|
|
@@ -780,7 +867,17 @@ var plugin = {
|
|
|
780
867
|
return makeDisabledToolResult();
|
|
781
868
|
}
|
|
782
869
|
const agenrPath = resolveAgenrPath(runtimeConfig);
|
|
783
|
-
|
|
870
|
+
const defaultProject = runtimeConfig?.project?.trim() || void 0;
|
|
871
|
+
const toolConfig = {
|
|
872
|
+
...runtimeConfig,
|
|
873
|
+
logger: api.logger
|
|
874
|
+
};
|
|
875
|
+
return runStoreTool(
|
|
876
|
+
agenrPath,
|
|
877
|
+
params,
|
|
878
|
+
toolConfig,
|
|
879
|
+
defaultProject
|
|
880
|
+
);
|
|
784
881
|
}
|
|
785
882
|
}
|
|
786
883
|
);
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"id": "agenr",
|
|
3
3
|
"name": "agenr Memory",
|
|
4
4
|
"description": "Local memory layer - injects agenr context at session start",
|
|
5
|
-
"version": "0.8.2",
|
|
6
5
|
"skills": [
|
|
7
6
|
"skills"
|
|
8
7
|
],
|
|
@@ -18,6 +17,10 @@
|
|
|
18
17
|
"type": "number",
|
|
19
18
|
"description": "Token budget for session-start recall. Default: 2000."
|
|
20
19
|
},
|
|
20
|
+
"project": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Project scope for recall and store. Omit for global (default)."
|
|
23
|
+
},
|
|
21
24
|
"enabled": {
|
|
22
25
|
"type": "boolean",
|
|
23
26
|
"description": "Set false to disable memory injection without uninstalling."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenr",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"openclaw": {
|
|
5
5
|
"extensions": [
|
|
6
6
|
"dist/openclaw-plugin/index.js"
|
|
@@ -11,13 +11,6 @@
|
|
|
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
|
-
},
|
|
21
14
|
"dependencies": {
|
|
22
15
|
"@clack/prompts": "^1.0.1",
|
|
23
16
|
"@libsql/client": "^0.17.0",
|
|
@@ -61,9 +54,11 @@
|
|
|
61
54
|
"README.md"
|
|
62
55
|
],
|
|
63
56
|
"author": "agenr-ai",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
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"
|
|
68
63
|
}
|
|
69
|
-
}
|
|
64
|
+
}
|