openclaw-openviking-setup-helper 0.3.0-beta.8 → 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 +745 -155
- 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,
|
|
@@ -1419,12 +1780,9 @@ async function configureOpenClawPlugin({
|
|
|
1419
1780
|
|
|
1420
1781
|
const ocEnv = getOpenClawEnv();
|
|
1421
1782
|
const needWorkdirFlag = OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR;
|
|
1422
|
-
const workdirCmds = new Set(["config", "openviking", "status", "gateway", "logs"]);
|
|
1423
1783
|
|
|
1424
1784
|
const oc = async (args) => {
|
|
1425
|
-
const
|
|
1426
|
-
const fullArgs = useWorkdir ? ["--workdir", OPENCLAW_DIR, ...args] : args;
|
|
1427
|
-
const result = await runCapture("openclaw", fullArgs, { env: ocEnv, shell: IS_WIN });
|
|
1785
|
+
const result = await runCapture("openclaw", args, { env: ocEnv, shell: IS_WIN });
|
|
1428
1786
|
if (result.code !== 0) {
|
|
1429
1787
|
const detail = result.err || result.out;
|
|
1430
1788
|
throw new Error(`openclaw ${args.join(" ")} failed (exit code ${result.code})${detail ? `: ${detail}` : ""}`);
|
|
@@ -1432,16 +1790,61 @@ async function configureOpenClawPlugin({
|
|
|
1432
1790
|
return result;
|
|
1433
1791
|
};
|
|
1434
1792
|
|
|
1793
|
+
// Direct file manipulation for config (reliable across all OpenClaw versions and --workdir scenarios).
|
|
1794
|
+
// OpenClaw CLI's --workdir / OPENCLAW_STATE_DIR support is inconsistent, so we read/write openclaw.json directly.
|
|
1795
|
+
const configPath = getOpenClawConfigPath();
|
|
1796
|
+
const readCfg = async () => {
|
|
1797
|
+
if (!existsSync(configPath)) return {};
|
|
1798
|
+
try { return JSON.parse(await readFile(configPath, "utf8")); } catch { return {}; }
|
|
1799
|
+
};
|
|
1800
|
+
const writeCfg = async (cfg) => {
|
|
1801
|
+
const out = JSON.stringify(cfg, null, 2) + "\n";
|
|
1802
|
+
const tmp = `${configPath}.ov-install-tmp.${process.pid}`;
|
|
1803
|
+
await writeFile(tmp, out, "utf8");
|
|
1804
|
+
await rename(tmp, configPath);
|
|
1805
|
+
};
|
|
1806
|
+
const ensurePluginRegistered = async (cfg) => {
|
|
1807
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
1808
|
+
const p = cfg.plugins;
|
|
1809
|
+
if (!p.entries) p.entries = {};
|
|
1810
|
+
if (!p.entries[pluginId]) p.entries[pluginId] = {};
|
|
1811
|
+
p.entries[pluginId].enabled = true;
|
|
1812
|
+
if (!p.entries[pluginId].config) p.entries[pluginId].config = {};
|
|
1813
|
+
if (!Array.isArray(p.allow)) p.allow = [];
|
|
1814
|
+
if (!p.allow.includes(pluginId)) p.allow.push(pluginId);
|
|
1815
|
+
return cfg;
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1818
|
+
await cleanupConflictingPluginVariants();
|
|
1819
|
+
|
|
1435
1820
|
if (!preserveExistingConfig) {
|
|
1436
1821
|
await scrubStaleOpenClawPluginRegistration();
|
|
1437
1822
|
}
|
|
1438
1823
|
|
|
1439
|
-
// Enable plugin (
|
|
1440
|
-
|
|
1824
|
+
// Enable plugin: try CLI first (default path), fall back to direct file for --workdir
|
|
1825
|
+
if (!needWorkdirFlag) {
|
|
1826
|
+
try {
|
|
1827
|
+
await oc(["plugins", "enable", pluginId]);
|
|
1828
|
+
} catch (_e) {
|
|
1829
|
+
info(tr("plugins enable via CLI failed, registering directly", "CLI plugins enable 失败,直接注册"));
|
|
1830
|
+
const cfg = await readCfg();
|
|
1831
|
+
await ensurePluginRegistered(cfg);
|
|
1832
|
+
await writeCfg(cfg);
|
|
1833
|
+
}
|
|
1834
|
+
} else {
|
|
1835
|
+
info(tr("Using direct config write for non-default workdir", "非默认目录,使用直接配置写入"));
|
|
1836
|
+
const cfg = await readCfg();
|
|
1837
|
+
await ensurePluginRegistered(cfg);
|
|
1838
|
+
await writeCfg(cfg);
|
|
1839
|
+
}
|
|
1441
1840
|
|
|
1442
1841
|
if (preserveExistingConfig) {
|
|
1443
1842
|
if (claimSlot) {
|
|
1444
|
-
|
|
1843
|
+
const cfg = await readCfg();
|
|
1844
|
+
if (!cfg.plugins) cfg.plugins = {};
|
|
1845
|
+
if (!cfg.plugins.slots) cfg.plugins.slots = {};
|
|
1846
|
+
cfg.plugins.slots[pluginSlot] = pluginId;
|
|
1847
|
+
await writeCfg(cfg);
|
|
1445
1848
|
}
|
|
1446
1849
|
info(
|
|
1447
1850
|
tr(
|
|
@@ -1449,34 +1852,62 @@ async function configureOpenClawPlugin({
|
|
|
1449
1852
|
`已保留 ${pluginId} 的现有插件运行时配置`,
|
|
1450
1853
|
),
|
|
1451
1854
|
);
|
|
1452
|
-
return;
|
|
1855
|
+
return { runtimeConfigOk: true };
|
|
1453
1856
|
}
|
|
1454
1857
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1858
|
+
const writeConfigDirect = async (pluginConfig, slotValue) => {
|
|
1859
|
+
const cfg = await readCfg();
|
|
1860
|
+
await ensurePluginRegistered(cfg);
|
|
1861
|
+
Object.assign(cfg.plugins.entries[pluginId].config, pluginConfig);
|
|
1862
|
+
if (slotValue) {
|
|
1863
|
+
if (!cfg.plugins.slots) cfg.plugins.slots = {};
|
|
1864
|
+
cfg.plugins.slots[pluginSlot] = slotValue;
|
|
1461
1865
|
}
|
|
1866
|
+
await writeCfg(cfg);
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
// Legacy (memory) plugins: direct JSON write
|
|
1870
|
+
if (resolvedPluginKind === "memory") {
|
|
1462
1871
|
const effectiveRuntimeConfig = runtimeConfig || {
|
|
1463
1872
|
baseUrl: remoteBaseUrl,
|
|
1464
1873
|
apiKey: remoteApiKey,
|
|
1465
1874
|
agent_prefix: remoteAgentPrefix,
|
|
1466
1875
|
};
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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",
|
|
1892
|
+
baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
|
|
1893
|
+
targetUri: "viking://user/memories",
|
|
1894
|
+
autoRecall: true,
|
|
1895
|
+
autoCapture: true,
|
|
1896
|
+
apiKey: effectiveRuntimeConfig.apiKey || undefined,
|
|
1897
|
+
agentId: agentVal || undefined,
|
|
1470
1898
|
};
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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;
|
|
1474
1905
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
info(tr("OpenClaw plugin configured (legacy mode)", "OpenClaw
|
|
1479
|
-
return;
|
|
1906
|
+
if (!pluginConfig.baseUrl) pluginConfig.baseUrl = effectiveRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
1907
|
+
|
|
1908
|
+
await writeConfigDirect(pluginConfig, claimSlot ? pluginId : null);
|
|
1909
|
+
info(tr("OpenClaw plugin configured (legacy mode, remote)", "OpenClaw 插件配置完成(旧版模式,远程连接)"));
|
|
1910
|
+
return { runtimeConfigOk: true };
|
|
1480
1911
|
}
|
|
1481
1912
|
|
|
1482
1913
|
// Current (context-engine) plugins: delegate to `openclaw openviking setup --json`
|
|
@@ -1486,38 +1917,60 @@ async function configureOpenClawPlugin({
|
|
|
1486
1917
|
baseUrl: remoteBaseUrl,
|
|
1487
1918
|
apiKey: remoteApiKey,
|
|
1488
1919
|
agent_prefix: remoteAgentPrefix,
|
|
1920
|
+
accountId: remoteAccountId,
|
|
1921
|
+
userId: remoteUserId,
|
|
1489
1922
|
};
|
|
1490
1923
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
setupArgs.push("--agent-id", effectiveRuntimeConfig.agent_prefix);
|
|
1501
|
-
}
|
|
1502
|
-
if (claimSlot) {
|
|
1503
|
-
setupArgs.push("--force-slot");
|
|
1504
|
-
}
|
|
1505
|
-
if (installYes) {
|
|
1506
|
-
setupArgs.push("--allow-offline");
|
|
1507
|
-
}
|
|
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 */ }
|
|
1508
1933
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
1957
|
|
|
1514
|
-
|
|
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
|
+
}
|
|
1515
1970
|
|
|
1516
1971
|
let parsed = null;
|
|
1517
|
-
|
|
1518
|
-
parsed =
|
|
1519
|
-
} catch {
|
|
1520
|
-
// If JSON parse fails, fall back to checking exit code
|
|
1972
|
+
if (setupResult) {
|
|
1973
|
+
parsed = parseJsonObjectFromOutput(`${setupResult.out || ""}\n${setupResult.err || ""}`);
|
|
1521
1974
|
}
|
|
1522
1975
|
|
|
1523
1976
|
if (parsed) {
|
|
@@ -1550,76 +2003,67 @@ async function configureOpenClawPlugin({
|
|
|
1550
2003
|
`Setup failed: ${parsed.error || "unknown error"}`,
|
|
1551
2004
|
`配置失败: ${parsed.error || "未知错误"}`,
|
|
1552
2005
|
));
|
|
2006
|
+
return {
|
|
2007
|
+
runtimeConfigOk: false,
|
|
2008
|
+
error: parsed.error || parsed.action || "unknown error",
|
|
2009
|
+
};
|
|
1553
2010
|
}
|
|
1554
2011
|
}
|
|
1555
|
-
} else if (setupResult.code !== 0) {
|
|
1556
|
-
// JSON parse failed and non-zero exit
|
|
2012
|
+
} else if (setupResult && setupResult.code !== 0) {
|
|
1557
2013
|
warn(tr(
|
|
1558
|
-
`openclaw openviking setup exited with code ${setupResult.code}. Falling back to direct config.`,
|
|
1559
|
-
`openclaw openviking setup 退出码 ${setupResult.code}
|
|
2014
|
+
`openclaw openviking setup exited with code ${setupResult.code}. Falling back to direct JSON config write.`,
|
|
2015
|
+
`openclaw openviking setup 退出码 ${setupResult.code},回退到直接写入 JSON 配置。`,
|
|
1560
2016
|
));
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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));
|
|
1573
2031
|
}
|
|
1574
|
-
fallbackOk = false;
|
|
1575
|
-
warn(tr(
|
|
1576
|
-
`Could not set config field "${key}": schema validation failed (plugin version may not support this field)`,
|
|
1577
|
-
`无法设置配置字段 "${key}":schema 校验失败(插件版本可能不支持此字段)`,
|
|
1578
|
-
));
|
|
1579
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",
|
|
2039
|
+
baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
|
|
2040
|
+
apiKey: effectiveRuntimeConfig.apiKey || "",
|
|
2041
|
+
accountId: effectiveRuntimeConfig.accountId || undefined,
|
|
2042
|
+
userId: effectiveRuntimeConfig.userId || undefined,
|
|
1580
2043
|
};
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if (effectiveRuntimeConfig.apiKey) {
|
|
1584
|
-
await tryConfigSet("apiKey", effectiveRuntimeConfig.apiKey);
|
|
1585
|
-
}
|
|
1586
|
-
if (effectiveRuntimeConfig.agent_prefix) {
|
|
1587
|
-
await tryConfigSet("agent_prefix", effectiveRuntimeConfig.agent_prefix, "agentId");
|
|
1588
|
-
}
|
|
1589
|
-
if (claimSlot) {
|
|
1590
|
-
try {
|
|
1591
|
-
await oc(["config", "set", `plugins.slots.${pluginSlot}`, pluginId]);
|
|
1592
|
-
} catch (_e) {
|
|
1593
|
-
fallbackOk = false;
|
|
1594
|
-
warn(tr(
|
|
1595
|
-
"Could not activate contextEngine slot",
|
|
1596
|
-
"无法激活 contextEngine slot",
|
|
1597
|
-
));
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
if (fallbackOk) {
|
|
1601
|
-
info(tr("OpenClaw plugin configured (fallback)", "OpenClaw 插件配置完成(回退模式)"));
|
|
2044
|
+
if (useAgentPrefix) {
|
|
2045
|
+
candidates.agent_prefix = agentVal;
|
|
1602
2046
|
} else {
|
|
1603
|
-
|
|
1604
|
-
"OpenClaw plugin partially configured (fallback). Some fields could not be set. Run `openclaw openviking setup` to complete configuration.",
|
|
1605
|
-
"OpenClaw 插件部分配置完成(回退模式)。部分字段未能设置,请运行 `openclaw openviking setup` 完善配置。",
|
|
1606
|
-
));
|
|
2047
|
+
candidates.agentId = agentVal;
|
|
1607
2048
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
));
|
|
1613
|
-
|
|
1614
|
-
if (effectiveRuntimeConfig.apiKey) {
|
|
1615
|
-
info(tr(
|
|
1616
|
-
"Note: If using a root API key, run `openclaw openviking setup` to configure accountId/userId (required for tenant isolation).",
|
|
1617
|
-
"提示:如果使用的是 root API key,请运行 `openclaw openviking setup` 配置 accountId/userId(多租户隔离必需)。",
|
|
1618
|
-
));
|
|
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;
|
|
1619
2055
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
2056
|
+
if (!pluginConfig.baseUrl) pluginConfig.baseUrl = effectiveRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
2057
|
+
if (!("apiKey" in pluginConfig)) pluginConfig.apiKey = effectiveRuntimeConfig.apiKey || "";
|
|
2058
|
+
|
|
2059
|
+
await writeConfigDirect(pluginConfig, claimSlot ? pluginId : null);
|
|
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
|
+
));
|
|
1622
2064
|
}
|
|
2065
|
+
|
|
2066
|
+
return { runtimeConfigOk: true };
|
|
1623
2067
|
}
|
|
1624
2068
|
|
|
1625
2069
|
async function writeOpenvikingEnv() {
|
|
@@ -1677,6 +2121,125 @@ function getExistingEnvFiles() {
|
|
|
1677
2121
|
return existsSync(envPath) ? { shellPath: envPath } : null;
|
|
1678
2122
|
}
|
|
1679
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
|
+
|
|
1680
2243
|
async function main() {
|
|
1681
2244
|
console.log("");
|
|
1682
2245
|
bold(tr("🦣 OpenClaw OpenViking plugin installer", "🦣 OpenClaw OpenViking 插件安装"));
|
|
@@ -1687,6 +2250,10 @@ async function main() {
|
|
|
1687
2250
|
await printCurrentVersionInfo();
|
|
1688
2251
|
return;
|
|
1689
2252
|
}
|
|
2253
|
+
if (uninstallPlugin) {
|
|
2254
|
+
await performUninstall();
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
1690
2257
|
if (rollbackLastUpgrade) {
|
|
1691
2258
|
info(tr("Mode: rollback last plugin upgrade", "模式: 回滚最近一次插件升级"));
|
|
1692
2259
|
if (pluginVersionExplicit) {
|
|
@@ -1720,7 +2287,7 @@ async function main() {
|
|
|
1720
2287
|
|
|
1721
2288
|
await deployPluginFromRemote();
|
|
1722
2289
|
|
|
1723
|
-
await configureOpenClawPlugin(
|
|
2290
|
+
const configResult = await configureOpenClawPlugin(
|
|
1724
2291
|
upgradePluginOnly
|
|
1725
2292
|
? {
|
|
1726
2293
|
runtimeConfig: upgradeRuntimeConfig,
|
|
@@ -1728,15 +2295,23 @@ async function main() {
|
|
|
1728
2295
|
}
|
|
1729
2296
|
: { preserveExistingConfig: false },
|
|
1730
2297
|
);
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
if (
|
|
1738
|
-
|
|
1739
|
-
|
|
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
|
+
}
|
|
1740
2315
|
}
|
|
1741
2316
|
let envFiles = getExistingEnvFiles();
|
|
1742
2317
|
if (!upgradePluginOnly) {
|
|
@@ -1746,18 +2321,33 @@ async function main() {
|
|
|
1746
2321
|
}
|
|
1747
2322
|
|
|
1748
2323
|
console.log("");
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2324
|
+
if (runtimeConfigOk) {
|
|
2325
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2326
|
+
bold(` ${tr("Installation complete!", "安装完成!")}`);
|
|
2327
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
2328
|
+
console.log("");
|
|
1753
2329
|
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
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("");
|
|
1759
2338
|
}
|
|
1760
|
-
|
|
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("═══════════════════════════════════════════════════════════");
|
|
1761
2351
|
console.log("");
|
|
1762
2352
|
}
|
|
1763
2353
|
|