openclaw-cloudflare-vectorize-memory 0.1.1 → 0.1.3

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 CHANGED
@@ -123,16 +123,30 @@ You can also store `cloudflare.apiToken` as an OpenClaw secret ref instead of pl
123
123
 
124
124
  Run:
125
125
 
126
+ ```bash
127
+ openclaw cf-memory init
128
+ ```
129
+
130
+ to create or repair the configured Vectorize index so it matches the active embedding model dimensions.
131
+
132
+ Validate configuration without changing infrastructure:
133
+
126
134
  ```bash
127
135
  openclaw cf-memory doctor
128
136
  ```
129
137
 
130
- To validate config and create the Vectorize index when missing:
138
+ Validate configuration and create the Vectorize index when missing:
131
139
 
132
140
  ```bash
133
141
  openclaw cf-memory doctor --create-index
134
142
  ```
135
143
 
144
+ Run an end-to-end smoke test that verifies embedding, write, search, and cleanup:
145
+
146
+ ```bash
147
+ openclaw cf-memory test
148
+ ```
149
+
136
150
  The doctor flow checks:
137
151
 
138
152
  - Cloudflare credentials
@@ -143,6 +157,18 @@ The doctor flow checks:
143
157
 
144
158
  ## CLI usage
145
159
 
160
+ Initialize or repair the Vectorize index:
161
+
162
+ ```bash
163
+ openclaw cf-memory init
164
+ ```
165
+
166
+ Run a smoke test:
167
+
168
+ ```bash
169
+ openclaw cf-memory test
170
+ ```
171
+
146
172
  Migrate the default OpenClaw markdown memory corpus from the current workspace:
147
173
 
148
174
  ```bash
@@ -0,0 +1,18 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
2
+
3
+ export default definePluginEntry({
4
+ id: "memory-cloudflare-vectorize",
5
+ name: "Cloudflare Vectorize Memory",
6
+ description: "OpenClaw memory plugin backed by Cloudflare Vectorize and Workers AI embeddings.",
7
+ register(api) {
8
+ api.registerCli(() => {}, {
9
+ descriptors: [
10
+ {
11
+ name: "cf-memory",
12
+ description: "Manage Cloudflare Vectorize memory",
13
+ hasSubcommands: true,
14
+ },
15
+ ],
16
+ });
17
+ },
18
+ });
package/dist/cli.js CHANGED
@@ -5,23 +5,26 @@ function r(e) {
5
5
  console.log(JSON.stringify(e, null, 2));
6
6
  }
7
7
  function i(e) {
8
+ for (let t of e.checks) console.log(`[${t.status}] ${t.name}: ${t.message}`);
9
+ }
10
+ function a(e) {
8
11
  if (!e) return;
9
12
  let t = JSON.parse(e);
10
13
  if (!t || typeof t != "object" || Array.isArray(t)) throw Error("--metadata must be a JSON object.");
11
14
  return t;
12
15
  }
13
- function a(e) {
16
+ function o(e) {
14
17
  if (!e) return;
15
18
  let t = JSON.parse(e);
16
19
  if (!t || typeof t != "object" || Array.isArray(t)) throw Error("--filter must be a JSON object.");
17
20
  return t;
18
21
  }
19
- function o(e) {
22
+ function s(e) {
20
23
  return !!e && typeof e == "object" && typeof e.opts == "function";
21
24
  }
22
- function s(e) {
25
+ function c(e) {
23
26
  let t = e.at(-1);
24
- return o(t) ? {
27
+ return s(t) ? {
25
28
  positionals: e.slice(0, -1),
26
29
  options: t.opts?.() ?? {}
27
30
  } : {
@@ -29,83 +32,97 @@ function s(e) {
29
32
  options: {}
30
33
  };
31
34
  }
32
- function c(e) {
35
+ function l(e) {
33
36
  if (e !== void 0) {
34
37
  if (e === "overwrite" || e === "skip" || e === "fail") return e;
35
38
  throw Error("--if-exists must be overwrite, skip, or fail.");
36
39
  }
37
40
  }
38
- function l(o, l) {
39
- let u = o.command("cf-memory").description("Manage Cloudflare memory records.");
40
- function d(e) {
41
- return s(e).options;
41
+ function u(s, u) {
42
+ let d = s.command("cf-memory").description("Manage Cloudflare memory records.");
43
+ function f(e) {
44
+ return c(e).options;
42
45
  }
43
- u.command("doctor").description("Validate Workers AI and Vectorize configuration.").option("--create-index", "Create the Vectorize index if missing.").option("--json", "Print structured JSON output.").action(async (...e) => {
44
- let t = d(e), i = await (await n({
45
- pluginConfig: l.pluginConfig,
46
- openClawConfig: l.openClawConfig,
46
+ d.command("doctor").description("Validate Workers AI and Vectorize configuration.").option("--create-index", "Create the Vectorize index if missing.").option("--json", "Print structured JSON output.").action(async (...e) => {
47
+ let t = f(e), a = await (await n({
48
+ pluginConfig: u.pluginConfig,
49
+ openClawConfig: u.openClawConfig,
47
50
  env: process.env,
48
- resolvePath: l.resolvePath
51
+ resolvePath: u.resolvePath
49
52
  })).doctor({ createIndexIfMissing: !!t.createIndex });
50
- if (t.json) r(i);
51
- else for (let e of i.checks) console.log(`[${e.status}] ${e.name}: ${e.message}`);
52
- i.ok || (process.exitCode = 1);
53
- }), u.command("search").description("Search stored Cloudflare memory.").argument("<query>", "Semantic search query.").option("--namespace <namespace>", "Optional namespace override.").option("--limit <count>", "Maximum number of results.").option("--filter <json>", "Optional metadata filter JSON.").action(async (e, t) => {
53
+ t.json ? r(a) : i(a), a.ok || (process.exitCode = 1);
54
+ }), d.command("init").description("Initialize the Cloudflare Vectorize index for the configured embedding model.").option("--json", "Print structured JSON output.").action(async (...e) => {
55
+ let t = f(e), a = await (await n({
56
+ pluginConfig: u.pluginConfig,
57
+ openClawConfig: u.openClawConfig,
58
+ env: process.env,
59
+ resolvePath: u.resolvePath
60
+ })).initializeIndex();
61
+ t.json ? r(a) : i(a), a.ok || (process.exitCode = 1);
62
+ }), d.command("test").description("Run an end-to-end embedding and semantic-search smoke test.").option("--json", "Print structured JSON output.").action(async (...e) => {
63
+ let t = f(e), a = await (await n({
64
+ pluginConfig: u.pluginConfig,
65
+ openClawConfig: u.openClawConfig,
66
+ env: process.env,
67
+ resolvePath: u.resolvePath
68
+ })).runSmokeTest();
69
+ t.json ? r(a) : i(a), a.ok || (process.exitCode = 1);
70
+ }), d.command("search").description("Search stored Cloudflare memory.").argument("<query>", "Semantic search query.").option("--namespace <namespace>", "Optional namespace override.").option("--limit <count>", "Maximum number of results.").option("--filter <json>", "Optional metadata filter JSON.").action(async (e, t) => {
54
71
  let i = t;
55
72
  r(await (await n({
56
- pluginConfig: l.pluginConfig,
57
- openClawConfig: l.openClawConfig,
73
+ pluginConfig: u.pluginConfig,
74
+ openClawConfig: u.openClawConfig,
58
75
  env: process.env,
59
- resolvePath: l.resolvePath
76
+ resolvePath: u.resolvePath
60
77
  })).search({
61
78
  query: String(e),
62
79
  namespace: i.namespace,
63
80
  maxResults: i.limit ? Number(i.limit) : void 0,
64
- filter: a(i.filter)
81
+ filter: o(i.filter)
65
82
  }));
66
- }), u.command("upsert").description("Insert or update a memory record.").argument("<text>", "Memory text.").option("--id <id>", "Stable logical id.").option("--title <title>", "Optional title.").option("--namespace <namespace>", "Optional namespace override.").option("--source <source>", "Optional source label.").option("--metadata <json>", "Optional metadata JSON object.").action(async (e, t) => {
67
- let a = t;
83
+ }), d.command("upsert").description("Insert or update a memory record.").argument("<text>", "Memory text.").option("--id <id>", "Stable logical id.").option("--title <title>", "Optional title.").option("--namespace <namespace>", "Optional namespace override.").option("--source <source>", "Optional source label.").option("--metadata <json>", "Optional metadata JSON object.").action(async (e, t) => {
84
+ let i = t;
68
85
  r(await (await n({
69
- pluginConfig: l.pluginConfig,
70
- openClawConfig: l.openClawConfig,
86
+ pluginConfig: u.pluginConfig,
87
+ openClawConfig: u.openClawConfig,
71
88
  env: process.env,
72
- resolvePath: l.resolvePath
89
+ resolvePath: u.resolvePath
73
90
  })).upsert({ input: {
74
- id: a.id,
75
- title: a.title,
91
+ id: i.id,
92
+ title: i.title,
76
93
  text: String(e),
77
- namespace: a.namespace,
78
- source: a.source,
79
- metadata: i(a.metadata)
94
+ namespace: i.namespace,
95
+ source: i.source,
96
+ metadata: a(i.metadata)
80
97
  } }));
81
- }), u.command("delete").description("Delete a memory record.").argument("<id>", "Logical memory record id.").option("--namespace <namespace>", "Optional namespace override.").action(async (e, t) => {
98
+ }), d.command("delete").description("Delete a memory record.").argument("<id>", "Logical memory record id.").option("--namespace <namespace>", "Optional namespace override.").action(async (e, t) => {
82
99
  let i = t;
83
100
  r({
84
101
  id: e,
85
102
  mutationId: await (await n({
86
- pluginConfig: l.pluginConfig,
87
- openClawConfig: l.openClawConfig,
103
+ pluginConfig: u.pluginConfig,
104
+ openClawConfig: u.openClawConfig,
88
105
  env: process.env,
89
- resolvePath: l.resolvePath
106
+ resolvePath: u.resolvePath
90
107
  })).delete({
91
108
  id: String(e),
92
109
  namespace: i.namespace
93
110
  })
94
111
  });
95
- }), u.command("migrate").description("Migrate legacy markdown memory into Cloudflare Vectorize.").argument("[sources...]", "Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.").option("--workspace <path>", "Workspace root used for default-provider discovery and relative path normalization.").option("--namespace <namespace>", "Target namespace override.").option("--derive-namespace-from-path", "Derive namespaces from the first relative path segment instead of using a single target namespace.").option("--if-exists <strategy>", "Duplicate handling: overwrite, skip, or fail.").option("--create-index", "Create the Vectorize index if missing.").option("--dry-run", "Plan the migration without writing records.").option("--json", "Print structured JSON output.").action(async (...i) => {
96
- let { positionals: a, options: o } = s(i), u = a[0], d = a.length === 0 ? [] : Array.isArray(u) ? u.map((e) => String(e)) : a.map((e) => String(e)), f = await t({
112
+ }), d.command("migrate").description("Migrate legacy markdown memory into Cloudflare Vectorize.").argument("[sources...]", "Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.").option("--workspace <path>", "Workspace root used for default-provider discovery and relative path normalization.").option("--namespace <namespace>", "Target namespace override.").option("--derive-namespace-from-path", "Derive namespaces from the first relative path segment instead of using a single target namespace.").option("--if-exists <strategy>", "Duplicate handling: overwrite, skip, or fail.").option("--create-index", "Create the Vectorize index if missing.").option("--dry-run", "Plan the migration without writing records.").option("--json", "Print structured JSON output.").action(async (...i) => {
113
+ let { positionals: a, options: o } = c(i), s = a[0], d = a.length === 0 ? [] : Array.isArray(s) ? s.map((e) => String(e)) : a.map((e) => String(e)), f = await t({
97
114
  service: await n({
98
- pluginConfig: l.pluginConfig,
99
- openClawConfig: l.openClawConfig,
115
+ pluginConfig: u.pluginConfig,
116
+ openClawConfig: u.openClawConfig,
100
117
  env: process.env,
101
- resolvePath: l.resolvePath
118
+ resolvePath: u.resolvePath
102
119
  }),
103
120
  options: {
104
121
  sourcePaths: d,
105
122
  workspaceDir: o.workspace,
106
123
  namespace: o.namespace,
107
124
  namespaceStrategy: o.deriveNamespaceFromPath ? "path" : "single-target",
108
- duplicateStrategy: c(o.ifExists),
125
+ duplicateStrategy: l(o.ifExists),
109
126
  dryRun: !!o.dryRun,
110
127
  createIndexIfMissing: !!o.createIndex
111
128
  }
@@ -114,6 +131,6 @@ function l(o, l) {
114
131
  });
115
132
  }
116
133
  //#endregion
117
- export { l as registerCloudflareMemoryCli };
134
+ export { u as registerCloudflareMemoryCli };
118
135
 
119
136
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\r\nimport type { MigrationDuplicateStrategy } from \"./types.js\";\r\nimport { formatMigrationSummary, runCloudflareMemoryMigration } from \"./migration.js\";\r\nimport { createCloudflareMemoryService } from \"./service-factory.js\";\r\nimport type { MetadataFilter } from \"./types.js\";\r\n\r\ntype CliCommand = {\r\n\tcommand: (name: string) => CliCommand;\r\n\tdescription: (description: string) => CliCommand;\r\n\targument: (name: string, description: string) => CliCommand;\r\n\toption: (flags: string, description: string) => CliCommand;\r\n\taction: (handler: (...args: unknown[]) => Promise<void> | void) => CliCommand;\r\n\topts?: () => Record<string, unknown>;\r\n};\r\n\r\nfunction printJson(value: unknown): void {\r\n\tconsole.log(JSON.stringify(value, null, 2));\r\n}\r\n\r\nfunction parseMetadataFlag(value: string | undefined): Record<string, string | number | boolean> | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--metadata must be a JSON object.\");\r\n\t}\r\n\treturn parsed as Record<string, string | number | boolean>;\r\n}\r\n\r\nfunction parseFilterFlag(value: string | undefined): MetadataFilter | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--filter must be a JSON object.\");\r\n\t}\r\n\treturn parsed as MetadataFilter;\r\n}\r\n\r\nfunction isCliCommand(value: unknown): value is CliCommand {\r\n\treturn Boolean(value) && typeof value === \"object\" && typeof (value as CliCommand).opts === \"function\";\r\n}\r\n\r\nfunction resolveInvocation(args: unknown[]): { positionals: unknown[]; options: Record<string, unknown> } {\r\n\tconst maybeCommand = args.at(-1);\r\n\tif (!isCliCommand(maybeCommand)) {\r\n\t\treturn {\r\n\t\t\tpositionals: args,\r\n\t\t\toptions: {},\r\n\t\t};\r\n\t}\r\n\treturn {\r\n\t\tpositionals: args.slice(0, -1),\r\n\t\toptions: maybeCommand.opts?.() ?? {},\r\n\t};\r\n}\r\n\r\nfunction parseDuplicateStrategy(value: unknown): MigrationDuplicateStrategy | undefined {\r\n\tif (value === undefined) {\r\n\t\treturn undefined;\r\n\t}\r\n\tif (value === \"overwrite\" || value === \"skip\" || value === \"fail\") {\r\n\t\treturn value;\r\n\t}\r\n\tthrow new Error(\"--if-exists must be overwrite, skip, or fail.\");\r\n}\r\n\r\nexport function registerCloudflareMemoryCli(\r\n\tprogram: {\r\n\t\tcommand: (name: string) => CliCommand;\r\n\t},\r\n\tparams: {\r\n\t\tpluginConfig: unknown;\r\n\t\topenClawConfig: OpenClawConfig;\r\n\t\tresolvePath?: (input: string) => string;\r\n\t},\r\n): void {\r\n\tconst root = program.command(\"cf-memory\").description(\"Manage Cloudflare memory records.\");\r\n\r\n\tfunction resolveOptions(args: unknown[]): Record<string, unknown> {\r\n\t\treturn resolveInvocation(args).options;\r\n\t}\r\n\r\n\troot\r\n\t\t.command(\"doctor\")\r\n\t\t.description(\"Validate Workers AI and Vectorize configuration.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst options = resolveOptions(args);\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst report = await service.doctor({\r\n\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(report);\r\n\t\t\t} else {\r\n\t\t\t\tfor (const check of report.checks) {\r\n\t\t\t\t\tconsole.log(`[${check.status}] ${check.name}: ${check.message}`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!report.ok) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"search\")\r\n\t\t.description(\"Search stored Cloudflare memory.\")\r\n\t\t.argument(\"<query>\", \"Semantic search query.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--limit <count>\", \"Maximum number of results.\")\r\n\t\t.option(\"--filter <json>\", \"Optional metadata filter JSON.\")\r\n\t\t.action(async (query, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst results = await service.search({\r\n\t\t\t\tquery: String(query),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\tmaxResults: options.limit ? Number(options.limit) : undefined,\r\n\t\t\t\tfilter: parseFilterFlag(options.filter as string | undefined),\r\n\t\t\t});\r\n\t\t\tprintJson(results);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"upsert\")\r\n\t\t.description(\"Insert or update a memory record.\")\r\n\t\t.argument(\"<text>\", \"Memory text.\")\r\n\t\t.option(\"--id <id>\", \"Stable logical id.\")\r\n\t\t.option(\"--title <title>\", \"Optional title.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--source <source>\", \"Optional source label.\")\r\n\t\t.option(\"--metadata <json>\", \"Optional metadata JSON object.\")\r\n\t\t.action(async (text, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst result = await service.upsert({\r\n\t\t\t\tinput: {\r\n\t\t\t\t\tid: options.id as string | undefined,\r\n\t\t\t\t\ttitle: options.title as string | undefined,\r\n\t\t\t\t\ttext: String(text),\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tsource: options.source as string | undefined,\r\n\t\t\t\t\tmetadata: parseMetadataFlag(options.metadata as string | undefined),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tprintJson(result);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"delete\")\r\n\t\t.description(\"Delete a memory record.\")\r\n\t\t.argument(\"<id>\", \"Logical memory record id.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.action(async (id, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst mutationId = await service.delete({\r\n\t\t\t\tid: String(id),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t});\r\n\t\t\tprintJson({ id, mutationId });\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"migrate\")\r\n\t\t.description(\"Migrate legacy markdown memory into Cloudflare Vectorize.\")\r\n\t\t.argument(\"[sources...]\", \"Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.\")\r\n\t\t.option(\"--workspace <path>\", \"Workspace root used for default-provider discovery and relative path normalization.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Target namespace override.\")\r\n\t\t.option(\"--derive-namespace-from-path\", \"Derive namespaces from the first relative path segment instead of using a single target namespace.\")\r\n\t\t.option(\"--if-exists <strategy>\", \"Duplicate handling: overwrite, skip, or fail.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--dry-run\", \"Plan the migration without writing records.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst { positionals, options } = resolveInvocation(args);\r\n\t\t\tconst rawSources = positionals[0];\r\n\t\t\tconst sourcePaths =\r\n\t\t\t\tpositionals.length === 0\r\n\t\t\t\t\t? []\r\n\t\t\t\t\t: Array.isArray(rawSources)\r\n\t\t\t\t\t\t? rawSources.map((value) => String(value))\r\n\t\t\t\t\t\t: positionals.map((value) => String(value));\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst summary = await runCloudflareMemoryMigration({\r\n\t\t\t\tservice,\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tsourcePaths,\r\n\t\t\t\t\tworkspaceDir: options.workspace as string | undefined,\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tnamespaceStrategy: options.deriveNamespaceFromPath ? \"path\" : \"single-target\",\r\n\t\t\t\t\tduplicateStrategy: parseDuplicateStrategy(options.ifExists),\r\n\t\t\t\t\tdryRun: Boolean(options.dryRun),\r\n\t\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(summary);\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log(formatMigrationSummary(summary));\r\n\t\t\t}\r\n\t\t\tif (summary.failed > 0) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n}\r\n"],"mappings":";;;AAeA,SAAS,EAAU,GAAsB;AACxC,SAAQ,IAAI,KAAK,UAAU,GAAO,MAAM,EAAE,CAAC;;AAG5C,SAAS,EAAkB,GAAkF;AAC5G,KAAI,CAAC,EACJ;CAED,IAAM,IAAS,KAAK,MAAM,EAAM;AAChC,KAAI,CAAC,KAAU,OAAO,KAAW,YAAY,MAAM,QAAQ,EAAO,CACjE,OAAU,MAAM,oCAAoC;AAErD,QAAO;;AAGR,SAAS,EAAgB,GAAuD;AAC/E,KAAI,CAAC,EACJ;CAED,IAAM,IAAS,KAAK,MAAM,EAAM;AAChC,KAAI,CAAC,KAAU,OAAO,KAAW,YAAY,MAAM,QAAQ,EAAO,CACjE,OAAU,MAAM,kCAAkC;AAEnD,QAAO;;AAGR,SAAS,EAAa,GAAqC;AAC1D,QAAO,EAAQ,KAAU,OAAO,KAAU,YAAY,OAAQ,EAAqB,QAAS;;AAG7F,SAAS,EAAkB,GAA+E;CACzG,IAAM,IAAe,EAAK,GAAG,GAAG;AAOhC,QANK,EAAa,EAAa,GAMxB;EACN,aAAa,EAAK,MAAM,GAAG,GAAG;EAC9B,SAAS,EAAa,QAAQ,IAAI,EAAE;EACpC,GARO;EACN,aAAa;EACb,SAAS,EAAE;EACX;;AAQH,SAAS,EAAuB,GAAwD;AACnF,WAAU,KAAA,GAGd;MAAI,MAAU,eAAe,MAAU,UAAU,MAAU,OAC1D,QAAO;AAER,QAAU,MAAM,gDAAgD;;;AAGjE,SAAgB,EACf,GAGA,GAKO;CACP,IAAM,IAAO,EAAQ,QAAQ,YAAY,CAAC,YAAY,oCAAoC;CAE1F,SAAS,EAAe,GAA0C;AACjE,SAAO,EAAkB,EAAK,CAAC;;AAyGhC,CAtGA,EACE,QAAQ,SAAS,CACjB,YAAY,mDAAmD,CAC/D,OAAO,kBAAkB,yCAAyC,CAClE,OAAO,UAAU,gCAAgC,CACjD,OAAO,OAAO,GAAG,MAAS;EAC1B,IAAM,IAAU,EAAe,EAAK,EAO9B,IAAS,OANC,MAAM,EAA8B;GACnD,cAAc,EAAO;GACrB,gBAAgB,EAAO;GACvB,KAAK,QAAQ;GACb,aAAa,EAAO;GACpB,CAAC,EAC2B,OAAO,EACnC,sBAAsB,EAAQ,EAAQ,aACtC,CAAC;AACF,MAAI,EAAQ,KACX,GAAU,EAAO;MAEjB,MAAK,IAAM,KAAS,EAAO,OAC1B,SAAQ,IAAI,IAAI,EAAM,OAAO,IAAI,EAAM,KAAK,IAAI,EAAM,UAAU;AAGlE,EAAK,EAAO,OACX,QAAQ,WAAW;GAEnB,EAEH,EACE,QAAQ,SAAS,CACjB,YAAY,mCAAmC,CAC/C,SAAS,WAAW,yBAAyB,CAC7C,OAAO,2BAA2B,+BAA+B,CACjE,OAAO,mBAAmB,6BAA6B,CACvD,OAAO,mBAAmB,iCAAiC,CAC3D,OAAO,OAAO,GAAO,MAAS;EAC9B,IAAM,IAAU;AAahB,IANgB,OANA,MAAM,EAA8B;GACnD,cAAc,EAAO;GACrB,gBAAgB,EAAO;GACvB,KAAK,QAAQ;GACb,aAAa,EAAO;GACpB,CAAC,EAC4B,OAAO;GACpC,OAAO,OAAO,EAAM;GACpB,WAAW,EAAQ;GACnB,YAAY,EAAQ,QAAQ,OAAO,EAAQ,MAAM,GAAG,KAAA;GACpD,QAAQ,EAAgB,EAAQ,OAA6B;GAC7D,CAAC,CACgB;GACjB,EAEH,EACE,QAAQ,SAAS,CACjB,YAAY,oCAAoC,CAChD,SAAS,UAAU,eAAe,CAClC,OAAO,aAAa,qBAAqB,CACzC,OAAO,mBAAmB,kBAAkB,CAC5C,OAAO,2BAA2B,+BAA+B,CACjE,OAAO,qBAAqB,yBAAyB,CACrD,OAAO,qBAAqB,iCAAiC,CAC7D,OAAO,OAAO,GAAM,MAAS;EAC7B,IAAM,IAAU;AAiBhB,IAVe,OANC,MAAM,EAA8B;GACnD,cAAc,EAAO;GACrB,gBAAgB,EAAO;GACvB,KAAK,QAAQ;GACb,aAAa,EAAO;GACpB,CAAC,EAC2B,OAAO,EACnC,OAAO;GACN,IAAI,EAAQ;GACZ,OAAO,EAAQ;GACf,MAAM,OAAO,EAAK;GAClB,WAAW,EAAQ;GACnB,QAAQ,EAAQ;GAChB,UAAU,EAAkB,EAAQ,SAA+B;GACnE,EACD,CAAC,CACe;GAChB,EAEH,EACE,QAAQ,SAAS,CACjB,YAAY,0BAA0B,CACtC,SAAS,QAAQ,4BAA4B,CAC7C,OAAO,2BAA2B,+BAA+B,CACjE,OAAO,OAAO,GAAI,MAAS;EAC3B,IAAM,IAAU;AAWhB,IAAU;GAAE;GAAI,YAJG,OANH,MAAM,EAA8B;IACnD,cAAc,EAAO;IACrB,gBAAgB,EAAO;IACvB,KAAK,QAAQ;IACb,aAAa,EAAO;IACpB,CAAC,EAC+B,OAAO;IACvC,IAAI,OAAO,EAAG;IACd,WAAW,EAAQ;IACnB,CAAC;GAC0B,CAAC;GAC5B,EAEH,EACE,QAAQ,UAAU,CAClB,YAAY,4DAA4D,CACxE,SAAS,gBAAgB,8GAA8G,CACvI,OAAO,sBAAsB,sFAAsF,CACnH,OAAO,2BAA2B,6BAA6B,CAC/D,OAAO,gCAAgC,qGAAqG,CAC5I,OAAO,0BAA0B,gDAAgD,CACjF,OAAO,kBAAkB,yCAAyC,CAClE,OAAO,aAAa,8CAA8C,CAClE,OAAO,UAAU,gCAAgC,CACjD,OAAO,OAAO,GAAG,MAAS;EAC1B,IAAM,EAAE,gBAAa,eAAY,EAAkB,EAAK,EAClD,IAAa,EAAY,IACzB,IACL,EAAY,WAAW,IACpB,EAAE,GACF,MAAM,QAAQ,EAAW,GACxB,EAAW,KAAK,MAAU,OAAO,EAAM,CAAC,GACxC,EAAY,KAAK,MAAU,OAAO,EAAM,CAAC,EAOxC,IAAU,MAAM,EAA6B;GAClD,SAPe,MAAM,EAA8B;IACnD,cAAc,EAAO;IACrB,gBAAgB,EAAO;IACvB,KAAK,QAAQ;IACb,aAAa,EAAO;IACpB,CAAC;GAGD,SAAS;IACR;IACA,cAAc,EAAQ;IACtB,WAAW,EAAQ;IACnB,mBAAmB,EAAQ,0BAA0B,SAAS;IAC9D,mBAAmB,EAAuB,EAAQ,SAAS;IAC3D,QAAQ,EAAQ,EAAQ;IACxB,sBAAsB,EAAQ,EAAQ;IACtC;GACD,CAAC;AAMF,EALI,EAAQ,OACX,EAAU,EAAQ,GAElB,QAAQ,IAAI,EAAuB,EAAQ,CAAC,EAEzC,EAAQ,SAAS,MACpB,QAAQ,WAAW;GAEnB"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\r\nimport type { DoctorReport, IndexInitializationReport, MetadataFilter, MigrationDuplicateStrategy, SmokeTestReport } from \"./types.js\";\r\nimport { formatMigrationSummary, runCloudflareMemoryMigration } from \"./migration.js\";\r\nimport { createCloudflareMemoryService } from \"./service-factory.js\";\r\n\r\ntype CliCommand = {\r\n\tcommand: (name: string) => CliCommand;\r\n\tdescription: (description: string) => CliCommand;\r\n\targument: (name: string, description: string) => CliCommand;\r\n\toption: (flags: string, description: string) => CliCommand;\r\n\taction: (handler: (...args: unknown[]) => Promise<void> | void) => CliCommand;\r\n\topts?: () => Record<string, unknown>;\r\n};\r\n\r\nfunction printJson(value: unknown): void {\r\n\tconsole.log(JSON.stringify(value, null, 2));\r\n}\r\n\r\nfunction printCheckReport(report: DoctorReport | IndexInitializationReport | SmokeTestReport): void {\r\n\tfor (const check of report.checks) {\r\n\t\tconsole.log(`[${check.status}] ${check.name}: ${check.message}`);\r\n\t}\r\n}\r\n\r\nfunction parseMetadataFlag(value: string | undefined): Record<string, string | number | boolean> | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--metadata must be a JSON object.\");\r\n\t}\r\n\treturn parsed as Record<string, string | number | boolean>;\r\n}\r\n\r\nfunction parseFilterFlag(value: string | undefined): MetadataFilter | undefined {\r\n\tif (!value) {\r\n\t\treturn undefined;\r\n\t}\r\n\tconst parsed = JSON.parse(value) as unknown;\r\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\r\n\t\tthrow new Error(\"--filter must be a JSON object.\");\r\n\t}\r\n\treturn parsed as MetadataFilter;\r\n}\r\n\r\nfunction isCliCommand(value: unknown): value is CliCommand {\r\n\treturn Boolean(value) && typeof value === \"object\" && typeof (value as CliCommand).opts === \"function\";\r\n}\r\n\r\nfunction resolveInvocation(args: unknown[]): { positionals: unknown[]; options: Record<string, unknown> } {\r\n\tconst maybeCommand = args.at(-1);\r\n\tif (!isCliCommand(maybeCommand)) {\r\n\t\treturn {\r\n\t\t\tpositionals: args,\r\n\t\t\toptions: {},\r\n\t\t};\r\n\t}\r\n\treturn {\r\n\t\tpositionals: args.slice(0, -1),\r\n\t\toptions: maybeCommand.opts?.() ?? {},\r\n\t};\r\n}\r\n\r\nfunction parseDuplicateStrategy(value: unknown): MigrationDuplicateStrategy | undefined {\r\n\tif (value === undefined) {\r\n\t\treturn undefined;\r\n\t}\r\n\tif (value === \"overwrite\" || value === \"skip\" || value === \"fail\") {\r\n\t\treturn value;\r\n\t}\r\n\tthrow new Error(\"--if-exists must be overwrite, skip, or fail.\");\r\n}\r\n\r\nexport function registerCloudflareMemoryCli(\r\n\tprogram: {\r\n\t\tcommand: (name: string) => CliCommand;\r\n\t},\r\n\tparams: {\r\n\t\tpluginConfig: unknown;\r\n\t\topenClawConfig: OpenClawConfig;\r\n\t\tresolvePath?: (input: string) => string;\r\n\t},\r\n): void {\r\n\tconst root = program.command(\"cf-memory\").description(\"Manage Cloudflare memory records.\");\r\n\r\n\tfunction resolveOptions(args: unknown[]): Record<string, unknown> {\r\n\t\treturn resolveInvocation(args).options;\r\n\t}\r\n\r\n\troot\r\n\t\t.command(\"doctor\")\r\n\t\t.description(\"Validate Workers AI and Vectorize configuration.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst options = resolveOptions(args);\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst report = await service.doctor({\r\n\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(report);\r\n\t\t\t} else {\r\n\t\t\t\tprintCheckReport(report);\r\n\t\t\t}\r\n\t\t\tif (!report.ok) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"init\")\r\n\t\t.description(\"Initialize the Cloudflare Vectorize index for the configured embedding model.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst options = resolveOptions(args);\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst report = await service.initializeIndex();\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(report);\r\n\t\t\t} else {\r\n\t\t\t\tprintCheckReport(report);\r\n\t\t\t}\r\n\t\t\tif (!report.ok) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"test\")\r\n\t\t.description(\"Run an end-to-end embedding and semantic-search smoke test.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst options = resolveOptions(args);\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst report = await service.runSmokeTest();\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(report);\r\n\t\t\t} else {\r\n\t\t\t\tprintCheckReport(report);\r\n\t\t\t}\r\n\t\t\tif (!report.ok) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"search\")\r\n\t\t.description(\"Search stored Cloudflare memory.\")\r\n\t\t.argument(\"<query>\", \"Semantic search query.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--limit <count>\", \"Maximum number of results.\")\r\n\t\t.option(\"--filter <json>\", \"Optional metadata filter JSON.\")\r\n\t\t.action(async (query, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst results = await service.search({\r\n\t\t\t\tquery: String(query),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\tmaxResults: options.limit ? Number(options.limit) : undefined,\r\n\t\t\t\tfilter: parseFilterFlag(options.filter as string | undefined),\r\n\t\t\t});\r\n\t\t\tprintJson(results);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"upsert\")\r\n\t\t.description(\"Insert or update a memory record.\")\r\n\t\t.argument(\"<text>\", \"Memory text.\")\r\n\t\t.option(\"--id <id>\", \"Stable logical id.\")\r\n\t\t.option(\"--title <title>\", \"Optional title.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.option(\"--source <source>\", \"Optional source label.\")\r\n\t\t.option(\"--metadata <json>\", \"Optional metadata JSON object.\")\r\n\t\t.action(async (text, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst result = await service.upsert({\r\n\t\t\t\tinput: {\r\n\t\t\t\t\tid: options.id as string | undefined,\r\n\t\t\t\t\ttitle: options.title as string | undefined,\r\n\t\t\t\t\ttext: String(text),\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tsource: options.source as string | undefined,\r\n\t\t\t\t\tmetadata: parseMetadataFlag(options.metadata as string | undefined),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tprintJson(result);\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"delete\")\r\n\t\t.description(\"Delete a memory record.\")\r\n\t\t.argument(\"<id>\", \"Logical memory record id.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Optional namespace override.\")\r\n\t\t.action(async (id, opts) => {\r\n\t\t\tconst options = opts as Record<string, unknown>;\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst mutationId = await service.delete({\r\n\t\t\t\tid: String(id),\r\n\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t});\r\n\t\t\tprintJson({ id, mutationId });\r\n\t\t});\r\n\r\n\troot\r\n\t\t.command(\"migrate\")\r\n\t\t.description(\"Migrate legacy markdown memory into Cloudflare Vectorize.\")\r\n\t\t.argument(\"[sources...]\", \"Markdown files, directories, or glob patterns. Defaults to the current OpenClaw memory corpus when omitted.\")\r\n\t\t.option(\"--workspace <path>\", \"Workspace root used for default-provider discovery and relative path normalization.\")\r\n\t\t.option(\"--namespace <namespace>\", \"Target namespace override.\")\r\n\t\t.option(\"--derive-namespace-from-path\", \"Derive namespaces from the first relative path segment instead of using a single target namespace.\")\r\n\t\t.option(\"--if-exists <strategy>\", \"Duplicate handling: overwrite, skip, or fail.\")\r\n\t\t.option(\"--create-index\", \"Create the Vectorize index if missing.\")\r\n\t\t.option(\"--dry-run\", \"Plan the migration without writing records.\")\r\n\t\t.option(\"--json\", \"Print structured JSON output.\")\r\n\t\t.action(async (...args) => {\r\n\t\t\tconst { positionals, options } = resolveInvocation(args);\r\n\t\t\tconst rawSources = positionals[0];\r\n\t\t\tconst sourcePaths =\r\n\t\t\t\tpositionals.length === 0 ? [] : Array.isArray(rawSources) ? rawSources.map((value) => String(value)) : positionals.map((value) => String(value));\r\n\t\t\tconst service = await createCloudflareMemoryService({\r\n\t\t\t\tpluginConfig: params.pluginConfig,\r\n\t\t\t\topenClawConfig: params.openClawConfig,\r\n\t\t\t\tenv: process.env,\r\n\t\t\t\tresolvePath: params.resolvePath,\r\n\t\t\t});\r\n\t\t\tconst summary = await runCloudflareMemoryMigration({\r\n\t\t\t\tservice,\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tsourcePaths,\r\n\t\t\t\t\tworkspaceDir: options.workspace as string | undefined,\r\n\t\t\t\t\tnamespace: options.namespace as string | undefined,\r\n\t\t\t\t\tnamespaceStrategy: options.deriveNamespaceFromPath ? \"path\" : \"single-target\",\r\n\t\t\t\t\tduplicateStrategy: parseDuplicateStrategy(options.ifExists),\r\n\t\t\t\t\tdryRun: Boolean(options.dryRun),\r\n\t\t\t\t\tcreateIndexIfMissing: Boolean(options.createIndex),\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tif (options.json) {\r\n\t\t\t\tprintJson(summary);\r\n\t\t\t} else {\r\n\t\t\t\tconsole.log(formatMigrationSummary(summary));\r\n\t\t\t}\r\n\t\t\tif (summary.failed > 0) {\r\n\t\t\t\tprocess.exitCode = 1;\r\n\t\t\t}\r\n\t\t});\r\n}\r\n"],"mappings":"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"}
package/dist/doctor.js CHANGED
@@ -6,26 +6,46 @@ async function e(e) {
6
6
  status: "pass",
7
7
  message: `Using Cloudflare account ${e.service.config.accountId} and Vectorize index ${e.service.config.indexName}.`
8
8
  });
9
- let n = await e.service.ensureIndexExists(e.createIndexIfMissing);
9
+ let n = await e.service.inspectEmbeddingDimensions();
10
10
  t.push({
11
- name: "vectorize-index",
11
+ name: "workers-ai-embeddings",
12
12
  status: "pass",
13
- message: n.created ? `Created Vectorize index "${e.service.config.indexName}" with ${n.dimensions} dimensions.` : `Vectorize index "${e.service.config.indexName}" is reachable.`
13
+ message: `Workers AI model ${e.service.config.model} returned ${n.embeddingDimensions} dimensions.`
14
+ }), n.configuredDimensions !== void 0 && t.push({
15
+ name: "create-index-dimensions",
16
+ status: n.configuredDimensionsMatchModel ? "pass" : "warn",
17
+ message: n.configuredDimensionsMatchModel ? `Configured createIndex.dimensions matches the embedding model (${n.embeddingDimensions}).` : `Configured createIndex.dimensions (${n.configuredDimensions}) does not match the embedding model (${n.embeddingDimensions}). Index creation uses the live embedding dimensions.`
14
18
  });
15
- let r = await e.service.embeddings.probeDimensions();
16
- return t.push({
17
- name: "workers-ai-embeddings",
19
+ let r;
20
+ if (e.createIndexIfMissing) r = await e.service.ensureIndexExists(!0, n.targetDimensions);
21
+ else {
22
+ let t = await e.service.describeIndexIfExists();
23
+ t && (r = {
24
+ created: !1,
25
+ dimensions: t.config.dimensions
26
+ });
27
+ }
28
+ return r ? (t.push({
29
+ name: "vectorize-index",
18
30
  status: "pass",
19
- message: `Workers AI model ${e.service.config.model} returned ${r} dimensions.`
20
- }), r === n.dimensions ? t.push({
31
+ message: r.created ? `Created Vectorize index "${e.service.config.indexName}" with ${r.dimensions} dimensions.` : `Vectorize index "${e.service.config.indexName}" is reachable.`
32
+ }), n.embeddingDimensions === r.dimensions ? t.push({
21
33
  name: "dimension-match",
22
34
  status: "pass",
23
35
  message: "Embedding dimensions match the Vectorize index."
24
36
  }) : t.push({
25
37
  name: "dimension-match",
26
38
  status: "fail",
27
- message: `Embedding dimensions (${r}) do not match the Vectorize index dimensions (${n.dimensions}).`
39
+ message: `Embedding dimensions (${n.embeddingDimensions}) do not match the Vectorize index dimensions (${r.dimensions}).`
40
+ })) : (t.push({
41
+ name: "vectorize-index",
42
+ status: "fail",
43
+ message: `Vectorize index "${e.service.config.indexName}" was not found. Run "openclaw cf-memory init" or rerun doctor with --create-index.`
28
44
  }), t.push({
45
+ name: "dimension-match",
46
+ status: "warn",
47
+ message: "Skipped dimension comparison because the Vectorize index does not exist yet."
48
+ })), t.push({
29
49
  name: "metadata-filters",
30
50
  status: e.service.config.metadataIndexedFields.length > 0 ? "pass" : "warn",
31
51
  message: e.service.config.metadataIndexedFields.length > 0 ? `Configured metadata-index guidance for: ${e.service.config.metadataIndexedFields.join(", ")}.` : "No metadataIndexedFields configured. Add metadata indexes in Cloudflare before relying on filter-heavy queries."
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.js","names":[],"sources":["../src/doctor.ts"],"sourcesContent":["import type { CloudflareMemoryService } from \"./service.js\";\nimport type { DoctorCheck, DoctorReport } from \"./types.js\";\n\nexport async function runDoctor(params: { service: CloudflareMemoryService; createIndexIfMissing: boolean }): Promise<DoctorReport> {\n\tconst checks: DoctorCheck[] = [];\n\n\tchecks.push({\n\t\tname: \"credentials\",\n\t\tstatus: \"pass\",\n\t\tmessage: `Using Cloudflare account ${params.service.config.accountId} and Vectorize index ${params.service.config.indexName}.`,\n\t});\n\n\tconst indexResult = await params.service.ensureIndexExists(params.createIndexIfMissing);\n\tchecks.push({\n\t\tname: \"vectorize-index\",\n\t\tstatus: \"pass\",\n\t\tmessage: indexResult.created\n\t\t\t? `Created Vectorize index \"${params.service.config.indexName}\" with ${indexResult.dimensions} dimensions.`\n\t\t\t: `Vectorize index \"${params.service.config.indexName}\" is reachable.`,\n\t});\n\n\tconst embeddingDimensions = await params.service.embeddings.probeDimensions();\n\tchecks.push({\n\t\tname: \"workers-ai-embeddings\",\n\t\tstatus: \"pass\",\n\t\tmessage: `Workers AI model ${params.service.config.model} returned ${embeddingDimensions} dimensions.`,\n\t});\n\n\tif (embeddingDimensions !== indexResult.dimensions) {\n\t\tchecks.push({\n\t\t\tname: \"dimension-match\",\n\t\t\tstatus: \"fail\",\n\t\t\tmessage: `Embedding dimensions (${embeddingDimensions}) do not match the Vectorize index dimensions (${indexResult.dimensions}).`,\n\t\t});\n\t} else {\n\t\tchecks.push({\n\t\t\tname: \"dimension-match\",\n\t\t\tstatus: \"pass\",\n\t\t\tmessage: \"Embedding dimensions match the Vectorize index.\",\n\t\t});\n\t}\n\n\tchecks.push({\n\t\tname: \"metadata-filters\",\n\t\tstatus: params.service.config.metadataIndexedFields.length > 0 ? \"pass\" : \"warn\",\n\t\tmessage:\n\t\t\tparams.service.config.metadataIndexedFields.length > 0\n\t\t\t\t? `Configured metadata-index guidance for: ${params.service.config.metadataIndexedFields.join(\", \")}.`\n\t\t\t\t: \"No metadataIndexedFields configured. Add metadata indexes in Cloudflare before relying on filter-heavy queries.\",\n\t});\n\n\tconst ok = checks.every((check) => check.status !== \"fail\");\n\treturn { ok, checks };\n}\n"],"mappings":";AAGA,eAAsB,EAAU,GAAoG;CACnI,IAAM,IAAwB,EAAE;AAEhC,GAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,4BAA4B,EAAO,QAAQ,OAAO,UAAU,uBAAuB,EAAO,QAAQ,OAAO,UAAU;EAC5H,CAAC;CAEF,IAAM,IAAc,MAAM,EAAO,QAAQ,kBAAkB,EAAO,qBAAqB;AACvF,GAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,EAAY,UAClB,4BAA4B,EAAO,QAAQ,OAAO,UAAU,SAAS,EAAY,WAAW,gBAC5F,oBAAoB,EAAO,QAAQ,OAAO,UAAU;EACvD,CAAC;CAEF,IAAM,IAAsB,MAAM,EAAO,QAAQ,WAAW,iBAAiB;AA+B7E,QA9BA,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,oBAAoB,EAAO,QAAQ,OAAO,MAAM,YAAY,EAAoB;EACzF,CAAC,EAEE,MAAwB,EAAY,aAOvC,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS;EACT,CAAC,GAVF,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,yBAAyB,EAAoB,iDAAiD,EAAY,WAAW;EAC9H,CAAC,EASH,EAAO,KAAK;EACX,MAAM;EACN,QAAQ,EAAO,QAAQ,OAAO,sBAAsB,SAAS,IAAI,SAAS;EAC1E,SACC,EAAO,QAAQ,OAAO,sBAAsB,SAAS,IAClD,2CAA2C,EAAO,QAAQ,OAAO,sBAAsB,KAAK,KAAK,CAAC,KAClG;EACJ,CAAC,EAGK;EAAE,IADE,EAAO,OAAO,MAAU,EAAM,WAAW,OAAO;EAC9C;EAAQ"}
1
+ {"version":3,"file":"doctor.js","names":[],"sources":["../src/doctor.ts"],"sourcesContent":["import type { CloudflareMemoryService } from \"./service.js\";\r\nimport type { DoctorCheck, DoctorReport } from \"./types.js\";\r\n\r\nexport async function runDoctor(params: { service: CloudflareMemoryService; createIndexIfMissing: boolean }): Promise<DoctorReport> {\r\n\tconst checks: DoctorCheck[] = [];\r\n\r\n\tchecks.push({\r\n\t\tname: \"credentials\",\r\n\t\tstatus: \"pass\",\r\n\t\tmessage: `Using Cloudflare account ${params.service.config.accountId} and Vectorize index ${params.service.config.indexName}.`,\r\n\t});\r\n\r\n\tconst embedding = await params.service.inspectEmbeddingDimensions();\r\n\tchecks.push({\r\n\t\tname: \"workers-ai-embeddings\",\r\n\t\tstatus: \"pass\",\r\n\t\tmessage: `Workers AI model ${params.service.config.model} returned ${embedding.embeddingDimensions} dimensions.`,\r\n\t});\r\n\tif (embedding.configuredDimensions !== undefined) {\r\n\t\tchecks.push({\r\n\t\t\tname: \"create-index-dimensions\",\r\n\t\t\tstatus: embedding.configuredDimensionsMatchModel ? \"pass\" : \"warn\",\r\n\t\t\tmessage: embedding.configuredDimensionsMatchModel\r\n\t\t\t\t? `Configured createIndex.dimensions matches the embedding model (${embedding.embeddingDimensions}).`\r\n\t\t\t\t: `Configured createIndex.dimensions (${embedding.configuredDimensions}) does not match the embedding model (${embedding.embeddingDimensions}). Index creation uses the live embedding dimensions.`,\r\n\t\t});\r\n\t}\r\n\r\n\tlet indexResult:\r\n\t\t| {\r\n\t\t\t\tcreated: boolean;\r\n\t\t\t\tdimensions: number;\r\n\t\t }\r\n\t\t| undefined;\r\n\tif (params.createIndexIfMissing) {\r\n\t\tindexResult = await params.service.ensureIndexExists(true, embedding.targetDimensions);\r\n\t} else {\r\n\t\tconst existingIndex = await params.service.describeIndexIfExists();\r\n\t\tif (existingIndex) {\r\n\t\t\tindexResult = {\r\n\t\t\t\tcreated: false,\r\n\t\t\t\tdimensions: existingIndex.config.dimensions,\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\tif (!indexResult) {\r\n\t\tchecks.push({\r\n\t\t\tname: \"vectorize-index\",\r\n\t\t\tstatus: \"fail\",\r\n\t\t\tmessage: `Vectorize index \"${params.service.config.indexName}\" was not found. Run \"openclaw cf-memory init\" or rerun doctor with --create-index.`,\r\n\t\t});\r\n\t\tchecks.push({\r\n\t\t\tname: \"dimension-match\",\r\n\t\t\tstatus: \"warn\",\r\n\t\t\tmessage: \"Skipped dimension comparison because the Vectorize index does not exist yet.\",\r\n\t\t});\r\n\t} else {\r\n\t\tchecks.push({\r\n\t\t\tname: \"vectorize-index\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: indexResult.created\r\n\t\t\t\t? `Created Vectorize index \"${params.service.config.indexName}\" with ${indexResult.dimensions} dimensions.`\r\n\t\t\t\t: `Vectorize index \"${params.service.config.indexName}\" is reachable.`,\r\n\t\t});\r\n\t\tif (embedding.embeddingDimensions !== indexResult.dimensions) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"dimension-match\",\r\n\t\t\t\tstatus: \"fail\",\r\n\t\t\t\tmessage: `Embedding dimensions (${embedding.embeddingDimensions}) do not match the Vectorize index dimensions (${indexResult.dimensions}).`,\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"dimension-match\",\r\n\t\t\t\tstatus: \"pass\",\r\n\t\t\t\tmessage: \"Embedding dimensions match the Vectorize index.\",\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\tchecks.push({\r\n\t\tname: \"metadata-filters\",\r\n\t\tstatus: params.service.config.metadataIndexedFields.length > 0 ? \"pass\" : \"warn\",\r\n\t\tmessage:\r\n\t\t\tparams.service.config.metadataIndexedFields.length > 0\r\n\t\t\t\t? `Configured metadata-index guidance for: ${params.service.config.metadataIndexedFields.join(\", \")}.`\r\n\t\t\t\t: \"No metadataIndexedFields configured. Add metadata indexes in Cloudflare before relying on filter-heavy queries.\",\r\n\t});\r\n\r\n\tconst ok = checks.every((check) => check.status !== \"fail\");\r\n\treturn { ok, checks };\r\n}\r\n"],"mappings":";AAGA,eAAsB,EAAU,GAAoG;CACnI,IAAM,IAAwB,EAAE;AAEhC,GAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,4BAA4B,EAAO,QAAQ,OAAO,UAAU,uBAAuB,EAAO,QAAQ,OAAO,UAAU;EAC5H,CAAC;CAEF,IAAM,IAAY,MAAM,EAAO,QAAQ,4BAA4B;AAMnE,CALA,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,oBAAoB,EAAO,QAAQ,OAAO,MAAM,YAAY,EAAU,oBAAoB;EACnG,CAAC,EACE,EAAU,yBAAyB,KAAA,KACtC,EAAO,KAAK;EACX,MAAM;EACN,QAAQ,EAAU,iCAAiC,SAAS;EAC5D,SAAS,EAAU,iCAChB,kEAAkE,EAAU,oBAAoB,MAChG,sCAAsC,EAAU,qBAAqB,wCAAwC,EAAU,oBAAoB;EAC9I,CAAC;CAGH,IAAI;AAMJ,KAAI,EAAO,qBACV,KAAc,MAAM,EAAO,QAAQ,kBAAkB,IAAM,EAAU,iBAAiB;MAChF;EACN,IAAM,IAAgB,MAAM,EAAO,QAAQ,uBAAuB;AAClE,EAAI,MACH,IAAc;GACb,SAAS;GACT,YAAY,EAAc,OAAO;GACjC;;AAgDH,QA5CK,KAYJ,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,EAAY,UAClB,4BAA4B,EAAO,QAAQ,OAAO,UAAU,SAAS,EAAY,WAAW,gBAC5F,oBAAoB,EAAO,QAAQ,OAAO,UAAU;EACvD,CAAC,EACE,EAAU,wBAAwB,EAAY,aAOjD,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS;EACT,CAAC,GAVF,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,yBAAyB,EAAU,oBAAoB,iDAAiD,EAAY,WAAW;EACxI,CAAC,KAvBH,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS,oBAAoB,EAAO,QAAQ,OAAO,UAAU;EAC7D,CAAC,EACF,EAAO,KAAK;EACX,MAAM;EACN,QAAQ;EACR,SAAS;EACT,CAAC,GAwBH,EAAO,KAAK;EACX,MAAM;EACN,QAAQ,EAAO,QAAQ,OAAO,sBAAsB,SAAS,IAAI,SAAS;EAC1E,SACC,EAAO,QAAQ,OAAO,sBAAsB,SAAS,IAClD,2CAA2C,EAAO,QAAQ,OAAO,sBAAsB,KAAK,KAAK,CAAC,KAClG;EACJ,CAAC,EAGK;EAAE,IADE,EAAO,OAAO,MAAU,EAAM,WAAW,OAAO;EAC9C;EAAQ"}
package/dist/index.js CHANGED
@@ -43,31 +43,34 @@ function _() {
43
43
  }
44
44
  };
45
45
  }
46
- var v = g({
46
+ function v(e) {
47
+ e.registerCli(({ program: t }) => {
48
+ c(t, {
49
+ pluginConfig: e.pluginConfig,
50
+ openClawConfig: e.config,
51
+ resolvePath: e.resolvePath
52
+ });
53
+ }, { descriptors: [{
54
+ name: "cf-memory",
55
+ description: "Manage Cloudflare Vectorize memory",
56
+ hasSubcommands: !0
57
+ }] });
58
+ }
59
+ var y = g({
47
60
  id: n,
48
61
  name: r,
49
62
  description: t,
50
63
  kind: "memory",
51
64
  configSchema: a,
52
65
  register(e) {
53
- a.parse?.(e.pluginConfig ?? {}), e.registerMemoryEmbeddingProvider(_()), e.registerMemoryCapability({
66
+ a.parse?.(e.pluginConfig ?? {}), v(e), !(e.registrationMode === "cli-metadata" || typeof e.registerMemoryEmbeddingProvider != "function" || typeof e.registerMemoryCapability != "function" || typeof e.registerTool != "function") && (e.registerMemoryEmbeddingProvider(_()), e.registerMemoryCapability({
54
67
  promptBuilder: l,
55
68
  runtime: d({
56
69
  pluginConfig: e.pluginConfig,
57
70
  resolvePath: e.resolvePath
58
71
  }),
59
72
  publicArtifacts: u(e.pluginConfig, e.resolvePath)
60
- }), e.registerTool((t) => m(e.pluginConfig, t), { names: ["cloudflare_memory_search"] }), e.registerTool((t) => p(e.pluginConfig, t), { names: ["cloudflare_memory_get"] }), e.registerTool((t) => h(e.pluginConfig, t), { names: ["cloudflare_memory_upsert"] }), e.registerTool((t) => f(e.pluginConfig, t), { names: ["cloudflare_memory_delete"] }), e.registerCli(({ program: t }) => {
61
- c(t, {
62
- pluginConfig: e.pluginConfig,
63
- openClawConfig: e.config,
64
- resolvePath: e.resolvePath
65
- });
66
- }, { descriptors: [{
67
- name: "cf-memory",
68
- description: "Manage Cloudflare Vectorize memory",
69
- hasSubcommands: !0
70
- }] }), o({
73
+ }), e.registerTool((t) => m(e.pluginConfig, t), { names: ["cloudflare_memory_search"] }), e.registerTool((t) => p(e.pluginConfig, t), { names: ["cloudflare_memory_get"] }), e.registerTool((t) => h(e.pluginConfig, t), { names: ["cloudflare_memory_upsert"] }), e.registerTool((t) => f(e.pluginConfig, t), { names: ["cloudflare_memory_delete"] }), o({
71
74
  pluginConfig: e.pluginConfig,
72
75
  openClawConfig: e.config,
73
76
  env: process.env,
@@ -77,10 +80,10 @@ var v = g({
77
80
  }).catch((t) => {
78
81
  let r = t instanceof Error ? t.message : "Unknown configuration error.";
79
82
  e.logger.warn(`${n}: deferred config validation reported: ${r}`);
80
- });
83
+ }));
81
84
  }
82
85
  });
83
86
  //#endregion
84
- export { v as default };
87
+ export { y as default };
85
88
 
86
89
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { MemoryEmbeddingProviderAdapter } from \"openclaw/plugin-sdk/memory-core-host-engine-embeddings\";\nimport { definePluginEntry, type OpenClawPluginApi } from \"openclaw/plugin-sdk/plugin-entry\";\nimport { registerCloudflareMemoryCli } from \"./cli.js\";\nimport { getPluginConfigFromOpenClawConfig, pluginConfigSchema, resolvePluginConfig } from \"./config.js\";\nimport { DEFAULT_EMBEDDING_MODEL, PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from \"./constants.js\";\nimport { buildPromptSection } from \"./prompt.js\";\nimport { createPublicArtifactsProvider } from \"./public-artifacts.js\";\nimport { createMemoryRuntime } from \"./runtime.js\";\nimport { CloudflareMemoryService } from \"./service.js\";\nimport { createDeleteTool, createGetTool, createSearchTool, createUpsertTool } from \"./tools.js\";\n\nfunction createMemoryEmbeddingProviderAdapter(): MemoryEmbeddingProviderAdapter {\n\treturn {\n\t\tid: \"cloudflare-workers-ai\",\n\t\tdefaultModel: DEFAULT_EMBEDDING_MODEL,\n\t\ttransport: \"remote\",\n\t\tallowExplicitWhenConfiguredAuto: true,\n\t\tasync create(options) {\n\t\t\tconst pluginConfig = getPluginConfigFromOpenClawConfig(options.config);\n\t\t\tconst resolved = await resolvePluginConfig({\n\t\t\t\tpluginConfig,\n\t\t\t\topenClawConfig: options.config,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tconst service = new CloudflareMemoryService(\n\t\t\t\t{\n\t\t\t\t\t...resolved,\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tworkersAiBaseUrl: options.remote?.baseUrl && options.remote.baseUrl.trim().length > 0 ? options.remote.baseUrl : resolved.workersAiBaseUrl,\n\t\t\t\t\tapiToken: typeof options.remote?.apiKey === \"string\" && options.remote.apiKey.trim().length > 0 ? options.remote.apiKey : resolved.apiToken,\n\t\t\t\t},\n\t\t\t\toptions.config,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tprovider: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tembedQuery: (text) => service.embeddings.embedQuery(text),\n\t\t\t\t\tembedBatch: (texts) => service.embeddings.embedBatch(texts),\n\t\t\t\t},\n\t\t\t\truntime: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tcacheKeyData: {\n\t\t\t\t\t\taccountId: resolved.accountId,\n\t\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport default definePluginEntry({\n\tid: PLUGIN_ID,\n\tname: PLUGIN_NAME,\n\tdescription: PLUGIN_DESCRIPTION,\n\tkind: \"memory\",\n\tconfigSchema: pluginConfigSchema,\n\tregister(api: OpenClawPluginApi) {\n\t\tpluginConfigSchema.parse?.(api.pluginConfig ?? {});\n\n\t\tapi.registerMemoryEmbeddingProvider(createMemoryEmbeddingProviderAdapter());\n\t\tapi.registerMemoryCapability({\n\t\t\tpromptBuilder: buildPromptSection,\n\t\t\truntime: createMemoryRuntime({\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t}),\n\t\t\tpublicArtifacts: createPublicArtifactsProvider(api.pluginConfig, api.resolvePath),\n\t\t});\n\n\t\tapi.registerTool((ctx) => createSearchTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_search\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createGetTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_get\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createUpsertTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_upsert\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createDeleteTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_delete\"],\n\t\t});\n\n\t\tapi.registerCli(\n\t\t\t({ program }) => {\n\t\t\t\tregisterCloudflareMemoryCli(program, {\n\t\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\t\topenClawConfig: api.config,\n\t\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t\t});\n\t\t\t},\n\t\t\t{\n\t\t\t\tdescriptors: [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: \"cf-memory\",\n\t\t\t\t\t\tdescription: \"Manage Cloudflare Vectorize memory\",\n\t\t\t\t\t\thasSubcommands: true,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t);\n\n\t\tvoid resolvePluginConfig({\n\t\t\tpluginConfig: api.pluginConfig,\n\t\t\topenClawConfig: api.config,\n\t\t\tenv: process.env,\n\t\t\tresolvePath: api.resolvePath,\n\t\t})\n\t\t\t.then((resolved) => {\n\t\t\t\tapi.logger.info(`${PLUGIN_ID}: registered for index ${resolved.indexName} using model ${resolved.model}.`);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"Unknown configuration error.\";\n\t\t\t\tapi.logger.warn(`${PLUGIN_ID}: deferred config validation reported: ${message}`);\n\t\t\t});\n\t},\n});\n"],"mappings":";;;;;;;;;;AAWA,SAAS,IAAuE;AAC/E,QAAO;EACN,IAAI;EACJ,cAAc;EACd,WAAW;EACX,iCAAiC;EACjC,MAAM,OAAO,GAAS;GAErB,IAAM,IAAW,MAAM,EAAoB;IAC1C,cAFoB,EAAkC,EAAQ,OAAO;IAGrE,gBAAgB,EAAQ;IACxB,KAAK,QAAQ;IACb,CAAC,EACI,IAAU,IAAI,EACnB;IACC,GAAG;IACH,OAAO,EAAQ,SAAS,EAAS;IACjC,kBAAkB,EAAQ,QAAQ,WAAW,EAAQ,OAAO,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,UAAU,EAAS;IAC1H,UAAU,OAAO,EAAQ,QAAQ,UAAW,YAAY,EAAQ,OAAO,OAAO,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,SAAS,EAAS;IACnI,EACD,EAAQ,OACR;AACD,UAAO;IACN,UAAU;KACT,IAAI;KACJ,OAAO,EAAQ,SAAS,EAAS;KACjC,aAAa,MAAS,EAAQ,WAAW,WAAW,EAAK;KACzD,aAAa,MAAU,EAAQ,WAAW,WAAW,EAAM;KAC3D;IACD,SAAS;KACR,IAAI;KACJ,cAAc;MACb,WAAW,EAAS;MACpB,OAAO,EAAQ,SAAS,EAAS;MACjC;KACD;IACD;;EAEF;;AAGF,IAAA,IAAe,EAAkB;CAChC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,MAAM;CACN,cAAc;CACd,SAAS,GAAwB;AA6C3B,EA5CL,EAAmB,QAAQ,EAAI,gBAAgB,EAAE,CAAC,EAElD,EAAI,gCAAgC,GAAsC,CAAC,EAC3E,EAAI,yBAAyB;GAC5B,eAAe;GACf,SAAS,EAAoB;IAC5B,cAAc,EAAI;IAClB,aAAa,EAAI;IACjB,CAAC;GACF,iBAAiB,EAA8B,EAAI,cAAc,EAAI,YAAY;GACjF,CAAC,EAEF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAc,EAAI,cAAc,EAAI,EAAE,EAC/D,OAAO,CAAC,wBAAwB,EAChC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EAEF,EAAI,aACF,EAAE,iBAAc;AAChB,KAA4B,GAAS;IACpC,cAAc,EAAI;IAClB,gBAAgB,EAAI;IACpB,aAAa,EAAI;IACjB,CAAC;KAEH,EACC,aAAa,CACZ;GACC,MAAM;GACN,aAAa;GACb,gBAAgB;GAChB,CACD,EACD,CACD,EAEI,EAAoB;GACxB,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,KAAK,QAAQ;GACb,aAAa,EAAI;GACjB,CAAC,CACA,MAAM,MAAa;AACnB,KAAI,OAAO,KAAK,GAAG,EAAU,yBAAyB,EAAS,UAAU,eAAe,EAAS,MAAM,GAAG;IACzG,CACD,OAAO,MAAmB;GAC1B,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU;AACzD,KAAI,OAAO,KAAK,GAAG,EAAU,yCAAyC,IAAU;IAC/E;;CAEJ,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { MemoryEmbeddingProviderAdapter } from \"openclaw/plugin-sdk/memory-core-host-engine-embeddings\";\nimport { definePluginEntry, type OpenClawPluginApi } from \"openclaw/plugin-sdk/plugin-entry\";\nimport { registerCloudflareMemoryCli } from \"./cli.js\";\nimport { getPluginConfigFromOpenClawConfig, pluginConfigSchema, resolvePluginConfig } from \"./config.js\";\nimport { DEFAULT_EMBEDDING_MODEL, PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from \"./constants.js\";\nimport { buildPromptSection } from \"./prompt.js\";\nimport { createPublicArtifactsProvider } from \"./public-artifacts.js\";\nimport { createMemoryRuntime } from \"./runtime.js\";\nimport { CloudflareMemoryService } from \"./service.js\";\nimport { createDeleteTool, createGetTool, createSearchTool, createUpsertTool } from \"./tools.js\";\n\nfunction createMemoryEmbeddingProviderAdapter(): MemoryEmbeddingProviderAdapter {\n\treturn {\n\t\tid: \"cloudflare-workers-ai\",\n\t\tdefaultModel: DEFAULT_EMBEDDING_MODEL,\n\t\ttransport: \"remote\",\n\t\tallowExplicitWhenConfiguredAuto: true,\n\t\tasync create(options) {\n\t\t\tconst pluginConfig = getPluginConfigFromOpenClawConfig(options.config);\n\t\t\tconst resolved = await resolvePluginConfig({\n\t\t\t\tpluginConfig,\n\t\t\t\topenClawConfig: options.config,\n\t\t\t\tenv: process.env,\n\t\t\t});\n\t\t\tconst service = new CloudflareMemoryService(\n\t\t\t\t{\n\t\t\t\t\t...resolved,\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tworkersAiBaseUrl: options.remote?.baseUrl && options.remote.baseUrl.trim().length > 0 ? options.remote.baseUrl : resolved.workersAiBaseUrl,\n\t\t\t\t\tapiToken: typeof options.remote?.apiKey === \"string\" && options.remote.apiKey.trim().length > 0 ? options.remote.apiKey : resolved.apiToken,\n\t\t\t\t},\n\t\t\t\toptions.config,\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tprovider: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\tembedQuery: (text) => service.embeddings.embedQuery(text),\n\t\t\t\t\tembedBatch: (texts) => service.embeddings.embedBatch(texts),\n\t\t\t\t},\n\t\t\t\truntime: {\n\t\t\t\t\tid: \"cloudflare-workers-ai\",\n\t\t\t\t\tcacheKeyData: {\n\t\t\t\t\t\taccountId: resolved.accountId,\n\t\t\t\t\t\tmodel: options.model || resolved.model,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction registerCloudflareMemoryCliEntry(api: Pick<OpenClawPluginApi, \"registerCli\" | \"pluginConfig\" | \"config\" | \"resolvePath\">): void {\n\tapi.registerCli(\n\t\t({ program }) => {\n\t\t\tregisterCloudflareMemoryCli(program, {\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\topenClawConfig: api.config,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t});\n\t\t},\n\t\t{\n\t\t\tdescriptors: [\n\t\t\t\t{\n\t\t\t\t\tname: \"cf-memory\",\n\t\t\t\t\tdescription: \"Manage Cloudflare Vectorize memory\",\n\t\t\t\t\thasSubcommands: true,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t);\n}\n\nexport default definePluginEntry({\n\tid: PLUGIN_ID,\n\tname: PLUGIN_NAME,\n\tdescription: PLUGIN_DESCRIPTION,\n\tkind: \"memory\",\n\tconfigSchema: pluginConfigSchema,\n\tregister(api: OpenClawPluginApi) {\n\t\tpluginConfigSchema.parse?.(api.pluginConfig ?? {});\n\n\t\tregisterCloudflareMemoryCliEntry(api);\n\n\t\tif (\n\t\t\tapi.registrationMode === \"cli-metadata\" ||\n\t\t\ttypeof api.registerMemoryEmbeddingProvider !== \"function\" ||\n\t\t\ttypeof api.registerMemoryCapability !== \"function\" ||\n\t\t\ttypeof api.registerTool !== \"function\"\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tapi.registerMemoryEmbeddingProvider(createMemoryEmbeddingProviderAdapter());\n\t\tapi.registerMemoryCapability({\n\t\t\tpromptBuilder: buildPromptSection,\n\t\t\truntime: createMemoryRuntime({\n\t\t\t\tpluginConfig: api.pluginConfig,\n\t\t\t\tresolvePath: api.resolvePath,\n\t\t\t}),\n\t\t\tpublicArtifacts: createPublicArtifactsProvider(api.pluginConfig, api.resolvePath),\n\t\t});\n\n\t\tapi.registerTool((ctx) => createSearchTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_search\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createGetTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_get\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createUpsertTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_upsert\"],\n\t\t});\n\t\tapi.registerTool((ctx) => createDeleteTool(api.pluginConfig, ctx), {\n\t\t\tnames: [\"cloudflare_memory_delete\"],\n\t\t});\n\n\t\tvoid resolvePluginConfig({\n\t\t\tpluginConfig: api.pluginConfig,\n\t\t\topenClawConfig: api.config,\n\t\t\tenv: process.env,\n\t\t\tresolvePath: api.resolvePath,\n\t\t})\n\t\t\t.then((resolved) => {\n\t\t\t\tapi.logger.info(`${PLUGIN_ID}: registered for index ${resolved.indexName} using model ${resolved.model}.`);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"Unknown configuration error.\";\n\t\t\t\tapi.logger.warn(`${PLUGIN_ID}: deferred config validation reported: ${message}`);\n\t\t\t});\n\t},\n});\n"],"mappings":";;;;;;;;;;AAWA,SAAS,IAAuE;AAC/E,QAAO;EACN,IAAI;EACJ,cAAc;EACd,WAAW;EACX,iCAAiC;EACjC,MAAM,OAAO,GAAS;GAErB,IAAM,IAAW,MAAM,EAAoB;IAC1C,cAFoB,EAAkC,EAAQ,OAAO;IAGrE,gBAAgB,EAAQ;IACxB,KAAK,QAAQ;IACb,CAAC,EACI,IAAU,IAAI,EACnB;IACC,GAAG;IACH,OAAO,EAAQ,SAAS,EAAS;IACjC,kBAAkB,EAAQ,QAAQ,WAAW,EAAQ,OAAO,QAAQ,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,UAAU,EAAS;IAC1H,UAAU,OAAO,EAAQ,QAAQ,UAAW,YAAY,EAAQ,OAAO,OAAO,MAAM,CAAC,SAAS,IAAI,EAAQ,OAAO,SAAS,EAAS;IACnI,EACD,EAAQ,OACR;AACD,UAAO;IACN,UAAU;KACT,IAAI;KACJ,OAAO,EAAQ,SAAS,EAAS;KACjC,aAAa,MAAS,EAAQ,WAAW,WAAW,EAAK;KACzD,aAAa,MAAU,EAAQ,WAAW,WAAW,EAAM;KAC3D;IACD,SAAS;KACR,IAAI;KACJ,cAAc;MACb,WAAW,EAAS;MACpB,OAAO,EAAQ,SAAS,EAAS;MACjC;KACD;IACD;;EAEF;;AAGF,SAAS,EAAiC,GAA+F;AACxI,GAAI,aACF,EAAE,iBAAc;AAChB,IAA4B,GAAS;GACpC,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,aAAa,EAAI;GACjB,CAAC;IAEH,EACC,aAAa,CACZ;EACC,MAAM;EACN,aAAa;EACb,gBAAgB;EAChB,CACD,EACD,CACD;;AAGF,IAAA,IAAe,EAAkB;CAChC,IAAI;CACJ,MAAM;CACN,aAAa;CACb,MAAM;CACN,cAAc;CACd,SAAS,GAAwB;AAChC,IAAmB,QAAQ,EAAI,gBAAgB,EAAE,CAAC,EAElD,EAAiC,EAAI,EAGpC,IAAI,qBAAqB,kBACzB,OAAO,EAAI,mCAAoC,cAC/C,OAAO,EAAI,4BAA6B,cACxC,OAAO,EAAI,gBAAiB,gBAK7B,EAAI,gCAAgC,GAAsC,CAAC,EAC3E,EAAI,yBAAyB;GAC5B,eAAe;GACf,SAAS,EAAoB;IAC5B,cAAc,EAAI;IAClB,aAAa,EAAI;IACjB,CAAC;GACF,iBAAiB,EAA8B,EAAI,cAAc,EAAI,YAAY;GACjF,CAAC,EAEF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAc,EAAI,cAAc,EAAI,EAAE,EAC/D,OAAO,CAAC,wBAAwB,EAChC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EACF,EAAI,cAAc,MAAQ,EAAiB,EAAI,cAAc,EAAI,EAAE,EAClE,OAAO,CAAC,2BAA2B,EACnC,CAAC,EAEG,EAAoB;GACxB,cAAc,EAAI;GAClB,gBAAgB,EAAI;GACpB,KAAK,QAAQ;GACb,aAAa,EAAI;GACjB,CAAC,CACA,MAAM,MAAa;AACnB,KAAI,OAAO,KAAK,GAAG,EAAU,yBAAyB,EAAS,UAAU,eAAe,EAAS,MAAM,GAAG;IACzG,CACD,OAAO,MAAmB;GAC1B,IAAM,IAAU,aAAiB,QAAQ,EAAM,UAAU;AACzD,KAAI,OAAO,KAAK,GAAG,EAAU,yCAAyC,IAAU;IAC/E;;CAEJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","names":[],"sources":["../src/migration.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\r\nimport { glob, readFile, stat } from \"node:fs/promises\";\r\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\r\nimport { listMemoryFiles } from \"openclaw/plugin-sdk/memory-core\";\r\nimport { sanitizeNamespace } from \"./namespace.js\";\r\nimport type { CloudflareMemoryService } from \"./service.js\";\r\nimport type {\r\n\tDoctorReport,\r\n\tMetadataValue,\r\n\tMigrationDuplicateStrategy,\r\n\tMigrationNamespaceStrategy,\r\n\tMigrationResult,\r\n\tMigrationRunOptions,\r\n\tMigrationSourceMode,\r\n\tMigrationSummary,\r\n} from \"./types.js\";\r\n\r\ntype DiscoveredMigrationFile = {\r\n\tabsolutePath: string;\r\n\trelativePath: string;\r\n};\r\n\r\ntype ParsedMigrationRecord = {\r\n\tinput: {\r\n\t\tid: string;\r\n\t\ttext: string;\r\n\t\ttitle?: string;\r\n\t\tmetadata: Record<string, MetadataValue>;\r\n\t\tnamespace?: string;\r\n\t\tsource?: string;\r\n\t};\r\n\tsourcePath: string;\r\n\trelativePath: string;\r\n};\r\n\r\nconst MARKDOWN_EXTENSIONS = new Set([\".md\", \".markdown\"]);\r\nconst RESERVED_FRONTMATTER_FIELDS = new Set([\"id\", \"namespace\", \"source\", \"title\"]);\r\n\r\nfunction normalizePathForMetadata(value: string): string {\r\n\treturn value.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\r\n}\r\n\r\nfunction normalizeRelativePath(value: string): string {\r\n\tconst normalized = normalizePathForMetadata(value).replace(/^\\/+/, \"\");\r\n\treturn normalized || basename(value);\r\n}\r\n\r\nfunction isMarkdownFile(value: string): boolean {\r\n\treturn MARKDOWN_EXTENSIONS.has(extname(value).toLowerCase());\r\n}\r\n\r\nfunction hasGlobMagic(value: string): boolean {\r\n\treturn /[*?[\\]{}]/.test(value);\r\n}\r\n\r\nfunction shouldIgnoreDiscoveredFile(value: string): boolean {\r\n\tconst normalized = normalizePathForMetadata(value).toLowerCase();\r\n\treturn normalized.includes(\"/node_modules/\") || normalized.includes(\"/.git/\");\r\n}\r\n\r\nasync function statIfExists(path: string) {\r\n\ttry {\r\n\t\treturn await stat(path);\r\n\t} catch {\r\n\t\treturn null;\r\n\t}\r\n}\r\n\r\nasync function collectDirectoryMarkdownFiles(directory: string): Promise<string[]> {\r\n\tconst matches: string[] = [];\r\n\tfor await (const match of glob(\"**/*.{md,markdown}\", { cwd: directory })) {\r\n\t\tconst absolutePath = resolve(directory, match);\r\n\t\tif (!shouldIgnoreDiscoveredFile(absolutePath)) {\r\n\t\t\tmatches.push(absolutePath);\r\n\t\t}\r\n\t}\r\n\treturn matches;\r\n}\r\n\r\nasync function collectGlobMatches(pattern: string, workspaceDir: string): Promise<string[]> {\r\n\tconst matches: string[] = [];\r\n\tconst normalizedPattern = normalizePathForMetadata(pattern);\r\n\tconst iterator = isAbsolute(pattern) ? glob(normalizedPattern) : glob(normalizedPattern, { cwd: workspaceDir });\r\n\tfor await (const match of iterator) {\r\n\t\tconst absolutePath = isAbsolute(match) ? match : resolve(workspaceDir, match);\r\n\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(absolutePath)) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst fileStats = await statIfExists(absolutePath);\r\n\t\tif (fileStats?.isFile()) {\r\n\t\t\tmatches.push(absolutePath);\r\n\t\t}\r\n\t}\r\n\treturn matches;\r\n}\r\n\r\nexport async function discoverMigrationFiles(params: {\r\n\tworkspaceDir: string;\r\n\tsourceMode: MigrationSourceMode;\r\n\tsourcePaths?: string[];\r\n}): Promise<DiscoveredMigrationFile[]> {\r\n\tconst workspaceDir = resolve(params.workspaceDir);\r\n\tconst discovered = new Map<string, DiscoveredMigrationFile>();\r\n\r\n\tif (params.sourceMode === \"default-provider\") {\r\n\t\tfor (const relPath of await listMemoryFiles(workspaceDir)) {\r\n\t\t\tconst relativePath = normalizeRelativePath(relPath);\r\n\t\t\tconst absolutePath = resolve(workspaceDir, relativePath);\r\n\t\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(relativePath)) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\tconst fileStats = await statIfExists(absolutePath);\r\n\t\t\tif (fileStats?.isFile()) {\r\n\t\t\t\tdiscovered.set(absolutePath.toLowerCase(), {\r\n\t\t\t\t\tabsolutePath,\r\n\t\t\t\t\trelativePath,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tfor (const input of params.sourcePaths ?? []) {\r\n\t\tconst absoluteInput = resolve(workspaceDir, input);\r\n\t\tconst fileStats = await statIfExists(absoluteInput);\r\n\t\tlet matches: string[] = [];\r\n\t\tif (fileStats?.isDirectory()) {\r\n\t\t\tmatches = await collectDirectoryMarkdownFiles(absoluteInput);\r\n\t\t} else if (fileStats?.isFile()) {\r\n\t\t\tmatches = isMarkdownFile(absoluteInput) ? [absoluteInput] : [];\r\n\t\t} else if (hasGlobMagic(input)) {\r\n\t\t\tmatches = await collectGlobMatches(input, workspaceDir);\r\n\t\t}\r\n\r\n\t\tfor (const match of matches) {\r\n\t\t\tconst relativePath = normalizeRelativePath(relative(workspaceDir, match));\r\n\t\t\tdiscovered.set(match.toLowerCase(), {\r\n\t\t\t\tabsolutePath: match,\r\n\t\t\t\trelativePath,\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\treturn [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));\r\n}\r\n\r\nfunction parseFrontmatterValue(value: string): MetadataValue | undefined {\r\n\tconst trimmed = value.trim();\r\n\tif (!trimmed) {\r\n\t\treturn \"\";\r\n\t}\r\n\tif (/^\"(.*)\"$/.test(trimmed) || /^'(.*)'$/.test(trimmed)) {\r\n\t\treturn trimmed.slice(1, -1);\r\n\t}\r\n\tif (trimmed === \"true\") {\r\n\t\treturn true;\r\n\t}\r\n\tif (trimmed === \"false\") {\r\n\t\treturn false;\r\n\t}\r\n\tconst numeric = Number(trimmed);\r\n\tif (!Number.isNaN(numeric) && trimmed !== \"\") {\r\n\t\treturn numeric;\r\n\t}\r\n\tif (trimmed.startsWith(\"[\") || trimmed.startsWith(\"{\")) {\r\n\t\treturn undefined;\r\n\t}\r\n\treturn trimmed;\r\n}\r\n\r\nfunction parseFrontmatter(content: string): { body: string; attributes: Record<string, MetadataValue> } {\r\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\");\r\n\tif (!normalized.startsWith(\"---\\n\")) {\r\n\t\treturn { body: normalized, attributes: {} };\r\n\t}\r\n\r\n\tconst lines = normalized.split(\"\\n\");\r\n\tconst closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === \"---\");\r\n\tif (closingIndex === -1) {\r\n\t\treturn { body: normalized, attributes: {} };\r\n\t}\r\n\r\n\tconst attributes: Record<string, MetadataValue> = {};\r\n\tfor (const line of lines.slice(1, closingIndex)) {\r\n\t\tconst separatorIndex = line.indexOf(\":\");\r\n\t\tif (separatorIndex === -1) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst key = line.slice(0, separatorIndex).trim();\r\n\t\tif (!key) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tconst parsedValue = parseFrontmatterValue(line.slice(separatorIndex + 1));\r\n\t\tif (parsedValue !== undefined) {\r\n\t\t\tattributes[key] = parsedValue;\r\n\t\t}\r\n\t}\r\n\r\n\treturn {\r\n\t\tbody: lines.slice(closingIndex + 1).join(\"\\n\"),\r\n\t\tattributes,\r\n\t};\r\n}\r\n\r\nfunction extractHeadingTitleAndBody(content: string): { title?: string; text: string } {\r\n\tconst lines = content.split(\"\\n\");\r\n\tconst firstContentLine = lines.findIndex((line) => line.trim().length > 0);\r\n\tif (firstContentLine === -1) {\r\n\t\treturn { text: \"\" };\r\n\t}\r\n\r\n\tconst headingMatch = /^#\\s+(.+?)\\s*$/.exec(lines[firstContentLine]?.trim() ?? \"\");\r\n\tif (!headingMatch) {\r\n\t\treturn { text: content.trim() };\r\n\t}\r\n\r\n\tconst remainingLines = [...lines];\r\n\tremainingLines.splice(firstContentLine, 1);\r\n\tif ((remainingLines[firstContentLine] ?? \"\").trim() === \"\") {\r\n\t\tremainingLines.splice(firstContentLine, 1);\r\n\t}\r\n\tconst body = remainingLines.join(\"\\n\").trim();\r\n\treturn {\r\n\t\ttitle: headingMatch[1].trim(),\r\n\t\ttext: body || content.trim(),\r\n\t};\r\n}\r\n\r\nfunction buildStableLogicalId(relativePath: string): string {\r\n\tconst withoutExtension = relativePath.replace(/\\.(md|markdown)$/i, \"\");\r\n\tconst slug = withoutExtension\r\n\t\t.toLowerCase()\r\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\r\n\t\t.replace(/-{2,}/g, \"-\")\r\n\t\t.replace(/^-+|-+$/g, \"\")\r\n\t\t.slice(0, 80);\r\n\tconst hash = createHash(\"sha1\").update(relativePath).digest(\"hex\").slice(0, 10);\r\n\treturn `${slug || \"memory\"}-${hash}`;\r\n}\r\n\r\nfunction pickTitle(relativePath: string, frontmatterTitle: MetadataValue | undefined, headingTitle: string | undefined): string | undefined {\r\n\tif (typeof frontmatterTitle === \"string\" && frontmatterTitle.trim().length > 0) {\r\n\t\treturn frontmatterTitle.trim();\r\n\t}\r\n\tif (headingTitle) {\r\n\t\treturn headingTitle;\r\n\t}\r\n\tconst fileName = basename(relativePath, extname(relativePath)).trim();\r\n\treturn fileName || undefined;\r\n}\r\n\r\nfunction buildTargetNamespace(service: CloudflareMemoryService, options: MigrationRunOptions, workspaceDir: string): string {\r\n\treturn service.resolveNamespace({\r\n\t\tnamespace: options.namespace,\r\n\t\tworkspaceDir,\r\n\t});\r\n}\r\n\r\nfunction deriveRecordNamespace(params: {\r\n\trelativePath: string;\r\n\tfrontmatterNamespace: MetadataValue | undefined;\r\n\ttargetNamespace: string;\r\n\tnamespaceStrategy: MigrationNamespaceStrategy;\r\n}): string {\r\n\tif (params.namespaceStrategy === \"single-target\") {\r\n\t\treturn params.targetNamespace;\r\n\t}\r\n\r\n\tif (typeof params.frontmatterNamespace === \"string\" && params.frontmatterNamespace.trim().length > 0) {\r\n\t\treturn sanitizeNamespace(params.frontmatterNamespace);\r\n\t}\r\n\r\n\tconst firstSegment = normalizeRelativePath(params.relativePath).split(\"/\")[0];\r\n\tif (!firstSegment || firstSegment === \".\" || firstSegment === \"..\") {\r\n\t\treturn params.targetNamespace;\r\n\t}\r\n\treturn sanitizeNamespace(firstSegment);\r\n}\r\n\r\nexport async function parseMigrationFile(params: {\r\n\tfile: DiscoveredMigrationFile;\r\n\tsourceMode: MigrationSourceMode;\r\n\ttargetNamespace: string;\r\n\tnamespaceStrategy: MigrationNamespaceStrategy;\r\n}): Promise<ParsedMigrationRecord | null> {\r\n\tconst raw = await readFile(params.file.absolutePath, \"utf8\");\r\n\tconst { body, attributes } = parseFrontmatter(raw);\r\n\tconst { title: headingTitle, text: extractedText } = extractHeadingTitleAndBody(body);\r\n\tconst title = pickTitle(params.file.relativePath, attributes.title, headingTitle);\r\n\tconst text = extractedText.trim() || title || \"\";\r\n\tif (!text) {\r\n\t\treturn null;\r\n\t}\r\n\r\n\tconst metadata: Record<string, MetadataValue> = {\r\n\t\tlegacySourceMode: params.sourceMode,\r\n\t\tlegacySourcePath: params.file.relativePath.startsWith(\"..\") ? params.file.absolutePath : params.file.relativePath,\r\n\t};\r\n\tfor (const [key, value] of Object.entries(attributes)) {\r\n\t\tif (RESERVED_FRONTMATTER_FIELDS.has(key)) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tmetadata[key] = value;\r\n\t}\r\n\r\n\tconst logicalId =\r\n\t\ttypeof attributes.id === \"string\" && attributes.id.trim().length > 0 ? attributes.id.trim() : buildStableLogicalId(params.file.relativePath);\r\n\tconst namespace = deriveRecordNamespace({\r\n\t\trelativePath: params.file.relativePath,\r\n\t\tfrontmatterNamespace: attributes.namespace,\r\n\t\ttargetNamespace: params.targetNamespace,\r\n\t\tnamespaceStrategy: params.namespaceStrategy,\r\n\t});\r\n\tconst source =\r\n\t\ttypeof attributes.source === \"string\" && attributes.source.trim().length > 0\r\n\t\t\t? attributes.source.trim()\r\n\t\t\t: params.sourceMode === \"default-provider\"\r\n\t\t\t\t? \"openclaw-default-memory\"\r\n\t\t\t\t: \"markdown-import\";\r\n\r\n\treturn {\r\n\t\tsourcePath: params.file.absolutePath,\r\n\t\trelativePath: params.file.relativePath,\r\n\t\tinput: {\r\n\t\t\tid: logicalId,\r\n\t\t\tnamespace,\r\n\t\t\ttitle,\r\n\t\t\ttext,\r\n\t\t\tsource,\r\n\t\t\tmetadata,\r\n\t\t},\r\n\t};\r\n}\r\n\r\nfunction formatDoctorFailure(report: DoctorReport): string {\r\n\tconst failedChecks = report.checks.filter((check) => check.status === \"fail\");\r\n\treturn failedChecks.map((check) => `${check.name}: ${check.message}`).join(\" | \");\r\n}\r\n\r\nexport async function runCloudflareMemoryMigration(params: {\r\n\tservice: CloudflareMemoryService;\r\n\toptions?: MigrationRunOptions;\r\n}): Promise<MigrationSummary> {\r\n\tconst options = params.options ?? {};\r\n\tconst workspaceDir = resolve(options.workspaceDir ?? process.cwd());\r\n\tconst sourceMode: MigrationSourceMode = (options.sourcePaths?.length ?? 0) > 0 ? \"paths\" : \"default-provider\";\r\n\tconst namespaceStrategy = options.namespaceStrategy ?? \"single-target\";\r\n\tconst duplicateStrategy: MigrationDuplicateStrategy = options.duplicateStrategy ?? \"overwrite\";\r\n\tconst dryRun = options.dryRun ?? false;\r\n\tconst targetNamespace = buildTargetNamespace(params.service, options, workspaceDir);\r\n\tconst doctor = await params.service.doctor({\r\n\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\r\n\t});\r\n\tif (!doctor.ok) {\r\n\t\tthrow new Error(`Migration validation failed. ${formatDoctorFailure(doctor)}`);\r\n\t}\r\n\r\n\tconst discoveredFiles = await discoverMigrationFiles({\r\n\t\tworkspaceDir,\r\n\t\tsourceMode,\r\n\t\tsourcePaths: options.sourcePaths,\r\n\t});\r\n\tif (discoveredFiles.length === 0) {\r\n\t\tthrow new Error(\r\n\t\t\tsourceMode === \"default-provider\"\r\n\t\t\t\t? `No default OpenClaw markdown memory files were found under ${workspaceDir}.`\r\n\t\t\t\t: \"No markdown files matched the provided migration sources.\",\r\n\t\t);\r\n\t}\r\n\r\n\tconst results: MigrationResult[] = [];\r\n\tlet preparedRecords = 0;\r\n\tlet imported = 0;\r\n\tlet skipped = 0;\r\n\tlet failed = 0;\r\n\r\n\tfor (const file of discoveredFiles) {\r\n\t\ttry {\r\n\t\t\tconst parsed = await parseMigrationFile({\r\n\t\t\t\tfile,\r\n\t\t\t\tsourceMode,\r\n\t\t\t\ttargetNamespace,\r\n\t\t\t\tnamespaceStrategy,\r\n\t\t\t});\r\n\t\t\tif (!parsed) {\r\n\t\t\t\tskipped += 1;\r\n\t\t\t\tresults.push({\r\n\t\t\t\t\taction: \"skipped\",\r\n\t\t\t\t\tsourcePath: file.absolutePath,\r\n\t\t\t\t\trelativePath: file.relativePath,\r\n\t\t\t\t\treason: \"File did not contain any importable markdown content.\",\r\n\t\t\t\t});\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tpreparedRecords += 1;\r\n\t\t\tconst logicalId = parsed.input.id;\r\n\t\t\tconst namespace = parsed.input.namespace;\r\n\t\t\tif (!logicalId || !namespace) {\r\n\t\t\t\tthrow new Error(\"Parsed migration record is missing a logical id or namespace.\");\r\n\t\t\t}\r\n\r\n\t\t\tif (duplicateStrategy !== \"overwrite\") {\r\n\t\t\t\tconst existing = await params.service.get({\r\n\t\t\t\t\tid: logicalId,\r\n\t\t\t\t\tnamespace,\r\n\t\t\t\t});\r\n\t\t\t\tif (existing) {\r\n\t\t\t\t\tif (duplicateStrategy === \"skip\") {\r\n\t\t\t\t\t\tskipped += 1;\r\n\t\t\t\t\t\tresults.push({\r\n\t\t\t\t\t\t\taction: \"skipped\",\r\n\t\t\t\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\t\t\t\tlogicalId,\r\n\t\t\t\t\t\t\tnamespace,\r\n\t\t\t\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t\t\t\t\treason: \"A record with the same logical id already exists.\",\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthrow new Error(`A record with logical id ${logicalId} already exists in namespace ${namespace}.`);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (dryRun) {\r\n\t\t\t\tresults.push({\r\n\t\t\t\t\taction: \"would-import\",\r\n\t\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\t\tlogicalId,\r\n\t\t\t\t\tnamespace,\r\n\t\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t\t});\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tawait params.service.upsert({\r\n\t\t\t\tinput: parsed.input,\r\n\t\t\t});\r\n\t\t\timported += 1;\r\n\t\t\tresults.push({\r\n\t\t\t\taction: \"imported\",\r\n\t\t\t\tsourcePath: parsed.sourcePath,\r\n\t\t\t\trelativePath: parsed.relativePath,\r\n\t\t\t\tlogicalId,\r\n\t\t\t\tnamespace,\r\n\t\t\t\ttitle: parsed.input.title,\r\n\t\t\t});\r\n\t\t} catch (error) {\r\n\t\t\tfailed += 1;\r\n\t\t\tresults.push({\r\n\t\t\t\taction: \"failed\",\r\n\t\t\t\tsourcePath: file.absolutePath,\r\n\t\t\t\trelativePath: file.relativePath,\r\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown migration failure.\",\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\treturn {\r\n\t\tdryRun,\r\n\t\tsourceMode,\r\n\t\tworkspaceDir,\r\n\t\tnamespaceStrategy,\r\n\t\ttargetNamespace: namespaceStrategy === \"single-target\" ? targetNamespace : undefined,\r\n\t\tdiscoveredFiles: discoveredFiles.length,\r\n\t\tpreparedRecords,\r\n\t\timported,\r\n\t\tskipped,\r\n\t\tfailed,\r\n\t\tdoctor,\r\n\t\tresults,\r\n\t};\r\n}\r\n\r\nexport function formatMigrationSummary(summary: MigrationSummary): string {\r\n\tconst lines = [\r\n\t\t`${summary.dryRun ? \"Dry-run\" : \"Migration\"} ${summary.failed > 0 ? \"completed with failures\" : \"completed\"}.`,\r\n\t\t`Source mode: ${summary.sourceMode}`,\r\n\t\t`Workspace: ${summary.workspaceDir}`,\r\n\t\t`Files scanned: ${summary.discoveredFiles}`,\r\n\t\t`Records prepared: ${summary.preparedRecords}`,\r\n\t\t`Imported: ${summary.imported}`,\r\n\t\t`Skipped: ${summary.skipped}`,\r\n\t\t`Failed: ${summary.failed}`,\r\n\t];\r\n\r\n\tif (summary.targetNamespace) {\r\n\t\tlines.splice(3, 0, `Target namespace: ${summary.targetNamespace}`);\r\n\t}\r\n\r\n\tconst failedResults = summary.results.filter((result) => result.action === \"failed\").slice(0, 10);\r\n\tif (failedResults.length > 0) {\r\n\t\tlines.push(\"\", \"Failures:\");\r\n\t\tfor (const result of failedResults) {\r\n\t\t\tlines.push(`- ${result.relativePath}: ${result.error}`);\r\n\t\t}\r\n\t}\r\n\r\n\treturn lines.join(\"\\n\");\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"migration.js","names":[],"sources":["../src/migration.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { glob, readFile, stat } from \"node:fs/promises\";\nimport { basename, extname, isAbsolute, relative, resolve } from \"node:path\";\nimport { listMemoryFiles } from \"openclaw/plugin-sdk/memory-core\";\nimport { sanitizeNamespace } from \"./namespace.js\";\nimport type { CloudflareMemoryService } from \"./service.js\";\nimport type {\n\tDoctorReport,\n\tMetadataValue,\n\tMigrationDuplicateStrategy,\n\tMigrationNamespaceStrategy,\n\tMigrationResult,\n\tMigrationRunOptions,\n\tMigrationSourceMode,\n\tMigrationSummary,\n} from \"./types.js\";\n\ntype DiscoveredMigrationFile = {\n\tabsolutePath: string;\n\trelativePath: string;\n};\n\ntype ParsedMigrationRecord = {\n\tinput: {\n\t\tid: string;\n\t\ttext: string;\n\t\ttitle?: string;\n\t\tmetadata: Record<string, MetadataValue>;\n\t\tnamespace?: string;\n\t\tsource?: string;\n\t};\n\tsourcePath: string;\n\trelativePath: string;\n};\n\nconst MARKDOWN_EXTENSIONS = new Set([\".md\", \".markdown\"]);\nconst RESERVED_FRONTMATTER_FIELDS = new Set([\"id\", \"namespace\", \"source\", \"title\"]);\n\nfunction normalizePathForMetadata(value: string): string {\n\treturn value.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n}\n\nfunction normalizeRelativePath(value: string): string {\n\tconst normalized = normalizePathForMetadata(value).replace(/^\\/+/, \"\");\n\treturn normalized || basename(value);\n}\n\nfunction isMarkdownFile(value: string): boolean {\n\treturn MARKDOWN_EXTENSIONS.has(extname(value).toLowerCase());\n}\n\nfunction hasGlobMagic(value: string): boolean {\n\treturn /[*?[\\]{}]/.test(value);\n}\n\nfunction shouldIgnoreDiscoveredFile(value: string): boolean {\n\tconst normalized = normalizePathForMetadata(value).toLowerCase();\n\treturn normalized.includes(\"/node_modules/\") || normalized.includes(\"/.git/\");\n}\n\nasync function statIfExists(path: string) {\n\ttry {\n\t\treturn await stat(path);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function collectDirectoryMarkdownFiles(directory: string): Promise<string[]> {\n\tconst matches: string[] = [];\n\tfor await (const match of glob(\"**/*.{md,markdown}\", { cwd: directory })) {\n\t\tconst absolutePath = resolve(directory, match);\n\t\tif (!shouldIgnoreDiscoveredFile(absolutePath)) {\n\t\t\tmatches.push(absolutePath);\n\t\t}\n\t}\n\treturn matches;\n}\n\nasync function collectGlobMatches(pattern: string, workspaceDir: string): Promise<string[]> {\n\tconst matches: string[] = [];\n\tconst normalizedPattern = normalizePathForMetadata(pattern);\n\tconst iterator = isAbsolute(pattern) ? glob(normalizedPattern) : glob(normalizedPattern, { cwd: workspaceDir });\n\tfor await (const match of iterator) {\n\t\tconst absolutePath = isAbsolute(match) ? match : resolve(workspaceDir, match);\n\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(absolutePath)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst fileStats = await statIfExists(absolutePath);\n\t\tif (fileStats?.isFile()) {\n\t\t\tmatches.push(absolutePath);\n\t\t}\n\t}\n\treturn matches;\n}\n\nexport async function discoverMigrationFiles(params: {\n\tworkspaceDir: string;\n\tsourceMode: MigrationSourceMode;\n\tsourcePaths?: string[];\n}): Promise<DiscoveredMigrationFile[]> {\n\tconst workspaceDir = resolve(params.workspaceDir);\n\tconst discovered = new Map<string, DiscoveredMigrationFile>();\n\n\tif (params.sourceMode === \"default-provider\") {\n\t\tfor (const relPath of await listMemoryFiles(workspaceDir)) {\n\t\t\tconst relativePath = normalizeRelativePath(relPath);\n\t\t\tconst absolutePath = resolve(workspaceDir, relativePath);\n\t\t\tif (shouldIgnoreDiscoveredFile(absolutePath) || !isMarkdownFile(relativePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst fileStats = await statIfExists(absolutePath);\n\t\t\tif (fileStats?.isFile()) {\n\t\t\t\tdiscovered.set(absolutePath.toLowerCase(), {\n\t\t\t\t\tabsolutePath,\n\t\t\t\t\trelativePath,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (const input of params.sourcePaths ?? []) {\n\t\tconst absoluteInput = resolve(workspaceDir, input);\n\t\tconst fileStats = await statIfExists(absoluteInput);\n\t\tlet matches: string[] = [];\n\t\tif (fileStats?.isDirectory()) {\n\t\t\tmatches = await collectDirectoryMarkdownFiles(absoluteInput);\n\t\t} else if (fileStats?.isFile()) {\n\t\t\tmatches = isMarkdownFile(absoluteInput) ? [absoluteInput] : [];\n\t\t} else if (hasGlobMagic(input)) {\n\t\t\tmatches = await collectGlobMatches(input, workspaceDir);\n\t\t}\n\n\t\tfor (const match of matches) {\n\t\t\tconst relativePath = normalizeRelativePath(relative(workspaceDir, match));\n\t\t\tdiscovered.set(match.toLowerCase(), {\n\t\t\t\tabsolutePath: match,\n\t\t\t\trelativePath,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn [...discovered.values()].sort((left, right) => left.relativePath.localeCompare(right.relativePath));\n}\n\nfunction parseFrontmatterValue(value: string): MetadataValue | undefined {\n\tconst trimmed = value.trim();\n\tif (!trimmed) {\n\t\treturn \"\";\n\t}\n\tif (/^\"(.*)\"$/.test(trimmed) || /^'(.*)'$/.test(trimmed)) {\n\t\treturn trimmed.slice(1, -1);\n\t}\n\tif (trimmed === \"true\") {\n\t\treturn true;\n\t}\n\tif (trimmed === \"false\") {\n\t\treturn false;\n\t}\n\tconst numeric = Number(trimmed);\n\tif (!Number.isNaN(numeric) && trimmed !== \"\") {\n\t\treturn numeric;\n\t}\n\tif (trimmed.startsWith(\"[\") || trimmed.startsWith(\"{\")) {\n\t\treturn undefined;\n\t}\n\treturn trimmed;\n}\n\nfunction parseFrontmatter(content: string): { body: string; attributes: Record<string, MetadataValue> } {\n\tconst normalized = content.replace(/\\r\\n/g, \"\\n\");\n\tif (!normalized.startsWith(\"---\\n\")) {\n\t\treturn { body: normalized, attributes: {} };\n\t}\n\n\tconst lines = normalized.split(\"\\n\");\n\tconst closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === \"---\");\n\tif (closingIndex === -1) {\n\t\treturn { body: normalized, attributes: {} };\n\t}\n\n\tconst attributes: Record<string, MetadataValue> = {};\n\tfor (const line of lines.slice(1, closingIndex)) {\n\t\tconst separatorIndex = line.indexOf(\":\");\n\t\tif (separatorIndex === -1) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = line.slice(0, separatorIndex).trim();\n\t\tif (!key) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst parsedValue = parseFrontmatterValue(line.slice(separatorIndex + 1));\n\t\tif (parsedValue !== undefined) {\n\t\t\tattributes[key] = parsedValue;\n\t\t}\n\t}\n\n\treturn {\n\t\tbody: lines.slice(closingIndex + 1).join(\"\\n\"),\n\t\tattributes,\n\t};\n}\n\nfunction extractHeadingTitleAndBody(content: string): { title?: string; text: string } {\n\tconst lines = content.split(\"\\n\");\n\tconst firstContentLine = lines.findIndex((line) => line.trim().length > 0);\n\tif (firstContentLine === -1) {\n\t\treturn { text: \"\" };\n\t}\n\n\tconst headingMatch = /^#\\s+(.+?)\\s*$/.exec(lines[firstContentLine]?.trim() ?? \"\");\n\tif (!headingMatch) {\n\t\treturn { text: content.trim() };\n\t}\n\n\tconst remainingLines = [...lines];\n\tremainingLines.splice(firstContentLine, 1);\n\tif ((remainingLines[firstContentLine] ?? \"\").trim() === \"\") {\n\t\tremainingLines.splice(firstContentLine, 1);\n\t}\n\tconst body = remainingLines.join(\"\\n\").trim();\n\treturn {\n\t\ttitle: headingMatch[1].trim(),\n\t\ttext: body || content.trim(),\n\t};\n}\n\nfunction buildStableLogicalId(relativePath: string): string {\n\tconst withoutExtension = relativePath.replace(/\\.(md|markdown)$/i, \"\");\n\tconst slug = withoutExtension\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, \"-\")\n\t\t.replace(/-{2,}/g, \"-\")\n\t\t.replace(/^-+|-+$/g, \"\")\n\t\t.slice(0, 80);\n\tconst hash = createHash(\"sha1\").update(relativePath).digest(\"hex\").slice(0, 10);\n\treturn `${slug || \"memory\"}-${hash}`;\n}\n\nfunction pickTitle(relativePath: string, frontmatterTitle: MetadataValue | undefined, headingTitle: string | undefined): string | undefined {\n\tif (typeof frontmatterTitle === \"string\" && frontmatterTitle.trim().length > 0) {\n\t\treturn frontmatterTitle.trim();\n\t}\n\tif (headingTitle) {\n\t\treturn headingTitle;\n\t}\n\tconst fileName = basename(relativePath, extname(relativePath)).trim();\n\treturn fileName || undefined;\n}\n\nfunction buildTargetNamespace(service: CloudflareMemoryService, options: MigrationRunOptions, workspaceDir: string): string {\n\treturn service.resolveNamespace({\n\t\tnamespace: options.namespace,\n\t\tworkspaceDir,\n\t});\n}\n\nfunction deriveRecordNamespace(params: {\n\trelativePath: string;\n\tfrontmatterNamespace: MetadataValue | undefined;\n\ttargetNamespace: string;\n\tnamespaceStrategy: MigrationNamespaceStrategy;\n}): string {\n\tif (params.namespaceStrategy === \"single-target\") {\n\t\treturn params.targetNamespace;\n\t}\n\n\tif (typeof params.frontmatterNamespace === \"string\" && params.frontmatterNamespace.trim().length > 0) {\n\t\treturn sanitizeNamespace(params.frontmatterNamespace);\n\t}\n\n\tconst firstSegment = normalizeRelativePath(params.relativePath).split(\"/\")[0];\n\tif (!firstSegment || firstSegment === \".\" || firstSegment === \"..\") {\n\t\treturn params.targetNamespace;\n\t}\n\treturn sanitizeNamespace(firstSegment);\n}\n\nexport async function parseMigrationFile(params: {\n\tfile: DiscoveredMigrationFile;\n\tsourceMode: MigrationSourceMode;\n\ttargetNamespace: string;\n\tnamespaceStrategy: MigrationNamespaceStrategy;\n}): Promise<ParsedMigrationRecord | null> {\n\tconst raw = await readFile(params.file.absolutePath, \"utf8\");\n\tconst { body, attributes } = parseFrontmatter(raw);\n\tconst { title: headingTitle, text: extractedText } = extractHeadingTitleAndBody(body);\n\tconst title = pickTitle(params.file.relativePath, attributes.title, headingTitle);\n\tconst text = extractedText.trim() || title || \"\";\n\tif (!text) {\n\t\treturn null;\n\t}\n\n\tconst metadata: Record<string, MetadataValue> = {\n\t\tlegacySourceMode: params.sourceMode,\n\t\tlegacySourcePath: params.file.relativePath.startsWith(\"..\") ? params.file.absolutePath : params.file.relativePath,\n\t};\n\tfor (const [key, value] of Object.entries(attributes)) {\n\t\tif (RESERVED_FRONTMATTER_FIELDS.has(key)) {\n\t\t\tcontinue;\n\t\t}\n\t\tmetadata[key] = value;\n\t}\n\n\tconst logicalId =\n\t\ttypeof attributes.id === \"string\" && attributes.id.trim().length > 0 ? attributes.id.trim() : buildStableLogicalId(params.file.relativePath);\n\tconst namespace = deriveRecordNamespace({\n\t\trelativePath: params.file.relativePath,\n\t\tfrontmatterNamespace: attributes.namespace,\n\t\ttargetNamespace: params.targetNamespace,\n\t\tnamespaceStrategy: params.namespaceStrategy,\n\t});\n\tconst source =\n\t\ttypeof attributes.source === \"string\" && attributes.source.trim().length > 0\n\t\t\t? attributes.source.trim()\n\t\t\t: params.sourceMode === \"default-provider\"\n\t\t\t\t? \"openclaw-default-memory\"\n\t\t\t\t: \"markdown-import\";\n\n\treturn {\n\t\tsourcePath: params.file.absolutePath,\n\t\trelativePath: params.file.relativePath,\n\t\tinput: {\n\t\t\tid: logicalId,\n\t\t\tnamespace,\n\t\t\ttitle,\n\t\t\ttext,\n\t\t\tsource,\n\t\t\tmetadata,\n\t\t},\n\t};\n}\n\nfunction formatDoctorFailure(report: DoctorReport): string {\n\tconst failedChecks = report.checks.filter((check) => check.status === \"fail\");\n\treturn failedChecks.map((check) => `${check.name}: ${check.message}`).join(\" | \");\n}\n\nexport async function runCloudflareMemoryMigration(params: { service: CloudflareMemoryService; options?: MigrationRunOptions }): Promise<MigrationSummary> {\n\tconst options = params.options ?? {};\n\tconst workspaceDir = resolve(options.workspaceDir ?? process.cwd());\n\tconst sourceMode: MigrationSourceMode = (options.sourcePaths?.length ?? 0) > 0 ? \"paths\" : \"default-provider\";\n\tconst namespaceStrategy = options.namespaceStrategy ?? \"single-target\";\n\tconst duplicateStrategy: MigrationDuplicateStrategy = options.duplicateStrategy ?? \"overwrite\";\n\tconst dryRun = options.dryRun ?? false;\n\tconst targetNamespace = buildTargetNamespace(params.service, options, workspaceDir);\n\tconst doctor = await params.service.doctor({\n\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\n\t});\n\tif (!doctor.ok) {\n\t\tthrow new Error(`Migration validation failed. ${formatDoctorFailure(doctor)}`);\n\t}\n\n\tconst discoveredFiles = await discoverMigrationFiles({\n\t\tworkspaceDir,\n\t\tsourceMode,\n\t\tsourcePaths: options.sourcePaths,\n\t});\n\tif (discoveredFiles.length === 0) {\n\t\tthrow new Error(\n\t\t\tsourceMode === \"default-provider\"\n\t\t\t\t? `No default OpenClaw markdown memory files were found under ${workspaceDir}.`\n\t\t\t\t: \"No markdown files matched the provided migration sources.\",\n\t\t);\n\t}\n\n\tconst results: MigrationResult[] = [];\n\tlet preparedRecords = 0;\n\tlet imported = 0;\n\tlet skipped = 0;\n\tlet failed = 0;\n\n\tfor (const file of discoveredFiles) {\n\t\ttry {\n\t\t\tconst parsed = await parseMigrationFile({\n\t\t\t\tfile,\n\t\t\t\tsourceMode,\n\t\t\t\ttargetNamespace,\n\t\t\t\tnamespaceStrategy,\n\t\t\t});\n\t\t\tif (!parsed) {\n\t\t\t\tskipped += 1;\n\t\t\t\tresults.push({\n\t\t\t\t\taction: \"skipped\",\n\t\t\t\t\tsourcePath: file.absolutePath,\n\t\t\t\t\trelativePath: file.relativePath,\n\t\t\t\t\treason: \"File did not contain any importable markdown content.\",\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tpreparedRecords += 1;\n\t\t\tconst logicalId = parsed.input.id;\n\t\t\tconst namespace = parsed.input.namespace;\n\t\t\tif (!logicalId || !namespace) {\n\t\t\t\tthrow new Error(\"Parsed migration record is missing a logical id or namespace.\");\n\t\t\t}\n\n\t\t\tif (duplicateStrategy !== \"overwrite\") {\n\t\t\t\tconst existing = await params.service.get({\n\t\t\t\t\tid: logicalId,\n\t\t\t\t\tnamespace,\n\t\t\t\t});\n\t\t\t\tif (existing) {\n\t\t\t\t\tif (duplicateStrategy === \"skip\") {\n\t\t\t\t\t\tskipped += 1;\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\taction: \"skipped\",\n\t\t\t\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\t\t\t\tlogicalId,\n\t\t\t\t\t\t\tnamespace,\n\t\t\t\t\t\t\ttitle: parsed.input.title,\n\t\t\t\t\t\t\treason: \"A record with the same logical id already exists.\",\n\t\t\t\t\t\t});\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`A record with logical id ${logicalId} already exists in namespace ${namespace}.`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (dryRun) {\n\t\t\t\tresults.push({\n\t\t\t\t\taction: \"would-import\",\n\t\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\t\tlogicalId,\n\t\t\t\t\tnamespace,\n\t\t\t\t\ttitle: parsed.input.title,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tawait params.service.upsert({\n\t\t\t\tinput: parsed.input,\n\t\t\t});\n\t\t\timported += 1;\n\t\t\tresults.push({\n\t\t\t\taction: \"imported\",\n\t\t\t\tsourcePath: parsed.sourcePath,\n\t\t\t\trelativePath: parsed.relativePath,\n\t\t\t\tlogicalId,\n\t\t\t\tnamespace,\n\t\t\t\ttitle: parsed.input.title,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tfailed += 1;\n\t\t\tresults.push({\n\t\t\t\taction: \"failed\",\n\t\t\t\tsourcePath: file.absolutePath,\n\t\t\t\trelativePath: file.relativePath,\n\t\t\t\terror: error instanceof Error ? error.message : \"Unknown migration failure.\",\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tdryRun,\n\t\tsourceMode,\n\t\tworkspaceDir,\n\t\tnamespaceStrategy,\n\t\ttargetNamespace: namespaceStrategy === \"single-target\" ? targetNamespace : undefined,\n\t\tdiscoveredFiles: discoveredFiles.length,\n\t\tpreparedRecords,\n\t\timported,\n\t\tskipped,\n\t\tfailed,\n\t\tdoctor,\n\t\tresults,\n\t};\n}\n\nexport function formatMigrationSummary(summary: MigrationSummary): string {\n\tconst lines = [\n\t\t`${summary.dryRun ? \"Dry-run\" : \"Migration\"} ${summary.failed > 0 ? \"completed with failures\" : \"completed\"}.`,\n\t\t`Source mode: ${summary.sourceMode}`,\n\t\t`Workspace: ${summary.workspaceDir}`,\n\t\t`Files scanned: ${summary.discoveredFiles}`,\n\t\t`Records prepared: ${summary.preparedRecords}`,\n\t\t`Imported: ${summary.imported}`,\n\t\t`Skipped: ${summary.skipped}`,\n\t\t`Failed: ${summary.failed}`,\n\t];\n\n\tif (summary.targetNamespace) {\n\t\tlines.splice(3, 0, `Target namespace: ${summary.targetNamespace}`);\n\t}\n\n\tconst failedResults = summary.results.filter((result) => result.action === \"failed\").slice(0, 10);\n\tif (failedResults.length > 0) {\n\t\tlines.push(\"\", \"Failures:\");\n\t\tfor (const result of failedResults) {\n\t\t\tlines.push(`- ${result.relativePath}: ${result.error}`);\n\t\t}\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n"],"mappings":"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"}
package/dist/service.js CHANGED
@@ -1,17 +1,18 @@
1
- import { resolveDefaultNamespace as e } from "./namespace.js";
2
- import { isCloudflareNotFoundError as t } from "./cloudflare-api.js";
3
- import { CompanionStore as n } from "./companion-store.js";
4
- import { runDoctor as r } from "./doctor.js";
5
- import { WorkersAiEmbeddingsClient as i } from "./embeddings-client.js";
6
- import { hydrateInlineRecord as a, mapRecordForUpsert as o } from "./record-mapper.js";
7
- import { VectorizeClient as s } from "./vectorize-client.js";
1
+ import { resolveDefaultNamespace as e, sanitizeNamespace as t } from "./namespace.js";
2
+ import { isCloudflareNotFoundError as n } from "./cloudflare-api.js";
3
+ import { CompanionStore as r } from "./companion-store.js";
4
+ import { runDoctor as i } from "./doctor.js";
5
+ import { WorkersAiEmbeddingsClient as a } from "./embeddings-client.js";
6
+ import { hydrateInlineRecord as o, mapRecordForUpsert as s } from "./record-mapper.js";
7
+ import { VectorizeClient as c } from "./vectorize-client.js";
8
+ import { randomUUID as l } from "node:crypto";
8
9
  //#region src/service.ts
9
- var c = class {
10
+ var u = 12e3, d = 1e3, f = class {
10
11
  embeddings;
11
12
  vectorize;
12
13
  companionStore;
13
14
  constructor(e, t) {
14
- this.config = e, this.openClawConfig = t, this.embeddings = new i(e), this.vectorize = new s(e), this.companionStore = new n(e.companionStorePath);
15
+ this.config = e, this.openClawConfig = t, this.embeddings = new a(e), this.vectorize = new c(e), this.companionStore = new r(e.companionStorePath);
15
16
  }
16
17
  resolveNamespace(t) {
17
18
  return e({
@@ -29,7 +30,7 @@ var c = class {
29
30
  filter: e.filter
30
31
  });
31
32
  return (await Promise.all(r.map(async (e) => {
32
- let t = a(e), n = t.text ?? (await this.companionStore.get(t.namespace, t.logicalId))?.text ?? "";
33
+ let t = o(e), n = t.text ?? (await this.companionStore.get(t.namespace, t.logicalId))?.text ?? "";
33
34
  return {
34
35
  ...t,
35
36
  text: n,
@@ -40,7 +41,7 @@ var c = class {
40
41
  async get(e) {
41
42
  let t = `${this.resolveNamespace(e)}::${e.id}`, [n] = await this.vectorize.getByIds([t]);
42
43
  if (!n) return null;
43
- let r = a(n), i = await this.companionStore.get(r.namespace, r.logicalId);
44
+ let r = o(n), i = await this.companionStore.get(r.namespace, r.logicalId);
44
45
  return {
45
46
  ...r,
46
47
  text: r.text ?? i?.text ?? ""
@@ -52,7 +53,7 @@ var c = class {
52
53
  sessionKey: e.sessionKey,
53
54
  agentId: e.agentId,
54
55
  workspaceDir: e.workspaceDir
55
- }), n = await this.embeddings.embedQuery(e.input.text), r = o({
56
+ }), n = await this.embeddings.embedQuery(e.input.text), r = s({
56
57
  input: e.input,
57
58
  namespace: t,
58
59
  embedding: n,
@@ -73,23 +74,207 @@ var c = class {
73
74
  return await this.companionStore.delete(t, e.id), this.vectorize.deleteByIds([`${t}::${e.id}`]);
74
75
  }
75
76
  async doctor(e) {
76
- return r({
77
+ return i({
77
78
  service: this,
78
79
  createIndexIfMissing: e.createIndexIfMissing ?? !1
79
80
  });
80
81
  }
81
- async ensureIndexExists(e) {
82
+ async inspectEmbeddingDimensions() {
83
+ let e = await this.embeddings.probeDimensions(), t = this.config.createIndex.dimensions;
84
+ return {
85
+ embeddingDimensions: e,
86
+ configuredDimensions: t,
87
+ configuredDimensionsMatchModel: t === void 0 || t === e,
88
+ targetDimensions: e
89
+ };
90
+ }
91
+ async describeIndexIfExists() {
92
+ try {
93
+ return await this.vectorize.describeIndex();
94
+ } catch (e) {
95
+ if (n(e)) return null;
96
+ throw e;
97
+ }
98
+ }
99
+ async initializeIndex(e) {
100
+ let t = e?.recreateIfDimensionMismatch ?? !0, n = [];
101
+ n.push({
102
+ name: "credentials",
103
+ status: "pass",
104
+ message: `Using Cloudflare account ${this.config.accountId} and Vectorize index ${this.config.indexName}.`
105
+ });
106
+ let r = await this.inspectEmbeddingDimensions();
107
+ n.push({
108
+ name: "workers-ai-embeddings",
109
+ status: "pass",
110
+ message: `Workers AI model ${this.config.model} returned ${r.embeddingDimensions} dimensions.`
111
+ }), r.configuredDimensions !== void 0 && n.push({
112
+ name: "create-index-dimensions",
113
+ status: r.configuredDimensionsMatchModel ? "pass" : "warn",
114
+ message: r.configuredDimensionsMatchModel ? `Configured createIndex.dimensions matches the embedding model (${r.embeddingDimensions}).` : `Configured createIndex.dimensions (${r.configuredDimensions}) does not match the embedding model (${r.embeddingDimensions}). Using the live embedding dimensions for initialization.`
115
+ });
116
+ let i = await this.describeIndexIfExists(), a = !1, o = !1, s = i?.config.dimensions;
117
+ if (!i) {
118
+ let e = await this.vectorize.createIndex(r.targetDimensions);
119
+ a = !0, s = e.config.dimensions, n.push({
120
+ name: "vectorize-index",
121
+ status: "pass",
122
+ message: `Created Vectorize index "${this.config.indexName}" with ${s} dimensions.`
123
+ });
124
+ } else if (i.config.dimensions === r.targetDimensions) n.push({
125
+ name: "vectorize-index",
126
+ status: "pass",
127
+ message: `Vectorize index "${this.config.indexName}" already uses ${i.config.dimensions} dimensions.`
128
+ });
129
+ else if (!t) n.push({
130
+ name: "vectorize-index",
131
+ status: "fail",
132
+ message: `Vectorize index "${this.config.indexName}" uses ${i.config.dimensions} dimensions, but the embedding model requires ${r.targetDimensions}. Recreate the index or rerun init with recreation enabled.`
133
+ });
134
+ else {
135
+ await this.vectorize.deleteIndex();
136
+ let e = await this.vectorize.createIndex(r.targetDimensions);
137
+ o = !0, s = e.config.dimensions, n.push({
138
+ name: "vectorize-index",
139
+ status: "pass",
140
+ message: `Recreated Vectorize index "${this.config.indexName}" from ${i.config.dimensions} to ${s} dimensions.`
141
+ });
142
+ }
143
+ return n.push({
144
+ name: "dimension-match",
145
+ status: s === r.embeddingDimensions ? "pass" : "fail",
146
+ message: s === r.embeddingDimensions ? "Embedding dimensions match the Vectorize index." : `Embedding dimensions (${r.embeddingDimensions}) do not match the Vectorize index dimensions (${s ?? "unknown"}).`
147
+ }), n.push({
148
+ name: "metadata-filters",
149
+ status: this.config.metadataIndexedFields.length > 0 ? "pass" : "warn",
150
+ message: this.config.metadataIndexedFields.length > 0 ? `Configured metadata-index guidance for: ${this.config.metadataIndexedFields.join(", ")}.` : "No metadataIndexedFields configured. Add metadata indexes in Cloudflare before relying on filter-heavy queries."
151
+ }), {
152
+ ok: n.every((e) => e.status !== "fail"),
153
+ checks: n,
154
+ created: a,
155
+ recreated: o,
156
+ embeddingDimensions: r.embeddingDimensions,
157
+ indexDimensions: s
158
+ };
159
+ }
160
+ async runSmokeTest(e) {
161
+ let n = [];
162
+ n.push({
163
+ name: "credentials",
164
+ status: "pass",
165
+ message: `Using Cloudflare account ${this.config.accountId} and Vectorize index ${this.config.indexName}.`
166
+ });
167
+ let r = await this.inspectEmbeddingDimensions();
168
+ n.push({
169
+ name: "workers-ai-embeddings",
170
+ status: "pass",
171
+ message: `Workers AI model ${this.config.model} returned ${r.embeddingDimensions} dimensions.`
172
+ }), r.configuredDimensions !== void 0 && n.push({
173
+ name: "create-index-dimensions",
174
+ status: r.configuredDimensionsMatchModel ? "pass" : "warn",
175
+ message: r.configuredDimensionsMatchModel ? `Configured createIndex.dimensions matches the embedding model (${r.embeddingDimensions}).` : `Configured createIndex.dimensions (${r.configuredDimensions}) does not match the embedding model (${r.embeddingDimensions}). Using the live embedding dimensions for validation.`
176
+ });
177
+ let i = await this.describeIndexIfExists();
178
+ if (!i) return n.push({
179
+ name: "vectorize-index",
180
+ status: "fail",
181
+ message: `Vectorize index "${this.config.indexName}" was not found. Run "openclaw cf-memory init" before rerunning this test.`
182
+ }), {
183
+ ok: !1,
184
+ checks: n,
185
+ namespace: "n/a",
186
+ logicalId: "n/a"
187
+ };
188
+ if (n.push({
189
+ name: "vectorize-index",
190
+ status: "pass",
191
+ message: `Vectorize index "${this.config.indexName}" is reachable.`
192
+ }), i.config.dimensions !== r.embeddingDimensions) return n.push({
193
+ name: "dimension-match",
194
+ status: "fail",
195
+ message: `Embedding dimensions (${r.embeddingDimensions}) do not match the Vectorize index dimensions (${i.config.dimensions}). Run "openclaw cf-memory init" to repair the index.`
196
+ }), {
197
+ ok: !1,
198
+ checks: n,
199
+ namespace: "n/a",
200
+ logicalId: "n/a"
201
+ };
202
+ n.push({
203
+ name: "dimension-match",
204
+ status: "pass",
205
+ message: "Embedding dimensions match the Vectorize index."
206
+ });
207
+ let a = t(`cf-memory-test-${l()}`), o = `cf-memory-test-${l()}`, s = `OpenClaw Cloudflare memory smoke test ${o}`, c = !1;
208
+ try {
209
+ await this.upsert({ input: {
210
+ id: o,
211
+ namespace: a,
212
+ text: s,
213
+ source: "cf-memory-test",
214
+ metadata: {
215
+ probe: !0,
216
+ probeId: o
217
+ }
218
+ } }), c = !0, n.push({
219
+ name: "probe-upsert",
220
+ status: "pass",
221
+ message: `Inserted smoke-test record ${o} in namespace ${a}.`
222
+ });
223
+ let t = await this.waitForSearchHit({
224
+ query: s,
225
+ namespace: a,
226
+ logicalId: o,
227
+ timeoutMs: e?.timeoutMs ?? u,
228
+ pollIntervalMs: e?.pollIntervalMs ?? d
229
+ });
230
+ n.push({
231
+ name: "probe-search",
232
+ status: t ? "pass" : "fail",
233
+ message: t ? "Semantic search returned the smoke-test record." : `Semantic search did not return the smoke-test record within ${(e?.timeoutMs ?? u) / 1e3} seconds.`
234
+ });
235
+ } catch (e) {
236
+ n.push({
237
+ name: c ? "probe-search" : "probe-upsert",
238
+ status: "fail",
239
+ message: e instanceof Error ? e.message : "Smoke test failed with an unknown error."
240
+ });
241
+ } finally {
242
+ try {
243
+ await this.delete({
244
+ id: o,
245
+ namespace: a
246
+ }), n.push({
247
+ name: "probe-cleanup",
248
+ status: "pass",
249
+ message: `Deleted smoke-test record ${o}.`
250
+ });
251
+ } catch (e) {
252
+ n.push({
253
+ name: "probe-cleanup",
254
+ status: "fail",
255
+ message: e instanceof Error ? e.message : `Failed to delete smoke-test record ${o}.`
256
+ });
257
+ }
258
+ }
259
+ return {
260
+ ok: n.every((e) => e.status !== "fail"),
261
+ checks: n,
262
+ namespace: a,
263
+ logicalId: o
264
+ };
265
+ }
266
+ async ensureIndexExists(e, t) {
82
267
  try {
83
268
  return {
84
269
  created: !1,
85
270
  dimensions: (await this.vectorize.describeIndex()).config.dimensions
86
271
  };
87
- } catch (n) {
88
- if (!e || !t(n)) throw n;
89
- let r = this.config.createIndex.dimensions ?? await this.embeddings.probeDimensions();
90
- return await this.vectorize.createIndex(r), {
272
+ } catch (r) {
273
+ if (!e || !n(r)) throw r;
274
+ let i = t ?? (await this.inspectEmbeddingDimensions()).targetDimensions;
275
+ return {
91
276
  created: !0,
92
- dimensions: r
277
+ dimensions: (await this.vectorize.createIndex(i)).config.dimensions
93
278
  };
94
279
  }
95
280
  }
@@ -107,8 +292,25 @@ var c = class {
107
292
  path: r
108
293
  };
109
294
  }
295
+ async waitForSearchHit(e) {
296
+ let t = Date.now() + e.timeoutMs;
297
+ for (; Date.now() <= t;) {
298
+ if ((await this.search({
299
+ query: e.query,
300
+ namespace: e.namespace,
301
+ maxResults: 5,
302
+ minScore: 0
303
+ })).some((t) => t.logicalId === e.logicalId)) return !0;
304
+ if (Date.now() + e.pollIntervalMs > t) break;
305
+ await this.pause(e.pollIntervalMs);
306
+ }
307
+ return !1;
308
+ }
309
+ async pause(e) {
310
+ await new Promise((t) => setTimeout(t, e));
311
+ }
110
312
  };
111
313
  //#endregion
112
- export { c as CloudflareMemoryService };
314
+ export { f as CloudflareMemoryService };
113
315
 
114
316
  //# sourceMappingURL=service.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../src/service.ts"],"sourcesContent":["import type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\nimport { isCloudflareNotFoundError } from \"./cloudflare-api.js\";\nimport { CompanionStore } from \"./companion-store.js\";\nimport { runDoctor } from \"./doctor.js\";\nimport { WorkersAiEmbeddingsClient } from \"./embeddings-client.js\";\nimport { resolveDefaultNamespace } from \"./namespace.js\";\nimport { hydrateInlineRecord, mapRecordForUpsert } from \"./record-mapper.js\";\nimport type {\n\tCompanionRecord,\n\tDoctorReport,\n\tHydratedMemoryRecord,\n\tMemoryRecordInput,\n\tMetadataFilter,\n\tResolvedPluginConfig,\n\tUpsertedMemoryRecord,\n} from \"./types.js\";\nimport { VectorizeClient } from \"./vectorize-client.js\";\n\nexport class CloudflareMemoryService {\n\treadonly embeddings: WorkersAiEmbeddingsClient;\n\treadonly vectorize: VectorizeClient;\n\treadonly companionStore: CompanionStore;\n\n\tconstructor(\n\t\treadonly config: ResolvedPluginConfig,\n\t\treadonly openClawConfig: OpenClawConfig,\n\t) {\n\t\tthis.embeddings = new WorkersAiEmbeddingsClient(config);\n\t\tthis.vectorize = new VectorizeClient(config);\n\t\tthis.companionStore = new CompanionStore(config.companionStorePath);\n\t}\n\n\tresolveNamespace(params: { namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): string {\n\t\treturn resolveDefaultNamespace({\n\t\t\tfixedNamespace: params.namespace ?? this.config.fixedNamespace,\n\t\t\tsessionKey: params.sessionKey,\n\t\t\tagentId: params.agentId,\n\t\t\tworkspaceDir: params.workspaceDir,\n\t\t});\n\t}\n\n\tasync search(params: {\n\t\tquery: string;\n\t\tnamespace?: string;\n\t\tmaxResults?: number;\n\t\tminScore?: number;\n\t\tfilter?: MetadataFilter;\n\t\tsessionKey?: string;\n\t\tagentId?: string;\n\t\tworkspaceDir?: string;\n\t}): Promise<Array<HydratedMemoryRecord & { score: number }>> {\n\t\tconst namespace = this.resolveNamespace(params);\n\t\tconst vector = await this.embeddings.embedQuery(params.query);\n\t\tconst matches = await this.vectorize.query({\n\t\t\tvector,\n\t\t\tnamespace,\n\t\t\ttopK: params.maxResults ?? this.config.topK,\n\t\t\tfilter: params.filter,\n\t\t});\n\n\t\tconst hydrated = await Promise.all(\n\t\t\tmatches.map(async (match) => {\n\t\t\t\tconst base = hydrateInlineRecord(match);\n\t\t\t\tconst text = base.text ?? (await this.companionStore.get(base.namespace, base.logicalId))?.text ?? \"\";\n\t\t\t\treturn {\n\t\t\t\t\t...base,\n\t\t\t\t\ttext,\n\t\t\t\t\tscore: match.score ?? 0,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\n\t\treturn hydrated.filter((record) => record.score >= (params.minScore ?? this.config.minScore));\n\t}\n\n\tasync get(params: { id: string; namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<HydratedMemoryRecord | null> {\n\t\tconst namespace = this.resolveNamespace(params);\n\t\tconst vectorId = `${namespace}::${params.id}`;\n\t\tconst [match] = await this.vectorize.getByIds([vectorId]);\n\t\tif (!match) {\n\t\t\treturn null;\n\t\t}\n\t\tconst base = hydrateInlineRecord(match);\n\t\tconst companion = await this.companionStore.get(base.namespace, base.logicalId);\n\t\treturn {\n\t\t\t...base,\n\t\t\ttext: base.text ?? companion?.text ?? \"\",\n\t\t};\n\t}\n\n\tasync upsert(params: { input: MemoryRecordInput; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<UpsertedMemoryRecord> {\n\t\tconst namespace = this.resolveNamespace({\n\t\t\tnamespace: params.input.namespace,\n\t\t\tsessionKey: params.sessionKey,\n\t\t\tagentId: params.agentId,\n\t\t\tworkspaceDir: params.workspaceDir,\n\t\t});\n\t\tconst embedding = await this.embeddings.embedQuery(params.input.text);\n\t\tconst mapped = mapRecordForUpsert({\n\t\t\tinput: params.input,\n\t\t\tnamespace,\n\t\t\tembedding,\n\t\t\tconfig: this.config,\n\t\t});\n\n\t\tif (mapped.companionRecord) {\n\t\t\tawait this.companionStore.upsert(mapped.companionRecord);\n\t\t}\n\n\t\tconst mutationId = await this.vectorize.upsert([mapped.vector]);\n\t\tconst hydrated = await this.get({\n\t\t\tid: mapped.logicalId,\n\t\t\tnamespace,\n\t\t});\n\n\t\treturn {\n\t\t\t...(hydrated ?? this.fromCompanionFallback(mapped.companionRecord, mapped.logicalId, namespace, mapped.path)),\n\t\t\tmutationId,\n\t\t};\n\t}\n\n\tasync delete(params: { id: string; namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<string | undefined> {\n\t\tconst namespace = this.resolveNamespace(params);\n\t\tawait this.companionStore.delete(namespace, params.id);\n\t\treturn this.vectorize.deleteByIds([`${namespace}::${params.id}`]);\n\t}\n\n\tasync doctor(options: { createIndexIfMissing?: boolean }): Promise<DoctorReport> {\n\t\treturn runDoctor({\n\t\t\tservice: this,\n\t\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\n\t\t});\n\t}\n\n\tasync ensureIndexExists(createIfMissing: boolean): Promise<{ created: boolean; dimensions: number }> {\n\t\ttry {\n\t\t\tconst description = await this.vectorize.describeIndex();\n\t\t\treturn {\n\t\t\t\tcreated: false,\n\t\t\t\tdimensions: description.config.dimensions,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tif (!createIfMissing || !isCloudflareNotFoundError(error)) {\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tconst dimensions = this.config.createIndex.dimensions ?? (await this.embeddings.probeDimensions());\n\t\t\tawait this.vectorize.createIndex(dimensions);\n\t\t\treturn {\n\t\t\t\tcreated: true,\n\t\t\t\tdimensions,\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate fromCompanionFallback(companionRecord: CompanionRecord | undefined, logicalId: string, namespace: string, path: string): UpsertedMemoryRecord {\n\t\treturn {\n\t\t\tlogicalId,\n\t\t\tvectorId: `${namespace}::${logicalId}`,\n\t\t\tnamespace,\n\t\t\ttitle: companionRecord?.title,\n\t\t\ttext: companionRecord?.text ?? \"\",\n\t\t\tmetadata: companionRecord?.metadata ?? {},\n\t\t\tsource: companionRecord?.source,\n\t\t\tcreatedAt: companionRecord?.createdAt,\n\t\t\tupdatedAt: companionRecord?.updatedAt,\n\t\t\tpath,\n\t\t};\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../src/service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\r\nimport type { OpenClawConfig } from \"openclaw/plugin-sdk/config-runtime\";\r\nimport { isCloudflareNotFoundError } from \"./cloudflare-api.js\";\r\nimport { CompanionStore } from \"./companion-store.js\";\r\nimport { runDoctor } from \"./doctor.js\";\r\nimport { WorkersAiEmbeddingsClient } from \"./embeddings-client.js\";\r\nimport { sanitizeNamespace, resolveDefaultNamespace } from \"./namespace.js\";\r\nimport { hydrateInlineRecord, mapRecordForUpsert } from \"./record-mapper.js\";\r\nimport type {\r\n\tCompanionRecord,\r\n\tDoctorCheck,\r\n\tDoctorReport,\r\n\tEmbeddingDimensionsInspection,\r\n\tHydratedMemoryRecord,\r\n\tIndexInitializationReport,\r\n\tMemoryRecordInput,\r\n\tMetadataFilter,\r\n\tResolvedPluginConfig,\r\n\tSmokeTestReport,\r\n\tUpsertedMemoryRecord,\r\n\tVectorizeIndexDescription,\r\n} from \"./types.js\";\r\nimport { VectorizeClient } from \"./vectorize-client.js\";\r\n\r\nconst SMOKE_TEST_TIMEOUT_MS = 12_000;\r\nconst SMOKE_TEST_POLL_INTERVAL_MS = 1_000;\r\n\r\nexport class CloudflareMemoryService {\r\n\treadonly embeddings: WorkersAiEmbeddingsClient;\r\n\treadonly vectorize: VectorizeClient;\r\n\treadonly companionStore: CompanionStore;\r\n\r\n\tconstructor(\r\n\t\treadonly config: ResolvedPluginConfig,\r\n\t\treadonly openClawConfig: OpenClawConfig,\r\n\t) {\r\n\t\tthis.embeddings = new WorkersAiEmbeddingsClient(config);\r\n\t\tthis.vectorize = new VectorizeClient(config);\r\n\t\tthis.companionStore = new CompanionStore(config.companionStorePath);\r\n\t}\r\n\r\n\tresolveNamespace(params: { namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): string {\r\n\t\treturn resolveDefaultNamespace({\r\n\t\t\tfixedNamespace: params.namespace ?? this.config.fixedNamespace,\r\n\t\t\tsessionKey: params.sessionKey,\r\n\t\t\tagentId: params.agentId,\r\n\t\t\tworkspaceDir: params.workspaceDir,\r\n\t\t});\r\n\t}\r\n\r\n\tasync search(params: {\r\n\t\tquery: string;\r\n\t\tnamespace?: string;\r\n\t\tmaxResults?: number;\r\n\t\tminScore?: number;\r\n\t\tfilter?: MetadataFilter;\r\n\t\tsessionKey?: string;\r\n\t\tagentId?: string;\r\n\t\tworkspaceDir?: string;\r\n\t}): Promise<Array<HydratedMemoryRecord & { score: number }>> {\r\n\t\tconst namespace = this.resolveNamespace(params);\r\n\t\tconst vector = await this.embeddings.embedQuery(params.query);\r\n\t\tconst matches = await this.vectorize.query({\r\n\t\t\tvector,\r\n\t\t\tnamespace,\r\n\t\t\ttopK: params.maxResults ?? this.config.topK,\r\n\t\t\tfilter: params.filter,\r\n\t\t});\r\n\r\n\t\tconst hydrated = await Promise.all(\r\n\t\t\tmatches.map(async (match) => {\r\n\t\t\t\tconst base = hydrateInlineRecord(match);\r\n\t\t\t\tconst text = base.text ?? (await this.companionStore.get(base.namespace, base.logicalId))?.text ?? \"\";\r\n\t\t\t\treturn {\r\n\t\t\t\t\t...base,\r\n\t\t\t\t\ttext,\r\n\t\t\t\t\tscore: match.score ?? 0,\r\n\t\t\t\t};\r\n\t\t\t}),\r\n\t\t);\r\n\r\n\t\treturn hydrated.filter((record) => record.score >= (params.minScore ?? this.config.minScore));\r\n\t}\r\n\r\n\tasync get(params: { id: string; namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<HydratedMemoryRecord | null> {\r\n\t\tconst namespace = this.resolveNamespace(params);\r\n\t\tconst vectorId = `${namespace}::${params.id}`;\r\n\t\tconst [match] = await this.vectorize.getByIds([vectorId]);\r\n\t\tif (!match) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tconst base = hydrateInlineRecord(match);\r\n\t\tconst companion = await this.companionStore.get(base.namespace, base.logicalId);\r\n\t\treturn {\r\n\t\t\t...base,\r\n\t\t\ttext: base.text ?? companion?.text ?? \"\",\r\n\t\t};\r\n\t}\r\n\r\n\tasync upsert(params: { input: MemoryRecordInput; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<UpsertedMemoryRecord> {\r\n\t\tconst namespace = this.resolveNamespace({\r\n\t\t\tnamespace: params.input.namespace,\r\n\t\t\tsessionKey: params.sessionKey,\r\n\t\t\tagentId: params.agentId,\r\n\t\t\tworkspaceDir: params.workspaceDir,\r\n\t\t});\r\n\t\tconst embedding = await this.embeddings.embedQuery(params.input.text);\r\n\t\tconst mapped = mapRecordForUpsert({\r\n\t\t\tinput: params.input,\r\n\t\t\tnamespace,\r\n\t\t\tembedding,\r\n\t\t\tconfig: this.config,\r\n\t\t});\r\n\r\n\t\tif (mapped.companionRecord) {\r\n\t\t\tawait this.companionStore.upsert(mapped.companionRecord);\r\n\t\t}\r\n\r\n\t\tconst mutationId = await this.vectorize.upsert([mapped.vector]);\r\n\t\tconst hydrated = await this.get({\r\n\t\t\tid: mapped.logicalId,\r\n\t\t\tnamespace,\r\n\t\t});\r\n\r\n\t\treturn {\r\n\t\t\t...(hydrated ?? this.fromCompanionFallback(mapped.companionRecord, mapped.logicalId, namespace, mapped.path)),\r\n\t\t\tmutationId,\r\n\t\t};\r\n\t}\r\n\r\n\tasync delete(params: { id: string; namespace?: string; sessionKey?: string; agentId?: string; workspaceDir?: string }): Promise<string | undefined> {\r\n\t\tconst namespace = this.resolveNamespace(params);\r\n\t\tawait this.companionStore.delete(namespace, params.id);\r\n\t\treturn this.vectorize.deleteByIds([`${namespace}::${params.id}`]);\r\n\t}\r\n\r\n\tasync doctor(options: { createIndexIfMissing?: boolean }): Promise<DoctorReport> {\r\n\t\treturn runDoctor({\r\n\t\t\tservice: this,\r\n\t\t\tcreateIndexIfMissing: options.createIndexIfMissing ?? false,\r\n\t\t});\r\n\t}\r\n\r\n\tasync inspectEmbeddingDimensions(): Promise<EmbeddingDimensionsInspection> {\r\n\t\tconst embeddingDimensions = await this.embeddings.probeDimensions();\r\n\t\tconst configuredDimensions = this.config.createIndex.dimensions;\r\n\t\treturn {\r\n\t\t\tembeddingDimensions,\r\n\t\t\tconfiguredDimensions,\r\n\t\t\tconfiguredDimensionsMatchModel: configuredDimensions === undefined || configuredDimensions === embeddingDimensions,\r\n\t\t\ttargetDimensions: embeddingDimensions,\r\n\t\t};\r\n\t}\r\n\r\n\tasync describeIndexIfExists(): Promise<VectorizeIndexDescription | null> {\r\n\t\ttry {\r\n\t\t\treturn await this.vectorize.describeIndex();\r\n\t\t} catch (error) {\r\n\t\t\tif (isCloudflareNotFoundError(error)) {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\tasync initializeIndex(options?: { recreateIfDimensionMismatch?: boolean }): Promise<IndexInitializationReport> {\r\n\t\tconst recreateIfDimensionMismatch = options?.recreateIfDimensionMismatch ?? true;\r\n\t\tconst checks: DoctorCheck[] = [];\r\n\t\tchecks.push({\r\n\t\t\tname: \"credentials\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: `Using Cloudflare account ${this.config.accountId} and Vectorize index ${this.config.indexName}.`,\r\n\t\t});\r\n\r\n\t\tconst embedding = await this.inspectEmbeddingDimensions();\r\n\t\tchecks.push({\r\n\t\t\tname: \"workers-ai-embeddings\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: `Workers AI model ${this.config.model} returned ${embedding.embeddingDimensions} dimensions.`,\r\n\t\t});\r\n\t\tif (embedding.configuredDimensions !== undefined) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"create-index-dimensions\",\r\n\t\t\t\tstatus: embedding.configuredDimensionsMatchModel ? \"pass\" : \"warn\",\r\n\t\t\t\tmessage: embedding.configuredDimensionsMatchModel\r\n\t\t\t\t\t? `Configured createIndex.dimensions matches the embedding model (${embedding.embeddingDimensions}).`\r\n\t\t\t\t\t: `Configured createIndex.dimensions (${embedding.configuredDimensions}) does not match the embedding model (${embedding.embeddingDimensions}). Using the live embedding dimensions for initialization.`,\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tconst existingIndex = await this.describeIndexIfExists();\r\n\t\tlet created = false;\r\n\t\tlet recreated = false;\r\n\t\tlet indexDimensions = existingIndex?.config.dimensions;\r\n\r\n\t\tif (!existingIndex) {\r\n\t\t\tconst createdIndex = await this.vectorize.createIndex(embedding.targetDimensions);\r\n\t\t\tcreated = true;\r\n\t\t\tindexDimensions = createdIndex.config.dimensions;\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"vectorize-index\",\r\n\t\t\t\tstatus: \"pass\",\r\n\t\t\t\tmessage: `Created Vectorize index \"${this.config.indexName}\" with ${indexDimensions} dimensions.`,\r\n\t\t\t});\r\n\t\t} else if (existingIndex.config.dimensions === embedding.targetDimensions) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"vectorize-index\",\r\n\t\t\t\tstatus: \"pass\",\r\n\t\t\t\tmessage: `Vectorize index \"${this.config.indexName}\" already uses ${existingIndex.config.dimensions} dimensions.`,\r\n\t\t\t});\r\n\t\t} else if (!recreateIfDimensionMismatch) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"vectorize-index\",\r\n\t\t\t\tstatus: \"fail\",\r\n\t\t\t\tmessage: `Vectorize index \"${this.config.indexName}\" uses ${existingIndex.config.dimensions} dimensions, but the embedding model requires ${embedding.targetDimensions}. Recreate the index or rerun init with recreation enabled.`,\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tawait this.vectorize.deleteIndex();\r\n\t\t\tconst recreatedIndex = await this.vectorize.createIndex(embedding.targetDimensions);\r\n\t\t\trecreated = true;\r\n\t\t\tindexDimensions = recreatedIndex.config.dimensions;\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"vectorize-index\",\r\n\t\t\t\tstatus: \"pass\",\r\n\t\t\t\tmessage: `Recreated Vectorize index \"${this.config.indexName}\" from ${existingIndex.config.dimensions} to ${indexDimensions} dimensions.`,\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tchecks.push({\r\n\t\t\tname: \"dimension-match\",\r\n\t\t\tstatus: indexDimensions === embedding.embeddingDimensions ? \"pass\" : \"fail\",\r\n\t\t\tmessage:\r\n\t\t\t\tindexDimensions === embedding.embeddingDimensions\r\n\t\t\t\t\t? \"Embedding dimensions match the Vectorize index.\"\r\n\t\t\t\t\t: `Embedding dimensions (${embedding.embeddingDimensions}) do not match the Vectorize index dimensions (${indexDimensions ?? \"unknown\"}).`,\r\n\t\t});\r\n\t\tchecks.push({\r\n\t\t\tname: \"metadata-filters\",\r\n\t\t\tstatus: this.config.metadataIndexedFields.length > 0 ? \"pass\" : \"warn\",\r\n\t\t\tmessage:\r\n\t\t\t\tthis.config.metadataIndexedFields.length > 0\r\n\t\t\t\t\t? `Configured metadata-index guidance for: ${this.config.metadataIndexedFields.join(\", \")}.`\r\n\t\t\t\t\t: \"No metadataIndexedFields configured. Add metadata indexes in Cloudflare before relying on filter-heavy queries.\",\r\n\t\t});\r\n\r\n\t\treturn {\r\n\t\t\tok: checks.every((check) => check.status !== \"fail\"),\r\n\t\t\tchecks,\r\n\t\t\tcreated,\r\n\t\t\trecreated,\r\n\t\t\tembeddingDimensions: embedding.embeddingDimensions,\r\n\t\t\tindexDimensions,\r\n\t\t};\r\n\t}\r\n\r\n\tasync runSmokeTest(options?: { timeoutMs?: number; pollIntervalMs?: number }): Promise<SmokeTestReport> {\r\n\t\tconst checks: DoctorCheck[] = [];\r\n\t\tchecks.push({\r\n\t\t\tname: \"credentials\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: `Using Cloudflare account ${this.config.accountId} and Vectorize index ${this.config.indexName}.`,\r\n\t\t});\r\n\r\n\t\tconst embedding = await this.inspectEmbeddingDimensions();\r\n\t\tchecks.push({\r\n\t\t\tname: \"workers-ai-embeddings\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: `Workers AI model ${this.config.model} returned ${embedding.embeddingDimensions} dimensions.`,\r\n\t\t});\r\n\t\tif (embedding.configuredDimensions !== undefined) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"create-index-dimensions\",\r\n\t\t\t\tstatus: embedding.configuredDimensionsMatchModel ? \"pass\" : \"warn\",\r\n\t\t\t\tmessage: embedding.configuredDimensionsMatchModel\r\n\t\t\t\t\t? `Configured createIndex.dimensions matches the embedding model (${embedding.embeddingDimensions}).`\r\n\t\t\t\t\t: `Configured createIndex.dimensions (${embedding.configuredDimensions}) does not match the embedding model (${embedding.embeddingDimensions}). Using the live embedding dimensions for validation.`,\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tconst existingIndex = await this.describeIndexIfExists();\r\n\t\tif (!existingIndex) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"vectorize-index\",\r\n\t\t\t\tstatus: \"fail\",\r\n\t\t\t\tmessage: `Vectorize index \"${this.config.indexName}\" was not found. Run \"openclaw cf-memory init\" before rerunning this test.`,\r\n\t\t\t});\r\n\t\t\treturn {\r\n\t\t\t\tok: false,\r\n\t\t\t\tchecks,\r\n\t\t\t\tnamespace: \"n/a\",\r\n\t\t\t\tlogicalId: \"n/a\",\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tchecks.push({\r\n\t\t\tname: \"vectorize-index\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: `Vectorize index \"${this.config.indexName}\" is reachable.`,\r\n\t\t});\r\n\t\tif (existingIndex.config.dimensions !== embedding.embeddingDimensions) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"dimension-match\",\r\n\t\t\t\tstatus: \"fail\",\r\n\t\t\t\tmessage: `Embedding dimensions (${embedding.embeddingDimensions}) do not match the Vectorize index dimensions (${existingIndex.config.dimensions}). Run \"openclaw cf-memory init\" to repair the index.`,\r\n\t\t\t});\r\n\t\t\treturn {\r\n\t\t\t\tok: false,\r\n\t\t\t\tchecks,\r\n\t\t\t\tnamespace: \"n/a\",\r\n\t\t\t\tlogicalId: \"n/a\",\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tchecks.push({\r\n\t\t\tname: \"dimension-match\",\r\n\t\t\tstatus: \"pass\",\r\n\t\t\tmessage: \"Embedding dimensions match the Vectorize index.\",\r\n\t\t});\r\n\r\n\t\tconst namespace = sanitizeNamespace(`cf-memory-test-${randomUUID()}`);\r\n\t\tconst logicalId = `cf-memory-test-${randomUUID()}`;\r\n\t\tconst probeText = `OpenClaw Cloudflare memory smoke test ${logicalId}`;\r\n\t\tlet probeUpserted = false;\r\n\r\n\t\ttry {\r\n\t\t\tawait this.upsert({\r\n\t\t\t\tinput: {\r\n\t\t\t\t\tid: logicalId,\r\n\t\t\t\t\tnamespace,\r\n\t\t\t\t\ttext: probeText,\r\n\t\t\t\t\tsource: \"cf-memory-test\",\r\n\t\t\t\t\tmetadata: {\r\n\t\t\t\t\t\tprobe: true,\r\n\t\t\t\t\t\tprobeId: logicalId,\r\n\t\t\t\t\t},\r\n\t\t\t\t},\r\n\t\t\t});\r\n\t\t\tprobeUpserted = true;\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"probe-upsert\",\r\n\t\t\t\tstatus: \"pass\",\r\n\t\t\t\tmessage: `Inserted smoke-test record ${logicalId} in namespace ${namespace}.`,\r\n\t\t\t});\r\n\r\n\t\t\tconst found = await this.waitForSearchHit({\r\n\t\t\t\tquery: probeText,\r\n\t\t\t\tnamespace,\r\n\t\t\t\tlogicalId,\r\n\t\t\t\ttimeoutMs: options?.timeoutMs ?? SMOKE_TEST_TIMEOUT_MS,\r\n\t\t\t\tpollIntervalMs: options?.pollIntervalMs ?? SMOKE_TEST_POLL_INTERVAL_MS,\r\n\t\t\t});\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: \"probe-search\",\r\n\t\t\t\tstatus: found ? \"pass\" : \"fail\",\r\n\t\t\t\tmessage: found\r\n\t\t\t\t\t? \"Semantic search returned the smoke-test record.\"\r\n\t\t\t\t\t: `Semantic search did not return the smoke-test record within ${(options?.timeoutMs ?? SMOKE_TEST_TIMEOUT_MS) / 1000} seconds.`,\r\n\t\t\t});\r\n\t\t} catch (error) {\r\n\t\t\tchecks.push({\r\n\t\t\t\tname: probeUpserted ? \"probe-search\" : \"probe-upsert\",\r\n\t\t\t\tstatus: \"fail\",\r\n\t\t\t\tmessage: error instanceof Error ? error.message : \"Smoke test failed with an unknown error.\",\r\n\t\t\t});\r\n\t\t} finally {\r\n\t\t\ttry {\r\n\t\t\t\tawait this.delete({ id: logicalId, namespace });\r\n\t\t\t\tchecks.push({\r\n\t\t\t\t\tname: \"probe-cleanup\",\r\n\t\t\t\t\tstatus: \"pass\",\r\n\t\t\t\t\tmessage: `Deleted smoke-test record ${logicalId}.`,\r\n\t\t\t\t});\r\n\t\t\t} catch (error) {\r\n\t\t\t\tchecks.push({\r\n\t\t\t\t\tname: \"probe-cleanup\",\r\n\t\t\t\t\tstatus: \"fail\",\r\n\t\t\t\t\tmessage: error instanceof Error ? error.message : `Failed to delete smoke-test record ${logicalId}.`,\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tok: checks.every((check) => check.status !== \"fail\"),\r\n\t\t\tchecks,\r\n\t\t\tnamespace,\r\n\t\t\tlogicalId,\r\n\t\t};\r\n\t}\r\n\r\n\tasync ensureIndexExists(createIfMissing: boolean, targetDimensions?: number): Promise<{ created: boolean; dimensions: number }> {\r\n\t\ttry {\r\n\t\t\tconst description = await this.vectorize.describeIndex();\r\n\t\t\treturn {\r\n\t\t\t\tcreated: false,\r\n\t\t\t\tdimensions: description.config.dimensions,\r\n\t\t\t};\r\n\t\t} catch (error) {\r\n\t\t\tif (!createIfMissing || !isCloudflareNotFoundError(error)) {\r\n\t\t\t\tthrow error;\r\n\t\t\t}\r\n\t\t\tconst dimensions = targetDimensions ?? (await this.inspectEmbeddingDimensions()).targetDimensions;\r\n\t\t\tconst createdIndex = await this.vectorize.createIndex(dimensions);\r\n\t\t\treturn {\r\n\t\t\t\tcreated: true,\r\n\t\t\t\tdimensions: createdIndex.config.dimensions,\r\n\t\t\t};\r\n\t\t}\r\n\t}\r\n\r\n\tprivate fromCompanionFallback(companionRecord: CompanionRecord | undefined, logicalId: string, namespace: string, path: string): UpsertedMemoryRecord {\r\n\t\treturn {\r\n\t\t\tlogicalId,\r\n\t\t\tvectorId: `${namespace}::${logicalId}`,\r\n\t\t\tnamespace,\r\n\t\t\ttitle: companionRecord?.title,\r\n\t\t\ttext: companionRecord?.text ?? \"\",\r\n\t\t\tmetadata: companionRecord?.metadata ?? {},\r\n\t\t\tsource: companionRecord?.source,\r\n\t\t\tcreatedAt: companionRecord?.createdAt,\r\n\t\t\tupdatedAt: companionRecord?.updatedAt,\r\n\t\t\tpath,\r\n\t\t};\r\n\t}\r\n\r\n\tprivate async waitForSearchHit(params: {\r\n\t\tquery: string;\r\n\t\tnamespace: string;\r\n\t\tlogicalId: string;\r\n\t\ttimeoutMs: number;\r\n\t\tpollIntervalMs: number;\r\n\t}): Promise<boolean> {\r\n\t\tconst deadline = Date.now() + params.timeoutMs;\r\n\t\twhile (Date.now() <= deadline) {\r\n\t\t\tconst results = await this.search({\r\n\t\t\t\tquery: params.query,\r\n\t\t\t\tnamespace: params.namespace,\r\n\t\t\t\tmaxResults: 5,\r\n\t\t\t\tminScore: 0,\r\n\t\t\t});\r\n\t\t\tif (results.some((record) => record.logicalId === params.logicalId)) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t\tif (Date.now() + params.pollIntervalMs > deadline) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tawait this.pause(params.pollIntervalMs);\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate async pause(ms: number): Promise<void> {\r\n\t\tawait new Promise((resolve) => setTimeout(resolve, ms));\r\n\t}\r\n}\r\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
2
2
  import { CompanionStore } from "./companion-store.js";
3
3
  import { WorkersAiEmbeddingsClient } from "./embeddings-client.js";
4
- import type { DoctorReport, HydratedMemoryRecord, MemoryRecordInput, MetadataFilter, ResolvedPluginConfig, UpsertedMemoryRecord } from "./types.js";
4
+ import type { DoctorReport, EmbeddingDimensionsInspection, HydratedMemoryRecord, IndexInitializationReport, MemoryRecordInput, MetadataFilter, ResolvedPluginConfig, SmokeTestReport, UpsertedMemoryRecord, VectorizeIndexDescription } from "./types.js";
5
5
  import { VectorizeClient } from "./vectorize-client.js";
6
6
  export declare class CloudflareMemoryService {
7
7
  readonly config: ResolvedPluginConfig;
@@ -51,9 +51,20 @@ export declare class CloudflareMemoryService {
51
51
  doctor(options: {
52
52
  createIndexIfMissing?: boolean;
53
53
  }): Promise<DoctorReport>;
54
- ensureIndexExists(createIfMissing: boolean): Promise<{
54
+ inspectEmbeddingDimensions(): Promise<EmbeddingDimensionsInspection>;
55
+ describeIndexIfExists(): Promise<VectorizeIndexDescription | null>;
56
+ initializeIndex(options?: {
57
+ recreateIfDimensionMismatch?: boolean;
58
+ }): Promise<IndexInitializationReport>;
59
+ runSmokeTest(options?: {
60
+ timeoutMs?: number;
61
+ pollIntervalMs?: number;
62
+ }): Promise<SmokeTestReport>;
63
+ ensureIndexExists(createIfMissing: boolean, targetDimensions?: number): Promise<{
55
64
  created: boolean;
56
65
  dimensions: number;
57
66
  }>;
58
67
  private fromCompanionFallback;
68
+ private waitForSearchHit;
69
+ private pause;
59
70
  }
@@ -130,6 +130,26 @@ export type DoctorReport = {
130
130
  ok: boolean;
131
131
  checks: DoctorCheck[];
132
132
  };
133
+ export type EmbeddingDimensionsInspection = {
134
+ embeddingDimensions: number;
135
+ configuredDimensions?: number;
136
+ configuredDimensionsMatchModel: boolean;
137
+ targetDimensions: number;
138
+ };
139
+ export type IndexInitializationReport = {
140
+ ok: boolean;
141
+ checks: DoctorCheck[];
142
+ created: boolean;
143
+ recreated: boolean;
144
+ embeddingDimensions: number;
145
+ indexDimensions?: number;
146
+ };
147
+ export type SmokeTestReport = {
148
+ ok: boolean;
149
+ checks: DoctorCheck[];
150
+ namespace: string;
151
+ logicalId: string;
152
+ };
133
153
  export type MigrationSourceMode = "paths" | "default-provider";
134
154
  export type MigrationDuplicateStrategy = "overwrite" | "skip" | "fail";
135
155
  export type MigrationNamespaceStrategy = "single-target" | "path";
@@ -4,6 +4,7 @@ export declare class VectorizeClient {
4
4
  constructor(config: ResolvedPluginConfig);
5
5
  describeIndex(): Promise<VectorizeIndexDescription>;
6
6
  createIndex(dimensions: number, metric?: import("./types.js").VectorizeMetric): Promise<VectorizeIndexDescription>;
7
+ deleteIndex(): Promise<void>;
7
8
  upsert(vectors: VectorizeVector[]): Promise<string | undefined>;
8
9
  query(params: {
9
10
  vector: number[];
@@ -25,6 +25,13 @@ var t = class {
25
25
  })
26
26
  });
27
27
  }
28
+ async deleteIndex() {
29
+ await e({
30
+ url: this.config.vectorizeBaseUrl,
31
+ apiToken: this.config.apiToken,
32
+ method: "DELETE"
33
+ });
34
+ }
28
35
  async upsert(t) {
29
36
  let n = t.map((e) => JSON.stringify(e)).join("\n");
30
37
  return (await e({
@@ -1 +1 @@
1
- {"version":3,"file":"vectorize-client.js","names":[],"sources":["../src/vectorize-client.ts"],"sourcesContent":["import { requestCloudflare } from \"./cloudflare-api.js\";\nimport type { MetadataFilter, ResolvedPluginConfig, VectorizeIndexDescription, VectorizeQueryMatch, VectorizeVector } from \"./types.js\";\n\ntype MutationResponse = {\n\tmutationId?: string;\n};\n\ntype QueryResponse = {\n\tcount?: number;\n\tmatches?: VectorizeQueryMatch[];\n};\n\nexport class VectorizeClient {\n\tconstructor(private readonly config: ResolvedPluginConfig) {}\n\n\tasync describeIndex(): Promise<VectorizeIndexDescription> {\n\t\treturn requestCloudflare<VectorizeIndexDescription>({\n\t\t\turl: this.config.vectorizeBaseUrl,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\tmethod: \"GET\",\n\t\t});\n\t}\n\n\tasync createIndex(dimensions: number, metric = this.config.createIndex.metric): Promise<VectorizeIndexDescription> {\n\t\treturn requestCloudflare<VectorizeIndexDescription>({\n\t\t\turl: `${this.config.apiBaseUrl}/accounts/${this.config.accountId}/vectorize/v2/indexes`,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\tbody: JSON.stringify({\n\t\t\t\tname: this.config.indexName,\n\t\t\t\tdescription: this.config.createIndex.description,\n\t\t\t\tconfig: {\n\t\t\t\t\tdimensions,\n\t\t\t\t\tmetric,\n\t\t\t\t},\n\t\t\t}),\n\t\t});\n\t}\n\n\tasync upsert(vectors: VectorizeVector[]): Promise<string | undefined> {\n\t\tconst body = vectors.map((vector) => JSON.stringify(vector)).join(\"\\n\");\n\t\tconst result = await requestCloudflare<MutationResponse>({\n\t\t\turl: `${this.config.vectorizeBaseUrl}/upsert`,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/x-ndjson\",\n\t\t\t},\n\t\t\tbody,\n\t\t});\n\t\treturn result.mutationId;\n\t}\n\n\tasync query(params: {\n\t\tvector: number[];\n\t\tnamespace?: string;\n\t\ttopK?: number;\n\t\tfilter?: MetadataFilter;\n\t\treturnValues?: boolean;\n\t}): Promise<VectorizeQueryMatch[]> {\n\t\tconst result = await requestCloudflare<QueryResponse>({\n\t\t\turl: `${this.config.vectorizeBaseUrl}/query`,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\tbody: JSON.stringify({\n\t\t\t\tvector: params.vector,\n\t\t\t\ttopK: params.topK ?? this.config.topK,\n\t\t\t\tfilter: params.filter,\n\t\t\t\tnamespace: params.namespace,\n\t\t\t\treturnValues: params.returnValues ?? false,\n\t\t\t}),\n\t\t});\n\t\treturn result.matches ?? [];\n\t}\n\n\tasync getByIds(ids: string[]): Promise<VectorizeQueryMatch[]> {\n\t\tif (ids.length === 0) {\n\t\t\treturn [];\n\t\t}\n\t\treturn requestCloudflare<VectorizeQueryMatch[]>({\n\t\t\turl: `${this.config.vectorizeBaseUrl}/get_by_ids`,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\tbody: JSON.stringify({ ids }),\n\t\t});\n\t}\n\n\tasync deleteByIds(ids: string[]): Promise<string | undefined> {\n\t\tif (ids.length === 0) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst result = await requestCloudflare<MutationResponse>({\n\t\t\turl: `${this.config.vectorizeBaseUrl}/delete_by_ids`,\n\t\t\tapiToken: this.config.apiToken,\n\t\t\tbody: JSON.stringify({ ids }),\n\t\t});\n\t\treturn result.mutationId;\n\t}\n}\n"],"mappings":";;AAYA,IAAa,IAAb,MAA6B;CAC5B,YAAY,GAA+C;AAA9B,OAAA,SAAA;;CAE7B,MAAM,gBAAoD;AACzD,SAAO,EAA6C;GACnD,KAAK,KAAK,OAAO;GACjB,UAAU,KAAK,OAAO;GACtB,QAAQ;GACR,CAAC;;CAGH,MAAM,YAAY,GAAoB,IAAS,KAAK,OAAO,YAAY,QAA4C;AAClH,SAAO,EAA6C;GACnD,KAAK,GAAG,KAAK,OAAO,WAAW,YAAY,KAAK,OAAO,UAAU;GACjE,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU;IACpB,MAAM,KAAK,OAAO;IAClB,aAAa,KAAK,OAAO,YAAY;IACrC,QAAQ;KACP;KACA;KACA;IACD,CAAC;GACF,CAAC;;CAGH,MAAM,OAAO,GAAyD;EACrE,IAAM,IAAO,EAAQ,KAAK,MAAW,KAAK,UAAU,EAAO,CAAC,CAAC,KAAK,KAAK;AASvE,UARe,MAAM,EAAoC;GACxD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,SAAS,EACR,gBAAgB,wBAChB;GACD;GACA,CAAC,EACY;;CAGf,MAAM,MAAM,GAMuB;AAYlC,UAXe,MAAM,EAAiC;GACrD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU;IACpB,QAAQ,EAAO;IACf,MAAM,EAAO,QAAQ,KAAK,OAAO;IACjC,QAAQ,EAAO;IACf,WAAW,EAAO;IAClB,cAAc,EAAO,gBAAgB;IACrC,CAAC;GACF,CAAC,EACY,WAAW,EAAE;;CAG5B,MAAM,SAAS,GAA+C;AAI7D,SAHI,EAAI,WAAW,IACX,EAAE,GAEH,EAAyC;GAC/C,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU,EAAE,QAAK,CAAC;GAC7B,CAAC;;CAGH,MAAM,YAAY,GAA4C;AACzD,QAAI,WAAW,EAQnB,SALe,MAAM,EAAoC;GACxD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU,EAAE,QAAK,CAAC;GAC7B,CAAC,EACY"}
1
+ {"version":3,"file":"vectorize-client.js","names":[],"sources":["../src/vectorize-client.ts"],"sourcesContent":["import { requestCloudflare } from \"./cloudflare-api.js\";\r\nimport type { MetadataFilter, ResolvedPluginConfig, VectorizeIndexDescription, VectorizeQueryMatch, VectorizeVector } from \"./types.js\";\r\n\r\ntype MutationResponse = {\r\n\tmutationId?: string;\r\n};\r\n\r\ntype QueryResponse = {\r\n\tcount?: number;\r\n\tmatches?: VectorizeQueryMatch[];\r\n};\r\n\r\nexport class VectorizeClient {\r\n\tconstructor(private readonly config: ResolvedPluginConfig) {}\r\n\r\n\tasync describeIndex(): Promise<VectorizeIndexDescription> {\r\n\t\treturn requestCloudflare<VectorizeIndexDescription>({\r\n\t\t\turl: this.config.vectorizeBaseUrl,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tmethod: \"GET\",\r\n\t\t});\r\n\t}\r\n\r\n\tasync createIndex(dimensions: number, metric = this.config.createIndex.metric): Promise<VectorizeIndexDescription> {\r\n\t\treturn requestCloudflare<VectorizeIndexDescription>({\r\n\t\t\turl: `${this.config.apiBaseUrl}/accounts/${this.config.accountId}/vectorize/v2/indexes`,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tname: this.config.indexName,\r\n\t\t\t\tdescription: this.config.createIndex.description,\r\n\t\t\t\tconfig: {\r\n\t\t\t\t\tdimensions,\r\n\t\t\t\t\tmetric,\r\n\t\t\t\t},\r\n\t\t\t}),\r\n\t\t});\r\n\t}\r\n\r\n\tasync deleteIndex(): Promise<void> {\r\n\t\tawait requestCloudflare<unknown>({\r\n\t\t\turl: this.config.vectorizeBaseUrl,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tmethod: \"DELETE\",\r\n\t\t});\r\n\t}\r\n\r\n\tasync upsert(vectors: VectorizeVector[]): Promise<string | undefined> {\r\n\t\tconst body = vectors.map((vector) => JSON.stringify(vector)).join(\"\\n\");\r\n\t\tconst result = await requestCloudflare<MutationResponse>({\r\n\t\t\turl: `${this.config.vectorizeBaseUrl}/upsert`,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\theaders: {\r\n\t\t\t\t\"Content-Type\": \"application/x-ndjson\",\r\n\t\t\t},\r\n\t\t\tbody,\r\n\t\t});\r\n\t\treturn result.mutationId;\r\n\t}\r\n\r\n\tasync query(params: {\r\n\t\tvector: number[];\r\n\t\tnamespace?: string;\r\n\t\ttopK?: number;\r\n\t\tfilter?: MetadataFilter;\r\n\t\treturnValues?: boolean;\r\n\t}): Promise<VectorizeQueryMatch[]> {\r\n\t\tconst result = await requestCloudflare<QueryResponse>({\r\n\t\t\turl: `${this.config.vectorizeBaseUrl}/query`,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tvector: params.vector,\r\n\t\t\t\ttopK: params.topK ?? this.config.topK,\r\n\t\t\t\tfilter: params.filter,\r\n\t\t\t\tnamespace: params.namespace,\r\n\t\t\t\treturnValues: params.returnValues ?? false,\r\n\t\t\t}),\r\n\t\t});\r\n\t\treturn result.matches ?? [];\r\n\t}\r\n\r\n\tasync getByIds(ids: string[]): Promise<VectorizeQueryMatch[]> {\r\n\t\tif (ids.length === 0) {\r\n\t\t\treturn [];\r\n\t\t}\r\n\t\treturn requestCloudflare<VectorizeQueryMatch[]>({\r\n\t\t\turl: `${this.config.vectorizeBaseUrl}/get_by_ids`,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tbody: JSON.stringify({ ids }),\r\n\t\t});\r\n\t}\r\n\r\n\tasync deleteByIds(ids: string[]): Promise<string | undefined> {\r\n\t\tif (ids.length === 0) {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\t\tconst result = await requestCloudflare<MutationResponse>({\r\n\t\t\turl: `${this.config.vectorizeBaseUrl}/delete_by_ids`,\r\n\t\t\tapiToken: this.config.apiToken,\r\n\t\t\tbody: JSON.stringify({ ids }),\r\n\t\t});\r\n\t\treturn result.mutationId;\r\n\t}\r\n}\r\n"],"mappings":";;AAYA,IAAa,IAAb,MAA6B;CAC5B,YAAY,GAA+C;AAA9B,OAAA,SAAA;;CAE7B,MAAM,gBAAoD;AACzD,SAAO,EAA6C;GACnD,KAAK,KAAK,OAAO;GACjB,UAAU,KAAK,OAAO;GACtB,QAAQ;GACR,CAAC;;CAGH,MAAM,YAAY,GAAoB,IAAS,KAAK,OAAO,YAAY,QAA4C;AAClH,SAAO,EAA6C;GACnD,KAAK,GAAG,KAAK,OAAO,WAAW,YAAY,KAAK,OAAO,UAAU;GACjE,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU;IACpB,MAAM,KAAK,OAAO;IAClB,aAAa,KAAK,OAAO,YAAY;IACrC,QAAQ;KACP;KACA;KACA;IACD,CAAC;GACF,CAAC;;CAGH,MAAM,cAA6B;AAClC,QAAM,EAA2B;GAChC,KAAK,KAAK,OAAO;GACjB,UAAU,KAAK,OAAO;GACtB,QAAQ;GACR,CAAC;;CAGH,MAAM,OAAO,GAAyD;EACrE,IAAM,IAAO,EAAQ,KAAK,MAAW,KAAK,UAAU,EAAO,CAAC,CAAC,KAAK,KAAK;AASvE,UARe,MAAM,EAAoC;GACxD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,SAAS,EACR,gBAAgB,wBAChB;GACD;GACA,CAAC,EACY;;CAGf,MAAM,MAAM,GAMuB;AAYlC,UAXe,MAAM,EAAiC;GACrD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU;IACpB,QAAQ,EAAO;IACf,MAAM,EAAO,QAAQ,KAAK,OAAO;IACjC,QAAQ,EAAO;IACf,WAAW,EAAO;IAClB,cAAc,EAAO,gBAAgB;IACrC,CAAC;GACF,CAAC,EACY,WAAW,EAAE;;CAG5B,MAAM,SAAS,GAA+C;AAI7D,SAHI,EAAI,WAAW,IACX,EAAE,GAEH,EAAyC;GAC/C,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU,EAAE,QAAK,CAAC;GAC7B,CAAC;;CAGH,MAAM,YAAY,GAA4C;AACzD,QAAI,WAAW,EAQnB,SALe,MAAM,EAAoC;GACxD,KAAK,GAAG,KAAK,OAAO,iBAAiB;GACrC,UAAU,KAAK,OAAO;GACtB,MAAM,KAAK,UAAU,EAAE,QAAK,CAAC;GAC7B,CAAC,EACY"}
@@ -1,3 +1,3 @@
1
- declare function handler(event: unknown): Promise<void>;
2
-
3
- export default handler;
1
+ declare function handler(event: unknown): Promise<void>;
2
+
3
+ export default handler;
@@ -1,17 +1,17 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- const bootstrapPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "BOOTSTRAP.md");
5
-
6
- export default async function handler(event) {
7
- if (event?.type !== "agent" || event?.action !== "bootstrap") {
8
- return;
9
- }
10
-
11
- const bootstrapFiles = event?.context?.bootstrapFiles;
12
- if (!Array.isArray(bootstrapFiles) || bootstrapFiles.includes(bootstrapPath)) {
13
- return;
14
- }
15
-
16
- bootstrapFiles.push(bootstrapPath);
17
- }
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ const bootstrapPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "BOOTSTRAP.md");
5
+
6
+ export default async function handler(event) {
7
+ if (event?.type !== "agent" || event?.action !== "bootstrap") {
8
+ return;
9
+ }
10
+
11
+ const bootstrapFiles = event?.context?.bootstrapFiles;
12
+ if (!Array.isArray(bootstrapFiles) || bootstrapFiles.includes(bootstrapPath)) {
13
+ return;
14
+ }
15
+
16
+ bootstrapFiles.push(bootstrapPath);
17
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "openclaw-cloudflare-vectorize-memory",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OpenClaw memory plugin for Cloudflare Vectorize and Workers AI embeddings.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/types/index.d.ts",
8
8
  "files": [
9
+ "cli-metadata.ts",
9
10
  "dist",
10
11
  "hooks",
11
12
  "openclaw.plugin.json",