openclaw-openviking-setup-helper 0.2.9-dev.3 → 0.2.9-dev.4
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 +1004 -867
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* openclaw-openviking-install
|
|
11
11
|
*
|
|
12
12
|
* Direct run:
|
|
13
|
-
* node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ] [ --upgrade-plugin ]
|
|
13
|
+
* node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ] [ --upgrade-plugin ]
|
|
14
14
|
* [ --plugin-version=TAG ] [ --openviking-version=V ] [ --repo=PATH ]
|
|
15
15
|
*
|
|
16
16
|
* Environment variables:
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
|
-
import { cp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
26
|
+
import { cp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
27
27
|
import { existsSync, readdirSync } from "node:fs";
|
|
28
|
-
import { dirname, join, relative } from "node:path";
|
|
28
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
29
29
|
import { createInterface } from "node:readline";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
|
|
@@ -61,19 +61,32 @@ const FALLBACK_LEGACY = {
|
|
|
61
61
|
optional: ["package-lock.json", ".gitignore"],
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
64
|
+
// Must match examples/openclaw-plugin/install-manifest.json (npm only installs package deps, not these .ts files).
|
|
65
|
+
const FALLBACK_CURRENT = {
|
|
66
|
+
dir: "openclaw-plugin",
|
|
67
|
+
id: "openviking",
|
|
68
|
+
kind: "context-engine",
|
|
69
|
+
slot: "contextEngine",
|
|
70
|
+
required: ["index.ts", "config.ts", "package.json"],
|
|
71
|
+
optional: [
|
|
72
|
+
"context-engine.ts",
|
|
73
|
+
"client.ts",
|
|
74
|
+
"process-manager.ts",
|
|
75
|
+
"memory-ranking.ts",
|
|
76
|
+
"text-utils.ts",
|
|
77
|
+
"tool-call-id.ts",
|
|
78
|
+
"session-transcript-repair.ts",
|
|
79
|
+
"openclaw.plugin.json",
|
|
80
|
+
"tsconfig.json",
|
|
81
|
+
"package-lock.json",
|
|
82
|
+
".gitignore",
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const PLUGIN_VARIANTS = [
|
|
87
|
+
{ ...FALLBACK_LEGACY, generation: "legacy", slotFallback: "none" },
|
|
88
|
+
{ ...FALLBACK_CURRENT, generation: "current", slotFallback: "legacy" },
|
|
89
|
+
];
|
|
77
90
|
|
|
78
91
|
// Resolved plugin config (set by resolvePluginConfig)
|
|
79
92
|
let resolvedPluginDir = "";
|
|
@@ -81,29 +94,29 @@ let resolvedPluginId = "";
|
|
|
81
94
|
let resolvedPluginKind = "";
|
|
82
95
|
let resolvedPluginSlot = "";
|
|
83
96
|
let resolvedFilesRequired = [];
|
|
84
|
-
let resolvedFilesOptional = [];
|
|
85
|
-
let resolvedNpmOmitDev = true;
|
|
86
|
-
let resolvedMinOpenclawVersion = "";
|
|
87
|
-
let resolvedMinOpenvikingVersion = "";
|
|
88
|
-
let resolvedPluginReleaseId = "";
|
|
89
|
-
|
|
90
|
-
let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
|
|
91
|
-
let langZh = false;
|
|
92
|
-
let openvikingVersion = process.env.OPENVIKING_VERSION || "";
|
|
93
|
-
let openvikingRepo = process.env.OPENVIKING_REPO || "";
|
|
94
|
-
let workdirExplicit = false;
|
|
95
|
-
let upgradePluginOnly = false;
|
|
96
|
-
let rollbackLastUpgrade = false;
|
|
97
|
+
let resolvedFilesOptional = [];
|
|
98
|
+
let resolvedNpmOmitDev = true;
|
|
99
|
+
let resolvedMinOpenclawVersion = "";
|
|
100
|
+
let resolvedMinOpenvikingVersion = "";
|
|
101
|
+
let resolvedPluginReleaseId = "";
|
|
102
|
+
|
|
103
|
+
let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
|
|
104
|
+
let langZh = false;
|
|
105
|
+
let openvikingVersion = process.env.OPENVIKING_VERSION || "";
|
|
106
|
+
let openvikingRepo = process.env.OPENVIKING_REPO || "";
|
|
107
|
+
let workdirExplicit = false;
|
|
108
|
+
let upgradePluginOnly = false;
|
|
109
|
+
let rollbackLastUpgrade = false;
|
|
97
110
|
|
|
98
111
|
let selectedMode = "local";
|
|
99
112
|
let selectedServerPort = DEFAULT_SERVER_PORT;
|
|
100
|
-
let remoteBaseUrl = "http://127.0.0.1:1933";
|
|
101
|
-
let remoteApiKey = "";
|
|
102
|
-
let remoteAgentId = "";
|
|
103
|
-
let openvikingPythonPath = "";
|
|
104
|
-
let upgradeRuntimeConfig = null;
|
|
105
|
-
let installedUpgradeState = null;
|
|
106
|
-
let upgradeAudit = null;
|
|
113
|
+
let remoteBaseUrl = "http://127.0.0.1:1933";
|
|
114
|
+
let remoteApiKey = "";
|
|
115
|
+
let remoteAgentId = "";
|
|
116
|
+
let openvikingPythonPath = "";
|
|
117
|
+
let upgradeRuntimeConfig = null;
|
|
118
|
+
let installedUpgradeState = null;
|
|
119
|
+
let upgradeAudit = null;
|
|
107
120
|
|
|
108
121
|
const argv = process.argv.slice(2);
|
|
109
122
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -112,19 +125,19 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
112
125
|
installYes = true;
|
|
113
126
|
continue;
|
|
114
127
|
}
|
|
115
|
-
if (arg === "--zh") {
|
|
116
|
-
langZh = true;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (arg === "--upgrade-plugin" || arg === "--update" || arg === "--upgrade") {
|
|
120
|
-
upgradePluginOnly = true;
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (arg === "--rollback" || arg === "--rollback-last-upgrade") {
|
|
124
|
-
rollbackLastUpgrade = true;
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
if (arg === "--workdir") {
|
|
128
|
+
if (arg === "--zh") {
|
|
129
|
+
langZh = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--upgrade-plugin" || arg === "--update" || arg === "--upgrade") {
|
|
133
|
+
upgradePluginOnly = true;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (arg === "--rollback" || arg === "--rollback-last-upgrade") {
|
|
137
|
+
rollbackLastUpgrade = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (arg === "--workdir") {
|
|
128
141
|
const workdir = argv[i + 1]?.trim();
|
|
129
142
|
if (!workdir) {
|
|
130
143
|
console.error("--workdir requires a path");
|
|
@@ -197,16 +210,16 @@ function printHelp() {
|
|
|
197
210
|
console.log("Options:");
|
|
198
211
|
console.log(" --github-repo=OWNER/REPO GitHub repository (default: volcengine/OpenViking)");
|
|
199
212
|
console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: main)");
|
|
200
|
-
console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)");
|
|
201
|
-
console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
|
|
202
|
-
console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)");
|
|
203
|
-
console.log(" --update, --upgrade-plugin");
|
|
204
|
-
console.log("
|
|
205
|
-
console.log(" --rollback, --rollback-last-upgrade");
|
|
206
|
-
console.log(" Roll back the last plugin upgrade using the saved audit/backup files");
|
|
207
|
-
console.log(" -y, --yes Non-interactive (use defaults)");
|
|
208
|
-
console.log(" --zh Chinese prompts");
|
|
209
|
-
console.log(" -h, --help This help");
|
|
213
|
+
console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)");
|
|
214
|
+
console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
|
|
215
|
+
console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)");
|
|
216
|
+
console.log(" --update, --upgrade-plugin");
|
|
217
|
+
console.log(" Upgrade only the plugin to the requested --plugin-version; keep ov.conf and do not change the OpenViking service");
|
|
218
|
+
console.log(" --rollback, --rollback-last-upgrade");
|
|
219
|
+
console.log(" Roll back the last plugin upgrade using the saved audit/backup files");
|
|
220
|
+
console.log(" -y, --yes Non-interactive (use defaults)");
|
|
221
|
+
console.log(" --zh Chinese prompts");
|
|
222
|
+
console.log(" -h, --help This help");
|
|
210
223
|
console.log("");
|
|
211
224
|
console.log("Examples:");
|
|
212
225
|
console.log(" # Install latest version");
|
|
@@ -214,18 +227,50 @@ function printHelp() {
|
|
|
214
227
|
console.log("");
|
|
215
228
|
console.log(" # Install from a fork repository");
|
|
216
229
|
console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
|
|
217
|
-
console.log("");
|
|
218
|
-
console.log(" # Install specific plugin version");
|
|
219
|
-
console.log(" node install.js --plugin-version=v0.2.8");
|
|
220
|
-
console.log("");
|
|
221
|
-
console.log(" # Upgrade only the plugin files");
|
|
222
|
-
console.log(" node install.js --update --plugin-version=main");
|
|
223
|
-
console.log("");
|
|
224
|
-
console.log(" # Roll back the last plugin upgrade");
|
|
225
|
-
console.log(" node install.js --rollback");
|
|
226
|
-
console.log("");
|
|
227
|
-
console.log("Env: REPO, PLUGIN_VERSION, OPENVIKING_VERSION, SKIP_OPENCLAW, SKIP_OPENVIKING, NPM_REGISTRY, PIP_INDEX_URL");
|
|
228
|
-
}
|
|
230
|
+
console.log("");
|
|
231
|
+
console.log(" # Install specific plugin version");
|
|
232
|
+
console.log(" node install.js --plugin-version=v0.2.8");
|
|
233
|
+
console.log("");
|
|
234
|
+
console.log(" # Upgrade only the plugin files");
|
|
235
|
+
console.log(" node install.js --update --plugin-version=main");
|
|
236
|
+
console.log("");
|
|
237
|
+
console.log(" # Roll back the last plugin upgrade");
|
|
238
|
+
console.log(" node install.js --rollback");
|
|
239
|
+
console.log("");
|
|
240
|
+
console.log("Env: REPO, PLUGIN_VERSION, OPENVIKING_VERSION, SKIP_OPENCLAW, SKIP_OPENVIKING, NPM_REGISTRY, PIP_INDEX_URL");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function formatCliArg(value) {
|
|
244
|
+
if (!value) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
return /[\s"]/u.test(value) ? JSON.stringify(value) : value;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getLegacyInstallCommandHint() {
|
|
251
|
+
const override = process.env.OPENVIKING_INSTALL_LEGACY_HINT?.trim();
|
|
252
|
+
if (override) {
|
|
253
|
+
return override;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const invokedScript = process.argv[1] ? basename(process.argv[1]) : "";
|
|
257
|
+
const args = ["--plugin-version", "<legacy-version>"];
|
|
258
|
+
if (workdirExplicit || OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
|
|
259
|
+
args.push("--workdir", formatCliArg(OPENCLAW_DIR));
|
|
260
|
+
}
|
|
261
|
+
if (REPO !== "volcengine/OpenViking") {
|
|
262
|
+
args.push("--github-repo", formatCliArg(REPO));
|
|
263
|
+
}
|
|
264
|
+
if (langZh) {
|
|
265
|
+
args.push("--zh");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (invokedScript === "install.js") {
|
|
269
|
+
return `node install.js ${args.join(" ")}`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return `ov-install ${args.join(" ")}`;
|
|
273
|
+
}
|
|
229
274
|
|
|
230
275
|
function tr(en, zh) {
|
|
231
276
|
return langZh ? zh : en;
|
|
@@ -506,25 +551,37 @@ function versionGte(v1, v2) {
|
|
|
506
551
|
return a3 >= b3;
|
|
507
552
|
}
|
|
508
553
|
|
|
509
|
-
function isSemverLike(value) {
|
|
510
|
-
return /^v?\d+(\.\d+){1,2}$/.test(value);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function validateRequestedPluginVersion() {
|
|
514
|
-
if (!isSemverLike(PLUGIN_VERSION)) return;
|
|
515
|
-
if (versionGte(PLUGIN_VERSION, "v0.2.7") && !versionGte(PLUGIN_VERSION, "v0.2.8")) {
|
|
516
|
-
err(tr("Plugin version v0.2.7 does not exist.", "插件版本 v0.2.7 不存在。"));
|
|
517
|
-
process.exit(1);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
if (upgradePluginOnly && rollbackLastUpgrade) {
|
|
522
|
-
console.error("--update/--upgrade-plugin and --rollback cannot be used together");
|
|
523
|
-
process.exit(1);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
554
|
+
function isSemverLike(value) {
|
|
555
|
+
return /^v?\d+(\.\d+){1,2}$/.test(value);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function validateRequestedPluginVersion() {
|
|
559
|
+
if (!isSemverLike(PLUGIN_VERSION)) return;
|
|
560
|
+
if (versionGte(PLUGIN_VERSION, "v0.2.7") && !versionGte(PLUGIN_VERSION, "v0.2.8")) {
|
|
561
|
+
err(tr("Plugin version v0.2.7 does not exist.", "插件版本 v0.2.7 不存在。"));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (upgradePluginOnly && rollbackLastUpgrade) {
|
|
567
|
+
console.error("--update/--upgrade-plugin and --rollback cannot be used together");
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function ensurePluginOnlyOperationArgs() {
|
|
572
|
+
if ((upgradePluginOnly || rollbackLastUpgrade) && openvikingVersion) {
|
|
573
|
+
err(
|
|
574
|
+
tr(
|
|
575
|
+
"Plugin-only upgrade/rollback does not support --openviking-version. Use --plugin-version to choose the plugin release, and run a full install if you need to change the OpenViking service version.",
|
|
576
|
+
"仅插件升级或回滚不支持 --openviking-version。请使用 --plugin-version 指定插件版本;如果需要调整 OpenViking 服务版本,请执行完整安装流程。",
|
|
577
|
+
),
|
|
578
|
+
);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Detect OpenClaw version
|
|
584
|
+
async function detectOpenClawVersion() {
|
|
528
585
|
try {
|
|
529
586
|
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
530
587
|
if (result.code === 0 && result.out) {
|
|
@@ -598,31 +655,31 @@ async function resolvePluginConfig() {
|
|
|
598
655
|
}
|
|
599
656
|
}
|
|
600
657
|
|
|
601
|
-
resolvedPluginDir = pluginDir;
|
|
602
|
-
resolvedPluginReleaseId = "";
|
|
603
|
-
|
|
604
|
-
if (manifestData) {
|
|
605
|
-
resolvedPluginId = manifestData.plugin?.id || "";
|
|
606
|
-
resolvedPluginKind = manifestData.plugin?.kind || "";
|
|
607
|
-
resolvedPluginSlot = manifestData.plugin?.slot || "";
|
|
608
|
-
resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
|
|
609
|
-
resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
|
|
610
|
-
resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
|
|
611
|
-
resolvedNpmOmitDev = manifestData.npm?.omitDev !== false;
|
|
612
|
-
resolvedFilesRequired = manifestData.files?.required || [];
|
|
613
|
-
resolvedFilesOptional = manifestData.files?.optional || [];
|
|
614
|
-
} else {
|
|
658
|
+
resolvedPluginDir = pluginDir;
|
|
659
|
+
resolvedPluginReleaseId = "";
|
|
660
|
+
|
|
661
|
+
if (manifestData) {
|
|
662
|
+
resolvedPluginId = manifestData.plugin?.id || "";
|
|
663
|
+
resolvedPluginKind = manifestData.plugin?.kind || "";
|
|
664
|
+
resolvedPluginSlot = manifestData.plugin?.slot || "";
|
|
665
|
+
resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
|
|
666
|
+
resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
|
|
667
|
+
resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
|
|
668
|
+
resolvedNpmOmitDev = manifestData.npm?.omitDev !== false;
|
|
669
|
+
resolvedFilesRequired = manifestData.files?.required || [];
|
|
670
|
+
resolvedFilesOptional = manifestData.files?.optional || [];
|
|
671
|
+
} else {
|
|
615
672
|
// No manifest — determine plugin identity by package.json name
|
|
616
673
|
let fallbackKey = pluginDir === "openclaw-memory-plugin" ? "legacy" : "current";
|
|
617
674
|
let compatVer = "";
|
|
618
675
|
|
|
619
676
|
const pkgJson = await tryFetch(`${ghRaw}/examples/${pluginDir}/package.json`);
|
|
620
677
|
if (pkgJson) {
|
|
621
|
-
try {
|
|
622
|
-
const pkg = JSON.parse(pkgJson);
|
|
623
|
-
const pkgName = pkg.name || "";
|
|
624
|
-
resolvedPluginReleaseId = pkg.version || "";
|
|
625
|
-
if (pkgName && pkgName !== "@openclaw/openviking") {
|
|
678
|
+
try {
|
|
679
|
+
const pkg = JSON.parse(pkgJson);
|
|
680
|
+
const pkgName = pkg.name || "";
|
|
681
|
+
resolvedPluginReleaseId = pkg.version || "";
|
|
682
|
+
if (pkgName && pkgName !== "@openclaw/openviking") {
|
|
626
683
|
fallbackKey = "legacy";
|
|
627
684
|
info(tr(`Detected legacy plugin by package name: ${pkgName}`, `通过 package.json 名称检测到旧版插件: ${pkgName}`));
|
|
628
685
|
} else if (pkgName) {
|
|
@@ -684,9 +741,9 @@ async function checkOpenClawCompatibility() {
|
|
|
684
741
|
}
|
|
685
742
|
|
|
686
743
|
// If user explicitly requested an old version, pass
|
|
687
|
-
if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.8")) {
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
744
|
+
if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.8")) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
690
747
|
|
|
691
748
|
// Check compatibility
|
|
692
749
|
if (!versionGte(ocVersion, resolvedMinOpenclawVersion)) {
|
|
@@ -700,8 +757,8 @@ async function checkOpenClawCompatibility() {
|
|
|
700
757
|
console.log(` ${tr("Option 1: Upgrade OpenClaw", "方案 1:升级 OpenClaw")}`);
|
|
701
758
|
console.log(` npm update -g openclaw --registry ${NPM_REGISTRY}`);
|
|
702
759
|
console.log("");
|
|
703
|
-
console.log(` ${tr("Option 2: Install a legacy plugin release compatible with your current OpenClaw version", "方案 2:安装与当前 OpenClaw 版本兼容的旧版插件")}`);
|
|
704
|
-
console.log(`
|
|
760
|
+
console.log(` ${tr("Option 2: Install a legacy plugin release compatible with your current OpenClaw version", "方案 2:安装与当前 OpenClaw 版本兼容的旧版插件")}`);
|
|
761
|
+
console.log(` ${getLegacyInstallCommandHint()}`);
|
|
705
762
|
console.log("");
|
|
706
763
|
process.exit(1);
|
|
707
764
|
}
|
|
@@ -928,554 +985,573 @@ async function configureOvConf() {
|
|
|
928
985
|
info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
|
|
929
986
|
}
|
|
930
987
|
|
|
931
|
-
function getOpenClawConfigPath() {
|
|
932
|
-
return join(OPENCLAW_DIR, "openclaw.json");
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
function getOpenClawEnv() {
|
|
936
|
-
if (OPENCLAW_DIR === DEFAULT_OPENCLAW_DIR) {
|
|
937
|
-
return { ...process.env };
|
|
938
|
-
}
|
|
939
|
-
return { ...process.env, OPENCLAW_STATE_DIR: OPENCLAW_DIR };
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
async function readJsonFileIfExists(filePath) {
|
|
943
|
-
if (!existsSync(filePath)) return null;
|
|
944
|
-
const raw = await readFile(filePath, "utf8");
|
|
945
|
-
return JSON.parse(raw);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function getInstallStatePathForPlugin(pluginId) {
|
|
949
|
-
return join(OPENCLAW_DIR, "extensions", pluginId, ".ov-install-state.json");
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
function getUpgradeAuditDir() {
|
|
953
|
-
return join(OPENCLAW_DIR, ".openviking-upgrade-backup");
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function getUpgradeAuditPath() {
|
|
957
|
-
return join(getUpgradeAuditDir(), "last-upgrade.json");
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function getOpenClawConfigBackupPath() {
|
|
961
|
-
return join(getUpgradeAuditDir(), "openclaw.json.bak");
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
function normalizePluginMode(value) {
|
|
965
|
-
return value === "remote" ? "remote" : "local";
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
function getPluginVariantById(pluginId) {
|
|
969
|
-
return PLUGIN_VARIANTS.find((variant) => variant.id === pluginId) || null;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
function detectPluginPresence(config, variant) {
|
|
973
|
-
const plugins = config?.plugins;
|
|
974
|
-
const reasons = [];
|
|
975
|
-
if (!plugins) {
|
|
976
|
-
return { variant, present: false, reasons };
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (plugins.entries && Object.prototype.hasOwnProperty.call(plugins.entries, variant.id)) {
|
|
980
|
-
reasons.push("entry");
|
|
981
|
-
}
|
|
982
|
-
if (plugins.slots?.[variant.slot] === variant.id) {
|
|
983
|
-
reasons.push("slot");
|
|
984
|
-
}
|
|
985
|
-
if (Array.isArray(plugins.allow) && plugins.allow.includes(variant.id)) {
|
|
986
|
-
reasons.push("allow");
|
|
987
|
-
}
|
|
988
|
-
if (
|
|
989
|
-
Array.isArray(plugins.load?.paths)
|
|
990
|
-
&& plugins.load.paths.some((item) => typeof item === "string" && (item.includes(variant.id) || item.includes(variant.dir)))
|
|
991
|
-
) {
|
|
992
|
-
reasons.push("loadPath");
|
|
993
|
-
}
|
|
994
|
-
if (existsSync(join(OPENCLAW_DIR, "extensions", variant.id))) {
|
|
995
|
-
reasons.push("dir");
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return { variant, present: reasons.length > 0, reasons };
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
async function detectInstalledPluginState() {
|
|
1002
|
-
const configPath = getOpenClawConfigPath();
|
|
1003
|
-
const config = await readJsonFileIfExists(configPath);
|
|
1004
|
-
const detections = [];
|
|
1005
|
-
for (const variant of PLUGIN_VARIANTS) {
|
|
1006
|
-
const detection = detectPluginPresence(config, variant);
|
|
1007
|
-
if (!detection.present) continue;
|
|
1008
|
-
detection.installState = await readJsonFileIfExists(getInstallStatePathForPlugin(variant.id));
|
|
1009
|
-
detections.push(detection);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
let generation = "none";
|
|
1013
|
-
if (detections.length === 1) {
|
|
1014
|
-
generation = detections[0].variant.generation;
|
|
1015
|
-
} else if (detections.length > 1) {
|
|
1016
|
-
generation = "mixed";
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
return {
|
|
1020
|
-
config,
|
|
1021
|
-
configPath,
|
|
1022
|
-
detections,
|
|
1023
|
-
generation,
|
|
1024
|
-
};
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
function formatInstalledDetectionLabel(detection) {
|
|
1028
|
-
const requestedRef = detection.installState?.requestedRef;
|
|
1029
|
-
const releaseId = detection.installState?.releaseId;
|
|
1030
|
-
if (requestedRef) return `${detection.variant.id}@${requestedRef}`;
|
|
1031
|
-
if (releaseId) return `${detection.variant.id}#${releaseId}`;
|
|
1032
|
-
return `${detection.variant.id} (${detection.variant.generation}, exact version unknown)`;
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
function formatInstalledStateLabel(installedState) {
|
|
1036
|
-
if (!installedState?.detections?.length) {
|
|
1037
|
-
return "not-installed";
|
|
1038
|
-
}
|
|
1039
|
-
return installedState.detections.map(formatInstalledDetectionLabel).join(" + ");
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
function formatTargetVersionLabel() {
|
|
1043
|
-
const base = `${resolvedPluginId || "openviking"}@${PLUGIN_VERSION}`;
|
|
1044
|
-
if (resolvedPluginReleaseId && resolvedPluginReleaseId !== PLUGIN_VERSION) {
|
|
1045
|
-
return `${base} (${resolvedPluginReleaseId})`;
|
|
1046
|
-
}
|
|
1047
|
-
return base;
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
function extractRuntimeConfigFromPluginEntry(entryConfig) {
|
|
1051
|
-
if (!entryConfig || typeof entryConfig !== "object") return null;
|
|
1052
|
-
|
|
1053
|
-
const mode = normalizePluginMode(entryConfig.mode);
|
|
1054
|
-
const runtime = { mode };
|
|
1055
|
-
|
|
1056
|
-
if (mode === "remote") {
|
|
1057
|
-
if (typeof entryConfig.baseUrl === "string" && entryConfig.baseUrl.trim()) {
|
|
1058
|
-
runtime.baseUrl = entryConfig.baseUrl.trim();
|
|
1059
|
-
}
|
|
1060
|
-
if (typeof entryConfig.apiKey === "string" && entryConfig.apiKey.trim()) {
|
|
1061
|
-
runtime.apiKey = entryConfig.apiKey;
|
|
1062
|
-
}
|
|
1063
|
-
if (typeof entryConfig.agentId === "string" && entryConfig.agentId.trim()) {
|
|
1064
|
-
runtime.agentId = entryConfig.agentId.trim();
|
|
1065
|
-
}
|
|
1066
|
-
return runtime;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (typeof entryConfig.configPath === "string" && entryConfig.configPath.trim()) {
|
|
1070
|
-
runtime.configPath = entryConfig.configPath.trim();
|
|
1071
|
-
}
|
|
1072
|
-
if (entryConfig.port !== undefined && entryConfig.port !== null && `${entryConfig.port}`.trim()) {
|
|
1073
|
-
const parsedPort = Number.parseInt(String(entryConfig.port), 10);
|
|
1074
|
-
if (Number.isFinite(parsedPort) && parsedPort > 0) {
|
|
1075
|
-
runtime.port = parsedPort;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return runtime;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
async function readPortFromOvConf(configPath) {
|
|
1082
|
-
const filePath = configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1083
|
-
if (!existsSync(filePath)) return null;
|
|
1084
|
-
try {
|
|
1085
|
-
const ovConf = await readJsonFileIfExists(filePath);
|
|
1086
|
-
const parsedPort = Number.parseInt(String(ovConf?.server?.port ?? ""), 10);
|
|
1087
|
-
return Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : null;
|
|
1088
|
-
} catch {
|
|
1089
|
-
return null;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
async function backupOpenClawConfig(configPath) {
|
|
1094
|
-
await mkdir(getUpgradeAuditDir(), { recursive: true });
|
|
1095
|
-
const backupPath = getOpenClawConfigBackupPath();
|
|
1096
|
-
const configText = await readFile(configPath, "utf8");
|
|
1097
|
-
await writeFile(backupPath, configText, "utf8");
|
|
1098
|
-
return backupPath;
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
async function writeUpgradeAuditFile(data) {
|
|
1102
|
-
await mkdir(getUpgradeAuditDir(), { recursive: true });
|
|
1103
|
-
await writeFile(getUpgradeAuditPath(), `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async function writeInstallStateFile({ operation, fromVersion, configBackupPath, pluginBackups }) {
|
|
1107
|
-
const installStatePath = getInstallStatePathForPlugin(resolvedPluginId || "openviking");
|
|
1108
|
-
const state = {
|
|
1109
|
-
pluginId: resolvedPluginId || "openviking",
|
|
1110
|
-
generation: getPluginVariantById(resolvedPluginId || "openviking")?.generation || "unknown",
|
|
1111
|
-
requestedRef: PLUGIN_VERSION,
|
|
1112
|
-
releaseId: resolvedPluginReleaseId || "",
|
|
1113
|
-
operation,
|
|
1114
|
-
fromVersion: fromVersion || "",
|
|
1115
|
-
configBackupPath: configBackupPath || "",
|
|
1116
|
-
pluginBackups: pluginBackups || [],
|
|
1117
|
-
installedAt: new Date().toISOString(),
|
|
1118
|
-
repo: REPO,
|
|
1119
|
-
};
|
|
1120
|
-
await writeFile(installStatePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
async function moveDirWithFallback(sourceDir, destDir) {
|
|
1124
|
-
try {
|
|
1125
|
-
await rename(sourceDir, destDir);
|
|
1126
|
-
} catch {
|
|
1127
|
-
await cp(sourceDir, destDir, { recursive: true, force: true });
|
|
1128
|
-
await rm(sourceDir, { recursive: true, force: true });
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
async function rollbackLastUpgradeOperation() {
|
|
1133
|
-
const auditPath = getUpgradeAuditPath();
|
|
1134
|
-
const audit = await readJsonFileIfExists(auditPath);
|
|
1135
|
-
if (!audit) {
|
|
1136
|
-
err(
|
|
1137
|
-
tr(
|
|
1138
|
-
`No rollback audit file found at ${auditPath}.`,
|
|
1139
|
-
`未找到回滚审计文件: ${auditPath}`,
|
|
1140
|
-
),
|
|
1141
|
-
);
|
|
1142
|
-
process.exit(1);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
if (audit.rolledBackAt) {
|
|
1146
|
-
warn(
|
|
1147
|
-
tr(
|
|
1148
|
-
`The last recorded upgrade was already rolled back at ${audit.rolledBackAt}.`,
|
|
1149
|
-
`最近一次升级已在 ${audit.rolledBackAt} 回滚。`,
|
|
1150
|
-
),
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
const configBackupPath = audit.configBackupPath || getOpenClawConfigBackupPath();
|
|
1155
|
-
if (!existsSync(configBackupPath)) {
|
|
1156
|
-
err(
|
|
1157
|
-
tr(
|
|
1158
|
-
`Rollback config backup is missing: ${configBackupPath}`,
|
|
1159
|
-
`回滚配置备份缺失: ${configBackupPath}`,
|
|
1160
|
-
),
|
|
1161
|
-
);
|
|
1162
|
-
process.exit(1);
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
const pluginBackups = Array.isArray(audit.pluginBackups) ? audit.pluginBackups : [];
|
|
1166
|
-
if (pluginBackups.length === 0) {
|
|
1167
|
-
err(tr("Rollback audit file contains no plugin backups.", "回滚审计文件中没有插件备份信息。"));
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
1170
|
-
for (const pluginBackup of pluginBackups) {
|
|
1171
|
-
if (!pluginBackup?.pluginId || !pluginBackup?.backupDir || !existsSync(pluginBackup.backupDir)) {
|
|
1172
|
-
err(
|
|
1173
|
-
tr(
|
|
1174
|
-
`Rollback plugin backup is missing: ${pluginBackup?.backupDir || "<unknown>"}`,
|
|
1175
|
-
`回滚插件备份缺失: ${pluginBackup?.backupDir || "<unknown>"}`,
|
|
1176
|
-
),
|
|
1177
|
-
);
|
|
1178
|
-
process.exit(1);
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
info(tr(`Rolling back last upgrade: ${audit.fromVersion || "unknown"} <- ${audit.toVersion || "unknown"}`, `开始回滚最近一次升级: ${audit.fromVersion || "unknown"} <- ${audit.toVersion || "unknown"}`));
|
|
1183
|
-
await stopOpenClawGatewayForUpgrade();
|
|
1184
|
-
|
|
1185
|
-
const configText = await readFile(configBackupPath, "utf8");
|
|
1186
|
-
await writeFile(getOpenClawConfigPath(), configText, "utf8");
|
|
1187
|
-
info(tr(`Restored openclaw.json from backup: ${configBackupPath}`, `已从备份恢复 openclaw.json: ${configBackupPath}`));
|
|
1188
|
-
|
|
1189
|
-
const extensionsDir = join(OPENCLAW_DIR, "extensions");
|
|
1190
|
-
await mkdir(extensionsDir, { recursive: true });
|
|
1191
|
-
for (const variant of PLUGIN_VARIANTS) {
|
|
1192
|
-
const liveDir = join(extensionsDir, variant.id);
|
|
1193
|
-
if (existsSync(liveDir)) {
|
|
1194
|
-
await rm(liveDir, { recursive: true, force: true });
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
for (const pluginBackup of pluginBackups) {
|
|
1199
|
-
if (!pluginBackup?.pluginId || !pluginBackup?.backupDir) continue;
|
|
1200
|
-
if (!existsSync(pluginBackup.backupDir)) {
|
|
1201
|
-
err(
|
|
1202
|
-
tr(
|
|
1203
|
-
`Rollback plugin backup is missing: ${pluginBackup.backupDir}`,
|
|
1204
|
-
`回滚插件备份缺失: ${pluginBackup.backupDir}`,
|
|
1205
|
-
),
|
|
1206
|
-
);
|
|
1207
|
-
process.exit(1);
|
|
1208
|
-
}
|
|
1209
|
-
const destDir = join(extensionsDir, pluginBackup.pluginId);
|
|
1210
|
-
await moveDirWithFallback(pluginBackup.backupDir, destDir);
|
|
1211
|
-
info(tr(`Restored plugin directory: ${destDir}`, `已恢复插件目录: ${destDir}`));
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
audit.rolledBackAt = new Date().toISOString();
|
|
1215
|
-
audit.rollbackConfigPath = configBackupPath;
|
|
1216
|
-
await writeUpgradeAuditFile(audit);
|
|
1217
|
-
|
|
1218
|
-
console.log("");
|
|
1219
|
-
bold(tr("Rollback complete!", "回滚完成!"));
|
|
1220
|
-
console.log("");
|
|
1221
|
-
info(tr(`Rollback audit file: ${auditPath}`, `回滚审计文件: ${auditPath}`));
|
|
1222
|
-
info(tr("Run `openclaw gateway` and `openclaw status` to verify the restored plugin state.", "请运行 `openclaw gateway` 和 `openclaw status` 验证恢复后的插件状态。"));
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
async function prepareUpgradeRuntimeConfig(installedState) {
|
|
1226
|
-
const plugins = installedState.config?.plugins ?? {};
|
|
1227
|
-
const candidateOrder = installedState.detections
|
|
1228
|
-
.map((item) => item.variant)
|
|
1229
|
-
.sort((left, right) => (right.generation === "current" ? 1 : 0) - (left.generation === "current" ? 1 : 0));
|
|
1230
|
-
|
|
1231
|
-
let runtime = null;
|
|
1232
|
-
for (const variant of candidateOrder) {
|
|
1233
|
-
const entryConfig = extractRuntimeConfigFromPluginEntry(plugins.entries?.[variant.id]?.config);
|
|
1234
|
-
if (entryConfig) {
|
|
1235
|
-
runtime = entryConfig;
|
|
1236
|
-
break;
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (!runtime) {
|
|
1241
|
-
runtime = { mode: "local" };
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
if (runtime.mode === "remote") {
|
|
1245
|
-
runtime.baseUrl = runtime.baseUrl || remoteBaseUrl;
|
|
1246
|
-
return runtime;
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
runtime.configPath = runtime.configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1250
|
-
runtime.port = runtime.port || await readPortFromOvConf(runtime.configPath) || DEFAULT_SERVER_PORT;
|
|
1251
|
-
return runtime;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
function removePluginConfig(config, variant) {
|
|
1255
|
-
const plugins = config?.plugins;
|
|
1256
|
-
if (!plugins) return false;
|
|
1257
|
-
|
|
1258
|
-
let changed = false;
|
|
1259
|
-
|
|
1260
|
-
if (Array.isArray(plugins.allow)) {
|
|
1261
|
-
const nextAllow = plugins.allow.filter((item) => item !== variant.id);
|
|
1262
|
-
changed = changed || nextAllow.length !== plugins.allow.length;
|
|
1263
|
-
plugins.allow = nextAllow;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
if (Array.isArray(plugins.load?.paths)) {
|
|
1267
|
-
const nextPaths = plugins.load.paths.filter(
|
|
1268
|
-
(item) => typeof item !== "string" || (!item.includes(variant.id) && !item.includes(variant.dir)),
|
|
1269
|
-
);
|
|
1270
|
-
changed = changed || nextPaths.length !== plugins.load.paths.length;
|
|
1271
|
-
plugins.load.paths = nextPaths;
|
|
1272
|
-
}
|
|
1273
|
-
|
|
1274
|
-
if (plugins.entries && Object.prototype.hasOwnProperty.call(plugins.entries, variant.id)) {
|
|
1275
|
-
delete plugins.entries[variant.id];
|
|
1276
|
-
changed = true;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
if (plugins.slots?.[variant.slot] === variant.id) {
|
|
1280
|
-
plugins.slots[variant.slot] = variant.slotFallback;
|
|
1281
|
-
changed = true;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
return changed;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
async function prunePreviousUpgradeBackups(disabledDir, variant, keepDir) {
|
|
1288
|
-
if (!existsSync(disabledDir)) return;
|
|
1289
|
-
|
|
1290
|
-
const prefix = `${variant.id}-upgrade-backup-`;
|
|
1291
|
-
const keepName = keepDir ? keepDir.split(/[\\/]/).pop() : "";
|
|
1292
|
-
const entries = readdirSync(disabledDir, { withFileTypes: true });
|
|
1293
|
-
for (const entry of entries) {
|
|
1294
|
-
if (!entry.isDirectory()) continue;
|
|
1295
|
-
if (!entry.name.startsWith(prefix)) continue;
|
|
1296
|
-
if (keepName && entry.name === keepName) continue;
|
|
1297
|
-
await rm(join(disabledDir, entry.name), { recursive: true, force: true });
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
async function backupPluginDirectory(variant) {
|
|
1302
|
-
const pluginDir = join(OPENCLAW_DIR, "extensions", variant.id);
|
|
1303
|
-
if (!existsSync(pluginDir)) return null;
|
|
1304
|
-
|
|
1305
|
-
const disabledDir = join(OPENCLAW_DIR, "disabled-extensions");
|
|
1306
|
-
const backupDir = join(disabledDir, `${variant.id}-upgrade-backup-${Date.now()}`);
|
|
1307
|
-
await mkdir(disabledDir, { recursive: true });
|
|
1308
|
-
try {
|
|
1309
|
-
await rename(pluginDir, backupDir);
|
|
1310
|
-
} catch {
|
|
1311
|
-
await cp(pluginDir, backupDir, { recursive: true, force: true });
|
|
1312
|
-
await rm(pluginDir, { recursive: true, force: true });
|
|
1313
|
-
}
|
|
1314
|
-
info(tr(`Backed up plugin directory: ${backupDir}`, `已备份插件目录: ${backupDir}`));
|
|
1315
|
-
await prunePreviousUpgradeBackups(disabledDir, variant, backupDir);
|
|
1316
|
-
return backupDir;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
async function stopOpenClawGatewayForUpgrade() {
|
|
1320
|
-
const result = await runCapture("openclaw", ["gateway", "stop"], {
|
|
1321
|
-
env: getOpenClawEnv(),
|
|
1322
|
-
shell: IS_WIN,
|
|
1323
|
-
});
|
|
1324
|
-
if (result.code === 0) {
|
|
1325
|
-
info(tr("Stopped OpenClaw gateway before plugin upgrade", "升级插件前已停止 OpenClaw gateway"));
|
|
1326
|
-
} else {
|
|
1327
|
-
warn(tr("OpenClaw gateway may not be running; continuing", "OpenClaw gateway 可能未在运行,继续执行"));
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function shouldClaimTargetSlot(installedState) {
|
|
1332
|
-
const currentOwner = installedState.config?.plugins?.slots?.[resolvedPluginSlot];
|
|
1333
|
-
if (!currentOwner || currentOwner === "none" || currentOwner === "legacy" || currentOwner === resolvedPluginId) {
|
|
1334
|
-
return true;
|
|
1335
|
-
}
|
|
1336
|
-
const currentOwnerVariant = getPluginVariantById(currentOwner);
|
|
1337
|
-
if (currentOwnerVariant && installedState.detections.some((item) => item.variant.id === currentOwnerVariant.id)) {
|
|
1338
|
-
return true;
|
|
1339
|
-
}
|
|
1340
|
-
return false;
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
async function cleanupInstalledPluginConfig(installedState) {
|
|
1344
|
-
if (!installedState.config || !installedState.config.plugins) {
|
|
1345
|
-
warn(tr("openclaw.json has no plugins section; skipped targeted plugin cleanup", "openclaw.json 中没有 plugins 配置,已跳过定向插件清理"));
|
|
1346
|
-
return;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
const nextConfig = structuredClone(installedState.config);
|
|
1350
|
-
let changed = false;
|
|
1351
|
-
for (const detection of installedState.detections) {
|
|
1352
|
-
changed = removePluginConfig(nextConfig, detection.variant) || changed;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
if (!changed) {
|
|
1356
|
-
info(tr("No OpenViking plugin config changes were required", "无需修改 OpenViking 插件配置"));
|
|
1357
|
-
return;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
await writeFile(installedState.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
1361
|
-
info(tr("Cleaned existing OpenViking plugin config only", "已仅清理 OpenViking 自身插件配置"));
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
async function prepareStrongPluginUpgrade() {
|
|
1365
|
-
const installedState = await detectInstalledPluginState();
|
|
1366
|
-
if (installedState.generation === "none") {
|
|
1367
|
-
err(
|
|
1368
|
-
tr(
|
|
1369
|
-
"Plugin upgrade mode requires an existing OpenViking plugin entry in openclaw.json.",
|
|
1370
|
-
"插件升级模式要求 openclaw.json 中已经存在 OpenViking 插件记录。",
|
|
1371
|
-
),
|
|
1372
|
-
);
|
|
1373
|
-
process.exit(1);
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
installedUpgradeState = installedState;
|
|
1377
|
-
upgradeRuntimeConfig = await prepareUpgradeRuntimeConfig(installedState);
|
|
1378
|
-
const fromVersion = formatInstalledStateLabel(installedState);
|
|
1379
|
-
const toVersion = formatTargetVersionLabel();
|
|
1380
|
-
selectedMode = upgradeRuntimeConfig.mode;
|
|
1381
|
-
info(
|
|
1382
|
-
tr(
|
|
1383
|
-
`Detected installed OpenViking plugin state: ${installedState.generation}`,
|
|
1384
|
-
`检测到已安装 OpenViking 插件状态: ${installedState.generation}`,
|
|
1385
|
-
),
|
|
1386
|
-
);
|
|
1387
|
-
if (upgradeRuntimeConfig.mode === "remote") {
|
|
1388
|
-
remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
1389
|
-
remoteApiKey = upgradeRuntimeConfig.apiKey || "";
|
|
1390
|
-
remoteAgentId = upgradeRuntimeConfig.agentId || "";
|
|
1391
|
-
} else {
|
|
1392
|
-
selectedServerPort = upgradeRuntimeConfig.port || DEFAULT_SERVER_PORT;
|
|
1393
|
-
}
|
|
1394
|
-
info(tr(`Upgrade runtime mode: ${selectedMode}`, `升级运行模式: ${selectedMode}`));
|
|
1395
|
-
|
|
1396
|
-
info(tr(`Upgrade path: ${fromVersion} -> ${toVersion}`, `升级路径: ${fromVersion} -> ${toVersion}`));
|
|
1397
|
-
|
|
1398
|
-
await stopOpenClawGatewayForUpgrade();
|
|
1399
|
-
const configBackupPath = await backupOpenClawConfig(installedState.configPath);
|
|
1400
|
-
info(tr(`Backed up openclaw.json: ${configBackupPath}`, `已备份 openclaw.json: ${configBackupPath}`));
|
|
1401
|
-
const pluginBackups = [];
|
|
1402
|
-
for (const detection of installedState.detections) {
|
|
1403
|
-
const backupDir = await backupPluginDirectory(detection.variant);
|
|
1404
|
-
if (backupDir) {
|
|
1405
|
-
pluginBackups.push({ pluginId: detection.variant.id, backupDir });
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
upgradeAudit = {
|
|
1409
|
-
operation: "upgrade",
|
|
1410
|
-
createdAt: new Date().toISOString(),
|
|
1411
|
-
fromVersion,
|
|
1412
|
-
toVersion,
|
|
1413
|
-
configBackupPath,
|
|
1414
|
-
pluginBackups,
|
|
1415
|
-
runtimeMode: selectedMode,
|
|
1416
|
-
};
|
|
1417
|
-
await writeUpgradeAuditFile(upgradeAudit);
|
|
1418
|
-
await cleanupInstalledPluginConfig(installedState);
|
|
1419
|
-
|
|
1420
|
-
info(
|
|
1421
|
-
tr(
|
|
1422
|
-
"Upgrade will keep the existing OpenViking runtime file and re-apply only the minimum plugin runtime settings.",
|
|
1423
|
-
"升级将保留现有 OpenViking 运行时文件,并只回填最小插件运行配置。",
|
|
1424
|
-
),
|
|
1425
|
-
);
|
|
1426
|
-
info(tr(`Upgrade audit file: ${getUpgradeAuditPath()}`, `升级审计文件: ${getUpgradeAuditPath()}`));
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
async function downloadPluginFile(destDir, fileName, url, required, index, total) {
|
|
1430
|
-
const maxRetries = 3;
|
|
1431
|
-
const destPath = join(destDir, fileName);
|
|
988
|
+
function getOpenClawConfigPath() {
|
|
989
|
+
return join(OPENCLAW_DIR, "openclaw.json");
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
function getOpenClawEnv() {
|
|
993
|
+
if (OPENCLAW_DIR === DEFAULT_OPENCLAW_DIR) {
|
|
994
|
+
return { ...process.env };
|
|
995
|
+
}
|
|
996
|
+
return { ...process.env, OPENCLAW_STATE_DIR: OPENCLAW_DIR };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function readJsonFileIfExists(filePath) {
|
|
1000
|
+
if (!existsSync(filePath)) return null;
|
|
1001
|
+
const raw = await readFile(filePath, "utf8");
|
|
1002
|
+
return JSON.parse(raw);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function getInstallStatePathForPlugin(pluginId) {
|
|
1006
|
+
return join(OPENCLAW_DIR, "extensions", pluginId, ".ov-install-state.json");
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function getUpgradeAuditDir() {
|
|
1010
|
+
return join(OPENCLAW_DIR, ".openviking-upgrade-backup");
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function getUpgradeAuditPath() {
|
|
1014
|
+
return join(getUpgradeAuditDir(), "last-upgrade.json");
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function getOpenClawConfigBackupPath() {
|
|
1018
|
+
return join(getUpgradeAuditDir(), "openclaw.json.bak");
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function normalizePluginMode(value) {
|
|
1022
|
+
return value === "remote" ? "remote" : "local";
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function getPluginVariantById(pluginId) {
|
|
1026
|
+
return PLUGIN_VARIANTS.find((variant) => variant.id === pluginId) || null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function detectPluginPresence(config, variant) {
|
|
1030
|
+
const plugins = config?.plugins;
|
|
1031
|
+
const reasons = [];
|
|
1032
|
+
if (!plugins) {
|
|
1033
|
+
return { variant, present: false, reasons };
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (plugins.entries && Object.prototype.hasOwnProperty.call(plugins.entries, variant.id)) {
|
|
1037
|
+
reasons.push("entry");
|
|
1038
|
+
}
|
|
1039
|
+
if (plugins.slots?.[variant.slot] === variant.id) {
|
|
1040
|
+
reasons.push("slot");
|
|
1041
|
+
}
|
|
1042
|
+
if (Array.isArray(plugins.allow) && plugins.allow.includes(variant.id)) {
|
|
1043
|
+
reasons.push("allow");
|
|
1044
|
+
}
|
|
1045
|
+
if (
|
|
1046
|
+
Array.isArray(plugins.load?.paths)
|
|
1047
|
+
&& plugins.load.paths.some((item) => typeof item === "string" && (item.includes(variant.id) || item.includes(variant.dir)))
|
|
1048
|
+
) {
|
|
1049
|
+
reasons.push("loadPath");
|
|
1050
|
+
}
|
|
1051
|
+
if (existsSync(join(OPENCLAW_DIR, "extensions", variant.id))) {
|
|
1052
|
+
reasons.push("dir");
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
return { variant, present: reasons.length > 0, reasons };
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async function detectInstalledPluginState() {
|
|
1059
|
+
const configPath = getOpenClawConfigPath();
|
|
1060
|
+
const config = await readJsonFileIfExists(configPath);
|
|
1061
|
+
const detections = [];
|
|
1062
|
+
for (const variant of PLUGIN_VARIANTS) {
|
|
1063
|
+
const detection = detectPluginPresence(config, variant);
|
|
1064
|
+
if (!detection.present) continue;
|
|
1065
|
+
detection.installState = await readJsonFileIfExists(getInstallStatePathForPlugin(variant.id));
|
|
1066
|
+
detections.push(detection);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
let generation = "none";
|
|
1070
|
+
if (detections.length === 1) {
|
|
1071
|
+
generation = detections[0].variant.generation;
|
|
1072
|
+
} else if (detections.length > 1) {
|
|
1073
|
+
generation = "mixed";
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
return {
|
|
1077
|
+
config,
|
|
1078
|
+
configPath,
|
|
1079
|
+
detections,
|
|
1080
|
+
generation,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function formatInstalledDetectionLabel(detection) {
|
|
1085
|
+
const requestedRef = detection.installState?.requestedRef;
|
|
1086
|
+
const releaseId = detection.installState?.releaseId;
|
|
1087
|
+
if (requestedRef) return `${detection.variant.id}@${requestedRef}`;
|
|
1088
|
+
if (releaseId) return `${detection.variant.id}#${releaseId}`;
|
|
1089
|
+
return `${detection.variant.id} (${detection.variant.generation}, exact version unknown)`;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function formatInstalledStateLabel(installedState) {
|
|
1093
|
+
if (!installedState?.detections?.length) {
|
|
1094
|
+
return "not-installed";
|
|
1095
|
+
}
|
|
1096
|
+
return installedState.detections.map(formatInstalledDetectionLabel).join(" + ");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function formatTargetVersionLabel() {
|
|
1100
|
+
const base = `${resolvedPluginId || "openviking"}@${PLUGIN_VERSION}`;
|
|
1101
|
+
if (resolvedPluginReleaseId && resolvedPluginReleaseId !== PLUGIN_VERSION) {
|
|
1102
|
+
return `${base} (${resolvedPluginReleaseId})`;
|
|
1103
|
+
}
|
|
1104
|
+
return base;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function extractRuntimeConfigFromPluginEntry(entryConfig) {
|
|
1108
|
+
if (!entryConfig || typeof entryConfig !== "object") return null;
|
|
1109
|
+
|
|
1110
|
+
const mode = normalizePluginMode(entryConfig.mode);
|
|
1111
|
+
const runtime = { mode };
|
|
1112
|
+
|
|
1113
|
+
if (mode === "remote") {
|
|
1114
|
+
if (typeof entryConfig.baseUrl === "string" && entryConfig.baseUrl.trim()) {
|
|
1115
|
+
runtime.baseUrl = entryConfig.baseUrl.trim();
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof entryConfig.apiKey === "string" && entryConfig.apiKey.trim()) {
|
|
1118
|
+
runtime.apiKey = entryConfig.apiKey;
|
|
1119
|
+
}
|
|
1120
|
+
if (typeof entryConfig.agentId === "string" && entryConfig.agentId.trim()) {
|
|
1121
|
+
runtime.agentId = entryConfig.agentId.trim();
|
|
1122
|
+
}
|
|
1123
|
+
return runtime;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (typeof entryConfig.configPath === "string" && entryConfig.configPath.trim()) {
|
|
1127
|
+
runtime.configPath = entryConfig.configPath.trim();
|
|
1128
|
+
}
|
|
1129
|
+
if (entryConfig.port !== undefined && entryConfig.port !== null && `${entryConfig.port}`.trim()) {
|
|
1130
|
+
const parsedPort = Number.parseInt(String(entryConfig.port), 10);
|
|
1131
|
+
if (Number.isFinite(parsedPort) && parsedPort > 0) {
|
|
1132
|
+
runtime.port = parsedPort;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return runtime;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
async function readPortFromOvConf(configPath) {
|
|
1139
|
+
const filePath = configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1140
|
+
if (!existsSync(filePath)) return null;
|
|
1141
|
+
try {
|
|
1142
|
+
const ovConf = await readJsonFileIfExists(filePath);
|
|
1143
|
+
const parsedPort = Number.parseInt(String(ovConf?.server?.port ?? ""), 10);
|
|
1144
|
+
return Number.isFinite(parsedPort) && parsedPort > 0 ? parsedPort : null;
|
|
1145
|
+
} catch {
|
|
1146
|
+
return null;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
async function backupOpenClawConfig(configPath) {
|
|
1151
|
+
await mkdir(getUpgradeAuditDir(), { recursive: true });
|
|
1152
|
+
const backupPath = getOpenClawConfigBackupPath();
|
|
1153
|
+
const configText = await readFile(configPath, "utf8");
|
|
1154
|
+
await writeFile(backupPath, configText, "utf8");
|
|
1155
|
+
return backupPath;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function writeUpgradeAuditFile(data) {
|
|
1159
|
+
await mkdir(getUpgradeAuditDir(), { recursive: true });
|
|
1160
|
+
await writeFile(getUpgradeAuditPath(), `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
async function writeInstallStateFile({ operation, fromVersion, configBackupPath, pluginBackups }) {
|
|
1164
|
+
const installStatePath = getInstallStatePathForPlugin(resolvedPluginId || "openviking");
|
|
1165
|
+
const state = {
|
|
1166
|
+
pluginId: resolvedPluginId || "openviking",
|
|
1167
|
+
generation: getPluginVariantById(resolvedPluginId || "openviking")?.generation || "unknown",
|
|
1168
|
+
requestedRef: PLUGIN_VERSION,
|
|
1169
|
+
releaseId: resolvedPluginReleaseId || "",
|
|
1170
|
+
operation,
|
|
1171
|
+
fromVersion: fromVersion || "",
|
|
1172
|
+
configBackupPath: configBackupPath || "",
|
|
1173
|
+
pluginBackups: pluginBackups || [],
|
|
1174
|
+
installedAt: new Date().toISOString(),
|
|
1175
|
+
repo: REPO,
|
|
1176
|
+
};
|
|
1177
|
+
await writeFile(installStatePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
async function moveDirWithFallback(sourceDir, destDir) {
|
|
1181
|
+
try {
|
|
1182
|
+
await rename(sourceDir, destDir);
|
|
1183
|
+
} catch {
|
|
1184
|
+
await cp(sourceDir, destDir, { recursive: true, force: true });
|
|
1185
|
+
await rm(sourceDir, { recursive: true, force: true });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
async function rollbackLastUpgradeOperation() {
|
|
1190
|
+
const auditPath = getUpgradeAuditPath();
|
|
1191
|
+
const audit = await readJsonFileIfExists(auditPath);
|
|
1192
|
+
if (!audit) {
|
|
1193
|
+
err(
|
|
1194
|
+
tr(
|
|
1195
|
+
`No rollback audit file found at ${auditPath}.`,
|
|
1196
|
+
`未找到回滚审计文件: ${auditPath}`,
|
|
1197
|
+
),
|
|
1198
|
+
);
|
|
1199
|
+
process.exit(1);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (audit.rolledBackAt) {
|
|
1203
|
+
warn(
|
|
1204
|
+
tr(
|
|
1205
|
+
`The last recorded upgrade was already rolled back at ${audit.rolledBackAt}.`,
|
|
1206
|
+
`最近一次升级已在 ${audit.rolledBackAt} 回滚。`,
|
|
1207
|
+
),
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const configBackupPath = audit.configBackupPath || getOpenClawConfigBackupPath();
|
|
1212
|
+
if (!existsSync(configBackupPath)) {
|
|
1213
|
+
err(
|
|
1214
|
+
tr(
|
|
1215
|
+
`Rollback config backup is missing: ${configBackupPath}`,
|
|
1216
|
+
`回滚配置备份缺失: ${configBackupPath}`,
|
|
1217
|
+
),
|
|
1218
|
+
);
|
|
1219
|
+
process.exit(1);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const pluginBackups = Array.isArray(audit.pluginBackups) ? audit.pluginBackups : [];
|
|
1223
|
+
if (pluginBackups.length === 0) {
|
|
1224
|
+
err(tr("Rollback audit file contains no plugin backups.", "回滚审计文件中没有插件备份信息。"));
|
|
1225
|
+
process.exit(1);
|
|
1226
|
+
}
|
|
1227
|
+
for (const pluginBackup of pluginBackups) {
|
|
1228
|
+
if (!pluginBackup?.pluginId || !pluginBackup?.backupDir || !existsSync(pluginBackup.backupDir)) {
|
|
1229
|
+
err(
|
|
1230
|
+
tr(
|
|
1231
|
+
`Rollback plugin backup is missing: ${pluginBackup?.backupDir || "<unknown>"}`,
|
|
1232
|
+
`回滚插件备份缺失: ${pluginBackup?.backupDir || "<unknown>"}`,
|
|
1233
|
+
),
|
|
1234
|
+
);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
info(tr(`Rolling back last upgrade: ${audit.fromVersion || "unknown"} <- ${audit.toVersion || "unknown"}`, `开始回滚最近一次升级: ${audit.fromVersion || "unknown"} <- ${audit.toVersion || "unknown"}`));
|
|
1240
|
+
await stopOpenClawGatewayForUpgrade();
|
|
1241
|
+
|
|
1242
|
+
const configText = await readFile(configBackupPath, "utf8");
|
|
1243
|
+
await writeFile(getOpenClawConfigPath(), configText, "utf8");
|
|
1244
|
+
info(tr(`Restored openclaw.json from backup: ${configBackupPath}`, `已从备份恢复 openclaw.json: ${configBackupPath}`));
|
|
1245
|
+
|
|
1246
|
+
const extensionsDir = join(OPENCLAW_DIR, "extensions");
|
|
1247
|
+
await mkdir(extensionsDir, { recursive: true });
|
|
1248
|
+
for (const variant of PLUGIN_VARIANTS) {
|
|
1249
|
+
const liveDir = join(extensionsDir, variant.id);
|
|
1250
|
+
if (existsSync(liveDir)) {
|
|
1251
|
+
await rm(liveDir, { recursive: true, force: true });
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
for (const pluginBackup of pluginBackups) {
|
|
1256
|
+
if (!pluginBackup?.pluginId || !pluginBackup?.backupDir) continue;
|
|
1257
|
+
if (!existsSync(pluginBackup.backupDir)) {
|
|
1258
|
+
err(
|
|
1259
|
+
tr(
|
|
1260
|
+
`Rollback plugin backup is missing: ${pluginBackup.backupDir}`,
|
|
1261
|
+
`回滚插件备份缺失: ${pluginBackup.backupDir}`,
|
|
1262
|
+
),
|
|
1263
|
+
);
|
|
1264
|
+
process.exit(1);
|
|
1265
|
+
}
|
|
1266
|
+
const destDir = join(extensionsDir, pluginBackup.pluginId);
|
|
1267
|
+
await moveDirWithFallback(pluginBackup.backupDir, destDir);
|
|
1268
|
+
info(tr(`Restored plugin directory: ${destDir}`, `已恢复插件目录: ${destDir}`));
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
audit.rolledBackAt = new Date().toISOString();
|
|
1272
|
+
audit.rollbackConfigPath = configBackupPath;
|
|
1273
|
+
await writeUpgradeAuditFile(audit);
|
|
1274
|
+
|
|
1275
|
+
console.log("");
|
|
1276
|
+
bold(tr("Rollback complete!", "回滚完成!"));
|
|
1277
|
+
console.log("");
|
|
1278
|
+
info(tr(`Rollback audit file: ${auditPath}`, `回滚审计文件: ${auditPath}`));
|
|
1279
|
+
info(tr("Run `openclaw gateway` and `openclaw status` to verify the restored plugin state.", "请运行 `openclaw gateway` 和 `openclaw status` 验证恢复后的插件状态。"));
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
async function prepareUpgradeRuntimeConfig(installedState) {
|
|
1283
|
+
const plugins = installedState.config?.plugins ?? {};
|
|
1284
|
+
const candidateOrder = installedState.detections
|
|
1285
|
+
.map((item) => item.variant)
|
|
1286
|
+
.sort((left, right) => (right.generation === "current" ? 1 : 0) - (left.generation === "current" ? 1 : 0));
|
|
1287
|
+
|
|
1288
|
+
let runtime = null;
|
|
1289
|
+
for (const variant of candidateOrder) {
|
|
1290
|
+
const entryConfig = extractRuntimeConfigFromPluginEntry(plugins.entries?.[variant.id]?.config);
|
|
1291
|
+
if (entryConfig) {
|
|
1292
|
+
runtime = entryConfig;
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (!runtime) {
|
|
1298
|
+
runtime = { mode: "local" };
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
if (runtime.mode === "remote") {
|
|
1302
|
+
runtime.baseUrl = runtime.baseUrl || remoteBaseUrl;
|
|
1303
|
+
return runtime;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
runtime.configPath = runtime.configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1307
|
+
runtime.port = runtime.port || await readPortFromOvConf(runtime.configPath) || DEFAULT_SERVER_PORT;
|
|
1308
|
+
return runtime;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function removePluginConfig(config, variant) {
|
|
1312
|
+
const plugins = config?.plugins;
|
|
1313
|
+
if (!plugins) return false;
|
|
1314
|
+
|
|
1315
|
+
let changed = false;
|
|
1316
|
+
|
|
1317
|
+
if (Array.isArray(plugins.allow)) {
|
|
1318
|
+
const nextAllow = plugins.allow.filter((item) => item !== variant.id);
|
|
1319
|
+
changed = changed || nextAllow.length !== plugins.allow.length;
|
|
1320
|
+
plugins.allow = nextAllow;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (Array.isArray(plugins.load?.paths)) {
|
|
1324
|
+
const nextPaths = plugins.load.paths.filter(
|
|
1325
|
+
(item) => typeof item !== "string" || (!item.includes(variant.id) && !item.includes(variant.dir)),
|
|
1326
|
+
);
|
|
1327
|
+
changed = changed || nextPaths.length !== plugins.load.paths.length;
|
|
1328
|
+
plugins.load.paths = nextPaths;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
if (plugins.entries && Object.prototype.hasOwnProperty.call(plugins.entries, variant.id)) {
|
|
1332
|
+
delete plugins.entries[variant.id];
|
|
1333
|
+
changed = true;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
if (plugins.slots?.[variant.slot] === variant.id) {
|
|
1337
|
+
plugins.slots[variant.slot] = variant.slotFallback;
|
|
1338
|
+
changed = true;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
return changed;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
async function prunePreviousUpgradeBackups(disabledDir, variant, keepDir) {
|
|
1345
|
+
if (!existsSync(disabledDir)) return;
|
|
1346
|
+
|
|
1347
|
+
const prefix = `${variant.id}-upgrade-backup-`;
|
|
1348
|
+
const keepName = keepDir ? keepDir.split(/[\\/]/).pop() : "";
|
|
1349
|
+
const entries = readdirSync(disabledDir, { withFileTypes: true });
|
|
1350
|
+
for (const entry of entries) {
|
|
1351
|
+
if (!entry.isDirectory()) continue;
|
|
1352
|
+
if (!entry.name.startsWith(prefix)) continue;
|
|
1353
|
+
if (keepName && entry.name === keepName) continue;
|
|
1354
|
+
await rm(join(disabledDir, entry.name), { recursive: true, force: true });
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async function backupPluginDirectory(variant) {
|
|
1359
|
+
const pluginDir = join(OPENCLAW_DIR, "extensions", variant.id);
|
|
1360
|
+
if (!existsSync(pluginDir)) return null;
|
|
1361
|
+
|
|
1362
|
+
const disabledDir = join(OPENCLAW_DIR, "disabled-extensions");
|
|
1363
|
+
const backupDir = join(disabledDir, `${variant.id}-upgrade-backup-${Date.now()}`);
|
|
1364
|
+
await mkdir(disabledDir, { recursive: true });
|
|
1365
|
+
try {
|
|
1366
|
+
await rename(pluginDir, backupDir);
|
|
1367
|
+
} catch {
|
|
1368
|
+
await cp(pluginDir, backupDir, { recursive: true, force: true });
|
|
1369
|
+
await rm(pluginDir, { recursive: true, force: true });
|
|
1370
|
+
}
|
|
1371
|
+
info(tr(`Backed up plugin directory: ${backupDir}`, `已备份插件目录: ${backupDir}`));
|
|
1372
|
+
await prunePreviousUpgradeBackups(disabledDir, variant, backupDir);
|
|
1373
|
+
return backupDir;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async function stopOpenClawGatewayForUpgrade() {
|
|
1377
|
+
const result = await runCapture("openclaw", ["gateway", "stop"], {
|
|
1378
|
+
env: getOpenClawEnv(),
|
|
1379
|
+
shell: IS_WIN,
|
|
1380
|
+
});
|
|
1381
|
+
if (result.code === 0) {
|
|
1382
|
+
info(tr("Stopped OpenClaw gateway before plugin upgrade", "升级插件前已停止 OpenClaw gateway"));
|
|
1383
|
+
} else {
|
|
1384
|
+
warn(tr("OpenClaw gateway may not be running; continuing", "OpenClaw gateway 可能未在运行,继续执行"));
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function shouldClaimTargetSlot(installedState) {
|
|
1389
|
+
const currentOwner = installedState.config?.plugins?.slots?.[resolvedPluginSlot];
|
|
1390
|
+
if (!currentOwner || currentOwner === "none" || currentOwner === "legacy" || currentOwner === resolvedPluginId) {
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
const currentOwnerVariant = getPluginVariantById(currentOwner);
|
|
1394
|
+
if (currentOwnerVariant && installedState.detections.some((item) => item.variant.id === currentOwnerVariant.id)) {
|
|
1395
|
+
return true;
|
|
1396
|
+
}
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
async function cleanupInstalledPluginConfig(installedState) {
|
|
1401
|
+
if (!installedState.config || !installedState.config.plugins) {
|
|
1402
|
+
warn(tr("openclaw.json has no plugins section; skipped targeted plugin cleanup", "openclaw.json 中没有 plugins 配置,已跳过定向插件清理"));
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
const nextConfig = structuredClone(installedState.config);
|
|
1407
|
+
let changed = false;
|
|
1408
|
+
for (const detection of installedState.detections) {
|
|
1409
|
+
changed = removePluginConfig(nextConfig, detection.variant) || changed;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (!changed) {
|
|
1413
|
+
info(tr("No OpenViking plugin config changes were required", "无需修改 OpenViking 插件配置"));
|
|
1414
|
+
return;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
await writeFile(installedState.configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
1418
|
+
info(tr("Cleaned existing OpenViking plugin config only", "已仅清理 OpenViking 自身插件配置"));
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
async function prepareStrongPluginUpgrade() {
|
|
1422
|
+
const installedState = await detectInstalledPluginState();
|
|
1423
|
+
if (installedState.generation === "none") {
|
|
1424
|
+
err(
|
|
1425
|
+
tr(
|
|
1426
|
+
"Plugin upgrade mode requires an existing OpenViking plugin entry in openclaw.json.",
|
|
1427
|
+
"插件升级模式要求 openclaw.json 中已经存在 OpenViking 插件记录。",
|
|
1428
|
+
),
|
|
1429
|
+
);
|
|
1430
|
+
process.exit(1);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
installedUpgradeState = installedState;
|
|
1434
|
+
upgradeRuntimeConfig = await prepareUpgradeRuntimeConfig(installedState);
|
|
1435
|
+
const fromVersion = formatInstalledStateLabel(installedState);
|
|
1436
|
+
const toVersion = formatTargetVersionLabel();
|
|
1437
|
+
selectedMode = upgradeRuntimeConfig.mode;
|
|
1438
|
+
info(
|
|
1439
|
+
tr(
|
|
1440
|
+
`Detected installed OpenViking plugin state: ${installedState.generation}`,
|
|
1441
|
+
`检测到已安装 OpenViking 插件状态: ${installedState.generation}`,
|
|
1442
|
+
),
|
|
1443
|
+
);
|
|
1444
|
+
if (upgradeRuntimeConfig.mode === "remote") {
|
|
1445
|
+
remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
|
|
1446
|
+
remoteApiKey = upgradeRuntimeConfig.apiKey || "";
|
|
1447
|
+
remoteAgentId = upgradeRuntimeConfig.agentId || "";
|
|
1448
|
+
} else {
|
|
1449
|
+
selectedServerPort = upgradeRuntimeConfig.port || DEFAULT_SERVER_PORT;
|
|
1450
|
+
}
|
|
1451
|
+
info(tr(`Upgrade runtime mode: ${selectedMode}`, `升级运行模式: ${selectedMode}`));
|
|
1452
|
+
|
|
1453
|
+
info(tr(`Upgrade path: ${fromVersion} -> ${toVersion}`, `升级路径: ${fromVersion} -> ${toVersion}`));
|
|
1454
|
+
|
|
1455
|
+
await stopOpenClawGatewayForUpgrade();
|
|
1456
|
+
const configBackupPath = await backupOpenClawConfig(installedState.configPath);
|
|
1457
|
+
info(tr(`Backed up openclaw.json: ${configBackupPath}`, `已备份 openclaw.json: ${configBackupPath}`));
|
|
1458
|
+
const pluginBackups = [];
|
|
1459
|
+
for (const detection of installedState.detections) {
|
|
1460
|
+
const backupDir = await backupPluginDirectory(detection.variant);
|
|
1461
|
+
if (backupDir) {
|
|
1462
|
+
pluginBackups.push({ pluginId: detection.variant.id, backupDir });
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
upgradeAudit = {
|
|
1466
|
+
operation: "upgrade",
|
|
1467
|
+
createdAt: new Date().toISOString(),
|
|
1468
|
+
fromVersion,
|
|
1469
|
+
toVersion,
|
|
1470
|
+
configBackupPath,
|
|
1471
|
+
pluginBackups,
|
|
1472
|
+
runtimeMode: selectedMode,
|
|
1473
|
+
};
|
|
1474
|
+
await writeUpgradeAuditFile(upgradeAudit);
|
|
1475
|
+
await cleanupInstalledPluginConfig(installedState);
|
|
1476
|
+
|
|
1477
|
+
info(
|
|
1478
|
+
tr(
|
|
1479
|
+
"Upgrade will keep the existing OpenViking runtime file and re-apply only the minimum plugin runtime settings.",
|
|
1480
|
+
"升级将保留现有 OpenViking 运行时文件,并只回填最小插件运行配置。",
|
|
1481
|
+
),
|
|
1482
|
+
);
|
|
1483
|
+
info(tr(`Upgrade audit file: ${getUpgradeAuditPath()}`, `升级审计文件: ${getUpgradeAuditPath()}`));
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
async function downloadPluginFile(destDir, fileName, url, required, index, total) {
|
|
1487
|
+
const maxRetries = 3;
|
|
1488
|
+
const destPath = join(destDir, fileName);
|
|
1432
1489
|
|
|
1433
1490
|
process.stdout.write(` [${index}/${total}] ${fileName} `);
|
|
1434
1491
|
|
|
1492
|
+
let lastStatus = 0;
|
|
1493
|
+
let saw404 = false;
|
|
1494
|
+
|
|
1435
1495
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1436
1496
|
try {
|
|
1437
1497
|
const response = await fetch(url);
|
|
1498
|
+
lastStatus = response.status;
|
|
1438
1499
|
if (response.ok) {
|
|
1439
1500
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1501
|
+
if (buffer.length === 0) {
|
|
1502
|
+
lastStatus = 0;
|
|
1503
|
+
} else {
|
|
1504
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
1505
|
+
await writeFile(destPath, buffer);
|
|
1506
|
+
console.log(" OK");
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
} else if (!required && response.status === 404) {
|
|
1510
|
+
saw404 = true;
|
|
1511
|
+
break;
|
|
1448
1512
|
}
|
|
1449
|
-
} catch {
|
|
1513
|
+
} catch {
|
|
1514
|
+
lastStatus = 0;
|
|
1515
|
+
}
|
|
1450
1516
|
|
|
1451
1517
|
if (attempt < maxRetries) {
|
|
1452
1518
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1453
1519
|
}
|
|
1454
1520
|
}
|
|
1455
1521
|
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1522
|
+
if (saw404 || lastStatus === 404) {
|
|
1523
|
+
if (fileName === ".gitignore") {
|
|
1524
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
1525
|
+
await writeFile(destPath, "node_modules/\n", "utf8");
|
|
1526
|
+
console.log(" OK");
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
console.log(tr(" skip", " 跳过"));
|
|
1460
1530
|
return;
|
|
1461
1531
|
}
|
|
1462
1532
|
|
|
1463
1533
|
if (!required) {
|
|
1464
|
-
console.log(
|
|
1465
|
-
|
|
1534
|
+
console.log("");
|
|
1535
|
+
err(
|
|
1536
|
+
tr(
|
|
1537
|
+
`Optional file failed after ${maxRetries} retries (HTTP ${lastStatus || "network"}): ${url}`,
|
|
1538
|
+
`可选文件已重试 ${maxRetries} 次仍失败(HTTP ${lastStatus || "网络错误"}): ${url}`,
|
|
1539
|
+
),
|
|
1540
|
+
);
|
|
1541
|
+
process.exit(1);
|
|
1466
1542
|
}
|
|
1467
1543
|
|
|
1468
1544
|
console.log("");
|
|
1469
|
-
err(tr(`Download failed: ${url}`,
|
|
1545
|
+
err(tr(`Download failed after ${maxRetries} retries: ${url}`, `下载失败(已重试 ${maxRetries} 次): ${url}`));
|
|
1470
1546
|
process.exit(1);
|
|
1471
1547
|
}
|
|
1472
1548
|
|
|
1473
|
-
async function downloadPlugin(destDir) {
|
|
1549
|
+
async function downloadPlugin(destDir) {
|
|
1474
1550
|
const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
|
|
1475
1551
|
const pluginDir = resolvedPluginDir;
|
|
1476
1552
|
const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
|
|
1477
1553
|
|
|
1478
|
-
await mkdir(destDir, { recursive: true });
|
|
1554
|
+
await mkdir(destDir, { recursive: true });
|
|
1479
1555
|
|
|
1480
1556
|
info(tr(`Downloading plugin from ${REPO}@${PLUGIN_VERSION} (${total} files)...`, `正在从 ${REPO}@${PLUGIN_VERSION} 下载插件(共 ${total} 个文件)...`));
|
|
1481
1557
|
|
|
@@ -1485,7 +1561,7 @@ async function downloadPlugin(destDir) {
|
|
|
1485
1561
|
if (!name) continue;
|
|
1486
1562
|
i++;
|
|
1487
1563
|
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
1488
|
-
await downloadPluginFile(destDir, name, url, true, i, total);
|
|
1564
|
+
await downloadPluginFile(destDir, name, url, true, i, total);
|
|
1489
1565
|
}
|
|
1490
1566
|
|
|
1491
1567
|
// Download optional files
|
|
@@ -1493,7 +1569,7 @@ async function downloadPlugin(destDir) {
|
|
|
1493
1569
|
if (!name) continue;
|
|
1494
1570
|
i++;
|
|
1495
1571
|
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
1496
|
-
await downloadPluginFile(destDir, name, url, false, i, total);
|
|
1572
|
+
await downloadPluginFile(destDir, name, url, false, i, total);
|
|
1497
1573
|
}
|
|
1498
1574
|
|
|
1499
1575
|
// npm install
|
|
@@ -1501,91 +1577,153 @@ async function downloadPlugin(destDir) {
|
|
|
1501
1577
|
const npmArgs = resolvedNpmOmitDev
|
|
1502
1578
|
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1503
1579
|
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1504
|
-
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1580
|
+
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1505
1581
|
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
1506
1582
|
}
|
|
1507
1583
|
|
|
1508
|
-
async function deployLocalPlugin(localPluginDir, destDir) {
|
|
1509
|
-
await rm(destDir, { recursive: true, force: true });
|
|
1510
|
-
await mkdir(destDir, { recursive: true });
|
|
1511
|
-
await cp(localPluginDir, destDir, {
|
|
1584
|
+
async function deployLocalPlugin(localPluginDir, destDir) {
|
|
1585
|
+
await rm(destDir, { recursive: true, force: true });
|
|
1586
|
+
await mkdir(destDir, { recursive: true });
|
|
1587
|
+
await cp(localPluginDir, destDir, {
|
|
1512
1588
|
recursive: true,
|
|
1513
1589
|
force: true,
|
|
1514
1590
|
filter: (sourcePath) => {
|
|
1515
1591
|
const rel = relative(localPluginDir, sourcePath);
|
|
1516
1592
|
if (!rel) return true;
|
|
1517
1593
|
const firstSegment = rel.split(/[\\/]/)[0];
|
|
1518
|
-
return firstSegment !== "node_modules" && firstSegment !== ".git";
|
|
1519
|
-
},
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
async function installPluginDependencies(destDir) {
|
|
1524
|
-
info(tr("Installing plugin npm dependencies...", "姝e湪瀹夎鎻掍欢 npm 渚濊禆..."));
|
|
1525
|
-
const npmArgs = resolvedNpmOmitDev
|
|
1526
|
-
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1527
|
-
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1528
|
-
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1529
|
-
return info(`Plugin prepared: ${destDir}`);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
async function createPluginStagingDir() {
|
|
1533
|
-
const pluginId = resolvedPluginId || "openviking";
|
|
1534
|
-
const extensionsDir = join(OPENCLAW_DIR, "extensions");
|
|
1535
|
-
const stagingDir = join(extensionsDir, `.${pluginId}.staging-${process.pid}-${Date.now()}`);
|
|
1536
|
-
await mkdir(extensionsDir, { recursive: true });
|
|
1537
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
1538
|
-
await mkdir(stagingDir, { recursive: true });
|
|
1539
|
-
return stagingDir;
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
async function finalizePluginDeployment(stagingDir) {
|
|
1543
|
-
await rm(PLUGIN_DEST, { recursive: true, force: true });
|
|
1544
|
-
try {
|
|
1545
|
-
await rename(stagingDir, PLUGIN_DEST);
|
|
1546
|
-
} catch {
|
|
1547
|
-
await cp(stagingDir, PLUGIN_DEST, { recursive: true, force: true });
|
|
1548
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
1549
|
-
}
|
|
1550
|
-
return info(`Plugin deployed: ${PLUGIN_DEST}`);
|
|
1551
|
-
info(tr(`Plugin prepared: ${destDir}`, `鎻掍欢宸插噯澶? ${destDir}`));
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
async function deployPluginFromRemote() {
|
|
1555
|
-
const stagingDir = await createPluginStagingDir();
|
|
1556
|
-
try {
|
|
1557
|
-
await downloadPlugin(stagingDir);
|
|
1558
|
-
await finalizePluginDeployment(stagingDir);
|
|
1559
|
-
} catch (error) {
|
|
1560
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
1561
|
-
throw error;
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1594
|
+
return firstSegment !== "node_modules" && firstSegment !== ".git";
|
|
1595
|
+
},
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
async function installPluginDependencies(destDir) {
|
|
1600
|
+
info(tr("Installing plugin npm dependencies...", "姝e湪瀹夎鎻掍欢 npm 渚濊禆..."));
|
|
1601
|
+
const npmArgs = resolvedNpmOmitDev
|
|
1602
|
+
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1603
|
+
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1604
|
+
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
1605
|
+
return info(`Plugin prepared: ${destDir}`);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
async function createPluginStagingDir() {
|
|
1609
|
+
const pluginId = resolvedPluginId || "openviking";
|
|
1610
|
+
const extensionsDir = join(OPENCLAW_DIR, "extensions");
|
|
1611
|
+
const stagingDir = join(extensionsDir, `.${pluginId}.staging-${process.pid}-${Date.now()}`);
|
|
1612
|
+
await mkdir(extensionsDir, { recursive: true });
|
|
1613
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
1614
|
+
await mkdir(stagingDir, { recursive: true });
|
|
1615
|
+
return stagingDir;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
async function finalizePluginDeployment(stagingDir) {
|
|
1619
|
+
await rm(PLUGIN_DEST, { recursive: true, force: true });
|
|
1620
|
+
try {
|
|
1621
|
+
await rename(stagingDir, PLUGIN_DEST);
|
|
1622
|
+
} catch {
|
|
1623
|
+
await cp(stagingDir, PLUGIN_DEST, { recursive: true, force: true });
|
|
1624
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
1625
|
+
}
|
|
1626
|
+
return info(`Plugin deployed: ${PLUGIN_DEST}`);
|
|
1627
|
+
info(tr(`Plugin prepared: ${destDir}`, `鎻掍欢宸插噯澶? ${destDir}`));
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
async function deployPluginFromRemote() {
|
|
1631
|
+
const stagingDir = await createPluginStagingDir();
|
|
1632
|
+
try {
|
|
1633
|
+
await downloadPlugin(stagingDir);
|
|
1634
|
+
await finalizePluginDeployment(stagingDir);
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
1637
|
+
throw error;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
/** Same as INSTALL*.md manual cleanup: stale entries block `plugins.slots.*` validation after reinstall. */
|
|
1642
|
+
function resolvedPluginSlotFallback() {
|
|
1643
|
+
if (resolvedPluginId === "memory-openviking") return "none";
|
|
1644
|
+
if (resolvedPluginId === "openviking") return "legacy";
|
|
1645
|
+
return "none";
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
async function scrubStaleOpenClawPluginRegistration() {
|
|
1649
|
+
const configPath = getOpenClawConfigPath();
|
|
1650
|
+
if (!existsSync(configPath)) return;
|
|
1651
|
+
const pluginId = resolvedPluginId;
|
|
1652
|
+
const slot = resolvedPluginSlot;
|
|
1653
|
+
const slotFallback = resolvedPluginSlotFallback();
|
|
1654
|
+
let raw;
|
|
1655
|
+
try {
|
|
1656
|
+
raw = await readFile(configPath, "utf8");
|
|
1657
|
+
} catch {
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
let cfg;
|
|
1661
|
+
try {
|
|
1662
|
+
cfg = JSON.parse(raw);
|
|
1663
|
+
} catch {
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
if (!cfg.plugins) return;
|
|
1667
|
+
const p = cfg.plugins;
|
|
1668
|
+
let changed = false;
|
|
1669
|
+
if (p.entries && Object.prototype.hasOwnProperty.call(p.entries, pluginId)) {
|
|
1670
|
+
delete p.entries[pluginId];
|
|
1671
|
+
changed = true;
|
|
1672
|
+
}
|
|
1673
|
+
if (Array.isArray(p.allow)) {
|
|
1674
|
+
const next = p.allow.filter((id) => id !== pluginId);
|
|
1675
|
+
if (next.length !== p.allow.length) {
|
|
1676
|
+
p.allow = next;
|
|
1677
|
+
changed = true;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (p.load && Array.isArray(p.load.paths)) {
|
|
1681
|
+
const norm = (s) => String(s).replace(/\\/g, "/");
|
|
1682
|
+
const extNeedle = `/extensions/${pluginId}`;
|
|
1683
|
+
const next = p.load.paths.filter((path) => {
|
|
1684
|
+
if (typeof path !== "string") return true;
|
|
1685
|
+
return !norm(path).includes(extNeedle);
|
|
1686
|
+
});
|
|
1687
|
+
if (next.length !== p.load.paths.length) {
|
|
1688
|
+
p.load.paths = next;
|
|
1689
|
+
changed = true;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
if (p.slots && p.slots[slot] === pluginId) {
|
|
1693
|
+
p.slots[slot] = slotFallback;
|
|
1694
|
+
changed = true;
|
|
1695
|
+
}
|
|
1696
|
+
if (!changed) return;
|
|
1697
|
+
const out = JSON.stringify(cfg, null, 2) + "\n";
|
|
1698
|
+
const tmp = `${configPath}.ov-install-tmp.${process.pid}`;
|
|
1699
|
+
await writeFile(tmp, out, "utf8");
|
|
1700
|
+
await rename(tmp, configPath);
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
async function deployPluginFromLocal(localPluginDir) {
|
|
1704
|
+
const stagingDir = await createPluginStagingDir();
|
|
1705
|
+
try {
|
|
1706
|
+
await deployLocalPlugin(localPluginDir, stagingDir);
|
|
1707
|
+
await installPluginDependencies(stagingDir);
|
|
1708
|
+
await finalizePluginDeployment(stagingDir);
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
1711
|
+
throw error;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
async function configureOpenClawPlugin({
|
|
1716
|
+
preserveExistingConfig = false,
|
|
1717
|
+
runtimeConfig = null,
|
|
1718
|
+
skipGatewayMode = false,
|
|
1719
|
+
claimSlot = true,
|
|
1720
|
+
} = {}) {
|
|
1583
1721
|
info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
|
|
1584
1722
|
|
|
1585
1723
|
const pluginId = resolvedPluginId;
|
|
1586
1724
|
const pluginSlot = resolvedPluginSlot;
|
|
1587
1725
|
|
|
1588
|
-
const ocEnv = getOpenClawEnv();
|
|
1726
|
+
const ocEnv = getOpenClawEnv();
|
|
1589
1727
|
|
|
1590
1728
|
const oc = async (args) => {
|
|
1591
1729
|
const result = await runCapture("openclaw", args, { env: ocEnv, shell: IS_WIN });
|
|
@@ -1596,55 +1734,59 @@ async function configureOpenClawPlugin({
|
|
|
1596
1734
|
return result;
|
|
1597
1735
|
};
|
|
1598
1736
|
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "
|
|
1640
|
-
await oc(["config", "set", `plugins.entries.${pluginId}.config.
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1737
|
+
if (!preserveExistingConfig) {
|
|
1738
|
+
await scrubStaleOpenClawPluginRegistration();
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// Enable plugin (files already deployed to extensions dir by deployPlugin)
|
|
1742
|
+
await oc(["plugins", "enable", pluginId]);
|
|
1743
|
+
if (claimSlot) {
|
|
1744
|
+
await oc(["config", "set", `plugins.slots.${pluginSlot}`, pluginId]);
|
|
1745
|
+
} else {
|
|
1746
|
+
warn(
|
|
1747
|
+
tr(
|
|
1748
|
+
`Skipped claiming plugins.slots.${pluginSlot}; it is currently owned by another plugin.`,
|
|
1749
|
+
`已跳过设置 plugins.slots.${pluginSlot},当前该 slot 由其他插件占用。`,
|
|
1750
|
+
),
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
if (preserveExistingConfig) {
|
|
1755
|
+
info(
|
|
1756
|
+
tr(
|
|
1757
|
+
`Preserved existing plugin runtime config for ${pluginId}`,
|
|
1758
|
+
`宸蹭繚鐣?${pluginId} 鐨勭幇鏈夋彃浠惰繍琛岄厤缃?`,
|
|
1759
|
+
),
|
|
1760
|
+
);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
const effectiveRuntimeConfig = runtimeConfig || (
|
|
1765
|
+
selectedMode === "remote"
|
|
1766
|
+
? { mode: "remote", baseUrl: remoteBaseUrl, apiKey: remoteApiKey, agentId: remoteAgentId }
|
|
1767
|
+
: { mode: "local", configPath: join(OPENVIKING_DIR, "ov.conf"), port: selectedServerPort }
|
|
1768
|
+
);
|
|
1769
|
+
|
|
1770
|
+
if (!skipGatewayMode) {
|
|
1771
|
+
await oc(["config", "set", "gateway.mode", effectiveRuntimeConfig.mode === "remote" ? "remote" : "local"]);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
// Set plugin config for the selected mode
|
|
1775
|
+
if (effectiveRuntimeConfig.mode === "local") {
|
|
1776
|
+
const ovConfPath = effectiveRuntimeConfig.configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1777
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "local"]);
|
|
1778
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.configPath`, ovConfPath]);
|
|
1779
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.port`, String(effectiveRuntimeConfig.port || DEFAULT_SERVER_PORT)]);
|
|
1780
|
+
} else {
|
|
1781
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "remote"]);
|
|
1782
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.baseUrl`, effectiveRuntimeConfig.baseUrl || remoteBaseUrl]);
|
|
1783
|
+
if (effectiveRuntimeConfig.apiKey) {
|
|
1784
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.apiKey`, effectiveRuntimeConfig.apiKey]);
|
|
1785
|
+
}
|
|
1786
|
+
if (effectiveRuntimeConfig.agentId) {
|
|
1787
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.agentId`, effectiveRuntimeConfig.agentId]);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1648
1790
|
|
|
1649
1791
|
// Legacy (memory) plugins need explicit targetUri/autoRecall/autoCapture (new version has defaults in config.ts)
|
|
1650
1792
|
if (resolvedPluginKind === "memory") {
|
|
@@ -1764,59 +1906,57 @@ async function writeOpenvikingEnv({ includePython }) {
|
|
|
1764
1906
|
return { shellPath: envPath };
|
|
1765
1907
|
}
|
|
1766
1908
|
|
|
1767
|
-
function wrapCommand(command, envFiles) {
|
|
1768
|
-
if (!envFiles) return command;
|
|
1769
|
-
if (IS_WIN) return `call "${envFiles.shellPath}" && ${command}`;
|
|
1770
|
-
return `source '${envFiles.shellPath.replace(/'/g, "'\"'\"'")}' && ${command}`;
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
function getExistingEnvFiles() {
|
|
1774
|
-
if (IS_WIN) {
|
|
1775
|
-
const batPath = join(OPENCLAW_DIR, "openviking.env.bat");
|
|
1776
|
-
const ps1Path = join(OPENCLAW_DIR, "openviking.env.ps1");
|
|
1777
|
-
if (existsSync(batPath)) {
|
|
1778
|
-
return { shellPath: batPath, powershellPath: existsSync(ps1Path) ? ps1Path : undefined };
|
|
1779
|
-
}
|
|
1780
|
-
if (existsSync(ps1Path)) {
|
|
1781
|
-
return { shellPath: ps1Path, powershellPath: ps1Path };
|
|
1782
|
-
}
|
|
1783
|
-
return null;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
const envPath = join(OPENCLAW_DIR, "openviking.env");
|
|
1787
|
-
return existsSync(envPath) ? { shellPath: envPath } : null;
|
|
1788
|
-
}
|
|
1789
|
-
|
|
1790
|
-
function ensureExistingPluginForUpgrade() {
|
|
1791
|
-
if (!existsSync(PLUGIN_DEST)) {
|
|
1792
|
-
err(
|
|
1793
|
-
tr(
|
|
1794
|
-
`Plugin upgrade mode expects an existing plugin at ${PLUGIN_DEST}. Run the full installer first if this is a fresh install.`,
|
|
1795
|
-
`鎻掍欢鍗囩骇妯″紡瑕佹眰鐜版湁鎻掍欢宸插畨瑁呭湪 ${PLUGIN_DEST}銆傚鏄娆″畨瑁咃紝璇疯繍琛屽叏閲忓畨瑁呮祦绋嬨€?`,
|
|
1796
|
-
),
|
|
1797
|
-
);
|
|
1798
|
-
process.exit(1);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
async function main() {
|
|
1909
|
+
function wrapCommand(command, envFiles) {
|
|
1910
|
+
if (!envFiles) return command;
|
|
1911
|
+
if (IS_WIN) return `call "${envFiles.shellPath}" && ${command}`;
|
|
1912
|
+
return `source '${envFiles.shellPath.replace(/'/g, "'\"'\"'")}' && ${command}`;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function getExistingEnvFiles() {
|
|
1916
|
+
if (IS_WIN) {
|
|
1917
|
+
const batPath = join(OPENCLAW_DIR, "openviking.env.bat");
|
|
1918
|
+
const ps1Path = join(OPENCLAW_DIR, "openviking.env.ps1");
|
|
1919
|
+
if (existsSync(batPath)) {
|
|
1920
|
+
return { shellPath: batPath, powershellPath: existsSync(ps1Path) ? ps1Path : undefined };
|
|
1921
|
+
}
|
|
1922
|
+
if (existsSync(ps1Path)) {
|
|
1923
|
+
return { shellPath: ps1Path, powershellPath: ps1Path };
|
|
1924
|
+
}
|
|
1925
|
+
return null;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
const envPath = join(OPENCLAW_DIR, "openviking.env");
|
|
1929
|
+
return existsSync(envPath) ? { shellPath: envPath } : null;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function ensureExistingPluginForUpgrade() {
|
|
1933
|
+
if (!existsSync(PLUGIN_DEST)) {
|
|
1934
|
+
err(
|
|
1935
|
+
tr(
|
|
1936
|
+
`Plugin upgrade mode expects an existing plugin at ${PLUGIN_DEST}. Run the full installer first if this is a fresh install.`,
|
|
1937
|
+
`鎻掍欢鍗囩骇妯″紡瑕佹眰鐜版湁鎻掍欢宸插畨瑁呭湪 ${PLUGIN_DEST}銆傚鏄娆″畨瑁咃紝璇疯繍琛屽叏閲忓畨瑁呮祦绋嬨€?`,
|
|
1938
|
+
),
|
|
1939
|
+
);
|
|
1940
|
+
process.exit(1);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
async function main() {
|
|
1803
1945
|
console.log("");
|
|
1804
1946
|
bold(tr("🦣 OpenClaw + OpenViking Installer", "🦣 OpenClaw + OpenViking 一键安装"));
|
|
1805
1947
|
console.log("");
|
|
1806
1948
|
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
}
|
|
1819
|
-
validateRequestedPluginVersion();
|
|
1949
|
+
ensurePluginOnlyOperationArgs();
|
|
1950
|
+
await selectWorkdir();
|
|
1951
|
+
if (rollbackLastUpgrade) {
|
|
1952
|
+
info(tr("Mode: rollback last plugin upgrade", "模式: 回滚最近一次插件升级"));
|
|
1953
|
+
if (PLUGIN_VERSION !== "main") {
|
|
1954
|
+
warn("--plugin-version is ignored in --rollback mode.");
|
|
1955
|
+
}
|
|
1956
|
+
await rollbackLastUpgradeOperation();
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
validateRequestedPluginVersion();
|
|
1820
1960
|
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
1821
1961
|
info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
|
|
1822
1962
|
info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
|
|
@@ -1824,31 +1964,28 @@ async function main() {
|
|
|
1824
1964
|
info(tr(`OpenViking version: ${openvikingVersion}`, `OpenViking 版本: ${openvikingVersion}`));
|
|
1825
1965
|
}
|
|
1826
1966
|
|
|
1827
|
-
if (upgradePluginOnly) {
|
|
1828
|
-
selectedMode = "local";
|
|
1829
|
-
info("Mode: plugin upgrade only (backup old plugin, clean only OpenViking plugin config, keep ov.conf)");
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
} else {
|
|
1834
|
-
await selectMode();
|
|
1835
|
-
}
|
|
1967
|
+
if (upgradePluginOnly) {
|
|
1968
|
+
selectedMode = "local";
|
|
1969
|
+
info("Mode: plugin upgrade only (backup old plugin, clean only OpenViking plugin config, keep ov.conf)");
|
|
1970
|
+
} else {
|
|
1971
|
+
await selectMode();
|
|
1972
|
+
}
|
|
1836
1973
|
info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
|
|
1837
1974
|
|
|
1838
|
-
if (upgradePluginOnly) {
|
|
1839
|
-
await checkOpenClaw();
|
|
1840
|
-
await resolvePluginConfig();
|
|
1841
|
-
await checkOpenClawCompatibility();
|
|
1842
|
-
await prepareStrongPluginUpgrade();
|
|
1843
|
-
} else if (selectedMode === "local") {
|
|
1844
|
-
await validateEnvironment();
|
|
1845
|
-
await checkOpenClaw();
|
|
1846
|
-
// Resolve plugin config after OpenClaw is available (for version detection)
|
|
1847
|
-
await resolvePluginConfig();
|
|
1848
|
-
await checkOpenClawCompatibility();
|
|
1849
|
-
checkRequestedOpenVikingCompatibility();
|
|
1850
|
-
await installOpenViking();
|
|
1851
|
-
await configureOvConf();
|
|
1975
|
+
if (upgradePluginOnly) {
|
|
1976
|
+
await checkOpenClaw();
|
|
1977
|
+
await resolvePluginConfig();
|
|
1978
|
+
await checkOpenClawCompatibility();
|
|
1979
|
+
await prepareStrongPluginUpgrade();
|
|
1980
|
+
} else if (selectedMode === "local") {
|
|
1981
|
+
await validateEnvironment();
|
|
1982
|
+
await checkOpenClaw();
|
|
1983
|
+
// Resolve plugin config after OpenClaw is available (for version detection)
|
|
1984
|
+
await resolvePluginConfig();
|
|
1985
|
+
await checkOpenClawCompatibility();
|
|
1986
|
+
checkRequestedOpenVikingCompatibility();
|
|
1987
|
+
await installOpenViking();
|
|
1988
|
+
await configureOvConf();
|
|
1852
1989
|
} else {
|
|
1853
1990
|
await checkOpenClaw();
|
|
1854
1991
|
await resolvePluginConfig();
|
|
@@ -1862,41 +1999,41 @@ async function main() {
|
|
|
1862
1999
|
pluginPath = localPluginDir;
|
|
1863
2000
|
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
|
|
1864
2001
|
info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
|
|
1865
|
-
await deployPluginFromLocal(pluginPath);
|
|
2002
|
+
await deployPluginFromLocal(pluginPath);
|
|
1866
2003
|
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
1867
2004
|
pluginPath = PLUGIN_DEST;
|
|
1868
2005
|
} else {
|
|
1869
|
-
await deployPluginFromRemote();
|
|
2006
|
+
await deployPluginFromRemote();
|
|
1870
2007
|
pluginPath = PLUGIN_DEST;
|
|
1871
2008
|
}
|
|
1872
2009
|
|
|
1873
|
-
await configureOpenClawPlugin(
|
|
1874
|
-
upgradePluginOnly
|
|
1875
|
-
? {
|
|
1876
|
-
runtimeConfig: upgradeRuntimeConfig,
|
|
1877
|
-
skipGatewayMode: true,
|
|
1878
|
-
claimSlot: installedUpgradeState ? shouldClaimTargetSlot(installedUpgradeState) : true,
|
|
1879
|
-
}
|
|
1880
|
-
: { preserveExistingConfig: false },
|
|
1881
|
-
);
|
|
1882
|
-
await writeInstallStateFile({
|
|
1883
|
-
operation: upgradePluginOnly ? "upgrade" : "install",
|
|
1884
|
-
fromVersion: upgradeAudit?.fromVersion || "",
|
|
1885
|
-
configBackupPath: upgradeAudit?.configBackupPath || "",
|
|
1886
|
-
pluginBackups: upgradeAudit?.pluginBackups || [],
|
|
1887
|
-
});
|
|
1888
|
-
if (upgradeAudit) {
|
|
1889
|
-
upgradeAudit.completedAt = new Date().toISOString();
|
|
1890
|
-
await writeUpgradeAuditFile(upgradeAudit);
|
|
1891
|
-
}
|
|
1892
|
-
let envFiles = getExistingEnvFiles();
|
|
1893
|
-
if (!upgradePluginOnly) {
|
|
1894
|
-
envFiles = await writeOpenvikingEnv({
|
|
1895
|
-
includePython: selectedMode === "local",
|
|
1896
|
-
});
|
|
1897
|
-
} else if (!envFiles && OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
|
|
1898
|
-
envFiles = await writeOpenvikingEnv({ includePython: false });
|
|
1899
|
-
}
|
|
2010
|
+
await configureOpenClawPlugin(
|
|
2011
|
+
upgradePluginOnly
|
|
2012
|
+
? {
|
|
2013
|
+
runtimeConfig: upgradeRuntimeConfig,
|
|
2014
|
+
skipGatewayMode: true,
|
|
2015
|
+
claimSlot: installedUpgradeState ? shouldClaimTargetSlot(installedUpgradeState) : true,
|
|
2016
|
+
}
|
|
2017
|
+
: { preserveExistingConfig: false },
|
|
2018
|
+
);
|
|
2019
|
+
await writeInstallStateFile({
|
|
2020
|
+
operation: upgradePluginOnly ? "upgrade" : "install",
|
|
2021
|
+
fromVersion: upgradeAudit?.fromVersion || "",
|
|
2022
|
+
configBackupPath: upgradeAudit?.configBackupPath || "",
|
|
2023
|
+
pluginBackups: upgradeAudit?.pluginBackups || [],
|
|
2024
|
+
});
|
|
2025
|
+
if (upgradeAudit) {
|
|
2026
|
+
upgradeAudit.completedAt = new Date().toISOString();
|
|
2027
|
+
await writeUpgradeAuditFile(upgradeAudit);
|
|
2028
|
+
}
|
|
2029
|
+
let envFiles = getExistingEnvFiles();
|
|
2030
|
+
if (!upgradePluginOnly) {
|
|
2031
|
+
envFiles = await writeOpenvikingEnv({
|
|
2032
|
+
includePython: selectedMode === "local",
|
|
2033
|
+
});
|
|
2034
|
+
} else if (!envFiles && OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
|
|
2035
|
+
envFiles = await writeOpenvikingEnv({ includePython: false });
|
|
2036
|
+
}
|
|
1900
2037
|
|
|
1901
2038
|
console.log("");
|
|
1902
2039
|
bold("═══════════════════════════════════════════════════════════");
|
|
@@ -1904,17 +2041,17 @@ async function main() {
|
|
|
1904
2041
|
bold("═══════════════════════════════════════════════════════════");
|
|
1905
2042
|
console.log("");
|
|
1906
2043
|
|
|
1907
|
-
if (upgradeAudit) {
|
|
1908
|
-
info(tr(`Upgrade path recorded: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`, `已记录升级路径: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`));
|
|
1909
|
-
info(tr(`Rollback config backup: ${upgradeAudit.configBackupPath}`, `回滚配置备份: ${upgradeAudit.configBackupPath}`));
|
|
1910
|
-
for (const pluginBackup of upgradeAudit.pluginBackups || []) {
|
|
1911
|
-
info(tr(`Rollback plugin backup: ${pluginBackup.backupDir}`, `回滚插件备份: ${pluginBackup.backupDir}`));
|
|
1912
|
-
}
|
|
1913
|
-
info(tr(`Rollback audit file: ${getUpgradeAuditPath()}`, `回滚审计文件: ${getUpgradeAuditPath()}`));
|
|
1914
|
-
console.log("");
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
if (selectedMode === "local") {
|
|
2044
|
+
if (upgradeAudit) {
|
|
2045
|
+
info(tr(`Upgrade path recorded: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`, `已记录升级路径: ${upgradeAudit.fromVersion} -> ${upgradeAudit.toVersion}`));
|
|
2046
|
+
info(tr(`Rollback config backup: ${upgradeAudit.configBackupPath}`, `回滚配置备份: ${upgradeAudit.configBackupPath}`));
|
|
2047
|
+
for (const pluginBackup of upgradeAudit.pluginBackups || []) {
|
|
2048
|
+
info(tr(`Rollback plugin backup: ${pluginBackup.backupDir}`, `回滚插件备份: ${pluginBackup.backupDir}`));
|
|
2049
|
+
}
|
|
2050
|
+
info(tr(`Rollback audit file: ${getUpgradeAuditPath()}`, `回滚审计文件: ${getUpgradeAuditPath()}`));
|
|
2051
|
+
console.log("");
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
if (selectedMode === "local") {
|
|
1918
2055
|
info(tr("Run these commands to start OpenClaw + OpenViking:", "请按以下命令启动 OpenClaw + OpenViking:"));
|
|
1919
2056
|
} else {
|
|
1920
2057
|
info(tr("Run these commands to start OpenClaw:", "请按以下命令启动 OpenClaw:"));
|