openclaw-openviking-setup-helper 0.3.0-beta.9 → 0.3.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 +682 -93
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
* OpenClaw OpenViking plugin installer (remote OpenViking server — does not install Python/OpenViking server).
|
|
4
4
|
*
|
|
5
5
|
* One-liner (after npm publish; use package name + bin name):
|
|
6
|
-
* npx -p openclaw-openviking-setup-helper ov-install [ -
|
|
6
|
+
* npx -p openclaw-openviking-setup-helper ov-install [ --base-url URL ] [ --api-key KEY ] [ --zh ] [ --workdir PATH ]
|
|
7
7
|
* Or install globally then run:
|
|
8
8
|
* npm i -g openclaw-openviking-setup-helper
|
|
9
9
|
* ov-install
|
|
10
10
|
* openclaw-openviking-install
|
|
11
11
|
*
|
|
12
12
|
* Direct run:
|
|
13
|
-
* node install.js [ -
|
|
13
|
+
* node install.js [ --base-url URL ] [ --api-key KEY ] [ --zh ] [ --workdir PATH ] [ --upgrade-plugin ]
|
|
14
14
|
* [ --plugin-version=TAG ]
|
|
15
15
|
*
|
|
16
16
|
* Environment variables:
|
|
17
|
-
* REPO, PLUGIN_VERSION (or BRANCH),
|
|
17
|
+
* REPO, PLUGIN_VERSION (or BRANCH), OPENVIKING_BASE_URL, OPENVIKING_API_KEY, SKIP_OPENCLAW
|
|
18
18
|
* NPM_REGISTRY
|
|
19
19
|
*/
|
|
20
20
|
|
|
@@ -33,6 +33,8 @@ const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || ""
|
|
|
33
33
|
let PLUGIN_VERSION = pluginVersionEnv;
|
|
34
34
|
let pluginVersionExplicit = Boolean(pluginVersionEnv);
|
|
35
35
|
const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
|
|
36
|
+
const DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION = "2026.5.3";
|
|
37
|
+
const OPENCLAW_SHORT_VERSION_YEAR = 2026;
|
|
36
38
|
|
|
37
39
|
const IS_WIN = process.platform === "win32";
|
|
38
40
|
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
@@ -47,8 +49,9 @@ const FALLBACK_LEGACY = {
|
|
|
47
49
|
id: "memory-openviking",
|
|
48
50
|
kind: "memory",
|
|
49
51
|
slot: "memory",
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
minOpenclawVersion: "2026.3.7",
|
|
53
|
+
required: ["index.ts", "config.ts", "client.ts", "openclaw.plugin.json", "package.json"],
|
|
54
|
+
optional: ["package-lock.json", ".gitignore", "memory-ranking.ts", "text-utils.ts", "process-manager.ts", "tsconfig.json"],
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
// Must match examples/openclaw-plugin/install-manifest.json (npm only installs package deps, not these .ts files).
|
|
@@ -57,15 +60,19 @@ const FALLBACK_CURRENT = {
|
|
|
57
60
|
id: "openviking",
|
|
58
61
|
kind: "context-engine",
|
|
59
62
|
slot: "contextEngine",
|
|
63
|
+
minOpenclawVersion: "2026.4.8",
|
|
60
64
|
required: ["index.ts", "config.ts", "package.json", "openclaw.plugin.json"],
|
|
61
65
|
optional: [
|
|
62
66
|
"context-engine.ts",
|
|
67
|
+
"auto-recall.ts",
|
|
63
68
|
"client.ts",
|
|
64
69
|
"process-manager.ts",
|
|
65
70
|
"memory-ranking.ts",
|
|
66
71
|
"text-utils.ts",
|
|
67
72
|
"tool-call-id.ts",
|
|
68
73
|
"session-transcript-repair.ts",
|
|
74
|
+
"runtime-utils.ts",
|
|
75
|
+
"commands/setup.ts",
|
|
69
76
|
"tsconfig.json",
|
|
70
77
|
"package-lock.json",
|
|
71
78
|
".gitignore",
|
|
@@ -85,21 +92,31 @@ let resolvedPluginSlot = "";
|
|
|
85
92
|
let resolvedFilesRequired = [];
|
|
86
93
|
let resolvedFilesOptional = [];
|
|
87
94
|
let resolvedNpmOmitDev = true;
|
|
95
|
+
let resolvedNpmBuild = false;
|
|
96
|
+
let resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
|
|
97
|
+
let resolvedNpmBuildScript = "build";
|
|
98
|
+
let resolvedNpmPruneAfterBuild = true;
|
|
88
99
|
let resolvedMinOpenclawVersion = "";
|
|
89
100
|
let resolvedMinOpenvikingVersion = "";
|
|
90
101
|
let resolvedPluginReleaseId = "";
|
|
102
|
+
let detectedOpenClawVersion = "";
|
|
91
103
|
|
|
92
|
-
let
|
|
104
|
+
let nonInteractive = false;
|
|
93
105
|
let langZh = false;
|
|
94
106
|
let workdirExplicit = false;
|
|
95
107
|
let upgradePluginOnly = false;
|
|
96
108
|
let rollbackLastUpgrade = false;
|
|
97
109
|
let showCurrentVersion = false;
|
|
110
|
+
let uninstallPlugin = false;
|
|
98
111
|
|
|
99
112
|
const selectedMode = "remote";
|
|
113
|
+
const baseUrlFromEnv = !!process.env.OPENVIKING_BASE_URL;
|
|
100
114
|
let remoteBaseUrl = (process.env.OPENVIKING_BASE_URL || "http://127.0.0.1:1933").trim();
|
|
101
115
|
let remoteApiKey = (process.env.OPENVIKING_API_KEY || "").trim();
|
|
102
116
|
let remoteAgentPrefix = (process.env.OPENVIKING_AGENT_PREFIX || "").trim();
|
|
117
|
+
let remoteAccountId = (process.env.OPENVIKING_ACCOUNT_ID || "").trim();
|
|
118
|
+
let remoteUserId = (process.env.OPENVIKING_USER_ID || "").trim();
|
|
119
|
+
let baseUrlExplicit = baseUrlFromEnv;
|
|
103
120
|
let upgradeRuntimeConfig = null;
|
|
104
121
|
let installedUpgradeState = null;
|
|
105
122
|
let upgradeAudit = null;
|
|
@@ -108,8 +125,11 @@ const argv = process.argv.slice(2);
|
|
|
108
125
|
for (let i = 0; i < argv.length; i++) {
|
|
109
126
|
const arg = argv[i];
|
|
110
127
|
if (arg === "-y" || arg === "--yes") {
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
err(tr(
|
|
129
|
+
"-y/--yes has been removed. Use --base-url <URL> [--api-key <KEY>] for non-interactive mode.",
|
|
130
|
+
"-y/--yes 已移除。使用 --base-url <URL> [--api-key <KEY>] 进入非交互模式。",
|
|
131
|
+
));
|
|
132
|
+
process.exit(1);
|
|
113
133
|
}
|
|
114
134
|
if (arg === "--zh") {
|
|
115
135
|
langZh = true;
|
|
@@ -127,6 +147,10 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
127
147
|
rollbackLastUpgrade = true;
|
|
128
148
|
continue;
|
|
129
149
|
}
|
|
150
|
+
if (arg === "--uninstall" || arg === "--remove") {
|
|
151
|
+
uninstallPlugin = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
130
154
|
if (arg === "--workdir") {
|
|
131
155
|
const workdir = argv[i + 1]?.trim();
|
|
132
156
|
if (!workdir) {
|
|
@@ -173,12 +197,71 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
173
197
|
i += 1;
|
|
174
198
|
continue;
|
|
175
199
|
}
|
|
200
|
+
if (arg === "--base-url") {
|
|
201
|
+
const val = argv[i + 1]?.trim();
|
|
202
|
+
if (!val) { console.error("--base-url requires a URL"); process.exit(1); }
|
|
203
|
+
remoteBaseUrl = val;
|
|
204
|
+
baseUrlExplicit = true;
|
|
205
|
+
i += 1;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (arg.startsWith("--base-url=")) {
|
|
209
|
+
remoteBaseUrl = arg.slice("--base-url=".length).trim();
|
|
210
|
+
baseUrlExplicit = true;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (arg === "--api-key") {
|
|
214
|
+
const val = argv[i + 1]?.trim();
|
|
215
|
+
if (!val) { console.error("--api-key requires a value"); process.exit(1); }
|
|
216
|
+
remoteApiKey = val;
|
|
217
|
+
i += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (arg.startsWith("--api-key=")) {
|
|
221
|
+
remoteApiKey = arg.slice("--api-key=".length).trim();
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (arg === "--agent-prefix") {
|
|
225
|
+
const val = argv[i + 1]?.trim();
|
|
226
|
+
if (!val) { console.error("--agent-prefix requires a value"); process.exit(1); }
|
|
227
|
+
remoteAgentPrefix = val;
|
|
228
|
+
i += 1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (arg.startsWith("--agent-prefix=")) {
|
|
232
|
+
remoteAgentPrefix = arg.slice("--agent-prefix=".length).trim();
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (arg === "--account-id") {
|
|
236
|
+
const val = argv[i + 1]?.trim();
|
|
237
|
+
if (!val) { console.error("--account-id requires a value"); process.exit(1); }
|
|
238
|
+
remoteAccountId = val;
|
|
239
|
+
i += 1;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (arg.startsWith("--account-id=")) {
|
|
243
|
+
remoteAccountId = arg.slice("--account-id=".length).trim();
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (arg === "--user-id") {
|
|
247
|
+
const val = argv[i + 1]?.trim();
|
|
248
|
+
if (!val) { console.error("--user-id requires a value"); process.exit(1); }
|
|
249
|
+
remoteUserId = val;
|
|
250
|
+
i += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (arg.startsWith("--user-id=")) {
|
|
254
|
+
remoteUserId = arg.slice("--user-id=".length).trim();
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
176
257
|
if (arg === "-h" || arg === "--help") {
|
|
177
258
|
printHelp();
|
|
178
259
|
process.exit(0);
|
|
179
260
|
}
|
|
180
261
|
}
|
|
181
262
|
|
|
263
|
+
nonInteractive = baseUrlExplicit;
|
|
264
|
+
|
|
182
265
|
function setOpenClawDir(dir) {
|
|
183
266
|
OPENCLAW_DIR = dir;
|
|
184
267
|
}
|
|
@@ -195,7 +278,12 @@ function printHelp() {
|
|
|
195
278
|
console.log(" Upgrade only the plugin to the requested --plugin-version; keeps existing plugin runtime config");
|
|
196
279
|
console.log(" --rollback, --rollback-last-upgrade");
|
|
197
280
|
console.log(" Roll back the last plugin upgrade using the saved audit/backup files");
|
|
198
|
-
console.log("
|
|
281
|
+
console.log(" --uninstall, --remove Uninstall OpenViking plugin from OpenClaw (backup config, remove plugin entries)");
|
|
282
|
+
console.log(" --base-url=URL OpenViking server URL (default: $OPENVIKING_BASE_URL or http://127.0.0.1:1933)");
|
|
283
|
+
console.log(" --api-key=KEY OpenViking API key (default: $OPENVIKING_API_KEY)");
|
|
284
|
+
console.log(" --agent-prefix=PREFIX Agent routing prefix (default: $OPENVIKING_AGENT_PREFIX)");
|
|
285
|
+
console.log(" --account-id=ID Account ID for root API key (default: $OPENVIKING_ACCOUNT_ID)");
|
|
286
|
+
console.log(" --user-id=ID User ID for root API key (default: $OPENVIKING_USER_ID)");
|
|
199
287
|
console.log(" --zh Chinese prompts");
|
|
200
288
|
console.log(" -h, --help This help");
|
|
201
289
|
console.log("");
|
|
@@ -326,6 +414,48 @@ function question(prompt, defaultValue = "") {
|
|
|
326
414
|
});
|
|
327
415
|
}
|
|
328
416
|
|
|
417
|
+
function isValidAgentPrefixInput(value) {
|
|
418
|
+
const trimmed = String(value || "").trim();
|
|
419
|
+
return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function parseJsonObjectFromOutput(output) {
|
|
423
|
+
const text = String(output || "").trim();
|
|
424
|
+
if (!text) return null;
|
|
425
|
+
try {
|
|
426
|
+
return JSON.parse(text);
|
|
427
|
+
} catch {
|
|
428
|
+
// OpenClaw may print plugin registration logs before --json output.
|
|
429
|
+
}
|
|
430
|
+
for (let index = text.lastIndexOf("{"); index >= 0; index = text.lastIndexOf("{", index - 1)) {
|
|
431
|
+
try {
|
|
432
|
+
const parsed = JSON.parse(text.slice(index).trim());
|
|
433
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
434
|
+
return parsed;
|
|
435
|
+
}
|
|
436
|
+
} catch {
|
|
437
|
+
// Keep scanning earlier braces until the outer JSON object is found.
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async function questionAgentPrefix(defaultValue = "") {
|
|
444
|
+
while (true) {
|
|
445
|
+
const answer = (await question(
|
|
446
|
+
tr("Agent Prefix (optional)", "Agent Prefix(可选)"),
|
|
447
|
+
defaultValue,
|
|
448
|
+
)).trim();
|
|
449
|
+
if (isValidAgentPrefixInput(answer)) {
|
|
450
|
+
return answer;
|
|
451
|
+
}
|
|
452
|
+
warn(tr(
|
|
453
|
+
"Agent Prefix may only contain letters, digits, underscores, and hyphens, or be empty.",
|
|
454
|
+
"Agent Prefix 只能包含字母、数字、下划线和连字符,或留空。",
|
|
455
|
+
));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
329
459
|
function detectOpenClawInstances() {
|
|
330
460
|
const instances = [];
|
|
331
461
|
try {
|
|
@@ -349,7 +479,7 @@ async function selectWorkdir() {
|
|
|
349
479
|
setOpenClawDir(instances[0]);
|
|
350
480
|
return;
|
|
351
481
|
}
|
|
352
|
-
if (
|
|
482
|
+
if (nonInteractive) return;
|
|
353
483
|
|
|
354
484
|
console.log("");
|
|
355
485
|
bold(tr("Found multiple OpenClaw instances:", "发现多个 OpenClaw 实例:"));
|
|
@@ -369,10 +499,10 @@ async function selectWorkdir() {
|
|
|
369
499
|
}
|
|
370
500
|
|
|
371
501
|
async function collectRemoteConfig() {
|
|
372
|
-
if (
|
|
502
|
+
if (nonInteractive) return;
|
|
373
503
|
remoteBaseUrl = await question(tr("OpenViking server URL", "OpenViking 服务器地址"), remoteBaseUrl);
|
|
374
504
|
remoteApiKey = await question(tr("API Key (optional)", "API Key(可选)"), remoteApiKey);
|
|
375
|
-
remoteAgentPrefix = await
|
|
505
|
+
remoteAgentPrefix = await questionAgentPrefix(remoteAgentPrefix);
|
|
376
506
|
}
|
|
377
507
|
|
|
378
508
|
async function checkOpenClaw() {
|
|
@@ -414,6 +544,51 @@ function versionGte(v1, v2) {
|
|
|
414
544
|
return a3 >= b3;
|
|
415
545
|
}
|
|
416
546
|
|
|
547
|
+
function parseOpenClawPolicyVersion(value) {
|
|
548
|
+
const parts = String(value || "")
|
|
549
|
+
.match(/\d+/g)
|
|
550
|
+
?.map((part) => Number.parseInt(part, 10) || 0) || [];
|
|
551
|
+
if (parts.length === 0) return [0, 0, 0];
|
|
552
|
+
if (parts[0] >= 2000) {
|
|
553
|
+
return [parts[0], parts[1] || 0, parts[2] || 0];
|
|
554
|
+
}
|
|
555
|
+
return [OPENCLAW_SHORT_VERSION_YEAR, parts[0] || 0, parts[1] || 0];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function openClawPolicyVersionGte(v1, v2) {
|
|
559
|
+
const a = parseOpenClawPolicyVersion(v1);
|
|
560
|
+
const b = parseOpenClawPolicyVersion(v2);
|
|
561
|
+
for (let i = 0; i < 3; i++) {
|
|
562
|
+
if (a[i] !== b[i]) return a[i] > b[i];
|
|
563
|
+
}
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function applyOpenClawBuildPolicy(openClawVersion) {
|
|
568
|
+
if (!resolvedNpmBuild || !resolvedNpmBuildMinOpenclawVersion) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!openClawVersion || openClawVersion === "0.0.0") {
|
|
572
|
+
warn(tr(
|
|
573
|
+
"Could not determine OpenClaw version; keeping plugin source build enabled.",
|
|
574
|
+
"无法确定 OpenClaw 版本,保持插件源码构建开启。",
|
|
575
|
+
));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (openClawPolicyVersionGte(openClawVersion, resolvedNpmBuildMinOpenclawVersion)) {
|
|
579
|
+
info(tr(
|
|
580
|
+
`OpenClaw ${openClawVersion} requires plugin source build (>= ${resolvedNpmBuildMinOpenclawVersion})`,
|
|
581
|
+
`OpenClaw ${openClawVersion} 需要插件源码构建(>= ${resolvedNpmBuildMinOpenclawVersion})`,
|
|
582
|
+
));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
resolvedNpmBuild = false;
|
|
586
|
+
info(tr(
|
|
587
|
+
`OpenClaw ${openClawVersion} is below ${resolvedNpmBuildMinOpenclawVersion}; skipping plugin source build`,
|
|
588
|
+
`OpenClaw ${openClawVersion} 低于 ${resolvedNpmBuildMinOpenclawVersion},跳过插件源码构建`,
|
|
589
|
+
));
|
|
590
|
+
}
|
|
591
|
+
|
|
417
592
|
function isSemverLike(value) {
|
|
418
593
|
return /^v?\d+(\.\d+){1,2}$/.test(value);
|
|
419
594
|
}
|
|
@@ -431,16 +606,29 @@ if (upgradePluginOnly && rollbackLastUpgrade) {
|
|
|
431
606
|
process.exit(1);
|
|
432
607
|
}
|
|
433
608
|
|
|
609
|
+
if (uninstallPlugin && (upgradePluginOnly || rollbackLastUpgrade)) {
|
|
610
|
+
console.error("--uninstall cannot be used with --upgrade-plugin or --rollback");
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
|
|
434
614
|
// Detect OpenClaw version
|
|
435
615
|
async function detectOpenClawVersion() {
|
|
616
|
+
if (detectedOpenClawVersion) {
|
|
617
|
+
return detectedOpenClawVersion;
|
|
618
|
+
}
|
|
436
619
|
try {
|
|
437
620
|
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
621
|
+
const output = `${result.out || ""}\n${result.err || ""}`;
|
|
622
|
+
if (result.code === 0 && output) {
|
|
623
|
+
const match = output.match(/\d+\.\d+(\.\d+)?/);
|
|
624
|
+
if (match) {
|
|
625
|
+
detectedOpenClawVersion = match[0];
|
|
626
|
+
return detectedOpenClawVersion;
|
|
627
|
+
}
|
|
441
628
|
}
|
|
442
629
|
} catch {}
|
|
443
|
-
|
|
630
|
+
detectedOpenClawVersion = "0.0.0";
|
|
631
|
+
return detectedOpenClawVersion;
|
|
444
632
|
}
|
|
445
633
|
|
|
446
634
|
// Try to fetch a URL, return response text or null
|
|
@@ -589,6 +777,12 @@ async function resolvePluginConfig() {
|
|
|
589
777
|
|
|
590
778
|
info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
|
|
591
779
|
|
|
780
|
+
resolvedNpmOmitDev = true;
|
|
781
|
+
resolvedNpmBuild = false;
|
|
782
|
+
resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
|
|
783
|
+
resolvedNpmBuildScript = "build";
|
|
784
|
+
resolvedNpmPruneAfterBuild = true;
|
|
785
|
+
|
|
592
786
|
let pluginDir = "";
|
|
593
787
|
let manifestData = null;
|
|
594
788
|
|
|
@@ -630,7 +824,19 @@ async function resolvePluginConfig() {
|
|
|
630
824
|
resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
|
|
631
825
|
resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
|
|
632
826
|
resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
|
|
633
|
-
|
|
827
|
+
const npmConfig = manifestData.npm && typeof manifestData.npm === "object"
|
|
828
|
+
? manifestData.npm
|
|
829
|
+
: {};
|
|
830
|
+
resolvedNpmOmitDev = npmConfig.omitDev !== false;
|
|
831
|
+
resolvedNpmBuild = npmConfig.build === true || npmConfig.buildFromSource === true;
|
|
832
|
+
resolvedNpmBuildMinOpenclawVersion =
|
|
833
|
+
typeof npmConfig.buildMinOpenclawVersion === "string" && npmConfig.buildMinOpenclawVersion.trim()
|
|
834
|
+
? npmConfig.buildMinOpenclawVersion.trim()
|
|
835
|
+
: DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
|
|
836
|
+
resolvedNpmBuildScript = typeof npmConfig.buildScript === "string" && npmConfig.buildScript.trim()
|
|
837
|
+
? npmConfig.buildScript.trim()
|
|
838
|
+
: "build";
|
|
839
|
+
resolvedNpmPruneAfterBuild = npmConfig.pruneAfterBuild !== false;
|
|
634
840
|
resolvedFilesRequired = manifestData.files?.required || [];
|
|
635
841
|
resolvedFilesOptional = manifestData.files?.optional || [];
|
|
636
842
|
} else {
|
|
@@ -665,6 +871,10 @@ async function resolvePluginConfig() {
|
|
|
665
871
|
resolvedFilesRequired = fallback.required;
|
|
666
872
|
resolvedFilesOptional = fallback.optional;
|
|
667
873
|
resolvedNpmOmitDev = true;
|
|
874
|
+
resolvedNpmBuild = false;
|
|
875
|
+
resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
|
|
876
|
+
resolvedNpmBuildScript = "build";
|
|
877
|
+
resolvedNpmPruneAfterBuild = true;
|
|
668
878
|
|
|
669
879
|
// If no compatVer from package.json, try main branch manifest
|
|
670
880
|
if (!compatVer && PLUGIN_VERSION !== "main") {
|
|
@@ -681,7 +891,7 @@ async function resolvePluginConfig() {
|
|
|
681
891
|
}
|
|
682
892
|
}
|
|
683
893
|
|
|
684
|
-
resolvedMinOpenclawVersion = compatVer || "2026.3.7";
|
|
894
|
+
resolvedMinOpenclawVersion = compatVer || fallback.minOpenclawVersion || "2026.3.7";
|
|
685
895
|
resolvedMinOpenvikingVersion = "";
|
|
686
896
|
}
|
|
687
897
|
|
|
@@ -699,6 +909,7 @@ async function checkOpenClawCompatibility() {
|
|
|
699
909
|
|
|
700
910
|
const ocVersion = await detectOpenClawVersion();
|
|
701
911
|
info(tr(`Detected OpenClaw version: ${ocVersion}`, `检测到 OpenClaw 版本: ${ocVersion}`));
|
|
912
|
+
applyOpenClawBuildPolicy(ocVersion);
|
|
702
913
|
|
|
703
914
|
// If no minimum version required, pass
|
|
704
915
|
if (!resolvedMinOpenclawVersion) {
|
|
@@ -711,7 +922,7 @@ async function checkOpenClawCompatibility() {
|
|
|
711
922
|
}
|
|
712
923
|
|
|
713
924
|
// Check compatibility
|
|
714
|
-
if (!
|
|
925
|
+
if (!openClawPolicyVersionGte(ocVersion, resolvedMinOpenclawVersion)) {
|
|
715
926
|
err(tr(
|
|
716
927
|
`OpenClaw ${ocVersion} does not support this plugin (requires >= ${resolvedMinOpenclawVersion})`,
|
|
717
928
|
`OpenClaw ${ocVersion} 不支持此插件(需要 >= ${resolvedMinOpenclawVersion})`
|
|
@@ -1270,6 +1481,103 @@ async function downloadPluginFile(destDir, fileName, url, required, index, total
|
|
|
1270
1481
|
process.exit(1);
|
|
1271
1482
|
}
|
|
1272
1483
|
|
|
1484
|
+
function runtimeOutputCandidatesForEntry(entry) {
|
|
1485
|
+
const normalized = String(entry || "").replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1486
|
+
if (!normalized.endsWith(".ts")) {
|
|
1487
|
+
return [];
|
|
1488
|
+
}
|
|
1489
|
+
const withoutExt = normalized.slice(0, -3);
|
|
1490
|
+
return [
|
|
1491
|
+
`dist/${withoutExt}.js`,
|
|
1492
|
+
`dist/${withoutExt}.mjs`,
|
|
1493
|
+
`dist/${withoutExt}.cjs`,
|
|
1494
|
+
`${withoutExt}.js`,
|
|
1495
|
+
`${withoutExt}.mjs`,
|
|
1496
|
+
`${withoutExt}.cjs`,
|
|
1497
|
+
];
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
async function assertBuiltRuntimeOutputs(destDir) {
|
|
1501
|
+
let pkg = null;
|
|
1502
|
+
try {
|
|
1503
|
+
pkg = JSON.parse(await readFile(join(destDir, "package.json"), "utf8"));
|
|
1504
|
+
} catch {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
const entries = [];
|
|
1509
|
+
const extensions = pkg?.openclaw?.extensions;
|
|
1510
|
+
if (Array.isArray(extensions)) {
|
|
1511
|
+
for (const entry of extensions) {
|
|
1512
|
+
if (typeof entry === "string") entries.push(entry);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
if (typeof pkg?.openclaw?.setupEntry === "string") {
|
|
1516
|
+
entries.push(pkg.openclaw.setupEntry);
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const missing = [];
|
|
1520
|
+
for (const entry of entries) {
|
|
1521
|
+
const candidates = runtimeOutputCandidatesForEntry(entry);
|
|
1522
|
+
if (candidates.length === 0) continue;
|
|
1523
|
+
const found = candidates.some((candidate) => existsSync(join(destDir, ...candidate.split("/"))));
|
|
1524
|
+
if (!found) {
|
|
1525
|
+
missing.push(`${entry} (expected one of: ${candidates.join(", ")})`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (missing.length === 0) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
err(tr(
|
|
1534
|
+
`Plugin build did not create required runtime output:\n - ${missing.join("\n - ")}`,
|
|
1535
|
+
`插件构建未生成必需的运行时产物:\n - ${missing.join("\n - ")}`,
|
|
1536
|
+
));
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
async function installPluginNpmDependencies(destDir) {
|
|
1541
|
+
if (!resolvedNpmBuild) {
|
|
1542
|
+
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
1543
|
+
const npmArgs = resolvedNpmOmitDev
|
|
1544
|
+
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1545
|
+
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1546
|
+
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
info(tr(
|
|
1551
|
+
"Installing plugin npm dependencies for source build...",
|
|
1552
|
+
"正在安装插件源码构建所需的 npm 依赖...",
|
|
1553
|
+
));
|
|
1554
|
+
await run("npm", [
|
|
1555
|
+
"install",
|
|
1556
|
+
"--include=dev",
|
|
1557
|
+
"--no-audit",
|
|
1558
|
+
"--no-fund",
|
|
1559
|
+
"--registry",
|
|
1560
|
+
NPM_REGISTRY,
|
|
1561
|
+
], { cwd: destDir, silent: false });
|
|
1562
|
+
|
|
1563
|
+
info(tr(
|
|
1564
|
+
`Building plugin runtime output with npm run ${resolvedNpmBuildScript}...`,
|
|
1565
|
+
`正在执行 npm run ${resolvedNpmBuildScript} 构建插件运行时产物...`,
|
|
1566
|
+
));
|
|
1567
|
+
await run("npm", ["run", resolvedNpmBuildScript], { cwd: destDir, silent: false });
|
|
1568
|
+
await assertBuiltRuntimeOutputs(destDir);
|
|
1569
|
+
|
|
1570
|
+
if (resolvedNpmOmitDev && resolvedNpmPruneAfterBuild) {
|
|
1571
|
+
info(tr("Pruning plugin dev dependencies...", "正在裁剪插件开发依赖..."));
|
|
1572
|
+
await run("npm", [
|
|
1573
|
+
"prune",
|
|
1574
|
+
"--omit=dev",
|
|
1575
|
+
"--no-audit",
|
|
1576
|
+
"--no-fund",
|
|
1577
|
+
], { cwd: destDir, silent: false });
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1273
1581
|
async function downloadPlugin(destDir) {
|
|
1274
1582
|
const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
|
|
1275
1583
|
const pluginDir = resolvedPluginDir;
|
|
@@ -1296,12 +1604,7 @@ async function downloadPlugin(destDir) {
|
|
|
1296
1604
|
await downloadPluginFile(destDir, name, url, false, i, total);
|
|
1297
1605
|
}
|
|
1298
1606
|
|
|
1299
|
-
|
|
1300
|
-
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
1301
|
-
const npmArgs = resolvedNpmOmitDev
|
|
1302
|
-
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1303
|
-
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1304
|
-
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1607
|
+
await installPluginNpmDependencies(destDir);
|
|
1305
1608
|
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
1306
1609
|
}
|
|
1307
1610
|
|
|
@@ -1407,6 +1710,64 @@ async function scrubStaleOpenClawPluginRegistration() {
|
|
|
1407
1710
|
await rename(tmp, configPath);
|
|
1408
1711
|
}
|
|
1409
1712
|
|
|
1713
|
+
async function cleanupConflictingPluginVariants() {
|
|
1714
|
+
const configPath = getOpenClawConfigPath();
|
|
1715
|
+
if (!existsSync(configPath)) return;
|
|
1716
|
+
let cfg;
|
|
1717
|
+
try {
|
|
1718
|
+
cfg = JSON.parse(await readFile(configPath, "utf8"));
|
|
1719
|
+
} catch { return; }
|
|
1720
|
+
if (!cfg.plugins) return;
|
|
1721
|
+
const p = cfg.plugins;
|
|
1722
|
+
let changed = false;
|
|
1723
|
+
for (const variant of PLUGIN_VARIANTS) {
|
|
1724
|
+
if (variant.id === resolvedPluginId) continue;
|
|
1725
|
+
if (p.entries && Object.prototype.hasOwnProperty.call(p.entries, variant.id)) {
|
|
1726
|
+
info(tr(`Removing conflicting plugin variant: ${variant.id}`, `正在移除冲突的插件变体: ${variant.id}`));
|
|
1727
|
+
delete p.entries[variant.id];
|
|
1728
|
+
changed = true;
|
|
1729
|
+
}
|
|
1730
|
+
if (Array.isArray(p.allow)) {
|
|
1731
|
+
const next = p.allow.filter((id) => id !== variant.id);
|
|
1732
|
+
if (next.length !== p.allow.length) {
|
|
1733
|
+
p.allow = next;
|
|
1734
|
+
changed = true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (p.installs && Object.prototype.hasOwnProperty.call(p.installs, variant.id)) {
|
|
1738
|
+
delete p.installs[variant.id];
|
|
1739
|
+
changed = true;
|
|
1740
|
+
}
|
|
1741
|
+
if (p.slots && p.slots[variant.slot] === variant.id) {
|
|
1742
|
+
p.slots[variant.slot] = variant.slotFallback || "none";
|
|
1743
|
+
changed = true;
|
|
1744
|
+
}
|
|
1745
|
+
if (p.load && Array.isArray(p.load.paths)) {
|
|
1746
|
+
const norm = (s) => String(s).replace(/\\/g, "/");
|
|
1747
|
+
const extNeedle = `/extensions/${variant.id}`;
|
|
1748
|
+
const next = p.load.paths.filter((path) => {
|
|
1749
|
+
if (typeof path !== "string") return true;
|
|
1750
|
+
return !norm(path).includes(extNeedle);
|
|
1751
|
+
});
|
|
1752
|
+
if (next.length !== p.load.paths.length) {
|
|
1753
|
+
p.load.paths = next;
|
|
1754
|
+
changed = true;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const variantDir = join(OPENCLAW_DIR, "extensions", variant.id);
|
|
1758
|
+
if (existsSync(variantDir)) {
|
|
1759
|
+
info(tr(`Removing conflicting plugin directory: ${variantDir}`, `正在移除冲突的插件目录: ${variantDir}`));
|
|
1760
|
+
await rm(variantDir, { recursive: true, force: true });
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
if (!changed) return;
|
|
1764
|
+
const out = JSON.stringify(cfg, null, 2) + "\n";
|
|
1765
|
+
const tmp = `${configPath}.ov-install-tmp.${process.pid}`;
|
|
1766
|
+
await writeFile(tmp, out, "utf8");
|
|
1767
|
+
await rename(tmp, configPath);
|
|
1768
|
+
info(tr("Conflicting plugin variants cleaned up", "冲突的插件变体已清理"));
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1410
1771
|
async function configureOpenClawPlugin({
|
|
1411
1772
|
preserveExistingConfig = false,
|
|
1412
1773
|
runtimeConfig = null,
|
|
@@ -1447,12 +1808,15 @@ async function configureOpenClawPlugin({
|
|
|
1447
1808
|
const p = cfg.plugins;
|
|
1448
1809
|
if (!p.entries) p.entries = {};
|
|
1449
1810
|
if (!p.entries[pluginId]) p.entries[pluginId] = {};
|
|
1811
|
+
p.entries[pluginId].enabled = true;
|
|
1450
1812
|
if (!p.entries[pluginId].config) p.entries[pluginId].config = {};
|
|
1451
1813
|
if (!Array.isArray(p.allow)) p.allow = [];
|
|
1452
1814
|
if (!p.allow.includes(pluginId)) p.allow.push(pluginId);
|
|
1453
1815
|
return cfg;
|
|
1454
1816
|
};
|
|
1455
1817
|
|
|
1818
|
+
await cleanupConflictingPluginVariants();
|
|
1819
|
+
|
|
1456
1820
|
if (!preserveExistingConfig) {
|
|
1457
1821
|
await scrubStaleOpenClawPluginRegistration();
|
|
1458
1822
|
}
|
|
@@ -1488,7 +1852,7 @@ async function configureOpenClawPlugin({
|
|
|
1488
1852
|
`已保留 ${pluginId} 的现有插件运行时配置`,
|
|
1489
1853
|
),
|
|
1490
1854
|
);
|
|
1491
|
-
return;
|
|
1855
|
+
return { runtimeConfigOk: true };
|
|
1492
1856
|
}
|
|
1493
1857
|
|
|
1494
1858
|
const writeConfigDirect = async (pluginConfig, slotValue) => {
|
|
@@ -1507,19 +1871,43 @@ async function configureOpenClawPlugin({
|
|
|
1507
1871
|
const effectiveRuntimeConfig = runtimeConfig || {
|
|
1508
1872
|
baseUrl: remoteBaseUrl,
|
|
1509
1873
|
apiKey: remoteApiKey,
|
|
1874
|
+
agent_prefix: remoteAgentPrefix,
|
|
1510
1875
|
};
|
|
1511
|
-
|
|
1876
|
+
|
|
1877
|
+
let allowedPropsLegacy = null;
|
|
1878
|
+
try {
|
|
1879
|
+
const manifestPath = join(PLUGIN_DEST, "openclaw.plugin.json");
|
|
1880
|
+
if (existsSync(manifestPath)) {
|
|
1881
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1882
|
+
const schema = manifest?.configSchema;
|
|
1883
|
+
if (schema?.properties && typeof schema.properties === "object") {
|
|
1884
|
+
allowedPropsLegacy = new Set(Object.keys(schema.properties));
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
} catch { /* ignore parse errors */ }
|
|
1888
|
+
|
|
1889
|
+
const agentVal = effectiveRuntimeConfig.agent_prefix || "";
|
|
1890
|
+
const candidates = {
|
|
1891
|
+
mode: "remote",
|
|
1512
1892
|
baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
|
|
1513
1893
|
targetUri: "viking://user/memories",
|
|
1514
1894
|
autoRecall: true,
|
|
1515
1895
|
autoCapture: true,
|
|
1896
|
+
apiKey: effectiveRuntimeConfig.apiKey || undefined,
|
|
1897
|
+
agentId: agentVal || undefined,
|
|
1516
1898
|
};
|
|
1517
|
-
|
|
1518
|
-
|
|
1899
|
+
|
|
1900
|
+
const pluginConfig = {};
|
|
1901
|
+
for (const [key, val] of Object.entries(candidates)) {
|
|
1902
|
+
if (val === undefined) continue;
|
|
1903
|
+
if (allowedPropsLegacy && !allowedPropsLegacy.has(key)) continue;
|
|
1904
|
+
pluginConfig[key] = val;
|
|
1519
1905
|
}
|
|
1906
|
+
if (!pluginConfig.baseUrl) pluginConfig.baseUrl = effectiveRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
1907
|
+
|
|
1520
1908
|
await writeConfigDirect(pluginConfig, claimSlot ? pluginId : null);
|
|
1521
|
-
info(tr("OpenClaw plugin configured (legacy mode)", "OpenClaw
|
|
1522
|
-
return;
|
|
1909
|
+
info(tr("OpenClaw plugin configured (legacy mode, remote)", "OpenClaw 插件配置完成(旧版模式,远程连接)"));
|
|
1910
|
+
return { runtimeConfigOk: true };
|
|
1523
1911
|
}
|
|
1524
1912
|
|
|
1525
1913
|
// Current (context-engine) plugins: delegate to `openclaw openviking setup --json`
|
|
@@ -1529,38 +1917,60 @@ async function configureOpenClawPlugin({
|
|
|
1529
1917
|
baseUrl: remoteBaseUrl,
|
|
1530
1918
|
apiKey: remoteApiKey,
|
|
1531
1919
|
agent_prefix: remoteAgentPrefix,
|
|
1920
|
+
accountId: remoteAccountId,
|
|
1921
|
+
userId: remoteUserId,
|
|
1532
1922
|
};
|
|
1533
1923
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
setupArgs.push("--agent-id", effectiveRuntimeConfig.agent_prefix);
|
|
1544
|
-
}
|
|
1545
|
-
if (claimSlot) {
|
|
1546
|
-
setupArgs.push("--force-slot");
|
|
1547
|
-
}
|
|
1548
|
-
if (installYes) {
|
|
1549
|
-
setupArgs.push("--allow-offline");
|
|
1550
|
-
}
|
|
1924
|
+
// Detect if the installed plugin supports `setup --json` by checking the deployed setup.ts
|
|
1925
|
+
let setupJsonSupported = false;
|
|
1926
|
+
try {
|
|
1927
|
+
const setupTsPath = join(PLUGIN_DEST, "commands", "setup.ts");
|
|
1928
|
+
if (existsSync(setupTsPath)) {
|
|
1929
|
+
const setupSrc = await readFile(setupTsPath, "utf8");
|
|
1930
|
+
setupJsonSupported = setupSrc.includes('"--json"') || setupSrc.includes("'--json'");
|
|
1931
|
+
}
|
|
1932
|
+
} catch { /* ignore read errors */ }
|
|
1551
1933
|
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1934
|
+
let setupResult = null;
|
|
1935
|
+
if (setupJsonSupported) {
|
|
1936
|
+
const setupArgs = ["openviking", "setup"];
|
|
1937
|
+
setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
|
|
1938
|
+
setupArgs.push("--json");
|
|
1939
|
+
if (effectiveRuntimeConfig.apiKey) {
|
|
1940
|
+
setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
|
|
1941
|
+
}
|
|
1942
|
+
if (effectiveRuntimeConfig.agent_prefix) {
|
|
1943
|
+
setupArgs.push("--agent-prefix", effectiveRuntimeConfig.agent_prefix);
|
|
1944
|
+
}
|
|
1945
|
+
if (effectiveRuntimeConfig.accountId) {
|
|
1946
|
+
setupArgs.push("--account-id", effectiveRuntimeConfig.accountId);
|
|
1947
|
+
}
|
|
1948
|
+
if (effectiveRuntimeConfig.userId) {
|
|
1949
|
+
setupArgs.push("--user-id", effectiveRuntimeConfig.userId);
|
|
1950
|
+
}
|
|
1951
|
+
if (claimSlot) {
|
|
1952
|
+
setupArgs.push("--force-slot");
|
|
1953
|
+
}
|
|
1954
|
+
if (nonInteractive) {
|
|
1955
|
+
setupArgs.push("--allow-offline");
|
|
1956
|
+
}
|
|
1556
1957
|
|
|
1557
|
-
|
|
1958
|
+
info(tr(
|
|
1959
|
+
"Delegating configuration to: openclaw openviking setup --json",
|
|
1960
|
+
"委托配置给: openclaw openviking setup --json",
|
|
1961
|
+
));
|
|
1962
|
+
|
|
1963
|
+
setupResult = await runCapture("openclaw", setupArgs, { env: ocEnv, shell: IS_WIN });
|
|
1964
|
+
} else {
|
|
1965
|
+
info(tr(
|
|
1966
|
+
"Installed plugin does not support setup --json, using direct config write",
|
|
1967
|
+
"已安装的插件不支持 setup --json,使用直接配置写入",
|
|
1968
|
+
));
|
|
1969
|
+
}
|
|
1558
1970
|
|
|
1559
1971
|
let parsed = null;
|
|
1560
|
-
|
|
1561
|
-
parsed =
|
|
1562
|
-
} catch {
|
|
1563
|
-
// If JSON parse fails, fall back to checking exit code
|
|
1972
|
+
if (setupResult) {
|
|
1973
|
+
parsed = parseJsonObjectFromOutput(`${setupResult.out || ""}\n${setupResult.err || ""}`);
|
|
1564
1974
|
}
|
|
1565
1975
|
|
|
1566
1976
|
if (parsed) {
|
|
@@ -1593,34 +2003,67 @@ async function configureOpenClawPlugin({
|
|
|
1593
2003
|
`Setup failed: ${parsed.error || "unknown error"}`,
|
|
1594
2004
|
`配置失败: ${parsed.error || "未知错误"}`,
|
|
1595
2005
|
));
|
|
2006
|
+
return {
|
|
2007
|
+
runtimeConfigOk: false,
|
|
2008
|
+
error: parsed.error || parsed.action || "unknown error",
|
|
2009
|
+
};
|
|
1596
2010
|
}
|
|
1597
2011
|
}
|
|
1598
|
-
} else if (setupResult.code !== 0) {
|
|
1599
|
-
// JSON parse failed and non-zero exit — setup CLI not available (old plugin version)
|
|
2012
|
+
} else if (setupResult && setupResult.code !== 0) {
|
|
1600
2013
|
warn(tr(
|
|
1601
2014
|
`openclaw openviking setup exited with code ${setupResult.code}. Falling back to direct JSON config write.`,
|
|
1602
2015
|
`openclaw openviking setup 退出码 ${setupResult.code},回退到直接写入 JSON 配置。`,
|
|
1603
2016
|
));
|
|
1604
|
-
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
if (!parsed) {
|
|
2020
|
+
// Direct write: only used when the installed plugin doesn't support `setup --json` (old version).
|
|
2021
|
+
// Read the deployed configSchema to determine which fields are allowed, avoiding
|
|
2022
|
+
// "additionalProperties" validation failures when writing new fields to old schemas.
|
|
2023
|
+
let allowedProps = null;
|
|
2024
|
+
try {
|
|
2025
|
+
const manifestPath = join(PLUGIN_DEST, "openclaw.plugin.json");
|
|
2026
|
+
if (existsSync(manifestPath)) {
|
|
2027
|
+
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
2028
|
+
const schema = manifest?.configSchema;
|
|
2029
|
+
if (schema?.properties && typeof schema.properties === "object") {
|
|
2030
|
+
allowedProps = new Set(Object.keys(schema.properties));
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
} catch { /* ignore parse errors, write all fields */ }
|
|
2034
|
+
|
|
2035
|
+
const agentVal = effectiveRuntimeConfig.agent_prefix || "";
|
|
2036
|
+
const useAgentPrefix = !allowedProps || allowedProps.has("agent_prefix");
|
|
2037
|
+
const candidates = {
|
|
2038
|
+
mode: "remote",
|
|
1605
2039
|
baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
|
|
2040
|
+
apiKey: effectiveRuntimeConfig.apiKey || "",
|
|
2041
|
+
accountId: effectiveRuntimeConfig.accountId || undefined,
|
|
2042
|
+
userId: effectiveRuntimeConfig.userId || undefined,
|
|
1606
2043
|
};
|
|
1607
|
-
if (
|
|
1608
|
-
|
|
2044
|
+
if (useAgentPrefix) {
|
|
2045
|
+
candidates.agent_prefix = agentVal;
|
|
2046
|
+
} else {
|
|
2047
|
+
candidates.agentId = agentVal;
|
|
1609
2048
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
2049
|
+
|
|
2050
|
+
const pluginConfig = {};
|
|
2051
|
+
for (const [key, val] of Object.entries(candidates)) {
|
|
2052
|
+
if (val === undefined) continue;
|
|
2053
|
+
if (allowedProps && !allowedProps.has(key)) continue;
|
|
2054
|
+
pluginConfig[key] = val;
|
|
1612
2055
|
}
|
|
2056
|
+
if (!pluginConfig.baseUrl) pluginConfig.baseUrl = effectiveRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
2057
|
+
if (!("apiKey" in pluginConfig)) pluginConfig.apiKey = effectiveRuntimeConfig.apiKey || "";
|
|
2058
|
+
|
|
1613
2059
|
await writeConfigDirect(pluginConfig, claimSlot ? pluginId : null);
|
|
1614
|
-
info(tr(
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
"提示:Root API Key 检测和租户上下文仅在 `openclaw openviking setup` 中可用。如果使用 root API key,请升级插件后重新执行 setup。",
|
|
1619
|
-
));
|
|
1620
|
-
}
|
|
1621
|
-
} else {
|
|
1622
|
-
info(tr("OpenClaw plugin configured", "OpenClaw 插件配置完成"));
|
|
2060
|
+
info(tr(
|
|
2061
|
+
`OpenClaw plugin configured (direct write): baseUrl=${pluginConfig.baseUrl}, apiKey=${pluginConfig.apiKey ? "***" : "(empty)"}`,
|
|
2062
|
+
`OpenClaw 插件配置完成(直接写入): baseUrl=${pluginConfig.baseUrl}, apiKey=${pluginConfig.apiKey ? "***" : "(空)"}`,
|
|
2063
|
+
));
|
|
1623
2064
|
}
|
|
2065
|
+
|
|
2066
|
+
return { runtimeConfigOk: true };
|
|
1624
2067
|
}
|
|
1625
2068
|
|
|
1626
2069
|
async function writeOpenvikingEnv() {
|
|
@@ -1678,6 +2121,125 @@ function getExistingEnvFiles() {
|
|
|
1678
2121
|
return existsSync(envPath) ? { shellPath: envPath } : null;
|
|
1679
2122
|
}
|
|
1680
2123
|
|
|
2124
|
+
async function performUninstall() {
|
|
2125
|
+
info(tr("Mode: uninstall plugin", "模式: 卸载插件"));
|
|
2126
|
+
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
2127
|
+
|
|
2128
|
+
const configPath = getOpenClawConfigPath();
|
|
2129
|
+
if (!existsSync(configPath)) {
|
|
2130
|
+
info(tr(
|
|
2131
|
+
"No openclaw.json found. Nothing to uninstall.",
|
|
2132
|
+
"未找到 openclaw.json,无需卸载。",
|
|
2133
|
+
));
|
|
2134
|
+
return;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
const installedState = await detectInstalledPluginState();
|
|
2138
|
+
if (installedState.generation === "none") {
|
|
2139
|
+
info(tr(
|
|
2140
|
+
"No OpenViking plugin entries found in openclaw.json. Nothing to uninstall.",
|
|
2141
|
+
"openclaw.json 中未找到 OpenViking 插件配置,无需卸载。",
|
|
2142
|
+
));
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
info(tr(
|
|
2147
|
+
`Detected installed plugin: ${formatInstalledStateLabel(installedState)}`,
|
|
2148
|
+
`检测到已安装插件: ${formatInstalledStateLabel(installedState)}`,
|
|
2149
|
+
));
|
|
2150
|
+
|
|
2151
|
+
if (!nonInteractive) {
|
|
2152
|
+
const answer = await question(
|
|
2153
|
+
tr("Confirm uninstall? (y/N)", "确认卸载?(y/N)"),
|
|
2154
|
+
"N",
|
|
2155
|
+
);
|
|
2156
|
+
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
2157
|
+
info(tr("Cancelled.", "已取消。"));
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// Step 1: Stop gateway
|
|
2163
|
+
info(tr("Step 1: Stopping OpenClaw gateway...", "步骤 1: 停止 OpenClaw gateway..."));
|
|
2164
|
+
await stopOpenClawGatewayForUpgrade();
|
|
2165
|
+
|
|
2166
|
+
// Step 2: Backup config
|
|
2167
|
+
info(tr("Step 2: Backing up configuration...", "步骤 2: 备份配置..."));
|
|
2168
|
+
const configBackupPath = await backupOpenClawConfig(configPath);
|
|
2169
|
+
info(tr(`Config backed up to: ${configBackupPath}`, `配置已备份至: ${configBackupPath}`));
|
|
2170
|
+
|
|
2171
|
+
// Step 3: Clean plugin config from openclaw.json
|
|
2172
|
+
info(tr("Step 3: Cleaning plugin configuration...", "步骤 3: 清理插件配置..."));
|
|
2173
|
+
await cleanupInstalledPluginConfig(installedState);
|
|
2174
|
+
|
|
2175
|
+
// Step 4: Backup and remove plugin directories
|
|
2176
|
+
info(tr("Step 4: Backing up plugin directories...", "步骤 4: 备份插件目录..."));
|
|
2177
|
+
const pluginBackups = [];
|
|
2178
|
+
for (const detection of installedState.detections) {
|
|
2179
|
+
const backupDir = await backupPluginDirectory(detection.variant);
|
|
2180
|
+
if (backupDir) {
|
|
2181
|
+
pluginBackups.push({ pluginId: detection.variant.id, backupDir });
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// Step 5: Remove env files
|
|
2186
|
+
info(tr("Step 5: Removing environment files...", "步骤 5: 移除环境文件..."));
|
|
2187
|
+
const envFilesToRemove = IS_WIN
|
|
2188
|
+
? [
|
|
2189
|
+
join(OPENCLAW_DIR, "openviking.env.bat"),
|
|
2190
|
+
join(OPENCLAW_DIR, "openviking.env.ps1"),
|
|
2191
|
+
]
|
|
2192
|
+
: [join(OPENCLAW_DIR, "openviking.env")];
|
|
2193
|
+
let removedEnvCount = 0;
|
|
2194
|
+
for (const f of envFilesToRemove) {
|
|
2195
|
+
if (existsSync(f)) {
|
|
2196
|
+
try {
|
|
2197
|
+
await rm(f);
|
|
2198
|
+
removedEnvCount++;
|
|
2199
|
+
info(tr(`Removed: ${f}`, `已移除: ${f}`));
|
|
2200
|
+
} catch { /* ignore */ }
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (removedEnvCount === 0) {
|
|
2204
|
+
info(tr("No environment files found.", "未找到环境文件。"));
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
// Step 6: Write uninstall audit
|
|
2208
|
+
const auditDir = getUpgradeAuditDir();
|
|
2209
|
+
await mkdir(auditDir, { recursive: true });
|
|
2210
|
+
const auditData = {
|
|
2211
|
+
operation: "uninstall",
|
|
2212
|
+
createdAt: new Date().toISOString(),
|
|
2213
|
+
fromVersion: formatInstalledStateLabel(installedState),
|
|
2214
|
+
configBackupPath,
|
|
2215
|
+
pluginBackups,
|
|
2216
|
+
};
|
|
2217
|
+
await writeUpgradeAuditFile(auditData);
|
|
2218
|
+
|
|
2219
|
+
// Done
|
|
2220
|
+
console.log("");
|
|
2221
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2222
|
+
bold(` ${tr("Uninstall complete!", "卸载完成!")}`);
|
|
2223
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2224
|
+
console.log("");
|
|
2225
|
+
|
|
2226
|
+
info(tr("OpenViking server/runtime is preserved (not uninstalled).", "OpenViking 服务端/运行时已保留(未卸载)。"));
|
|
2227
|
+
console.log("");
|
|
2228
|
+
|
|
2229
|
+
info(tr("To restore the plugin configuration:", "如需恢复插件配置:"));
|
|
2230
|
+
console.log(` 1) ${tr("Stop gateway:", "停止 gateway:")} openclaw gateway stop`);
|
|
2231
|
+
console.log(` 2) ${tr("Restore config:", "恢复配置:")} ${IS_WIN ? "copy" : "cp"} "${configBackupPath}" "${configPath}"`);
|
|
2232
|
+
for (const pb of pluginBackups) {
|
|
2233
|
+
const liveDir = join(OPENCLAW_DIR, "extensions", pb.pluginId);
|
|
2234
|
+
console.log(` 3) ${tr("Restore plugin:", "恢复插件:")} ${IS_WIN ? "move" : "mv"} "${pb.backupDir}" "${liveDir}"`);
|
|
2235
|
+
}
|
|
2236
|
+
console.log("");
|
|
2237
|
+
|
|
2238
|
+
info(tr("To reinstall:", "重新安装:"));
|
|
2239
|
+
console.log(" ov-install");
|
|
2240
|
+
console.log("");
|
|
2241
|
+
}
|
|
2242
|
+
|
|
1681
2243
|
async function main() {
|
|
1682
2244
|
console.log("");
|
|
1683
2245
|
bold(tr("🦣 OpenClaw OpenViking plugin installer", "🦣 OpenClaw OpenViking 插件安装"));
|
|
@@ -1688,6 +2250,10 @@ async function main() {
|
|
|
1688
2250
|
await printCurrentVersionInfo();
|
|
1689
2251
|
return;
|
|
1690
2252
|
}
|
|
2253
|
+
if (uninstallPlugin) {
|
|
2254
|
+
await performUninstall();
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
1691
2257
|
if (rollbackLastUpgrade) {
|
|
1692
2258
|
info(tr("Mode: rollback last plugin upgrade", "模式: 回滚最近一次插件升级"));
|
|
1693
2259
|
if (pluginVersionExplicit) {
|
|
@@ -1721,7 +2287,7 @@ async function main() {
|
|
|
1721
2287
|
|
|
1722
2288
|
await deployPluginFromRemote();
|
|
1723
2289
|
|
|
1724
|
-
await configureOpenClawPlugin(
|
|
2290
|
+
const configResult = await configureOpenClawPlugin(
|
|
1725
2291
|
upgradePluginOnly
|
|
1726
2292
|
? {
|
|
1727
2293
|
runtimeConfig: upgradeRuntimeConfig,
|
|
@@ -1729,15 +2295,23 @@ async function main() {
|
|
|
1729
2295
|
}
|
|
1730
2296
|
: { preserveExistingConfig: false },
|
|
1731
2297
|
);
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
if (
|
|
1739
|
-
|
|
1740
|
-
|
|
2298
|
+
const runtimeConfigOk = configResult?.runtimeConfigOk !== false;
|
|
2299
|
+
const runtimeConfigError = configResult?.error || "";
|
|
2300
|
+
|
|
2301
|
+
// Only mark the install as completed (state file + upgrade audit) when the
|
|
2302
|
+
// runtime config was actually applied. Plugin files are already on disk
|
|
2303
|
+
// either way, so subsequent runs can pick up from here.
|
|
2304
|
+
if (runtimeConfigOk) {
|
|
2305
|
+
await writeInstallStateFile({
|
|
2306
|
+
operation: upgradePluginOnly ? "upgrade" : "install",
|
|
2307
|
+
fromVersion: upgradeAudit?.fromVersion || "",
|
|
2308
|
+
configBackupPath: upgradeAudit?.configBackupPath || "",
|
|
2309
|
+
pluginBackups: upgradeAudit?.pluginBackups || [],
|
|
2310
|
+
});
|
|
2311
|
+
if (upgradeAudit) {
|
|
2312
|
+
upgradeAudit.completedAt = new Date().toISOString();
|
|
2313
|
+
await writeUpgradeAuditFile(upgradeAudit);
|
|
2314
|
+
}
|
|
1741
2315
|
}
|
|
1742
2316
|
let envFiles = getExistingEnvFiles();
|
|
1743
2317
|
if (!upgradePluginOnly) {
|
|
@@ -1747,18 +2321,33 @@ async function main() {
|
|
|
1747
2321
|
}
|
|
1748
2322
|
|
|
1749
2323
|
console.log("");
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
2324
|
+
if (runtimeConfigOk) {
|
|
2325
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2326
|
+
bold(` ${tr("Installation complete!", "安装完成!")}`);
|
|
2327
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2328
|
+
console.log("");
|
|
1754
2329
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
2330
|
+
if (upgradeAudit) {
|
|
2331
|
+
info(tr(`Upgrade path recorded: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`, `已记录升级路径: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`));
|
|
2332
|
+
info(tr(`Rollback config backup: ${upgradeAudit.configBackupPath}`, `回滚配置备份: ${upgradeAudit.configBackupPath}`));
|
|
2333
|
+
for (const pluginBackup of upgradeAudit.pluginBackups || []) {
|
|
2334
|
+
info(tr(`Rollback plugin backup: ${pluginBackup.backupDir}`, `回滚插件备份: ${pluginBackup.backupDir}`));
|
|
2335
|
+
}
|
|
2336
|
+
info(tr(`Rollback audit file: ${getUpgradeAuditPath()}`, `回滚审计文件: ${getUpgradeAuditPath()}`));
|
|
2337
|
+
console.log("");
|
|
1760
2338
|
}
|
|
1761
|
-
|
|
2339
|
+
} else {
|
|
2340
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2341
|
+
bold(` ${tr(
|
|
2342
|
+
"Plugin files installed, but runtime configuration was NOT applied",
|
|
2343
|
+
"插件文件已安装,但运行时配置未生效",
|
|
2344
|
+
)}`);
|
|
2345
|
+
bold(` ${tr(`Reason: ${runtimeConfigError}`, `原因: ${runtimeConfigError}`)}`);
|
|
2346
|
+
bold(` ${tr(
|
|
2347
|
+
"Re-run: openclaw openviking setup --reconfigure",
|
|
2348
|
+
"重新运行: openclaw openviking setup --reconfigure",
|
|
2349
|
+
)}`);
|
|
2350
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
1762
2351
|
console.log("");
|
|
1763
2352
|
}
|
|
1764
2353
|
|