myagentmemory 0.5.3 → 0.5.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/README.md +5 -5
- package/dist/cli-spec.js +1 -1
- package/dist/cli.js +30 -23
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +15 -0
- package/dist/plugin-host.d.ts +7 -1
- package/dist/plugin-runtime.d.ts +2 -0
- package/dist/plugin-runtime.js +13 -1
- package/dist/plugin-service.d.ts +1 -5
- package/dist/plugin-service.js +16 -58
- package/dist/upgrade.d.ts +9 -1
- package/dist/upgrade.js +37 -6
- package/docs/official-plugin-bootstrap.md +3 -4
- package/package.json +1 -2
- package/src/cli-spec.ts +1 -1
- package/src/plugin-host.ts +7 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Prefer to have an agent drive the whole thing — install, configure, verify, th
|
|
|
24
24
|
|
|
25
25
|
- AgentMemory injects your decisions, scratchpad, and daily log at session start — no copy-paste, no re-explaining.
|
|
26
26
|
- Repeated corrections become durable memory you can inspect and undo (Pro).
|
|
27
|
-
- Every memory is a plain Markdown file you own.
|
|
27
|
+
- Every memory is a plain Markdown file you own. AgentMemory's services do not receive memory content, session content, queries, or repository paths. Context you ask AgentMemory to return to a coding agent is then subject to that agent or model provider's data handling.
|
|
28
28
|
|
|
29
29
|
AgentMemory does not provide a Python SDK, does not provide a vector database, and does not provide a knowledge graph. It is a local Markdown store with a CLI, agent skills, and optional full-text and semantic search via [qmd](https://github.com/tobi/qmd). See [product boundary](docs/product-boundary.md) for full scope.
|
|
30
30
|
|
|
@@ -36,9 +36,9 @@ AgentMemory does not provide a Python SDK, does not provide a vector database, a
|
|
|
36
36
|
|
|
37
37
|
**Core remembers what you save. Pro learns from what you do.** Core remains free, MIT-licensed, and useful forever. Pro adds three things:
|
|
38
38
|
|
|
39
|
-
- **Remember past sessions** — ask *"what did we decide about auth?"* across Claude Code, Codex, and Cursor.
|
|
39
|
+
- **Remember past sessions** — ask *"what did we decide about auth?"* across Claude Code, Codex, and Pi session history. Cursor can use AgentMemory's skills and hooks, but Cursor transcript ingestion is not currently supported.
|
|
40
40
|
- **Learn from your patterns** — turn repeated corrections into memory you can inspect and undo.
|
|
41
|
-
- **Private by default** — memory and session content index locally.
|
|
41
|
+
- **Private by default** — memory and session content index locally. AgentMemory's services receive only a pseudonymous installation identifier and bounded compatibility metadata, never your memory or session content. Recall results provided locally to a coding agent are subject to that agent or model provider's data handling.
|
|
42
42
|
|
|
43
43
|
Preview what Pro would find in your existing sessions *before* installing anything:
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ agent-memory learn
|
|
|
50
50
|
agent-memory dashboard
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Pre-install preview: up to 50 local sessions per day. Free installed preview: 20 recalls + 5 learning scans per local day.
|
|
53
|
+
Pre-install preview: up to 50 local sessions per day. Free installed preview: 20 recalls + 5 learning scans per local day. AgentMemory's services do not receive memory, session, query, or repository content; installation sends only a pseudonymous identifier and bounded compatibility metadata. Recall results provided locally to a coding agent are subject to that agent or model provider's data handling. Full detail on [privacy, signing, and installation](docs/official-plugin-bootstrap.md).
|
|
54
54
|
|
|
55
55
|
## Installation
|
|
56
56
|
|
|
@@ -59,7 +59,7 @@ Pre-install preview: up to 50 local sessions per day. Free installed preview: 20
|
|
|
59
59
|
brew tap jayzeng/agentmemory https://github.com/jayzeng/agentmemory
|
|
60
60
|
brew install jayzeng/agentmemory/agent-memory
|
|
61
61
|
|
|
62
|
-
# Install the portable CLI globally (Node.js 20+;
|
|
62
|
+
# Install the portable Core CLI globally (Node.js 20+; Pro session recall requires Node.js 22.13+)
|
|
63
63
|
npm install -g myagentmemory
|
|
64
64
|
|
|
65
65
|
# If corporate TLS inspection requires a private CA, use your organization's CA file:
|
package/dist/cli-spec.js
CHANGED
|
@@ -67,7 +67,7 @@ export const PLUGIN_COMMAND_DESCRIPTIONS = {
|
|
|
67
67
|
install: "authenticate if needed, then install or upgrade the official bundle",
|
|
68
68
|
update: "upgrade an existing official bundle when a compatible release exists",
|
|
69
69
|
uninstall: "remove official plugin executables while preserving user data",
|
|
70
|
-
manage: "open
|
|
70
|
+
manage: "open account management when that future service is available",
|
|
71
71
|
};
|
|
72
72
|
export const WORKER_ACTION_DESCRIPTIONS = {};
|
|
73
73
|
export const SCRATCHPAD_ACTION_DESCRIPTIONS = {
|
package/dist/cli.js
CHANGED
|
@@ -124,10 +124,6 @@ function levenshtein(a, b) {
|
|
|
124
124
|
}
|
|
125
125
|
return prev[b.length];
|
|
126
126
|
}
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// Pro plan / cap-exhausted UX
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
const UPGRADE_URL = "https://agentmemory.paperpilot.me/upgrade";
|
|
131
127
|
function detectCapExhausted(result) {
|
|
132
128
|
if (result.ok !== false)
|
|
133
129
|
return null;
|
|
@@ -194,13 +190,11 @@ function printCapExhaustedBox(command, info) {
|
|
|
194
190
|
"─────────────────────────────────────────────────────────────",
|
|
195
191
|
usedLine,
|
|
196
192
|
"",
|
|
197
|
-
"
|
|
198
|
-
` ${UPGRADE_URL}`,
|
|
193
|
+
" Paid plans are not available yet. Try again after the free-preview allowance resets.",
|
|
199
194
|
"─────────────────────────────────────────────────────────────",
|
|
200
195
|
"",
|
|
201
196
|
];
|
|
202
197
|
console.error(lines.join("\n"));
|
|
203
|
-
openExternalUrl(UPGRADE_URL);
|
|
204
198
|
}
|
|
205
199
|
// Persist the last usage decision to disk so `pro status` can show counters.
|
|
206
200
|
function cacheProUsage(decision) {
|
|
@@ -394,9 +388,9 @@ function printProOverview(installed) {
|
|
|
394
388
|
console.log("Core remembers what you save. Pro learns from what you do.");
|
|
395
389
|
console.log("");
|
|
396
390
|
console.log("AgentMemory Pro:");
|
|
397
|
-
console.log(' Remember past sessions Ask "what did we decide about auth?" across Claude Code, Codex, and
|
|
391
|
+
console.log(' Remember past sessions Ask "what did we decide about auth?" across Claude Code, Codex, and Pi history.');
|
|
398
392
|
console.log(" Learn from your patterns Turn repeated corrections into memory you can inspect and undo.");
|
|
399
|
-
console.log(" Private by default
|
|
393
|
+
console.log(" Private by default AgentMemory services never receive memory or session content.");
|
|
400
394
|
console.log("");
|
|
401
395
|
if (installed) {
|
|
402
396
|
printProUsageCounters();
|
|
@@ -443,7 +437,7 @@ function printPluginResult(result, json, allowBrowser) {
|
|
|
443
437
|
console.log(`AgentMemory Pro${version} has an update available.`);
|
|
444
438
|
break;
|
|
445
439
|
case "uninstalled":
|
|
446
|
-
console.log("AgentMemory Pro executable components were removed. Memory and
|
|
440
|
+
console.log("AgentMemory Pro executable components were removed. Memory and local activation state were preserved.");
|
|
447
441
|
break;
|
|
448
442
|
case "not_installed":
|
|
449
443
|
console.log("AgentMemory Pro is not installed.");
|
|
@@ -674,10 +668,15 @@ const STOP_NAG_REASON = "Before stopping: if this session produced a durable fac
|
|
|
674
668
|
"ignore this and stop normally.";
|
|
675
669
|
/**
|
|
676
670
|
* Stop hook handler — fires at the end of every assistant turn (not once per
|
|
677
|
-
* session).
|
|
678
|
-
* to nudge a memory-write check without being
|
|
679
|
-
*
|
|
680
|
-
*
|
|
671
|
+
* session). Continues the conversation at most once every STOP_NAG_INTERVAL
|
|
672
|
+
* turns per session_id to nudge a memory-write check without being
|
|
673
|
+
* disruptive. Uses `hookSpecificOutput.additionalContext` rather than
|
|
674
|
+
* `decision: "block"` — functionally identical (both go through the same
|
|
675
|
+
* `stop_hook_active` re-entry check and Claude Code's loop-protection cap),
|
|
676
|
+
* but additionalContext renders as "Stop hook feedback" in the transcript
|
|
677
|
+
* instead of the alarming-looking "Stop hook error". Always allows the stop
|
|
678
|
+
* (empty stdout) on missing session_id, `stop_hook_active` (Claude Code's own
|
|
679
|
+
* re-entrancy signal — never nag twice in a row), or any internal error.
|
|
681
680
|
*/
|
|
682
681
|
async function cmdStop(_flags) {
|
|
683
682
|
const TIMEOUT_MS = 3_000;
|
|
@@ -695,7 +694,9 @@ async function cmdStop(_flags) {
|
|
|
695
694
|
if (!sessionId || payload?.stop_hook_active === true)
|
|
696
695
|
return;
|
|
697
696
|
if (shouldNagOnStop(sessionId, Date.now())) {
|
|
698
|
-
process.stdout.write(JSON.stringify({
|
|
697
|
+
process.stdout.write(JSON.stringify({
|
|
698
|
+
hookSpecificOutput: { hookEventName: "Stop", additionalContext: STOP_NAG_REASON },
|
|
699
|
+
}));
|
|
699
700
|
}
|
|
700
701
|
})().catch(() => {
|
|
701
702
|
// Any failure in the Stop hook must be swallowed — never trap the user
|
|
@@ -1698,7 +1699,7 @@ async function cmdSetup(flags) {
|
|
|
1698
1699
|
console.log(colorize("The local plugin is live. Feel the magic now:", "green"));
|
|
1699
1700
|
console.log(` ${colorize('agent-memory recall "what did we decide about auth?"', "cyan")} — search past sessions`);
|
|
1700
1701
|
console.log(` ${colorize("agent-memory learn", "cyan")} — surface repeated corrections`);
|
|
1701
|
-
console.log(` ${colorize("agent-memory
|
|
1702
|
+
console.log(` ${colorize("agent-memory index", "cyan")} — refresh the supported local session index`);
|
|
1702
1703
|
console.log(` ${colorize("agent-memory dashboard", "cyan")} — private local dashboard`);
|
|
1703
1704
|
}
|
|
1704
1705
|
else if (skipPlugin) {
|
|
@@ -1852,10 +1853,10 @@ function printProPitch(mode) {
|
|
|
1852
1853
|
else {
|
|
1853
1854
|
console.log(`${colorize("Optional: AgentMemory Pro", "bold")} — ${colorize("memory that learns from your work", "dim")}`);
|
|
1854
1855
|
}
|
|
1855
|
-
console.log(` ${colorize("Recall across sessions", "cyan")} Ask "what did we decide about auth?" across Claude, Codex,
|
|
1856
|
+
console.log(` ${colorize("Recall across sessions", "cyan")} Ask "what did we decide about auth?" across Claude Code, Codex, and Pi history.`);
|
|
1856
1857
|
console.log(` ${colorize("Learn from corrections", "cyan")} Turn repeated fixes into memory you can inspect and undo.`);
|
|
1857
|
-
console.log(` ${colorize("
|
|
1858
|
-
console.log(` ${colorize("Private by default", "cyan")}
|
|
1858
|
+
console.log(` ${colorize("Local session index", "cyan")} Scan supported session history without uploading it to AgentMemory.`);
|
|
1859
|
+
console.log(` ${colorize("Private by default", "cyan")} AgentMemory services never receive memory or session content.`);
|
|
1859
1860
|
console.log(` ${colorize("Included at no cost:", "green")} ${colorize("20 recalls + 5 learning scans per day", "bold")}. Local indexing and dashboard remain free.`);
|
|
1860
1861
|
console.log("");
|
|
1861
1862
|
}
|
|
@@ -2383,9 +2384,10 @@ Usage:
|
|
|
2383
2384
|
agent-memory plugin manage [--no-browser]
|
|
2384
2385
|
|
|
2385
2386
|
The public core remains fully usable without AgentMemory Pro. Install uses a random
|
|
2386
|
-
installation identifier and requires no account or email. The free tier includes
|
|
2387
|
-
20 recalls and 5 learning scans per local day; indexing and the Memory Dashboard
|
|
2388
|
-
remain available.
|
|
2387
|
+
installation identifier and requires no account or email. The free tier includes
|
|
2388
|
+
20 recalls and 5 learning scans per local day; indexing and the Memory Dashboard
|
|
2389
|
+
remain available. AgentMemory services never receive memory or session content;
|
|
2390
|
+
recall results are subject to the coding agent or model provider you invoke.`);
|
|
2389
2391
|
}
|
|
2390
2392
|
function pluginCommandFailure(command, error) {
|
|
2391
2393
|
return {
|
|
@@ -3123,11 +3125,16 @@ async function cmdServe(flags) {
|
|
|
3123
3125
|
server.addTool({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema }, (input) => runtime.runMcpTool(tool.name, input));
|
|
3124
3126
|
}
|
|
3125
3127
|
server.addStartupHook(() => runtime.runMcpStartup());
|
|
3128
|
+
server.addShutdownHook(() => runtime.runMcpShutdown());
|
|
3126
3129
|
}
|
|
3127
3130
|
catch {
|
|
3128
3131
|
// Pro not installed or failed to load — serve with core tools only.
|
|
3129
3132
|
}
|
|
3130
3133
|
await server.start();
|
|
3134
|
+
// Hard backstop: a Pro plugin's fs.watch handles (or any other resource
|
|
3135
|
+
// that keeps the event loop alive) must never prevent this process from
|
|
3136
|
+
// exiting once stdin has closed.
|
|
3137
|
+
process.exit(0);
|
|
3131
3138
|
}
|
|
3132
3139
|
// ---------------------------------------------------------------------------
|
|
3133
3140
|
// Usage
|
|
@@ -3416,7 +3423,7 @@ async function main() {
|
|
|
3416
3423
|
capability: "session",
|
|
3417
3424
|
});
|
|
3418
3425
|
if (decision.state === "exhausted") {
|
|
3419
|
-
console.error(`AgentMemory free session allowance resets in ${formatResetTime(decision.resetAt)}.
|
|
3426
|
+
console.error(`AgentMemory free session allowance resets in ${formatResetTime(decision.resetAt)}. Paid plans are not available yet.`);
|
|
3420
3427
|
}
|
|
3421
3428
|
}
|
|
3422
3429
|
}
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -17,9 +17,11 @@ export declare class StdioMcpServer {
|
|
|
17
17
|
private readonly version;
|
|
18
18
|
private readonly tools;
|
|
19
19
|
private readonly startupHooks;
|
|
20
|
+
private readonly shutdownHooks;
|
|
20
21
|
constructor(version?: string);
|
|
21
22
|
addTool(definition: McpToolDefinition, handler: McpToolHandler): void;
|
|
22
23
|
addStartupHook(fn: () => void | Promise<void>): void;
|
|
24
|
+
addShutdownHook(fn: () => void | Promise<void>): void;
|
|
23
25
|
start(): Promise<void>;
|
|
24
26
|
private handleMessage;
|
|
25
27
|
private respond;
|
package/dist/mcp-server.js
CHANGED
|
@@ -9,6 +9,7 @@ export class StdioMcpServer {
|
|
|
9
9
|
version;
|
|
10
10
|
tools = new Map();
|
|
11
11
|
startupHooks = [];
|
|
12
|
+
shutdownHooks = [];
|
|
12
13
|
constructor(version = "0.0.0") {
|
|
13
14
|
this.version = version;
|
|
14
15
|
}
|
|
@@ -18,6 +19,9 @@ export class StdioMcpServer {
|
|
|
18
19
|
addStartupHook(fn) {
|
|
19
20
|
this.startupHooks.push(fn);
|
|
20
21
|
}
|
|
22
|
+
addShutdownHook(fn) {
|
|
23
|
+
this.shutdownHooks.push(fn);
|
|
24
|
+
}
|
|
21
25
|
async start() {
|
|
22
26
|
// Run all startup hooks before entering the message loop.
|
|
23
27
|
for (const hook of this.startupHooks)
|
|
@@ -40,6 +44,17 @@ export class StdioMcpServer {
|
|
|
40
44
|
rl.on("close", resolve);
|
|
41
45
|
process.stdin.on("end", resolve);
|
|
42
46
|
});
|
|
47
|
+
// A hook may hold resources (e.g. fs.watch handles) that keep the event
|
|
48
|
+
// loop alive past stdin close — run them, but don't let one broken hook
|
|
49
|
+
// block the others or block process exit.
|
|
50
|
+
for (const hook of this.shutdownHooks) {
|
|
51
|
+
try {
|
|
52
|
+
await hook();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Non-fatal — the caller still hard-exits after start() returns.
|
|
56
|
+
}
|
|
57
|
+
}
|
|
43
58
|
}
|
|
44
59
|
handleMessage(msg) {
|
|
45
60
|
const id = msg.id;
|
package/dist/plugin-host.d.ts
CHANGED
|
@@ -147,7 +147,12 @@ export interface PluginMcpToolInputSchema {
|
|
|
147
147
|
export interface PluginMcpToolV1 {
|
|
148
148
|
name: string;
|
|
149
149
|
description: string;
|
|
150
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Capability checked on every invocation. Optional only to keep plugin API 1
|
|
152
|
+
* source-compatible with bundles built before capability-gated MCP tools
|
|
153
|
+
* were introduced; legacy tools load but are denied until updated.
|
|
154
|
+
*/
|
|
155
|
+
requiredCapability?: string;
|
|
151
156
|
inputSchema: PluginMcpToolInputSchema;
|
|
152
157
|
run(input: Record<string, unknown>): unknown | Promise<unknown>;
|
|
153
158
|
}
|
|
@@ -160,6 +165,7 @@ export interface AgentMemoryPluginHostV1 {
|
|
|
160
165
|
registerContextProvider?(provider: PluginContextProviderV1): void;
|
|
161
166
|
registerMcpTool?(tool: PluginMcpToolV1): void;
|
|
162
167
|
registerMcpStartup?(fn: () => void | Promise<void>): void;
|
|
168
|
+
registerMcpShutdown?(fn: () => void | Promise<void>): void;
|
|
163
169
|
getStateDirectory(): string;
|
|
164
170
|
getMemoryDirectory(): string;
|
|
165
171
|
getEntitlement(): Promise<PluginEntitlementStatusV1>;
|
package/dist/plugin-runtime.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class InstalledPluginRuntimeV1 {
|
|
|
15
15
|
private readonly contextProviders;
|
|
16
16
|
private readonly mcpTools;
|
|
17
17
|
private readonly mcpStartupHooks;
|
|
18
|
+
private readonly mcpShutdownHooks;
|
|
18
19
|
private loaded;
|
|
19
20
|
constructor(options: PluginRuntimeOptionsV1);
|
|
20
21
|
load(): Promise<boolean>;
|
|
@@ -44,6 +45,7 @@ export declare class InstalledPluginRuntimeV1 {
|
|
|
44
45
|
*/
|
|
45
46
|
runMcpTool(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
46
47
|
runMcpStartup(): Promise<void>;
|
|
48
|
+
runMcpShutdown(): Promise<void>;
|
|
47
49
|
private createHost;
|
|
48
50
|
private refreshEntitlement;
|
|
49
51
|
}
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -65,6 +65,7 @@ export class InstalledPluginRuntimeV1 {
|
|
|
65
65
|
contextProviders = [];
|
|
66
66
|
mcpTools = [];
|
|
67
67
|
mcpStartupHooks = [];
|
|
68
|
+
mcpShutdownHooks = [];
|
|
68
69
|
loaded = false;
|
|
69
70
|
constructor(options) {
|
|
70
71
|
this.options = options;
|
|
@@ -200,6 +201,10 @@ export class InstalledPluginRuntimeV1 {
|
|
|
200
201
|
const tool = this.mcpTools.find((candidate) => candidate.name === name);
|
|
201
202
|
if (!tool)
|
|
202
203
|
return { error: `Unknown MCP tool: ${name}` };
|
|
204
|
+
if (!tool.requiredCapability)
|
|
205
|
+
return {
|
|
206
|
+
error: `The ${name} tool was built for an older plugin API and must be updated before it can run`,
|
|
207
|
+
};
|
|
203
208
|
const entitlement = await this.refreshEntitlement();
|
|
204
209
|
if (!isPluginCapabilityEnabled(entitlement, tool.requiredCapability))
|
|
205
210
|
return { error: `Capability ${tool.requiredCapability} is not enabled for the ${name} tool` };
|
|
@@ -209,6 +214,10 @@ export class InstalledPluginRuntimeV1 {
|
|
|
209
214
|
for (const hook of this.mcpStartupHooks)
|
|
210
215
|
await hook();
|
|
211
216
|
}
|
|
217
|
+
async runMcpShutdown() {
|
|
218
|
+
for (const hook of this.mcpShutdownHooks)
|
|
219
|
+
await hook();
|
|
220
|
+
}
|
|
212
221
|
createHost(manifest) {
|
|
213
222
|
const descriptors = new Map(manifest.commands.map((command) => [command.name, command]));
|
|
214
223
|
const stateRoot = path.join(this.store.root, "state");
|
|
@@ -257,7 +266,7 @@ export class InstalledPluginRuntimeV1 {
|
|
|
257
266
|
this.contextProviders.push({ provider, pluginId: manifest.id });
|
|
258
267
|
},
|
|
259
268
|
registerMcpTool: (tool) => {
|
|
260
|
-
if (!(manifest.capabilities ?? []).includes(tool.requiredCapability))
|
|
269
|
+
if (tool.requiredCapability && !(manifest.capabilities ?? []).includes(tool.requiredCapability))
|
|
261
270
|
throw new PluginBootstrapFailure("plugin_mcp_tool_invalid", `Plugin ${manifest.id} registered an MCP tool with an undeclared capability`);
|
|
262
271
|
if (!tool.name || this.mcpTools.some((existing) => existing.name === tool.name))
|
|
263
272
|
throw new PluginBootstrapFailure("plugin_mcp_tool_invalid", `Plugin MCP tool ${tool.name || "(unnamed)"} is invalid or already registered`);
|
|
@@ -266,6 +275,9 @@ export class InstalledPluginRuntimeV1 {
|
|
|
266
275
|
registerMcpStartup: (fn) => {
|
|
267
276
|
this.mcpStartupHooks.push(fn);
|
|
268
277
|
},
|
|
278
|
+
registerMcpShutdown: (fn) => {
|
|
279
|
+
this.mcpShutdownHooks.push(fn);
|
|
280
|
+
},
|
|
269
281
|
getStateDirectory: () => stateDirectory,
|
|
270
282
|
getMemoryDirectory: () => {
|
|
271
283
|
assertPermission(manifest, "memory:read");
|
package/dist/plugin-service.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PluginAccessDecisionV1, type PluginBootstrapBackendV1, type PluginNextActionV1, type
|
|
1
|
+
import { type PluginAccessDecisionV1, type PluginBootstrapBackendV1, type PluginNextActionV1, type SignedPluginReleaseV1 } from "./plugin-bootstrap.js";
|
|
2
2
|
import { type PluginEntitlementStatusV1 } from "./plugin-host.js";
|
|
3
3
|
interface AgentMemoryServiceBackendOptions {
|
|
4
4
|
root?: string;
|
|
@@ -27,9 +27,6 @@ export declare class AgentMemoryServiceBackend implements PluginBootstrapBackend
|
|
|
27
27
|
channel: string;
|
|
28
28
|
allowAuthentication: boolean;
|
|
29
29
|
}): Promise<PluginAccessDecisionV1>;
|
|
30
|
-
reserveSession(operationId: string): Promise<PluginSessionUsageDecisionV1>;
|
|
31
|
-
commitSession(operationId: string): Promise<PluginSessionUsageDecisionV1>;
|
|
32
|
-
releaseSession(operationId: string): Promise<PluginSessionUsageDecisionV1>;
|
|
33
30
|
listReleases(request: {
|
|
34
31
|
bundleId: string;
|
|
35
32
|
channel: string;
|
|
@@ -43,7 +40,6 @@ export declare class AgentMemoryServiceBackend implements PluginBootstrapBackend
|
|
|
43
40
|
private activationPath;
|
|
44
41
|
private readActivation;
|
|
45
42
|
private writeActivation;
|
|
46
|
-
private sessionUsage;
|
|
47
43
|
private request;
|
|
48
44
|
}
|
|
49
45
|
export declare class TemporaryPluginBackend extends AgentMemoryServiceBackend {
|
package/dist/plugin-service.js
CHANGED
|
@@ -139,8 +139,8 @@ function activationPage(action, error) {
|
|
|
139
139
|
<p class="terminal-note" id="terminal-note">Your terminal will finish setup after activation.</p>
|
|
140
140
|
<details>
|
|
141
141
|
<summary>What’s shared during activation</summary>
|
|
142
|
-
<p>Your email identifies your free daily allowance. The CLI also sends core and bundle versions, platform, architecture, and release channel. The service stores
|
|
143
|
-
<p class="never-sent"><strong>
|
|
142
|
+
<p>Your email identifies your free daily allowance. The CLI also sends core and bundle versions, platform, architecture, and release channel. The service stores bounded activation metadata. Activation records expire after 365 days without use.</p>
|
|
143
|
+
<p class="never-sent"><strong>Not included in AgentMemory's application payload:</strong> memory, session content, queries, repository paths, raw agent session identifiers, IP addresses, or user-agent strings.</p>
|
|
144
144
|
</details>
|
|
145
145
|
</section>
|
|
146
146
|
</main>
|
|
@@ -394,8 +394,6 @@ export class AgentMemoryServiceBackend {
|
|
|
394
394
|
validatePluginEntitlementStatusV1(value.entitlement);
|
|
395
395
|
if (typeof value.artifactGrant !== "string" || !value.artifactGrant)
|
|
396
396
|
throw new PluginBootstrapFailure("service_response_invalid", "The access response omitted its artifact grant");
|
|
397
|
-
if (typeof value.usageCredential !== "string" || !ACTIVATION_CREDENTIAL.test(value.usageCredential))
|
|
398
|
-
throw new PluginBootstrapFailure("service_response_invalid", "The access response omitted its usage credential");
|
|
399
397
|
const recallQuota = value.entitlement.capabilities.recall?.quota;
|
|
400
398
|
const learningQuota = value.entitlement.capabilities.learning?.quota;
|
|
401
399
|
if (value.entitlement.plan !== "free" ||
|
|
@@ -408,18 +406,9 @@ export class AgentMemoryServiceBackend {
|
|
|
408
406
|
value.entitlement.capabilities["session-worker"]?.enabled !== false ||
|
|
409
407
|
value.entitlement.capabilities["web-console"]?.enabled !== true)
|
|
410
408
|
throw new PluginBootstrapFailure("service_response_invalid", "The free preview policy is invalid");
|
|
411
|
-
this.writeActivation(installationId
|
|
409
|
+
this.writeActivation(installationId);
|
|
412
410
|
return { kind: "granted", entitlement: value.entitlement, artifactGrant: value.artifactGrant };
|
|
413
411
|
}
|
|
414
|
-
async reserveSession(operationId) {
|
|
415
|
-
return this.sessionUsage("reserve", operationId);
|
|
416
|
-
}
|
|
417
|
-
async commitSession(operationId) {
|
|
418
|
-
return this.sessionUsage("commit", operationId);
|
|
419
|
-
}
|
|
420
|
-
async releaseSession(operationId) {
|
|
421
|
-
return this.sessionUsage("release", operationId);
|
|
422
|
-
}
|
|
423
412
|
async listReleases(request) {
|
|
424
413
|
const response = await this.request(`${this.apiOrigin}/v1/plugin/releases`, {
|
|
425
414
|
headers: { Authorization: `Bearer ${request.artifactGrant}` },
|
|
@@ -474,28 +463,28 @@ export class AgentMemoryServiceBackend {
|
|
|
474
463
|
if (!stat.isFile() || stat.isSymbolicLink() || (process.platform !== "win32" && (stat.mode & 0o077) !== 0))
|
|
475
464
|
return null;
|
|
476
465
|
const value = JSON.parse(fs.readFileSync(activationPath, "utf-8"));
|
|
477
|
-
if (value.
|
|
478
|
-
typeof value.installationId !== "string" ||
|
|
466
|
+
if (typeof value.installationId !== "string" ||
|
|
479
467
|
!/^am_install_[A-Za-z0-9_-]{32}$/.test(value.installationId) ||
|
|
480
468
|
!Number.isFinite(Date.parse(value.activatedAt)) ||
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
469
|
+
(value.schemaVersion !== 4 &&
|
|
470
|
+
(value.schemaVersion !== 3 ||
|
|
471
|
+
typeof value.usageCredential !== "string" ||
|
|
472
|
+
!ACTIVATION_CREDENTIAL.test(value.usageCredential) ||
|
|
473
|
+
!Number.isSafeInteger(value.dailySessionLimit))))
|
|
485
474
|
return null;
|
|
486
|
-
return
|
|
475
|
+
return {
|
|
476
|
+
schemaVersion: 4,
|
|
477
|
+
installationId: value.installationId,
|
|
478
|
+
activatedAt: value.activatedAt,
|
|
479
|
+
};
|
|
487
480
|
}
|
|
488
481
|
catch {
|
|
489
482
|
return null;
|
|
490
483
|
}
|
|
491
484
|
}
|
|
492
|
-
writeActivation(installationId
|
|
485
|
+
writeActivation(installationId) {
|
|
493
486
|
if (!/^am_install_[A-Za-z0-9_-]{32}$/.test(installationId))
|
|
494
487
|
throw new PluginBootstrapFailure("activation_failed", "The installation identifier is invalid");
|
|
495
|
-
if (!ACTIVATION_CREDENTIAL.test(usageCredential))
|
|
496
|
-
throw new PluginBootstrapFailure("activation_failed", "The activation credential is invalid");
|
|
497
|
-
if (!Number.isSafeInteger(dailySessionLimit) || dailySessionLimit <= 0 || dailySessionLimit > 10_000)
|
|
498
|
-
throw new PluginBootstrapFailure("activation_failed", "The free session allowance is invalid");
|
|
499
488
|
const target = this.activationPath();
|
|
500
489
|
fs.mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
501
490
|
const rootStat = fs.lstatSync(this.root);
|
|
@@ -509,43 +498,12 @@ export class AgentMemoryServiceBackend {
|
|
|
509
498
|
throw new PluginBootstrapFailure("activation_path_invalid", "The plugin activation directory is unsafe");
|
|
510
499
|
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
511
500
|
fs.writeFileSync(temporary, `${JSON.stringify({
|
|
512
|
-
schemaVersion:
|
|
501
|
+
schemaVersion: 4,
|
|
513
502
|
installationId,
|
|
514
503
|
activatedAt: new Date().toISOString(),
|
|
515
|
-
usageCredential,
|
|
516
|
-
dailySessionLimit,
|
|
517
504
|
}, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
518
505
|
fs.renameSync(temporary, target);
|
|
519
506
|
}
|
|
520
|
-
async sessionUsage(action, operationId) {
|
|
521
|
-
const activation = this.readActivation();
|
|
522
|
-
if (!activation)
|
|
523
|
-
throw new PluginBootstrapFailure("auth_required", "Run plugin install to activate AgentMemory");
|
|
524
|
-
const response = await this.request(`${this.apiOrigin}/v1/plugin/sessions/${action}`, {
|
|
525
|
-
method: "POST",
|
|
526
|
-
headers: {
|
|
527
|
-
Authorization: `Bearer ${activation.usageCredential}`,
|
|
528
|
-
"Content-Type": "application/json",
|
|
529
|
-
},
|
|
530
|
-
body: JSON.stringify({ schemaVersion: 1, operationId }),
|
|
531
|
-
});
|
|
532
|
-
const value = (await readJson(response));
|
|
533
|
-
const decision = value.decision;
|
|
534
|
-
if (!decision ||
|
|
535
|
-
typeof decision.allowed !== "boolean" ||
|
|
536
|
-
!["reserved", "committed", "released", "exhausted", "missing"].includes(String(decision.state)) ||
|
|
537
|
-
!Number.isSafeInteger(decision.limit) ||
|
|
538
|
-
Number(decision.limit) <= 0 ||
|
|
539
|
-
!Number.isSafeInteger(decision.used) ||
|
|
540
|
-
Number(decision.used) < 0 ||
|
|
541
|
-
!Number.isSafeInteger(decision.remaining) ||
|
|
542
|
-
Number(decision.remaining) < 0 ||
|
|
543
|
-
typeof decision.resetAt !== "string" ||
|
|
544
|
-
!Number.isFinite(Date.parse(decision.resetAt)) ||
|
|
545
|
-
typeof decision.idempotent !== "boolean")
|
|
546
|
-
throw new PluginBootstrapFailure("service_response_invalid", "The session usage response is invalid");
|
|
547
|
-
return decision;
|
|
548
|
-
}
|
|
549
507
|
async request(url, init = {}) {
|
|
550
508
|
let response;
|
|
551
509
|
try {
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* retried before the next cache refresh, and never thrown.
|
|
16
16
|
*/
|
|
17
17
|
import { type SpawnOptions } from "node:child_process";
|
|
18
|
-
export type InstallManager = "bun" | "npm" | "pnpm" | "yarn" | "unknown";
|
|
18
|
+
export type InstallManager = "bun" | "homebrew" | "npm" | "pnpm" | "yarn" | "unknown";
|
|
19
19
|
export interface InstallMethod {
|
|
20
20
|
manager: InstallManager;
|
|
21
21
|
global: boolean;
|
|
@@ -77,6 +77,14 @@ export declare function readUpgradePolicy(): UpgradePolicy & {
|
|
|
77
77
|
};
|
|
78
78
|
/** Atomically persist the auto-upgrade policy. Merges with whatever is already on disk. */
|
|
79
79
|
export declare function writeUpgradePolicy(patch: Partial<UpgradePolicy>): UpgradePolicy;
|
|
80
|
+
export declare function resolveSelfLaunch(input: {
|
|
81
|
+
execPath: string;
|
|
82
|
+
scriptCandidate: string | undefined;
|
|
83
|
+
fileExists: (candidate: string) => boolean;
|
|
84
|
+
}): {
|
|
85
|
+
command: string;
|
|
86
|
+
args: string[];
|
|
87
|
+
};
|
|
80
88
|
/**
|
|
81
89
|
* Best-effort detection of how `myagentmemory` was installed. Path signatures
|
|
82
90
|
* are heuristic but cover the common managers. On no match we fall back to
|
package/dist/upgrade.js
CHANGED
|
@@ -167,12 +167,34 @@ export function writeUpgradePolicy(patch) {
|
|
|
167
167
|
// ---------------------------------------------------------------------------
|
|
168
168
|
// Install-method detection
|
|
169
169
|
// ---------------------------------------------------------------------------
|
|
170
|
+
export function resolveSelfLaunch(input) {
|
|
171
|
+
const scriptCandidate = input.scriptCandidate;
|
|
172
|
+
const isRealScript = typeof scriptCandidate === "string" &&
|
|
173
|
+
scriptCandidate.length > 0 &&
|
|
174
|
+
!scriptCandidate.startsWith("/$bunfs/") &&
|
|
175
|
+
input.fileExists(scriptCandidate);
|
|
176
|
+
return isRealScript ? { command: input.execPath, args: [scriptCandidate] } : { command: input.execPath, args: [] };
|
|
177
|
+
}
|
|
178
|
+
function currentSelfLaunch() {
|
|
179
|
+
return resolveSelfLaunch({
|
|
180
|
+
execPath: process.execPath,
|
|
181
|
+
scriptCandidate: process.argv[1],
|
|
182
|
+
fileExists: fs.existsSync,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
170
185
|
function selfInstallPath() {
|
|
186
|
+
const launch = currentSelfLaunch();
|
|
187
|
+
const candidate = launch.args[0] ?? launch.command;
|
|
171
188
|
try {
|
|
172
|
-
return
|
|
189
|
+
return fs.realpathSync(candidate);
|
|
173
190
|
}
|
|
174
191
|
catch {
|
|
175
|
-
|
|
192
|
+
try {
|
|
193
|
+
return url.fileURLToPath(import.meta.url);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return candidate;
|
|
197
|
+
}
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
/**
|
|
@@ -184,6 +206,16 @@ export function detectInstallMethod(location = selfInstallPath()) {
|
|
|
184
206
|
const normalized = location.replace(/\\/g, "/");
|
|
185
207
|
const home = os.homedir().replace(/\\/g, "/");
|
|
186
208
|
const pkg = `${NPM_PACKAGE_NAME}@latest`;
|
|
209
|
+
// Compiled CLI installed by the official Homebrew formula. Resolve symlinks
|
|
210
|
+
// before detection so /opt/homebrew/bin/agent-memory reaches its Cellar path.
|
|
211
|
+
if (normalized.includes("/Cellar/agent-memory/")) {
|
|
212
|
+
return {
|
|
213
|
+
manager: "homebrew",
|
|
214
|
+
global: true,
|
|
215
|
+
origin: location,
|
|
216
|
+
command: ["brew", "upgrade", "jayzeng/agentmemory/agent-memory"],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
187
219
|
// bun global install
|
|
188
220
|
if (normalized.includes("/.bun/install/global/") || normalized.includes("/bun/install/global/")) {
|
|
189
221
|
return { manager: "bun", global: true, origin: location, command: ["bun", "add", "-g", pkg] };
|
|
@@ -241,11 +273,10 @@ export function runInstaller(method, opts = {}) {
|
|
|
241
273
|
*/
|
|
242
274
|
export function refreshUpgradeCacheBackground() {
|
|
243
275
|
try {
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
if (!binary || !script)
|
|
276
|
+
const launch = currentSelfLaunch();
|
|
277
|
+
if (!launch.command)
|
|
247
278
|
return;
|
|
248
|
-
const child = spawn(
|
|
279
|
+
const child = spawn(launch.command, [...launch.args, "upgrade", "--background", "--refresh", "--quiet", "--json"], {
|
|
249
280
|
detached: true,
|
|
250
281
|
stdio: "ignore",
|
|
251
282
|
env: { ...process.env, AGENT_MEMORY_UPGRADE_BACKGROUND: "1" },
|
|
@@ -89,7 +89,7 @@ The `pro` namespace is the user-facing surface. The `plugin` namespace remains s
|
|
|
89
89
|
- `status` is read-only. It reports the installed bundle, selected channel, compatibility, entitlement state, and update availability.
|
|
90
90
|
- `install` authenticates when necessary, then installs, upgrades, or reports current state.
|
|
91
91
|
- `update` requires an existing installation and never starts a new purchase implicitly.
|
|
92
|
-
- `uninstall` removes executable plugin material and the active receipt. It preserves core memory, plugin state, and the permission-restricted activation
|
|
92
|
+
- `uninstall` removes executable plugin material and the active receipt. It preserves core memory, plugin state, and the permission-restricted local activation record.
|
|
93
93
|
- `manage` remains unavailable until authenticated account and billing management exists.
|
|
94
94
|
|
|
95
95
|
Installed plugins contribute top-level commands including `recall` and `learn`; `dashboard` is a product-facing alias for the lower-level `web` command. Bootstrap command names are reserved by the core and cannot be replaced by a plugin.
|
|
@@ -172,7 +172,6 @@ An Enterprise administrator may pre-provision an organization entitlement or man
|
|
|
172
172
|
The service exposes:
|
|
173
173
|
|
|
174
174
|
- `POST /v1/plugin/access` for an anonymous free-preview policy, compatibility credential, and short-lived artifact grant;
|
|
175
|
-
- `POST /v1/plugin/sessions/reserve|commit|release` for migration compatibility with activation-v2 clients;
|
|
176
175
|
- `GET /v1/plugin/releases` for an Ed25519-signed release selected from the private R2 catalog;
|
|
177
176
|
- `GET|HEAD /v1/artifacts/download` for the exact content-addressed object authorized by the bearer grant.
|
|
178
177
|
|
|
@@ -191,7 +190,7 @@ The bootstrap may send only:
|
|
|
191
190
|
- core version, plugin-host API version, platform, and architecture;
|
|
192
191
|
- requested bundle ID, installed bundle version, and release channel;
|
|
193
192
|
- a pseudonymous license or organization identifier;
|
|
194
|
-
- protocol nonces
|
|
193
|
+
- protocol nonces and authentication material required for the request.
|
|
195
194
|
|
|
196
195
|
It must never send memory contents, search queries, session contents, raw agent session identifiers, working-directory names, repository names, filesystem paths, or qmd data. The bounded allowance counter is authorization state, not general product telemetry.
|
|
197
196
|
|
|
@@ -267,7 +266,7 @@ An install or upgrade must:
|
|
|
267
266
|
|
|
268
267
|
Failure before activation leaves the previous version active. Failure immediately after activation restores the previous receipt. Concurrent installers do not interleave. The core never invokes package-manager lifecycle scripts or elevates privileges.
|
|
269
268
|
|
|
270
|
-
Uninstall removes executable versions, the active receipt, contributed skills, and managed hooks. It does not remove `MEMORY.md`, daily logs, topics, scratchpad items, source session logs, plugin-created review data, or
|
|
269
|
+
Uninstall removes executable versions, the active receipt, contributed skills, and managed hooks. It does not remove `MEMORY.md`, daily logs, topics, scratchpad items, source session logs, plugin-created review data, or local activation state. The top-level `agent-memory uninstall` command composes this with hook/skill/MCP/completion removal in one step; its explicit `--data` flag additionally deletes the memory directory and the entire plugin install root (bundles, receipts, and activation state) once the user opts in and confirms.
|
|
271
270
|
|
|
272
271
|
## Plugin host API v1
|
|
273
272
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myagentmemory",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "agentmemory (agent-memory) is persistent memory for coding agents (Claude Code, OpenAI Codex, Cursor, Agent) with qmd-powered semantic search across daily logs, long-term memory, and scratchpad",
|
|
5
5
|
"main": "./dist/core.js",
|
|
6
6
|
"types": "./dist/core.d.ts",
|
|
@@ -93,7 +93,6 @@
|
|
|
93
93
|
"dist/plugin-runtime.js",
|
|
94
94
|
"dist/plugin-service.d.ts",
|
|
95
95
|
"dist/plugin-service.js",
|
|
96
|
-
|
|
97
96
|
"dist/mcp-server.d.ts",
|
|
98
97
|
"dist/mcp-server.js",
|
|
99
98
|
"dist/upgrade.d.ts",
|
package/src/cli-spec.ts
CHANGED
|
@@ -82,7 +82,7 @@ export const PLUGIN_COMMAND_DESCRIPTIONS: Record<(typeof PLUGIN_COMMANDS)[number
|
|
|
82
82
|
install: "authenticate if needed, then install or upgrade the official bundle",
|
|
83
83
|
update: "upgrade an existing official bundle when a compatible release exists",
|
|
84
84
|
uninstall: "remove official plugin executables while preserving user data",
|
|
85
|
-
manage: "open
|
|
85
|
+
manage: "open account management when that future service is available",
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
export const WORKER_ACTION_DESCRIPTIONS: Record<(typeof WORKER_ACTIONS)[number], string> = {};
|
package/src/plugin-host.ts
CHANGED
|
@@ -176,7 +176,12 @@ export interface PluginMcpToolInputSchema {
|
|
|
176
176
|
export interface PluginMcpToolV1 {
|
|
177
177
|
name: string;
|
|
178
178
|
description: string;
|
|
179
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Capability checked on every invocation. Optional only to keep plugin API 1
|
|
181
|
+
* source-compatible with bundles built before capability-gated MCP tools
|
|
182
|
+
* were introduced; legacy tools load but are denied until updated.
|
|
183
|
+
*/
|
|
184
|
+
requiredCapability?: string;
|
|
180
185
|
inputSchema: PluginMcpToolInputSchema;
|
|
181
186
|
run(input: Record<string, unknown>): unknown | Promise<unknown>;
|
|
182
187
|
}
|
|
@@ -190,6 +195,7 @@ export interface AgentMemoryPluginHostV1 {
|
|
|
190
195
|
registerContextProvider?(provider: PluginContextProviderV1): void;
|
|
191
196
|
registerMcpTool?(tool: PluginMcpToolV1): void;
|
|
192
197
|
registerMcpStartup?(fn: () => void | Promise<void>): void;
|
|
198
|
+
registerMcpShutdown?(fn: () => void | Promise<void>): void;
|
|
193
199
|
getStateDirectory(): string;
|
|
194
200
|
getMemoryDirectory(): string;
|
|
195
201
|
getEntitlement(): Promise<PluginEntitlementStatusV1>;
|