myagentmemory 0.4.17 → 0.5.2
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 +68 -71
- package/dist/cli-spec.d.ts +7 -1
- package/dist/cli-spec.js +214 -12
- package/dist/cli.js +2053 -156
- package/dist/completions.js +24 -18
- package/dist/core.d.ts +42 -4
- package/dist/core.js +242 -68
- package/dist/hooks.d.ts +21 -1
- package/dist/hooks.js +382 -87
- package/dist/mcp-server.d.ts +27 -0
- package/dist/mcp-server.js +106 -0
- package/dist/plugin-bootstrap.js +4 -4
- package/dist/plugin-host.d.ts +34 -0
- package/dist/plugin-runtime.d.ts +22 -1
- package/dist/plugin-runtime.js +65 -2
- package/dist/plugin-service.d.ts +10 -4
- package/dist/plugin-service.js +52 -8
- package/dist/upgrade.d.ts +80 -0
- package/dist/upgrade.js +243 -0
- package/docs/official-plugin-bootstrap.md +3 -3
- package/package.json +24 -4
- package/scripts/install-skills.sh +1 -1
- package/skills/agent/SKILL.md +12 -2
- package/skills/claude-code/SKILL.md +17 -2
- package/skills/codex/SKILL.md +14 -2
- package/skills/cursor/SKILL.md +14 -2
- package/src/cli-spec.ts +218 -12
- package/src/completions.ts +26 -18
- package/src/core.ts +312 -123
- package/src/hooks.ts +395 -85
- package/src/plugin-bootstrap.ts +4 -4
- package/src/plugin-host.ts +34 -0
- package/src/cli.ts +0 -1332
- package/src/plugin-runtime.ts +0 -390
- package/src/plugin-service.ts +0 -627
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Bare stdio MCP server — no external dependencies
|
|
4
|
+
//
|
|
5
|
+
// MCP uses newline-delimited JSON-RPC 2.0 over stdin/stdout (NOT Content-Length
|
|
6
|
+
// framing). Each message is one JSON object followed by \n.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export class StdioMcpServer {
|
|
9
|
+
version;
|
|
10
|
+
tools = new Map();
|
|
11
|
+
startupHooks = [];
|
|
12
|
+
constructor(version = "0.0.0") {
|
|
13
|
+
this.version = version;
|
|
14
|
+
}
|
|
15
|
+
addTool(definition, handler) {
|
|
16
|
+
this.tools.set(definition.name, { definition, handler });
|
|
17
|
+
}
|
|
18
|
+
addStartupHook(fn) {
|
|
19
|
+
this.startupHooks.push(fn);
|
|
20
|
+
}
|
|
21
|
+
async start() {
|
|
22
|
+
// Run all startup hooks before entering the message loop.
|
|
23
|
+
for (const hook of this.startupHooks)
|
|
24
|
+
await hook();
|
|
25
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
26
|
+
rl.on("line", (line) => {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (!trimmed)
|
|
29
|
+
return;
|
|
30
|
+
let message;
|
|
31
|
+
try {
|
|
32
|
+
message = JSON.parse(trimmed);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.handleMessage(message);
|
|
38
|
+
});
|
|
39
|
+
await new Promise((resolve) => {
|
|
40
|
+
rl.on("close", resolve);
|
|
41
|
+
process.stdin.on("end", resolve);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
handleMessage(msg) {
|
|
45
|
+
const id = msg.id;
|
|
46
|
+
const method = typeof msg.method === "string" ? msg.method : "";
|
|
47
|
+
// Notifications (no id) — no response needed.
|
|
48
|
+
if (id === undefined || id === null) {
|
|
49
|
+
// notifications/initialized is the only one we care about; ignore the rest.
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (method === "initialize") {
|
|
53
|
+
this.respond(id, {
|
|
54
|
+
protocolVersion: "2024-11-05",
|
|
55
|
+
capabilities: { tools: {} },
|
|
56
|
+
serverInfo: { name: "agent-memory", version: this.version },
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (method === "ping") {
|
|
61
|
+
this.respond(id, {});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (method === "tools/list") {
|
|
65
|
+
this.respond(id, {
|
|
66
|
+
tools: [...this.tools.values()].map(({ definition }) => definition),
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (method === "tools/call") {
|
|
71
|
+
const params = (msg.params ?? {});
|
|
72
|
+
const toolName = typeof params.name === "string" ? params.name : "";
|
|
73
|
+
const toolInput = (params.arguments ?? {});
|
|
74
|
+
const entry = this.tools.get(toolName);
|
|
75
|
+
if (!entry) {
|
|
76
|
+
this.respondError(id, -32601, `Unknown tool: ${toolName}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Execute handler — handle both sync and async.
|
|
80
|
+
Promise.resolve()
|
|
81
|
+
.then(() => entry.handler(toolInput))
|
|
82
|
+
.then((result) => {
|
|
83
|
+
this.respond(id, {
|
|
84
|
+
content: [
|
|
85
|
+
{ type: "text", text: typeof result === "string" ? result : JSON.stringify(result, null, 2) },
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
})
|
|
89
|
+
.catch((error) => {
|
|
90
|
+
this.respond(id, {
|
|
91
|
+
content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
92
|
+
isError: true,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Unknown method — JSON-RPC method not found.
|
|
98
|
+
this.respondError(id, -32601, `Method not found: ${method}`);
|
|
99
|
+
}
|
|
100
|
+
respond(id, result) {
|
|
101
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, result })}\n`);
|
|
102
|
+
}
|
|
103
|
+
respondError(id, code, message) {
|
|
104
|
+
process.stdout.write(`${JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
package/dist/plugin-bootstrap.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { AGENT_MEMORY_PLUGIN_API_VERSION, isSafeBundlePath, validateBundleManifestV1, } from "./plugin-host.js";
|
|
6
|
-
import {
|
|
6
|
+
import { AgentMemoryServiceBackend } from "./plugin-service.js";
|
|
7
7
|
export const OFFICIAL_BUNDLE_ID = "agentmemory.pro";
|
|
8
8
|
export const OFFICIAL_PLUGIN_IDS = ["agentmemory.session-intelligence", "agentmemory.web-console"];
|
|
9
9
|
const MISSING_ENTITLEMENT = {
|
|
@@ -20,7 +20,7 @@ const OFFICIAL_PLUGINS = [
|
|
|
20
20
|
const PACKAGE_MAX_BYTES = 64 * 1024 * 1024;
|
|
21
21
|
const PACKAGE_MAX_EXPANDED_BYTES = 128 * 1024 * 1024;
|
|
22
22
|
const PACKAGE_MAX_FILES = 10_000;
|
|
23
|
-
const
|
|
23
|
+
const RELEASE_SIGNING_KEY_2026_08 = `-----BEGIN PUBLIC KEY-----
|
|
24
24
|
MCowBQYDK2VwAyEASefZFUVFy1EmvGbd0ckHZThmPgqQ3u9HCwZRReAZQW8=
|
|
25
25
|
-----END PUBLIC KEY-----`;
|
|
26
26
|
export class PluginBootstrapFailure extends Error {
|
|
@@ -423,11 +423,11 @@ export class PluginBootstrapV1 {
|
|
|
423
423
|
}
|
|
424
424
|
export function createDefaultPluginBootstrap(coreVersion) {
|
|
425
425
|
const store = new FilePluginInstallStore();
|
|
426
|
-
const backend = new
|
|
426
|
+
const backend = new AgentMemoryServiceBackend({ root: store.root, coreVersion });
|
|
427
427
|
return new PluginBootstrapV1({
|
|
428
428
|
coreVersion,
|
|
429
429
|
backend,
|
|
430
|
-
verifier: new Ed25519ReleaseVerifier({ "agentmemory-temporary-2026-08":
|
|
430
|
+
verifier: new Ed25519ReleaseVerifier({ "agentmemory-temporary-2026-08": RELEASE_SIGNING_KEY_2026_08 }),
|
|
431
431
|
store,
|
|
432
432
|
healthCheck: async (directory, release) => {
|
|
433
433
|
const { createInstalledBundleHealthCheck } = await import("./plugin-runtime.js");
|
package/dist/plugin-host.d.ts
CHANGED
|
@@ -73,6 +73,21 @@ export interface PluginSessionStartHookV1 {
|
|
|
73
73
|
requiredCapability: string;
|
|
74
74
|
run(context: PluginSessionStartContextV1): Promise<void>;
|
|
75
75
|
}
|
|
76
|
+
export interface PluginBackgroundRefreshContextV1 {
|
|
77
|
+
host: string;
|
|
78
|
+
cwd?: string;
|
|
79
|
+
signal: AbortSignal;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Un-metered per-turn refresh callback. Fired on every UserPromptSubmit so
|
|
83
|
+
* plugins can keep background workers alive across `/clear` and long idle.
|
|
84
|
+
* MUST be idempotent and cheap — no quota is charged.
|
|
85
|
+
*/
|
|
86
|
+
export interface PluginBackgroundRefreshHookV1 {
|
|
87
|
+
name: string;
|
|
88
|
+
requiredCapability: string;
|
|
89
|
+
run(context: PluginBackgroundRefreshContextV1): Promise<void>;
|
|
90
|
+
}
|
|
76
91
|
export interface PluginMemoryWriteV1 {
|
|
77
92
|
target: "long_term" | "daily" | "topic";
|
|
78
93
|
content: string;
|
|
@@ -120,12 +135,31 @@ export interface PluginContextProviderV1 {
|
|
|
120
135
|
signal: AbortSignal;
|
|
121
136
|
}): Promise<PluginContextSectionV1[]>;
|
|
122
137
|
}
|
|
138
|
+
export interface PluginMcpToolInputSchema {
|
|
139
|
+
type: "object";
|
|
140
|
+
properties: Record<string, {
|
|
141
|
+
type: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
enum?: string[];
|
|
144
|
+
}>;
|
|
145
|
+
required?: string[];
|
|
146
|
+
}
|
|
147
|
+
export interface PluginMcpToolV1 {
|
|
148
|
+
name: string;
|
|
149
|
+
description: string;
|
|
150
|
+
requiredCapability: string;
|
|
151
|
+
inputSchema: PluginMcpToolInputSchema;
|
|
152
|
+
run(input: Record<string, unknown>): unknown | Promise<unknown>;
|
|
153
|
+
}
|
|
123
154
|
export interface AgentMemoryPluginHostV1 {
|
|
124
155
|
apiVersion: 1;
|
|
125
156
|
coreVersion: string;
|
|
126
157
|
registerCommand(command: PluginCommandV1): void;
|
|
127
158
|
registerSessionStartHook(hook: PluginSessionStartHookV1): void;
|
|
159
|
+
registerBackgroundRefresh?(hook: PluginBackgroundRefreshHookV1): void;
|
|
128
160
|
registerContextProvider?(provider: PluginContextProviderV1): void;
|
|
161
|
+
registerMcpTool?(tool: PluginMcpToolV1): void;
|
|
162
|
+
registerMcpStartup?(fn: () => void | Promise<void>): void;
|
|
129
163
|
getStateDirectory(): string;
|
|
130
164
|
getMemoryDirectory(): string;
|
|
131
165
|
getEntitlement(): Promise<PluginEntitlementStatusV1>;
|
package/dist/plugin-runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PluginBootstrapBackendV1, type PluginInstallStoreV1, type PluginSessionUsageDecisionV1, type SignedPluginReleaseV1 } from "./plugin-bootstrap.js";
|
|
2
|
-
import { type PluginCommandContextV1, type PluginCommandResultV1, type PluginContextSectionV1, type PluginSessionStartHookV1 } from "./plugin-host.js";
|
|
2
|
+
import { type PluginBackgroundRefreshContextV1, type PluginCommandContextV1, type PluginCommandResultV1, type PluginContextSectionV1, type PluginMcpToolV1, type PluginSessionStartHookV1 } from "./plugin-host.js";
|
|
3
3
|
export interface PluginRuntimeOptionsV1 {
|
|
4
4
|
coreVersion: string;
|
|
5
5
|
store?: PluginInstallStoreV1;
|
|
@@ -11,18 +11,39 @@ export declare class InstalledPluginRuntimeV1 {
|
|
|
11
11
|
private readonly backend;
|
|
12
12
|
private readonly commands;
|
|
13
13
|
private readonly hooks;
|
|
14
|
+
private readonly backgroundRefreshHooks;
|
|
14
15
|
private readonly contextProviders;
|
|
16
|
+
private readonly mcpTools;
|
|
17
|
+
private readonly mcpStartupHooks;
|
|
15
18
|
private loaded;
|
|
16
19
|
constructor(options: PluginRuntimeOptionsV1);
|
|
17
20
|
load(): Promise<boolean>;
|
|
18
21
|
run(name: string, context: PluginCommandContextV1): Promise<PluginCommandResultV1 | null>;
|
|
19
22
|
runSessionStart(context: Parameters<PluginSessionStartHookV1["run"]>[0]): Promise<PluginSessionUsageDecisionV1 | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Fire all registered background-refresh hooks. Un-metered — intended for
|
|
25
|
+
* per-turn UserPromptSubmit invocation, keeping workers alive across
|
|
26
|
+
* `/clear` and idle without charging session quota. Failures propagate to
|
|
27
|
+
* the caller (which is expected to swallow them silently).
|
|
28
|
+
*/
|
|
29
|
+
refreshBackgroundWorkers(context: PluginBackgroundRefreshContextV1): Promise<void>;
|
|
20
30
|
provideContext(context: {
|
|
21
31
|
host: string;
|
|
22
32
|
cwd?: string;
|
|
23
33
|
query?: string;
|
|
24
34
|
signal: AbortSignal;
|
|
25
35
|
}): Promise<PluginContextSectionV1[]>;
|
|
36
|
+
getMcpTools(): PluginMcpToolV1[];
|
|
37
|
+
/**
|
|
38
|
+
* Invoke a registered MCP tool by name, re-checking entitlement on every
|
|
39
|
+
* call — matching `run()`'s behavior for commands. MCP tools are
|
|
40
|
+
* registered once at `serve --mcp` startup and the server process can
|
|
41
|
+
* live for a long session, so a tool's entitlement must be re-verified
|
|
42
|
+
* per-call rather than trusted from registration time (e.g. a trial
|
|
43
|
+
* expiring mid-session must actually stop the tool from working).
|
|
44
|
+
*/
|
|
45
|
+
runMcpTool(name: string, input: Record<string, unknown>): Promise<unknown>;
|
|
46
|
+
runMcpStartup(): Promise<void>;
|
|
26
47
|
private createHost;
|
|
27
48
|
private refreshEntitlement;
|
|
28
49
|
}
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -5,7 +5,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
import { getMemoryDir, memoryWrite, redactSecrets, scheduleQmdUpdate } from "./core.js";
|
|
6
6
|
import { FilePluginInstallStore, OFFICIAL_BUNDLE_ID, PluginBootstrapFailure, } from "./plugin-bootstrap.js";
|
|
7
7
|
import { AGENT_MEMORY_PLUGIN_API_VERSION, isPluginCapabilityEnabled, validateBundleManifestV1, validatePluginEntitlementStatusV1, validatePluginManifestV1, } from "./plugin-host.js";
|
|
8
|
-
import {
|
|
8
|
+
import { AgentMemoryServiceBackend } from "./plugin-service.js";
|
|
9
9
|
function assertPermission(manifest, permission) {
|
|
10
10
|
if (!manifest.permissions.includes(permission))
|
|
11
11
|
throw new PluginBootstrapFailure("plugin_permission_denied", `Plugin ${manifest.id} did not declare permission ${permission}`);
|
|
@@ -61,12 +61,15 @@ export class InstalledPluginRuntimeV1 {
|
|
|
61
61
|
backend;
|
|
62
62
|
commands = new Map();
|
|
63
63
|
hooks = [];
|
|
64
|
+
backgroundRefreshHooks = [];
|
|
64
65
|
contextProviders = [];
|
|
66
|
+
mcpTools = [];
|
|
67
|
+
mcpStartupHooks = [];
|
|
65
68
|
loaded = false;
|
|
66
69
|
constructor(options) {
|
|
67
70
|
this.options = options;
|
|
68
71
|
this.store = options.store ?? new FilePluginInstallStore();
|
|
69
|
-
this.backend = options.backend ?? new
|
|
72
|
+
this.backend = options.backend ?? new AgentMemoryServiceBackend({ root: this.store.root });
|
|
70
73
|
}
|
|
71
74
|
async load() {
|
|
72
75
|
if (this.loaded)
|
|
@@ -133,6 +136,22 @@ export class InstalledPluginRuntimeV1 {
|
|
|
133
136
|
throw error;
|
|
134
137
|
}
|
|
135
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Fire all registered background-refresh hooks. Un-metered — intended for
|
|
141
|
+
* per-turn UserPromptSubmit invocation, keeping workers alive across
|
|
142
|
+
* `/clear` and idle without charging session quota. Failures propagate to
|
|
143
|
+
* the caller (which is expected to swallow them silently).
|
|
144
|
+
*/
|
|
145
|
+
async refreshBackgroundWorkers(context) {
|
|
146
|
+
if (!(await this.load()) || this.backgroundRefreshHooks.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
const entitlement = await this.refreshEntitlement();
|
|
149
|
+
for (const hook of this.backgroundRefreshHooks) {
|
|
150
|
+
if (!isPluginCapabilityEnabled(entitlement, hook.requiredCapability))
|
|
151
|
+
continue;
|
|
152
|
+
await hook.run(context);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
136
155
|
async provideContext(context) {
|
|
137
156
|
if (!(await this.load()) || this.contextProviders.length === 0)
|
|
138
157
|
return [];
|
|
@@ -164,6 +183,32 @@ export class InstalledPluginRuntimeV1 {
|
|
|
164
183
|
}
|
|
165
184
|
return sections;
|
|
166
185
|
}
|
|
186
|
+
getMcpTools() {
|
|
187
|
+
return [...this.mcpTools];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Invoke a registered MCP tool by name, re-checking entitlement on every
|
|
191
|
+
* call — matching `run()`'s behavior for commands. MCP tools are
|
|
192
|
+
* registered once at `serve --mcp` startup and the server process can
|
|
193
|
+
* live for a long session, so a tool's entitlement must be re-verified
|
|
194
|
+
* per-call rather than trusted from registration time (e.g. a trial
|
|
195
|
+
* expiring mid-session must actually stop the tool from working).
|
|
196
|
+
*/
|
|
197
|
+
async runMcpTool(name, input) {
|
|
198
|
+
if (!(await this.load()))
|
|
199
|
+
return { error: `Unknown MCP tool: ${name}` };
|
|
200
|
+
const tool = this.mcpTools.find((candidate) => candidate.name === name);
|
|
201
|
+
if (!tool)
|
|
202
|
+
return { error: `Unknown MCP tool: ${name}` };
|
|
203
|
+
const entitlement = await this.refreshEntitlement();
|
|
204
|
+
if (!isPluginCapabilityEnabled(entitlement, tool.requiredCapability))
|
|
205
|
+
return { error: `Capability ${tool.requiredCapability} is not enabled for the ${name} tool` };
|
|
206
|
+
return tool.run(input);
|
|
207
|
+
}
|
|
208
|
+
async runMcpStartup() {
|
|
209
|
+
for (const hook of this.mcpStartupHooks)
|
|
210
|
+
await hook();
|
|
211
|
+
}
|
|
167
212
|
createHost(manifest) {
|
|
168
213
|
const descriptors = new Map(manifest.commands.map((command) => [command.name, command]));
|
|
169
214
|
const stateRoot = path.join(this.store.root, "state");
|
|
@@ -197,6 +242,13 @@ export class InstalledPluginRuntimeV1 {
|
|
|
197
242
|
throw new PluginBootstrapFailure("plugin_hook_invalid", `Plugin ${manifest.id} registered a hook with an undeclared capability`);
|
|
198
243
|
this.hooks.push(hook);
|
|
199
244
|
},
|
|
245
|
+
registerBackgroundRefresh: (hook) => {
|
|
246
|
+
if (!(manifest.capabilities ?? []).includes(hook.requiredCapability))
|
|
247
|
+
throw new PluginBootstrapFailure("plugin_hook_invalid", `Plugin ${manifest.id} registered a background refresh hook with an undeclared capability`);
|
|
248
|
+
if (!hook.name || this.backgroundRefreshHooks.some((existing) => existing.name === hook.name))
|
|
249
|
+
throw new PluginBootstrapFailure("plugin_hook_invalid", `Plugin background refresh hook ${hook.name || "(unnamed)"} is invalid or already registered`);
|
|
250
|
+
this.backgroundRefreshHooks.push(hook);
|
|
251
|
+
},
|
|
200
252
|
registerContextProvider: (provider) => {
|
|
201
253
|
if (!(manifest.capabilities ?? []).includes(provider.requiredCapability))
|
|
202
254
|
throw new PluginBootstrapFailure("plugin_context_invalid", `Plugin ${manifest.id} registered a context provider with an undeclared capability`);
|
|
@@ -204,6 +256,16 @@ export class InstalledPluginRuntimeV1 {
|
|
|
204
256
|
throw new PluginBootstrapFailure("plugin_context_invalid", `Plugin context provider ${provider.name || "(unnamed)"} is invalid or already registered`);
|
|
205
257
|
this.contextProviders.push({ provider, pluginId: manifest.id });
|
|
206
258
|
},
|
|
259
|
+
registerMcpTool: (tool) => {
|
|
260
|
+
if (!(manifest.capabilities ?? []).includes(tool.requiredCapability))
|
|
261
|
+
throw new PluginBootstrapFailure("plugin_mcp_tool_invalid", `Plugin ${manifest.id} registered an MCP tool with an undeclared capability`);
|
|
262
|
+
if (!tool.name || this.mcpTools.some((existing) => existing.name === tool.name))
|
|
263
|
+
throw new PluginBootstrapFailure("plugin_mcp_tool_invalid", `Plugin MCP tool ${tool.name || "(unnamed)"} is invalid or already registered`);
|
|
264
|
+
this.mcpTools.push(tool);
|
|
265
|
+
},
|
|
266
|
+
registerMcpStartup: (fn) => {
|
|
267
|
+
this.mcpStartupHooks.push(fn);
|
|
268
|
+
},
|
|
207
269
|
getStateDirectory: () => stateDirectory,
|
|
208
270
|
getMemoryDirectory: () => {
|
|
209
271
|
assertPermission(manifest, "memory:read");
|
|
@@ -254,6 +316,7 @@ export function createInstalledBundleHealthCheck(coreVersion, backend, storeRoot
|
|
|
254
316
|
coreVersion,
|
|
255
317
|
registerCommand() { },
|
|
256
318
|
registerSessionStartHook() { },
|
|
319
|
+
registerBackgroundRefresh() { },
|
|
257
320
|
getStateDirectory: () => stateDirectory,
|
|
258
321
|
getMemoryDirectory: () => {
|
|
259
322
|
assertPermission(plugin.manifest, "memory:read");
|
package/dist/plugin-service.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PluginAccessDecisionV1, type PluginBootstrapBackendV1, type PluginNextActionV1, type PluginSessionUsageDecisionV1, type SignedPluginReleaseV1 } from "./plugin-bootstrap.js";
|
|
2
2
|
import { type PluginEntitlementStatusV1 } from "./plugin-host.js";
|
|
3
|
-
interface
|
|
3
|
+
interface AgentMemoryServiceBackendOptions {
|
|
4
4
|
root?: string;
|
|
5
5
|
coreVersion?: string;
|
|
6
6
|
apiOrigin?: string;
|
|
@@ -10,8 +10,8 @@ interface TemporaryPluginBackendOptions {
|
|
|
10
10
|
activate?: () => Promise<string>;
|
|
11
11
|
}
|
|
12
12
|
export declare function openLoopbackUrl(url: string): boolean;
|
|
13
|
-
export declare function
|
|
14
|
-
export declare class
|
|
13
|
+
export declare function collectActivation(openUrl?: (url: string) => boolean): Promise<string>;
|
|
14
|
+
export declare class AgentMemoryServiceBackend implements PluginBootstrapBackendV1 {
|
|
15
15
|
private readonly root;
|
|
16
16
|
private readonly coreVersion;
|
|
17
17
|
private readonly apiOrigin;
|
|
@@ -19,7 +19,7 @@ export declare class TemporaryPluginBackend implements PluginBootstrapBackendV1
|
|
|
19
19
|
private readonly fetchImplementation;
|
|
20
20
|
private readonly openUrl;
|
|
21
21
|
private readonly activate;
|
|
22
|
-
constructor(options?:
|
|
22
|
+
constructor(options?: AgentMemoryServiceBackendOptions);
|
|
23
23
|
getLocalEntitlement(): Promise<PluginEntitlementStatusV1>;
|
|
24
24
|
resolveAccess(request: {
|
|
25
25
|
bundleId: string;
|
|
@@ -46,4 +46,10 @@ export declare class TemporaryPluginBackend implements PluginBootstrapBackendV1
|
|
|
46
46
|
private sessionUsage;
|
|
47
47
|
private request;
|
|
48
48
|
}
|
|
49
|
+
export declare class TemporaryPluginBackend extends AgentMemoryServiceBackend {
|
|
50
|
+
constructor(options: {
|
|
51
|
+
root: string;
|
|
52
|
+
coreVersion: string;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
49
55
|
export {};
|
package/dist/plugin-service.js
CHANGED
|
@@ -7,7 +7,8 @@ import { getDefaultPluginInstallRoot, PluginBootstrapFailure, } from "./plugin-b
|
|
|
7
7
|
import { validatePluginEntitlementStatusV1 } from "./plugin-host.js";
|
|
8
8
|
const API_ORIGIN = "https://api.agentmemory.paperpilot.me";
|
|
9
9
|
const ARTIFACT_ORIGIN = "https://plugins.agentmemory.paperpilot.me";
|
|
10
|
-
const ACTIVATION_FILE = "credentials/
|
|
10
|
+
const ACTIVATION_FILE = "credentials/activation.json";
|
|
11
|
+
const ACTIVATION_FILE_LEGACY = "credentials/temporary-access.json";
|
|
11
12
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
12
13
|
const EMAIL_MAX_BYTES = 254;
|
|
13
14
|
const FORM_MAX_BYTES = 2_048;
|
|
@@ -30,15 +31,33 @@ function freeEntitlement() {
|
|
|
30
31
|
features: ["session-intelligence", "web-console"],
|
|
31
32
|
capabilities: {
|
|
32
33
|
"session-index": { enabled: true },
|
|
33
|
-
recall: { enabled: true, quota: { limit:
|
|
34
|
+
recall: { enabled: true, quota: { limit: 20, window: "day", scope: "device" } },
|
|
34
35
|
"session-worker": { enabled: false },
|
|
35
|
-
learning: { enabled: true, quota: { limit:
|
|
36
|
+
learning: { enabled: true, quota: { limit: 5, window: "day", scope: "device" } },
|
|
36
37
|
"retrieval-evaluation": { enabled: true },
|
|
37
38
|
"operational-metrics": { enabled: true },
|
|
38
39
|
"web-console": { enabled: true },
|
|
39
40
|
"memory-explorer": { enabled: true },
|
|
40
41
|
},
|
|
41
|
-
reason: "
|
|
42
|
+
reason: "Included at no cost: 20 recalls and 5 learning scans per local day; local indexing and dashboard access remain available",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function devEntitlement() {
|
|
46
|
+
return {
|
|
47
|
+
plan: "pro",
|
|
48
|
+
state: "active",
|
|
49
|
+
features: ["session-intelligence", "web-console"],
|
|
50
|
+
capabilities: {
|
|
51
|
+
"session-index": { enabled: true },
|
|
52
|
+
recall: { enabled: true },
|
|
53
|
+
"session-worker": { enabled: true },
|
|
54
|
+
learning: { enabled: true },
|
|
55
|
+
"retrieval-evaluation": { enabled: true },
|
|
56
|
+
"operational-metrics": { enabled: true },
|
|
57
|
+
"web-console": { enabled: true },
|
|
58
|
+
"memory-explorer": { enabled: true },
|
|
59
|
+
},
|
|
60
|
+
reason: "Dev entitlement: all capabilities enabled, no quotas (AGENT_MEMORY_DEV_ENTITLEMENT=1)",
|
|
42
61
|
};
|
|
43
62
|
}
|
|
44
63
|
function isEmail(value) {
|
|
@@ -253,7 +272,7 @@ export function openLoopbackUrl(url) {
|
|
|
253
272
|
return false;
|
|
254
273
|
}
|
|
255
274
|
}
|
|
256
|
-
export async function
|
|
275
|
+
export async function collectActivation(openUrl = openLoopbackUrl) {
|
|
257
276
|
const nonce = randomBytes(24).toString("hex");
|
|
258
277
|
const activationPath = `/activate/${nonce}`;
|
|
259
278
|
let expectedHost = "";
|
|
@@ -317,7 +336,7 @@ export async function collectTemporaryActivation(openUrl = openLoopbackUrl) {
|
|
|
317
336
|
throw new PluginBootstrapFailure("browser_unavailable", `Open this URL in a browser: ${url}`);
|
|
318
337
|
}
|
|
319
338
|
const timer = setTimeout(() => {
|
|
320
|
-
fail?.(new PluginBootstrapFailure("activation_timeout", "
|
|
339
|
+
fail?.(new PluginBootstrapFailure("activation_timeout", "Browser activation timed out", true));
|
|
321
340
|
server.close();
|
|
322
341
|
}, 5 * 60_000);
|
|
323
342
|
timer.unref();
|
|
@@ -329,7 +348,7 @@ export async function collectTemporaryActivation(openUrl = openLoopbackUrl) {
|
|
|
329
348
|
server.close();
|
|
330
349
|
}
|
|
331
350
|
}
|
|
332
|
-
export class
|
|
351
|
+
export class AgentMemoryServiceBackend {
|
|
333
352
|
root;
|
|
334
353
|
coreVersion;
|
|
335
354
|
apiOrigin;
|
|
@@ -348,7 +367,11 @@ export class TemporaryPluginBackend {
|
|
|
348
367
|
}
|
|
349
368
|
async getLocalEntitlement() {
|
|
350
369
|
const activation = this.readActivation();
|
|
351
|
-
|
|
370
|
+
if (!activation)
|
|
371
|
+
return cloneEntitlement(MISSING_ENTITLEMENT);
|
|
372
|
+
if (process.env.AGENT_MEMORY_DEV_ENTITLEMENT === "1")
|
|
373
|
+
return devEntitlement();
|
|
374
|
+
return freeEntitlement();
|
|
352
375
|
}
|
|
353
376
|
async resolveAccess(request) {
|
|
354
377
|
const activation = this.readActivation();
|
|
@@ -377,13 +400,18 @@ export class TemporaryPluginBackend {
|
|
|
377
400
|
const learningQuota = value.entitlement.capabilities.learning?.quota;
|
|
378
401
|
if (value.entitlement.plan !== "free" ||
|
|
379
402
|
value.entitlement.state !== "active" ||
|
|
403
|
+
value.entitlement.capabilities.recall?.enabled !== true ||
|
|
380
404
|
!recallQuota ||
|
|
405
|
+
recallQuota.limit !== 20 ||
|
|
381
406
|
recallQuota.scope !== "device" ||
|
|
382
407
|
recallQuota.window !== "day" ||
|
|
408
|
+
value.entitlement.capabilities.learning?.enabled !== true ||
|
|
383
409
|
!learningQuota ||
|
|
410
|
+
learningQuota.limit !== 5 ||
|
|
384
411
|
learningQuota.scope !== "device" ||
|
|
385
412
|
learningQuota.window !== "day" ||
|
|
386
413
|
value.entitlement.capabilities["session-index"]?.enabled !== true ||
|
|
414
|
+
value.entitlement.capabilities["session-worker"]?.enabled !== false ||
|
|
387
415
|
value.entitlement.capabilities["web-console"]?.enabled !== true)
|
|
388
416
|
throw new PluginBootstrapFailure("service_response_invalid", "The free preview policy is invalid");
|
|
389
417
|
this.writeActivation(installationId, value.usageCredential, 1);
|
|
@@ -427,6 +455,17 @@ export class TemporaryPluginBackend {
|
|
|
427
455
|
}
|
|
428
456
|
readActivation() {
|
|
429
457
|
const activationPath = this.activationPath();
|
|
458
|
+
if (!fs.existsSync(activationPath)) {
|
|
459
|
+
const legacyPath = path.join(this.root, ...ACTIVATION_FILE_LEGACY.split("/"));
|
|
460
|
+
if (fs.existsSync(legacyPath)) {
|
|
461
|
+
try {
|
|
462
|
+
fs.renameSync(legacyPath, activationPath);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
/* best-effort migration */
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
430
469
|
if (!fs.existsSync(activationPath))
|
|
431
470
|
return null;
|
|
432
471
|
try {
|
|
@@ -542,3 +581,8 @@ export class TemporaryPluginBackend {
|
|
|
542
581
|
return response;
|
|
543
582
|
}
|
|
544
583
|
}
|
|
584
|
+
export class TemporaryPluginBackend extends AgentMemoryServiceBackend {
|
|
585
|
+
constructor(options) {
|
|
586
|
+
super({ ...options, openUrl: () => false });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upgrade orchestration for the `agent-memory` CLI and its official Pro plugin bundle.
|
|
3
|
+
*
|
|
4
|
+
* Two consumers:
|
|
5
|
+
* 1. `agent-memory upgrade` — explicit user command; checks and (optionally) installs.
|
|
6
|
+
* 2. `agent-memory hook session-start` — passive notice from a 24h-cached record.
|
|
7
|
+
*
|
|
8
|
+
* Network calls always have a hard timeout and always fail closed (upgrade is a
|
|
9
|
+
* quality-of-life feature; a flaky registry must never break the CLI).
|
|
10
|
+
*/
|
|
11
|
+
import { type SpawnOptions } from "node:child_process";
|
|
12
|
+
export type InstallManager = "bun" | "npm" | "pnpm" | "yarn" | "unknown";
|
|
13
|
+
export interface InstallMethod {
|
|
14
|
+
manager: InstallManager;
|
|
15
|
+
global: boolean;
|
|
16
|
+
/** Absolute path we think holds the current install (for diagnostics). */
|
|
17
|
+
origin: string;
|
|
18
|
+
/** Argv used to invoke the package manager (e.g. ["npm","i","-g","myagentmemory@latest"]). */
|
|
19
|
+
command: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface UpgradeCache {
|
|
22
|
+
checkedAt: string;
|
|
23
|
+
cliCurrent: string;
|
|
24
|
+
cliLatest: string | null;
|
|
25
|
+
pluginCurrent: string | null;
|
|
26
|
+
pluginLatest: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface UpgradeStatus {
|
|
29
|
+
cli: {
|
|
30
|
+
current: string;
|
|
31
|
+
latest: string | null;
|
|
32
|
+
upgradeAvailable: boolean;
|
|
33
|
+
};
|
|
34
|
+
plugin: {
|
|
35
|
+
current: string | null;
|
|
36
|
+
latest: string | null;
|
|
37
|
+
upgradeAvailable: boolean;
|
|
38
|
+
};
|
|
39
|
+
checkedAt: string;
|
|
40
|
+
fromCache: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function readUpgradeCache(): UpgradeCache | null;
|
|
43
|
+
export declare function writeUpgradeCache(record: UpgradeCache): void;
|
|
44
|
+
export declare function isCacheFresh(record: UpgradeCache | null, now?: number): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Best-effort detection of how `myagentmemory` was installed. Path signatures
|
|
47
|
+
* are heuristic but cover the common managers. On no match we fall back to
|
|
48
|
+
* `npm -g` per the user's choice ("best-effort try anyway").
|
|
49
|
+
*/
|
|
50
|
+
export declare function detectInstallMethod(location?: string): InstallMethod;
|
|
51
|
+
export interface InstallResult {
|
|
52
|
+
ok: boolean;
|
|
53
|
+
code: number | null;
|
|
54
|
+
stdout: string;
|
|
55
|
+
stderr: string;
|
|
56
|
+
command: string[];
|
|
57
|
+
}
|
|
58
|
+
export declare function runInstaller(method: InstallMethod, opts?: SpawnOptions): InstallResult;
|
|
59
|
+
/**
|
|
60
|
+
* Fire-and-forget: spawn a detached child that runs `agent-memory upgrade
|
|
61
|
+
* --check --refresh --quiet` so the next session-start has a fresh cache.
|
|
62
|
+
* Never awaits, never throws.
|
|
63
|
+
*/
|
|
64
|
+
export declare function refreshUpgradeCacheBackground(): void;
|
|
65
|
+
export interface CheckOptions {
|
|
66
|
+
cliCurrent: string;
|
|
67
|
+
pluginCurrent: string | null;
|
|
68
|
+
/** When true, do NOT hit the network — read cache only. Returns fromCache=true even on miss. */
|
|
69
|
+
cacheOnly?: boolean;
|
|
70
|
+
/** When true, force a network refresh and rewrite the cache regardless of freshness. */
|
|
71
|
+
refresh?: boolean;
|
|
72
|
+
/** Optional injected npm fetcher (used by tests). */
|
|
73
|
+
fetchCliLatest?: () => Promise<string | null>;
|
|
74
|
+
/** Optional injected plugin-latest resolver. When omitted, plugin latest is taken from pluginLatestHint. */
|
|
75
|
+
pluginLatestHint?: string | null;
|
|
76
|
+
/** Explicit signal from the bootstrap that a newer release exists even when the version number is unknown. */
|
|
77
|
+
pluginUpgradeAvailable?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export declare function checkForUpgrades(opts: CheckOptions): Promise<UpgradeStatus>;
|
|
80
|
+
export declare function formatUpgradeNotice(status: UpgradeStatus): string | null;
|