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.
- package/install.js +441 -113
- 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,
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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://
|
|
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 =
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
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 [
|
|
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("
|
|
130
|
-
console.log(" --
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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: ${
|
|
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",
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
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(
|
|
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("(
|
|
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
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
637
|
-
|
|
931
|
+
const pluginId = resolvedPluginId;
|
|
932
|
+
const pluginSlot = resolvedPluginSlot;
|
|
638
933
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
-
|
|
669
|
-
|
|
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
|
-
|
|
672
|
-
|
|
673
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
1091
|
+
await deployLocalPlugin(pluginPath);
|
|
775
1092
|
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
776
|
-
|
|
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(
|
|
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.
|
|
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-
|
|
24
|
+
"directory": "examples/openclaw-plugin/setup-helper"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"install.js"
|