openclaw-openviking-setup-helper 0.3.0-beta.25 → 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 +204 -92
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -106,8 +106,10 @@ 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,18 +415,23 @@ 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) {
426
+ });
427
+ }
428
+
429
+ function isYes(answer) {
430
+ const normalized = String(answer || "").trim().toLowerCase();
431
+ return normalized === "y" || normalized === "yes";
432
+ }
433
+
434
+ function isValidAgentPrefixInput(value) {
418
435
  const trimmed = String(value || "").trim();
419
436
  return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
420
437
  }
@@ -922,7 +939,7 @@ async function checkOpenClawCompatibility() {
922
939
  }
923
940
 
924
941
  // Check compatibility
925
- if (!openClawPolicyVersionGte(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
 
@@ -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 {
@@ -1931,13 +2005,14 @@ async function configureOpenClawPlugin({
1931
2005
  }
1932
2006
  } catch { /* ignore read errors */ }
1933
2007
 
1934
- let setupResult = null;
1935
- if (setupJsonSupported) {
1936
- const setupArgs = ["openviking", "setup"];
1937
- setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
1938
- setupArgs.push("--json");
1939
- if (effectiveRuntimeConfig.apiKey) {
1940
- setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
2008
+ let setupResult = null;
2009
+ let parsed = null;
2010
+ const runSetupJson = async (extraArgs = []) => {
2011
+ const setupArgs = ["openviking", "setup"];
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 || ""}`);
1974
- }
1975
-
1976
- if (parsed) {
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
+ ));
2053
+ }
2054
+
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.25",
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": {