openclaw-openviking-setup-helper 0.2.9-dev.1 → 0.2.9-dev.2
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 +853 -142
- 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 ]
|
|
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,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
|
-
import { cp, mkdir, 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
28
|
import { dirname, join, relative } from "node:path";
|
|
29
29
|
import { createInterface } from "node:readline";
|
|
@@ -61,14 +61,19 @@ const FALLBACK_LEGACY = {
|
|
|
61
61
|
optional: ["package-lock.json", ".gitignore"],
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
const FALLBACK_CURRENT = {
|
|
65
|
-
dir: "openclaw-plugin",
|
|
66
|
-
id: "openviking",
|
|
67
|
-
kind: "context-engine",
|
|
68
|
-
slot: "contextEngine",
|
|
69
|
-
required: ["index.ts", "config.ts", "openclaw.plugin.json", "package.json"],
|
|
70
|
-
optional: ["context-engine.ts", "client.ts", "process-manager.ts", "memory-ranking.ts", "text-utils.ts", "session-transcript-repair.ts", "tool-call-id.ts", "tsconfig.json", "package-lock.json", ".gitignore"],
|
|
71
|
-
};
|
|
64
|
+
const FALLBACK_CURRENT = {
|
|
65
|
+
dir: "openclaw-plugin",
|
|
66
|
+
id: "openviking",
|
|
67
|
+
kind: "context-engine",
|
|
68
|
+
slot: "contextEngine",
|
|
69
|
+
required: ["index.ts", "config.ts", "openclaw.plugin.json", "package.json"],
|
|
70
|
+
optional: ["context-engine.ts", "client.ts", "process-manager.ts", "memory-ranking.ts", "text-utils.ts", "session-transcript-repair.ts", "tool-call-id.ts", "tsconfig.json", "package-lock.json", ".gitignore"],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const PLUGIN_VARIANTS = [
|
|
74
|
+
{ ...FALLBACK_LEGACY, generation: "legacy", slotFallback: "none" },
|
|
75
|
+
{ ...FALLBACK_CURRENT, generation: "current", slotFallback: "legacy" },
|
|
76
|
+
];
|
|
72
77
|
|
|
73
78
|
// Resolved plugin config (set by resolvePluginConfig)
|
|
74
79
|
let resolvedPluginDir = "";
|
|
@@ -76,23 +81,29 @@ let resolvedPluginId = "";
|
|
|
76
81
|
let resolvedPluginKind = "";
|
|
77
82
|
let resolvedPluginSlot = "";
|
|
78
83
|
let resolvedFilesRequired = [];
|
|
79
|
-
let resolvedFilesOptional = [];
|
|
80
|
-
let resolvedNpmOmitDev = true;
|
|
81
|
-
let resolvedMinOpenclawVersion = "";
|
|
82
|
-
let resolvedMinOpenvikingVersion = "";
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
let
|
|
88
|
-
let
|
|
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;
|
|
89
97
|
|
|
90
98
|
let selectedMode = "local";
|
|
91
99
|
let selectedServerPort = DEFAULT_SERVER_PORT;
|
|
92
|
-
let remoteBaseUrl = "http://127.0.0.1:1933";
|
|
93
|
-
let remoteApiKey = "";
|
|
94
|
-
let remoteAgentId = "";
|
|
95
|
-
let openvikingPythonPath = "";
|
|
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;
|
|
96
107
|
|
|
97
108
|
const argv = process.argv.slice(2);
|
|
98
109
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -101,11 +112,19 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
101
112
|
installYes = true;
|
|
102
113
|
continue;
|
|
103
114
|
}
|
|
104
|
-
if (arg === "--zh") {
|
|
105
|
-
langZh = true;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (arg === "--
|
|
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") {
|
|
109
128
|
const workdir = argv[i + 1]?.trim();
|
|
110
129
|
if (!workdir) {
|
|
111
130
|
console.error("--workdir requires a path");
|
|
@@ -178,12 +197,16 @@ function printHelp() {
|
|
|
178
197
|
console.log("Options:");
|
|
179
198
|
console.log(" --github-repo=OWNER/REPO GitHub repository (default: volcengine/OpenViking)");
|
|
180
199
|
console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: main)");
|
|
181
|
-
console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)");
|
|
182
|
-
console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
|
|
183
|
-
console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)");
|
|
184
|
-
console.log("
|
|
185
|
-
console.log("
|
|
186
|
-
console.log("
|
|
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(" Backup old plugin, clean only OpenViking plugin config, keep ov.conf");
|
|
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");
|
|
187
210
|
console.log("");
|
|
188
211
|
console.log("Examples:");
|
|
189
212
|
console.log(" # Install latest version");
|
|
@@ -191,12 +214,18 @@ function printHelp() {
|
|
|
191
214
|
console.log("");
|
|
192
215
|
console.log(" # Install from a fork repository");
|
|
193
216
|
console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
|
|
194
|
-
console.log("");
|
|
195
|
-
console.log(" # Install specific plugin version");
|
|
196
|
-
console.log(" node install.js --plugin-version=v0.2.8");
|
|
197
|
-
console.log("");
|
|
198
|
-
console.log("
|
|
199
|
-
|
|
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
|
+
}
|
|
200
229
|
|
|
201
230
|
function tr(en, zh) {
|
|
202
231
|
return langZh ? zh : en;
|
|
@@ -477,12 +506,25 @@ function versionGte(v1, v2) {
|
|
|
477
506
|
return a3 >= b3;
|
|
478
507
|
}
|
|
479
508
|
|
|
480
|
-
function isSemverLike(value) {
|
|
481
|
-
return /^v?\d+(\.\d+){1,2}$/.test(value);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
+
// Detect OpenClaw version
|
|
527
|
+
async function detectOpenClawVersion() {
|
|
486
528
|
try {
|
|
487
529
|
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
488
530
|
if (result.code === 0 && result.out) {
|
|
@@ -556,28 +598,31 @@ async function resolvePluginConfig() {
|
|
|
556
598
|
}
|
|
557
599
|
}
|
|
558
600
|
|
|
559
|
-
resolvedPluginDir = pluginDir;
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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 {
|
|
571
615
|
// No manifest — determine plugin identity by package.json name
|
|
572
616
|
let fallbackKey = pluginDir === "openclaw-memory-plugin" ? "legacy" : "current";
|
|
573
617
|
let compatVer = "";
|
|
574
618
|
|
|
575
619
|
const pkgJson = await tryFetch(`${ghRaw}/examples/${pluginDir}/package.json`);
|
|
576
620
|
if (pkgJson) {
|
|
577
|
-
try {
|
|
578
|
-
const pkg = JSON.parse(pkgJson);
|
|
579
|
-
const pkgName = pkg.name || "";
|
|
580
|
-
|
|
621
|
+
try {
|
|
622
|
+
const pkg = JSON.parse(pkgJson);
|
|
623
|
+
const pkgName = pkg.name || "";
|
|
624
|
+
resolvedPluginReleaseId = pkg.version || "";
|
|
625
|
+
if (pkgName && pkgName !== "@openclaw/openviking") {
|
|
581
626
|
fallbackKey = "legacy";
|
|
582
627
|
info(tr(`Detected legacy plugin by package name: ${pkgName}`, `通过 package.json 名称检测到旧版插件: ${pkgName}`));
|
|
583
628
|
} else if (pkgName) {
|
|
@@ -639,9 +684,9 @@ async function checkOpenClawCompatibility() {
|
|
|
639
684
|
}
|
|
640
685
|
|
|
641
686
|
// If user explicitly requested an old version, pass
|
|
642
|
-
if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
687
|
+
if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.8")) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
645
690
|
|
|
646
691
|
// Check compatibility
|
|
647
692
|
if (!versionGte(ocVersion, resolvedMinOpenclawVersion)) {
|
|
@@ -656,7 +701,7 @@ async function checkOpenClawCompatibility() {
|
|
|
656
701
|
console.log(` npm update -g openclaw --registry ${NPM_REGISTRY}`);
|
|
657
702
|
console.log("");
|
|
658
703
|
console.log(` ${tr("Option 2: Install legacy plugin (v0.2.8)", "方案 2:安装旧版插件 (v0.2.8)")}`);
|
|
659
|
-
console.log(` node install.js --plugin-version=v0.2.
|
|
704
|
+
console.log(` node install.js --plugin-version=v0.2.6${langZh ? " --zh" : ""}`);
|
|
660
705
|
console.log("");
|
|
661
706
|
process.exit(1);
|
|
662
707
|
}
|
|
@@ -883,8 +928,507 @@ async function configureOvConf() {
|
|
|
883
928
|
info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
|
|
884
929
|
}
|
|
885
930
|
|
|
886
|
-
|
|
887
|
-
|
|
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);
|
|
888
1432
|
|
|
889
1433
|
process.stdout.write(` [${index}/${total}] ${fileName} `);
|
|
890
1434
|
|
|
@@ -893,7 +1437,8 @@ async function downloadPluginFile(fileName, url, required, index, total) {
|
|
|
893
1437
|
const response = await fetch(url);
|
|
894
1438
|
if (response.ok) {
|
|
895
1439
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
896
|
-
await
|
|
1440
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
1441
|
+
await writeFile(destPath, buffer);
|
|
897
1442
|
console.log("✓");
|
|
898
1443
|
return;
|
|
899
1444
|
}
|
|
@@ -910,7 +1455,8 @@ async function downloadPluginFile(fileName, url, required, index, total) {
|
|
|
910
1455
|
|
|
911
1456
|
if (fileName === ".gitignore") {
|
|
912
1457
|
console.log(tr("(retries failed, using minimal .gitignore)", "(重试失败,使用最小 .gitignore)"));
|
|
913
|
-
await
|
|
1458
|
+
await mkdir(dirname(destPath), { recursive: true });
|
|
1459
|
+
await writeFile(destPath, "node_modules/\n", "utf8");
|
|
914
1460
|
return;
|
|
915
1461
|
}
|
|
916
1462
|
|
|
@@ -924,12 +1470,12 @@ async function downloadPluginFile(fileName, url, required, index, total) {
|
|
|
924
1470
|
process.exit(1);
|
|
925
1471
|
}
|
|
926
1472
|
|
|
927
|
-
async function downloadPlugin() {
|
|
1473
|
+
async function downloadPlugin(destDir) {
|
|
928
1474
|
const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
|
|
929
1475
|
const pluginDir = resolvedPluginDir;
|
|
930
1476
|
const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
|
|
931
1477
|
|
|
932
|
-
await mkdir(
|
|
1478
|
+
await mkdir(destDir, { recursive: true });
|
|
933
1479
|
|
|
934
1480
|
info(tr(`Downloading plugin from ${REPO}@${PLUGIN_VERSION} (${total} files)...`, `正在从 ${REPO}@${PLUGIN_VERSION} 下载插件(共 ${total} 个文件)...`));
|
|
935
1481
|
|
|
@@ -939,7 +1485,7 @@ async function downloadPlugin() {
|
|
|
939
1485
|
if (!name) continue;
|
|
940
1486
|
i++;
|
|
941
1487
|
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
942
|
-
await downloadPluginFile(name, url, true, i, total);
|
|
1488
|
+
await downloadPluginFile(destDir, name, url, true, i, total);
|
|
943
1489
|
}
|
|
944
1490
|
|
|
945
1491
|
// Download optional files
|
|
@@ -947,7 +1493,7 @@ async function downloadPlugin() {
|
|
|
947
1493
|
if (!name) continue;
|
|
948
1494
|
i++;
|
|
949
1495
|
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
950
|
-
await downloadPluginFile(name, url, false, i, total);
|
|
1496
|
+
await downloadPluginFile(destDir, name, url, false, i, total);
|
|
951
1497
|
}
|
|
952
1498
|
|
|
953
1499
|
// npm install
|
|
@@ -955,35 +1501,91 @@ async function downloadPlugin() {
|
|
|
955
1501
|
const npmArgs = resolvedNpmOmitDev
|
|
956
1502
|
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
957
1503
|
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
958
|
-
await run("npm", npmArgs, { cwd:
|
|
1504
|
+
await run("npm", npmArgs, { cwd: destDir, silent: false });
|
|
959
1505
|
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
960
1506
|
}
|
|
961
1507
|
|
|
962
|
-
async function deployLocalPlugin(localPluginDir) {
|
|
963
|
-
await rm(
|
|
964
|
-
await mkdir(
|
|
965
|
-
await cp(localPluginDir,
|
|
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, {
|
|
966
1512
|
recursive: true,
|
|
967
1513
|
force: true,
|
|
968
1514
|
filter: (sourcePath) => {
|
|
969
1515
|
const rel = relative(localPluginDir, sourcePath);
|
|
970
1516
|
if (!rel) return true;
|
|
971
1517
|
const firstSegment = rel.split(/[\\/]/)[0];
|
|
972
|
-
return firstSegment !== "node_modules" && firstSegment !== ".git";
|
|
973
|
-
},
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
async function
|
|
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
|
+
async function deployPluginFromLocal(localPluginDir) {
|
|
1566
|
+
const stagingDir = await createPluginStagingDir();
|
|
1567
|
+
try {
|
|
1568
|
+
await deployLocalPlugin(localPluginDir, stagingDir);
|
|
1569
|
+
await installPluginDependencies(stagingDir);
|
|
1570
|
+
await finalizePluginDeployment(stagingDir);
|
|
1571
|
+
} catch (error) {
|
|
1572
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
1573
|
+
throw error;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
async function configureOpenClawPlugin({
|
|
1578
|
+
preserveExistingConfig = false,
|
|
1579
|
+
runtimeConfig = null,
|
|
1580
|
+
skipGatewayMode = false,
|
|
1581
|
+
claimSlot = true,
|
|
1582
|
+
} = {}) {
|
|
978
1583
|
info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
|
|
979
1584
|
|
|
980
1585
|
const pluginId = resolvedPluginId;
|
|
981
1586
|
const pluginSlot = resolvedPluginSlot;
|
|
982
1587
|
|
|
983
|
-
const ocEnv =
|
|
984
|
-
if (OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
|
|
985
|
-
ocEnv.OPENCLAW_STATE_DIR = OPENCLAW_DIR;
|
|
986
|
-
}
|
|
1588
|
+
const ocEnv = getOpenClawEnv();
|
|
987
1589
|
|
|
988
1590
|
const oc = async (args) => {
|
|
989
1591
|
const result = await runCapture("openclaw", args, { env: ocEnv, shell: IS_WIN });
|
|
@@ -994,29 +1596,55 @@ async function configureOpenClawPlugin() {
|
|
|
994
1596
|
return result;
|
|
995
1597
|
};
|
|
996
1598
|
|
|
997
|
-
// Enable plugin (files already deployed to extensions dir by deployPlugin)
|
|
998
|
-
await oc(["plugins", "enable", pluginId]);
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1599
|
+
// Enable plugin (files already deployed to extensions dir by deployPlugin)
|
|
1600
|
+
await oc(["plugins", "enable", pluginId]);
|
|
1601
|
+
if (claimSlot) {
|
|
1602
|
+
await oc(["config", "set", `plugins.slots.${pluginSlot}`, pluginId]);
|
|
1603
|
+
} else {
|
|
1604
|
+
warn(
|
|
1605
|
+
tr(
|
|
1606
|
+
`Skipped claiming plugins.slots.${pluginSlot}; it is currently owned by another plugin.`,
|
|
1607
|
+
`已跳过设置 plugins.slots.${pluginSlot},当前该 slot 由其他插件占用。`,
|
|
1608
|
+
),
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
if (preserveExistingConfig) {
|
|
1613
|
+
info(
|
|
1614
|
+
tr(
|
|
1615
|
+
`Preserved existing plugin runtime config for ${pluginId}`,
|
|
1616
|
+
`宸蹭繚鐣?${pluginId} 鐨勭幇鏈夋彃浠惰繍琛岄厤缃?`,
|
|
1617
|
+
),
|
|
1618
|
+
);
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const effectiveRuntimeConfig = runtimeConfig || (
|
|
1623
|
+
selectedMode === "remote"
|
|
1624
|
+
? { mode: "remote", baseUrl: remoteBaseUrl, apiKey: remoteApiKey, agentId: remoteAgentId }
|
|
1625
|
+
: { mode: "local", configPath: join(OPENVIKING_DIR, "ov.conf"), port: selectedServerPort }
|
|
1626
|
+
);
|
|
1627
|
+
|
|
1628
|
+
if (!skipGatewayMode) {
|
|
1629
|
+
await oc(["config", "set", "gateway.mode", effectiveRuntimeConfig.mode === "remote" ? "remote" : "local"]);
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Set plugin config for the selected mode
|
|
1633
|
+
if (effectiveRuntimeConfig.mode === "local") {
|
|
1634
|
+
const ovConfPath = effectiveRuntimeConfig.configPath || join(OPENVIKING_DIR, "ov.conf");
|
|
1635
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "local"]);
|
|
1636
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.configPath`, ovConfPath]);
|
|
1637
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.port`, String(effectiveRuntimeConfig.port || DEFAULT_SERVER_PORT)]);
|
|
1638
|
+
} else {
|
|
1639
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "remote"]);
|
|
1640
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.baseUrl`, effectiveRuntimeConfig.baseUrl || remoteBaseUrl]);
|
|
1641
|
+
if (effectiveRuntimeConfig.apiKey) {
|
|
1642
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.apiKey`, effectiveRuntimeConfig.apiKey]);
|
|
1643
|
+
}
|
|
1644
|
+
if (effectiveRuntimeConfig.agentId) {
|
|
1645
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.agentId`, effectiveRuntimeConfig.agentId]);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1020
1648
|
|
|
1021
1649
|
// Legacy (memory) plugins need explicit targetUri/autoRecall/autoCapture (new version has defaults in config.ts)
|
|
1022
1650
|
if (resolvedPluginKind === "memory") {
|
|
@@ -1136,18 +1764,59 @@ async function writeOpenvikingEnv({ includePython }) {
|
|
|
1136
1764
|
return { shellPath: envPath };
|
|
1137
1765
|
}
|
|
1138
1766
|
|
|
1139
|
-
function wrapCommand(command, envFiles) {
|
|
1140
|
-
if (!envFiles) return command;
|
|
1141
|
-
if (IS_WIN) return `call "${envFiles.shellPath}" && ${command}`;
|
|
1142
|
-
return `source '${envFiles.shellPath.replace(/'/g, "'\"'\"'")}' && ${command}`;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
|
|
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() {
|
|
1146
1803
|
console.log("");
|
|
1147
1804
|
bold(tr("🦣 OpenClaw + OpenViking Installer", "🦣 OpenClaw + OpenViking 一键安装"));
|
|
1148
1805
|
console.log("");
|
|
1149
1806
|
|
|
1150
|
-
await selectWorkdir();
|
|
1807
|
+
await selectWorkdir();
|
|
1808
|
+
if (rollbackLastUpgrade) {
|
|
1809
|
+
info(tr("Mode: rollback last plugin upgrade", "模式: 回滚最近一次插件升级"));
|
|
1810
|
+
if (PLUGIN_VERSION !== "main") {
|
|
1811
|
+
warn("--plugin-version is ignored in --rollback mode.");
|
|
1812
|
+
}
|
|
1813
|
+
if (openvikingVersion) {
|
|
1814
|
+
warn("--openviking-version is ignored in --rollback mode.");
|
|
1815
|
+
}
|
|
1816
|
+
await rollbackLastUpgradeOperation();
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
validateRequestedPluginVersion();
|
|
1151
1820
|
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
1152
1821
|
info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
|
|
1153
1822
|
info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
|
|
@@ -1155,18 +1824,31 @@ async function main() {
|
|
|
1155
1824
|
info(tr(`OpenViking version: ${openvikingVersion}`, `OpenViking 版本: ${openvikingVersion}`));
|
|
1156
1825
|
}
|
|
1157
1826
|
|
|
1158
|
-
|
|
1827
|
+
if (upgradePluginOnly) {
|
|
1828
|
+
selectedMode = "local";
|
|
1829
|
+
info("Mode: plugin upgrade only (backup old plugin, clean only OpenViking plugin config, keep ov.conf)");
|
|
1830
|
+
if (openvikingVersion) {
|
|
1831
|
+
warn("--openviking-version is ignored in --upgrade-plugin mode.");
|
|
1832
|
+
}
|
|
1833
|
+
} else {
|
|
1834
|
+
await selectMode();
|
|
1835
|
+
}
|
|
1159
1836
|
info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
|
|
1160
1837
|
|
|
1161
|
-
if (
|
|
1162
|
-
await
|
|
1163
|
-
await
|
|
1164
|
-
|
|
1165
|
-
await
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
await
|
|
1169
|
-
|
|
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();
|
|
1170
1852
|
} else {
|
|
1171
1853
|
await checkOpenClaw();
|
|
1172
1854
|
await resolvePluginConfig();
|
|
@@ -1180,22 +1862,41 @@ async function main() {
|
|
|
1180
1862
|
pluginPath = localPluginDir;
|
|
1181
1863
|
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
|
|
1182
1864
|
info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
|
|
1183
|
-
await
|
|
1865
|
+
await deployPluginFromLocal(pluginPath);
|
|
1184
1866
|
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
1185
|
-
const npmArgs = resolvedNpmOmitDev
|
|
1186
|
-
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1187
|
-
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1188
|
-
await run("npm", npmArgs, { cwd: PLUGIN_DEST, silent: false });
|
|
1189
1867
|
pluginPath = PLUGIN_DEST;
|
|
1190
1868
|
} else {
|
|
1191
|
-
await
|
|
1869
|
+
await deployPluginFromRemote();
|
|
1192
1870
|
pluginPath = PLUGIN_DEST;
|
|
1193
1871
|
}
|
|
1194
1872
|
|
|
1195
|
-
await configureOpenClawPlugin(
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
+
}
|
|
1199
1900
|
|
|
1200
1901
|
console.log("");
|
|
1201
1902
|
bold("═══════════════════════════════════════════════════════════");
|
|
@@ -1203,7 +1904,17 @@ async function main() {
|
|
|
1203
1904
|
bold("═══════════════════════════════════════════════════════════");
|
|
1204
1905
|
console.log("");
|
|
1205
1906
|
|
|
1206
|
-
if (
|
|
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") {
|
|
1207
1918
|
info(tr("Run these commands to start OpenClaw + OpenViking:", "请按以下命令启动 OpenClaw + OpenViking:"));
|
|
1208
1919
|
} else {
|
|
1209
1920
|
info(tr("Run these commands to start OpenClaw:", "请按以下命令启动 OpenClaw:"));
|