@matrixorigin/thememoria 0.4.0

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.
@@ -0,0 +1,727 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PLUGIN_ID="memory-memoria"
5
+ DEFAULT_REPO_URL="https://github.com/matrixorigin/Memoria.git"
6
+ DEFAULT_REPO_REF="main"
7
+ DEFAULT_MEMORIA_VERSION="v0.1.0"
8
+
9
+ MEMORIA_TOOL_NAMES=(
10
+ memory_search
11
+ memory_get
12
+ memory_store
13
+ memory_retrieve
14
+ memory_recall
15
+ memory_list
16
+ memory_stats
17
+ memory_profile
18
+ memory_correct
19
+ memory_purge
20
+ memory_forget
21
+ memory_health
22
+ memory_observe
23
+ memory_governance
24
+ memory_consolidate
25
+ memory_reflect
26
+ memory_extract_entities
27
+ memory_link_entities
28
+ memory_rebuild_index
29
+ memory_capabilities
30
+ memory_snapshot
31
+ memory_snapshots
32
+ memory_rollback
33
+ memory_branch
34
+ memory_branches
35
+ memory_checkout
36
+ memory_branch_delete
37
+ memory_merge
38
+ memory_diff
39
+ )
40
+
41
+ log() {
42
+ printf '[memory-memoria] %s\n' "$*"
43
+ }
44
+
45
+ fail() {
46
+ printf '[memory-memoria] error: %s\n' "$*" >&2
47
+ exit 1
48
+ }
49
+
50
+ need_cmd() {
51
+ command -v "$1" >/dev/null 2>&1 || fail "Missing required command: $1"
52
+ }
53
+
54
+ validate_openclaw_bin() {
55
+ local candidate="$1"
56
+ if ! "${candidate}" --version >/dev/null 2>&1; then
57
+ return 1
58
+ fi
59
+ return 0
60
+ }
61
+
62
+ resolve_openclaw_bin() {
63
+ local candidate="${1:-openclaw}"
64
+ local resolved=''
65
+
66
+ if [[ "${candidate}" == */* ]]; then
67
+ [[ -x "${candidate}" ]] || fail "OPENCLAW_BIN is not executable: ${candidate}"
68
+ printf '%s' "${candidate}"
69
+ return 0
70
+ fi
71
+
72
+ resolved="$(command -v "${candidate}" 2>/dev/null || true)"
73
+ if [[ -n "${resolved}" ]]; then
74
+ printf '%s' "${resolved}"
75
+ return 0
76
+ fi
77
+
78
+ for fallback in \
79
+ "${HOME}/Library/pnpm/openclaw" \
80
+ "${HOME}/.local/share/pnpm/openclaw" \
81
+ "${HOME}/.pnpm/openclaw"
82
+ do
83
+ if [[ -x "${fallback}" ]]; then
84
+ printf '%s' "${fallback}"
85
+ return 0
86
+ fi
87
+ done
88
+
89
+ if command -v pnpm >/dev/null 2>&1; then
90
+ resolved="$(pnpm bin -g 2>/dev/null || true)"
91
+ if [[ -n "${resolved}" && -x "${resolved}/openclaw" ]]; then
92
+ printf '%s' "${resolved}/openclaw"
93
+ return 0
94
+ fi
95
+ fi
96
+
97
+ fail "Missing required command: openclaw. Set OPENCLAW_BIN=/absolute/path/to/openclaw"
98
+ }
99
+
100
+ usage() {
101
+ cat <<'EOF'
102
+ Install the OpenClaw Memoria plugin using the Rust Memoria CLI runtime.
103
+
104
+ Usage:
105
+ bash scripts/install-openclaw-memoria.sh [options]
106
+ curl -fsSL <raw-script-url> | env MEMORIA_EMBEDDING_API_KEY=... bash -s --
107
+
108
+ Options:
109
+ --source-dir <path> Use an existing checkout instead of cloning.
110
+ --install-dir <path> Clone target when --source-dir is not provided.
111
+ --repo-url <url> Git repo to clone when no local checkout is used.
112
+ --ref <ref> Git branch, tag, or ref to clone. Default: main.
113
+ --openclaw-bin <path|command> Use an existing openclaw executable.
114
+ --memoria-bin <path|command> Use an existing memoria executable.
115
+ --memoria-version <tag> Rust Memoria release tag. Default: v0.1.0.
116
+ --memoria-install-dir <path> Where to install memoria if it is missing.
117
+ --binary-only Only install/validate the memoria binary, then exit.
118
+ --skip-memoria-install Require an existing memoria executable.
119
+ --skip-plugin-install Assume the plugin is already installed/enabled in OpenClaw.
120
+ --verify Run verify_plugin_install.mjs after installation.
121
+ --help Show this help text.
122
+
123
+ Environment overrides:
124
+ OPENCLAW_BIN Default: auto-detected openclaw executable
125
+ OPENCLAW_HOME Optional target OpenClaw home.
126
+ MEMORIA_DB_URL Default: mysql://root:111@127.0.0.1:6001/memoria
127
+ MEMORIA_DEFAULT_USER_ID Default: openclaw-user
128
+ MEMORIA_USER_ID_STRATEGY Default: config
129
+ MEMORIA_AUTO_RECALL Default: true
130
+ MEMORIA_AUTO_OBSERVE Default: false
131
+ MEMORIA_EXECUTABLE Alias for --memoria-bin
132
+ MEMORIA_RELEASE_TAG Alias for --memoria-version
133
+ MEMORIA_BINARY_INSTALL_DIR Alias for --memoria-install-dir
134
+ MEMORIA_EMBEDDING_PROVIDER Default: openai
135
+ MEMORIA_EMBEDDING_MODEL Default: text-embedding-3-small
136
+ MEMORIA_EMBEDDING_BASE_URL Optional for official OpenAI; required for compatible gateways
137
+ MEMORIA_EMBEDDING_API_KEY Required unless provider=local
138
+ MEMORIA_EMBEDDING_DIM Auto-filled for common models; otherwise required
139
+ MEMORIA_LLM_BASE_URL Optional OpenAI-compatible base URL
140
+ MEMORIA_LLM_API_KEY Optional; required if autoObserve=true
141
+ MEMORIA_LLM_MODEL Optional; required if autoObserve=true
142
+ EOF
143
+ }
144
+
145
+ normalize_bool() {
146
+ local raw="${1:-}"
147
+ raw="$(printf '%s' "${raw}" | tr '[:upper:]' '[:lower:]')"
148
+ case "${raw}" in
149
+ 1|true|yes|on)
150
+ printf 'true'
151
+ ;;
152
+ 0|false|no|off)
153
+ printf 'false'
154
+ ;;
155
+ *)
156
+ fail "Expected boolean value, got: ${raw}"
157
+ ;;
158
+ esac
159
+ }
160
+
161
+ infer_embedding_dim() {
162
+ local model="${1:-}"
163
+ case "${model}" in
164
+ text-embedding-3-small|openai/text-embedding-3-small)
165
+ printf '1536'
166
+ ;;
167
+ text-embedding-3-large|openai/text-embedding-3-large)
168
+ printf '3072'
169
+ ;;
170
+ text-embedding-ada-002|openai/text-embedding-ada-002)
171
+ printf '1536'
172
+ ;;
173
+ all-MiniLM-L6-v2|sentence-transformers/all-MiniLM-L6-v2)
174
+ printf '384'
175
+ ;;
176
+ BAAI/bge-m3)
177
+ printf '1024'
178
+ ;;
179
+ *)
180
+ printf ''
181
+ ;;
182
+ esac
183
+ }
184
+
185
+ normalize_base_url() {
186
+ local url="${1:-}"
187
+ url="${url%/}"
188
+ case "${url}" in
189
+ */embeddings)
190
+ url="${url%/embeddings}"
191
+ ;;
192
+ */chat/completions)
193
+ url="${url%/chat/completions}"
194
+ ;;
195
+ */completions)
196
+ url="${url%/completions}"
197
+ ;;
198
+ esac
199
+ printf '%s' "${url}"
200
+ }
201
+
202
+ normalize_db_url() {
203
+ local url="${1:-}"
204
+ printf '%s' "${url/mysql+pymysql:\/\//mysql://}"
205
+ }
206
+
207
+ resolve_memoria_target() {
208
+ local os arch
209
+ os="$(uname -s | tr '[:upper:]' '[:lower:]')"
210
+ arch="$(uname -m)"
211
+ case "${arch}" in
212
+ x86_64|amd64) arch="x86_64" ;;
213
+ aarch64|arm64) arch="aarch64" ;;
214
+ *) arch="" ;;
215
+ esac
216
+ case "${os}" in
217
+ linux)
218
+ [[ "${arch}" == "x86_64" ]] && printf 'x86_64-unknown-linux-musl' && return 0
219
+ [[ "${arch}" == "aarch64" ]] && printf 'aarch64-unknown-linux-musl' && return 0
220
+ ;;
221
+ darwin)
222
+ [[ "${arch}" == "x86_64" ]] && printf 'x86_64-apple-darwin' && return 0
223
+ [[ "${arch}" == "aarch64" ]] && printf 'aarch64-apple-darwin' && return 0
224
+ ;;
225
+ esac
226
+ fail "Unsupported platform: $(uname -s) $(uname -m)"
227
+ }
228
+
229
+ install_memoria_binary() {
230
+ local install_dir="$1"
231
+ local version="$2"
232
+ local target asset url sum_url tmp
233
+
234
+ need_cmd curl
235
+ need_cmd tar
236
+
237
+ target="$(resolve_memoria_target)"
238
+ asset="memoria-${target}.tar.gz"
239
+ url="https://github.com/matrixorigin/Memoria/releases/download/${version}/${asset}"
240
+ sum_url="https://github.com/matrixorigin/Memoria/releases/download/${version}/SHA256SUMS.txt"
241
+
242
+ mkdir -p "${install_dir}"
243
+ tmp="$(mktemp -d)"
244
+ trap 'rm -rf "${tmp}"' RETURN
245
+
246
+ log "Downloading Rust Memoria ${version} (${target})"
247
+ curl -fL# -o "${tmp}/${asset}" "${url}"
248
+
249
+ if curl -fsSL -o "${tmp}/SHA256SUMS.txt" "${sum_url}" 2>/dev/null; then
250
+ if command -v sha256sum >/dev/null 2>&1; then
251
+ (cd "${tmp}" && grep -F "${asset}" SHA256SUMS.txt | sha256sum -c - >/dev/null)
252
+ elif command -v shasum >/dev/null 2>&1; then
253
+ (cd "${tmp}" && grep -F "${asset}" SHA256SUMS.txt | shasum -a 256 -c - >/dev/null)
254
+ fi
255
+ fi
256
+
257
+ tar -xzf "${tmp}/${asset}" -C "${tmp}"
258
+ cp "${tmp}/memoria" "${install_dir}/memoria"
259
+ chmod +x "${install_dir}/memoria"
260
+ printf '%s' "${install_dir}/memoria"
261
+ }
262
+
263
+ resolve_memoria_bin() {
264
+ local candidate="${1:-}"
265
+ if [[ -n "${candidate}" ]]; then
266
+ if [[ "${candidate}" == */* ]]; then
267
+ [[ -x "${candidate}" ]] || fail "Memoria executable is not executable: ${candidate}"
268
+ printf '%s' "${candidate}"
269
+ return 0
270
+ fi
271
+ local resolved=''
272
+ resolved="$(command -v "${candidate}" 2>/dev/null || true)"
273
+ [[ -n "${resolved}" ]] || fail "Could not find memoria executable in PATH: ${candidate}"
274
+ printf '%s' "${resolved}"
275
+ return 0
276
+ fi
277
+
278
+ local resolved=''
279
+ resolved="$(command -v memoria 2>/dev/null || true)"
280
+ if [[ -n "${resolved}" ]]; then
281
+ printf '%s' "${resolved}"
282
+ return 0
283
+ fi
284
+
285
+ return 1
286
+ }
287
+
288
+ run_openclaw() {
289
+ if [[ -n "${OPENCLAW_HOME_VALUE}" ]]; then
290
+ OPENCLAW_HOME="${OPENCLAW_HOME_VALUE}" "${OPENCLAW_BIN}" "$@"
291
+ else
292
+ "${OPENCLAW_BIN}" "$@"
293
+ fi
294
+ }
295
+
296
+ config_file_path() {
297
+ if [[ -n "${OPENCLAW_HOME_VALUE}" ]]; then
298
+ printf '%s/.openclaw/openclaw.json' "${OPENCLAW_HOME_VALUE}"
299
+ else
300
+ printf '%s/.openclaw/openclaw.json' "${HOME}"
301
+ fi
302
+ }
303
+
304
+ skills_dir_path() {
305
+ if [[ -n "${OPENCLAW_HOME_VALUE}" ]]; then
306
+ printf '%s/.openclaw/skills' "${OPENCLAW_HOME_VALUE}"
307
+ else
308
+ printf '%s/.openclaw/skills' "${HOME}"
309
+ fi
310
+ }
311
+
312
+ install_bundled_skills() {
313
+ local source_skills_dir="$1"
314
+ local managed_skills_dir="$2"
315
+
316
+ [[ -d "${source_skills_dir}" ]] || return 0
317
+
318
+ mkdir -p "${managed_skills_dir}"
319
+ local skill_dir=""
320
+ for skill_dir in "${source_skills_dir}"/*; do
321
+ [[ -d "${skill_dir}" ]] || continue
322
+ local skill_name
323
+ skill_name="$(basename -- "${skill_dir}")"
324
+ rm -rf "${managed_skills_dir}/${skill_name}"
325
+ cp -R "${skill_dir}" "${managed_skills_dir}/${skill_name}"
326
+ log "Installed managed skill: ${skill_name}"
327
+ done
328
+ }
329
+
330
+ SOURCE_DIR="${MEMORIA_SOURCE_DIR:-}"
331
+ INSTALL_DIR="${MEMORIA_INSTALL_DIR:-$HOME/.local/share/openclaw-plugins/openclaw-memoria}"
332
+ REPO_URL="${MEMORIA_REPO_URL:-$DEFAULT_REPO_URL}"
333
+ REPO_REF="${MEMORIA_REPO_REF:-$DEFAULT_REPO_REF}"
334
+ OPENCLAW_BIN="${OPENCLAW_BIN:-openclaw}"
335
+ OPENCLAW_HOME_VALUE="${OPENCLAW_HOME:-}"
336
+ MEMORIA_BIN="${MEMORIA_EXECUTABLE:-${MEMORIA_BIN:-}}"
337
+ MEMORIA_RELEASE_TAG="${MEMORIA_RELEASE_TAG:-$DEFAULT_MEMORIA_VERSION}"
338
+ MEMORIA_BINARY_INSTALL_DIR="${MEMORIA_BINARY_INSTALL_DIR:-$HOME/.local/bin}"
339
+ SKIP_MEMORIA_INSTALL=false
340
+ SKIP_PLUGIN_INSTALL=false
341
+ BINARY_ONLY=false
342
+ RUN_VERIFY=false
343
+
344
+ while [[ $# -gt 0 ]]; do
345
+ case "$1" in
346
+ --source-dir)
347
+ SOURCE_DIR="${2:?missing value for --source-dir}"
348
+ shift 2
349
+ ;;
350
+ --install-dir)
351
+ INSTALL_DIR="${2:?missing value for --install-dir}"
352
+ shift 2
353
+ ;;
354
+ --repo-url)
355
+ REPO_URL="${2:?missing value for --repo-url}"
356
+ shift 2
357
+ ;;
358
+ --ref)
359
+ REPO_REF="${2:?missing value for --ref}"
360
+ shift 2
361
+ ;;
362
+ --openclaw-bin)
363
+ OPENCLAW_BIN="${2:?missing value for --openclaw-bin}"
364
+ shift 2
365
+ ;;
366
+ --memoria-bin)
367
+ MEMORIA_BIN="${2:?missing value for --memoria-bin}"
368
+ shift 2
369
+ ;;
370
+ --memoria-version)
371
+ MEMORIA_RELEASE_TAG="${2:?missing value for --memoria-version}"
372
+ shift 2
373
+ ;;
374
+ --memoria-install-dir)
375
+ MEMORIA_BINARY_INSTALL_DIR="${2:?missing value for --memoria-install-dir}"
376
+ shift 2
377
+ ;;
378
+ --skip-memoria-install)
379
+ SKIP_MEMORIA_INSTALL=true
380
+ shift
381
+ ;;
382
+ --binary-only)
383
+ BINARY_ONLY=true
384
+ shift
385
+ ;;
386
+ --skip-plugin-install)
387
+ SKIP_PLUGIN_INSTALL=true
388
+ shift
389
+ ;;
390
+ --verify)
391
+ RUN_VERIFY=true
392
+ shift
393
+ ;;
394
+ --help|-h)
395
+ usage
396
+ exit 0
397
+ ;;
398
+ *)
399
+ fail "Unknown option: $1"
400
+ ;;
401
+ esac
402
+ done
403
+
404
+ OPENCLAW_BIN="$(resolve_openclaw_bin "${OPENCLAW_BIN}")"
405
+ validate_openclaw_bin "${OPENCLAW_BIN}" || fail "OpenClaw executable is not healthy: ${OPENCLAW_BIN}. Fix OpenClaw first, then retry."
406
+ need_cmd node
407
+
408
+ log "Using OpenClaw executable: ${OPENCLAW_BIN}"
409
+ log "OpenClaw version: $("${OPENCLAW_BIN}" --version 2>/dev/null | head -n 1)"
410
+
411
+ if [[ -z "${SOURCE_DIR}" ]]; then
412
+ if [[ -f "${PWD}/openclaw.plugin.json" && -f "${PWD}/package.json" ]]; then
413
+ SOURCE_DIR="${PWD}"
414
+ else
415
+ SCRIPT_SOURCE="${BASH_SOURCE:-}"
416
+ if [[ -n "${SCRIPT_SOURCE}" ]]; then
417
+ SCRIPT_DIR="$(cd -- "$(dirname -- "${SCRIPT_SOURCE}")" && pwd)"
418
+ REPO_CANDIDATE="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
419
+ if [[ -f "${REPO_CANDIDATE}/openclaw.plugin.json" && -f "${REPO_CANDIDATE}/package.json" ]]; then
420
+ SOURCE_DIR="${REPO_CANDIDATE}"
421
+ fi
422
+ fi
423
+ fi
424
+ fi
425
+
426
+ if [[ -z "${SOURCE_DIR}" ]]; then
427
+ need_cmd git
428
+ SOURCE_DIR="${INSTALL_DIR}"
429
+ mkdir -p "$(dirname -- "${SOURCE_DIR}")"
430
+ if [[ -d "${SOURCE_DIR}/.git" ]]; then
431
+ log "Updating existing checkout in ${SOURCE_DIR}"
432
+ git -C "${SOURCE_DIR}" fetch --depth 1 origin "${REPO_REF}"
433
+ git -C "${SOURCE_DIR}" checkout -f FETCH_HEAD
434
+ elif [[ -e "${SOURCE_DIR}" ]]; then
435
+ fail "Install dir already exists and is not a git checkout: ${SOURCE_DIR}"
436
+ else
437
+ log "Cloning ${REPO_URL}#${REPO_REF} to ${SOURCE_DIR}"
438
+ git clone --depth 1 --branch "${REPO_REF}" "${REPO_URL}" "${SOURCE_DIR}"
439
+ fi
440
+ else
441
+ SOURCE_DIR="$(cd -- "${SOURCE_DIR}" && pwd)"
442
+ log "Using existing checkout: ${SOURCE_DIR}"
443
+ fi
444
+
445
+ if [[ ! -f "${SOURCE_DIR}/openclaw.plugin.json" || ! -f "${SOURCE_DIR}/package.json" ]]; then
446
+ if [[ -f "${SOURCE_DIR}/plugins/openclaw/openclaw.plugin.json" && -f "${SOURCE_DIR}/plugins/openclaw/package.json" ]]; then
447
+ SOURCE_DIR="${SOURCE_DIR}/plugins/openclaw"
448
+ log "Resolved plugin source directory: ${SOURCE_DIR}"
449
+ fi
450
+ fi
451
+
452
+ [[ -f "${SOURCE_DIR}/openclaw.plugin.json" ]] || fail "Missing openclaw.plugin.json in ${SOURCE_DIR}"
453
+ [[ -f "${SOURCE_DIR}/package.json" ]] || fail "Missing package.json in ${SOURCE_DIR}"
454
+
455
+ if MEMORIA_EXECUTABLE_VALUE="$(resolve_memoria_bin "${MEMORIA_BIN}" 2>/dev/null)"; then
456
+ log "Using existing memoria executable: ${MEMORIA_EXECUTABLE_VALUE}"
457
+ else
458
+ [[ "${SKIP_MEMORIA_INSTALL}" == false ]] || fail "--skip-memoria-install requires an existing memoria executable"
459
+ MEMORIA_EXECUTABLE_VALUE="$(install_memoria_binary "${MEMORIA_BINARY_INSTALL_DIR}" "${MEMORIA_RELEASE_TAG}")"
460
+ log "Installed memoria executable: ${MEMORIA_EXECUTABLE_VALUE}"
461
+ fi
462
+ log "Memoria version: $("${MEMORIA_EXECUTABLE_VALUE}" --version 2>/dev/null | head -n 1)"
463
+
464
+ if [[ "${BINARY_ONLY}" == true ]]; then
465
+ cat <<EOF
466
+
467
+ Binary install complete.
468
+
469
+ Memoria executable: ${MEMORIA_EXECUTABLE_VALUE}
470
+ EOF
471
+ exit 0
472
+ fi
473
+
474
+ MEMORIA_DB_URL="$(normalize_db_url "${MEMORIA_DB_URL:-mysql://root:111@127.0.0.1:6001/memoria}")"
475
+ MEMORIA_DEFAULT_USER_ID="${MEMORIA_DEFAULT_USER_ID:-openclaw-user}"
476
+ MEMORIA_USER_ID_STRATEGY="${MEMORIA_USER_ID_STRATEGY:-config}"
477
+ MEMORIA_AUTO_RECALL="$(normalize_bool "${MEMORIA_AUTO_RECALL:-true}")"
478
+ MEMORIA_AUTO_OBSERVE="$(normalize_bool "${MEMORIA_AUTO_OBSERVE:-false}")"
479
+ MEMORIA_EMBEDDING_PROVIDER="${MEMORIA_EMBEDDING_PROVIDER:-openai}"
480
+ MEMORIA_EMBEDDING_MODEL="${MEMORIA_EMBEDDING_MODEL:-text-embedding-3-small}"
481
+ MEMORIA_EMBEDDING_BASE_URL="${MEMORIA_EMBEDDING_BASE_URL:-}"
482
+ MEMORIA_EMBEDDING_API_KEY="${MEMORIA_EMBEDDING_API_KEY:-}"
483
+ MEMORIA_EMBEDDING_DIM="${MEMORIA_EMBEDDING_DIM:-}"
484
+ MEMORIA_LLM_BASE_URL="${MEMORIA_LLM_BASE_URL:-}"
485
+ MEMORIA_LLM_API_KEY="${MEMORIA_LLM_API_KEY:-}"
486
+ MEMORIA_LLM_MODEL="${MEMORIA_LLM_MODEL:-}"
487
+
488
+ EMBEDDING_BASE_URL_RAW="${MEMORIA_EMBEDDING_BASE_URL}"
489
+ LLM_BASE_URL_RAW="${MEMORIA_LLM_BASE_URL}"
490
+
491
+ MEMORIA_EMBEDDING_BASE_URL="$(normalize_base_url "${MEMORIA_EMBEDDING_BASE_URL}")"
492
+ MEMORIA_LLM_BASE_URL="$(normalize_base_url "${MEMORIA_LLM_BASE_URL}")"
493
+
494
+ if [[ -n "${EMBEDDING_BASE_URL_RAW}" && "${EMBEDDING_BASE_URL_RAW}" != "${MEMORIA_EMBEDDING_BASE_URL}" ]]; then
495
+ log "Normalized embedding base URL to ${MEMORIA_EMBEDDING_BASE_URL}"
496
+ fi
497
+ if [[ -n "${LLM_BASE_URL_RAW}" && "${LLM_BASE_URL_RAW}" != "${MEMORIA_LLM_BASE_URL}" ]]; then
498
+ log "Normalized LLM base URL to ${MEMORIA_LLM_BASE_URL}"
499
+ fi
500
+
501
+ KNOWN_EMBEDDING_DIM="$(infer_embedding_dim "${MEMORIA_EMBEDDING_MODEL}")"
502
+ if [[ "${MEMORIA_EMBEDDING_PROVIDER}" != "local" && -z "${MEMORIA_EMBEDDING_API_KEY}" ]]; then
503
+ fail "MEMORIA_EMBEDDING_API_KEY is required unless provider=local"
504
+ fi
505
+ if [[ "${MEMORIA_EMBEDDING_PROVIDER}" != "local" && -z "${MEMORIA_EMBEDDING_DIM}" ]]; then
506
+ MEMORIA_EMBEDDING_DIM="${KNOWN_EMBEDDING_DIM}"
507
+ [[ -n "${MEMORIA_EMBEDDING_DIM}" ]] || fail "MEMORIA_EMBEDDING_DIM is required for model ${MEMORIA_EMBEDDING_MODEL}"
508
+ log "Auto-selected embedding dimension ${MEMORIA_EMBEDDING_DIM} for ${MEMORIA_EMBEDDING_MODEL}"
509
+ fi
510
+ if [[ "${MEMORIA_EMBEDDING_PROVIDER}" == "local" ]]; then
511
+ log "Embedding provider is local. Make sure your memoria binary was built with local-embedding support."
512
+ fi
513
+ if [[ "${MEMORIA_AUTO_OBSERVE}" == "true" ]]; then
514
+ [[ -n "${MEMORIA_LLM_API_KEY}" ]] || fail "MEMORIA_AUTO_OBSERVE=true requires MEMORIA_LLM_API_KEY"
515
+ [[ -n "${MEMORIA_LLM_MODEL}" ]] || fail "MEMORIA_AUTO_OBSERVE=true requires MEMORIA_LLM_MODEL"
516
+ fi
517
+
518
+ CONFIG_FILE="$(config_file_path)"
519
+
520
+ if [[ "${SKIP_PLUGIN_INSTALL}" == false ]]; then
521
+ log "Installing plugin into OpenClaw"
522
+ run_openclaw plugins install --link "${SOURCE_DIR}"
523
+ run_openclaw plugins enable "${PLUGIN_ID}"
524
+ else
525
+ log "Skipping OpenClaw plugin install/enable; assuming ${PLUGIN_ID} is already active"
526
+ fi
527
+
528
+ log "Writing plugin configuration"
529
+ MEMORIA_TOOL_NAMES_JSON="$(printf '%s\n' "${MEMORIA_TOOL_NAMES[@]}" | node -e 'const fs=require("node:fs"); const lines=fs.readFileSync(0,"utf8").trim().split(/\n+/).filter(Boolean); process.stdout.write(JSON.stringify(lines));')"
530
+
531
+ CONFIG_FILE="${CONFIG_FILE}" \
532
+ PLUGIN_ID="${PLUGIN_ID}" \
533
+ SOURCE_DIR="${SOURCE_DIR}" \
534
+ MEMORIA_EXECUTABLE_VALUE="${MEMORIA_EXECUTABLE_VALUE}" \
535
+ MEMORIA_DB_URL="${MEMORIA_DB_URL}" \
536
+ MEMORIA_DEFAULT_USER_ID="${MEMORIA_DEFAULT_USER_ID}" \
537
+ MEMORIA_USER_ID_STRATEGY="${MEMORIA_USER_ID_STRATEGY}" \
538
+ MEMORIA_AUTO_RECALL="${MEMORIA_AUTO_RECALL}" \
539
+ MEMORIA_AUTO_OBSERVE="${MEMORIA_AUTO_OBSERVE}" \
540
+ MEMORIA_EMBEDDING_PROVIDER="${MEMORIA_EMBEDDING_PROVIDER}" \
541
+ MEMORIA_EMBEDDING_MODEL="${MEMORIA_EMBEDDING_MODEL}" \
542
+ MEMORIA_EMBEDDING_BASE_URL="${MEMORIA_EMBEDDING_BASE_URL}" \
543
+ MEMORIA_EMBEDDING_API_KEY="${MEMORIA_EMBEDDING_API_KEY}" \
544
+ MEMORIA_EMBEDDING_DIM="${MEMORIA_EMBEDDING_DIM}" \
545
+ MEMORIA_LLM_BASE_URL="${MEMORIA_LLM_BASE_URL}" \
546
+ MEMORIA_LLM_API_KEY="${MEMORIA_LLM_API_KEY}" \
547
+ MEMORIA_LLM_MODEL="${MEMORIA_LLM_MODEL}" \
548
+ MEMORIA_TOOL_NAMES_JSON="${MEMORIA_TOOL_NAMES_JSON}" \
549
+ node - <<'NODE'
550
+ const fs = require("node:fs");
551
+ const path = require("node:path");
552
+
553
+ const configPath = path.resolve(process.env.CONFIG_FILE);
554
+ const pluginId = process.env.PLUGIN_ID;
555
+ const sourceDir = path.resolve(process.env.SOURCE_DIR);
556
+ const memoriaToolNames = JSON.parse(process.env.MEMORIA_TOOL_NAMES_JSON);
557
+
558
+ function readJson(filePath) {
559
+ if (!fs.existsSync(filePath)) {
560
+ return {};
561
+ }
562
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
563
+ }
564
+
565
+ function writeJson(filePath, value) {
566
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
567
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
568
+ }
569
+
570
+ function manifestPluginId(candidatePath) {
571
+ try {
572
+ const manifestPath = path.join(candidatePath, "openclaw.plugin.json");
573
+ if (!fs.existsSync(manifestPath)) {
574
+ return null;
575
+ }
576
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
577
+ return typeof manifest.id === "string" ? manifest.id : null;
578
+ } catch {
579
+ return null;
580
+ }
581
+ }
582
+
583
+ function mergeToolPolicy(policy) {
584
+ const result = policy && typeof policy === "object" && !Array.isArray(policy) ? { ...policy } : {};
585
+ const targetKey = Array.isArray(result.allow) ? "allow" : Array.isArray(result.alsoAllow) ? "alsoAllow" : "alsoAllow";
586
+ const current = Array.isArray(result[targetKey]) ? [...result[targetKey]] : [];
587
+ for (const toolName of memoriaToolNames) {
588
+ if (!current.includes(toolName)) {
589
+ current.push(toolName);
590
+ }
591
+ }
592
+ result[targetKey] = current;
593
+ return result;
594
+ }
595
+
596
+ const data = readJson(configPath);
597
+ const plugins = data.plugins && typeof data.plugins === "object" && !Array.isArray(data.plugins)
598
+ ? data.plugins
599
+ : (data.plugins = {});
600
+
601
+ const load = plugins.load && typeof plugins.load === "object" && !Array.isArray(plugins.load)
602
+ ? plugins.load
603
+ : (plugins.load = {});
604
+ const existingLoadPaths = Array.isArray(load.paths) ? load.paths : [];
605
+ const nextLoadPaths = [];
606
+ const seenLoadPaths = new Set();
607
+
608
+ for (const entry of existingLoadPaths) {
609
+ if (typeof entry !== "string" || !entry.trim()) {
610
+ continue;
611
+ }
612
+ const trimmed = entry.trim();
613
+ const resolved = path.resolve(trimmed.replace(/^~(?=$|\/|\\)/, process.env.HOME || "~"));
614
+ if ((trimmed.includes("openclaw-memoria") || trimmed.includes(pluginId)) && !fs.existsSync(resolved)) {
615
+ continue;
616
+ }
617
+ if (seenLoadPaths.has(resolved)) {
618
+ continue;
619
+ }
620
+ seenLoadPaths.add(resolved);
621
+ nextLoadPaths.push(trimmed);
622
+ }
623
+
624
+ if (!seenLoadPaths.has(sourceDir)) {
625
+ nextLoadPaths.push(sourceDir);
626
+ }
627
+
628
+ load.paths = nextLoadPaths;
629
+
630
+ plugins.allow = Array.isArray(plugins.allow) ? plugins.allow : [];
631
+ if (!plugins.allow.includes(pluginId)) {
632
+ plugins.allow.push(pluginId);
633
+ }
634
+
635
+ plugins.entries = plugins.entries && typeof plugins.entries === "object" && !Array.isArray(plugins.entries)
636
+ ? plugins.entries
637
+ : {};
638
+ delete plugins.entries[JSON.stringify(pluginId)];
639
+
640
+ const pluginEntry = plugins.entries[pluginId] && typeof plugins.entries[pluginId] === "object" && !Array.isArray(plugins.entries[pluginId])
641
+ ? plugins.entries[pluginId]
642
+ : (plugins.entries[pluginId] = {});
643
+ pluginEntry.enabled = true;
644
+ pluginEntry.config = {
645
+ backend: "embedded",
646
+ memoriaExecutable: process.env.MEMORIA_EXECUTABLE_VALUE,
647
+ dbUrl: process.env.MEMORIA_DB_URL,
648
+ defaultUserId: process.env.MEMORIA_DEFAULT_USER_ID,
649
+ userIdStrategy: process.env.MEMORIA_USER_ID_STRATEGY,
650
+ autoRecall: process.env.MEMORIA_AUTO_RECALL === "true",
651
+ autoObserve: process.env.MEMORIA_AUTO_OBSERVE === "true",
652
+ embeddingProvider: process.env.MEMORIA_EMBEDDING_PROVIDER,
653
+ embeddingModel: process.env.MEMORIA_EMBEDDING_MODEL
654
+ };
655
+
656
+ const optionalFields = {
657
+ embeddingBaseUrl: process.env.MEMORIA_EMBEDDING_BASE_URL,
658
+ embeddingApiKey: process.env.MEMORIA_EMBEDDING_API_KEY,
659
+ llmBaseUrl: process.env.MEMORIA_LLM_BASE_URL,
660
+ llmApiKey: process.env.MEMORIA_LLM_API_KEY,
661
+ llmModel: process.env.MEMORIA_LLM_MODEL
662
+ };
663
+ for (const [key, value] of Object.entries(optionalFields)) {
664
+ if (value) {
665
+ pluginEntry.config[key] = value;
666
+ }
667
+ }
668
+ if (process.env.MEMORIA_EMBEDDING_DIM) {
669
+ pluginEntry.config.embeddingDim = Number.parseInt(process.env.MEMORIA_EMBEDDING_DIM, 10);
670
+ }
671
+
672
+ pluginEntry.hooks = pluginEntry.hooks && typeof pluginEntry.hooks === "object" && !Array.isArray(pluginEntry.hooks)
673
+ ? pluginEntry.hooks
674
+ : {};
675
+ pluginEntry.hooks.allowPromptInjection = true;
676
+
677
+ plugins.slots = plugins.slots && typeof plugins.slots === "object" && !Array.isArray(plugins.slots)
678
+ ? plugins.slots
679
+ : {};
680
+ plugins.slots.memory = pluginId;
681
+
682
+ data.tools = data.tools && typeof data.tools === "object" && !Array.isArray(data.tools)
683
+ ? data.tools
684
+ : {};
685
+ Object.assign(data.tools, mergeToolPolicy(data.tools));
686
+
687
+ if (data.agents && typeof data.agents === "object" && Array.isArray(data.agents.list)) {
688
+ for (const agent of data.agents.list) {
689
+ if (!agent || typeof agent !== "object" || Array.isArray(agent)) {
690
+ continue;
691
+ }
692
+ agent.tools = mergeToolPolicy(agent.tools);
693
+ }
694
+ }
695
+
696
+ writeJson(configPath, data);
697
+ NODE
698
+
699
+ log "Validating OpenClaw config"
700
+ run_openclaw config validate >/dev/null
701
+
702
+ install_bundled_skills "${SOURCE_DIR}/skills" "$(skills_dir_path)"
703
+
704
+ if [[ "${RUN_VERIFY}" == true ]]; then
705
+ log "Running install verification"
706
+ node "${SOURCE_DIR}/scripts/verify_plugin_install.mjs" \
707
+ --openclaw-bin "${OPENCLAW_BIN}" \
708
+ --config-file "${CONFIG_FILE}" \
709
+ --memoria-bin "${MEMORIA_EXECUTABLE_VALUE}"
710
+ fi
711
+
712
+ cat <<EOF
713
+
714
+ Install complete.
715
+
716
+ Plugin source: ${SOURCE_DIR}
717
+ Memoria executable: ${MEMORIA_EXECUTABLE_VALUE}
718
+ OpenClaw config: ${CONFIG_FILE}
719
+
720
+ Recommended smoke checks:
721
+ openclaw memoria capabilities
722
+ openclaw memoria stats
723
+ openclaw ltm list --limit 10
724
+
725
+ If embedded mode is enabled, make sure MatrixOne is reachable at:
726
+ ${MEMORIA_DB_URL}
727
+ EOF