om-memory-system 3.5.0 → 3.6.0-next.38

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.
Files changed (54) hide show
  1. package/README.md +24 -16
  2. package/dist/adapters/claude-code/hook-client.d.ts +73 -0
  3. package/dist/adapters/claude-code/hook-client.js +285 -0
  4. package/dist/adapters/claude-code/hook-command.d.ts +6 -0
  5. package/dist/adapters/claude-code/hook-command.js +25 -0
  6. package/dist/cli/index.js +19 -3
  7. package/dist/cli/memory-command.d.ts +24 -0
  8. package/dist/cli/memory-command.js +180 -0
  9. package/dist/core/retrieval.d.ts +21 -0
  10. package/dist/core/retrieval.js +35 -0
  11. package/dist/importer/backfill-controls.d.ts +10 -0
  12. package/dist/importer/backfill-controls.js +40 -11
  13. package/dist/importer/backfill-model.d.ts +1 -1
  14. package/dist/importer/backfill-model.js +3 -0
  15. package/dist/importer/claude-conversation.d.ts +74 -0
  16. package/dist/importer/claude-conversation.js +216 -0
  17. package/dist/importer/claude-hook-api.d.ts +50 -0
  18. package/dist/importer/claude-hook-api.js +441 -0
  19. package/dist/importer/claude-import.d.ts +15 -0
  20. package/dist/importer/claude-import.js +62 -0
  21. package/dist/importer/claude-reader.d.ts +99 -0
  22. package/dist/importer/claude-reader.js +292 -0
  23. package/dist/importer/external-backfill-models.js +2 -2
  24. package/dist/importer/import-args.d.ts +1 -1
  25. package/dist/importer/import-args.js +11 -4
  26. package/dist/importer/import-readiness.d.ts +13 -1
  27. package/dist/importer/import-readiness.js +19 -0
  28. package/dist/importer/import-runs.js +1 -1
  29. package/dist/importer/import-sessions.d.ts +4 -1
  30. package/dist/importer/import-sessions.js +14 -2
  31. package/dist/importer/import-sources.d.ts +8 -2
  32. package/dist/importer/import-sources.js +21 -4
  33. package/dist/importer/map-suggestions.d.ts +1 -0
  34. package/dist/importer/map-suggestions.js +6 -1
  35. package/dist/importer/run-import.js +14 -10
  36. package/dist/importer/web-import-jobs.js +10 -3
  37. package/dist/index.js +9 -23
  38. package/dist/services/ai/live-model-choice.d.ts +5 -0
  39. package/dist/services/ai/live-model-choice.js +7 -0
  40. package/dist/services/backfill-state.d.ts +1 -1
  41. package/dist/services/capture-retry-queue.js +3 -2
  42. package/dist/services/claude-capture-cursor.d.ts +10 -0
  43. package/dist/services/claude-capture-cursor.js +44 -0
  44. package/dist/services/settings-snapshot.d.ts +1 -0
  45. package/dist/services/settings-snapshot.js +3 -1
  46. package/dist/services/web-server.d.ts +2 -0
  47. package/dist/services/web-server.js +61 -28
  48. package/dist/types/host-label.d.ts +3 -0
  49. package/dist/types/host-label.js +9 -0
  50. package/dist/types/index.d.ts +1 -1
  51. package/dist/web/assets/index-BFiVixOK.js +124 -0
  52. package/dist/web/index.html +1 -1
  53. package/package.json +1 -1
  54. package/dist/web/assets/index-DIy--9g_.js +0 -124
@@ -33,8 +33,9 @@ export function validateWebImportRequest(value) {
33
33
  if (!value || typeof value !== "object" || Array.isArray(value))
34
34
  throw new Error("Invalid import request");
35
35
  const input = value;
36
- if (input.host !== "pi" && input.host !== "opencode")
37
- throw new Error("Choose Pi or OpenCode");
36
+ if (input.host !== "pi" && input.host !== "opencode" && input.host !== "claude-code") {
37
+ throw new Error("Choose Pi, OpenCode, or Claude Code");
38
+ }
38
39
  if (!input.options || typeof input.options !== "object" || Array.isArray(input.options))
39
40
  throw new Error("Invalid import options");
40
41
  const options = input.options;
@@ -72,12 +73,18 @@ export function validateWebImportRequest(value) {
72
73
  (typeof input.modelChoice !== "string" ||
73
74
  (input.modelChoice !== "external" && !/^[^/]+\/.+$/.test(input.modelChoice))))
74
75
  throw new Error("Choose a signed-in provider/model or the external API");
76
+ // Claude Code has no session model: its imports always use the external API.
77
+ if (input.host === "claude-code" &&
78
+ input.modelChoice !== undefined &&
79
+ input.modelChoice !== "external")
80
+ throw new Error("Claude Code imports use the external API");
81
+ const modelChoice = input.host === "claude-code" ? "external" : input.modelChoice;
75
82
  return {
76
83
  host: input.host,
77
84
  source: input.source,
78
85
  selection: validateImportSelection(input.selection),
79
86
  options: options,
80
- modelChoice: input.modelChoice,
87
+ modelChoice,
81
88
  };
82
89
  }
83
90
  /** One in-memory slot per web server, shared by previews and imports. */
package/dist/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import { memoryClient } from "./services/client.js";
3
- import { formatContextForPrompt } from "./services/context.js";
4
3
  import { getTags } from "./services/tags.js";
5
4
  import { performAutoCapture } from "./services/auto-capture.js";
6
5
  import { createOpenCodeAutoCaptureHost } from "./adapters/opencode/auto-capture-host.js";
7
6
  import { isInternalPrompt, isStructuredSummaryPromptMessage, recordUserPrompt, } from "./adapters/opencode/user-prompt.js";
8
7
  import { executeMemoryOperation } from "./core/memory-operations.js";
9
8
  import { OPENCODE_IMPORT_COMMAND, OPENCODE_IMPORT_DESCRIPTION, latestUserMessageModel, runOpencodeImportCommand, } from "./adapters/opencode/import-command.js";
10
- import { formatMemoriesForCompaction } from "./core/retrieval.js";
9
+ import { buildRecentMemoriesSection, formatMemoriesForCompaction } from "./core/retrieval.js";
11
10
  import { performUserProfileLearning } from "./adapters/opencode/profile-learning.js";
12
11
  import { userPromptManager } from "./services/user-prompt/user-prompt-manager.js";
13
12
  import { startWebServer, WebServer } from "./services/web-server.js";
@@ -489,27 +488,14 @@ export const OmmsPlugin = async (ctx) => {
489
488
  !m.parts.every((p) => p.type !== "text" || p.synthetic === true)).length === 1);
490
489
  if (!shouldInject)
491
490
  return;
492
- const listResult = await memoryClient.listMemories(tags.project.tag, CONFIG.chatMessage.maxMemories);
493
- let memories = listResult.success ? listResult.memories : [];
494
- if (CONFIG.chatMessage.excludeCurrentSession) {
495
- memories = memories.filter((m) => m.metadata?.sessionID !== input.sessionID);
496
- }
497
- if (CONFIG.chatMessage.maxAgeDays) {
498
- const cutoffDate = Date.now() - CONFIG.chatMessage.maxAgeDays * 86400000;
499
- memories = memories.filter((m) => new Date(m.createdAt).getTime() > cutoffDate);
500
- }
501
- if (memories.length === 0)
502
- return;
503
- const projectMemories = {
504
- results: memories.map((m) => ({
505
- similarity: 1.0,
506
- memory: m.summary,
507
- })),
508
- total: memories.length,
509
- timing: 0,
510
- };
511
- const userId = tags.user.userEmail || null;
512
- const memoryContext = await formatContextForPrompt(userId, projectMemories);
491
+ const memoryContext = await buildRecentMemoriesSection({
492
+ projectTag: tags.project.tag,
493
+ userEmail: tags.user.userEmail,
494
+ sessionId: input.sessionID,
495
+ maxMemories: CONFIG.chatMessage.maxMemories,
496
+ excludeCurrentSession: CONFIG.chatMessage.excludeCurrentSession,
497
+ maxAgeDays: CONFIG.chatMessage.maxAgeDays,
498
+ });
513
499
  if (memoryContext) {
514
500
  const contextPart = {
515
501
  id: `prt-memory-context-${Date.now()}`,
@@ -49,6 +49,11 @@ export declare function resolvePiLiveModel(config: AutoCaptureProviderRuntimeCon
49
49
  piProvider?: string;
50
50
  piModel?: string;
51
51
  }): PiLiveModelChoice;
52
+ /**
53
+ * Claude Code's live model: the external API only. Its hooks cannot call the
54
+ * session's model, so there is no host model and no session fallback.
55
+ */
56
+ export declare function resolveClaudeCodeLiveModel(config: AutoCaptureProviderRuntimeConfig): AutoCaptureProviderStatus;
52
57
  /** True when the external API is fully configured and can serve as a fallback. */
53
58
  export declare function isExternalModelReady(config: AutoCaptureProviderRuntimeConfig): boolean;
54
59
  /** Why the external API cannot be used as a host's chosen model, or an empty list. */
@@ -93,6 +93,13 @@ export function resolvePiLiveModel(config) {
93
93
  return { kind: "session" };
94
94
  return { kind: "unready", issues: manual.issues };
95
95
  }
96
+ /**
97
+ * Claude Code's live model: the external API only. Its hooks cannot call the
98
+ * session's model, so there is no host model and no session fallback.
99
+ */
100
+ export function resolveClaudeCodeLiveModel(config) {
101
+ return externalChoiceStatus(config);
102
+ }
96
103
  /** True when the external API is fully configured and can serve as a fallback. */
97
104
  export function isExternalModelReady(config) {
98
105
  return getManualProviderStatus(config).ready;
@@ -1,4 +1,4 @@
1
- type BackfillHost = "pi" | "opencode";
1
+ type BackfillHost = "pi" | "opencode" | "claude-code";
2
2
  export type BackfillStatus = {
3
3
  host: BackfillHost;
4
4
  cutoff: number;
@@ -179,11 +179,12 @@ export async function deleteCaptureRetry(id, config) {
179
179
  export async function countCaptureRetries(config) {
180
180
  const db = await database(config);
181
181
  const rows = await db.all("SELECT host, COUNT(*) AS count FROM capture_retry_queue GROUP BY host");
182
- const counts = { opencode: 0, pi: 0 };
182
+ const counts = { opencode: 0, pi: 0, "claude-code": 0 };
183
183
  for (const row of rows) {
184
184
  const host = String(row.host);
185
- if (host === "opencode" || host === "pi")
185
+ if (host === "opencode" || host === "pi" || host === "claude-code") {
186
186
  counts[host] = Number(row.count);
187
+ }
187
188
  }
188
189
  return counts;
189
190
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The last Claude Code user entry that live capture handled, per session. It
3
+ * lives in `user-prompts.db` next to the capture retry queue, so a web app
4
+ * restart does not capture the last turn again. Callers pass `CONFIG`.
5
+ */
6
+ export interface ClaudeCaptureCursorConfig {
7
+ storagePath: string;
8
+ }
9
+ export declare function readClaudeCaptureCursor(sessionId: string, config: ClaudeCaptureCursorConfig): Promise<string | null>;
10
+ export declare function writeClaudeCaptureCursor(sessionId: string, userEntryId: string, config: ClaudeCaptureCursorConfig, now?: number): Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { join } from "node:path";
2
+ import { tursoConnectionManager } from "./turso/connection-manager.js";
3
+ /** A session with no capture for this long loses its cursor. */
4
+ const CURSOR_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
5
+ const prepared = new Map();
6
+ async function database(config) {
7
+ const path = join(config.storagePath, "user-prompts.db");
8
+ const db = await tursoConnectionManager.getConnection(path);
9
+ let ready = prepared.get(path);
10
+ if (!ready) {
11
+ ready = db
12
+ .batch([
13
+ {
14
+ sql: `CREATE TABLE IF NOT EXISTS claude_capture_cursors (
15
+ session_id TEXT PRIMARY KEY,
16
+ last_user_entry_id TEXT NOT NULL,
17
+ updated_at INTEGER NOT NULL
18
+ )`,
19
+ },
20
+ {
21
+ sql: "DELETE FROM claude_capture_cursors WHERE updated_at < ?",
22
+ args: [Date.now() - CURSOR_RETENTION_MS],
23
+ },
24
+ ])
25
+ .then(() => undefined);
26
+ // A failed preparation is tried again on the next call.
27
+ ready.catch(() => prepared.delete(path));
28
+ prepared.set(path, ready);
29
+ }
30
+ await ready;
31
+ return db;
32
+ }
33
+ export async function readClaudeCaptureCursor(sessionId, config) {
34
+ const db = await database(config);
35
+ const row = await db.get("SELECT last_user_entry_id FROM claude_capture_cursors WHERE session_id = ?", [sessionId]);
36
+ return row ? String(row.last_user_entry_id) : null;
37
+ }
38
+ export async function writeClaudeCaptureCursor(sessionId, userEntryId, config, now = Date.now()) {
39
+ const db = await database(config);
40
+ await db.run(`INSERT INTO claude_capture_cursors (session_id, last_user_entry_id, updated_at)
41
+ VALUES (?, ?, ?)
42
+ ON CONFLICT(session_id) DO UPDATE SET
43
+ last_user_entry_id = excluded.last_user_entry_id, updated_at = excluded.updated_at`, [sessionId, userEntryId, now]);
44
+ }
@@ -27,6 +27,7 @@ export declare function getSettingsSnapshot(directory: string): {
27
27
  effective: {
28
28
  opencode: import("./ai/live-model-choice.js").AutoCaptureProviderStatus;
29
29
  pi: import("./ai/live-model-choice.js").PiLiveModelChoice;
30
+ "claude-code": import("./ai/live-model-choice.js").AutoCaptureProviderStatus;
30
31
  };
31
32
  };
32
33
  /**
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { parse } from "jsonc-parser";
4
4
  import { CONFIG, getGlobalConfigSourcePath } from "../config.js";
5
5
  import { readGlobalConfigRevision } from "./global-config-writer.js";
6
- import { getAutoCaptureProviderStatus, isExternalModelReady, resolvePiLiveModel, } from "./ai/live-model-choice.js";
6
+ import { getAutoCaptureProviderStatus, isExternalModelReady, resolveClaudeCodeLiveModel, resolvePiLiveModel, } from "./ai/live-model-choice.js";
7
7
  const editable = [
8
8
  "opencodeProvider",
9
9
  "opencodeModel",
@@ -92,6 +92,8 @@ export function getSettingsSnapshot(directory) {
92
92
  effective: {
93
93
  opencode: getAutoCaptureProviderStatus(CONFIG),
94
94
  pi: resolvePiLiveModel(CONFIG),
95
+ // Claude Code capture uses the external API only; `issues` names each missing setting.
96
+ "claude-code": resolveClaudeCodeLiveModel(CONFIG),
95
97
  },
96
98
  };
97
99
  }
@@ -39,6 +39,8 @@ export declare class WebServer {
39
39
  private backfillControlsInstance?;
40
40
  /** Run an import-page action; errors carry their own status and never file contents. */
41
41
  private importResponse;
42
+ /** Claude Code hook requests; they reach this point only with a valid API token. */
43
+ private claudeHookResponse;
42
44
  private backfillControls;
43
45
  private importJobs;
44
46
  constructor(config: WebServerConfig);
@@ -36,7 +36,7 @@ function serveFetch(opts) {
36
36
  hostname: opts.hostname,
37
37
  fetch: opts.fetch,
38
38
  });
39
- return { stop: () => bunHandle.stop() };
39
+ return Promise.resolve({ stop: () => bunHandle.stop() });
40
40
  }
41
41
  // Node path: wrap node:http around the fetch-style handler. The adapter
42
42
  // converts IncomingMessage → Web Request and Web Response → ServerResponse.
@@ -95,30 +95,34 @@ function serveFetch(opts) {
95
95
  }
96
96
  }
97
97
  });
98
- // Surface EADDRINUSE synchronously so callers can detect the
99
- // already-running-instance case the same way they do under Bun.
100
- let listenError;
101
- server.on("error", (err) => {
102
- if (err.code === "EADDRINUSE") {
103
- listenError = err;
104
- }
105
- });
106
- // exclusive: false disables SO_EXCLUSIVEADDRUSE on Windows, allowing
107
- // rebind after a crashed predecessor left orphaned sockets behind.
108
- server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true, exclusive: false });
109
98
  server.unref();
110
99
  server.timeout = NODE_HTTP_IDLE_TIMEOUT_MS;
111
100
  server.keepAliveTimeout = 10000;
112
101
  server.headersTimeout = 11000;
113
- if (listenError) {
114
- throw listenError;
115
- }
116
- return {
117
- stop: () => {
118
- server.closeAllConnections();
119
- server.close();
120
- },
121
- };
102
+ // Node reports EADDRINUSE after listen() returns, so wait for the outcome.
103
+ // Callers then see a busy port the same way they do under Bun.
104
+ return new Promise((resolve, reject) => {
105
+ const onError = (error) => {
106
+ server.off("listening", onListening);
107
+ reject(error);
108
+ };
109
+ const onListening = () => {
110
+ server.off("error", onError);
111
+ // Keep a listener, so a later server error is logged instead of crashing the process.
112
+ server.on("error", (error) => log("Web server error", { error: String(error) }));
113
+ resolve({
114
+ stop: () => {
115
+ server.closeAllConnections();
116
+ server.close();
117
+ },
118
+ });
119
+ };
120
+ server.once("error", onError);
121
+ server.once("listening", onListening);
122
+ // exclusive: false disables SO_EXCLUSIVEADDRUSE on Windows, allowing
123
+ // rebind after a crashed predecessor left orphaned sockets behind.
124
+ server.listen({ port: opts.port, host: opts.hostname, reuseAddr: true, exclusive: false });
125
+ });
122
126
  }
123
127
  const __filename = fileURLToPath(import.meta.url);
124
128
  const __dirname = dirname(__filename);
@@ -157,6 +161,26 @@ export class WebServer {
157
161
  return this.jsonResponse({ error: error instanceof Error ? error.message : "Import request failed" }, typeof code === "number" ? code : 400);
158
162
  }
159
163
  }
164
+ /** Claude Code hook requests; they reach this point only with a valid API token. */
165
+ async claudeHookResponse(req, path) {
166
+ const api = await import("../importer/claude-hook-api.js");
167
+ const body = await req.json().catch(() => null);
168
+ try {
169
+ if (path === "/api/claude/capture") {
170
+ return this.jsonResponse(api.handleClaudeCapture(body), 202);
171
+ }
172
+ const cwd = this.config.directory ?? process.cwd();
173
+ return this.jsonResponse(await api.handleClaudeRetrieve(body, {
174
+ startBackfill: async () => (await this.backfillControls()).startAuto("claude-code", cwd),
175
+ }));
176
+ }
177
+ catch (error) {
178
+ if (error instanceof api.ClaudeHookRequestError) {
179
+ return this.jsonResponse({ error: error.message }, error.status);
180
+ }
181
+ throw error;
182
+ }
183
+ }
160
184
  async backfillControls() {
161
185
  const { BackfillControls } = await import("../importer/web-import-api.js");
162
186
  return (this.backfillControlsInstance ??= new BackfillControls());
@@ -192,12 +216,16 @@ export class WebServer {
192
216
  .catch(() => { });
193
217
  assertWebServerNetworkAuth(this.config.host, this.config.apiToken, this.config.auth?.isEnabled() ?? false);
194
218
  try {
195
- this.server = serveFetch({
219
+ this.server = await serveFetch({
196
220
  port: this.config.port,
197
221
  hostname: this.config.host,
198
222
  fetch: this.handleRequest.bind(this),
199
223
  });
200
224
  this.isOwner = true;
225
+ // This process serves the Claude Code hooks, so it also retries their failed captures.
226
+ void import("../importer/claude-hook-api.js")
227
+ .then(({ startClaudeCodeWorker }) => startClaudeCodeWorker())
228
+ .catch(() => { });
201
229
  }
202
230
  catch (error) {
203
231
  const errorMsg = String(error);
@@ -426,12 +454,13 @@ export class WebServer {
426
454
  return this.jsonResponse({
427
455
  pi: await readBackfillStatus("pi"),
428
456
  opencode: await readBackfillStatus("opencode"),
457
+ "claude-code": await readBackfillStatus("claude-code"),
429
458
  });
430
459
  }
431
460
  if (path === "/api/settings/backfill/runs" && method === "GET") {
432
461
  return this.jsonResponse(await (await this.backfillControls()).status());
433
462
  }
434
- const backfillAction = /^\/api\/settings\/backfill\/(pi|opencode)\/(run|pause|resume)$/.exec(path);
463
+ const backfillAction = /^\/api\/settings\/backfill\/(pi|opencode|claude-code)\/(run|pause|resume)$/.exec(path);
435
464
  if (backfillAction && method === "POST") {
436
465
  const host = backfillAction[1];
437
466
  const controls = await this.backfillControls();
@@ -569,7 +598,7 @@ export class WebServer {
569
598
  retryQueue: await countCaptureRetries(CONFIG),
570
599
  });
571
600
  }
572
- const retryNow = /^\/api\/settings\/capture-retry\/(pi|opencode)\/run$/.exec(path);
601
+ const retryNow = /^\/api\/settings\/capture-retry\/(pi|opencode|claude-code)\/run$/.exec(path);
573
602
  if (retryNow && method === "POST") {
574
603
  const [{ CONFIG }, { requestCaptureRetryNow }] = await Promise.all([
575
604
  import("../config.js"),
@@ -620,16 +649,16 @@ export class WebServer {
620
649
  return this.jsonResponse({ error: "Browsing is available only on a loopback bind; enter a path instead" }, 403);
621
650
  }
622
651
  const body = (await req.json());
623
- if (body?.host !== "pi" && body?.host !== "opencode") {
624
- return this.jsonResponse({ error: "Choose Pi or OpenCode" }, 400);
652
+ if (body?.host !== "pi" && body?.host !== "opencode" && body?.host !== "claude-code") {
653
+ return this.jsonResponse({ error: "Choose Pi, OpenCode, or Claude Code" }, 400);
625
654
  }
626
655
  const { browseImportSources } = await import("../importer/web-import-api.js");
627
656
  return this.importResponse(() => browseImportSources(body.host, body.path));
628
657
  }
629
658
  if (path === "/api/settings/imports/sources/validate" && method === "POST") {
630
659
  const body = (await req.json());
631
- if (body?.host !== "pi" && body?.host !== "opencode") {
632
- return this.jsonResponse({ error: "Choose Pi or OpenCode" }, 400);
660
+ if (body?.host !== "pi" && body?.host !== "opencode" && body?.host !== "claude-code") {
661
+ return this.jsonResponse({ error: "Choose Pi, OpenCode, or Claude Code" }, 400);
633
662
  }
634
663
  const { validateImportSource } = await import("../importer/web-import-api.js");
635
664
  return this.importResponse(() => validateImportSource(body.host, body.path));
@@ -718,6 +747,10 @@ export class WebServer {
718
747
  const result = await handleSearch(query, tag, page, pageSize);
719
748
  return this.jsonResponse(result);
720
749
  }
750
+ if ((path === "/api/claude/retrieve" || path === "/api/claude/capture") &&
751
+ method === "POST") {
752
+ return this.claudeHookResponse(req, path);
753
+ }
721
754
  if (path === "/api/stats" && method === "GET") {
722
755
  const result = await handleStats();
723
756
  return this.jsonResponse(result);
@@ -0,0 +1,3 @@
1
+ import type { MemoryHost } from "./index.js";
2
+ /** The host's display name in messages and reports. */
3
+ export declare function hostLabel(host: MemoryHost): string;
@@ -0,0 +1,9 @@
1
+ const HOST_LABELS = {
2
+ opencode: "OpenCode",
3
+ pi: "Pi",
4
+ "claude-code": "Claude Code",
5
+ };
6
+ /** The host's display name in messages and reports. */
7
+ export function hostLabel(host) {
8
+ return HOST_LABELS[host];
9
+ }
@@ -1,5 +1,5 @@
1
1
  export type MemoryType = string;
2
- export type MemoryHost = "opencode" | "pi";
2
+ export type MemoryHost = "opencode" | "pi" | "claude-code";
3
3
  export type MemorySourceType = "live-capture" | "history-import";
4
4
  export interface MemoryMetadata {
5
5
  type?: MemoryType;