myagentmemory 0.4.16 → 0.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -131,11 +131,31 @@ export interface PluginStructuredErrorV1 {
131
131
  retryable?: boolean;
132
132
  }
133
133
 
134
+ export interface PluginContextSectionV1 {
135
+ id: string;
136
+ label: string;
137
+ content: string;
138
+ artifactPath?: string;
139
+ metadata?: Record<string, unknown>;
140
+ }
141
+
142
+ export interface PluginContextProviderV1 {
143
+ name: string;
144
+ requiredCapability: string;
145
+ provide(context: {
146
+ host: string;
147
+ cwd?: string;
148
+ query?: string;
149
+ signal: AbortSignal;
150
+ }): Promise<PluginContextSectionV1[]>;
151
+ }
152
+
134
153
  export interface AgentMemoryPluginHostV1 {
135
154
  apiVersion: 1;
136
155
  coreVersion: string;
137
156
  registerCommand(command: PluginCommandV1): void;
138
157
  registerSessionStartHook(hook: PluginSessionStartHookV1): void;
158
+ registerContextProvider?(provider: PluginContextProviderV1): void;
139
159
  getStateDirectory(): string;
140
160
  getMemoryDirectory(): string;
141
161
  getEntitlement(): Promise<PluginEntitlementStatusV1>;
@@ -23,6 +23,8 @@ import {
23
23
  type PluginCommandContextV1,
24
24
  type PluginCommandResultV1,
25
25
  type PluginCommandV1,
26
+ type PluginContextProviderV1,
27
+ type PluginContextSectionV1,
26
28
  type PluginEntitlementStatusV1,
27
29
  type PluginMemoryCorrectionV1,
28
30
  type PluginMemoryWriteV1,
@@ -38,6 +40,11 @@ interface RegisteredCommand {
38
40
  pluginId: string;
39
41
  }
40
42
 
43
+ interface RegisteredContextProvider {
44
+ provider: PluginContextProviderV1;
45
+ pluginId: string;
46
+ }
47
+
41
48
  export interface PluginRuntimeOptionsV1 {
42
49
  coreVersion: string;
43
50
  store?: PluginInstallStoreV1;
@@ -130,6 +137,7 @@ export class InstalledPluginRuntimeV1 {
130
137
  private readonly backend: PluginBootstrapBackendV1;
131
138
  private readonly commands = new Map<string, RegisteredCommand>();
132
139
  private readonly hooks: PluginSessionStartHookV1[] = [];
140
+ private readonly contextProviders: RegisteredContextProvider[] = [];
133
141
  private loaded = false;
134
142
 
135
143
  constructor(private readonly options: PluginRuntimeOptionsV1) {
@@ -202,6 +210,49 @@ export class InstalledPluginRuntimeV1 {
202
210
  }
203
211
  }
204
212
 
213
+ async provideContext(context: {
214
+ host: string;
215
+ cwd?: string;
216
+ query?: string;
217
+ signal: AbortSignal;
218
+ }): Promise<PluginContextSectionV1[]> {
219
+ if (!(await this.load()) || this.contextProviders.length === 0) return [];
220
+ const entitlement = await this.refreshEntitlement();
221
+ const sections: PluginContextSectionV1[] = [];
222
+ for (const registered of this.contextProviders) {
223
+ if (!isPluginCapabilityEnabled(entitlement, registered.provider.requiredCapability)) continue;
224
+ const provided = await registered.provider.provide(context);
225
+ if (!Array.isArray(provided) || provided.length > 16)
226
+ throw new PluginBootstrapFailure(
227
+ "plugin_context_invalid",
228
+ `Plugin ${registered.pluginId} returned invalid context sections`,
229
+ );
230
+ for (const section of provided) {
231
+ if (
232
+ !section ||
233
+ typeof section.id !== "string" ||
234
+ section.id.length === 0 ||
235
+ section.id.length > 256 ||
236
+ typeof section.label !== "string" ||
237
+ section.label.length === 0 ||
238
+ section.label.length > 256 ||
239
+ typeof section.content !== "string" ||
240
+ Buffer.byteLength(section.content, "utf-8") > 64 * 1024 ||
241
+ (section.artifactPath !== undefined &&
242
+ (typeof section.artifactPath !== "string" || section.artifactPath.length > 4_096)) ||
243
+ (section.metadata !== undefined &&
244
+ (!section.metadata || typeof section.metadata !== "object" || Array.isArray(section.metadata)))
245
+ )
246
+ throw new PluginBootstrapFailure(
247
+ "plugin_context_invalid",
248
+ `Plugin ${registered.pluginId} returned an invalid context section`,
249
+ );
250
+ sections.push(structuredClone(section));
251
+ }
252
+ }
253
+ return sections;
254
+ }
255
+
205
256
  private createHost(manifest: AgentMemoryPluginManifestV1): AgentMemoryPluginHostV1 {
206
257
  const descriptors = new Map(manifest.commands.map((command) => [command.name, command]));
207
258
  const stateRoot = path.join(this.store.root, "state");
@@ -242,6 +293,19 @@ export class InstalledPluginRuntimeV1 {
242
293
  );
243
294
  this.hooks.push(hook);
244
295
  },
296
+ registerContextProvider: (provider) => {
297
+ if (!(manifest.capabilities ?? []).includes(provider.requiredCapability))
298
+ throw new PluginBootstrapFailure(
299
+ "plugin_context_invalid",
300
+ `Plugin ${manifest.id} registered a context provider with an undeclared capability`,
301
+ );
302
+ if (!provider.name || this.contextProviders.some((item) => item.provider.name === provider.name))
303
+ throw new PluginBootstrapFailure(
304
+ "plugin_context_invalid",
305
+ `Plugin context provider ${provider.name || "(unnamed)"} is invalid or already registered`,
306
+ );
307
+ this.contextProviders.push({ provider, pluginId: manifest.id });
308
+ },
245
309
  getStateDirectory: () => stateDirectory,
246
310
  getMemoryDirectory: () => {
247
311
  assertPermission(manifest, "memory:read");
@@ -29,12 +29,12 @@ const MISSING_ENTITLEMENT: PluginEntitlementStatusV1 = {
29
29
  state: "missing",
30
30
  features: [],
31
31
  capabilities: {},
32
- reason: "Enter an email address to activate the free daily session allowance",
32
+ reason: "Install the no-account Pro preview to activate local recall and learning",
33
33
  };
34
34
 
35
35
  interface TemporaryActivationV1 {
36
- schemaVersion: 2;
37
- email: string;
36
+ schemaVersion: 3;
37
+ installationId: string;
38
38
  activatedAt: string;
39
39
  usageCredential: string;
40
40
  dailySessionLimit: number;
@@ -54,24 +54,23 @@ function cloneEntitlement(value: PluginEntitlementStatusV1): PluginEntitlementSt
54
54
  return structuredClone(value);
55
55
  }
56
56
 
57
- function freeEntitlement(dailySessionLimit: number): PluginEntitlementStatusV1 {
57
+ function freeEntitlement(): PluginEntitlementStatusV1 {
58
58
  return {
59
59
  plan: "free",
60
60
  state: "active",
61
61
  features: ["session-intelligence", "web-console"],
62
62
  capabilities: {
63
63
  "session-index": { enabled: true },
64
- "session-worker": {
65
- enabled: true,
66
- quota: { limit: dailySessionLimit, window: "day", scope: "account" },
67
- },
68
- learning: { enabled: true },
64
+ recall: { enabled: true, quota: { limit: 10, window: "day", scope: "device" } },
65
+ "session-worker": { enabled: false },
66
+ learning: { enabled: true, quota: { limit: 1, window: "day", scope: "device" } },
69
67
  "retrieval-evaluation": { enabled: true },
70
68
  "operational-metrics": { enabled: true },
71
69
  "web-console": { enabled: true },
72
70
  "memory-explorer": { enabled: true },
73
71
  },
74
- reason: `${dailySessionLimit} free agent sessions per UTC day`,
72
+ reason:
73
+ "Free preview: 10 recalls and 1 learning scan per local day; indexing and dashboard access remain available",
75
74
  };
76
75
  }
77
76
 
@@ -388,12 +387,12 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
388
387
  this.artifactOrigin = options.artifactOrigin ?? ARTIFACT_ORIGIN;
389
388
  this.fetchImplementation = options.fetch ?? globalThis.fetch;
390
389
  this.openUrl = options.openUrl ?? openLoopbackUrl;
391
- this.activate = options.activate ?? (() => collectTemporaryActivation(this.openUrl));
390
+ this.activate = options.activate ?? (async () => `am_install_${randomBytes(24).toString("base64url")}`);
392
391
  }
393
392
 
394
393
  async getLocalEntitlement(): Promise<PluginEntitlementStatusV1> {
395
394
  const activation = this.readActivation();
396
- return activation ? freeEntitlement(activation.dailySessionLimit) : cloneEntitlement(MISSING_ENTITLEMENT);
395
+ return activation ? freeEntitlement() : cloneEntitlement(MISSING_ENTITLEMENT);
397
396
  }
398
397
 
399
398
  async resolveAccess(request: {
@@ -403,33 +402,19 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
403
402
  allowAuthentication: boolean;
404
403
  }): Promise<PluginAccessDecisionV1> {
405
404
  const activation = this.readActivation();
406
- let email = activation?.email;
407
- if (!email) {
408
- if (!request.allowAuthentication)
409
- return {
410
- kind: "auth_required",
411
- entitlement: cloneEntitlement(MISSING_ENTITLEMENT),
412
- nextAction: {
413
- kind: "authenticate",
414
- url: "https://jayzeng.github.io/agentmemory/",
415
- message: "Run plugin install in an interactive terminal to enter an email address",
416
- },
417
- };
418
- email = await this.activate();
419
- }
405
+ const installationId = activation?.installationId ?? (await this.activate());
420
406
  const response = await this.request(`${this.apiOrigin}/v1/plugin/access`, {
421
407
  method: "POST",
422
408
  headers: { "Content-Type": "application/json" },
423
409
  body: JSON.stringify({
424
- schemaVersion: 1,
425
- email,
410
+ schemaVersion: 2,
411
+ installationId,
426
412
  bundleId: request.bundleId,
427
413
  installedVersion: request.installedVersion ?? null,
428
414
  coreVersion: this.coreVersion,
429
415
  channel: request.channel,
430
416
  platform: process.platform,
431
417
  architecture: process.arch,
432
- consentVersion: "activation-v2",
433
418
  }),
434
419
  });
435
420
  const value = (await readJson(response)) as {
@@ -445,16 +430,22 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
445
430
  "service_response_invalid",
446
431
  "The access response omitted its usage credential",
447
432
  );
448
- const freeQuota = value.entitlement.capabilities["session-worker"]?.quota;
433
+ const recallQuota = value.entitlement.capabilities.recall?.quota;
434
+ const learningQuota = value.entitlement.capabilities.learning?.quota;
449
435
  if (
450
436
  value.entitlement.plan !== "free" ||
451
437
  value.entitlement.state !== "active" ||
452
- !freeQuota ||
453
- freeQuota.scope !== "account" ||
454
- freeQuota.window !== "day"
438
+ !recallQuota ||
439
+ recallQuota.scope !== "device" ||
440
+ recallQuota.window !== "day" ||
441
+ !learningQuota ||
442
+ learningQuota.scope !== "device" ||
443
+ learningQuota.window !== "day" ||
444
+ value.entitlement.capabilities["session-index"]?.enabled !== true ||
445
+ value.entitlement.capabilities["web-console"]?.enabled !== true
455
446
  )
456
- throw new PluginBootstrapFailure("service_response_invalid", "The free session policy is invalid");
457
- this.writeActivation(email, value.usageCredential, freeQuota.limit);
447
+ throw new PluginBootstrapFailure("service_response_invalid", "The free preview policy is invalid");
448
+ this.writeActivation(installationId, value.usageCredential, 1);
458
449
  return { kind: "granted", entitlement: value.entitlement, artifactGrant: value.artifactGrant };
459
450
  }
460
451
 
@@ -523,8 +514,9 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
523
514
  return null;
524
515
  const value = JSON.parse(fs.readFileSync(activationPath, "utf-8")) as TemporaryActivationV1;
525
516
  if (
526
- value.schemaVersion !== 2 ||
527
- !isEmail(value.email) ||
517
+ value.schemaVersion !== 3 ||
518
+ typeof value.installationId !== "string" ||
519
+ !/^am_install_[A-Za-z0-9_-]{32}$/.test(value.installationId) ||
528
520
  !Number.isFinite(Date.parse(value.activatedAt)) ||
529
521
  !ACTIVATION_CREDENTIAL.test(value.usageCredential) ||
530
522
  !Number.isSafeInteger(value.dailySessionLimit) ||
@@ -538,8 +530,9 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
538
530
  }
539
531
  }
540
532
 
541
- private writeActivation(email: string, usageCredential: string, dailySessionLimit: number): void {
542
- if (!isEmail(email)) throw new PluginBootstrapFailure("email_invalid", "Enter a valid email address");
533
+ private writeActivation(installationId: string, usageCredential: string, dailySessionLimit: number): void {
534
+ if (!/^am_install_[A-Za-z0-9_-]{32}$/.test(installationId))
535
+ throw new PluginBootstrapFailure("activation_failed", "The installation identifier is invalid");
543
536
  if (!ACTIVATION_CREDENTIAL.test(usageCredential))
544
537
  throw new PluginBootstrapFailure("activation_failed", "The activation credential is invalid");
545
538
  if (!Number.isSafeInteger(dailySessionLimit) || dailySessionLimit <= 0 || dailySessionLimit > 10_000)
@@ -558,7 +551,13 @@ export class TemporaryPluginBackend implements PluginBootstrapBackendV1 {
558
551
  fs.writeFileSync(
559
552
  temporary,
560
553
  `${JSON.stringify(
561
- { schemaVersion: 2, email, activatedAt: new Date().toISOString(), usageCredential, dailySessionLimit },
554
+ {
555
+ schemaVersion: 3,
556
+ installationId,
557
+ activatedAt: new Date().toISOString(),
558
+ usageCredential,
559
+ dailySessionLimit,
560
+ },
562
561
  null,
563
562
  2,
564
563
  )}\n`,