openclaw-openviking-setup-helper 0.3.1-beta.0 → 2026.6.18

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 +772 -613
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -9,34 +9,34 @@
9
9
  * ov-install
10
10
  * openclaw-openviking-install
11
11
  *
12
- * Direct run:
13
- * node install.js [ --base-url URL ] [ --api-key KEY ] [ --zh ] [ --workdir PATH ] [ --upgrade-plugin ]
14
- * [ --plugin-version=VERSION ] [ --plugin-source=npm|github ]
15
- *
16
- * Environment variables:
17
- * PLUGIN_SOURCE, PLUGIN_NPM_PACKAGE, REPO, PLUGIN_VERSION (or BRANCH),
18
- * OPENVIKING_BASE_URL, OPENVIKING_API_KEY, SKIP_OPENCLAW, NPM_REGISTRY
19
- */
12
+ * Direct run:
13
+ * node install.js [ --base-url URL ] [ --api-key KEY ] [ --zh ] [ --workdir PATH ] [ --upgrade-plugin ]
14
+ * [ --plugin-version=VERSION ] [ --plugin-source=npm|github ]
15
+ *
16
+ * Environment variables:
17
+ * PLUGIN_SOURCE, PLUGIN_NPM_PACKAGE, REPO, PLUGIN_VERSION (or BRANCH),
18
+ * OPENVIKING_BASE_URL, OPENVIKING_API_KEY, SKIP_OPENCLAW, NPM_REGISTRY
19
+ */
20
20
 
21
21
  import { spawn } from "node:child_process";
22
- import { cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
23
- import { existsSync, readdirSync } from "node:fs";
24
- import { basename, dirname, join } from "node:path";
25
- import { tmpdir } from "node:os";
26
- import { createInterface } from "node:readline";
27
- import { fileURLToPath } from "node:url";
22
+ import { cp, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
23
+ import { existsSync, readdirSync } from "node:fs";
24
+ import { basename, dirname, join } from "node:path";
25
+ import { tmpdir } from "node:os";
26
+ import { createInterface } from "node:readline";
27
+ import { fileURLToPath } from "node:url";
28
28
 
29
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
30
 
31
- let REPO = process.env.REPO || "volcengine/OpenViking";
32
- const DEFAULT_PLUGIN_NPM_PACKAGE = "@openviking/openclaw-plugin";
33
- let pluginNpmPackage = (process.env.PLUGIN_NPM_PACKAGE || DEFAULT_PLUGIN_NPM_PACKAGE).trim();
34
- let pluginSource = (process.env.PLUGIN_SOURCE || "npm").trim().toLowerCase();
35
- let pluginSourceExplicit = Boolean(process.env.PLUGIN_SOURCE);
36
- // PLUGIN_VERSION takes precedence over BRANCH (legacy). If omitted, resolve the latest npm dist-tag or GitHub tag.
37
- const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || "").trim();
38
- let PLUGIN_VERSION = pluginVersionEnv;
39
- let pluginVersionExplicit = Boolean(pluginVersionEnv);
31
+ let REPO = process.env.REPO || "volcengine/OpenViking";
32
+ const DEFAULT_PLUGIN_NPM_PACKAGE = "@openviking/openclaw-plugin";
33
+ let pluginNpmPackage = (process.env.PLUGIN_NPM_PACKAGE || DEFAULT_PLUGIN_NPM_PACKAGE).trim();
34
+ let pluginSource = (process.env.PLUGIN_SOURCE || "npm").trim().toLowerCase();
35
+ let pluginSourceExplicit = Boolean(process.env.PLUGIN_SOURCE);
36
+ // PLUGIN_VERSION takes precedence over BRANCH (legacy). If omitted, resolve the latest npm dist-tag or GitHub tag.
37
+ const pluginVersionEnv = (process.env.PLUGIN_VERSION || process.env.BRANCH || "").trim();
38
+ let PLUGIN_VERSION = pluginVersionEnv;
39
+ let pluginVersionExplicit = Boolean(pluginVersionEnv);
40
40
  const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
41
41
  const DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION = "2026.5.3";
42
42
  const OPENCLAW_SHORT_VERSION_YEAR = 2026;
@@ -102,27 +102,30 @@ let resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
102
102
  let resolvedNpmBuildScript = "build";
103
103
  let resolvedNpmPruneAfterBuild = true;
104
104
  let resolvedMinOpenclawVersion = "";
105
- let resolvedMinOpenvikingVersion = "";
106
- let resolvedPluginReleaseId = "";
107
- let detectedOpenClawVersion = "";
108
- let npmPackageTempDir = "";
109
- let npmPackageExtractDir = "";
105
+ let resolvedMinOpenvikingVersion = "";
106
+ let resolvedPluginReleaseId = "";
107
+ let detectedOpenClawVersion = "";
108
+ let npmPackageTempDir = "";
109
+ let npmPackageExtractDir = "";
110
110
 
111
111
  let nonInteractive = false;
112
112
  let langZh = false;
113
113
  let workdirExplicit = false;
114
114
  let upgradePluginOnly = false;
115
115
  let rollbackLastUpgrade = false;
116
- let showCurrentVersion = false;
117
- let uninstallPlugin = false;
118
- let forceSlotExplicit = false;
119
- let allowOfflineExplicit = false;
116
+ let showCurrentVersion = false;
117
+ let uninstallPlugin = false;
118
+ let forceSlotExplicit = false;
119
+ let allowOfflineExplicit = false;
120
120
 
121
121
  const selectedMode = "remote";
122
122
  const baseUrlFromEnv = !!process.env.OPENVIKING_BASE_URL;
123
123
  let remoteBaseUrl = (process.env.OPENVIKING_BASE_URL || "http://127.0.0.1:1933").trim();
124
124
  let remoteApiKey = (process.env.OPENVIKING_API_KEY || "").trim();
125
- let remoteAgentPrefix = (process.env.OPENVIKING_AGENT_PREFIX || "").trim();
125
+ let remotePeerRole = (process.env.OPENVIKING_PEER_ROLE || "").trim().toLowerCase();
126
+ let remotePeerPrefix = (process.env.OPENVIKING_PEER_PREFIX || "").trim();
127
+ let peerRoleExplicit = !!process.env.OPENVIKING_PEER_ROLE;
128
+ if (!remotePeerRole) remotePeerRole = "assistant";
126
129
  let remoteAccountId = (process.env.OPENVIKING_ACCOUNT_ID || "").trim();
127
130
  let remoteUserId = (process.env.OPENVIKING_USER_ID || "").trim();
128
131
  let baseUrlExplicit = baseUrlFromEnv;
@@ -156,19 +159,19 @@ for (let i = 0; i < argv.length; i++) {
156
159
  rollbackLastUpgrade = true;
157
160
  continue;
158
161
  }
159
- if (arg === "--uninstall" || arg === "--remove") {
160
- uninstallPlugin = true;
161
- continue;
162
- }
163
- if (arg === "--force-slot") {
164
- forceSlotExplicit = true;
165
- continue;
166
- }
167
- if (arg === "--allow-offline") {
168
- allowOfflineExplicit = true;
169
- continue;
170
- }
171
- if (arg === "--workdir") {
162
+ if (arg === "--uninstall" || arg === "--remove") {
163
+ uninstallPlugin = true;
164
+ continue;
165
+ }
166
+ if (arg === "--force-slot") {
167
+ forceSlotExplicit = true;
168
+ continue;
169
+ }
170
+ if (arg === "--allow-offline") {
171
+ allowOfflineExplicit = true;
172
+ continue;
173
+ }
174
+ if (arg === "--workdir") {
172
175
  const workdir = argv[i + 1]?.trim();
173
176
  if (!workdir) {
174
177
  console.error("--workdir requires a path");
@@ -185,81 +188,81 @@ for (let i = 0; i < argv.length; i++) {
185
188
  console.error("--plugin-version requires a value");
186
189
  process.exit(1);
187
190
  }
188
- PLUGIN_VERSION = version;
189
- pluginVersionExplicit = true;
190
- continue;
191
- }
191
+ PLUGIN_VERSION = version;
192
+ pluginVersionExplicit = true;
193
+ continue;
194
+ }
192
195
  if (arg === "--plugin-version") {
193
196
  const version = argv[i + 1]?.trim();
194
197
  if (!version) {
195
198
  console.error("--plugin-version requires a value");
196
199
  process.exit(1);
197
200
  }
198
- PLUGIN_VERSION = version;
199
- pluginVersionExplicit = true;
200
- i += 1;
201
- continue;
202
- }
203
- if (arg.startsWith("--github-repo=")) {
204
- REPO = arg.slice("--github-repo=".length).trim();
205
- if (!pluginSourceExplicit) {
206
- pluginSource = "github";
207
- }
208
- continue;
209
- }
210
- if (arg === "--github-repo") {
201
+ PLUGIN_VERSION = version;
202
+ pluginVersionExplicit = true;
203
+ i += 1;
204
+ continue;
205
+ }
206
+ if (arg.startsWith("--github-repo=")) {
207
+ REPO = arg.slice("--github-repo=".length).trim();
208
+ if (!pluginSourceExplicit) {
209
+ pluginSource = "github";
210
+ }
211
+ continue;
212
+ }
213
+ if (arg === "--github-repo") {
211
214
  const repo = argv[i + 1]?.trim();
212
215
  if (!repo) {
213
216
  console.error("--github-repo requires a value (e.g. owner/repo)");
214
217
  process.exit(1);
215
- }
216
- REPO = repo;
217
- if (!pluginSourceExplicit) {
218
- pluginSource = "github";
219
- }
220
- i += 1;
221
- continue;
222
- }
223
- if (arg.startsWith("--plugin-source=") || arg.startsWith("--source=")) {
224
- const value = arg.includes("--plugin-source=")
225
- ? arg.slice("--plugin-source=".length).trim()
226
- : arg.slice("--source=".length).trim();
227
- pluginSource = value.toLowerCase();
228
- pluginSourceExplicit = true;
229
- continue;
230
- }
231
- if (arg === "--plugin-source" || arg === "--source") {
232
- const value = argv[i + 1]?.trim();
233
- if (!value) {
234
- console.error(`${arg} requires a value (npm or github)`);
235
- process.exit(1);
236
- }
237
- pluginSource = value.toLowerCase();
238
- pluginSourceExplicit = true;
239
- i += 1;
240
- continue;
241
- }
242
- if (arg.startsWith("--plugin-package=") || arg.startsWith("--npm-package=")) {
243
- const value = arg.includes("--plugin-package=")
244
- ? arg.slice("--plugin-package=".length).trim()
245
- : arg.slice("--npm-package=".length).trim();
246
- if (!value) {
247
- console.error("--plugin-package requires a package name");
248
- process.exit(1);
249
- }
250
- pluginNpmPackage = value;
251
- continue;
252
- }
253
- if (arg === "--plugin-package" || arg === "--npm-package") {
254
- const value = argv[i + 1]?.trim();
255
- if (!value) {
256
- console.error(`${arg} requires a package name`);
257
- process.exit(1);
258
- }
259
- pluginNpmPackage = value;
260
- i += 1;
261
- continue;
262
- }
218
+ }
219
+ REPO = repo;
220
+ if (!pluginSourceExplicit) {
221
+ pluginSource = "github";
222
+ }
223
+ i += 1;
224
+ continue;
225
+ }
226
+ if (arg.startsWith("--plugin-source=") || arg.startsWith("--source=")) {
227
+ const value = arg.includes("--plugin-source=")
228
+ ? arg.slice("--plugin-source=".length).trim()
229
+ : arg.slice("--source=".length).trim();
230
+ pluginSource = value.toLowerCase();
231
+ pluginSourceExplicit = true;
232
+ continue;
233
+ }
234
+ if (arg === "--plugin-source" || arg === "--source") {
235
+ const value = argv[i + 1]?.trim();
236
+ if (!value) {
237
+ console.error(`${arg} requires a value (npm or github)`);
238
+ process.exit(1);
239
+ }
240
+ pluginSource = value.toLowerCase();
241
+ pluginSourceExplicit = true;
242
+ i += 1;
243
+ continue;
244
+ }
245
+ if (arg.startsWith("--plugin-package=") || arg.startsWith("--npm-package=")) {
246
+ const value = arg.includes("--plugin-package=")
247
+ ? arg.slice("--plugin-package=".length).trim()
248
+ : arg.slice("--npm-package=".length).trim();
249
+ if (!value) {
250
+ console.error("--plugin-package requires a package name");
251
+ process.exit(1);
252
+ }
253
+ pluginNpmPackage = value;
254
+ continue;
255
+ }
256
+ if (arg === "--plugin-package" || arg === "--npm-package") {
257
+ const value = argv[i + 1]?.trim();
258
+ if (!value) {
259
+ console.error(`${arg} requires a package name`);
260
+ process.exit(1);
261
+ }
262
+ pluginNpmPackage = value;
263
+ i += 1;
264
+ continue;
265
+ }
263
266
  if (arg === "--base-url") {
264
267
  const val = argv[i + 1]?.trim();
265
268
  if (!val) { console.error("--base-url requires a URL"); process.exit(1); }
@@ -284,15 +287,32 @@ for (let i = 0; i < argv.length; i++) {
284
287
  remoteApiKey = arg.slice("--api-key=".length).trim();
285
288
  continue;
286
289
  }
287
- if (arg === "--agent-prefix") {
290
+ if (arg === "--peer-role") {
291
+ const val = argv[i + 1]?.trim();
292
+ if (!val) { console.error("--peer-role requires a value"); process.exit(1); }
293
+ const role = normalizePeerRole(val);
294
+ if (!role) { console.error('--peer-role must be "none", "assistant", or "person"'); process.exit(1); }
295
+ remotePeerRole = role;
296
+ peerRoleExplicit = true;
297
+ i += 1;
298
+ continue;
299
+ }
300
+ if (arg.startsWith("--peer-role=")) {
301
+ const role = normalizePeerRole(arg.slice("--peer-role=".length));
302
+ if (!role) { console.error('--peer-role must be "none", "assistant", or "person"'); process.exit(1); }
303
+ remotePeerRole = role;
304
+ peerRoleExplicit = true;
305
+ continue;
306
+ }
307
+ if (arg === "--peer-prefix") {
288
308
  const val = argv[i + 1]?.trim();
289
- if (!val) { console.error("--agent-prefix requires a value"); process.exit(1); }
290
- remoteAgentPrefix = val;
309
+ if (!val) { console.error("--peer-prefix requires a value"); process.exit(1); }
310
+ remotePeerPrefix = val;
291
311
  i += 1;
292
312
  continue;
293
313
  }
294
- if (arg.startsWith("--agent-prefix=")) {
295
- remoteAgentPrefix = arg.slice("--agent-prefix=".length).trim();
314
+ if (arg.startsWith("--peer-prefix=")) {
315
+ remotePeerPrefix = arg.slice("--peer-prefix=".length).trim();
296
316
  continue;
297
317
  }
298
318
  if (arg === "--account-id") {
@@ -323,6 +343,12 @@ for (let i = 0; i < argv.length; i++) {
323
343
  }
324
344
  }
325
345
 
346
+ remotePeerRole = normalizePeerRole(remotePeerRole) || "assistant";
347
+ if (!isValidPeerPrefixInput(remotePeerPrefix)) {
348
+ console.error("--peer-prefix may only contain letters, digits, underscores, and hyphens");
349
+ process.exit(1);
350
+ }
351
+
326
352
  nonInteractive = baseUrlExplicit;
327
353
 
328
354
  function setOpenClawDir(dir) {
@@ -331,13 +357,13 @@ function setOpenClawDir(dir) {
331
357
 
332
358
  function printHelp() {
333
359
  console.log("Usage: node install.js [ OPTIONS ]");
334
- console.log("");
335
- console.log("Options:");
336
- console.log(" --plugin-source=npm|github");
337
- console.log(" Plugin download source (default: npm)");
338
- console.log(" --plugin-package=NAME npm plugin package (default: @openviking/openclaw-plugin)");
339
- console.log(" --github-repo=OWNER/REPO GitHub repository (implies --plugin-source=github unless source is set)");
340
- console.log(" --plugin-version=VERSION Plugin version (npm version/tag or Git tag; default: npm latest)");
360
+ console.log("");
361
+ console.log("Options:");
362
+ console.log(" --plugin-source=npm|github");
363
+ console.log(" Plugin download source (default: npm)");
364
+ console.log(" --plugin-package=NAME npm plugin package (default: @openviking/openclaw-plugin)");
365
+ console.log(" --github-repo=OWNER/REPO GitHub repository (implies --plugin-source=github unless source is set)");
366
+ console.log(" --plugin-version=VERSION Plugin version (npm version/tag or Git tag; default: npm latest)");
341
367
  console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
342
368
  console.log(" --current-version Print installed plugin version and exit");
343
369
  console.log(" --update, --upgrade-plugin");
@@ -347,12 +373,13 @@ function printHelp() {
347
373
  console.log(" --uninstall, --remove Uninstall OpenViking plugin from OpenClaw (backup config, remove plugin entries)");
348
374
  console.log(" --base-url=URL OpenViking server URL (default: $OPENVIKING_BASE_URL or http://127.0.0.1:1933)");
349
375
  console.log(" --api-key=KEY OpenViking API key (default: $OPENVIKING_API_KEY)");
350
- console.log(" --agent-prefix=PREFIX Agent routing prefix (default: $OPENVIKING_AGENT_PREFIX)");
351
- console.log(" --account-id=ID Account ID for root API key (default: $OPENVIKING_ACCOUNT_ID)");
352
- console.log(" --user-id=ID User ID for root API key (default: $OPENVIKING_USER_ID)");
353
- console.log(" --force-slot Explicitly replace an existing contextEngine slot owner");
354
- console.log(" --allow-offline Explicitly save config when the OpenViking server is unreachable");
355
- console.log(" --zh Chinese prompts");
376
+ console.log(" --peer-role=ROLE Peer role: none, assistant, or person (default: $OPENVIKING_PEER_ROLE or assistant)");
377
+ console.log(" --peer-prefix=PREFIX Prefix for assistant peer_id values (default: $OPENVIKING_PEER_PREFIX)");
378
+ console.log(" --account-id=ID Account ID for root API key (default: $OPENVIKING_ACCOUNT_ID)");
379
+ console.log(" --user-id=ID User ID for root API key (default: $OPENVIKING_USER_ID)");
380
+ console.log(" --force-slot Explicitly replace an existing contextEngine slot owner");
381
+ console.log(" --allow-offline Explicitly save config when the OpenViking server is unreachable");
382
+ console.log(" --zh Chinese prompts");
356
383
  console.log(" -h, --help This help");
357
384
  console.log("");
358
385
  console.log("Examples:");
@@ -362,11 +389,11 @@ function printHelp() {
362
389
  console.log(" # Show installed versions");
363
390
  console.log(" node install.js --current-version");
364
391
  console.log("");
365
- console.log(" # Install a specific release version");
366
- console.log(" node install.js --plugin-version=2026.5.8");
367
- console.log("");
368
- console.log(" # Install from a fork repository");
369
- console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
392
+ console.log(" # Install a specific release version");
393
+ console.log(" node install.js --plugin-version=2026.6.18");
394
+ console.log("");
395
+ console.log(" # Install from a fork repository");
396
+ console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
370
397
  console.log("");
371
398
  console.log(" # Install specific plugin version");
372
399
  console.log(" node install.js --plugin-version=v0.2.8");
@@ -377,8 +404,8 @@ function printHelp() {
377
404
  console.log(" # Roll back the last plugin upgrade");
378
405
  console.log(" node install.js --rollback");
379
406
  console.log("");
380
- console.log("Env: PLUGIN_SOURCE, PLUGIN_NPM_PACKAGE, REPO, PLUGIN_VERSION, SKIP_OPENCLAW, NPM_REGISTRY");
381
- }
407
+ console.log("Env: PLUGIN_SOURCE, PLUGIN_NPM_PACKAGE, REPO, PLUGIN_VERSION, SKIP_OPENCLAW, NPM_REGISTRY");
408
+ }
382
409
 
383
410
  function formatCliArg(value) {
384
411
  if (!value) {
@@ -471,27 +498,32 @@ function runCapture(cmd, args, opts = {}) {
471
498
  });
472
499
  }
473
500
 
474
- function question(prompt, defaultValue = "") {
475
- const rl = createInterface({ input: process.stdin, output: process.stdout });
476
- const suffix = defaultValue ? ` [${defaultValue}]` : "";
477
- return new Promise((resolve) => {
478
- rl.question(`${prompt}${suffix}: `, (answer) => {
501
+ function question(prompt, defaultValue = "") {
502
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
503
+ const suffix = defaultValue ? ` [${defaultValue}]` : "";
504
+ return new Promise((resolve) => {
505
+ rl.question(`${prompt}${suffix}: `, (answer) => {
479
506
  rl.close();
480
507
  resolve((answer ?? defaultValue).trim() || defaultValue);
481
508
  });
482
- });
483
- }
484
-
485
- function isYes(answer) {
486
- const normalized = String(answer || "").trim().toLowerCase();
487
- return normalized === "y" || normalized === "yes";
488
- }
489
-
490
- function isValidAgentPrefixInput(value) {
509
+ });
510
+ }
511
+
512
+ function isYes(answer) {
513
+ const normalized = String(answer || "").trim().toLowerCase();
514
+ return normalized === "y" || normalized === "yes";
515
+ }
516
+
517
+ function isValidPeerPrefixInput(value) {
491
518
  const trimmed = String(value || "").trim();
492
519
  return !trimmed || /^[a-zA-Z0-9_-]+$/.test(trimmed);
493
520
  }
494
521
 
522
+ function normalizePeerRole(value) {
523
+ const role = String(value || "").trim().toLowerCase();
524
+ return role === "none" || role === "assistant" || role === "person" ? role : null;
525
+ }
526
+
495
527
  function parseJsonObjectFromOutput(output) {
496
528
  const text = String(output || "").trim();
497
529
  if (!text) return null;
@@ -513,18 +545,33 @@ function parseJsonObjectFromOutput(output) {
513
545
  return null;
514
546
  }
515
547
 
516
- async function questionAgentPrefix(defaultValue = "") {
548
+ async function questionPeerRole(defaultValue = "assistant") {
549
+ while (true) {
550
+ const answer = await question(
551
+ tr("Peer Role (none/assistant/person)", "Peer Role(none/assistant/person)"),
552
+ defaultValue,
553
+ );
554
+ const role = normalizePeerRole(answer);
555
+ if (role) return role;
556
+ warn(tr(
557
+ 'Peer Role must be "none", "assistant", or "person".',
558
+ 'Peer Role 必须是 "none"、"assistant" 或 "person"。',
559
+ ));
560
+ }
561
+ }
562
+
563
+ async function questionPeerPrefix(defaultValue = "") {
517
564
  while (true) {
518
565
  const answer = (await question(
519
- tr("Agent Prefix (optional)", "Agent Prefix(可选)"),
566
+ tr("Peer Prefix (optional)", "Peer Prefix(可选)"),
520
567
  defaultValue,
521
568
  )).trim();
522
- if (isValidAgentPrefixInput(answer)) {
569
+ if (isValidPeerPrefixInput(answer)) {
523
570
  return answer;
524
571
  }
525
572
  warn(tr(
526
- "Agent Prefix may only contain letters, digits, underscores, and hyphens, or be empty.",
527
- "Agent Prefix 只能包含字母、数字、下划线和连字符,或留空。",
573
+ "Peer Prefix may only contain letters, digits, underscores, and hyphens, or be empty.",
574
+ "Peer Prefix 只能包含字母、数字、下划线和连字符,或留空。",
528
575
  ));
529
576
  }
530
577
  }
@@ -575,7 +622,10 @@ async function collectRemoteConfig() {
575
622
  if (nonInteractive) return;
576
623
  remoteBaseUrl = await question(tr("OpenViking server URL", "OpenViking 服务器地址"), remoteBaseUrl);
577
624
  remoteApiKey = await question(tr("API Key (optional)", "API Key(可选)"), remoteApiKey);
578
- remoteAgentPrefix = await questionAgentPrefix(remoteAgentPrefix);
625
+ remotePeerRole = await questionPeerRole(remotePeerRole);
626
+ remotePeerPrefix = remotePeerRole === "assistant"
627
+ ? await questionPeerPrefix(remotePeerPrefix)
628
+ : "";
579
629
  }
580
630
 
581
631
  async function checkOpenClaw() {
@@ -679,30 +729,30 @@ if (upgradePluginOnly && rollbackLastUpgrade) {
679
729
  process.exit(1);
680
730
  }
681
731
 
682
- if (uninstallPlugin && (upgradePluginOnly || rollbackLastUpgrade)) {
683
- console.error("--uninstall cannot be used with --upgrade-plugin or --rollback");
684
- process.exit(1);
685
- }
686
-
687
- if (!["npm", "github"].includes(pluginSource)) {
688
- console.error("--plugin-source must be either npm or github");
689
- process.exit(1);
690
- }
691
-
692
- function looksLikeLegacyGitHubRef(value) {
693
- const ref = String(value || "").trim();
694
- if (!ref) return false;
695
- if (/^v\d+(\.\d+){1,2}([-.].*)?$/i.test(ref)) return true;
696
- if (["main", "master"].includes(ref.toLowerCase())) return true;
697
- return false;
698
- }
699
-
700
- if (!pluginSourceExplicit && pluginVersionExplicit && looksLikeLegacyGitHubRef(PLUGIN_VERSION)) {
701
- pluginSource = "github";
702
- }
703
-
704
- // Detect OpenClaw version
705
- async function detectOpenClawVersion() {
732
+ if (uninstallPlugin && (upgradePluginOnly || rollbackLastUpgrade)) {
733
+ console.error("--uninstall cannot be used with --upgrade-plugin or --rollback");
734
+ process.exit(1);
735
+ }
736
+
737
+ if (!["npm", "github"].includes(pluginSource)) {
738
+ console.error("--plugin-source must be either npm or github");
739
+ process.exit(1);
740
+ }
741
+
742
+ function looksLikeLegacyGitHubRef(value) {
743
+ const ref = String(value || "").trim();
744
+ if (!ref) return false;
745
+ if (/^v\d+(\.\d+){1,2}([-.].*)?$/i.test(ref)) return true;
746
+ if (["main", "master"].includes(ref.toLowerCase())) return true;
747
+ return false;
748
+ }
749
+
750
+ if (!pluginSourceExplicit && pluginVersionExplicit && looksLikeLegacyGitHubRef(PLUGIN_VERSION)) {
751
+ pluginSource = "github";
752
+ }
753
+
754
+ // Detect OpenClaw version
755
+ async function detectOpenClawVersion() {
706
756
  if (detectedOpenClawVersion) {
707
757
  return detectedOpenClawVersion;
708
758
  }
@@ -754,10 +804,10 @@ function compareSemverDesc(a, b) {
754
804
  return versionGte(a, b) ? -1 : 1;
755
805
  }
756
806
 
757
- function pickLatestPluginTag(tagNames) {
758
- const normalized = tagNames
759
- .map((tag) => String(tag ?? "").trim())
760
- .filter(Boolean);
807
+ function pickLatestPluginTag(tagNames) {
808
+ const normalized = tagNames
809
+ .map((tag) => String(tag ?? "").trim())
810
+ .filter(Boolean);
761
811
 
762
812
  const semverTags = normalized
763
813
  .filter((tag) => isSemverLike(tag))
@@ -766,118 +816,118 @@ function pickLatestPluginTag(tagNames) {
766
816
  if (semverTags.length > 0) {
767
817
  return semverTags[0];
768
818
  }
769
-
770
- return normalized[0] || "";
771
- }
772
-
773
- function npmPackageSpec(version = PLUGIN_VERSION) {
774
- return version ? `${pluginNpmPackage}@${version}` : pluginNpmPackage;
775
- }
776
-
777
- function parseNpmJsonOutput(output) {
778
- const text = String(output || "").trim();
779
- if (!text) return null;
780
- try {
781
- return JSON.parse(text);
782
- } catch {
783
- const firstArray = text.indexOf("[");
784
- const lastArray = text.lastIndexOf("]");
785
- if (firstArray >= 0 && lastArray > firstArray) {
786
- try {
787
- return JSON.parse(text.slice(firstArray, lastArray + 1));
788
- } catch {}
789
- }
790
- const firstObject = text.indexOf("{");
791
- const lastObject = text.lastIndexOf("}");
792
- if (firstObject >= 0 && lastObject > firstObject) {
793
- try {
794
- return JSON.parse(text.slice(firstObject, lastObject + 1));
795
- } catch {}
796
- }
797
- }
798
- return null;
799
- }
800
-
801
- async function resolveDefaultPluginVersionFromNpm() {
802
- info(tr(
803
- `No plugin version specified; resolving latest npm version from ${pluginNpmPackage}...`,
804
- `No plugin version specified; resolving latest npm version from ${pluginNpmPackage}...`,
805
- ));
806
-
807
- const result = await runCapture("npm", [
808
- "view",
809
- `${pluginNpmPackage}@latest`,
810
- "version",
811
- "--json",
812
- "--registry",
813
- NPM_REGISTRY,
814
- ], { shell: IS_WIN });
815
-
816
- if (result.code === 0) {
817
- const parsed = parseNpmJsonOutput(result.out);
818
- const version = typeof parsed === "string" ? parsed : String(result.out || "").trim().replace(/^"|"$/g, "");
819
- if (version) {
820
- PLUGIN_VERSION = version;
821
- info(tr(
822
- `Resolved default plugin version to npm latest: ${PLUGIN_VERSION}`,
823
- `Resolved default plugin version to npm latest: ${PLUGIN_VERSION}`,
824
- ));
825
- return true;
826
- }
827
- }
828
-
829
- warn(tr(
830
- `Could not resolve npm latest for ${pluginNpmPackage}${result.err ? `: ${result.err}` : ""}`,
831
- `Could not resolve npm latest for ${pluginNpmPackage}${result.err ? `: ${result.err}` : ""}`,
832
- ));
833
- return false;
834
- }
835
-
836
- async function ensureNpmPackageExtracted() {
837
- if (npmPackageExtractDir && existsSync(npmPackageExtractDir)) {
838
- return npmPackageExtractDir;
839
- }
840
-
841
- npmPackageTempDir = await mkdtemp(join(tmpdir(), "ov-plugin-npm-"));
842
- info(tr(
843
- `Downloading plugin package from npm: ${npmPackageSpec()}`,
844
- `Downloading plugin package from npm: ${npmPackageSpec()}`,
845
- ));
846
-
847
- const packResult = await runCapture("npm", [
848
- "pack",
849
- npmPackageSpec(),
850
- "--pack-destination",
851
- npmPackageTempDir,
852
- "--json",
853
- "--registry",
854
- NPM_REGISTRY,
855
- ], { shell: IS_WIN });
856
-
857
- if (packResult.code !== 0) {
858
- throw new Error(`npm pack failed for ${npmPackageSpec()}${packResult.err ? `: ${packResult.err}` : ""}`);
859
- }
860
-
861
- const parsed = parseNpmJsonOutput(packResult.out);
862
- const first = Array.isArray(parsed) ? parsed[0] : parsed;
863
- const filename = first?.filename || readdirSync(npmPackageTempDir).find((name) => name.endsWith(".tgz"));
864
- if (!filename) {
865
- throw new Error(`npm pack did not produce a tarball for ${npmPackageSpec()}`);
866
- }
867
-
868
- const tarballPath = join(npmPackageTempDir, filename);
869
- const extractRoot = join(npmPackageTempDir, "extract");
870
- await mkdir(extractRoot, { recursive: true });
871
- await run("tar", ["-xzf", tarballPath, "-C", extractRoot], { silent: true, shell: IS_WIN });
872
-
873
- const packageDir = join(extractRoot, "package");
874
- if (!existsSync(packageDir)) {
875
- throw new Error(`npm package ${npmPackageSpec()} did not contain the expected package directory`);
876
- }
877
-
878
- npmPackageExtractDir = packageDir;
879
- return npmPackageExtractDir;
880
- }
819
+
820
+ return normalized[0] || "";
821
+ }
822
+
823
+ function npmPackageSpec(version = PLUGIN_VERSION) {
824
+ return version ? `${pluginNpmPackage}@${version}` : pluginNpmPackage;
825
+ }
826
+
827
+ function parseNpmJsonOutput(output) {
828
+ const text = String(output || "").trim();
829
+ if (!text) return null;
830
+ try {
831
+ return JSON.parse(text);
832
+ } catch {
833
+ const firstArray = text.indexOf("[");
834
+ const lastArray = text.lastIndexOf("]");
835
+ if (firstArray >= 0 && lastArray > firstArray) {
836
+ try {
837
+ return JSON.parse(text.slice(firstArray, lastArray + 1));
838
+ } catch {}
839
+ }
840
+ const firstObject = text.indexOf("{");
841
+ const lastObject = text.lastIndexOf("}");
842
+ if (firstObject >= 0 && lastObject > firstObject) {
843
+ try {
844
+ return JSON.parse(text.slice(firstObject, lastObject + 1));
845
+ } catch {}
846
+ }
847
+ }
848
+ return null;
849
+ }
850
+
851
+ async function resolveDefaultPluginVersionFromNpm() {
852
+ info(tr(
853
+ `No plugin version specified; resolving latest npm version from ${pluginNpmPackage}...`,
854
+ `No plugin version specified; resolving latest npm version from ${pluginNpmPackage}...`,
855
+ ));
856
+
857
+ const result = await runCapture("npm", [
858
+ "view",
859
+ `${pluginNpmPackage}@latest`,
860
+ "version",
861
+ "--json",
862
+ "--registry",
863
+ NPM_REGISTRY,
864
+ ], { shell: IS_WIN });
865
+
866
+ if (result.code === 0) {
867
+ const parsed = parseNpmJsonOutput(result.out);
868
+ const version = typeof parsed === "string" ? parsed : String(result.out || "").trim().replace(/^"|"$/g, "");
869
+ if (version) {
870
+ PLUGIN_VERSION = version;
871
+ info(tr(
872
+ `Resolved default plugin version to npm latest: ${PLUGIN_VERSION}`,
873
+ `Resolved default plugin version to npm latest: ${PLUGIN_VERSION}`,
874
+ ));
875
+ return true;
876
+ }
877
+ }
878
+
879
+ warn(tr(
880
+ `Could not resolve npm latest for ${pluginNpmPackage}${result.err ? `: ${result.err}` : ""}`,
881
+ `Could not resolve npm latest for ${pluginNpmPackage}${result.err ? `: ${result.err}` : ""}`,
882
+ ));
883
+ return false;
884
+ }
885
+
886
+ async function ensureNpmPackageExtracted() {
887
+ if (npmPackageExtractDir && existsSync(npmPackageExtractDir)) {
888
+ return npmPackageExtractDir;
889
+ }
890
+
891
+ npmPackageTempDir = await mkdtemp(join(tmpdir(), "ov-plugin-npm-"));
892
+ info(tr(
893
+ `Downloading plugin package from npm: ${npmPackageSpec()}`,
894
+ `Downloading plugin package from npm: ${npmPackageSpec()}`,
895
+ ));
896
+
897
+ const packResult = await runCapture("npm", [
898
+ "pack",
899
+ npmPackageSpec(),
900
+ "--pack-destination",
901
+ npmPackageTempDir,
902
+ "--json",
903
+ "--registry",
904
+ NPM_REGISTRY,
905
+ ], { shell: IS_WIN });
906
+
907
+ if (packResult.code !== 0) {
908
+ throw new Error(`npm pack failed for ${npmPackageSpec()}${packResult.err ? `: ${packResult.err}` : ""}`);
909
+ }
910
+
911
+ const parsed = parseNpmJsonOutput(packResult.out);
912
+ const first = Array.isArray(parsed) ? parsed[0] : parsed;
913
+ const filename = first?.filename || readdirSync(npmPackageTempDir).find((name) => name.endsWith(".tgz"));
914
+ if (!filename) {
915
+ throw new Error(`npm pack did not produce a tarball for ${npmPackageSpec()}`);
916
+ }
917
+
918
+ const tarballPath = join(npmPackageTempDir, filename);
919
+ const extractRoot = join(npmPackageTempDir, "extract");
920
+ await mkdir(extractRoot, { recursive: true });
921
+ await run("tar", ["-xzf", tarballPath, "-C", extractRoot], { silent: true, shell: IS_WIN });
922
+
923
+ const packageDir = join(extractRoot, "package");
924
+ if (!existsSync(packageDir)) {
925
+ throw new Error(`npm package ${npmPackageSpec()} did not contain the expected package directory`);
926
+ }
927
+
928
+ npmPackageExtractDir = packageDir;
929
+ return npmPackageExtractDir;
930
+ }
881
931
 
882
932
  function parseGitLsRemoteTags(output) {
883
933
  return String(output ?? "")
@@ -889,23 +939,23 @@ function parseGitLsRemoteTags(output) {
889
939
  .filter(Boolean);
890
940
  }
891
941
 
892
- async function resolveDefaultPluginVersion() {
893
- if (PLUGIN_VERSION) {
894
- return;
895
- }
896
-
897
- if (pluginSource === "npm") {
898
- if (await resolveDefaultPluginVersionFromNpm()) {
899
- return;
900
- }
901
- warn(tr(
902
- "Falling back to GitHub tag resolution.",
903
- "Falling back to GitHub tag resolution.",
904
- ));
905
- }
906
-
907
- info(tr(
908
- `No plugin version specified; resolving latest tag from ${REPO}...`,
942
+ async function resolveDefaultPluginVersion() {
943
+ if (PLUGIN_VERSION) {
944
+ return;
945
+ }
946
+
947
+ if (pluginSource === "npm") {
948
+ if (await resolveDefaultPluginVersionFromNpm()) {
949
+ return;
950
+ }
951
+ warn(tr(
952
+ "Falling back to GitHub tag resolution.",
953
+ "Falling back to GitHub tag resolution.",
954
+ ));
955
+ }
956
+
957
+ info(tr(
958
+ `No plugin version specified; resolving latest tag from ${REPO}...`,
909
959
  `未指定插件版本,正在解析 ${REPO} 的最新 tag...`,
910
960
  ));
911
961
 
@@ -977,117 +1027,117 @@ async function resolveDefaultPluginVersion() {
977
1027
  if (failures.length > 0) {
978
1028
  warn(failures.join(" | "));
979
1029
  }
980
- process.exit(1);
981
- }
982
-
983
- function applyManifestConfig(manifestData) {
984
- resolvedPluginId = manifestData.plugin?.id || "";
985
- resolvedPluginKind = manifestData.plugin?.kind || "";
986
- resolvedPluginSlot = manifestData.plugin?.slot || "";
987
- resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
988
- resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
989
- resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
990
- const npmConfig = manifestData.npm && typeof manifestData.npm === "object"
991
- ? manifestData.npm
992
- : {};
993
- resolvedNpmOmitDev = npmConfig.omitDev !== false;
994
- resolvedNpmBuild = npmConfig.build === true || npmConfig.buildFromSource === true;
995
- resolvedNpmBuildMinOpenclawVersion =
996
- typeof npmConfig.buildMinOpenclawVersion === "string" && npmConfig.buildMinOpenclawVersion.trim()
997
- ? npmConfig.buildMinOpenclawVersion.trim()
998
- : DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
999
- resolvedNpmBuildScript = typeof npmConfig.buildScript === "string" && npmConfig.buildScript.trim()
1000
- ? npmConfig.buildScript.trim()
1001
- : "build";
1002
- resolvedNpmPruneAfterBuild = npmConfig.pruneAfterBuild !== false;
1003
- resolvedFilesRequired = manifestData.files?.required || [];
1004
- resolvedFilesOptional = manifestData.files?.optional || [];
1005
- }
1006
-
1007
- function hasPrebuiltRuntimeOutputs(packageDir) {
1008
- return existsSync(join(packageDir, "dist", "index.js"));
1009
- }
1010
-
1011
- async function resolvePluginConfigFromNpm() {
1012
- info(tr(
1013
- `Resolving plugin configuration from npm package: ${npmPackageSpec()}`,
1014
- `Resolving plugin configuration from npm package: ${npmPackageSpec()}`,
1015
- ));
1016
-
1017
- const packageDir = await ensureNpmPackageExtracted();
1018
- const manifestPath = join(packageDir, "install-manifest.json");
1019
- const packageJsonPath = join(packageDir, "package.json");
1020
- let manifestData = null;
1021
- let packageJson = null;
1022
-
1023
- if (existsSync(packageJsonPath)) {
1024
- try {
1025
- packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
1026
- resolvedPluginReleaseId = packageJson.version || "";
1027
- } catch {}
1028
- }
1029
-
1030
- if (existsSync(manifestPath)) {
1031
- try {
1032
- manifestData = JSON.parse(await readFile(manifestPath, "utf8"));
1033
- info(tr("Found manifest in npm package", "Found manifest in npm package"));
1034
- } catch {}
1035
- }
1036
-
1037
- resolvedPluginDir = ".";
1038
- if (manifestData) {
1039
- applyManifestConfig(manifestData);
1040
- } else {
1041
- const pkgName = packageJson?.name || "";
1042
- const fallback = pkgName && pkgName !== DEFAULT_PLUGIN_NPM_PACKAGE ? FALLBACK_LEGACY : FALLBACK_CURRENT;
1043
- resolvedPluginId = fallback.id;
1044
- resolvedPluginKind = fallback.kind;
1045
- resolvedPluginSlot = fallback.slot;
1046
- resolvedFilesRequired = fallback.required;
1047
- resolvedFilesOptional = fallback.optional;
1048
- resolvedNpmOmitDev = true;
1049
- resolvedNpmBuild = false;
1050
- resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
1051
- resolvedNpmBuildScript = "build";
1052
- resolvedNpmPruneAfterBuild = true;
1053
- resolvedMinOpenclawVersion = (packageJson?.engines?.openclaw || "").replace(/^>=?\s*/, "").trim()
1054
- || fallback.minOpenclawVersion
1055
- || "2026.3.7";
1056
- resolvedMinOpenvikingVersion = "";
1057
- }
1058
-
1059
- if (hasPrebuiltRuntimeOutputs(packageDir)) {
1060
- resolvedNpmBuild = false;
1061
- info(tr(
1062
- "npm package contains prebuilt runtime output; skipping source build.",
1063
- "npm package contains prebuilt runtime output; skipping source build.",
1064
- ));
1065
- }
1066
-
1067
- PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
1068
- info(tr(`Plugin: ${resolvedPluginId} (${resolvedPluginKind})`, `Plugin: ${resolvedPluginId} (${resolvedPluginKind})`));
1069
- }
1070
-
1071
- // Resolve plugin configuration from manifest or fallback
1072
- async function resolvePluginConfig() {
1073
- if (pluginSource === "npm") {
1074
- try {
1075
- await resolvePluginConfigFromNpm();
1076
- return;
1077
- } catch (error) {
1078
- warn(tr(
1079
- `npm plugin resolution failed: ${error?.message || error}`,
1080
- `npm plugin resolution failed: ${error?.message || error}`,
1081
- ));
1082
- warn(tr(
1083
- "Falling back to GitHub plugin download.",
1084
- "Falling back to GitHub plugin download.",
1085
- ));
1086
- pluginSource = "github";
1087
- }
1088
- }
1089
-
1090
- const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
1030
+ process.exit(1);
1031
+ }
1032
+
1033
+ function applyManifestConfig(manifestData) {
1034
+ resolvedPluginId = manifestData.plugin?.id || "";
1035
+ resolvedPluginKind = manifestData.plugin?.kind || "";
1036
+ resolvedPluginSlot = manifestData.plugin?.slot || "";
1037
+ resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
1038
+ resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
1039
+ resolvedPluginReleaseId = manifestData.pluginVersion || manifestData.release?.id || "";
1040
+ const npmConfig = manifestData.npm && typeof manifestData.npm === "object"
1041
+ ? manifestData.npm
1042
+ : {};
1043
+ resolvedNpmOmitDev = npmConfig.omitDev !== false;
1044
+ resolvedNpmBuild = npmConfig.build === true || npmConfig.buildFromSource === true;
1045
+ resolvedNpmBuildMinOpenclawVersion =
1046
+ typeof npmConfig.buildMinOpenclawVersion === "string" && npmConfig.buildMinOpenclawVersion.trim()
1047
+ ? npmConfig.buildMinOpenclawVersion.trim()
1048
+ : DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
1049
+ resolvedNpmBuildScript = typeof npmConfig.buildScript === "string" && npmConfig.buildScript.trim()
1050
+ ? npmConfig.buildScript.trim()
1051
+ : "build";
1052
+ resolvedNpmPruneAfterBuild = npmConfig.pruneAfterBuild !== false;
1053
+ resolvedFilesRequired = manifestData.files?.required || [];
1054
+ resolvedFilesOptional = manifestData.files?.optional || [];
1055
+ }
1056
+
1057
+ function hasPrebuiltRuntimeOutputs(packageDir) {
1058
+ return existsSync(join(packageDir, "dist", "index.js"));
1059
+ }
1060
+
1061
+ async function resolvePluginConfigFromNpm() {
1062
+ info(tr(
1063
+ `Resolving plugin configuration from npm package: ${npmPackageSpec()}`,
1064
+ `Resolving plugin configuration from npm package: ${npmPackageSpec()}`,
1065
+ ));
1066
+
1067
+ const packageDir = await ensureNpmPackageExtracted();
1068
+ const manifestPath = join(packageDir, "install-manifest.json");
1069
+ const packageJsonPath = join(packageDir, "package.json");
1070
+ let manifestData = null;
1071
+ let packageJson = null;
1072
+
1073
+ if (existsSync(packageJsonPath)) {
1074
+ try {
1075
+ packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
1076
+ resolvedPluginReleaseId = packageJson.version || "";
1077
+ } catch {}
1078
+ }
1079
+
1080
+ if (existsSync(manifestPath)) {
1081
+ try {
1082
+ manifestData = JSON.parse(await readFile(manifestPath, "utf8"));
1083
+ info(tr("Found manifest in npm package", "Found manifest in npm package"));
1084
+ } catch {}
1085
+ }
1086
+
1087
+ resolvedPluginDir = ".";
1088
+ if (manifestData) {
1089
+ applyManifestConfig(manifestData);
1090
+ } else {
1091
+ const pkgName = packageJson?.name || "";
1092
+ const fallback = pkgName && pkgName !== DEFAULT_PLUGIN_NPM_PACKAGE ? FALLBACK_LEGACY : FALLBACK_CURRENT;
1093
+ resolvedPluginId = fallback.id;
1094
+ resolvedPluginKind = fallback.kind;
1095
+ resolvedPluginSlot = fallback.slot;
1096
+ resolvedFilesRequired = fallback.required;
1097
+ resolvedFilesOptional = fallback.optional;
1098
+ resolvedNpmOmitDev = true;
1099
+ resolvedNpmBuild = false;
1100
+ resolvedNpmBuildMinOpenclawVersion = DEFAULT_NPM_BUILD_MIN_OPENCLAW_VERSION;
1101
+ resolvedNpmBuildScript = "build";
1102
+ resolvedNpmPruneAfterBuild = true;
1103
+ resolvedMinOpenclawVersion = (packageJson?.engines?.openclaw || "").replace(/^>=?\s*/, "").trim()
1104
+ || fallback.minOpenclawVersion
1105
+ || "2026.3.7";
1106
+ resolvedMinOpenvikingVersion = "";
1107
+ }
1108
+
1109
+ if (hasPrebuiltRuntimeOutputs(packageDir)) {
1110
+ resolvedNpmBuild = false;
1111
+ info(tr(
1112
+ "npm package contains prebuilt runtime output; skipping source build.",
1113
+ "npm package contains prebuilt runtime output; skipping source build.",
1114
+ ));
1115
+ }
1116
+
1117
+ PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
1118
+ info(tr(`Plugin: ${resolvedPluginId} (${resolvedPluginKind})`, `Plugin: ${resolvedPluginId} (${resolvedPluginKind})`));
1119
+ }
1120
+
1121
+ // Resolve plugin configuration from manifest or fallback
1122
+ async function resolvePluginConfig() {
1123
+ if (pluginSource === "npm") {
1124
+ try {
1125
+ await resolvePluginConfigFromNpm();
1126
+ return;
1127
+ } catch (error) {
1128
+ warn(tr(
1129
+ `npm plugin resolution failed: ${error?.message || error}`,
1130
+ `npm plugin resolution failed: ${error?.message || error}`,
1131
+ ));
1132
+ warn(tr(
1133
+ "Falling back to GitHub plugin download.",
1134
+ "Falling back to GitHub plugin download.",
1135
+ ));
1136
+ pluginSource = "github";
1137
+ }
1138
+ }
1139
+
1140
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
1091
1141
 
1092
1142
  info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
1093
1143
 
@@ -1164,7 +1214,8 @@ async function resolvePluginConfig() {
1164
1214
  const pkg = JSON.parse(pkgJson);
1165
1215
  const pkgName = pkg.name || "";
1166
1216
  resolvedPluginReleaseId = pkg.version || "";
1167
- if (pkgName && pkgName !== "@openclaw/openviking") {
1217
+ const currentPackageNames = new Set(["@openviking/openclaw-plugin", "@openclaw/openviking"]);
1218
+ if (pkgName && !currentPackageNames.has(pkgName)) {
1168
1219
  fallbackKey = "legacy";
1169
1220
  info(tr(`Detected legacy plugin by package name: ${pkgName}`, `通过 package.json 名称检测到旧版插件: ${pkgName}`));
1170
1221
  } else if (pkgName) {
@@ -1399,18 +1450,22 @@ function extractRuntimeConfigFromPluginEntry(entryConfig) {
1399
1450
  if (typeof entryConfig.apiKey === "string" && entryConfig.apiKey.trim()) {
1400
1451
  runtime.apiKey = entryConfig.apiKey;
1401
1452
  }
1402
- const prefix = entryConfig.agent_prefix || entryConfig.agentId;
1403
- if (typeof prefix === "string" && prefix.trim()) {
1404
- runtime.agent_prefix = prefix.trim();
1405
- }
1406
- if (typeof entryConfig.accountId === "string" && entryConfig.accountId.trim()) {
1407
- runtime.accountId = entryConfig.accountId.trim();
1408
- }
1409
- if (typeof entryConfig.userId === "string" && entryConfig.userId.trim()) {
1410
- runtime.userId = entryConfig.userId.trim();
1411
- }
1412
- return runtime;
1413
- }
1453
+ const role = normalizePeerRole(entryConfig.peer_role);
1454
+ if (role) {
1455
+ runtime.peer_role = role;
1456
+ }
1457
+ const prefix = entryConfig.peer_prefix;
1458
+ if (typeof prefix === "string" && prefix.trim()) {
1459
+ runtime.peer_prefix = prefix.trim();
1460
+ }
1461
+ if (typeof entryConfig.accountId === "string" && entryConfig.accountId.trim()) {
1462
+ runtime.accountId = entryConfig.accountId.trim();
1463
+ }
1464
+ if (typeof entryConfig.userId === "string" && entryConfig.userId.trim()) {
1465
+ runtime.userId = entryConfig.userId.trim();
1466
+ }
1467
+ return runtime;
1468
+ }
1414
1469
 
1415
1470
  async function backupOpenClawConfig(configPath) {
1416
1471
  await mkdir(getUpgradeAuditDir(), { recursive: true });
@@ -1700,12 +1755,13 @@ async function prepareStrongPluginUpgrade() {
1700
1755
  `检测到已安装 OpenViking 插件状态: ${installedState.generation}`,
1701
1756
  ),
1702
1757
  );
1703
- remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
1704
- remoteApiKey = upgradeRuntimeConfig.apiKey || "";
1705
- remoteAgentPrefix = upgradeRuntimeConfig.agent_prefix || "";
1706
- remoteAccountId = upgradeRuntimeConfig.accountId || "";
1707
- remoteUserId = upgradeRuntimeConfig.userId || "";
1708
- info(tr(`Upgrade runtime mode: ${selectedMode} (remote OpenViking server)`, `升级运行模式: ${selectedMode}(远程 OpenViking 服务)`));
1758
+ remoteBaseUrl = upgradeRuntimeConfig.baseUrl || remoteBaseUrl;
1759
+ remoteApiKey = upgradeRuntimeConfig.apiKey || "";
1760
+ remotePeerRole = upgradeRuntimeConfig.peer_role || remotePeerRole || "assistant";
1761
+ remotePeerPrefix = upgradeRuntimeConfig.peer_prefix || "";
1762
+ remoteAccountId = upgradeRuntimeConfig.accountId || "";
1763
+ remoteUserId = upgradeRuntimeConfig.userId || "";
1764
+ info(tr(`Upgrade runtime mode: ${selectedMode} (remote OpenViking server)`, `升级运行模式: ${selectedMode}(远程 OpenViking 服务)`));
1709
1765
 
1710
1766
  info(tr(`Upgrade path: ${fromVersion} -> ${toVersion}`, `升级路径: ${fromVersion} -> ${toVersion}`));
1711
1767
 
@@ -1803,6 +1859,96 @@ async function downloadPluginFile(destDir, fileName, url, required, index, total
1803
1859
  process.exit(1);
1804
1860
  }
1805
1861
 
1862
+ function githubContentsUrl(pluginDir, fileName) {
1863
+ const path = `examples/${pluginDir}/${fileName}`.replace(/\/+$/u, "");
1864
+ const encodedPath = path.split("/").map((part) => encodeURIComponent(part)).join("/");
1865
+ return `https://api.github.com/repos/${REPO}/contents/${encodedPath}?ref=${encodeURIComponent(PLUGIN_VERSION)}`;
1866
+ }
1867
+
1868
+ async function fetchGitHubDirectoryEntries(pluginDir, dirName, required) {
1869
+ const maxRetries = 3;
1870
+ const url = githubContentsUrl(pluginDir, dirName);
1871
+ let lastStatus = 0;
1872
+
1873
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1874
+ try {
1875
+ const response = await fetch(url, { headers: { "User-Agent": "openviking-setup-helper" } });
1876
+ lastStatus = response.status;
1877
+ if (response.ok) {
1878
+ const json = await response.json();
1879
+ if (Array.isArray(json)) return json;
1880
+ lastStatus = 0;
1881
+ } else if (!required && response.status === 404) {
1882
+ return null;
1883
+ }
1884
+ } catch {
1885
+ lastStatus = 0;
1886
+ }
1887
+
1888
+ if (attempt < maxRetries) {
1889
+ await new Promise((resolve) => setTimeout(resolve, 2000));
1890
+ }
1891
+ }
1892
+
1893
+ if (!required) {
1894
+ err(tr(
1895
+ `Optional directory failed after ${maxRetries} retries (HTTP ${lastStatus || "network"}): ${url}`,
1896
+ `可选目录已重试 ${maxRetries} 次仍失败(HTTP ${lastStatus || "网络错误"}): ${url}`,
1897
+ ));
1898
+ process.exit(1);
1899
+ }
1900
+
1901
+ err(tr(
1902
+ `Directory download failed after ${maxRetries} retries (HTTP ${lastStatus || "network"}): ${url}`,
1903
+ `目录下载失败(已重试 ${maxRetries} 次,HTTP ${lastStatus || "网络错误"}): ${url}`,
1904
+ ));
1905
+ process.exit(1);
1906
+ }
1907
+
1908
+ async function collectGitHubDirectoryFiles(pluginDir, dirName, required) {
1909
+ const entries = await fetchGitHubDirectoryEntries(pluginDir, dirName, required);
1910
+ if (!entries) return [];
1911
+
1912
+ const prefix = `examples/${pluginDir}/`;
1913
+ const files = [];
1914
+ for (const entry of entries) {
1915
+ if (entry?.type === "file" && entry.download_url) {
1916
+ const relativePath = String(entry.path || "").startsWith(prefix)
1917
+ ? String(entry.path).slice(prefix.length)
1918
+ : `${dirName}${entry.name}`;
1919
+ files.push({ fileName: relativePath, url: entry.download_url });
1920
+ continue;
1921
+ }
1922
+
1923
+ if (entry?.type === "dir" && entry.path) {
1924
+ const relativeDir = String(entry.path).startsWith(prefix)
1925
+ ? String(entry.path).slice(prefix.length)
1926
+ : `${dirName}${entry.name}/`;
1927
+ files.push(...await collectGitHubDirectoryFiles(pluginDir, `${relativeDir}/`, required));
1928
+ }
1929
+ }
1930
+
1931
+ return files;
1932
+ }
1933
+
1934
+ async function downloadPluginDirectory(destDir, pluginDir, dirName, required, index, total) {
1935
+ process.stdout.write(` [${index}/${total}] ${dirName} `);
1936
+ const files = await collectGitHubDirectoryFiles(pluginDir, dirName, required);
1937
+ if (files.length === 0) {
1938
+ console.log(required ? " empty" : tr(" skip", " 跳过"));
1939
+ if (!required) return;
1940
+ err(tr(`Required directory is empty or unavailable: ${dirName}`, `必需目录为空或不可用: ${dirName}`));
1941
+ process.exit(1);
1942
+ }
1943
+ console.log(` OK (${files.length} files)`);
1944
+
1945
+ let fileIndex = 0;
1946
+ for (const file of files) {
1947
+ fileIndex++;
1948
+ await downloadPluginFile(destDir, file.fileName, file.url, required, `${index}.${fileIndex}`, total);
1949
+ }
1950
+ }
1951
+
1806
1952
  function runtimeOutputCandidatesForEntry(entry) {
1807
1953
  const normalized = String(entry || "").replace(/\\/g, "/").replace(/^\.\//, "");
1808
1954
  if (!normalized.endsWith(".ts")) {
@@ -1859,7 +2005,7 @@ async function assertBuiltRuntimeOutputs(destDir) {
1859
2005
  process.exit(1);
1860
2006
  }
1861
2007
 
1862
- async function installPluginNpmDependencies(destDir) {
2008
+ async function installPluginNpmDependencies(destDir) {
1863
2009
  if (!resolvedNpmBuild) {
1864
2010
  info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
1865
2011
  const npmArgs = resolvedNpmOmitDev
@@ -1897,40 +2043,40 @@ async function installPluginNpmDependencies(destDir) {
1897
2043
  "--no-audit",
1898
2044
  "--no-fund",
1899
2045
  ], { cwd: destDir, silent: false });
1900
- }
1901
- }
1902
-
1903
- async function copyNpmPackageToDest(destDir) {
1904
- const packageDir = await ensureNpmPackageExtracted();
1905
- await mkdir(destDir, { recursive: true });
1906
- const entries = readdirSync(packageDir, { withFileTypes: true });
1907
- for (const entry of entries) {
1908
- await cp(join(packageDir, entry.name), join(destDir, entry.name), { recursive: true, force: true });
1909
- }
1910
- }
1911
-
1912
- async function cleanupNpmPackageTemp() {
1913
- if (!npmPackageTempDir) return;
1914
- await rm(npmPackageTempDir, { recursive: true, force: true });
1915
- npmPackageTempDir = "";
1916
- npmPackageExtractDir = "";
1917
- }
1918
-
1919
- async function downloadPlugin(destDir) {
1920
- if (pluginSource === "npm") {
1921
- await mkdir(destDir, { recursive: true });
1922
- info(tr(
1923
- `Installing plugin from npm package ${npmPackageSpec()}...`,
1924
- `Installing plugin from npm package ${npmPackageSpec()}...`,
1925
- ));
1926
- await copyNpmPackageToDest(destDir);
1927
- await installPluginNpmDependencies(destDir);
1928
- info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `Plugin deployed: ${PLUGIN_DEST}`));
1929
- return;
1930
- }
1931
-
1932
- const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
1933
- const pluginDir = resolvedPluginDir;
2046
+ }
2047
+ }
2048
+
2049
+ async function copyNpmPackageToDest(destDir) {
2050
+ const packageDir = await ensureNpmPackageExtracted();
2051
+ await mkdir(destDir, { recursive: true });
2052
+ const entries = readdirSync(packageDir, { withFileTypes: true });
2053
+ for (const entry of entries) {
2054
+ await cp(join(packageDir, entry.name), join(destDir, entry.name), { recursive: true, force: true });
2055
+ }
2056
+ }
2057
+
2058
+ async function cleanupNpmPackageTemp() {
2059
+ if (!npmPackageTempDir) return;
2060
+ await rm(npmPackageTempDir, { recursive: true, force: true });
2061
+ npmPackageTempDir = "";
2062
+ npmPackageExtractDir = "";
2063
+ }
2064
+
2065
+ async function downloadPlugin(destDir) {
2066
+ if (pluginSource === "npm") {
2067
+ await mkdir(destDir, { recursive: true });
2068
+ info(tr(
2069
+ `Installing plugin from npm package ${npmPackageSpec()}...`,
2070
+ `Installing plugin from npm package ${npmPackageSpec()}...`,
2071
+ ));
2072
+ await copyNpmPackageToDest(destDir);
2073
+ await installPluginNpmDependencies(destDir);
2074
+ info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `Plugin deployed: ${PLUGIN_DEST}`));
2075
+ return;
2076
+ }
2077
+
2078
+ const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
2079
+ const pluginDir = resolvedPluginDir;
1934
2080
  const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
1935
2081
 
1936
2082
  await mkdir(destDir, { recursive: true });
@@ -1942,6 +2088,10 @@ async function downloadPlugin(destDir) {
1942
2088
  for (const name of resolvedFilesRequired) {
1943
2089
  if (!name) continue;
1944
2090
  i++;
2091
+ if (name.endsWith("/")) {
2092
+ await downloadPluginDirectory(destDir, pluginDir, name, true, i, total);
2093
+ continue;
2094
+ }
1945
2095
  const url = `${ghRaw}/examples/${pluginDir}/${name}`;
1946
2096
  await downloadPluginFile(destDir, name, url, true, i, total);
1947
2097
  }
@@ -1950,6 +2100,10 @@ async function downloadPlugin(destDir) {
1950
2100
  for (const name of resolvedFilesOptional) {
1951
2101
  if (!name) continue;
1952
2102
  i++;
2103
+ if (name.endsWith("/")) {
2104
+ await downloadPluginDirectory(destDir, pluginDir, name, false, i, total);
2105
+ continue;
2106
+ }
1953
2107
  const url = `${ghRaw}/examples/${pluginDir}/${name}`;
1954
2108
  await downloadPluginFile(destDir, name, url, false, i, total);
1955
2109
  }
@@ -1987,18 +2141,18 @@ async function finalizePluginDeployment(stagingDir) {
1987
2141
  return info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
1988
2142
  }
1989
2143
 
1990
- async function deployPluginFromRemote() {
1991
- const stagingDir = await createPluginStagingDir();
1992
- try {
1993
- await downloadPlugin(stagingDir);
1994
- await finalizePluginDeployment(stagingDir);
1995
- } catch (error) {
1996
- await rm(stagingDir, { recursive: true, force: true });
1997
- throw error;
1998
- } finally {
1999
- await cleanupNpmPackageTemp();
2000
- }
2001
- }
2144
+ async function deployPluginFromRemote() {
2145
+ const stagingDir = await createPluginStagingDir();
2146
+ try {
2147
+ await downloadPlugin(stagingDir);
2148
+ await finalizePluginDeployment(stagingDir);
2149
+ } catch (error) {
2150
+ await rm(stagingDir, { recursive: true, force: true });
2151
+ throw error;
2152
+ } finally {
2153
+ await cleanupNpmPackageTemp();
2154
+ }
2155
+ }
2002
2156
 
2003
2157
  /** Same as INSTALL*.md manual cleanup: stale entries block `plugins.slots.*` validation after reinstall. */
2004
2158
  function resolvedPluginSlotFallback() {
@@ -2272,7 +2426,8 @@ async function configureOpenClawPlugin({
2272
2426
  const effectiveRuntimeConfig = runtimeConfig || {
2273
2427
  baseUrl: remoteBaseUrl,
2274
2428
  apiKey: remoteApiKey,
2275
- agent_prefix: remoteAgentPrefix,
2429
+ peer_role: remotePeerRole,
2430
+ peer_prefix: remotePeerPrefix,
2276
2431
  };
2277
2432
 
2278
2433
  let allowedPropsLegacy = null;
@@ -2287,7 +2442,7 @@ async function configureOpenClawPlugin({
2287
2442
  }
2288
2443
  } catch { /* ignore parse errors */ }
2289
2444
 
2290
- const agentVal = effectiveRuntimeConfig.agent_prefix || "";
2445
+ const peerVal = effectiveRuntimeConfig.peer_prefix || "";
2291
2446
  const candidates = {
2292
2447
  mode: "remote",
2293
2448
  baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
@@ -2295,7 +2450,7 @@ async function configureOpenClawPlugin({
2295
2450
  autoRecall: true,
2296
2451
  autoCapture: true,
2297
2452
  apiKey: effectiveRuntimeConfig.apiKey || undefined,
2298
- agentId: agentVal || undefined,
2453
+ peer_prefix: peerVal || undefined,
2299
2454
  };
2300
2455
 
2301
2456
  const pluginConfig = {};
@@ -2317,7 +2472,8 @@ async function configureOpenClawPlugin({
2317
2472
  const effectiveRuntimeConfig = runtimeConfig || {
2318
2473
  baseUrl: remoteBaseUrl,
2319
2474
  apiKey: remoteApiKey,
2320
- agent_prefix: remoteAgentPrefix,
2475
+ peer_role: remotePeerRole,
2476
+ peer_prefix: remotePeerPrefix,
2321
2477
  accountId: remoteAccountId,
2322
2478
  userId: remoteUserId,
2323
2479
  };
@@ -2332,82 +2488,85 @@ async function configureOpenClawPlugin({
2332
2488
  }
2333
2489
  } catch { /* ignore read errors */ }
2334
2490
 
2335
- let setupResult = null;
2336
- let parsed = null;
2337
- const runSetupJson = async (extraArgs = []) => {
2338
- const setupArgs = ["openviking", "setup"];
2339
- setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
2340
- setupArgs.push("--json");
2341
- if (effectiveRuntimeConfig.apiKey) {
2342
- setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
2491
+ let setupResult = null;
2492
+ let parsed = null;
2493
+ const runSetupJson = async (extraArgs = []) => {
2494
+ const setupArgs = ["openviking", "setup"];
2495
+ setupArgs.push("--base-url", effectiveRuntimeConfig.baseUrl || remoteBaseUrl);
2496
+ setupArgs.push("--json");
2497
+ if (effectiveRuntimeConfig.apiKey) {
2498
+ setupArgs.push("--api-key", effectiveRuntimeConfig.apiKey);
2499
+ }
2500
+ if (effectiveRuntimeConfig.peer_role) {
2501
+ setupArgs.push("--peer-role", effectiveRuntimeConfig.peer_role);
2343
2502
  }
2344
- if (effectiveRuntimeConfig.agent_prefix) {
2345
- setupArgs.push("--agent-prefix", effectiveRuntimeConfig.agent_prefix);
2503
+ if (effectiveRuntimeConfig.peer_prefix) {
2504
+ setupArgs.push("--peer-prefix", effectiveRuntimeConfig.peer_prefix);
2346
2505
  }
2347
2506
  if (effectiveRuntimeConfig.accountId) {
2348
2507
  setupArgs.push("--account-id", effectiveRuntimeConfig.accountId);
2349
2508
  }
2350
- if (effectiveRuntimeConfig.userId) {
2351
- setupArgs.push("--user-id", effectiveRuntimeConfig.userId);
2352
- }
2353
- if (forceSlotExplicit) {
2354
- setupArgs.push("--force-slot");
2355
- }
2356
- if (allowOfflineExplicit) {
2357
- setupArgs.push("--allow-offline");
2358
- }
2359
- setupArgs.push(...extraArgs);
2360
-
2361
- const result = await runCapture("openclaw", setupArgs, { env: ocEnv, shell: IS_WIN });
2362
- return {
2363
- result,
2364
- parsed: parseJsonObjectFromOutput(`${result.out || ""}\n${result.err || ""}`),
2365
- };
2366
- };
2367
-
2368
- if (setupJsonSupported) {
2369
- info(tr(
2370
- "Delegating configuration to: openclaw openviking setup --json",
2371
- "委托配置给: openclaw openviking setup --json",
2372
- ));
2373
-
2374
- ({ result: setupResult, parsed } = await runSetupJson());
2375
- } else {
2376
- info(tr(
2377
- "Installed plugin does not support setup --json, using direct config write",
2378
- "已安装的插件不支持 setup --json,使用直接配置写入",
2379
- ));
2380
- }
2381
-
2382
- if (parsed && !parsed.success && !nonInteractive) {
2383
- if (parsed.action === "slot_blocked" && !forceSlotExplicit) {
2384
- const answer = await question(
2385
- tr(
2386
- `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2387
- `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2388
- ),
2389
- );
2390
- if (isYes(answer)) {
2391
- ({ result: setupResult, parsed } = await runSetupJson(["--force-slot"]));
2392
- }
2393
- } else if (
2394
- typeof parsed.error === "string" &&
2395
- parsed.error.includes("Server unreachable") &&
2396
- !allowOfflineExplicit
2397
- ) {
2398
- const answer = await question(
2399
- tr(
2400
- "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2401
- "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2402
- ),
2403
- );
2404
- if (isYes(answer)) {
2405
- ({ result: setupResult, parsed } = await runSetupJson(["--allow-offline"]));
2406
- }
2407
- }
2408
- }
2409
-
2410
- if (parsed) {
2509
+ if (effectiveRuntimeConfig.userId) {
2510
+ setupArgs.push("--user-id", effectiveRuntimeConfig.userId);
2511
+ }
2512
+ if (forceSlotExplicit) {
2513
+ setupArgs.push("--force-slot");
2514
+ }
2515
+ if (allowOfflineExplicit) {
2516
+ setupArgs.push("--allow-offline");
2517
+ }
2518
+ setupArgs.push(...extraArgs);
2519
+
2520
+ const result = await runCapture("openclaw", setupArgs, { env: ocEnv, shell: IS_WIN });
2521
+ return {
2522
+ result,
2523
+ parsed: parseJsonObjectFromOutput(`${result.out || ""}\n${result.err || ""}`),
2524
+ };
2525
+ };
2526
+
2527
+ if (setupJsonSupported) {
2528
+ info(tr(
2529
+ "Delegating configuration to: openclaw openviking setup --json",
2530
+ "委托配置给: openclaw openviking setup --json",
2531
+ ));
2532
+
2533
+ ({ result: setupResult, parsed } = await runSetupJson());
2534
+ } else {
2535
+ info(tr(
2536
+ "Installed plugin does not support setup --json, using direct config write",
2537
+ "已安装的插件不支持 setup --json,使用直接配置写入",
2538
+ ));
2539
+ }
2540
+
2541
+ if (parsed && !parsed.success && !nonInteractive) {
2542
+ if (parsed.action === "slot_blocked" && !forceSlotExplicit) {
2543
+ const answer = await question(
2544
+ tr(
2545
+ `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2546
+ `contextEngine slot is owned by "${parsed.slot?.previousOwner}". Replace it with OpenViking? (y/N)`,
2547
+ ),
2548
+ );
2549
+ if (isYes(answer)) {
2550
+ ({ result: setupResult, parsed } = await runSetupJson(["--force-slot"]));
2551
+ }
2552
+ } else if (
2553
+ typeof parsed.error === "string" &&
2554
+ parsed.error.includes("Server unreachable") &&
2555
+ !allowOfflineExplicit
2556
+ ) {
2557
+ const answer = await question(
2558
+ tr(
2559
+ "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2560
+ "OpenViking server is unreachable. Save config offline anyway? (y/N)",
2561
+ ),
2562
+ );
2563
+ if (isYes(answer)) {
2564
+ ({ result: setupResult, parsed } = await runSetupJson(["--allow-offline"]));
2565
+ }
2566
+ }
2567
+ }
2568
+
2569
+ if (parsed) {
2411
2570
  if (parsed.success) {
2412
2571
  info(tr("OpenClaw plugin configured via setup", "OpenClaw 插件通过 setup 配置完成"));
2413
2572
  if (parsed.health?.ok) {
@@ -2425,37 +2584,37 @@ async function configureOpenClawPlugin({
2425
2584
  if (parsed.slot?.activated) {
2426
2585
  info(tr(`contextEngine slot activated`, `contextEngine slot 已激活`));
2427
2586
  }
2428
- } else {
2429
- // Setup returned success: false
2430
- const setupError = parsed.error || parsed.action || "unknown error";
2431
- if (parsed.action === "slot_blocked") {
2432
- warn(tr(
2433
- `Config saved but contextEngine slot is owned by "${parsed.slot?.previousOwner}". Use --force-slot to override.`,
2434
- `配置已保存,但 contextEngine slot 被 "${parsed.slot?.previousOwner}" 占用。使用 --force-slot 覆盖。`,
2435
- ));
2436
- } else {
2437
- err(tr(
2438
- `Setup failed: ${setupError}`,
2439
- `配置失败: ${setupError}`,
2440
- ));
2441
- }
2442
- return {
2443
- runtimeConfigOk: false,
2444
- error: setupError,
2445
- };
2446
- }
2447
- } else if (setupJsonSupported) {
2448
- const setupError = setupResult
2449
- ? `openclaw openviking setup did not return JSON (exit code ${setupResult.code})`
2450
- : "openclaw openviking setup did not run";
2451
- err(tr(`Setup failed: ${setupError}`, `配置失败: ${setupError}`));
2452
- return {
2453
- runtimeConfigOk: false,
2454
- error: setupError,
2455
- };
2456
- }
2457
-
2458
- if (!setupJsonSupported) {
2587
+ } else {
2588
+ // Setup returned success: false
2589
+ const setupError = parsed.error || parsed.action || "unknown error";
2590
+ if (parsed.action === "slot_blocked") {
2591
+ warn(tr(
2592
+ `Config saved but contextEngine slot is owned by "${parsed.slot?.previousOwner}". Use --force-slot to override.`,
2593
+ `配置已保存,但 contextEngine slot 被 "${parsed.slot?.previousOwner}" 占用。使用 --force-slot 覆盖。`,
2594
+ ));
2595
+ } else {
2596
+ err(tr(
2597
+ `Setup failed: ${setupError}`,
2598
+ `配置失败: ${setupError}`,
2599
+ ));
2600
+ }
2601
+ return {
2602
+ runtimeConfigOk: false,
2603
+ error: setupError,
2604
+ };
2605
+ }
2606
+ } else if (setupJsonSupported) {
2607
+ const setupError = setupResult
2608
+ ? `openclaw openviking setup did not return JSON (exit code ${setupResult.code})`
2609
+ : "openclaw openviking setup did not run";
2610
+ err(tr(`Setup failed: ${setupError}`, `配置失败: ${setupError}`));
2611
+ return {
2612
+ runtimeConfigOk: false,
2613
+ error: setupError,
2614
+ };
2615
+ }
2616
+
2617
+ if (!setupJsonSupported) {
2459
2618
  // Direct write: only used when the installed plugin doesn't support `setup --json` (old version).
2460
2619
  // Read the deployed configSchema to determine which fields are allowed, avoiding
2461
2620
  // "additionalProperties" validation failures when writing new fields to old schemas.
@@ -2471,8 +2630,9 @@ async function configureOpenClawPlugin({
2471
2630
  }
2472
2631
  } catch { /* ignore parse errors, write all fields */ }
2473
2632
 
2474
- const agentVal = effectiveRuntimeConfig.agent_prefix || "";
2475
- const useAgentPrefix = !allowedProps || allowedProps.has("agent_prefix");
2633
+ const peerRole = normalizePeerRole(effectiveRuntimeConfig.peer_role) || "assistant";
2634
+ const peerVal = effectiveRuntimeConfig.peer_prefix || "";
2635
+ const usePeerFields = !allowedProps || allowedProps.has("peer_role") || allowedProps.has("peer_prefix");
2476
2636
  const candidates = {
2477
2637
  mode: "remote",
2478
2638
  baseUrl: effectiveRuntimeConfig.baseUrl || remoteBaseUrl,
@@ -2480,10 +2640,9 @@ async function configureOpenClawPlugin({
2480
2640
  accountId: effectiveRuntimeConfig.accountId || undefined,
2481
2641
  userId: effectiveRuntimeConfig.userId || undefined,
2482
2642
  };
2483
- if (useAgentPrefix) {
2484
- candidates.agent_prefix = agentVal;
2485
- } else {
2486
- candidates.agentId = agentVal;
2643
+ if (usePeerFields) {
2644
+ candidates.peer_role = peerRole;
2645
+ if (peerVal) candidates.peer_prefix = peerVal;
2487
2646
  }
2488
2647
 
2489
2648
  const pluginConfig = {};
@@ -2592,7 +2751,7 @@ async function performUninstall() {
2592
2751
  tr("Confirm uninstall? (y/N)", "确认卸载?(y/N)"),
2593
2752
  "N",
2594
2753
  );
2595
- if (!isYes(answer)) {
2754
+ if (!isYes(answer)) {
2596
2755
  info(tr("Cancelled.", "已取消。"));
2597
2756
  return;
2598
2757
  }
@@ -2701,16 +2860,16 @@ async function main() {
2701
2860
  await rollbackLastUpgradeOperation();
2702
2861
  return;
2703
2862
  }
2704
- await resolveDefaultPluginVersion();
2705
- validateRequestedPluginVersion();
2706
- info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
2707
- info(tr(`Plugin source: ${pluginSource}`, `Plugin source: ${pluginSource}`));
2708
- if (pluginSource === "npm") {
2709
- info(tr(`Plugin package: ${pluginNpmPackage}`, `Plugin package: ${pluginNpmPackage}`));
2710
- } else {
2711
- info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
2712
- }
2713
- info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
2863
+ await resolveDefaultPluginVersion();
2864
+ validateRequestedPluginVersion();
2865
+ info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
2866
+ info(tr(`Plugin source: ${pluginSource}`, `Plugin source: ${pluginSource}`));
2867
+ if (pluginSource === "npm") {
2868
+ info(tr(`Plugin package: ${pluginNpmPackage}`, `Plugin package: ${pluginNpmPackage}`));
2869
+ } else {
2870
+ info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
2871
+ }
2872
+ info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
2714
2873
 
2715
2874
  if (upgradePluginOnly) {
2716
2875
  info(tr("Mode: plugin upgrade only", "模式: 仅升级插件"));