@xfxstudio/claworld 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -19
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -5
- package/skills/claworld-help/SKILL.md +84 -83
- package/skills/claworld-join-and-chat/SKILL.md +1 -1
- package/src/openclaw/plugin/onboarding.js +128 -103
- package/src/product-shell/agent-cards/spec-builder.js +2 -2
- package/src/product-shell/onboarding/onboarding-service.js +27 -25
- package/bin/claworld.mjs +0 -9
- package/src/openclaw/installer/cli.js +0 -406
- package/src/openclaw/installer/core.js +0 -2122
- package/src/openclaw/installer/doctor.js +0 -876
- package/src/openclaw/installer/workspace-contract.js +0 -427
|
@@ -1,2122 +0,0 @@
|
|
|
1
|
-
import { accessSync, constants as FS_CONSTANTS } from 'fs';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { spawnSync } from 'child_process';
|
|
6
|
-
import vm from 'vm';
|
|
7
|
-
import {
|
|
8
|
-
applyClaworldBootstrapConfig,
|
|
9
|
-
DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
10
|
-
DEFAULT_CLAWORLD_AGENT_ID,
|
|
11
|
-
DEFAULT_CLAWORLD_SERVER_URL,
|
|
12
|
-
ensureObject,
|
|
13
|
-
expandUserPath,
|
|
14
|
-
findClaworldManagedRuntimeBackup,
|
|
15
|
-
normalizeText,
|
|
16
|
-
resolveClaworldManagedRuntimeOptions,
|
|
17
|
-
setClaworldManagedRuntimeBackupState,
|
|
18
|
-
stripClaworldManagedRuntimeConfig,
|
|
19
|
-
} from '../plugin/managed-config.js';
|
|
20
|
-
import {
|
|
21
|
-
defaultClaworldAccountId,
|
|
22
|
-
inspectClaworldChannelAccount,
|
|
23
|
-
listClaworldAccountIds,
|
|
24
|
-
} from '../plugin/config-schema.js';
|
|
25
|
-
import {
|
|
26
|
-
CLAWORLD_INSTALLER_COMMAND,
|
|
27
|
-
CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
28
|
-
CLAWORLD_INSTALLER_PACKAGE_NAME,
|
|
29
|
-
CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
30
|
-
CLAWORLD_UNINSTALL_COMMAND,
|
|
31
|
-
CLAWORLD_UPDATE_COMMAND,
|
|
32
|
-
} from './constants.js';
|
|
33
|
-
import { seedManagedWorkspaceContract } from './workspace-contract.js';
|
|
34
|
-
|
|
35
|
-
export const DEFAULT_OPENCLAW_BIN = 'openclaw';
|
|
36
|
-
const DEFAULT_NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
37
|
-
export const DEFAULT_OPENCLAW_CONFIG_PATH = '~/.openclaw/openclaw.json';
|
|
38
|
-
export const DEFAULT_OPENCLAW_STATE_DIR = null;
|
|
39
|
-
export const DEFAULT_INSTALL_TIMEOUT_MS = 15_000;
|
|
40
|
-
export const DEFAULT_VERIFICATION_ATTEMPTS = 6;
|
|
41
|
-
export const DEFAULT_VERIFICATION_DELAY_MS = 1_000;
|
|
42
|
-
export const seedManagedWorkspace = seedManagedWorkspaceContract;
|
|
43
|
-
const TRACKED_PLUGIN_UPDATEABLE_SOURCES = new Set(['npm', 'marketplace']);
|
|
44
|
-
|
|
45
|
-
function resolveRequireGatewayRunning(env = process.env) {
|
|
46
|
-
const normalized = normalizeText(env?.CLAWORLD_INSTALLER_REQUIRE_GATEWAY_RUNNING, null);
|
|
47
|
-
if (!normalized) return true;
|
|
48
|
-
const lowered = normalized.toLowerCase();
|
|
49
|
-
if (['0', 'false', 'no', 'off'].includes(lowered)) return false;
|
|
50
|
-
if (['1', 'true', 'yes', 'on'].includes(lowered)) return true;
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function normalizeComparablePath(value) {
|
|
55
|
-
if (!value) return null;
|
|
56
|
-
return path.resolve(String(value));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function splitPackageNameSegments(packageName = CLAWORLD_INSTALLER_PACKAGE_NAME) {
|
|
60
|
-
const normalized = String(packageName || '').trim();
|
|
61
|
-
if (!normalized) return ['claworld'];
|
|
62
|
-
return normalized.split('/').filter(Boolean);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function resolveInstallerManagedPluginInstallRoot(configPath = DEFAULT_OPENCLAW_CONFIG_PATH) {
|
|
66
|
-
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
67
|
-
return path.join(path.dirname(resolvedConfigPath), 'extensions', 'claworld');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function resolveInstallerManagedPluginSourcePath(installRoot, packageName = CLAWORLD_INSTALLER_PACKAGE_NAME) {
|
|
71
|
-
return path.join(installRoot, 'node_modules', ...splitPackageNameSegments(packageName));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function readJsonFile(filePath) {
|
|
75
|
-
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function resolveLocalPluginInstallTarget({
|
|
79
|
-
installMode = 'npm',
|
|
80
|
-
installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
81
|
-
repoRoot = null,
|
|
82
|
-
commandRunner = defaultCommandRunner,
|
|
83
|
-
cwd = process.cwd(),
|
|
84
|
-
env = process.env,
|
|
85
|
-
dryRun = false,
|
|
86
|
-
} = {}) {
|
|
87
|
-
if (installMode === 'npm') {
|
|
88
|
-
return {
|
|
89
|
-
installSource,
|
|
90
|
-
managedRepoRoot: repoRoot,
|
|
91
|
-
stagedPackageRoot: null,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const resolvedRepoRoot = normalizeComparablePath(repoRoot);
|
|
96
|
-
const resolvedInstallSource = normalizeComparablePath(installSource);
|
|
97
|
-
const derivedRepoRoot = resolvedRepoRoot
|
|
98
|
-
|| (
|
|
99
|
-
resolvedInstallSource?.endsWith(`${path.sep}packages${path.sep}openclaw-plugin`)
|
|
100
|
-
? path.resolve(resolvedInstallSource, '..', '..')
|
|
101
|
-
: null
|
|
102
|
-
);
|
|
103
|
-
const shouldStageRepo = Boolean(
|
|
104
|
-
derivedRepoRoot
|
|
105
|
-
&& (
|
|
106
|
-
resolvedInstallSource === derivedRepoRoot
|
|
107
|
-
|| resolvedInstallSource === path.join(derivedRepoRoot, 'packages', 'openclaw-plugin')
|
|
108
|
-
|| resolvedInstallSource === CLAWORLD_INSTALLER_PACKAGE_NAME
|
|
109
|
-
|| resolvedInstallSource === CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE
|
|
110
|
-
),
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (!shouldStageRepo) {
|
|
114
|
-
return {
|
|
115
|
-
installSource,
|
|
116
|
-
managedRepoRoot: installMode === 'link' ? installSource : repoRoot,
|
|
117
|
-
stagedPackageRoot: null,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const stagedPackageRoot = path.join(derivedRepoRoot, '.tmp', 'openclaw-plugin-package');
|
|
122
|
-
const buildScriptPath = path.join(derivedRepoRoot, 'scripts', 'build-openclaw-plugin-package.mjs');
|
|
123
|
-
if (!dryRun) {
|
|
124
|
-
await executeCommand({
|
|
125
|
-
commandRunner,
|
|
126
|
-
bin: process.execPath,
|
|
127
|
-
args: [buildScriptPath, '--output-dir', stagedPackageRoot],
|
|
128
|
-
cwd,
|
|
129
|
-
env,
|
|
130
|
-
dryRun,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
installSource: stagedPackageRoot,
|
|
135
|
-
managedRepoRoot: stagedPackageRoot,
|
|
136
|
-
stagedPackageRoot,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function listBindings(config = {}) {
|
|
141
|
-
return Array.isArray(config.bindings) ? config.bindings : [];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function findAgentEntry(config = {}, agentId) {
|
|
145
|
-
const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
146
|
-
return list
|
|
147
|
-
.map((item) => ensureObject(item))
|
|
148
|
-
.find((item) => item.id === agentId) || null;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function hasManagedBinding(config = {}, { agentId, accountId } = {}) {
|
|
152
|
-
return listBindings(config).some((binding) => {
|
|
153
|
-
const candidate = ensureObject(binding);
|
|
154
|
-
const match = ensureObject(candidate.match);
|
|
155
|
-
return candidate.agentId === agentId
|
|
156
|
-
&& match.channel === 'claworld'
|
|
157
|
-
&& match.accountId === accountId;
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function isManagedToolAllowlistReady(config = {}, options = {}) {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function isRelayBootstrapReady(account = {}) {
|
|
166
|
-
return Boolean(
|
|
167
|
-
account?.configured
|
|
168
|
-
&& normalizeText(account?.appToken, null),
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function parseCommandVersion(text) {
|
|
173
|
-
const match = String(text || '').match(/OpenClaw\s+([0-9]+(?:\.[0-9]+)+(?:-[0-9A-Za-z.-]+)?)/i)
|
|
174
|
-
|| String(text || '').match(/([0-9]+(?:\.[0-9]+)+(?:-[0-9A-Za-z.-]+)?)/);
|
|
175
|
-
return match ? match[1] : null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function parseVersionParts(version) {
|
|
179
|
-
const normalized = String(version || '')
|
|
180
|
-
.trim()
|
|
181
|
-
.replace(/^[^\d]+/, '');
|
|
182
|
-
return normalized
|
|
183
|
-
.split('.')
|
|
184
|
-
.map((value) => Number.parseInt(value, 10))
|
|
185
|
-
.filter((value) => Number.isFinite(value));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function compareVersionParts(leftVersion, rightVersion) {
|
|
189
|
-
const leftParts = parseVersionParts(leftVersion);
|
|
190
|
-
const rightParts = parseVersionParts(rightVersion);
|
|
191
|
-
const length = Math.max(leftParts.length, rightParts.length);
|
|
192
|
-
for (let index = 0; index < length; index += 1) {
|
|
193
|
-
const left = leftParts[index] ?? 0;
|
|
194
|
-
const right = rightParts[index] ?? 0;
|
|
195
|
-
if (left > right) return 1;
|
|
196
|
-
if (left < right) return -1;
|
|
197
|
-
}
|
|
198
|
-
return 0;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
202
|
-
const parsed = new URL(serverUrl);
|
|
203
|
-
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
204
|
-
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
205
|
-
parsed.pathname = '';
|
|
206
|
-
parsed.search = '';
|
|
207
|
-
parsed.hash = '';
|
|
208
|
-
return parsed.toString().replace(/\/$/, '');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function buildFetchHeaders({ apiKey = null, appToken = null, body = false } = {}) {
|
|
212
|
-
return {
|
|
213
|
-
accept: 'application/json',
|
|
214
|
-
...(body ? { 'content-type': 'application/json' } : {}),
|
|
215
|
-
...(apiKey ? { 'x-api-key': apiKey } : {}),
|
|
216
|
-
...(appToken ? { authorization: `Bearer ${appToken}` } : {}),
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function createInstallerError(code, message, context = {}) {
|
|
221
|
-
const error = new Error(message);
|
|
222
|
-
error.code = code;
|
|
223
|
-
error.context = context;
|
|
224
|
-
return error;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function cloneObject(value = {}) {
|
|
228
|
-
return JSON.parse(JSON.stringify(ensureObject(value)));
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function isExplicitCommandPath(command = '') {
|
|
232
|
-
const normalized = String(command || '').trim();
|
|
233
|
-
if (!normalized) return false;
|
|
234
|
-
return normalized.includes('/') || normalized.includes('\\') || path.isAbsolute(normalized);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function splitPathEnvEntries(pathValue = '') {
|
|
238
|
-
return String(pathValue || '')
|
|
239
|
-
.split(path.delimiter)
|
|
240
|
-
.map((entry) => entry.trim())
|
|
241
|
-
.filter(Boolean);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function isNodeModulesBinEntry(entry = '') {
|
|
245
|
-
const normalized = path.resolve(String(entry || ''));
|
|
246
|
-
return path.basename(normalized) === '.bin'
|
|
247
|
-
&& path.basename(path.dirname(normalized)) === 'node_modules';
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function resolveCommandNameCandidates(command = '', env = process.env) {
|
|
251
|
-
const normalized = String(command || '').trim();
|
|
252
|
-
if (!normalized) return [];
|
|
253
|
-
if (process.platform !== 'win32' || path.extname(normalized)) {
|
|
254
|
-
return [normalized];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const pathExt = splitPathEnvEntries(env?.PATHEXT || process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD')
|
|
258
|
-
.map((ext) => ext.toLowerCase());
|
|
259
|
-
return [...new Set([normalized, ...pathExt.map((ext) => `${normalized}${ext}`)])];
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function hasExecutableAccess(filePath = '') {
|
|
263
|
-
try {
|
|
264
|
-
accessSync(filePath, FS_CONSTANTS.X_OK);
|
|
265
|
-
return true;
|
|
266
|
-
} catch {
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function resolveOpenclawCliBinary({
|
|
272
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
273
|
-
env = process.env,
|
|
274
|
-
} = {}) {
|
|
275
|
-
const requestedBin = normalizeText(openclawBin, DEFAULT_OPENCLAW_BIN);
|
|
276
|
-
if (
|
|
277
|
-
requestedBin !== DEFAULT_OPENCLAW_BIN
|
|
278
|
-
|| isExplicitCommandPath(requestedBin)
|
|
279
|
-
) {
|
|
280
|
-
return {
|
|
281
|
-
requestedBin,
|
|
282
|
-
binaryPath: requestedBin,
|
|
283
|
-
binarySource: isExplicitCommandPath(requestedBin) ? 'explicit_path' : 'explicit_command',
|
|
284
|
-
skippedLocalBinaryPaths: [],
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const candidates = [];
|
|
289
|
-
for (const entry of splitPathEnvEntries(env?.PATH || process.env.PATH || '')) {
|
|
290
|
-
for (const name of resolveCommandNameCandidates(requestedBin, env)) {
|
|
291
|
-
const candidatePath = path.join(entry, name);
|
|
292
|
-
if (hasExecutableAccess(candidatePath)) {
|
|
293
|
-
candidates.push(candidatePath);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const uniqueCandidates = [...new Set(candidates)];
|
|
299
|
-
const hostCandidates = uniqueCandidates.filter((candidate) => !isNodeModulesBinEntry(path.dirname(candidate)));
|
|
300
|
-
const localCandidates = uniqueCandidates.filter((candidate) => isNodeModulesBinEntry(path.dirname(candidate)));
|
|
301
|
-
const binaryPath = hostCandidates[0] || uniqueCandidates[0] || requestedBin;
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
requestedBin,
|
|
305
|
-
binaryPath,
|
|
306
|
-
binarySource: hostCandidates[0]
|
|
307
|
-
? 'host_path'
|
|
308
|
-
: uniqueCandidates[0]
|
|
309
|
-
? 'package_local_path'
|
|
310
|
-
: 'default_command',
|
|
311
|
-
skippedLocalBinaryPaths: localCandidates.filter((candidate) => candidate !== binaryPath),
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function parseJson(text, fallback = null) {
|
|
316
|
-
try {
|
|
317
|
-
return JSON.parse(String(text || '').trim());
|
|
318
|
-
} catch {
|
|
319
|
-
return fallback;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
export function parseJsonDocument(text, fallback = null) {
|
|
324
|
-
const source = String(text || '').trim();
|
|
325
|
-
const direct = parseJson(source, null);
|
|
326
|
-
if (direct) return direct;
|
|
327
|
-
|
|
328
|
-
const lines = source.split('\n');
|
|
329
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
330
|
-
const candidate = lines.slice(index).join('\n').trim();
|
|
331
|
-
if (!candidate.startsWith('{') && !candidate.startsWith('[')) continue;
|
|
332
|
-
const parsed = parseJson(candidate, null);
|
|
333
|
-
if (parsed) return parsed;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return fallback;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function parseCommandJsonOutput(result = {}, fallback = null) {
|
|
340
|
-
const stdout = String(result?.stdout || '').trim();
|
|
341
|
-
const stderr = String(result?.stderr || '').trim();
|
|
342
|
-
const combined = [stdout, stderr].filter(Boolean).join('\n');
|
|
343
|
-
const reversed = [stderr, stdout].filter(Boolean).join('\n');
|
|
344
|
-
return (
|
|
345
|
-
parseJsonDocument(stdout, null)
|
|
346
|
-
|| parseJsonDocument(stderr, null)
|
|
347
|
-
|| parseJsonDocument(combined, null)
|
|
348
|
-
|| parseJsonDocument(reversed, null)
|
|
349
|
-
|| fallback
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function parseLegacyChannelTokenStatus(tokenValue = '') {
|
|
354
|
-
const normalized = normalizeText(tokenValue, '').toLowerCase();
|
|
355
|
-
if (!normalized || normalized === 'missing' || normalized === 'none' || normalized === 'unset') {
|
|
356
|
-
return {
|
|
357
|
-
tokenStatus: 'missing',
|
|
358
|
-
tokenSource: 'none',
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
if (
|
|
362
|
-
normalized === 'registration'
|
|
363
|
-
|| normalized === 'registration-required'
|
|
364
|
-
|| normalized === 'registration_required'
|
|
365
|
-
|| normalized === 'agentcode'
|
|
366
|
-
|| normalized === 'agent-code'
|
|
367
|
-
) {
|
|
368
|
-
return {
|
|
369
|
-
tokenStatus: 'registration_required',
|
|
370
|
-
tokenSource: 'registration',
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
return {
|
|
374
|
-
tokenStatus: 'available',
|
|
375
|
-
tokenSource: normalized,
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function parseLegacyChannelStatus(text = '') {
|
|
380
|
-
const entries = String(text || '')
|
|
381
|
-
.split('\n')
|
|
382
|
-
.map((line) => line.trim())
|
|
383
|
-
.filter(Boolean)
|
|
384
|
-
.map((line) => line.match(/^-+\s*Claworld\s+(.+?):\s*(.+)$/i))
|
|
385
|
-
.filter(Boolean)
|
|
386
|
-
.map((match) => {
|
|
387
|
-
const accountId = normalizeText(match[1], null);
|
|
388
|
-
const flags = String(match[2] || '')
|
|
389
|
-
.split(',')
|
|
390
|
-
.map((flag) => flag.trim().toLowerCase())
|
|
391
|
-
.filter(Boolean);
|
|
392
|
-
const tokenFlag = flags.find((flag) => flag.startsWith('token:')) || '';
|
|
393
|
-
const tokenValue = tokenFlag.includes(':') ? tokenFlag.split(':').slice(1).join(':').trim() : '';
|
|
394
|
-
const tokenState = parseLegacyChannelTokenStatus(tokenValue);
|
|
395
|
-
const notConfigured = flags.includes('not configured') || flags.includes('unconfigured');
|
|
396
|
-
|
|
397
|
-
return {
|
|
398
|
-
accountId,
|
|
399
|
-
enabled: flags.includes('disabled') ? false : true,
|
|
400
|
-
configured: notConfigured ? false : flags.includes('configured'),
|
|
401
|
-
...tokenState,
|
|
402
|
-
rawStatus: flags,
|
|
403
|
-
};
|
|
404
|
-
})
|
|
405
|
-
.filter((entry) => entry.accountId);
|
|
406
|
-
|
|
407
|
-
if (entries.length === 0) {
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return {
|
|
412
|
-
channels: {
|
|
413
|
-
claworld: {
|
|
414
|
-
configured: entries.some((entry) => entry.configured === true),
|
|
415
|
-
},
|
|
416
|
-
},
|
|
417
|
-
channelAccounts: {
|
|
418
|
-
claworld: entries,
|
|
419
|
-
},
|
|
420
|
-
sourceFormat: 'legacy_text',
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function readClaworldAccountStatus(channelsStatus, accountId) {
|
|
425
|
-
const items = Array.isArray(channelsStatus?.channelAccounts?.claworld)
|
|
426
|
-
? channelsStatus.channelAccounts.claworld
|
|
427
|
-
: [];
|
|
428
|
-
return items.find((item) => item?.accountId === accountId) || null;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function parsePluginInfo(text = '') {
|
|
432
|
-
const source = String(text || '');
|
|
433
|
-
const status = source.match(/^\s*Status:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
434
|
-
const install = source.match(/^\s*Install:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
435
|
-
const sourcePath = source.match(/^\s*Source path:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
436
|
-
const version = source.match(/^\s*Version:\s+(.+)$/m)?.[1]?.trim()
|
|
437
|
-
|| source.match(/^\s*Recorded version:\s+(.+)$/m)?.[1]?.trim()
|
|
438
|
-
|| null;
|
|
439
|
-
return {
|
|
440
|
-
installed: Boolean(source.trim()),
|
|
441
|
-
loaded: status === 'loaded',
|
|
442
|
-
status,
|
|
443
|
-
install,
|
|
444
|
-
sourcePath,
|
|
445
|
-
version,
|
|
446
|
-
raw: source,
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function parseBindingLine(text = '', { agentId, accountId } = {}) {
|
|
451
|
-
return String(text || '')
|
|
452
|
-
.split('\n')
|
|
453
|
-
.map((line) => line.trim())
|
|
454
|
-
.find((line) => line === `- ${agentId} <- claworld accountId=${accountId}`) || null;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function normalizePathSuffix(value = '') {
|
|
458
|
-
return String(value || '').replace(/\\/g, '/').replace(/\/+$/, '');
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function isInstallerManagedLocalSourceRecord(installRecord = {}) {
|
|
462
|
-
const source = normalizeText(installRecord.source, null);
|
|
463
|
-
if (source !== 'path') {
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
const spec = normalizeText(installRecord.spec, null) || normalizeText(installRecord.resolvedSpec, null);
|
|
467
|
-
if (!spec || !spec.startsWith('@xfxstudio/claworld@')) {
|
|
468
|
-
return false;
|
|
469
|
-
}
|
|
470
|
-
const installPath = normalizePathSuffix(installRecord.installPath);
|
|
471
|
-
const sourcePath = normalizePathSuffix(installRecord.sourcePath);
|
|
472
|
-
return (
|
|
473
|
-
installPath.endsWith('/extensions/claworld')
|
|
474
|
-
&& sourcePath.endsWith('/extensions/claworld/node_modules/@xfxstudio/claworld')
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function inspectTrackedClaworldPluginInstall(config = {}) {
|
|
479
|
-
const installRecord = ensureObject(config?.plugins?.installs?.claworld);
|
|
480
|
-
const tracked = Object.keys(installRecord).length > 0;
|
|
481
|
-
const recordedSource = normalizeText(installRecord.source, null);
|
|
482
|
-
const source = isInstallerManagedLocalSourceRecord(installRecord)
|
|
483
|
-
? 'installer_npm'
|
|
484
|
-
: recordedSource;
|
|
485
|
-
return {
|
|
486
|
-
tracked,
|
|
487
|
-
updateable: tracked && TRACKED_PLUGIN_UPDATEABLE_SOURCES.has(source),
|
|
488
|
-
source,
|
|
489
|
-
recordedSource,
|
|
490
|
-
spec: normalizeText(installRecord.spec, null),
|
|
491
|
-
resolvedSpec: normalizeText(installRecord.resolvedSpec, null),
|
|
492
|
-
resolvedVersion: normalizeText(installRecord.resolvedVersion, null),
|
|
493
|
-
installPath: normalizeText(installRecord.installPath, null),
|
|
494
|
-
sourcePath: normalizeText(installRecord.sourcePath, null),
|
|
495
|
-
record: tracked ? installRecord : null,
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function needsPluginBootstrapConfig(config = {}) {
|
|
500
|
-
if (config?.channels?.claworld) {
|
|
501
|
-
return true;
|
|
502
|
-
}
|
|
503
|
-
return listBindings(config).some((binding) => ensureObject(binding).match?.channel === 'claworld');
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function buildPluginBootstrapConfig(config = {}) {
|
|
507
|
-
const next = cloneObject(config);
|
|
508
|
-
|
|
509
|
-
const channels = ensureObject(next.channels);
|
|
510
|
-
if (Object.prototype.hasOwnProperty.call(channels, 'claworld')) {
|
|
511
|
-
delete channels.claworld;
|
|
512
|
-
}
|
|
513
|
-
if (Object.keys(channels).length > 0) {
|
|
514
|
-
next.channels = channels;
|
|
515
|
-
} else {
|
|
516
|
-
delete next.channels;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (Array.isArray(next.bindings)) {
|
|
520
|
-
next.bindings = next.bindings.filter((binding) => ensureObject(binding).match?.channel !== 'claworld');
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
return next;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async function preparePluginBootstrapConfig({
|
|
527
|
-
configPath = null,
|
|
528
|
-
config = {},
|
|
529
|
-
dryRun = false,
|
|
530
|
-
} = {}) {
|
|
531
|
-
if (!configPath || !needsPluginBootstrapConfig(config)) {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
if (dryRun) {
|
|
536
|
-
return {
|
|
537
|
-
configPath,
|
|
538
|
-
pluginConfig: ensureObject(config.plugins),
|
|
539
|
-
cleanup: async () => {},
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'claworld-plugin-bootstrap-'));
|
|
544
|
-
const tempConfigPath = path.join(tempRoot, path.basename(configPath || 'openclaw.json'));
|
|
545
|
-
await writeConfig(tempConfigPath, buildPluginBootstrapConfig(config));
|
|
546
|
-
return {
|
|
547
|
-
configPath: tempConfigPath,
|
|
548
|
-
pluginConfig: null,
|
|
549
|
-
cleanup: async () => {
|
|
550
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
551
|
-
},
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function mergePluginMetadata(config = {}, pluginConfig = {}) {
|
|
556
|
-
const extraPlugins = ensureObject(pluginConfig);
|
|
557
|
-
if (Object.keys(extraPlugins).length === 0) {
|
|
558
|
-
return cloneObject(config);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const next = cloneObject(config);
|
|
562
|
-
const existingPlugins = ensureObject(next.plugins);
|
|
563
|
-
next.plugins = {
|
|
564
|
-
...existingPlugins,
|
|
565
|
-
...extraPlugins,
|
|
566
|
-
entries: {
|
|
567
|
-
...ensureObject(existingPlugins.entries),
|
|
568
|
-
...ensureObject(extraPlugins.entries),
|
|
569
|
-
},
|
|
570
|
-
installs: {
|
|
571
|
-
...ensureObject(existingPlugins.installs),
|
|
572
|
-
...ensureObject(extraPlugins.installs),
|
|
573
|
-
},
|
|
574
|
-
};
|
|
575
|
-
return next;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
async function installPublishedClaworldPackageToLocalSource({
|
|
579
|
-
installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
580
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
581
|
-
commandRunner = defaultCommandRunner,
|
|
582
|
-
cwd = process.cwd(),
|
|
583
|
-
env = process.env,
|
|
584
|
-
dryRun = false,
|
|
585
|
-
refresh = false,
|
|
586
|
-
} = {}) {
|
|
587
|
-
const installRoot = resolveInstallerManagedPluginInstallRoot(configPath);
|
|
588
|
-
const sourcePath = resolveInstallerManagedPluginSourcePath(installRoot);
|
|
589
|
-
if (!dryRun && refresh) {
|
|
590
|
-
await fs.rm(installRoot, { recursive: true, force: true });
|
|
591
|
-
}
|
|
592
|
-
if (!dryRun) {
|
|
593
|
-
await fs.mkdir(installRoot, { recursive: true });
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
await executeCommand({
|
|
597
|
-
commandRunner,
|
|
598
|
-
bin: DEFAULT_NPM_BIN,
|
|
599
|
-
args: [
|
|
600
|
-
'install',
|
|
601
|
-
'--ignore-scripts',
|
|
602
|
-
'--no-package-lock',
|
|
603
|
-
'--omit=dev',
|
|
604
|
-
'--prefix',
|
|
605
|
-
installRoot,
|
|
606
|
-
installSource,
|
|
607
|
-
],
|
|
608
|
-
cwd,
|
|
609
|
-
env,
|
|
610
|
-
dryRun,
|
|
611
|
-
capture: false,
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
let resolvedVersion = null;
|
|
615
|
-
if (!dryRun) {
|
|
616
|
-
const sourcePackageJson = await readJsonFile(path.join(sourcePath, 'package.json'));
|
|
617
|
-
resolvedVersion = normalizeText(sourcePackageJson?.version, null);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
return {
|
|
621
|
-
installRoot,
|
|
622
|
-
sourcePath,
|
|
623
|
-
resolvedVersion,
|
|
624
|
-
pluginConfig: {
|
|
625
|
-
installs: {
|
|
626
|
-
claworld: {
|
|
627
|
-
source: 'path',
|
|
628
|
-
spec: installSource,
|
|
629
|
-
resolvedSpec: installSource,
|
|
630
|
-
...(resolvedVersion ? { resolvedVersion } : {}),
|
|
631
|
-
installPath: installRoot,
|
|
632
|
-
sourcePath,
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
function defaultCommandRunner({
|
|
640
|
-
bin,
|
|
641
|
-
args,
|
|
642
|
-
cwd = process.cwd(),
|
|
643
|
-
env = process.env,
|
|
644
|
-
dryRun = false,
|
|
645
|
-
capture = true,
|
|
646
|
-
} = {}) {
|
|
647
|
-
const resolvedBin = resolveOpenclawCliBinary({
|
|
648
|
-
openclawBin: bin,
|
|
649
|
-
env,
|
|
650
|
-
});
|
|
651
|
-
const effectiveBin = resolvedBin.binaryPath;
|
|
652
|
-
const rendered = [effectiveBin, ...args].join(' ');
|
|
653
|
-
if (dryRun) {
|
|
654
|
-
return {
|
|
655
|
-
status: 0,
|
|
656
|
-
stdout: '',
|
|
657
|
-
stderr: '',
|
|
658
|
-
rendered,
|
|
659
|
-
dryRun: true,
|
|
660
|
-
requestedBin: bin,
|
|
661
|
-
resolvedBin: effectiveBin,
|
|
662
|
-
binSource: resolvedBin.binarySource,
|
|
663
|
-
skippedBinCandidates: resolvedBin.skippedLocalBinaryPaths,
|
|
664
|
-
};
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(effectiveBin);
|
|
668
|
-
const result = spawnSync(effectiveBin, args, {
|
|
669
|
-
cwd,
|
|
670
|
-
env,
|
|
671
|
-
shell: useShell,
|
|
672
|
-
windowsHide: true,
|
|
673
|
-
stdio: capture ? 'pipe' : 'inherit',
|
|
674
|
-
encoding: 'utf8',
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
if (result.error) {
|
|
678
|
-
const error = createInstallerError(
|
|
679
|
-
'openclaw_command_failed',
|
|
680
|
-
`Failed to run "${rendered}": ${result.error.message}`,
|
|
681
|
-
{ rendered },
|
|
682
|
-
);
|
|
683
|
-
error.cause = result.error;
|
|
684
|
-
throw error;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
return {
|
|
688
|
-
status: result.status ?? 0,
|
|
689
|
-
stdout: result.stdout || '',
|
|
690
|
-
stderr: result.stderr || '',
|
|
691
|
-
rendered,
|
|
692
|
-
requestedBin: bin,
|
|
693
|
-
resolvedBin: effectiveBin,
|
|
694
|
-
binSource: resolvedBin.binarySource,
|
|
695
|
-
skippedBinCandidates: resolvedBin.skippedLocalBinaryPaths,
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
async function executeCommand({
|
|
700
|
-
commandRunner = defaultCommandRunner,
|
|
701
|
-
bin,
|
|
702
|
-
args,
|
|
703
|
-
cwd = process.cwd(),
|
|
704
|
-
env = process.env,
|
|
705
|
-
dryRun = false,
|
|
706
|
-
capture = true,
|
|
707
|
-
allowFailure = false,
|
|
708
|
-
} = {}) {
|
|
709
|
-
const result = await Promise.resolve(commandRunner({
|
|
710
|
-
bin,
|
|
711
|
-
args,
|
|
712
|
-
cwd,
|
|
713
|
-
env,
|
|
714
|
-
dryRun,
|
|
715
|
-
capture,
|
|
716
|
-
}));
|
|
717
|
-
if (!allowFailure && result.status !== 0) {
|
|
718
|
-
throw createInstallerError(
|
|
719
|
-
'openclaw_command_failed',
|
|
720
|
-
`Command failed (${result.status}): ${result.rendered || [bin, ...args].join(' ')}`,
|
|
721
|
-
{ result },
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
return result;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
export function parseConfigObject(sourceText, sourceLabel = 'openclaw config') {
|
|
728
|
-
const text = String(sourceText || '').replace(/^\uFEFF/, '').trim();
|
|
729
|
-
if (!text) return {};
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
const parsed = JSON.parse(text);
|
|
733
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
734
|
-
throw new Error('config root must be an object');
|
|
735
|
-
}
|
|
736
|
-
return parsed;
|
|
737
|
-
} catch {}
|
|
738
|
-
|
|
739
|
-
try {
|
|
740
|
-
const expression = text.replace(/;\s*$/, '');
|
|
741
|
-
const script = new vm.Script(`(${expression}\n)`, { filename: sourceLabel });
|
|
742
|
-
const result = script.runInNewContext(Object.create(null), { timeout: 1000 });
|
|
743
|
-
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
744
|
-
throw new Error('config root must evaluate to an object');
|
|
745
|
-
}
|
|
746
|
-
return JSON.parse(JSON.stringify(result));
|
|
747
|
-
} catch (error) {
|
|
748
|
-
throw createInstallerError(
|
|
749
|
-
'invalid_openclaw_config',
|
|
750
|
-
`Failed to parse ${sourceLabel}: ${error.message}`,
|
|
751
|
-
{ sourceLabel },
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
export async function loadConfigFromDisk(configPath) {
|
|
757
|
-
try {
|
|
758
|
-
const raw = await fs.readFile(configPath, 'utf8');
|
|
759
|
-
return { existed: true, config: parseConfigObject(raw, configPath) };
|
|
760
|
-
} catch (error) {
|
|
761
|
-
if (error && error.code === 'ENOENT') {
|
|
762
|
-
return { existed: false, config: {} };
|
|
763
|
-
}
|
|
764
|
-
throw error;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
export function resolveClaworldInstallerStatePath(configPath) {
|
|
769
|
-
const resolvedConfigPath = path.resolve(String(configPath || DEFAULT_OPENCLAW_CONFIG_PATH));
|
|
770
|
-
return path.join(path.dirname(resolvedConfigPath), '.claworld-installer-state.json');
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
export async function loadInstallerStateFromDisk(installerStatePath) {
|
|
774
|
-
try {
|
|
775
|
-
const raw = await fs.readFile(installerStatePath, 'utf8');
|
|
776
|
-
return { existed: true, state: parseConfigObject(raw, installerStatePath) };
|
|
777
|
-
} catch (error) {
|
|
778
|
-
if (error && error.code === 'ENOENT') {
|
|
779
|
-
return { existed: false, state: {} };
|
|
780
|
-
}
|
|
781
|
-
throw error;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
export async function backupConfigIfPresent(configPath, existed, dryRun = false) {
|
|
786
|
-
if (!existed) return null;
|
|
787
|
-
const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '').replace('T', '-');
|
|
788
|
-
const backupPath = `${configPath}.bak.${stamp}`;
|
|
789
|
-
if (dryRun) return backupPath;
|
|
790
|
-
await fs.copyFile(configPath, backupPath);
|
|
791
|
-
return backupPath;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
export async function writeConfig(configPath, config, dryRun = false) {
|
|
795
|
-
const rendered = `${JSON.stringify(config, null, 2)}\n`;
|
|
796
|
-
if (dryRun) return rendered;
|
|
797
|
-
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
798
|
-
await fs.writeFile(configPath, rendered, 'utf8');
|
|
799
|
-
return rendered;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
export async function writeInstallerState(installerStatePath, installerState, dryRun = false) {
|
|
803
|
-
const renderedState = ensureObject(installerState);
|
|
804
|
-
if (Object.keys(renderedState).length === 0) {
|
|
805
|
-
if (dryRun) return '';
|
|
806
|
-
await fs.rm(installerStatePath, { force: true });
|
|
807
|
-
return '';
|
|
808
|
-
}
|
|
809
|
-
const rendered = `${JSON.stringify(renderedState, null, 2)}\n`;
|
|
810
|
-
if (dryRun) return rendered;
|
|
811
|
-
await fs.mkdir(path.dirname(installerStatePath), { recursive: true });
|
|
812
|
-
await fs.writeFile(installerStatePath, rendered, 'utf8');
|
|
813
|
-
return rendered;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
export function buildOpenclawCommandEnv({ configPath, stateDir = null, env = process.env } = {}) {
|
|
817
|
-
return {
|
|
818
|
-
...env,
|
|
819
|
-
...(configPath ? { OPENCLAW_CONFIG_PATH: configPath } : {}),
|
|
820
|
-
...(stateDir ? { OPENCLAW_STATE_DIR: stateDir } : {}),
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
export function inspectManagedClaworldInstall({
|
|
825
|
-
cfg = {},
|
|
826
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
827
|
-
input = {},
|
|
828
|
-
overrides = {},
|
|
829
|
-
installerState = {},
|
|
830
|
-
} = {}) {
|
|
831
|
-
const configuredAccountIds = listClaworldAccountIds(cfg);
|
|
832
|
-
const hasAnyConfig = configuredAccountIds.length > 0 || cfg?.channels?.claworld != null;
|
|
833
|
-
const managedOptions = resolveClaworldManagedRuntimeOptions({
|
|
834
|
-
cfg,
|
|
835
|
-
accountId,
|
|
836
|
-
input,
|
|
837
|
-
overrides,
|
|
838
|
-
installerState,
|
|
839
|
-
});
|
|
840
|
-
const managedAgentPresent = Boolean(findAgentEntry(cfg, managedOptions.agentId));
|
|
841
|
-
const managedBindingPresent = hasManagedBinding(cfg, managedOptions);
|
|
842
|
-
const toolsReady = isManagedToolAllowlistReady(cfg, managedOptions);
|
|
843
|
-
const managedAccountPresent = configuredAccountIds.includes(managedOptions.accountId);
|
|
844
|
-
const accountStatus = managedAccountPresent
|
|
845
|
-
? inspectClaworldChannelAccount(cfg, managedOptions.accountId)
|
|
846
|
-
: inspectClaworldChannelAccount({}, managedOptions.accountId);
|
|
847
|
-
const managedRuntimeReady = isRelayBootstrapReady(accountStatus);
|
|
848
|
-
const managedReady = Boolean(
|
|
849
|
-
managedAccountPresent
|
|
850
|
-
&& managedRuntimeReady
|
|
851
|
-
&& managedBindingPresent
|
|
852
|
-
&& toolsReady
|
|
853
|
-
&& (managedOptions.manageAgentEntry !== true || managedAgentPresent)
|
|
854
|
-
);
|
|
855
|
-
|
|
856
|
-
let statusLabel = 'needs setup';
|
|
857
|
-
let selectionHint = 'remote relay world channel';
|
|
858
|
-
if (managedRuntimeReady) {
|
|
859
|
-
statusLabel = managedReady ? 'configured' : 'configured (managed refresh recommended)';
|
|
860
|
-
selectionHint = managedReady ? 'configured · managed runtime' : 'configured · managed refresh';
|
|
861
|
-
} else if (hasAnyConfig) {
|
|
862
|
-
statusLabel = 'configured (activation pending)';
|
|
863
|
-
selectionHint = 'configured · activation pending';
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
return {
|
|
867
|
-
hasAnyConfig,
|
|
868
|
-
configuredAccountIds,
|
|
869
|
-
defaultAccountId: defaultClaworldAccountId(cfg) || null,
|
|
870
|
-
managedOptions,
|
|
871
|
-
managedAccountPresent,
|
|
872
|
-
managedAgentPresent,
|
|
873
|
-
managedBindingPresent,
|
|
874
|
-
toolsReady,
|
|
875
|
-
accountStatus,
|
|
876
|
-
managedRuntimeReady,
|
|
877
|
-
managedReady,
|
|
878
|
-
reusableAppToken: normalizeText(
|
|
879
|
-
managedOptions.appToken,
|
|
880
|
-
normalizeText(accountStatus?.appToken, null),
|
|
881
|
-
),
|
|
882
|
-
statusLabel,
|
|
883
|
-
selectionHint,
|
|
884
|
-
quickstartScore: managedRuntimeReady ? 2 : 5,
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
export function buildManagedOnboardingStatus({ cfg = {}, accountId = DEFAULT_CLAWORLD_ACCOUNT_ID } = {}) {
|
|
889
|
-
const inspection = inspectManagedClaworldInstall({ cfg, accountId });
|
|
890
|
-
const configured = inspection.configuredAccountIds.some((configuredAccountId) =>
|
|
891
|
-
isRelayBootstrapReady(inspectClaworldChannelAccount(cfg, configuredAccountId)),
|
|
892
|
-
);
|
|
893
|
-
return {
|
|
894
|
-
configured,
|
|
895
|
-
statusLines: [`Claworld: ${inspection.statusLabel}`],
|
|
896
|
-
selectionHint: inspection.selectionHint,
|
|
897
|
-
quickstartScore: configured ? 2 : inspection.quickstartScore,
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
export async function detectOpenclawHost({
|
|
902
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
903
|
-
commandRunner = defaultCommandRunner,
|
|
904
|
-
cwd = process.cwd(),
|
|
905
|
-
env = process.env,
|
|
906
|
-
dryRun = false,
|
|
907
|
-
} = {}) {
|
|
908
|
-
const result = await executeCommand({
|
|
909
|
-
commandRunner,
|
|
910
|
-
bin: openclawBin,
|
|
911
|
-
args: ['--version'],
|
|
912
|
-
cwd,
|
|
913
|
-
env,
|
|
914
|
-
dryRun,
|
|
915
|
-
});
|
|
916
|
-
const version = parseCommandVersion(result.stdout || result.stderr);
|
|
917
|
-
if (!version) {
|
|
918
|
-
throw createInstallerError(
|
|
919
|
-
'openclaw_version_unreadable',
|
|
920
|
-
'Unable to determine the installed OpenClaw version.',
|
|
921
|
-
{ result },
|
|
922
|
-
);
|
|
923
|
-
}
|
|
924
|
-
return {
|
|
925
|
-
version,
|
|
926
|
-
raw: (result.stdout || result.stderr || '').trim(),
|
|
927
|
-
requestedBin: result.requestedBin || openclawBin,
|
|
928
|
-
binaryPath: result.resolvedBin || openclawBin,
|
|
929
|
-
binarySource: result.binSource || 'default_command',
|
|
930
|
-
skippedLocalBinaryPaths: Array.isArray(result.skippedBinCandidates)
|
|
931
|
-
? result.skippedBinCandidates
|
|
932
|
-
: [],
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
export async function inspectClaworldPluginInstall({
|
|
937
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
938
|
-
configPath = null,
|
|
939
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
940
|
-
commandRunner = defaultCommandRunner,
|
|
941
|
-
cwd = process.cwd(),
|
|
942
|
-
env = process.env,
|
|
943
|
-
dryRun = false,
|
|
944
|
-
} = {}) {
|
|
945
|
-
const result = await executeCommand({
|
|
946
|
-
commandRunner,
|
|
947
|
-
bin: openclawBin,
|
|
948
|
-
args: ['plugins', 'info', 'claworld'],
|
|
949
|
-
cwd,
|
|
950
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
951
|
-
dryRun,
|
|
952
|
-
allowFailure: true,
|
|
953
|
-
});
|
|
954
|
-
if (result.status !== 0) {
|
|
955
|
-
return {
|
|
956
|
-
installed: false,
|
|
957
|
-
loaded: false,
|
|
958
|
-
status: null,
|
|
959
|
-
raw: `${result.stdout || ''}${result.stderr || ''}`.trim(),
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
return parsePluginInfo(`${result.stdout || ''}${result.stderr || ''}`);
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
export async function ensureClaworldPluginInstalled({
|
|
966
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
967
|
-
configPath = null,
|
|
968
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
969
|
-
config = {},
|
|
970
|
-
commandRunner = defaultCommandRunner,
|
|
971
|
-
cwd = process.cwd(),
|
|
972
|
-
env = process.env,
|
|
973
|
-
dryRun = false,
|
|
974
|
-
installMode = 'npm',
|
|
975
|
-
installSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
976
|
-
refresh = false,
|
|
977
|
-
} = {}) {
|
|
978
|
-
const trackedInstall = inspectTrackedClaworldPluginInstall(config);
|
|
979
|
-
const bootstrap = await preparePluginBootstrapConfig({
|
|
980
|
-
configPath,
|
|
981
|
-
config,
|
|
982
|
-
dryRun,
|
|
983
|
-
});
|
|
984
|
-
const pluginConfigPath = bootstrap?.configPath || configPath;
|
|
985
|
-
|
|
986
|
-
try {
|
|
987
|
-
const before = await inspectClaworldPluginInstall({
|
|
988
|
-
openclawBin,
|
|
989
|
-
configPath: pluginConfigPath,
|
|
990
|
-
stateDir,
|
|
991
|
-
commandRunner,
|
|
992
|
-
cwd,
|
|
993
|
-
env,
|
|
994
|
-
dryRun,
|
|
995
|
-
});
|
|
996
|
-
const reusableInstallerManagedNpmInstall = (
|
|
997
|
-
installMode === 'npm'
|
|
998
|
-
&& trackedInstall.tracked
|
|
999
|
-
&& trackedInstall.source === 'installer_npm'
|
|
1000
|
-
&& trackedInstall.recordedSource === 'path'
|
|
1001
|
-
&& normalizeText(trackedInstall.sourcePath, null)
|
|
1002
|
-
);
|
|
1003
|
-
if (
|
|
1004
|
-
before.installed
|
|
1005
|
-
&& !refresh
|
|
1006
|
-
&& (installMode !== 'npm' || reusableInstallerManagedNpmInstall)
|
|
1007
|
-
) {
|
|
1008
|
-
return {
|
|
1009
|
-
changed: false,
|
|
1010
|
-
action: 'reused_existing_plugin_install',
|
|
1011
|
-
plugin: before,
|
|
1012
|
-
pluginConfig: bootstrap?.pluginConfig || null,
|
|
1013
|
-
};
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
if (installMode === 'skip') {
|
|
1017
|
-
return {
|
|
1018
|
-
changed: false,
|
|
1019
|
-
action: 'skipped_plugin_install',
|
|
1020
|
-
plugin: before,
|
|
1021
|
-
pluginConfig: bootstrap?.pluginConfig || null,
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
if (installMode === 'npm') {
|
|
1026
|
-
const localInstall = await installPublishedClaworldPackageToLocalSource({
|
|
1027
|
-
installSource,
|
|
1028
|
-
configPath,
|
|
1029
|
-
commandRunner,
|
|
1030
|
-
cwd,
|
|
1031
|
-
env,
|
|
1032
|
-
dryRun,
|
|
1033
|
-
refresh,
|
|
1034
|
-
});
|
|
1035
|
-
return {
|
|
1036
|
-
changed: true,
|
|
1037
|
-
action: reusableInstallerManagedNpmInstall ? 'refreshed_local_plugin_source' : 'staged_local_plugin_source',
|
|
1038
|
-
plugin: before,
|
|
1039
|
-
pluginConfig: {
|
|
1040
|
-
...ensureObject(bootstrap?.pluginConfig),
|
|
1041
|
-
...ensureObject(localInstall.pluginConfig),
|
|
1042
|
-
entries: {
|
|
1043
|
-
...ensureObject(bootstrap?.pluginConfig?.entries),
|
|
1044
|
-
...ensureObject(localInstall.pluginConfig?.entries),
|
|
1045
|
-
},
|
|
1046
|
-
installs: {
|
|
1047
|
-
...ensureObject(bootstrap?.pluginConfig?.installs),
|
|
1048
|
-
...ensureObject(localInstall.pluginConfig?.installs),
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1051
|
-
installSource,
|
|
1052
|
-
managedRepoRoot: localInstall.sourcePath,
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
if (before.installed && refresh && installMode === 'copy') {
|
|
1057
|
-
await executeCommand({
|
|
1058
|
-
commandRunner,
|
|
1059
|
-
bin: openclawBin,
|
|
1060
|
-
args: ['plugins', 'uninstall', 'claworld', '--force'],
|
|
1061
|
-
cwd,
|
|
1062
|
-
env: buildOpenclawCommandEnv({ configPath: pluginConfigPath, stateDir, env }),
|
|
1063
|
-
dryRun,
|
|
1064
|
-
capture: false,
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const args = ['plugins', 'install', '--dangerously-force-unsafe-install'];
|
|
1069
|
-
if (installMode === 'link') args.push('--link');
|
|
1070
|
-
args.push(installSource);
|
|
1071
|
-
await executeCommand({
|
|
1072
|
-
commandRunner,
|
|
1073
|
-
bin: openclawBin,
|
|
1074
|
-
args,
|
|
1075
|
-
cwd,
|
|
1076
|
-
env: buildOpenclawCommandEnv({ configPath: pluginConfigPath, stateDir, env }),
|
|
1077
|
-
dryRun,
|
|
1078
|
-
capture: false,
|
|
1079
|
-
});
|
|
1080
|
-
|
|
1081
|
-
const after = await inspectClaworldPluginInstall({
|
|
1082
|
-
openclawBin,
|
|
1083
|
-
configPath: pluginConfigPath,
|
|
1084
|
-
stateDir,
|
|
1085
|
-
commandRunner,
|
|
1086
|
-
cwd,
|
|
1087
|
-
env,
|
|
1088
|
-
dryRun,
|
|
1089
|
-
});
|
|
1090
|
-
if (!after.installed) {
|
|
1091
|
-
throw createInstallerError(
|
|
1092
|
-
'claworld_plugin_install_failed',
|
|
1093
|
-
'OpenClaw did not report the claworld plugin as installed after plugin install.',
|
|
1094
|
-
{ installMode, installSource, before, after },
|
|
1095
|
-
);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
const pluginConfig = pluginConfigPath
|
|
1099
|
-
? (await loadConfigFromDisk(pluginConfigPath)).config?.plugins || null
|
|
1100
|
-
: null;
|
|
1101
|
-
|
|
1102
|
-
return {
|
|
1103
|
-
changed: true,
|
|
1104
|
-
action: before.installed ? 'refreshed_plugin_install' : 'installed_plugin',
|
|
1105
|
-
plugin: after,
|
|
1106
|
-
pluginConfig,
|
|
1107
|
-
installSource,
|
|
1108
|
-
};
|
|
1109
|
-
} finally {
|
|
1110
|
-
if (bootstrap) {
|
|
1111
|
-
await bootstrap.cleanup();
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
export async function updateTrackedClaworldPluginInstall({
|
|
1117
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1118
|
-
configPath = null,
|
|
1119
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1120
|
-
config = {},
|
|
1121
|
-
installSource = null,
|
|
1122
|
-
commandRunner = defaultCommandRunner,
|
|
1123
|
-
cwd = process.cwd(),
|
|
1124
|
-
env = process.env,
|
|
1125
|
-
dryRun = false,
|
|
1126
|
-
} = {}) {
|
|
1127
|
-
const trackedInstall = inspectTrackedClaworldPluginInstall(config);
|
|
1128
|
-
const before = await inspectClaworldPluginInstall({
|
|
1129
|
-
openclawBin,
|
|
1130
|
-
configPath,
|
|
1131
|
-
stateDir,
|
|
1132
|
-
commandRunner,
|
|
1133
|
-
cwd,
|
|
1134
|
-
env,
|
|
1135
|
-
dryRun,
|
|
1136
|
-
});
|
|
1137
|
-
|
|
1138
|
-
if (!before.installed) {
|
|
1139
|
-
throw createInstallerError(
|
|
1140
|
-
'claworld_plugin_not_installed',
|
|
1141
|
-
'The Claworld plugin is not installed or not discoverable. Run the install command before update.',
|
|
1142
|
-
{ plugin: before, trackedInstall },
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
if (trackedInstall.source === 'installer_npm') {
|
|
1147
|
-
const localInstall = await installPublishedClaworldPackageToLocalSource({
|
|
1148
|
-
installSource: normalizeText(
|
|
1149
|
-
installSource,
|
|
1150
|
-
trackedInstall.spec || CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
1151
|
-
),
|
|
1152
|
-
configPath,
|
|
1153
|
-
commandRunner,
|
|
1154
|
-
cwd,
|
|
1155
|
-
env,
|
|
1156
|
-
dryRun,
|
|
1157
|
-
refresh: true,
|
|
1158
|
-
});
|
|
1159
|
-
return {
|
|
1160
|
-
changed: true,
|
|
1161
|
-
action: dryRun ? 'dry_run_updated_local_plugin_source' : 'updated_local_plugin_source',
|
|
1162
|
-
trackedInstall,
|
|
1163
|
-
plugin: before,
|
|
1164
|
-
pluginConfig: localInstall.pluginConfig,
|
|
1165
|
-
managedRepoRoot: localInstall.sourcePath,
|
|
1166
|
-
};
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
if (!trackedInstall.tracked) {
|
|
1170
|
-
return {
|
|
1171
|
-
changed: false,
|
|
1172
|
-
action: 'skipped_untracked_plugin_update',
|
|
1173
|
-
reason: 'plugins.installs.claworld_missing',
|
|
1174
|
-
trackedInstall,
|
|
1175
|
-
plugin: before,
|
|
1176
|
-
};
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
if (!trackedInstall.updateable) {
|
|
1180
|
-
return {
|
|
1181
|
-
changed: false,
|
|
1182
|
-
action: 'skipped_untracked_plugin_update',
|
|
1183
|
-
reason: `plugins.installs.claworld_source_${trackedInstall.source || 'unknown'}_is_not_host_updateable`,
|
|
1184
|
-
trackedInstall,
|
|
1185
|
-
plugin: before,
|
|
1186
|
-
};
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
await executeCommand({
|
|
1190
|
-
commandRunner,
|
|
1191
|
-
bin: openclawBin,
|
|
1192
|
-
args: ['plugins', 'update', 'claworld'],
|
|
1193
|
-
cwd,
|
|
1194
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
1195
|
-
dryRun,
|
|
1196
|
-
capture: false,
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
|
-
const after = await inspectClaworldPluginInstall({
|
|
1200
|
-
openclawBin,
|
|
1201
|
-
configPath,
|
|
1202
|
-
stateDir,
|
|
1203
|
-
commandRunner,
|
|
1204
|
-
cwd,
|
|
1205
|
-
env,
|
|
1206
|
-
dryRun,
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
if (!after.installed) {
|
|
1210
|
-
throw createInstallerError(
|
|
1211
|
-
'claworld_plugin_update_failed',
|
|
1212
|
-
'OpenClaw no longer reported the claworld plugin as installed after plugin update.',
|
|
1213
|
-
{ before, after, trackedInstall },
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return {
|
|
1218
|
-
changed: true,
|
|
1219
|
-
action: dryRun ? 'dry_run_tracked_plugin_update' : 'updated_tracked_plugin',
|
|
1220
|
-
trackedInstall,
|
|
1221
|
-
plugin: after,
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
async function fetchJson(fetchImpl, url, init = {}) {
|
|
1226
|
-
let response;
|
|
1227
|
-
try {
|
|
1228
|
-
response = await fetchImpl(url, init);
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
throw createInstallerError(
|
|
1231
|
-
'installer_fetch_failed',
|
|
1232
|
-
`Failed to reach ${url}: ${error?.message || String(error)}`,
|
|
1233
|
-
{ url, method: init?.method || 'GET' },
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
const text = await response.text();
|
|
1238
|
-
return {
|
|
1239
|
-
ok: response.ok,
|
|
1240
|
-
status: response.status,
|
|
1241
|
-
body: parseJsonDocument(text, null),
|
|
1242
|
-
text,
|
|
1243
|
-
};
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
export async function fetchInstallManifest({
|
|
1247
|
-
serverUrl = DEFAULT_CLAWORLD_SERVER_URL,
|
|
1248
|
-
apiKey = null,
|
|
1249
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1250
|
-
} = {}) {
|
|
1251
|
-
if (typeof fetchImpl !== 'function') {
|
|
1252
|
-
throw createInstallerError('missing_fetch', 'Install manifest fetch requires a fetch implementation.');
|
|
1253
|
-
}
|
|
1254
|
-
const url = `${normalizeRelayHttpBaseUrl(serverUrl)}/v1/meta/install`;
|
|
1255
|
-
const response = await fetchJson(fetchImpl, url, {
|
|
1256
|
-
method: 'GET',
|
|
1257
|
-
headers: buildFetchHeaders({ apiKey }),
|
|
1258
|
-
});
|
|
1259
|
-
if (!response.ok || !response.body) {
|
|
1260
|
-
throw createInstallerError(
|
|
1261
|
-
'install_manifest_unavailable',
|
|
1262
|
-
`Failed to read Claworld install contract from ${url}.`,
|
|
1263
|
-
{ url, response },
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
return response.body;
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
export async function activateInstall({
|
|
1270
|
-
serverUrl = DEFAULT_CLAWORLD_SERVER_URL,
|
|
1271
|
-
apiKey = null,
|
|
1272
|
-
appToken = null,
|
|
1273
|
-
displayName = null,
|
|
1274
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1275
|
-
} = {}) {
|
|
1276
|
-
if (typeof fetchImpl !== 'function') {
|
|
1277
|
-
throw createInstallerError('missing_fetch', 'Install activation requires a fetch implementation.');
|
|
1278
|
-
}
|
|
1279
|
-
const url = `${normalizeRelayHttpBaseUrl(serverUrl)}/v1/onboarding/activate`;
|
|
1280
|
-
const body = {};
|
|
1281
|
-
if (displayName) {
|
|
1282
|
-
body.displayName = displayName;
|
|
1283
|
-
}
|
|
1284
|
-
const response = await fetchJson(fetchImpl, url, {
|
|
1285
|
-
method: 'POST',
|
|
1286
|
-
headers: buildFetchHeaders({ apiKey, appToken, body: true }),
|
|
1287
|
-
body: JSON.stringify(body),
|
|
1288
|
-
});
|
|
1289
|
-
if (!response.ok || !response.body) {
|
|
1290
|
-
throw createInstallerError(
|
|
1291
|
-
'install_activation_failed',
|
|
1292
|
-
`Failed to activate Claworld install via ${url}.`,
|
|
1293
|
-
{ url, response },
|
|
1294
|
-
);
|
|
1295
|
-
}
|
|
1296
|
-
return response.body;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
export async function readGatewayStatus({
|
|
1300
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1301
|
-
configPath = null,
|
|
1302
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1303
|
-
commandRunner = defaultCommandRunner,
|
|
1304
|
-
cwd = process.cwd(),
|
|
1305
|
-
env = process.env,
|
|
1306
|
-
dryRun = false,
|
|
1307
|
-
} = {}) {
|
|
1308
|
-
const result = await executeCommand({
|
|
1309
|
-
commandRunner,
|
|
1310
|
-
bin: openclawBin,
|
|
1311
|
-
args: ['gateway', 'status', '--json', '--no-probe'],
|
|
1312
|
-
cwd,
|
|
1313
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
1314
|
-
dryRun,
|
|
1315
|
-
});
|
|
1316
|
-
const payload = parseCommandJsonOutput(result, null);
|
|
1317
|
-
if (!payload) {
|
|
1318
|
-
throw createInstallerError(
|
|
1319
|
-
'invalid_gateway_status',
|
|
1320
|
-
'OpenClaw gateway status did not produce JSON output.',
|
|
1321
|
-
{ result },
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
return payload;
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
export async function readChannelStatus({
|
|
1328
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1329
|
-
configPath = null,
|
|
1330
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1331
|
-
commandRunner = defaultCommandRunner,
|
|
1332
|
-
cwd = process.cwd(),
|
|
1333
|
-
env = process.env,
|
|
1334
|
-
dryRun = false,
|
|
1335
|
-
} = {}) {
|
|
1336
|
-
const result = await executeCommand({
|
|
1337
|
-
commandRunner,
|
|
1338
|
-
bin: openclawBin,
|
|
1339
|
-
args: ['channels', 'status', '--json'],
|
|
1340
|
-
cwd,
|
|
1341
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
1342
|
-
dryRun,
|
|
1343
|
-
});
|
|
1344
|
-
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
1345
|
-
const payload = parseCommandJsonOutput(result, null) || parseLegacyChannelStatus(output);
|
|
1346
|
-
if (!payload) {
|
|
1347
|
-
throw createInstallerError(
|
|
1348
|
-
'invalid_channel_status',
|
|
1349
|
-
'OpenClaw channel status did not produce JSON output.',
|
|
1350
|
-
{ result },
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
|
-
return payload;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
export async function readAgentBindings({
|
|
1357
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1358
|
-
configPath = null,
|
|
1359
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1360
|
-
commandRunner = defaultCommandRunner,
|
|
1361
|
-
cwd = process.cwd(),
|
|
1362
|
-
env = process.env,
|
|
1363
|
-
dryRun = false,
|
|
1364
|
-
} = {}) {
|
|
1365
|
-
return await executeCommand({
|
|
1366
|
-
commandRunner,
|
|
1367
|
-
bin: openclawBin,
|
|
1368
|
-
args: ['agents', 'bindings'],
|
|
1369
|
-
cwd,
|
|
1370
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
1371
|
-
dryRun,
|
|
1372
|
-
allowFailure: true,
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
export async function validateOpenclawConfig({
|
|
1377
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1378
|
-
configPath = null,
|
|
1379
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1380
|
-
commandRunner = defaultCommandRunner,
|
|
1381
|
-
cwd = process.cwd(),
|
|
1382
|
-
env = process.env,
|
|
1383
|
-
dryRun = false,
|
|
1384
|
-
} = {}) {
|
|
1385
|
-
await executeCommand({
|
|
1386
|
-
commandRunner,
|
|
1387
|
-
bin: openclawBin,
|
|
1388
|
-
args: ['config', 'validate'],
|
|
1389
|
-
cwd,
|
|
1390
|
-
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
1391
|
-
dryRun,
|
|
1392
|
-
capture: false,
|
|
1393
|
-
});
|
|
1394
|
-
return { ok: true };
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
export async function refreshOpenclawRuntime({
|
|
1398
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1399
|
-
configPath = null,
|
|
1400
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1401
|
-
commandRunner = defaultCommandRunner,
|
|
1402
|
-
cwd = process.cwd(),
|
|
1403
|
-
env = process.env,
|
|
1404
|
-
dryRun = false,
|
|
1405
|
-
} = {}) {
|
|
1406
|
-
const commandEnv = buildOpenclawCommandEnv({ configPath, stateDir, env });
|
|
1407
|
-
const restart = await executeCommand({
|
|
1408
|
-
commandRunner,
|
|
1409
|
-
bin: openclawBin,
|
|
1410
|
-
args: ['gateway', 'restart'],
|
|
1411
|
-
cwd,
|
|
1412
|
-
env: commandEnv,
|
|
1413
|
-
dryRun,
|
|
1414
|
-
capture: false,
|
|
1415
|
-
allowFailure: true,
|
|
1416
|
-
});
|
|
1417
|
-
if (restart.status === 0) {
|
|
1418
|
-
return { ok: true, action: 'restart', result: restart };
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
const start = await executeCommand({
|
|
1422
|
-
commandRunner,
|
|
1423
|
-
bin: openclawBin,
|
|
1424
|
-
args: ['gateway', 'start'],
|
|
1425
|
-
cwd,
|
|
1426
|
-
env: commandEnv,
|
|
1427
|
-
dryRun,
|
|
1428
|
-
capture: false,
|
|
1429
|
-
allowFailure: true,
|
|
1430
|
-
});
|
|
1431
|
-
if (start.status === 0) {
|
|
1432
|
-
return { ok: true, action: 'start', result: start };
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
throw createInstallerError(
|
|
1436
|
-
'openclaw_gateway_refresh_failed',
|
|
1437
|
-
'Failed to restart or start the OpenClaw gateway service.',
|
|
1438
|
-
{ restart, start },
|
|
1439
|
-
);
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
function wait(delayMs) {
|
|
1443
|
-
return new Promise((resolve) => {
|
|
1444
|
-
setTimeout(resolve, delayMs);
|
|
1445
|
-
});
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
export async function verifyClaworldInstall({
|
|
1449
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1450
|
-
configPath = null,
|
|
1451
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1452
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1453
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
1454
|
-
commandRunner = defaultCommandRunner,
|
|
1455
|
-
cwd = process.cwd(),
|
|
1456
|
-
env = process.env,
|
|
1457
|
-
dryRun = false,
|
|
1458
|
-
attempts = DEFAULT_VERIFICATION_ATTEMPTS,
|
|
1459
|
-
delayMs = DEFAULT_VERIFICATION_DELAY_MS,
|
|
1460
|
-
requireGatewayRunning = resolveRequireGatewayRunning(env),
|
|
1461
|
-
requireChannelToken = true,
|
|
1462
|
-
} = {}) {
|
|
1463
|
-
let lastResult = null;
|
|
1464
|
-
|
|
1465
|
-
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1466
|
-
const [plugin, gatewayStatus, channelStatus, bindings] = await Promise.all([
|
|
1467
|
-
inspectClaworldPluginInstall({
|
|
1468
|
-
openclawBin,
|
|
1469
|
-
configPath,
|
|
1470
|
-
stateDir,
|
|
1471
|
-
commandRunner,
|
|
1472
|
-
cwd,
|
|
1473
|
-
env,
|
|
1474
|
-
dryRun,
|
|
1475
|
-
}),
|
|
1476
|
-
readGatewayStatus({
|
|
1477
|
-
openclawBin,
|
|
1478
|
-
configPath,
|
|
1479
|
-
stateDir,
|
|
1480
|
-
commandRunner,
|
|
1481
|
-
cwd,
|
|
1482
|
-
env,
|
|
1483
|
-
dryRun,
|
|
1484
|
-
}),
|
|
1485
|
-
readChannelStatus({
|
|
1486
|
-
openclawBin,
|
|
1487
|
-
configPath,
|
|
1488
|
-
stateDir,
|
|
1489
|
-
commandRunner,
|
|
1490
|
-
cwd,
|
|
1491
|
-
env,
|
|
1492
|
-
dryRun,
|
|
1493
|
-
}),
|
|
1494
|
-
readAgentBindings({
|
|
1495
|
-
openclawBin,
|
|
1496
|
-
configPath,
|
|
1497
|
-
stateDir,
|
|
1498
|
-
commandRunner,
|
|
1499
|
-
cwd,
|
|
1500
|
-
env,
|
|
1501
|
-
dryRun,
|
|
1502
|
-
}),
|
|
1503
|
-
]);
|
|
1504
|
-
|
|
1505
|
-
const channelAccount = readClaworldAccountStatus(channelStatus, accountId);
|
|
1506
|
-
const gatewayRunning = gatewayStatus?.service?.runtime?.status === 'running';
|
|
1507
|
-
const bindingLine = parseBindingLine(`${bindings.stdout || ''}${bindings.stderr || ''}`, {
|
|
1508
|
-
agentId,
|
|
1509
|
-
accountId,
|
|
1510
|
-
});
|
|
1511
|
-
const channelReady = Boolean(
|
|
1512
|
-
channelAccount
|
|
1513
|
-
&& channelAccount.configured === true
|
|
1514
|
-
&& channelAccount.enabled !== false
|
|
1515
|
-
&& (
|
|
1516
|
-
requireChannelToken !== true
|
|
1517
|
-
|| channelAccount.tokenStatus === 'available'
|
|
1518
|
-
)
|
|
1519
|
-
);
|
|
1520
|
-
const pluginReady = Boolean(plugin.installed);
|
|
1521
|
-
const bindingReady = Boolean(bindingLine);
|
|
1522
|
-
|
|
1523
|
-
lastResult = {
|
|
1524
|
-
ok: pluginReady && channelReady && bindingReady && (!requireGatewayRunning || gatewayRunning),
|
|
1525
|
-
attempt,
|
|
1526
|
-
plugin,
|
|
1527
|
-
gatewayStatus,
|
|
1528
|
-
channelStatus,
|
|
1529
|
-
channelAccount,
|
|
1530
|
-
bindingLine,
|
|
1531
|
-
gatewayRunning,
|
|
1532
|
-
channelReady,
|
|
1533
|
-
bindingReady,
|
|
1534
|
-
requireGatewayRunning,
|
|
1535
|
-
requireChannelToken,
|
|
1536
|
-
};
|
|
1537
|
-
|
|
1538
|
-
if (lastResult.ok) {
|
|
1539
|
-
return lastResult;
|
|
1540
|
-
}
|
|
1541
|
-
if (attempt < attempts) {
|
|
1542
|
-
await wait(delayMs);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
return lastResult || {
|
|
1547
|
-
ok: false,
|
|
1548
|
-
attempt: 0,
|
|
1549
|
-
};
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
async function reconcileManagedClaworldRuntime({
|
|
1553
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1554
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1555
|
-
installerStatePath = null,
|
|
1556
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1557
|
-
currentConfigState = { existed: false, config: {} },
|
|
1558
|
-
currentInstallerState = {},
|
|
1559
|
-
currentConfig = {},
|
|
1560
|
-
host = null,
|
|
1561
|
-
serverUrl = null,
|
|
1562
|
-
apiKey = null,
|
|
1563
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1564
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
1565
|
-
workspace = null,
|
|
1566
|
-
displayName = null,
|
|
1567
|
-
toolProfile = null,
|
|
1568
|
-
approvalMode = null,
|
|
1569
|
-
sessionDmScope = null,
|
|
1570
|
-
repoRoot = null,
|
|
1571
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1572
|
-
commandRunner = defaultCommandRunner,
|
|
1573
|
-
cwd = process.cwd(),
|
|
1574
|
-
env = process.env,
|
|
1575
|
-
dryRun = false,
|
|
1576
|
-
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1577
|
-
} = {}) {
|
|
1578
|
-
const existingInstall = inspectManagedClaworldInstall({
|
|
1579
|
-
cfg: currentConfig,
|
|
1580
|
-
accountId,
|
|
1581
|
-
installerState: currentInstallerState,
|
|
1582
|
-
overrides: {
|
|
1583
|
-
agentId,
|
|
1584
|
-
workspace,
|
|
1585
|
-
displayName,
|
|
1586
|
-
toolProfile,
|
|
1587
|
-
approvalMode,
|
|
1588
|
-
sessionDmScope,
|
|
1589
|
-
...(repoRoot ? { repoRoot } : {}),
|
|
1590
|
-
},
|
|
1591
|
-
});
|
|
1592
|
-
const effectiveServerUrl = normalizeText(
|
|
1593
|
-
serverUrl,
|
|
1594
|
-
normalizeText(existingInstall.accountStatus?.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
|
|
1595
|
-
);
|
|
1596
|
-
|
|
1597
|
-
const installAccountId = normalizeText(
|
|
1598
|
-
accountId,
|
|
1599
|
-
DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1600
|
-
);
|
|
1601
|
-
const installAgentId = normalizeText(
|
|
1602
|
-
agentId,
|
|
1603
|
-
installAccountId,
|
|
1604
|
-
);
|
|
1605
|
-
|
|
1606
|
-
const preflight = inspectManagedClaworldInstall({
|
|
1607
|
-
cfg: currentConfig,
|
|
1608
|
-
accountId: installAccountId,
|
|
1609
|
-
installerState: currentInstallerState,
|
|
1610
|
-
overrides: {
|
|
1611
|
-
agentId: installAgentId,
|
|
1612
|
-
workspace,
|
|
1613
|
-
serverUrl: effectiveServerUrl,
|
|
1614
|
-
displayName,
|
|
1615
|
-
toolProfile,
|
|
1616
|
-
approvalMode,
|
|
1617
|
-
sessionDmScope,
|
|
1618
|
-
...(repoRoot ? { repoRoot } : {}),
|
|
1619
|
-
},
|
|
1620
|
-
});
|
|
1621
|
-
const desiredDisplayName = normalizeText(
|
|
1622
|
-
displayName,
|
|
1623
|
-
normalizeText(preflight.managedOptions.displayName, null),
|
|
1624
|
-
);
|
|
1625
|
-
|
|
1626
|
-
const managedOptions = resolveClaworldManagedRuntimeOptions({
|
|
1627
|
-
cfg: currentConfig,
|
|
1628
|
-
installerState: currentInstallerState,
|
|
1629
|
-
accountId: installAccountId,
|
|
1630
|
-
overrides: {
|
|
1631
|
-
agentId: installAgentId,
|
|
1632
|
-
workspace,
|
|
1633
|
-
serverUrl: effectiveServerUrl,
|
|
1634
|
-
apiKey,
|
|
1635
|
-
displayName: desiredDisplayName,
|
|
1636
|
-
toolProfile,
|
|
1637
|
-
approvalMode,
|
|
1638
|
-
sessionDmScope,
|
|
1639
|
-
replaceManagedRuntime: true,
|
|
1640
|
-
installPlugin: false,
|
|
1641
|
-
pluginInstallMode: 'skip',
|
|
1642
|
-
...(repoRoot ? { repoRoot } : {}),
|
|
1643
|
-
},
|
|
1644
|
-
});
|
|
1645
|
-
|
|
1646
|
-
const transformed = applyClaworldBootstrapConfig(currentConfig, managedOptions);
|
|
1647
|
-
const configChanged = JSON.stringify(currentConfigState.config) !== JSON.stringify(transformed.config);
|
|
1648
|
-
const backupPath = configChanged
|
|
1649
|
-
? await backupConfigIfPresent(configPath, currentConfigState.existed, dryRun)
|
|
1650
|
-
: null;
|
|
1651
|
-
if (configChanged) {
|
|
1652
|
-
await writeConfig(configPath, transformed.config, dryRun);
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
const workspaceActions = managedOptions.manageWorkspace
|
|
1656
|
-
? await seedManagedWorkspace(managedOptions, dryRun)
|
|
1657
|
-
: [];
|
|
1658
|
-
await validateOpenclawConfig({
|
|
1659
|
-
openclawBin,
|
|
1660
|
-
configPath,
|
|
1661
|
-
stateDir,
|
|
1662
|
-
commandRunner,
|
|
1663
|
-
cwd,
|
|
1664
|
-
env,
|
|
1665
|
-
dryRun,
|
|
1666
|
-
});
|
|
1667
|
-
const runtimeRefresh = await refreshOpenclawRuntime({
|
|
1668
|
-
openclawBin,
|
|
1669
|
-
configPath,
|
|
1670
|
-
stateDir,
|
|
1671
|
-
commandRunner,
|
|
1672
|
-
cwd,
|
|
1673
|
-
env,
|
|
1674
|
-
dryRun,
|
|
1675
|
-
});
|
|
1676
|
-
const verification = await verifyClaworldInstall({
|
|
1677
|
-
openclawBin,
|
|
1678
|
-
configPath,
|
|
1679
|
-
stateDir,
|
|
1680
|
-
accountId: installAccountId,
|
|
1681
|
-
agentId: installAgentId,
|
|
1682
|
-
commandRunner,
|
|
1683
|
-
cwd,
|
|
1684
|
-
env,
|
|
1685
|
-
dryRun,
|
|
1686
|
-
delayMs: Math.min(timeoutMs, DEFAULT_VERIFICATION_DELAY_MS),
|
|
1687
|
-
requireChannelToken: Boolean(managedOptions.appToken),
|
|
1688
|
-
});
|
|
1689
|
-
if (!verification.ok) {
|
|
1690
|
-
throw createInstallerError(
|
|
1691
|
-
'claworld_install_verification_failed',
|
|
1692
|
-
'Claworld install verification did not confirm the managed channel runtime shape.',
|
|
1693
|
-
{ verification },
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
let installerStateChanged = false;
|
|
1698
|
-
if (installerStatePath && installAccountId) {
|
|
1699
|
-
const nextInstallerState = JSON.parse(JSON.stringify(ensureObject(currentInstallerState)));
|
|
1700
|
-
setClaworldManagedRuntimeBackupState(nextInstallerState, installAccountId, null);
|
|
1701
|
-
installerStateChanged = JSON.stringify(currentInstallerState) !== JSON.stringify(nextInstallerState);
|
|
1702
|
-
if (installerStateChanged) {
|
|
1703
|
-
await writeInstallerState(installerStatePath, nextInstallerState, dryRun);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
return {
|
|
1708
|
-
backupPath,
|
|
1709
|
-
existingInstall,
|
|
1710
|
-
effectiveServerUrl,
|
|
1711
|
-
preflight,
|
|
1712
|
-
activationStatus: managedOptions.appToken ? 'ready' : 'pending',
|
|
1713
|
-
managedOptions,
|
|
1714
|
-
transformed,
|
|
1715
|
-
configChanged,
|
|
1716
|
-
workspaceActions,
|
|
1717
|
-
runtimeRefresh,
|
|
1718
|
-
verification,
|
|
1719
|
-
installerStatePath,
|
|
1720
|
-
installerStateChanged,
|
|
1721
|
-
};
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
export async function runClaworldInstallerInstall({
|
|
1725
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1726
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1727
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1728
|
-
serverUrl = null,
|
|
1729
|
-
apiKey = null,
|
|
1730
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1731
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
1732
|
-
workspace = null,
|
|
1733
|
-
displayName = null,
|
|
1734
|
-
toolProfile = null,
|
|
1735
|
-
approvalMode = null,
|
|
1736
|
-
sessionDmScope = null,
|
|
1737
|
-
repoRoot = null,
|
|
1738
|
-
pluginInstallMode = 'npm',
|
|
1739
|
-
pluginInstallSource = CLAWORLD_INSTALLER_DEFAULT_PLUGIN_SOURCE,
|
|
1740
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1741
|
-
commandRunner = defaultCommandRunner,
|
|
1742
|
-
cwd = process.cwd(),
|
|
1743
|
-
env = process.env,
|
|
1744
|
-
dryRun = false,
|
|
1745
|
-
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1746
|
-
} = {}) {
|
|
1747
|
-
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
1748
|
-
const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
|
|
1749
|
-
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
1750
|
-
const commandEnv = buildOpenclawCommandEnv({
|
|
1751
|
-
configPath: resolvedConfigPath,
|
|
1752
|
-
stateDir: resolvedStateDir,
|
|
1753
|
-
env,
|
|
1754
|
-
});
|
|
1755
|
-
const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1756
|
-
const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
|
|
1757
|
-
const currentConfigBeforePluginInstall = currentConfigState.config;
|
|
1758
|
-
const host = await detectOpenclawHost({
|
|
1759
|
-
openclawBin,
|
|
1760
|
-
commandRunner,
|
|
1761
|
-
cwd,
|
|
1762
|
-
env: commandEnv,
|
|
1763
|
-
dryRun,
|
|
1764
|
-
});
|
|
1765
|
-
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
1766
|
-
throw createInstallerError(
|
|
1767
|
-
'openclaw_version_too_old',
|
|
1768
|
-
`OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
1769
|
-
{ hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
const localPluginInstall = await resolveLocalPluginInstallTarget({
|
|
1774
|
-
installMode: pluginInstallMode,
|
|
1775
|
-
installSource: pluginInstallSource,
|
|
1776
|
-
repoRoot,
|
|
1777
|
-
commandRunner,
|
|
1778
|
-
cwd,
|
|
1779
|
-
env,
|
|
1780
|
-
dryRun,
|
|
1781
|
-
});
|
|
1782
|
-
|
|
1783
|
-
const plugin = await ensureClaworldPluginInstalled({
|
|
1784
|
-
openclawBin,
|
|
1785
|
-
configPath: resolvedConfigPath,
|
|
1786
|
-
stateDir: resolvedStateDir,
|
|
1787
|
-
config: currentConfigBeforePluginInstall,
|
|
1788
|
-
commandRunner,
|
|
1789
|
-
cwd,
|
|
1790
|
-
env,
|
|
1791
|
-
dryRun,
|
|
1792
|
-
installMode: pluginInstallMode,
|
|
1793
|
-
installSource: localPluginInstall.installSource,
|
|
1794
|
-
refresh: false,
|
|
1795
|
-
});
|
|
1796
|
-
const currentConfig = mergePluginMetadata(
|
|
1797
|
-
currentConfigBeforePluginInstall,
|
|
1798
|
-
plugin.pluginConfig,
|
|
1799
|
-
);
|
|
1800
|
-
const lifecycle = await reconcileManagedClaworldRuntime({
|
|
1801
|
-
openclawBin,
|
|
1802
|
-
configPath: resolvedConfigPath,
|
|
1803
|
-
installerStatePath,
|
|
1804
|
-
stateDir: resolvedStateDir,
|
|
1805
|
-
currentConfigState,
|
|
1806
|
-
currentInstallerState,
|
|
1807
|
-
currentConfig,
|
|
1808
|
-
host,
|
|
1809
|
-
serverUrl,
|
|
1810
|
-
apiKey,
|
|
1811
|
-
accountId,
|
|
1812
|
-
agentId,
|
|
1813
|
-
workspace,
|
|
1814
|
-
displayName,
|
|
1815
|
-
toolProfile,
|
|
1816
|
-
approvalMode,
|
|
1817
|
-
sessionDmScope,
|
|
1818
|
-
repoRoot: plugin.managedRepoRoot || localPluginInstall.managedRepoRoot,
|
|
1819
|
-
fetchImpl,
|
|
1820
|
-
commandRunner,
|
|
1821
|
-
cwd,
|
|
1822
|
-
env,
|
|
1823
|
-
dryRun,
|
|
1824
|
-
timeoutMs,
|
|
1825
|
-
});
|
|
1826
|
-
|
|
1827
|
-
return {
|
|
1828
|
-
ok: true,
|
|
1829
|
-
command: CLAWORLD_INSTALLER_COMMAND,
|
|
1830
|
-
configPath: resolvedConfigPath,
|
|
1831
|
-
installerStatePath,
|
|
1832
|
-
stateDir: resolvedStateDir,
|
|
1833
|
-
host,
|
|
1834
|
-
plugin,
|
|
1835
|
-
...lifecycle,
|
|
1836
|
-
};
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
export async function runClaworldInstallerUpdate({
|
|
1840
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1841
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1842
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1843
|
-
serverUrl = null,
|
|
1844
|
-
apiKey = null,
|
|
1845
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1846
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
1847
|
-
workspace = null,
|
|
1848
|
-
displayName = null,
|
|
1849
|
-
pluginInstallSource = null,
|
|
1850
|
-
toolProfile = null,
|
|
1851
|
-
approvalMode = null,
|
|
1852
|
-
sessionDmScope = null,
|
|
1853
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1854
|
-
commandRunner = defaultCommandRunner,
|
|
1855
|
-
cwd = process.cwd(),
|
|
1856
|
-
env = process.env,
|
|
1857
|
-
dryRun = false,
|
|
1858
|
-
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1859
|
-
} = {}) {
|
|
1860
|
-
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
1861
|
-
const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
|
|
1862
|
-
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
1863
|
-
const commandEnv = buildOpenclawCommandEnv({
|
|
1864
|
-
configPath: resolvedConfigPath,
|
|
1865
|
-
stateDir: resolvedStateDir,
|
|
1866
|
-
env,
|
|
1867
|
-
});
|
|
1868
|
-
const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1869
|
-
const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
|
|
1870
|
-
const host = await detectOpenclawHost({
|
|
1871
|
-
openclawBin,
|
|
1872
|
-
commandRunner,
|
|
1873
|
-
cwd,
|
|
1874
|
-
env: commandEnv,
|
|
1875
|
-
dryRun,
|
|
1876
|
-
});
|
|
1877
|
-
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
1878
|
-
throw createInstallerError(
|
|
1879
|
-
'openclaw_version_too_old',
|
|
1880
|
-
`OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
1881
|
-
{ hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
1882
|
-
);
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
const plugin = await updateTrackedClaworldPluginInstall({
|
|
1886
|
-
openclawBin,
|
|
1887
|
-
configPath: resolvedConfigPath,
|
|
1888
|
-
stateDir: resolvedStateDir,
|
|
1889
|
-
config: currentConfigState.config,
|
|
1890
|
-
installSource: pluginInstallSource,
|
|
1891
|
-
commandRunner,
|
|
1892
|
-
cwd,
|
|
1893
|
-
env,
|
|
1894
|
-
dryRun,
|
|
1895
|
-
});
|
|
1896
|
-
|
|
1897
|
-
const refreshedConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1898
|
-
const currentConfig = mergePluginMetadata(refreshedConfigState.config, plugin.pluginConfig || null);
|
|
1899
|
-
const lifecycle = await reconcileManagedClaworldRuntime({
|
|
1900
|
-
openclawBin,
|
|
1901
|
-
configPath: resolvedConfigPath,
|
|
1902
|
-
installerStatePath,
|
|
1903
|
-
stateDir: resolvedStateDir,
|
|
1904
|
-
currentConfigState: refreshedConfigState,
|
|
1905
|
-
currentInstallerState,
|
|
1906
|
-
currentConfig,
|
|
1907
|
-
host,
|
|
1908
|
-
serverUrl,
|
|
1909
|
-
apiKey,
|
|
1910
|
-
accountId,
|
|
1911
|
-
agentId,
|
|
1912
|
-
workspace,
|
|
1913
|
-
displayName,
|
|
1914
|
-
toolProfile,
|
|
1915
|
-
approvalMode,
|
|
1916
|
-
sessionDmScope,
|
|
1917
|
-
repoRoot: plugin.managedRepoRoot || null,
|
|
1918
|
-
fetchImpl,
|
|
1919
|
-
commandRunner,
|
|
1920
|
-
cwd,
|
|
1921
|
-
env,
|
|
1922
|
-
dryRun,
|
|
1923
|
-
timeoutMs,
|
|
1924
|
-
});
|
|
1925
|
-
|
|
1926
|
-
return {
|
|
1927
|
-
ok: true,
|
|
1928
|
-
command: CLAWORLD_UPDATE_COMMAND,
|
|
1929
|
-
configPath: resolvedConfigPath,
|
|
1930
|
-
installerStatePath,
|
|
1931
|
-
stateDir: resolvedStateDir,
|
|
1932
|
-
host,
|
|
1933
|
-
plugin,
|
|
1934
|
-
...lifecycle,
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
|
|
1938
|
-
export async function runClaworldInstallerUninstall({
|
|
1939
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1940
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1941
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1942
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1943
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
1944
|
-
commandRunner = defaultCommandRunner,
|
|
1945
|
-
cwd = process.cwd(),
|
|
1946
|
-
env = process.env,
|
|
1947
|
-
dryRun = false,
|
|
1948
|
-
} = {}) {
|
|
1949
|
-
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
1950
|
-
const installerStatePath = resolveClaworldInstallerStatePath(resolvedConfigPath);
|
|
1951
|
-
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
1952
|
-
const commandEnv = buildOpenclawCommandEnv({
|
|
1953
|
-
configPath: resolvedConfigPath,
|
|
1954
|
-
stateDir: resolvedStateDir,
|
|
1955
|
-
env,
|
|
1956
|
-
});
|
|
1957
|
-
const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1958
|
-
const currentInstallerState = (await loadInstallerStateFromDisk(installerStatePath)).state;
|
|
1959
|
-
const currentConfig = currentConfigState.config;
|
|
1960
|
-
const trackedInstall = inspectTrackedClaworldPluginInstall(currentConfig);
|
|
1961
|
-
const host = await detectOpenclawHost({
|
|
1962
|
-
openclawBin,
|
|
1963
|
-
commandRunner,
|
|
1964
|
-
cwd,
|
|
1965
|
-
env: commandEnv,
|
|
1966
|
-
dryRun,
|
|
1967
|
-
});
|
|
1968
|
-
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
1969
|
-
throw createInstallerError(
|
|
1970
|
-
'openclaw_version_too_old',
|
|
1971
|
-
`OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
1972
|
-
{ hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
1973
|
-
);
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
const pluginBefore = await inspectClaworldPluginInstall({
|
|
1977
|
-
openclawBin,
|
|
1978
|
-
configPath: resolvedConfigPath,
|
|
1979
|
-
stateDir: resolvedStateDir,
|
|
1980
|
-
commandRunner,
|
|
1981
|
-
cwd,
|
|
1982
|
-
env,
|
|
1983
|
-
dryRun,
|
|
1984
|
-
});
|
|
1985
|
-
const transformed = stripClaworldManagedRuntimeConfig(currentConfig, {
|
|
1986
|
-
accountId,
|
|
1987
|
-
agentId,
|
|
1988
|
-
preserveBackup: true,
|
|
1989
|
-
});
|
|
1990
|
-
const configChanged = JSON.stringify(currentConfig) !== JSON.stringify(transformed.config);
|
|
1991
|
-
const backupPath = configChanged
|
|
1992
|
-
? await backupConfigIfPresent(resolvedConfigPath, currentConfigState.existed, dryRun)
|
|
1993
|
-
: null;
|
|
1994
|
-
if (configChanged) {
|
|
1995
|
-
await writeConfig(resolvedConfigPath, transformed.config, dryRun);
|
|
1996
|
-
}
|
|
1997
|
-
const nextInstallerState = JSON.parse(JSON.stringify(ensureObject(currentInstallerState)));
|
|
1998
|
-
setClaworldManagedRuntimeBackupState(nextInstallerState, accountId, transformed.backup);
|
|
1999
|
-
const installerStateChanged = JSON.stringify(currentInstallerState) !== JSON.stringify(nextInstallerState);
|
|
2000
|
-
if (installerStateChanged) {
|
|
2001
|
-
await writeInstallerState(installerStatePath, nextInstallerState, dryRun);
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
await validateOpenclawConfig({
|
|
2005
|
-
openclawBin,
|
|
2006
|
-
configPath: resolvedConfigPath,
|
|
2007
|
-
stateDir: resolvedStateDir,
|
|
2008
|
-
commandRunner,
|
|
2009
|
-
cwd,
|
|
2010
|
-
env,
|
|
2011
|
-
dryRun,
|
|
2012
|
-
});
|
|
2013
|
-
|
|
2014
|
-
const uninstallBootstrap = pluginBefore.installed
|
|
2015
|
-
? await preparePluginBootstrapConfig({
|
|
2016
|
-
configPath: resolvedConfigPath,
|
|
2017
|
-
config: currentConfig,
|
|
2018
|
-
dryRun,
|
|
2019
|
-
})
|
|
2020
|
-
: null;
|
|
2021
|
-
|
|
2022
|
-
let pluginAction = 'plugin_already_absent';
|
|
2023
|
-
try {
|
|
2024
|
-
if (trackedInstall.source === 'installer_npm') {
|
|
2025
|
-
const localInstallPath = normalizeText(trackedInstall.installPath, null) || normalizeText(trackedInstall.sourcePath, null);
|
|
2026
|
-
if (!localInstallPath) {
|
|
2027
|
-
throw createInstallerError(
|
|
2028
|
-
'claworld_installer_managed_source_missing',
|
|
2029
|
-
'Installer-managed Claworld source is missing its local install path.',
|
|
2030
|
-
{ trackedInstall },
|
|
2031
|
-
);
|
|
2032
|
-
}
|
|
2033
|
-
if (!dryRun) {
|
|
2034
|
-
await fs.rm(localInstallPath, { recursive: true, force: true });
|
|
2035
|
-
}
|
|
2036
|
-
pluginAction = 'removed_local_plugin_source';
|
|
2037
|
-
} else if (pluginBefore.installed) {
|
|
2038
|
-
await executeCommand({
|
|
2039
|
-
commandRunner,
|
|
2040
|
-
bin: openclawBin,
|
|
2041
|
-
args: ['plugins', 'uninstall', 'claworld', '--force'],
|
|
2042
|
-
cwd,
|
|
2043
|
-
env: buildOpenclawCommandEnv({
|
|
2044
|
-
configPath: uninstallBootstrap?.configPath || resolvedConfigPath,
|
|
2045
|
-
stateDir: resolvedStateDir,
|
|
2046
|
-
env,
|
|
2047
|
-
}),
|
|
2048
|
-
dryRun,
|
|
2049
|
-
capture: false,
|
|
2050
|
-
});
|
|
2051
|
-
pluginAction = 'uninstalled_plugin';
|
|
2052
|
-
}
|
|
2053
|
-
} finally {
|
|
2054
|
-
if (uninstallBootstrap) {
|
|
2055
|
-
await uninstallBootstrap.cleanup();
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
const runtimeRefresh = await refreshOpenclawRuntime({
|
|
2060
|
-
openclawBin,
|
|
2061
|
-
configPath: resolvedConfigPath,
|
|
2062
|
-
stateDir: resolvedStateDir,
|
|
2063
|
-
commandRunner,
|
|
2064
|
-
cwd,
|
|
2065
|
-
env,
|
|
2066
|
-
dryRun,
|
|
2067
|
-
});
|
|
2068
|
-
const pluginAfter = await inspectClaworldPluginInstall({
|
|
2069
|
-
openclawBin,
|
|
2070
|
-
configPath: resolvedConfigPath,
|
|
2071
|
-
stateDir: resolvedStateDir,
|
|
2072
|
-
commandRunner,
|
|
2073
|
-
cwd,
|
|
2074
|
-
env,
|
|
2075
|
-
dryRun,
|
|
2076
|
-
});
|
|
2077
|
-
if ((pluginBefore.installed || trackedInstall.source === 'installer_npm') && pluginAfter.installed) {
|
|
2078
|
-
throw createInstallerError(
|
|
2079
|
-
'claworld_plugin_uninstall_failed',
|
|
2080
|
-
'OpenClaw still reports the claworld plugin as installed after uninstall.',
|
|
2081
|
-
{ before: pluginBefore, after: pluginAfter },
|
|
2082
|
-
);
|
|
2083
|
-
}
|
|
2084
|
-
|
|
2085
|
-
const gatewayStatus = await readGatewayStatus({
|
|
2086
|
-
openclawBin,
|
|
2087
|
-
configPath: resolvedConfigPath,
|
|
2088
|
-
stateDir: resolvedStateDir,
|
|
2089
|
-
commandRunner,
|
|
2090
|
-
cwd,
|
|
2091
|
-
env,
|
|
2092
|
-
dryRun,
|
|
2093
|
-
});
|
|
2094
|
-
|
|
2095
|
-
return {
|
|
2096
|
-
ok: true,
|
|
2097
|
-
command: CLAWORLD_UNINSTALL_COMMAND,
|
|
2098
|
-
configPath: resolvedConfigPath,
|
|
2099
|
-
installerStatePath,
|
|
2100
|
-
stateDir: resolvedStateDir,
|
|
2101
|
-
host,
|
|
2102
|
-
backupPath,
|
|
2103
|
-
transformed,
|
|
2104
|
-
configChanged,
|
|
2105
|
-
installerStateChanged,
|
|
2106
|
-
plugin: {
|
|
2107
|
-
action: pluginAction,
|
|
2108
|
-
before: pluginBefore,
|
|
2109
|
-
after: pluginAfter,
|
|
2110
|
-
},
|
|
2111
|
-
runtimeRefresh,
|
|
2112
|
-
gatewayStatus,
|
|
2113
|
-
};
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
export {
|
|
2117
|
-
compareVersionParts,
|
|
2118
|
-
defaultCommandRunner,
|
|
2119
|
-
findAgentEntry,
|
|
2120
|
-
parseCommandVersion,
|
|
2121
|
-
readClaworldAccountStatus,
|
|
2122
|
-
};
|