openclaw-openviking-setup-helper 0.3.0-beta.24 → 0.3.0-beta.26

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.
Files changed (2) hide show
  1. package/install.js +479 -367
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -29,12 +29,12 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
29
29
 
30
30
  let REPO = process.env.REPO || "volcengine/OpenViking";
31
31
  // PLUGIN_VERSION takes precedence over BRANCH (legacy). If omitted, resolve the latest tag from GitHub.
32
- const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || "").trim();
33
- let PLUGIN_VERSION = pluginVersionEnv;
34
- let pluginVersionExplicit = Boolean(pluginVersionEnv);
35
- const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
36
- const DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION = "2026.5.3";
37
- const OPENCLAW_SHORT_VERSION_YEAR = 2026;
32
+ const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || "").trim();
33
+ let PLUGIN_VERSION = pluginVersionEnv;
34
+ let pluginVersionExplicit = Boolean(pluginVersionEnv);
35
+ const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
36
+ const DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION = "2026.5.3";
37
+ const OPENCLAW_SHORT_VERSION_YEAR = 2026;
38
38
 
39
39
  const IS_WIN = process.platform === "win32";
40
40
  const HOME = process.env.HOME || process.env.USERPROFILE || "";
@@ -46,13 +46,13 @@ let PLUGIN_DEST = ""; // Will be set after resolving plugin config
46
46
  // Fallback configs for old versions without manifest
47
47
  const FALLBACK_LEGACY = {
48
48
  dir: "openclaw-memory-plugin",
49
- id: "memory-openviking",
50
- kind: "memory",
51
- slot: "memory",
52
- minOpenclawVersion: "2026.3.7",
53
- required: ["index.ts", "config.ts", "client.ts", "openclaw.plugin.json", "package.json"],
54
- optional: ["package-lock.json", ".gitignore", "memory-ranking.ts", "text-utils.ts", "process-manager.ts", "tsconfig.json"],
55
- };
49
+ id: "memory-openviking",
50
+ kind: "memory",
51
+ slot: "memory",
52
+ minOpenclawVersion: "2026.3.7",
53
+ required: ["index.ts", "config.ts", "client.ts", "openclaw.plugin.json", "package.json"],
54
+ optional: ["package-lock.json", ".gitignore", "memory-ranking.ts", "text-utils.ts", "process-manager.ts", "tsconfig.json"],
55
+ };
56
56
 
57
57
  // Must match examples/openclaw-plugin/install-manifest.json (npm only installs package deps, not these .ts files).
58
58
  const FALLBACK_CURRENT = {
@@ -60,7 +60,7 @@ const FALLBACK_CURRENT = {
60
60
  id: "openviking",
61
61
  kind: "context-engine",
62
62
  slot: "contextEngine",
63
- minOpenclawVersion: "2026.4.8",
63
+ minOpenclawVersion: "2026.4.8",
64
64
  required: ["index.ts", "config.ts", "package.json", "openclaw.plugin.json"],
65
65
  optional: [
66
66
  "context-engine.ts",
@@ -89,25 +89,27 @@ let resolvedPluginDir = "";
89
89
  let resolvedPluginId = "";
90
90
  let resolvedPluginKind = "";
91
91
  let resolvedPluginSlot = "";
92
- let resolvedFilesRequired = [];
93
- let resolvedFilesOptional = [];
94
- let resolvedNpmOmitDev = true;
95
- let resolvedNpmBuild = false;
96
- let resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
97
- let resolvedNpmBuildScript = "build";
98
- let resolvedNpmPruneAfterBuild = true;
99
- let resolvedMinOpenclawVersion = "";
100
- let resolvedMinOpenvikingVersion = "";
101
- let resolvedPluginReleaseId = "";
102
- let detectedOpenClawVersion = "";
92
+ let resolvedFilesRequired = [];
93
+ let resolvedFilesOptional = [];
94
+ let resolvedNpmOmitDev = true;
95
+ let resolvedNpmBuild = false;
96
+ let resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
97
+ let resolvedNpmBuildScript = "build";
98
+ let resolvedNpmPruneAfterBuild = true;
99
+ let resolvedMinOpenclawVersion = "";
100
+ let resolvedMinOpenvikingVersion = "";
101
+ let resolvedPluginReleaseId = "";
102
+ let detectedOpenClawVersion = "";
103
103
 
104
104
  let nonInteractive = false;
105
105
  let langZh = false;
106
106
  let workdirExplicit = false;
107
107
  let upgradePluginOnly = false;
108
108
  let rollbackLastUpgrade = false;
109
- let showCurrentVersion = false;
110
- let uninstallPlugin = false;
109
+ let showCurrentVersion = false;
110
+ let uninstallPlugin = false;
111
+ let forceSlotExplicit = false;
112
+ let allowOfflineExplicit = false;
111
113
 
112
114
  const selectedMode = "remote";
113
115
  const baseUrlFromEnv = !!process.env.OPENVIKING_BASE_URL;
@@ -147,11 +149,19 @@ for (let i = 0; i < argv.length; i++) {
147
149
  rollbackLastUpgrade = true;
148
150
  continue;
149
151
  }
150
- if (arg === "--uninstall" || arg === "--remove") {
151
- uninstallPlugin = true;
152
- continue;
153
- }
154
- if (arg === "--workdir") {
152
+ if (arg === "--uninstall" || arg === "--remove") {
153
+ uninstallPlugin = true;
154
+ continue;
155
+ }
156
+ if (arg === "--force-slot") {
157
+ forceSlotExplicit = true;
158
+ continue;
159
+ }
160
+ if (arg === "--allow-offline") {
161
+ allowOfflineExplicit = true;
162
+ continue;
163
+ }
164
+ if (arg === "--workdir") {
155
165
  const workdir = argv[i + 1]?.trim();
156
166
  if (!workdir) {
157
167
  console.error("--workdir requires a path");
@@ -282,9 +292,11 @@ function printHelp() {
282
292
  console.log(" --base-url=URL OpenViking server URL (default: $OPENVIKING_BASE_URL or http://127.0.0.1:1933)");
283
293
  console.log(" --api-key=KEY OpenViking API key (default: $OPENVIKING_API_KEY)");
284
294
  console.log(" --agent-prefix=PREFIX Agent routing prefix (default: $OPENVIKING_AGENT_PREFIX)");
285
- console.log(" --account-id=ID Account ID for root API key (default: $OPENVIKING_ACCOUNT_ID)");
286
- console.log(" --user-id=ID User ID for root API key (default: $OPENVIKING_USER_ID)");
287
- console.log(" --zh Chinese prompts");
295
+ console.log(" --account-id=ID Account ID for root API key (default: $OPENVIKING_ACCOUNT_ID)");
296
+ console.log(" --user-id=ID User ID for root API key (default: $OPENVIKING_USER_ID)");
297
+ console.log(" --force-slot Explicitly replace an existing contextEngine slot owner");
298
+ console.log(" --allow-offline Explicitly save config when the OpenViking server is unreachable");
299
+ console.log(" --zh Chinese prompts");
288
300
  console.log(" -h, --help This help");
289
301
  console.log("");
290
302
  console.log("Examples:");
@@ -403,47 +415,52 @@ function runCapture(cmd, args, opts = {}) {
403
415
  });
404
416
  }
405
417
 
406
- function question(prompt, defaultValue = "") {
407
- const rl = createInterface({ input: process.stdin, output: process.stdout });
408
- const suffix = defaultValue ? ` [${defaultValue}]` : "";
409
- return new Promise((resolve) => {
410
- rl.question(`${prompt}${suffix}: `, (answer) => {
418
+ function question(prompt, defaultValue = "") {
419
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
420
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
421
+ return new Promise((resolve) => {
422
+ rl.question(`${prompt}${suffix}: `, (answer) => {
411
423
  rl.close();
412
424
  resolve((answer ?? defaultValue).trim() || defaultValue);
413
425
  });
414
- });
415
- }
416
-
417
- function isValidAgentPrefixInput(value) {
418
- const trimmed = String(value || "").trim();
419
- return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
426
+ });
420
427
  }
421
428
 
422
- function parseJsonObjectFromOutput(output) {
423
- const text = String(output || "").trim();
424
- if (!text) return null;
425
- try {
426
- return JSON.parse(text);
427
- } catch {
428
- // OpenClaw may print plugin registration logs before --json output.
429
- }
430
- for (let index = text.lastIndexOf("{"); index >= 0; index = text.lastIndexOf("{", index - 1)) {
431
- try {
432
- const parsed = JSON.parse(text.slice(index).trim());
433
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
434
- return parsed;
435
- }
436
- } catch {
437
- // Keep scanning earlier braces until the outer JSON object is found.
438
- }
439
- }
440
- return null;
429
+ function isYes(answer) {
430
+ const normalized = String(answer || "").trim().toLowerCase();
431
+ return normalized === "y" || normalized === "yes";
441
432
  }
442
433
 
443
- async function questionAgentPrefix(defaultValue = "") {
444
- while (true) {
445
- const answer = (await question(
446
- tr("Agent Prefix (optional)", "Agent Prefix(可选)"),
434
+ function isValidAgentPrefixInput(value) {
435
+ const trimmed = String(value || "").trim();
436
+ return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
437
+ }
438
+
439
+ function parseJsonObjectFromOutput(output) {
440
+ const text = String(output || "").trim();
441
+ if (!text) return null;
442
+ try {
443
+ return JSON.parse(text);
444
+ } catch {
445
+ // OpenClaw may print plugin registration logs before --json output.
446
+ }
447
+ for (let index = text.lastIndexOf("{"); index >= 0; index = text.lastIndexOf("{", index - 1)) {
448
+ try {
449
+ const parsed = JSON.parse(text.slice(index).trim());
450
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
451
+ return parsed;
452
+ }
453
+ } catch {
454
+ // Keep scanning earlier braces until the outer JSON object is found.
455
+ }
456
+ }
457
+ return null;
458
+ }
459
+
460
+ async function questionAgentPrefix(defaultValue = "") {
461
+ while (true) {
462
+ const answer = (await question(
463
+ tr("Agent Prefix (optional)", "Agent Prefix(可选)"),
447
464
  defaultValue,
448
465
  )).trim();
449
466
  if (isValidAgentPrefixInput(answer)) {
@@ -530,68 +547,68 @@ async function checkOpenClaw() {
530
547
  }
531
548
 
532
549
  // Compare versions: returns true if v1 >= v2
533
- function versionGte(v1, v2) {
534
- const parseVersion = (v) => {
535
- const cleaned = v.replace(/^v/, "").replace(/-.*$/, "");
536
- const parts = cleaned.split(".").map((p) => Number.parseInt(p, 10) || 0);
537
- while (parts.length < 3) parts.push(0);
550
+ function versionGte(v1, v2) {
551
+ const parseVersion = (v) => {
552
+ const cleaned = v.replace(/^v/, "").replace(/-.*$/, "");
553
+ const parts = cleaned.split(".").map((p) => Number.parseInt(p, 10) || 0);
554
+ while (parts.length < 3) parts.push(0);
538
555
  return parts;
539
556
  };
540
557
  const [a1, a2, a3] = parseVersion(v1);
541
558
  const [b1, b2, b3] = parseVersion(v2);
542
559
  if (a1 !== b1) return a1 > b1;
543
- if (a2 !== b2) return a2 > b2;
544
- return a3 >= b3;
545
- }
546
-
547
- function parseOpenClawPolicyVersion(value) {
548
- const parts = String(value || "")
549
- .match(/\d+/g)
550
- ?.map((part) => Number.parseInt(part, 10) || 0) || [];
551
- if (parts.length === 0) return [0, 0, 0];
552
- if (parts[0] >= 2000) {
553
- return [parts[0], parts[1] || 0, parts[2] || 0];
554
- }
555
- return [OPENCLAW_SHORT_VERSION_YEAR, parts[0] || 0, parts[1] || 0];
556
- }
557
-
558
- function openClawPolicyVersionGte(v1, v2) {
559
- const a = parseOpenClawPolicyVersion(v1);
560
- const b = parseOpenClawPolicyVersion(v2);
561
- for (let i = 0; i < 3; i++) {
562
- if (a[i] !== b[i]) return a[i] > b[i];
563
- }
564
- return true;
565
- }
566
-
567
- function applyOpenClawBuildPolicy(openClawVersion) {
568
- if (!resolvedNpmBuild || !resolvedNpmBuildMinOpenclawVersion) {
569
- return;
570
- }
571
- if (!openClawVersion || openClawVersion === "0.0.0") {
572
- warn(tr(
573
- "Could not determine OpenClaw version; keeping plugin source build enabled.",
574
- "无法确定 OpenClaw 版本,保持插件源码构建开启。",
575
- ));
576
- return;
577
- }
578
- if (openClawPolicyVersionGte(openClawVersion, resolvedNpmBuildMinOpenclawVersion)) {
579
- info(tr(
580
- `OpenClaw ${openClawVersion} requires plugin source build (>= ${resolvedNpmBuildMinOpenclawVersion})`,
581
- `OpenClaw ${openClawVersion} 需要插件源码构建(>= ${resolvedNpmBuildMinOpenclawVersion})`,
582
- ));
583
- return;
584
- }
585
- resolvedNpmBuild = false;
586
- info(tr(
587
- `OpenClaw ${openClawVersion} is below ${resolvedNpmBuildMinOpenclawVersion}; skipping plugin source build`,
588
- `OpenClaw ${openClawVersion} 低于 ${resolvedNpmBuildMinOpenclawVersion},跳过插件源码构建`,
589
- ));
590
- }
591
-
592
- function isSemverLike(value) {
593
- return /^v?\d+(\.\d+){1,2}$/.test(value);
594
- }
560
+ if (a2 !== b2) return a2 > b2;
561
+ return a3 >= b3;
562
+ }
563
+
564
+ function parseOpenClawPolicyVersion(value) {
565
+ const parts = String(value || "")
566
+ .match(/\d+/g)
567
+ ?.map((part) => Number.parseInt(part, 10) || 0) || [];
568
+ if (parts.length === 0) return [0, 0, 0];
569
+ if (parts[0] >= 2000) {
570
+ return [parts[0], parts[1] || 0, parts[2] || 0];
571
+ }
572
+ return [OPENCLAW_SHORT_VERSION_YEAR, parts[0] || 0, parts[1] || 0];
573
+ }
574
+
575
+ function openClawPolicyVersionGte(v1, v2) {
576
+ const a = parseOpenClawPolicyVersion(v1);
577
+ const b = parseOpenClawPolicyVersion(v2);
578
+ for (let i = 0; i < 3; i++) {
579
+ if (a[i] !== b[i]) return a[i] > b[i];
580
+ }
581
+ return true;
582
+ }
583
+
584
+ function applyOpenClawBuildPolicy(openClawVersion) {
585
+ if (!resolvedNpmBuild || !resolvedNpmBuildMinOpenclawVersion) {
586
+ return;
587
+ }
588
+ if (!openClawVersion || openClawVersion === "0.0.0") {
589
+ warn(tr(
590
+ "Could not determine OpenClaw version; keeping plugin source build enabled.",
591
+ "无法确定 OpenClaw 版本,保持插件源码构建开启。",
592
+ ));
593
+ return;
594
+ }
595
+ if (openClawPolicyVersionGte(openClawVersion, resolvedNpmBuildMinOpenclawVersion)) {
596
+ info(tr(
597
+ `OpenClaw ${openClawVersion} requires plugin source build (>= ${resolvedNpmBuildMinOpenclawVersion})`,
598
+ `OpenClaw ${openClawVersion} 需要插件源码构建(>= ${resolvedNpmBuildMinOpenclawVersion})`,
599
+ ));
600
+ return;
601
+ }
602
+ resolvedNpmBuild = false;
603
+ info(tr(
604
+ `OpenClaw ${openClawVersion} is below ${resolvedNpmBuildMinOpenclawVersion}; skipping plugin source build`,
605
+ `OpenClaw ${openClawVersion} 低于 ${resolvedNpmBuildMinOpenclawVersion},跳过插件源码构建`,
606
+ ));
607
+ }
608
+
609
+ function isSemverLike(value) {
610
+ return /^v?\d+(\.\d+){1,2}$/.test(value);
611
+ }
595
612
 
596
613
  function validateRequestedPluginVersion() {
597
614
  if (!isSemverLike(PLUGIN_VERSION)) return;
@@ -612,24 +629,24 @@ if (uninstallPlugin && (upgradePluginOnly || rollbackLastUpgrade)) {
612
629
  }
613
630
 
614
631
  // Detect OpenClaw version
615
- async function detectOpenClawVersion() {
616
- if (detectedOpenClawVersion) {
617
- return detectedOpenClawVersion;
618
- }
619
- try {
620
- const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
621
- const output = `${result.out || ""}\n${result.err || ""}`;
622
- if (result.code === 0 && output) {
623
- const match = output.match(/\d+\.\d+(\.\d+)?/);
624
- if (match) {
625
- detectedOpenClawVersion = match[0];
626
- return detectedOpenClawVersion;
627
- }
628
- }
629
- } catch {}
630
- detectedOpenClawVersion = "0.0.0";
631
- return detectedOpenClawVersion;
632
- }
632
+ async function detectOpenClawVersion() {
633
+ if (detectedOpenClawVersion) {
634
+ return detectedOpenClawVersion;
635
+ }
636
+ try {
637
+ const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
638
+ const output = `${result.out || ""}\n${result.err || ""}`;
639
+ if (result.code === 0 && output) {
640
+ const match = output.match(/\d+\.\d+(\.\d+)?/);
641
+ if (match) {
642
+ detectedOpenClawVersion = match[0];
643
+ return detectedOpenClawVersion;
644
+ }
645
+ }
646
+ } catch {}
647
+ detectedOpenClawVersion = "0.0.0";
648
+ return detectedOpenClawVersion;
649
+ }
633
650
 
634
651
  // Try to fetch a URL, return response text or null
635
652
  async function tryFetch(url, timeout = 15000) {
@@ -772,19 +789,19 @@ async function resolveDefaultPluginVersion() {
772
789
  }
773
790
 
774
791
  // Resolve plugin configuration from manifest or fallback
775
- async function resolvePluginConfig() {
776
- const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
777
-
778
- info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
779
-
780
- resolvedNpmOmitDev = true;
781
- resolvedNpmBuild = false;
782
- resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
783
- resolvedNpmBuildScript = "build";
784
- resolvedNpmPruneAfterBuild = true;
785
-
786
- let pluginDir = "";
787
- let manifestData = null;
792
+ async function resolvePluginConfig() {
793
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
794
+
795
+ info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
796
+
797
+ resolvedNpmOmitDev = true;
798
+ resolvedNpmBuild = false;
799
+ resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
800
+ resolvedNpmBuildScript = "build";
801
+ resolvedNpmPruneAfterBuild = true;
802
+
803
+ let pluginDir = "";
804
+ let manifestData = null;
788
805
 
789
806
  // Try to detect plugin directory and download manifest
790
807
  const manifestCurrent = await tryFetch(`${ghRaw}/examples/openclaw-plugin/install-manifest.json`);
@@ -821,25 +838,25 @@ async function resolvePluginConfig() {
821
838
  resolvedPluginId = manifestData.plugin?.id || "";
822
839
  resolvedPluginKind = manifestData.plugin?.kind || "";
823
840
  resolvedPluginSlot = manifestData.plugin?.slot || "";
824
- resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
825
- resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
826
- resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
827
- const npmConfig = manifestData.npm && typeof manifestData.npm === "object"
828
- ? manifestData.npm
829
- : {};
830
- resolvedNpmOmitDev = npmConfig.omitDev !== false;
831
- resolvedNpmBuild = npmConfig.build === true || npmConfig.buildFromSource === true;
832
- resolvedNpmBuildMinOpenclawVersion =
833
- typeof npmConfig.buildMinOpenclawVersion === "string" && npmConfig.buildMinOpenclawVersion.trim()
834
- ? npmConfig.buildMinOpenclawVersion.trim()
835
- : DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
836
- resolvedNpmBuildScript = typeof npmConfig.buildScript === "string" && npmConfig.buildScript.trim()
837
- ? npmConfig.buildScript.trim()
838
- : "build";
839
- resolvedNpmPruneAfterBuild = npmConfig.pruneAfterBuild !== false;
840
- resolvedFilesRequired = manifestData.files?.required || [];
841
- resolvedFilesOptional = manifestData.files?.optional || [];
842
- } else {
841
+ resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
842
+ resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
843
+ resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
844
+ const npmConfig = manifestData.npm && typeof manifestData.npm === "object"
845
+ ? manifestData.npm
846
+ : {};
847
+ resolvedNpmOmitDev = npmConfig.omitDev !== false;
848
+ resolvedNpmBuild = npmConfig.build === true || npmConfig.buildFromSource === true;
849
+ resolvedNpmBuildMinOpenclawVersion =
850
+ typeof npmConfig.buildMinOpenclawVersion === "string" && npmConfig.buildMinOpenclawVersion.trim()
851
+ ? npmConfig.buildMinOpenclawVersion.trim()
852
+ : DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
853
+ resolvedNpmBuildScript = typeof npmConfig.buildScript === "string" && npmConfig.buildScript.trim()
854
+ ? npmConfig.buildScript.trim()
855
+ : "build";
856
+ resolvedNpmPruneAfterBuild = npmConfig.pruneAfterBuild !== false;
857
+ resolvedFilesRequired = manifestData.files?.required || [];
858
+ resolvedFilesOptional = manifestData.files?.optional || [];
859
+ } else {
843
860
  // No manifest — determine plugin identity by package.json name
844
861
  let fallbackKey = pluginDir === "openclaw-memory-plugin" ? "legacy" : "current";
845
862
  let compatVer = "";
@@ -868,15 +885,15 @@ async function resolvePluginConfig() {
868
885
  resolvedPluginId = fallback.id;
869
886
  resolvedPluginKind = fallback.kind;
870
887
  resolvedPluginSlot = fallback.slot;
871
- resolvedFilesRequired = fallback.required;
872
- resolvedFilesOptional = fallback.optional;
873
- resolvedNpmOmitDev = true;
874
- resolvedNpmBuild = false;
875
- resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
876
- resolvedNpmBuildScript = "build";
877
- resolvedNpmPruneAfterBuild = true;
878
-
879
- // If no compatVer from package.json, try main branch manifest
888
+ resolvedFilesRequired = fallback.required;
889
+ resolvedFilesOptional = fallback.optional;
890
+ resolvedNpmOmitDev = true;
891
+ resolvedNpmBuild = false;
892
+ resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
893
+ resolvedNpmBuildScript = "build";
894
+ resolvedNpmPruneAfterBuild = true;
895
+
896
+ // If no compatVer from package.json, try main branch manifest
880
897
  if (!compatVer && PLUGIN_VERSION !== "main") {
881
898
  const mainRaw = `https://raw.githubusercontent.com/${REPO}/main`;
882
899
  const mainManifest = await tryFetch(`${mainRaw}/examples/openclaw-plugin/install-manifest.json`);
@@ -907,12 +924,12 @@ async function checkOpenClawCompatibility() {
907
924
  return;
908
925
  }
909
926
 
910
- const ocVersion = await detectOpenClawVersion();
911
- info(tr(`Detected OpenClaw version: ${ocVersion}`, `检测到 OpenClaw 版本: ${ocVersion}`));
912
- applyOpenClawBuildPolicy(ocVersion);
913
-
914
- // If no minimum version required, pass
915
- if (!resolvedMinOpenclawVersion) {
927
+ const ocVersion = await detectOpenClawVersion();
928
+ info(tr(`Detected OpenClaw version: ${ocVersion}`, `检测到 OpenClaw 版本: ${ocVersion}`));
929
+ applyOpenClawBuildPolicy(ocVersion);
930
+
931
+ // If no minimum version required, pass
932
+ if (!resolvedMinOpenclawVersion) {
916
933
  return;
917
934
  }
918
935
 
@@ -922,7 +939,7 @@ async function checkOpenClawCompatibility() {
922
939
  }
923
940
 
924
941
  // Check compatibility
925
- if (!versionGte(ocVersion, resolvedMinOpenclawVersion)) {
942
+ if (!openClawPolicyVersionGte(ocVersion, resolvedMinOpenclawVersion)) {
926
943
  err(tr(
927
944
  `OpenClaw ${ocVersion} does not support this plugin (requires >= ${resolvedMinOpenclawVersion})`,
928
945
  `OpenClaw ${ocVersion} 不支持此插件(需要 >= ${resolvedMinOpenclawVersion})`
@@ -1086,11 +1103,17 @@ function extractRuntimeConfigFromPluginEntry(entryConfig) {
1086
1103
  runtime.apiKey = entryConfig.apiKey;
1087
1104
  }
1088
1105
  const prefix = entryConfig.agent_prefix || entryConfig.agentId;
1089
- if (typeof prefix === "string" && prefix.trim()) {
1090
- runtime.agent_prefix = prefix.trim();
1091
- }
1092
- return runtime;
1093
- }
1106
+ if (typeof prefix === "string" && prefix.trim()) {
1107
+ runtime.agent_prefix = prefix.trim();
1108
+ }
1109
+ if (typeof entryConfig.accountId === "string" && entryConfig.accountId.trim()) {
1110
+ runtime.accountId = entryConfig.accountId.trim();
1111
+ }
1112
+ if (typeof entryConfig.userId === "string" && entryConfig.userId.trim()) {
1113
+ runtime.userId = entryConfig.userId.trim();
1114
+ }
1115
+ return runtime;
1116
+ }
1094
1117
 
1095
1118
  async function backupOpenClawConfig(configPath) {
1096
1119
  await mkdir(getUpgradeAuditDir(), { recursive: true });
@@ -1380,10 +1403,12 @@ async function prepareStrongPluginUpgrade() {
1380
1403
  `检测到已安装 OpenViking 插件状态: ${installedState.generation}`,
1381
1404
  ),
1382
1405
  );
1383
- remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
1384
- remoteApiKey = upgradeRuntimeConfig.apiKey || "";
1385
- remoteAgentPrefix = upgradeRuntimeConfig.agent_prefix || "";
1386
- info(tr(`Upgrade runtime mode: ${selectedMode} (remote OpenViking server)`, `升级运行模式: ${selectedMode}(远程 OpenViking 服务)`));
1406
+ remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
1407
+ remoteApiKey = upgradeRuntimeConfig.apiKey || "";
1408
+ remoteAgentPrefix = upgradeRuntimeConfig.agent_prefix || "";
1409
+ remoteAccountId = upgradeRuntimeConfig.accountId || "";
1410
+ remoteUserId = upgradeRuntimeConfig.userId || "";
1411
+ info(tr(`Upgrade runtime mode: ${selectedMode} (remote OpenViking server)`, `升级运行模式: ${selectedMode}(远程 OpenViking 服务)`));
1387
1412
 
1388
1413
  info(tr(`Upgrade path: ${fromVersion} -> ${toVersion}`, `升级路径: ${fromVersion} -> ${toVersion}`));
1389
1414
 
@@ -1418,7 +1443,7 @@ async function prepareStrongPluginUpgrade() {
1418
1443
  info(tr(`Upgrade audit file: ${getUpgradeAuditPath()}`, `升级审计文件: ${getUpgradeAuditPath()}`));
1419
1444
  }
1420
1445
 
1421
- async function downloadPluginFile(destDir, fileName, url, required, index, total) {
1446
+ async function downloadPluginFile(destDir, fileName, url, required, index, total) {
1422
1447
  const maxRetries = 3;
1423
1448
  const destPath = join(destDir, fileName);
1424
1449
 
@@ -1478,109 +1503,109 @@ async function downloadPluginFile(destDir, fileName, url, required, index, total
1478
1503
 
1479
1504
  console.log("");
1480
1505
  err(tr(`Download failed after ${maxRetries} retries: ${url}`, `下载失败(已重试 ${maxRetries} 次): ${url}`));
1481
- process.exit(1);
1482
- }
1483
-
1484
- function runtimeOutputCandidatesForEntry(entry) {
1485
- const normalized = String(entry || "").replace(/\\/g, "/").replace(/^\.\//, "");
1486
- if (!normalized.endsWith(".ts")) {
1487
- return [];
1488
- }
1489
- const withoutExt = normalized.slice(0, -3);
1490
- return [
1491
- `dist/${withoutExt}.js`,
1492
- `dist/${withoutExt}.mjs`,
1493
- `dist/${withoutExt}.cjs`,
1494
- `${withoutExt}.js`,
1495
- `${withoutExt}.mjs`,
1496
- `${withoutExt}.cjs`,
1497
- ];
1498
- }
1499
-
1500
- async function assertBuiltRuntimeOutputs(destDir) {
1501
- let pkg = null;
1502
- try {
1503
- pkg = JSON.parse(await readFile(join(destDir, "package.json"), "utf8"));
1504
- } catch {
1505
- return;
1506
- }
1507
-
1508
- const entries = [];
1509
- const extensions = pkg?.openclaw?.extensions;
1510
- if (Array.isArray(extensions)) {
1511
- for (const entry of extensions) {
1512
- if (typeof entry === "string") entries.push(entry);
1513
- }
1514
- }
1515
- if (typeof pkg?.openclaw?.setupEntry === "string") {
1516
- entries.push(pkg.openclaw.setupEntry);
1517
- }
1518
-
1519
- const missing = [];
1520
- for (const entry of entries) {
1521
- const candidates = runtimeOutputCandidatesForEntry(entry);
1522
- if (candidates.length === 0) continue;
1523
- const found = candidates.some((candidate) => existsSync(join(destDir, ...candidate.split("/"))));
1524
- if (!found) {
1525
- missing.push(`${entry} (expected one of: ${candidates.join(", ")})`);
1526
- }
1527
- }
1528
-
1529
- if (missing.length === 0) {
1530
- return;
1531
- }
1532
-
1533
- err(tr(
1534
- `Plugin build did not create required runtime output:\n - ${missing.join("\n - ")}`,
1535
- `插件构建未生成必需的运行时产物:\n - ${missing.join("\n - ")}`,
1536
- ));
1537
- process.exit(1);
1538
- }
1539
-
1540
- async function installPluginNpmDependencies(destDir) {
1541
- if (!resolvedNpmBuild) {
1542
- info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
1543
- const npmArgs = resolvedNpmOmitDev
1544
- ? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
1545
- : ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
1546
- await run("npm", npmArgs, { cwd: destDir, silent: false });
1547
- return;
1548
- }
1549
-
1550
- info(tr(
1551
- "Installing plugin npm dependencies for source build...",
1552
- "正在安装插件源码构建所需的 npm 依赖...",
1553
- ));
1554
- await run("npm", [
1555
- "install",
1556
- "--include=dev",
1557
- "--no-audit",
1558
- "--no-fund",
1559
- "--registry",
1560
- NPM_REGISTRY,
1561
- ], { cwd: destDir, silent: false });
1562
-
1563
- info(tr(
1564
- `Building plugin runtime output with npm run ${resolvedNpmBuildScript}...`,
1565
- `正在执行 npm run ${resolvedNpmBuildScript} 构建插件运行时产物...`,
1566
- ));
1567
- await run("npm", ["run", resolvedNpmBuildScript], { cwd: destDir, silent: false });
1568
- await assertBuiltRuntimeOutputs(destDir);
1569
-
1570
- if (resolvedNpmOmitDev && resolvedNpmPruneAfterBuild) {
1571
- info(tr("Pruning plugin dev dependencies...", "正在裁剪插件开发依赖..."));
1572
- await run("npm", [
1573
- "prune",
1574
- "--omit=dev",
1575
- "--no-audit",
1576
- "--no-fund",
1577
- ], { cwd: destDir, silent: false });
1578
- }
1579
- }
1580
-
1581
- async function downloadPlugin(destDir) {
1582
- const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
1583
- const pluginDir = resolvedPluginDir;
1506
+ process.exit(1);
1507
+ }
1508
+
1509
+ function runtimeOutputCandidatesForEntry(entry) {
1510
+ const normalized = String(entry || "").replace(/\\/g, "/").replace(/^\.\//, "");
1511
+ if (!normalized.endsWith(".ts")) {
1512
+ return [];
1513
+ }
1514
+ const withoutExt = normalized.slice(0, -3);
1515
+ return [
1516
+ `dist/${withoutExt}.js`,
1517
+ `dist/${withoutExt}.mjs`,
1518
+ `dist/${withoutExt}.cjs`,
1519
+ `${withoutExt}.js`,
1520
+ `${withoutExt}.mjs`,
1521
+ `${withoutExt}.cjs`,
1522
+ ];
1523
+ }
1524
+
1525
+ async function assertBuiltRuntimeOutputs(destDir) {
1526
+ let pkg = null;
1527
+ try {
1528
+ pkg = JSON.parse(await readFile(join(destDir, "package.json"), "utf8"));
1529
+ } catch {
1530
+ return;
1531
+ }
1532
+
1533
+ const entries = [];
1534
+ const extensions = pkg?.openclaw?.extensions;
1535
+ if (Array.isArray(extensions)) {
1536
+ for (const entry of extensions) {
1537
+ if (typeof entry === "string") entries.push(entry);
1538
+ }
1539
+ }
1540
+ if (typeof pkg?.openclaw?.setupEntry === "string") {
1541
+ entries.push(pkg.openclaw.setupEntry);
1542
+ }
1543
+
1544
+ const missing = [];
1545
+ for (const entry of entries) {
1546
+ const candidates = runtimeOutputCandidatesForEntry(entry);
1547
+ if (candidates.length === 0) continue;
1548
+ const found = candidates.some((candidate) => existsSync(join(destDir, ...candidate.split("/"))));
1549
+ if (!found) {
1550
+ missing.push(`${entry} (expected one of: ${candidates.join(", ")})`);
1551
+ }
1552
+ }
1553
+
1554
+ if (missing.length === 0) {
1555
+ return;
1556
+ }
1557
+
1558
+ err(tr(
1559
+ `Plugin build did not create required runtime output:\n - ${missing.join("\n - ")}`,
1560
+ `插件构建未生成必需的运行时产物:\n - ${missing.join("\n - ")}`,
1561
+ ));
1562
+ process.exit(1);
1563
+ }
1564
+
1565
+ async function installPluginNpmDependencies(destDir) {
1566
+ if (!resolvedNpmBuild) {
1567
+ info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
1568
+ const npmArgs = resolvedNpmOmitDev
1569
+ ? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
1570
+ : ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
1571
+ await run("npm", npmArgs, { cwd: destDir, silent: false });
1572
+ return;
1573
+ }
1574
+
1575
+ info(tr(
1576
+ "Installing plugin npm dependencies for source build...",
1577
+ "正在安装插件源码构建所需的 npm 依赖...",
1578
+ ));
1579
+ await run("npm", [
1580
+ "install",
1581
+ "--include=dev",
1582
+ "--no-audit",
1583
+ "--no-fund",
1584
+ "--registry",
1585
+ NPM_REGISTRY,
1586
+ ], { cwd: destDir, silent: false });
1587
+
1588
+ info(tr(
1589
+ `Building plugin runtime output with npm run ${resolvedNpmBuildScript}...`,
1590
+ `正在执行 npm run ${resolvedNpmBuildScript} 构建插件运行时产物...`,
1591
+ ));
1592
+ await run("npm", ["run", resolvedNpmBuildScript], { cwd: destDir, silent: false });
1593
+ await assertBuiltRuntimeOutputs(destDir);
1594
+
1595
+ if (resolvedNpmOmitDev && resolvedNpmPruneAfterBuild) {
1596
+ info(tr("Pruning plugin dev dependencies...", "正在裁剪插件开发依赖..."));
1597
+ await run("npm", [
1598
+ "prune",
1599
+ "--omit=dev",
1600
+ "--no-audit",
1601
+ "--no-fund",
1602
+ ], { cwd: destDir, silent: false });
1603
+ }
1604
+ }
1605
+
1606
+ async function downloadPlugin(destDir) {
1607
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
1608
+ const pluginDir = resolvedPluginDir;
1584
1609
  const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
1585
1610
 
1586
1611
  await mkdir(destDir, { recursive: true });
@@ -1604,9 +1629,9 @@ async function downloadPlugin(destDir) {
1604
1629
  await downloadPluginFile(destDir, name, url, false, i, total);
1605
1630
  }
1606
1631
 
1607
- await installPluginNpmDependencies(destDir);
1608
- info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
1609
- }
1632
+ await installPluginNpmDependencies(destDir);
1633
+ info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
1634
+ }
1610
1635
 
1611
1636
  async function createPluginStagingDir() {
1612
1637
  const pluginId = resolvedPluginId || "openviking";
@@ -1768,6 +1793,53 @@ async function cleanupConflictingPluginVariants() {
1768
1793
  info(tr("Conflicting plugin variants cleaned up", "冲突的插件变体已清理"));
1769
1794
  }
1770
1795
 
1796
+ function normalizeOpenClawLoadPath(filePath) {
1797
+ return String(filePath || "")
1798
+ .replace(/\\/g, "/")
1799
+ .replace(/\/+$/g, "");
1800
+ }
1801
+
1802
+ async function ensureOpenClawPluginLoadPath() {
1803
+ const configPath = getOpenClawConfigPath();
1804
+ let cfg = {};
1805
+ if (existsSync(configPath)) {
1806
+ try {
1807
+ cfg = JSON.parse(await readFile(configPath, "utf8"));
1808
+ } catch {
1809
+ return;
1810
+ }
1811
+ }
1812
+
1813
+ const pluginPath = PLUGIN_DEST;
1814
+ const normalizedPluginPath = normalizeOpenClawLoadPath(pluginPath);
1815
+ const plugins = cfg.plugins && typeof cfg.plugins === "object" && !Array.isArray(cfg.plugins)
1816
+ ? cfg.plugins
1817
+ : {};
1818
+ const load = plugins.load && typeof plugins.load === "object" && !Array.isArray(plugins.load)
1819
+ ? plugins.load
1820
+ : {};
1821
+ const paths = Array.isArray(load.paths) ? load.paths : [];
1822
+ if (paths.some((item) => normalizeOpenClawLoadPath(item) === normalizedPluginPath)) {
1823
+ return;
1824
+ }
1825
+
1826
+ const next = {
1827
+ ...cfg,
1828
+ plugins: {
1829
+ ...plugins,
1830
+ load: {
1831
+ ...load,
1832
+ paths: [...paths, pluginPath],
1833
+ },
1834
+ },
1835
+ };
1836
+ await mkdir(dirname(configPath), { recursive: true });
1837
+ const tmp = `${configPath}.ov-install-tmp.${process.pid}`;
1838
+ await writeFile(tmp, `${JSON.stringify(next, null, 2)}\n`, "utf8");
1839
+ await rename(tmp, configPath);
1840
+ info(tr(`Added OpenClaw plugin load path: ${pluginPath}`, `已添加 OpenClaw 插件加载路径: ${pluginPath}`));
1841
+ }
1842
+
1771
1843
  async function configureOpenClawPlugin({
1772
1844
  preserveExistingConfig = false,
1773
1845
  runtimeConfig = null,
@@ -1821,6 +1893,8 @@ async function configureOpenClawPlugin({
1821
1893
  await scrubStaleOpenClawPluginRegistration();
1822
1894
  }
1823
1895
 
1896
+ await ensureOpenClawPluginLoadPath();
1897
+
1824
1898
  // Enable plugin: try CLI first (default path), fall back to direct file for --workdir
1825
1899
  if (!needWorkdirFlag) {
1826
1900
  try {
@@ -1932,12 +2006,13 @@ async function configureOpenClawPlugin({
1932
2006
  } catch { /* ignore read errors */ }
1933
2007
 
1934
2008
  let setupResult = null;
1935
- if (setupJsonSupported) {
2009
+ let parsed = null;
2010
+ const runSetupJson = async (extraArgs = []) => {
1936
2011
  const setupArgs = ["openviking", "setup"];
1937
- setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
1938
- setupArgs.push("--json");
1939
- if (effectiveRuntimeConfig.apiKey) {
1940
- setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
2012
+ setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
2013
+ setupArgs.push("--json");
2014
+ if (effectiveRuntimeConfig.apiKey) {
2015
+ setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
1941
2016
  }
1942
2017
  if (effectiveRuntimeConfig.agent_prefix) {
1943
2018
  setupArgs.push("--agent-prefix", effectiveRuntimeConfig.agent_prefix);
@@ -1945,35 +2020,67 @@ async function configureOpenClawPlugin({
1945
2020
  if (effectiveRuntimeConfig.accountId) {
1946
2021
  setupArgs.push("--account-id", effectiveRuntimeConfig.accountId);
1947
2022
  }
1948
- if (effectiveRuntimeConfig.userId) {
1949
- setupArgs.push("--user-id", effectiveRuntimeConfig.userId);
1950
- }
1951
- if (claimSlot) {
1952
- setupArgs.push("--force-slot");
1953
- }
1954
- if (nonInteractive) {
1955
- setupArgs.push("--allow-offline");
1956
- }
1957
-
1958
- info(tr(
1959
- "Delegating configuration to: openclaw openviking setup --json",
1960
- "委托配置给: openclaw openviking setup --json",
1961
- ));
1962
-
1963
- setupResult = await runCapture("openclaw", setupArgs, { env: ocEnv, shell: IS_WIN });
1964
- } else {
1965
- info(tr(
1966
- "Installed plugin does not support setup --json, using direct config write",
1967
- "已安装的插件不支持 setup --json,使用直接配置写入",
1968
- ));
1969
- }
1970
-
1971
- let parsed = null;
1972
- if (setupResult) {
1973
- parsed = parseJsonObjectFromOutput(`${setupResult.out || ""}\n${setupResult.err || ""}`);
2023
+ if (effectiveRuntimeConfig.userId) {
2024
+ setupArgs.push("--user-id", effectiveRuntimeConfig.userId);
2025
+ }
2026
+ if (forceSlotExplicit) {
2027
+ setupArgs.push("--force-slot");
2028
+ }
2029
+ if (allowOfflineExplicit) {
2030
+ setupArgs.push("--allow-offline");
2031
+ }
2032
+ setupArgs.push(...extraArgs);
2033
+
2034
+ const result = await runCapture("openclaw", setupArgs, { env: ocEnv, shell: IS_WIN });
2035
+ return {
2036
+ result,
2037
+ parsed: parseJsonObjectFromOutput(`${result.out || ""}\n${result.err || ""}`),
2038
+ };
2039
+ };
2040
+
2041
+ if (setupJsonSupported) {
2042
+ info(tr(
2043
+ "Delegating configuration to: openclaw openviking setup --json",
2044
+ "委托配置给: openclaw openviking setup --json",
2045
+ ));
2046
+
2047
+ ({ result: setupResult, parsed } = await runSetupJson());
2048
+ } else {
2049
+ info(tr(
2050
+ "Installed plugin does not support setup --json, using direct config write",
2051
+ "已安装的插件不支持 setup --json,使用直接配置写入",
2052
+ ));
1974
2053
  }
1975
2054
 
1976
- if (parsed) {
2055
+ if (parsed && !parsed.success && !nonInteractive) {
2056
+ if (parsed.action === "slot_blocked" && !forceSlotExplicit) {
2057
+ const answer = await question(
2058
+ tr(
2059
+ `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2060
+ `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2061
+ ),
2062
+ );
2063
+ if (isYes(answer)) {
2064
+ ({ result: setupResult, parsed } = await runSetupJson(["--force-slot"]));
2065
+ }
2066
+ } else if (
2067
+ typeof parsed.error === "string" &&
2068
+ parsed.error.includes("Server unreachable") &&
2069
+ !allowOfflineExplicit
2070
+ ) {
2071
+ const answer = await question(
2072
+ tr(
2073
+ "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2074
+ "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2075
+ ),
2076
+ );
2077
+ if (isYes(answer)) {
2078
+ ({ result: setupResult, parsed } = await runSetupJson(["--allow-offline"]));
2079
+ }
2080
+ }
2081
+ }
2082
+
2083
+ if (parsed) {
1977
2084
  if (parsed.success) {
1978
2085
  info(tr("OpenClaw plugin configured via setup", "OpenClaw 插件通过 setup 配置完成"));
1979
2086
  if (parsed.health?.ok) {
@@ -1991,32 +2098,37 @@ async function configureOpenClawPlugin({
1991
2098
  if (parsed.slot?.activated) {
1992
2099
  info(tr(`contextEngine slot activated`, `contextEngine slot 已激活`));
1993
2100
  }
1994
- } else {
1995
- // Setup returned success: false
1996
- if (parsed.action === "slot_blocked") {
1997
- warn(tr(
1998
- `Config saved but contextEngine slot is owned by "${parsed.slot?.previousOwner}". Use --force-slot to override.`,
1999
- `配置已保存,但 contextEngine slot "${parsed.slot?.previousOwner}" 占用。使用 --force-slot 覆盖。`,
2000
- ));
2001
- } else {
2002
- err(tr(
2003
- `Setup failed: ${parsed.error || "unknown error"}`,
2004
- `配置失败: ${parsed.error || "未知错误"}`,
2005
- ));
2006
- return {
2007
- runtimeConfigOk: false,
2008
- error: parsed.error || parsed.action || "unknown error",
2009
- };
2010
- }
2011
- }
2012
- } else if (setupResult && setupResult.code !== 0) {
2013
- warn(tr(
2014
- `openclaw openviking setup exited with code ${setupResult.code}. Falling back to direct JSON config write.`,
2015
- `openclaw openviking setup 退出码 ${setupResult.code},回退到直接写入 JSON 配置。`,
2016
- ));
2017
- }
2018
-
2019
- if (!parsed) {
2101
+ } else {
2102
+ // Setup returned success: false
2103
+ const setupError = parsed.error || parsed.action || "unknown error";
2104
+ if (parsed.action === "slot_blocked") {
2105
+ warn(tr(
2106
+ `Config saved but contextEngine slot is owned by "${parsed.slot?.previousOwner}". Use --force-slot to override.`,
2107
+ `配置已保存,但 contextEngine slot 被 "${parsed.slot?.previousOwner}" 占用。使用 --force-slot 覆盖。`,
2108
+ ));
2109
+ } else {
2110
+ err(tr(
2111
+ `Setup failed: ${setupError}`,
2112
+ `配置失败: ${setupError}`,
2113
+ ));
2114
+ }
2115
+ return {
2116
+ runtimeConfigOk: false,
2117
+ error: setupError,
2118
+ };
2119
+ }
2120
+ } else if (setupJsonSupported) {
2121
+ const setupError = setupResult
2122
+ ? `openclaw openviking setup did not return JSON (exit code ${setupResult.code})`
2123
+ : "openclaw openviking setup did not run";
2124
+ err(tr(`Setup failed: ${setupError}`, `配置失败: ${setupError}`));
2125
+ return {
2126
+ runtimeConfigOk: false,
2127
+ error: setupError,
2128
+ };
2129
+ }
2130
+
2131
+ if (!setupJsonSupported) {
2020
2132
  // Direct write: only used when the installed plugin doesn't support `setup --json` (old version).
2021
2133
  // Read the deployed configSchema to determine which fields are allowed, avoiding
2022
2134
  // "additionalProperties" validation failures when writing new fields to old schemas.
@@ -2153,7 +2265,7 @@ async function performUninstall() {
2153
2265
  tr("Confirm uninstall? (y/N)", "确认卸载?(y/N)"),
2154
2266
  "N",
2155
2267
  );
2156
- if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
2268
+ if (!isYes(answer)) {
2157
2269
  info(tr("Cancelled.", "已取消。"));
2158
2270
  return;
2159
2271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-openviking-setup-helper",
3
- "version": "0.3.0-beta.24",
3
+ "version": "0.3.0-beta.26",
4
4
  "description": "Setup helper for installing OpenViking memory plugin into OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {