openclaw-openviking-setup-helper 0.2.8 → 0.2.9-dev.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.
Files changed (2) hide show
  1. package/install.js +441 -113
  2. package/package.json +2 -2
package/install.js CHANGED
@@ -11,10 +11,10 @@
11
11
  *
12
12
  * Direct run:
13
13
  * node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ]
14
- * [ --openviking-version=V ] [ --repo=PATH ]
14
+ * [ --plugin-version=TAG ] [ --openviking-version=V ] [ --repo=PATH ]
15
15
  *
16
16
  * Environment variables:
17
- * REPO, BRANCH, OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
17
+ * REPO, PLUGIN_VERSION (or BRANCH), OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
18
18
  * OPENVIKING_VERSION Pip install openviking==VERSION (omit for latest)
19
19
  * OPENVIKING_REPO Repo path: source install (pip -e) + local plugin (default: off)
20
20
  * NPM_REGISTRY, PIP_INDEX_URL
@@ -23,26 +23,26 @@
23
23
  */
24
24
 
25
25
  import { spawn } from "node:child_process";
26
- import { mkdir, readFile, writeFile } from "node:fs/promises";
27
- import { existsSync, readdirSync } from "node:fs";
28
- import { dirname, join } from "node:path";
26
+ import { cp, mkdir, rm, writeFile } from "node:fs/promises";
27
+ import { existsSync, readdirSync } from "node:fs";
28
+ import { dirname, join, relative } from "node:path";
29
29
  import { createInterface } from "node:readline";
30
30
  import { fileURLToPath } from "node:url";
31
31
 
32
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
33
33
 
34
- const REPO = process.env.REPO || "volcengine/OpenViking";
35
- const BRANCH = process.env.BRANCH || "main";
36
- const GH_RAW = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
34
+ let REPO = process.env.REPO || "volcengine/OpenViking";
35
+ // PLUGIN_VERSION takes precedence over BRANCH (legacy)
36
+ let PLUGIN_VERSION = process.env.PLUGIN_VERSION || process.env.BRANCH || "main";
37
37
  const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
38
- const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
38
+ const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://mirrors.volces.com/pypi/simple/";
39
39
 
40
40
  const IS_WIN = process.platform === "win32";
41
41
  const HOME = process.env.HOME || process.env.USERPROFILE || "";
42
42
 
43
43
  const DEFAULT_OPENCLAW_DIR = join(HOME, ".openclaw");
44
44
  let OPENCLAW_DIR = DEFAULT_OPENCLAW_DIR;
45
- let PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
45
+ let PLUGIN_DEST = ""; // Will be set after resolving plugin config
46
46
 
47
47
  const OPENVIKING_DIR = join(HOME, ".openviking");
48
48
 
@@ -51,21 +51,35 @@ const DEFAULT_AGFS_PORT = 1833;
51
51
  const DEFAULT_VLM_MODEL = "doubao-seed-2-0-pro-260215";
52
52
  const DEFAULT_EMBED_MODEL = "doubao-embedding-vision-251215";
53
53
 
54
- const REQUIRED_PLUGIN_FILES = [
55
- "examples/openclaw-memory-plugin/index.ts",
56
- "examples/openclaw-memory-plugin/config.ts",
57
- "examples/openclaw-memory-plugin/openclaw.plugin.json",
58
- "examples/openclaw-memory-plugin/package.json",
59
- "examples/openclaw-memory-plugin/package-lock.json",
60
- "examples/openclaw-memory-plugin/.gitignore",
61
- ];
62
-
63
- const OPTIONAL_PLUGIN_FILES = [
64
- "examples/openclaw-memory-plugin/client.ts",
65
- "examples/openclaw-memory-plugin/process-manager.ts",
66
- "examples/openclaw-memory-plugin/memory-ranking.ts",
67
- "examples/openclaw-memory-plugin/text-utils.ts",
68
- ];
54
+ // Fallback configs for old versions without manifest
55
+ const FALLBACK_LEGACY = {
56
+ dir: "openclaw-memory-plugin",
57
+ id: "memory-openviking",
58
+ kind: "memory",
59
+ slot: "memory",
60
+ required: ["index.ts", "config.ts", "openclaw.plugin.json", "package.json"],
61
+ optional: ["package-lock.json", ".gitignore"],
62
+ };
63
+
64
+ const FALLBACK_CURRENT = {
65
+ dir: "openclaw-plugin",
66
+ id: "openviking",
67
+ kind: "context-engine",
68
+ slot: "contextEngine",
69
+ required: ["index.ts", "context-engine.ts", "config.ts", "client.ts", "process-manager.ts", "memory-ranking.ts", "text-utils.ts", "session-transcript-repair.ts", "tool-call-id.ts", "openclaw.plugin.json", "package.json", "tsconfig.json"],
70
+ optional: ["package-lock.json", ".gitignore"],
71
+ };
72
+
73
+ // Resolved plugin config (set by resolvePluginConfig)
74
+ let resolvedPluginDir = "";
75
+ let resolvedPluginId = "";
76
+ let resolvedPluginKind = "";
77
+ let resolvedPluginSlot = "";
78
+ let resolvedFilesRequired = [];
79
+ let resolvedFilesOptional = [];
80
+ let resolvedNpmOmitDev = true;
81
+ let resolvedMinOpenclawVersion = "";
82
+ let resolvedMinOpenvikingVersion = "";
69
83
 
70
84
  let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
71
85
  let langZh = false;
@@ -102,38 +116,86 @@ for (let i = 0; i < argv.length; i++) {
102
116
  i += 1;
103
117
  continue;
104
118
  }
119
+ if (arg.startsWith("--plugin-version=")) {
120
+ PLUGIN_VERSION = arg.slice("--plugin-version=".length).trim();
121
+ continue;
122
+ }
123
+ if (arg === "--plugin-version") {
124
+ const version = argv[i + 1]?.trim();
125
+ if (!version) {
126
+ console.error("--plugin-version requires a value");
127
+ process.exit(1);
128
+ }
129
+ PLUGIN_VERSION = version;
130
+ i += 1;
131
+ continue;
132
+ }
105
133
  if (arg.startsWith("--openviking-version=")) {
106
134
  openvikingVersion = arg.slice("--openviking-version=".length).trim();
107
135
  continue;
108
136
  }
137
+ if (arg === "--openviking-version") {
138
+ const version = argv[i + 1]?.trim();
139
+ if (!version) {
140
+ console.error("--openviking-version requires a value");
141
+ process.exit(1);
142
+ }
143
+ openvikingVersion = version;
144
+ i += 1;
145
+ continue;
146
+ }
109
147
  if (arg.startsWith("--repo=")) {
110
148
  openvikingRepo = arg.slice("--repo=".length).trim();
111
149
  continue;
112
150
  }
151
+ if (arg.startsWith("--github-repo=")) {
152
+ REPO = arg.slice("--github-repo=".length).trim();
153
+ continue;
154
+ }
155
+ if (arg === "--github-repo") {
156
+ const repo = argv[i + 1]?.trim();
157
+ if (!repo) {
158
+ console.error("--github-repo requires a value (e.g. owner/repo)");
159
+ process.exit(1);
160
+ }
161
+ REPO = repo;
162
+ i += 1;
163
+ continue;
164
+ }
113
165
  if (arg === "-h" || arg === "--help") {
114
166
  printHelp();
115
167
  process.exit(0);
116
168
  }
117
169
  }
118
170
 
119
- const OPENVIKING_PIP_SPEC = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
120
-
121
171
  function setOpenClawDir(dir) {
122
172
  OPENCLAW_DIR = dir;
123
- PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
124
173
  }
125
174
 
126
175
  function printHelp() {
127
- console.log("Usage: node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ] [ --openviking-version=V ] [ --repo=PATH ]");
176
+ console.log("Usage: node install.js [ OPTIONS ]");
177
+ console.log("");
178
+ console.log("Options:");
179
+ console.log(" --github-repo=OWNER/REPO GitHub repository (default: volcengine/OpenViking)");
180
+ console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: main)");
181
+ console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)");
182
+ console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
183
+ console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)");
184
+ console.log(" -y, --yes Non-interactive (use defaults)");
185
+ console.log(" --zh Chinese prompts");
186
+ console.log(" -h, --help This help");
187
+ console.log("");
188
+ console.log("Examples:");
189
+ console.log(" # Install latest version");
190
+ console.log(" node install.js");
191
+ console.log("");
192
+ console.log(" # Install from a fork repository");
193
+ console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
128
194
  console.log("");
129
- console.log(" -y, --yes Non-interactive (use defaults)");
130
- console.log(" --zh Chinese prompts");
131
- console.log(" --workdir OpenClaw config directory (default: ~/.openclaw)");
132
- console.log(" --openviking-version=VERSION Pip install openviking==VERSION (default: latest)");
133
- console.log(" --repo=PATH Use OpenViking repo at PATH: pip install -e PATH, plugin from repo (default: off)");
134
- console.log(" -h, --help This help");
195
+ console.log(" # Install specific plugin version");
196
+ console.log(" node install.js --plugin-version=v0.2.8");
135
197
  console.log("");
136
- console.log("Env: OPENVIKING_REPO, REPO, BRANCH, SKIP_OPENCLAW, SKIP_OPENVIKING, OPENVIKING_VERSION, NPM_REGISTRY, PIP_INDEX_URL");
198
+ console.log("Env: REPO, PLUGIN_VERSION, OPENVIKING_VERSION, SKIP_OPENCLAW, SKIP_OPENVIKING, NPM_REGISTRY, PIP_INDEX_URL");
137
199
  }
138
200
 
139
201
  function tr(en, zh) {
@@ -389,7 +451,191 @@ async function checkOpenClaw() {
389
451
  process.exit(1);
390
452
  }
391
453
 
392
- async function installOpenViking() {
454
+ // Compare versions: returns true if v1 >= v2
455
+ function versionGte(v1, v2) {
456
+ const parseVersion = (v) => {
457
+ const cleaned = v.replace(/^v/, "").replace(/-.*$/, "");
458
+ const parts = cleaned.split(".").map((p) => Number.parseInt(p, 10) || 0);
459
+ while (parts.length < 3) parts.push(0);
460
+ return parts;
461
+ };
462
+ const [a1, a2, a3] = parseVersion(v1);
463
+ const [b1, b2, b3] = parseVersion(v2);
464
+ if (a1 !== b1) return a1 > b1;
465
+ if (a2 !== b2) return a2 > b2;
466
+ return a3 >= b3;
467
+ }
468
+
469
+ function isSemverLike(value) {
470
+ return /^v?\d+(\.\d+){1,2}$/.test(value);
471
+ }
472
+
473
+ // Detect OpenClaw version
474
+ async function detectOpenClawVersion() {
475
+ try {
476
+ const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
477
+ if (result.code === 0 && result.out) {
478
+ const match = result.out.match(/\d+\.\d+(\.\d+)?/);
479
+ if (match) return match[0];
480
+ }
481
+ } catch {}
482
+ return "0.0.0";
483
+ }
484
+
485
+ // Try to fetch a URL, return response text or null
486
+ async function tryFetch(url, timeout = 15000) {
487
+ try {
488
+ const controller = new AbortController();
489
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
490
+ const response = await fetch(url, { signal: controller.signal });
491
+ clearTimeout(timeoutId);
492
+ if (response.ok) {
493
+ return await response.text();
494
+ }
495
+ } catch {}
496
+ return null;
497
+ }
498
+
499
+ // Check if a remote file exists
500
+ async function testRemoteFile(url) {
501
+ try {
502
+ const controller = new AbortController();
503
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
504
+ const response = await fetch(url, { method: "HEAD", signal: controller.signal });
505
+ clearTimeout(timeoutId);
506
+ return response.ok;
507
+ } catch {}
508
+ return false;
509
+ }
510
+
511
+ // Resolve plugin configuration from manifest or fallback
512
+ async function resolvePluginConfig() {
513
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
514
+
515
+ info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
516
+
517
+ let pluginDir = "";
518
+ let manifestData = null;
519
+
520
+ // Try to detect plugin directory and download manifest
521
+ const manifestCurrent = await tryFetch(`${ghRaw}/examples/openclaw-plugin/install-manifest.json`);
522
+ if (manifestCurrent) {
523
+ pluginDir = "openclaw-plugin";
524
+ try {
525
+ manifestData = JSON.parse(manifestCurrent);
526
+ } catch {}
527
+ info(tr("Found manifest in openclaw-plugin", "在 openclaw-plugin 中找到 manifest"));
528
+ } else {
529
+ const manifestLegacy = await tryFetch(`${ghRaw}/examples/openclaw-memory-plugin/install-manifest.json`);
530
+ if (manifestLegacy) {
531
+ pluginDir = "openclaw-memory-plugin";
532
+ try {
533
+ manifestData = JSON.parse(manifestLegacy);
534
+ } catch {}
535
+ info(tr("Found manifest in openclaw-memory-plugin", "在 openclaw-memory-plugin 中找到 manifest"));
536
+ } else if (await testRemoteFile(`${ghRaw}/examples/openclaw-plugin/index.ts`)) {
537
+ pluginDir = "openclaw-plugin";
538
+ info(tr("No manifest found, using fallback for openclaw-plugin", "未找到 manifest,使用 openclaw-plugin 回退配置"));
539
+ } else if (await testRemoteFile(`${ghRaw}/examples/openclaw-memory-plugin/index.ts`)) {
540
+ pluginDir = "openclaw-memory-plugin";
541
+ info(tr("No manifest found, using fallback for openclaw-memory-plugin", "未找到 manifest,使用 openclaw-memory-plugin 回退配置"));
542
+ } else {
543
+ err(tr(`Cannot find plugin directory for version: ${PLUGIN_VERSION}`, `无法找到版本 ${PLUGIN_VERSION} 的插件目录`));
544
+ process.exit(1);
545
+ }
546
+ }
547
+
548
+ resolvedPluginDir = pluginDir;
549
+
550
+ if (manifestData) {
551
+ resolvedPluginId = manifestData.plugin?.id || "";
552
+ resolvedPluginKind = manifestData.plugin?.kind || "";
553
+ resolvedPluginSlot = manifestData.plugin?.slot || "";
554
+ resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
555
+ resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
556
+ resolvedNpmOmitDev = manifestData.npm?.omitDev !== false;
557
+ resolvedFilesRequired = manifestData.files?.required || [];
558
+ resolvedFilesOptional = manifestData.files?.optional || [];
559
+ } else {
560
+ // Use fallback config
561
+ const fallback = pluginDir === "openclaw-memory-plugin" ? FALLBACK_LEGACY : FALLBACK_CURRENT;
562
+ resolvedPluginDir = fallback.dir;
563
+ resolvedPluginId = fallback.id;
564
+ resolvedPluginKind = fallback.kind;
565
+ resolvedPluginSlot = fallback.slot;
566
+ resolvedFilesRequired = fallback.required;
567
+ resolvedFilesOptional = fallback.optional;
568
+ resolvedNpmOmitDev = true;
569
+
570
+ if (pluginDir === "openclaw-plugin") {
571
+ resolvedMinOpenclawVersion = "3.7";
572
+ } else {
573
+ resolvedMinOpenclawVersion = "";
574
+ }
575
+ resolvedMinOpenvikingVersion = "";
576
+ }
577
+
578
+ // Set plugin destination
579
+ PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId);
580
+
581
+ info(tr(`Plugin: ${resolvedPluginId} (${resolvedPluginKind})`, `插件: ${resolvedPluginId} (${resolvedPluginKind})`));
582
+ }
583
+
584
+ // Check OpenClaw version compatibility
585
+ async function checkOpenClawCompatibility() {
586
+ if (process.env.SKIP_OPENCLAW === "1") {
587
+ return;
588
+ }
589
+
590
+ const ocVersion = await detectOpenClawVersion();
591
+ info(tr(`Detected OpenClaw version: ${ocVersion}`, `检测到 OpenClaw 版本: ${ocVersion}`));
592
+
593
+ // If no minimum version required, pass
594
+ if (!resolvedMinOpenclawVersion) {
595
+ return;
596
+ }
597
+
598
+ // If user explicitly requested an old version, pass
599
+ if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.9")) {
600
+ return;
601
+ }
602
+
603
+ // Check compatibility
604
+ if (!versionGte(ocVersion, resolvedMinOpenclawVersion)) {
605
+ err(tr(
606
+ `OpenClaw ${ocVersion} does not support this plugin (requires >= ${resolvedMinOpenclawVersion})`,
607
+ `OpenClaw ${ocVersion} 不支持此插件(需要 >= ${resolvedMinOpenclawVersion})`
608
+ ));
609
+ console.log("");
610
+ bold(tr("Please choose one of the following options:", "请选择以下方案之一:"));
611
+ console.log("");
612
+ console.log(` ${tr("Option 1: Upgrade OpenClaw", "方案 1:升级 OpenClaw")}`);
613
+ console.log(` npm update -g openclaw --registry ${NPM_REGISTRY}`);
614
+ console.log("");
615
+ console.log(` ${tr("Option 2: Install legacy plugin (v0.2.8)", "方案 2:安装旧版插件 (v0.2.8)")}`);
616
+ console.log(` node install.js --plugin-version=v0.2.8${langZh ? " --zh" : ""}`);
617
+ console.log("");
618
+ process.exit(1);
619
+ }
620
+ }
621
+
622
+ function checkRequestedOpenVikingCompatibility() {
623
+ if (!resolvedMinOpenvikingVersion || !openvikingVersion) return;
624
+ if (versionGte(openvikingVersion, resolvedMinOpenvikingVersion)) return;
625
+
626
+ err(tr(
627
+ `OpenViking ${openvikingVersion} does not support this plugin (requires >= ${resolvedMinOpenvikingVersion})`,
628
+ `OpenViking ${openvikingVersion} 不支持此插件(需要 >= ${resolvedMinOpenvikingVersion})`,
629
+ ));
630
+ console.log("");
631
+ console.log(tr(
632
+ "Use a newer OpenViking version, or omit --openviking-version to install the latest release.",
633
+ "请使用更新版本的 OpenViking,或省略 --openviking-version 以安装最新版本。",
634
+ ));
635
+ process.exit(1);
636
+ }
637
+
638
+ async function installOpenViking() {
393
639
  if (process.env.SKIP_OPENVIKING === "1") {
394
640
  info(tr("Skipping OpenViking install (SKIP_OPENVIKING=1)", "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)"));
395
641
  return;
@@ -412,14 +658,20 @@ async function installOpenViking() {
412
658
  return;
413
659
  }
414
660
 
415
- info(tr("Installing OpenViking from PyPI...", "正在安装 OpenViking (PyPI)..."));
661
+ // Determine package spec
662
+ const pkgSpec = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
663
+ if (openvikingVersion) {
664
+ info(tr(`Installing OpenViking ${openvikingVersion} from PyPI...`, `正在安装 OpenViking ${openvikingVersion} (PyPI)...`));
665
+ } else {
666
+ info(tr("Installing OpenViking (latest) from PyPI...", "正在安装 OpenViking (最新版) (PyPI)..."));
667
+ }
416
668
  info(tr(`Using pip index: ${PIP_INDEX_URL}`, `使用 pip 镜像源: ${PIP_INDEX_URL}`));
417
669
 
418
- info(`Package: ${OPENVIKING_PIP_SPEC}`);
670
+ info(`Package: ${pkgSpec}`);
419
671
  await runCapture(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
420
672
  const installResult = await runLiveCapture(
421
673
  py,
422
- ["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
674
+ ["-m", "pip", "install", "--progress-bar", "on", pkgSpec, "-i", PIP_INDEX_URL],
423
675
  { shell: false },
424
676
  );
425
677
  if (installResult.code === 0) {
@@ -439,7 +691,7 @@ async function installOpenViking() {
439
691
  if (reuseCheck.code === 0) {
440
692
  await runLiveCapture(
441
693
  venvPy,
442
- ["-m", "pip", "install", "--progress-bar", "on", "-U", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
694
+ ["-m", "pip", "install", "--progress-bar", "on", "-U", pkgSpec, "-i", PIP_INDEX_URL],
443
695
  { shell: false },
444
696
  );
445
697
  openvikingPythonPath = venvPy;
@@ -475,7 +727,7 @@ async function installOpenViking() {
475
727
  await runCapture(venvPy, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
476
728
  const venvInstall = await runLiveCapture(
477
729
  venvPy,
478
- ["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
730
+ ["-m", "pip", "install", "--progress-bar", "on", pkgSpec, "-i", PIP_INDEX_URL],
479
731
  { shell: false },
480
732
  );
481
733
  if (venvInstall.code === 0) {
@@ -492,7 +744,7 @@ async function installOpenViking() {
492
744
  if (process.env.OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES === "1") {
493
745
  const systemInstall = await runLiveCapture(
494
746
  py,
495
- ["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
747
+ ["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages", pkgSpec, "-i", PIP_INDEX_URL],
496
748
  { shell: false },
497
749
  );
498
750
  if (systemInstall.code === 0) {
@@ -549,20 +801,28 @@ async function configureOvConf() {
549
801
  vectordb: { name: "context", backend: "local", project: "default" },
550
802
  agfs: { port: agfsPortNum, log_level: "warn", backend: "local", timeout: 10, retry_times: 3 },
551
803
  },
552
- embedding: {
553
- dense: {
554
- backend: "volcengine",
555
- api_key: embeddingApiKey || null,
556
- model: embeddingModel,
557
- api_base: "https://ark.cn-beijing.volces.com/api/v3",
804
+ log: {
805
+ level: "WARNING",
806
+ format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
807
+ output: "file",
808
+ rotation: true,
809
+ rotation_days: 3,
810
+ rotation_interval: "midnight",
811
+ },
812
+ embedding: {
813
+ dense: {
814
+ provider: "volcengine",
815
+ api_key: embeddingApiKey || null,
816
+ model: embeddingModel,
817
+ api_base: "https://ark.cn-beijing.volces.com/api/v3",
558
818
  dimension: 1024,
559
819
  input: "multimodal",
560
820
  },
561
- },
562
- vlm: {
563
- backend: "volcengine",
564
- api_key: vlmApiKey || null,
565
- model: vlmModel,
821
+ },
822
+ vlm: {
823
+ provider: "volcengine",
824
+ api_key: vlmApiKey || null,
825
+ model: vlmModel,
566
826
  api_base: "https://ark.cn-beijing.volces.com/api/v3",
567
827
  temperature: 0.1,
568
828
  max_retries: 3,
@@ -574,9 +834,7 @@ async function configureOvConf() {
574
834
  info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
575
835
  }
576
836
 
577
- async function downloadPluginFile(relPath, required, index, total) {
578
- const fileName = relPath.split("/").pop();
579
- const url = `${GH_RAW}/${relPath}`;
837
+ async function downloadPluginFile(fileName, url, required, index, total) {
580
838
  const maxRetries = 3;
581
839
 
582
840
  process.stdout.write(` [${index}/${total}] ${fileName} `);
@@ -591,7 +849,7 @@ async function downloadPluginFile(relPath, required, index, total) {
591
849
  return;
592
850
  }
593
851
  if (!required && response.status === 404) {
594
- console.log(tr("(not present in target branch, skipped)", "(目标分支不存在,已跳过)"));
852
+ console.log(tr("(skipped)", "(跳过)"));
595
853
  return;
596
854
  }
597
855
  } catch {}
@@ -607,77 +865,110 @@ async function downloadPluginFile(relPath, required, index, total) {
607
865
  return;
608
866
  }
609
867
 
868
+ if (!required) {
869
+ console.log(tr("(skipped)", "(跳过)"));
870
+ return;
871
+ }
872
+
610
873
  console.log("");
611
874
  err(tr(`Download failed: ${url}`, `下载失败: ${url}`));
612
875
  process.exit(1);
613
876
  }
614
877
 
615
878
  async function downloadPlugin() {
879
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
880
+ const pluginDir = resolvedPluginDir;
881
+ const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
882
+
616
883
  await mkdir(PLUGIN_DEST, { recursive: true });
617
- const files = [
618
- ...REQUIRED_PLUGIN_FILES.map((relPath) => ({ relPath, required: true })),
619
- ...OPTIONAL_PLUGIN_FILES.map((relPath) => ({ relPath, required: false })),
620
- ];
621
884
 
622
- info(tr(`Downloading memory-openviking plugin from ${REPO}@${BRANCH}...`, `正在从 ${REPO}@${BRANCH} 下载 memory-openviking 插件...`));
623
- for (let i = 0; i < files.length; i++) {
624
- const file = files[i];
625
- await downloadPluginFile(file.relPath, file.required, i + 1, files.length);
885
+ info(tr(`Downloading plugin from ${REPO}@${PLUGIN_VERSION} (${total} files)...`, `正在从 ${REPO}@${PLUGIN_VERSION} 下载插件(共 ${total} 个文件)...`));
886
+
887
+ let i = 0;
888
+ // Download required files
889
+ for (const name of resolvedFilesRequired) {
890
+ if (!name) continue;
891
+ i++;
892
+ const url = `${ghRaw}/examples/${pluginDir}/${name}`;
893
+ await downloadPluginFile(name, url, true, i, total);
894
+ }
895
+
896
+ // Download optional files
897
+ for (const name of resolvedFilesOptional) {
898
+ if (!name) continue;
899
+ i++;
900
+ const url = `${ghRaw}/examples/${pluginDir}/${name}`;
901
+ await downloadPluginFile(name, url, false, i, total);
626
902
  }
627
903
 
904
+ // npm install
628
905
  info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
629
- await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: PLUGIN_DEST, silent: false });
906
+ const npmArgs = resolvedNpmOmitDev
907
+ ? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
908
+ : ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
909
+ await run("npm", npmArgs, { cwd: PLUGIN_DEST, silent: false });
630
910
  info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
631
911
  }
632
912
 
633
- async function configureOpenClawPlugin(pluginPath = PLUGIN_DEST) {
913
+ async function deployLocalPlugin(localPluginDir) {
914
+ await rm(PLUGIN_DEST, { recursive: true, force: true });
915
+ await mkdir(PLUGIN_DEST, { recursive: true });
916
+ await cp(localPluginDir, PLUGIN_DEST, {
917
+ recursive: true,
918
+ force: true,
919
+ filter: (sourcePath) => {
920
+ const rel = relative(localPluginDir, sourcePath);
921
+ if (!rel) return true;
922
+ const firstSegment = rel.split(/[\\/]/)[0];
923
+ return firstSegment !== "node_modules" && firstSegment !== ".git";
924
+ },
925
+ });
926
+ }
927
+
928
+ async function configureOpenClawPlugin() {
634
929
  info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
635
930
 
636
- const configPath = join(OPENCLAW_DIR, "openclaw.json");
637
- let config = {};
931
+ const pluginId = resolvedPluginId;
932
+ const pluginSlot = resolvedPluginSlot;
638
933
 
639
- if (existsSync(configPath)) {
640
- try {
641
- const raw = await readFile(configPath, "utf8");
642
- if (raw.trim()) config = JSON.parse(raw);
643
- } catch {
644
- warn(tr("Existing openclaw.json invalid. Rebuilding required sections.", "已有 openclaw.json 非法,将重建相关配置节点。"));
645
- }
934
+ const ocEnv = { ...process.env };
935
+ if (OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
936
+ ocEnv.OPENCLAW_STATE_DIR = OPENCLAW_DIR;
646
937
  }
647
938
 
648
- if (!config.plugins) config.plugins = {};
649
- if (!config.gateway) config.gateway = {};
650
- if (!config.plugins.slots) config.plugins.slots = {};
651
- if (!config.plugins.load) config.plugins.load = {};
652
- if (!config.plugins.entries) config.plugins.entries = {};
653
-
654
- const existingPaths = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
655
- config.plugins.enabled = true;
656
- config.plugins.allow = ["memory-openviking"];
657
- config.plugins.slots.memory = "memory-openviking";
658
- config.plugins.load.paths = [...new Set([...existingPaths, pluginPath])];
659
-
660
- const pluginConfig = {
661
- mode: selectedMode,
662
- targetUri: "viking://user/memories",
663
- autoRecall: true,
664
- autoCapture: true,
665
- };
666
-
939
+ const oc = async (args) => {
940
+ const result = await runCapture("openclaw", args, { env: ocEnv, shell: IS_WIN });
941
+ if (result.code !== 0) {
942
+ const detail = result.err || result.out;
943
+ throw new Error(`openclaw ${args.join(" ")} failed (exit code ${result.code})${detail ? `: ${detail}` : ""}`);
944
+ }
945
+ return result;
946
+ };
947
+
948
+ // Enable plugin (files already deployed to extensions dir by deployPlugin)
949
+ await oc(["plugins", "enable", pluginId]);
950
+ await oc(["config", "set", `plugins.slots.${pluginSlot}`, pluginId]);
951
+
952
+ // Set gateway mode
953
+ await oc(["config", "set", "gateway.mode", "local"]);
954
+
955
+ // Set plugin config for the selected mode
667
956
  if (selectedMode === "local") {
668
- pluginConfig.configPath = join(OPENVIKING_DIR, "ov.conf");
669
- pluginConfig.port = selectedServerPort;
957
+ const ovConfPath = join(OPENVIKING_DIR, "ov.conf");
958
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "local"]);
959
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.configPath`, ovConfPath]);
960
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.port`, String(selectedServerPort)]);
670
961
  } else {
671
- pluginConfig.baseUrl = remoteBaseUrl;
672
- if (remoteApiKey) pluginConfig.apiKey = remoteApiKey;
673
- if (remoteAgentId) pluginConfig.agentId = remoteAgentId;
962
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "remote"]);
963
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.baseUrl`, remoteBaseUrl]);
964
+ if (remoteApiKey) {
965
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.apiKey`, remoteApiKey]);
966
+ }
967
+ if (remoteAgentId) {
968
+ await oc(["config", "set", `plugins.entries.${pluginId}.config.agentId`, remoteAgentId]);
969
+ }
674
970
  }
675
971
 
676
- config.plugins.entries["memory-openviking"] = { config: pluginConfig };
677
- config.gateway.mode = "local";
678
-
679
- await mkdir(OPENCLAW_DIR, { recursive: true });
680
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
681
972
  info(tr("OpenClaw plugin configured", "OpenClaw 插件配置完成"));
682
973
  }
683
974
 
@@ -698,7 +989,21 @@ async function resolvePythonPath() {
698
989
 
699
990
  async function writeOpenvikingEnv({ includePython }) {
700
991
  const needStateDir = OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR;
701
- const pythonPath = includePython ? await resolvePythonPath() : "";
992
+ let pythonPath = "";
993
+ if (includePython) {
994
+ pythonPath = await resolvePythonPath();
995
+ if (!pythonPath) {
996
+ pythonPath = (process.env.OPENVIKING_PYTHON || "").trim() || (IS_WIN ? "python" : "python3");
997
+ warn(
998
+ tr(
999
+ "Could not resolve absolute Python path; wrote fallback OPENVIKING_PYTHON to openviking.env. Edit that file if OpenViking fails to start.",
1000
+ "未能解析 Python 绝对路径,已在 openviking.env 中写入后备值。若启动失败请手动修改为虚拟环境中的 python 可执行文件路径。",
1001
+ ),
1002
+ );
1003
+ }
1004
+ }
1005
+
1006
+ // Remote mode + default state dir + no python line → nothing to persist
702
1007
  if (!needStateDir && !pythonPath) return null;
703
1008
 
704
1009
  await mkdir(OPENCLAW_DIR, { recursive: true });
@@ -752,6 +1057,11 @@ async function main() {
752
1057
 
753
1058
  await selectWorkdir();
754
1059
  info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
1060
+ info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
1061
+ info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
1062
+ if (openvikingVersion) {
1063
+ info(tr(`OpenViking version: ${openvikingVersion}`, `OpenViking 版本: ${openvikingVersion}`));
1064
+ }
755
1065
 
756
1066
  await selectMode();
757
1067
  info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
@@ -759,28 +1069,38 @@ async function main() {
759
1069
  if (selectedMode === "local") {
760
1070
  await validateEnvironment();
761
1071
  await checkOpenClaw();
762
- await installOpenViking();
1072
+ // Resolve plugin config after OpenClaw is available (for version detection)
1073
+ await resolvePluginConfig();
1074
+ await checkOpenClawCompatibility();
1075
+ checkRequestedOpenVikingCompatibility();
1076
+ await installOpenViking();
763
1077
  await configureOvConf();
764
1078
  } else {
765
1079
  await checkOpenClaw();
1080
+ await resolvePluginConfig();
1081
+ await checkOpenClawCompatibility();
766
1082
  await collectRemoteConfig();
767
1083
  }
768
1084
 
769
1085
  let pluginPath;
770
- const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", "openclaw-memory-plugin") : "";
1086
+ const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", resolvedPluginDir || "openclaw-plugin") : "";
771
1087
  if (openvikingRepo && existsSync(join(localPluginDir, "index.ts"))) {
772
1088
  pluginPath = localPluginDir;
1089
+ PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
773
1090
  info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
774
- if (!existsSync(join(pluginPath, "node_modules"))) {
1091
+ await deployLocalPlugin(pluginPath);
775
1092
  info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
776
- await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: pluginPath, silent: false });
777
- }
1093
+ const npmArgs = resolvedNpmOmitDev
1094
+ ? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
1095
+ : ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
1096
+ await run("npm", npmArgs, { cwd: PLUGIN_DEST, silent: false });
1097
+ pluginPath = PLUGIN_DEST;
778
1098
  } else {
779
1099
  await downloadPlugin();
780
1100
  pluginPath = PLUGIN_DEST;
781
1101
  }
782
1102
 
783
- await configureOpenClawPlugin(pluginPath);
1103
+ await configureOpenClawPlugin();
784
1104
  const envFiles = await writeOpenvikingEnv({
785
1105
  includePython: selectedMode === "local",
786
1106
  });
@@ -803,6 +1123,14 @@ async function main() {
803
1123
  console.log("");
804
1124
 
805
1125
  if (selectedMode === "local") {
1126
+ if (envFiles?.shellPath && !IS_WIN) {
1127
+ info(
1128
+ tr(
1129
+ 'If source fails, set: export OPENVIKING_PYTHON="$(command -v python3)"',
1130
+ '若 source 失败,可执行: export OPENVIKING_PYTHON="$(command -v python3)"',
1131
+ ),
1132
+ );
1133
+ }
806
1134
  info(tr(`You can edit the config freely: ${OPENVIKING_DIR}/ov.conf`, `你可以按需自由修改配置文件: ${OPENVIKING_DIR}/ov.conf`));
807
1135
  } else {
808
1136
  info(tr(`Remote server: ${remoteBaseUrl}`, `远程服务器: ${remoteBaseUrl}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openviking-setup-helper",
3
- "version": "0.2.8",
3
+ "version": "0.2.9-dev.0",
4
4
  "description": "Setup helper for installing OpenViking memory plugin into OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "repository": {
22
22
  "type": "git",
23
23
  "url": "git+https://github.com/volcengine/OpenViking.git",
24
- "directory": "examples/openclaw-memory-plugin/setup-helper"
24
+ "directory": "examples/openclaw-plugin/setup-helper"
25
25
  },
26
26
  "files": [
27
27
  "install.js"