@xfxstudio/claworld 0.1.0
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 +60 -0
- package/bin/claworld.mjs +9 -0
- package/index.js +51 -0
- package/openclaw.plugin.json +470 -0
- package/package.json +76 -0
- package/setup-entry.js +6 -0
- package/src/lib/accepted-chat-kickoff.js +192 -0
- package/src/lib/agent-address.js +46 -0
- package/src/lib/agent-profile.js +69 -0
- package/src/lib/http-auth.js +151 -0
- package/src/lib/policy.js +118 -0
- package/src/lib/runtime-errors.js +149 -0
- package/src/lib/runtime-guidance.js +458 -0
- package/src/openclaw/index.js +53 -0
- package/src/openclaw/installer/cli.js +349 -0
- package/src/openclaw/installer/constants.js +6 -0
- package/src/openclaw/installer/core.js +1548 -0
- package/src/openclaw/installer/doctor.js +690 -0
- package/src/openclaw/installer/workspace-contract.js +403 -0
- package/src/openclaw/plugin/account-identity.js +66 -0
- package/src/openclaw/plugin/claworld-channel-plugin.js +3118 -0
- package/src/openclaw/plugin/config-schema.js +464 -0
- package/src/openclaw/plugin/lifecycle.js +114 -0
- package/src/openclaw/plugin/managed-config.js +648 -0
- package/src/openclaw/plugin/onboarding.js +291 -0
- package/src/openclaw/plugin/register.js +961 -0
- package/src/openclaw/plugin/relay-client.js +783 -0
- package/src/openclaw/plugin/runtime.js +12 -0
- package/src/openclaw/protocol/relay-event-protocol.js +31 -0
- package/src/openclaw/runtime/canonical-result-builder.js +116 -0
- package/src/openclaw/runtime/demo-session-bootstrap.js +37 -0
- package/src/openclaw/runtime/feedback-helper.js +145 -0
- package/src/openclaw/runtime/inbound-session-router.js +36 -0
- package/src/openclaw/runtime/outbound-session-bridge.js +17 -0
- package/src/openclaw/runtime/product-shell-helper.js +1712 -0
- package/src/openclaw/runtime/runtime-path.js +19 -0
- package/src/openclaw/runtime/system-message-orchestrator.js +1 -0
- package/src/openclaw/runtime/tool-contracts.js +714 -0
- package/src/openclaw/runtime/tool-inventory.js +92 -0
- package/src/openclaw/runtime/world-moderation-helper.js +415 -0
- package/src/openclaw/runtime/world-session-startup.js +1 -0
- package/src/product-shell/catalog/default-world-catalog.js +296 -0
- package/src/product-shell/contracts/candidate-feed.js +330 -0
- package/src/product-shell/contracts/chat-request-approval-policy.js +98 -0
- package/src/product-shell/contracts/world-manifest.js +435 -0
- package/src/product-shell/contracts/world-orchestration.js +1024 -0
- package/src/product-shell/feedback/feedback-contract.js +13 -0
- package/src/product-shell/feedback/feedback-routes.js +98 -0
- package/src/product-shell/feedback/feedback-service.js +254 -0
- package/src/product-shell/index.js +163 -0
- package/src/product-shell/matching/matchmaking-service.js +340 -0
- package/src/product-shell/membership/membership-service.js +277 -0
- package/src/product-shell/onboarding/onboarding-routes.js +37 -0
- package/src/product-shell/onboarding/onboarding-service.js +230 -0
- package/src/product-shell/orchestration/session-orchestrator.js +38 -0
- package/src/product-shell/results/result-service.js +15 -0
- package/src/product-shell/search/search-service.js +359 -0
- package/src/product-shell/social/chat-request-approval-policy.js +332 -0
- package/src/product-shell/social/chat-request-routes.js +108 -0
- package/src/product-shell/social/chat-request-service.js +632 -0
- package/src/product-shell/social/friend-routes.js +82 -0
- package/src/product-shell/social/friend-service.js +560 -0
- package/src/product-shell/social/social-routes.js +21 -0
- package/src/product-shell/social/social-service.js +140 -0
- package/src/product-shell/worlds/world-admin-service.js +705 -0
- package/src/product-shell/worlds/world-authorization.js +135 -0
- package/src/product-shell/worlds/world-broadcast-service.js +299 -0
- package/src/product-shell/worlds/world-routes.js +410 -0
- package/src/product-shell/worlds/world-service.js +89 -0
|
@@ -0,0 +1,1548 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
import vm from 'vm';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
8
|
+
DEFAULT_CLAWORLD_SERVER_URL,
|
|
9
|
+
applyClaworldManagedRuntimeConfig,
|
|
10
|
+
ensureObject,
|
|
11
|
+
expandUserPath,
|
|
12
|
+
normalizeText,
|
|
13
|
+
resolveClaworldManagedRuntimeOptions,
|
|
14
|
+
resolveToolNames,
|
|
15
|
+
} from '../plugin/managed-config.js';
|
|
16
|
+
import {
|
|
17
|
+
defaultClaworldAccountId,
|
|
18
|
+
inspectClaworldChannelAccount,
|
|
19
|
+
listClaworldAccountIds,
|
|
20
|
+
} from '../plugin/config-schema.js';
|
|
21
|
+
import {
|
|
22
|
+
CLAWORLD_INSTALLER_COMMAND,
|
|
23
|
+
CLAWORLD_INSTALLER_PACKAGE_NAME,
|
|
24
|
+
CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
25
|
+
CLAWORLD_UPDATE_COMMAND,
|
|
26
|
+
} from './constants.js';
|
|
27
|
+
import { seedManagedWorkspaceContract } from './workspace-contract.js';
|
|
28
|
+
|
|
29
|
+
export const DEFAULT_OPENCLAW_BIN = 'openclaw';
|
|
30
|
+
export const DEFAULT_OPENCLAW_CONFIG_PATH = '~/.openclaw/openclaw.json';
|
|
31
|
+
export const DEFAULT_OPENCLAW_STATE_DIR = null;
|
|
32
|
+
export const DEFAULT_INSTALL_TIMEOUT_MS = 15_000;
|
|
33
|
+
export const DEFAULT_VERIFICATION_ATTEMPTS = 6;
|
|
34
|
+
export const DEFAULT_VERIFICATION_DELAY_MS = 1_000;
|
|
35
|
+
export const seedManagedWorkspace = seedManagedWorkspaceContract;
|
|
36
|
+
const TRACKED_PLUGIN_UPDATEABLE_SOURCES = new Set(['npm', 'marketplace']);
|
|
37
|
+
|
|
38
|
+
function listBindings(config = {}) {
|
|
39
|
+
return Array.isArray(config.bindings) ? config.bindings : [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findAgentEntry(config = {}, agentId) {
|
|
43
|
+
const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
44
|
+
return list
|
|
45
|
+
.map((item) => ensureObject(item))
|
|
46
|
+
.find((item) => item.id === agentId) || null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function hasManagedBinding(config = {}, { agentId, accountId } = {}) {
|
|
50
|
+
return listBindings(config).some((binding) => {
|
|
51
|
+
const candidate = ensureObject(binding);
|
|
52
|
+
const match = ensureObject(candidate.match);
|
|
53
|
+
return candidate.agentId === agentId
|
|
54
|
+
&& match.channel === 'claworld'
|
|
55
|
+
&& match.accountId === accountId;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isManagedToolAllowlistReady(config = {}, options = {}) {
|
|
60
|
+
const allow = new Set(
|
|
61
|
+
(Array.isArray(config?.tools?.allow) ? config.tools.allow : [])
|
|
62
|
+
.map((item) => normalizeText(item, null))
|
|
63
|
+
.filter(Boolean),
|
|
64
|
+
);
|
|
65
|
+
if (allow.has('*')) return true;
|
|
66
|
+
return resolveToolNames(options).every((toolName) => allow.has(toolName));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isRelayBootstrapReady(account = {}) {
|
|
70
|
+
return Boolean(
|
|
71
|
+
account?.configured
|
|
72
|
+
&& (
|
|
73
|
+
normalizeText(account?.appToken, null)
|
|
74
|
+
|| (
|
|
75
|
+
account?.registration?.enabled === true
|
|
76
|
+
&& normalizeText(account?.registration?.agentCode, null)
|
|
77
|
+
)
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function parseCommandVersion(text) {
|
|
83
|
+
const match = String(text || '').match(/OpenClaw\s+([0-9]+(?:\.[0-9]+)+)/i)
|
|
84
|
+
|| String(text || '').match(/([0-9]+(?:\.[0-9]+)+)/);
|
|
85
|
+
return match ? match[1] : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseVersionParts(version) {
|
|
89
|
+
const normalized = String(version || '')
|
|
90
|
+
.trim()
|
|
91
|
+
.replace(/^[^\d]+/, '');
|
|
92
|
+
return normalized
|
|
93
|
+
.split('.')
|
|
94
|
+
.map((value) => Number.parseInt(value, 10))
|
|
95
|
+
.filter((value) => Number.isFinite(value));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function compareVersionParts(leftVersion, rightVersion) {
|
|
99
|
+
const leftParts = parseVersionParts(leftVersion);
|
|
100
|
+
const rightParts = parseVersionParts(rightVersion);
|
|
101
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
102
|
+
for (let index = 0; index < length; index += 1) {
|
|
103
|
+
const left = leftParts[index] ?? 0;
|
|
104
|
+
const right = rightParts[index] ?? 0;
|
|
105
|
+
if (left > right) return 1;
|
|
106
|
+
if (left < right) return -1;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function normalizeRelayHttpBaseUrl(serverUrl) {
|
|
112
|
+
const parsed = new URL(serverUrl);
|
|
113
|
+
if (parsed.protocol === 'ws:') parsed.protocol = 'http:';
|
|
114
|
+
if (parsed.protocol === 'wss:') parsed.protocol = 'https:';
|
|
115
|
+
parsed.pathname = '';
|
|
116
|
+
parsed.search = '';
|
|
117
|
+
parsed.hash = '';
|
|
118
|
+
return parsed.toString().replace(/\/$/, '');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildFetchHeaders({ apiKey = null, appToken = null, body = false } = {}) {
|
|
122
|
+
return {
|
|
123
|
+
accept: 'application/json',
|
|
124
|
+
...(body ? { 'content-type': 'application/json' } : {}),
|
|
125
|
+
...(apiKey ? { 'x-api-key': apiKey } : {}),
|
|
126
|
+
...(appToken ? { authorization: `Bearer ${appToken}` } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createInstallerError(code, message, context = {}) {
|
|
131
|
+
const error = new Error(message);
|
|
132
|
+
error.code = code;
|
|
133
|
+
error.context = context;
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function cloneObject(value = {}) {
|
|
138
|
+
return JSON.parse(JSON.stringify(ensureObject(value)));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseJson(text, fallback = null) {
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(String(text || '').trim());
|
|
144
|
+
} catch {
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function parseJsonDocument(text, fallback = null) {
|
|
150
|
+
const source = String(text || '').trim();
|
|
151
|
+
const direct = parseJson(source, null);
|
|
152
|
+
if (direct) return direct;
|
|
153
|
+
|
|
154
|
+
const lines = source.split('\n');
|
|
155
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
156
|
+
const candidate = lines.slice(index).join('\n').trim();
|
|
157
|
+
if (!candidate.startsWith('{') && !candidate.startsWith('[')) continue;
|
|
158
|
+
const parsed = parseJson(candidate, null);
|
|
159
|
+
if (parsed) return parsed;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return fallback;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseLegacyChannelTokenStatus(tokenValue = '') {
|
|
166
|
+
const normalized = normalizeText(tokenValue, '').toLowerCase();
|
|
167
|
+
if (!normalized || normalized === 'missing' || normalized === 'none' || normalized === 'unset') {
|
|
168
|
+
return {
|
|
169
|
+
tokenStatus: 'missing',
|
|
170
|
+
tokenSource: 'none',
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
normalized === 'registration'
|
|
175
|
+
|| normalized === 'registration-required'
|
|
176
|
+
|| normalized === 'registration_required'
|
|
177
|
+
|| normalized === 'agentcode'
|
|
178
|
+
|| normalized === 'agent-code'
|
|
179
|
+
) {
|
|
180
|
+
return {
|
|
181
|
+
tokenStatus: 'registration_required',
|
|
182
|
+
tokenSource: 'registration',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
tokenStatus: 'available',
|
|
187
|
+
tokenSource: normalized,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function parseLegacyChannelStatus(text = '') {
|
|
192
|
+
const entries = String(text || '')
|
|
193
|
+
.split('\n')
|
|
194
|
+
.map((line) => line.trim())
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.map((line) => line.match(/^-+\s*Claworld\s+(.+?):\s*(.+)$/i))
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
.map((match) => {
|
|
199
|
+
const accountId = normalizeText(match[1], null);
|
|
200
|
+
const flags = String(match[2] || '')
|
|
201
|
+
.split(',')
|
|
202
|
+
.map((flag) => flag.trim().toLowerCase())
|
|
203
|
+
.filter(Boolean);
|
|
204
|
+
const tokenFlag = flags.find((flag) => flag.startsWith('token:')) || '';
|
|
205
|
+
const tokenValue = tokenFlag.includes(':') ? tokenFlag.split(':').slice(1).join(':').trim() : '';
|
|
206
|
+
const tokenState = parseLegacyChannelTokenStatus(tokenValue);
|
|
207
|
+
const notConfigured = flags.includes('not configured') || flags.includes('unconfigured');
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
accountId,
|
|
211
|
+
enabled: flags.includes('disabled') ? false : true,
|
|
212
|
+
configured: notConfigured ? false : flags.includes('configured'),
|
|
213
|
+
...tokenState,
|
|
214
|
+
rawStatus: flags,
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
.filter((entry) => entry.accountId);
|
|
218
|
+
|
|
219
|
+
if (entries.length === 0) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
channels: {
|
|
225
|
+
claworld: {
|
|
226
|
+
configured: entries.some((entry) => entry.configured === true),
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
channelAccounts: {
|
|
230
|
+
claworld: entries,
|
|
231
|
+
},
|
|
232
|
+
sourceFormat: 'legacy_text',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function readClaworldAccountStatus(channelsStatus, accountId) {
|
|
237
|
+
const items = Array.isArray(channelsStatus?.channelAccounts?.claworld)
|
|
238
|
+
? channelsStatus.channelAccounts.claworld
|
|
239
|
+
: [];
|
|
240
|
+
return items.find((item) => item?.accountId === accountId) || null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parsePluginInfo(text = '') {
|
|
244
|
+
const source = String(text || '');
|
|
245
|
+
const status = source.match(/^\s*Status:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
246
|
+
const install = source.match(/^\s*Install:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
247
|
+
const sourcePath = source.match(/^\s*Source path:\s+(.+)$/m)?.[1]?.trim() || null;
|
|
248
|
+
const version = source.match(/^\s*Version:\s+(.+)$/m)?.[1]?.trim()
|
|
249
|
+
|| source.match(/^\s*Recorded version:\s+(.+)$/m)?.[1]?.trim()
|
|
250
|
+
|| null;
|
|
251
|
+
return {
|
|
252
|
+
installed: Boolean(source.trim()),
|
|
253
|
+
loaded: status === 'loaded',
|
|
254
|
+
status,
|
|
255
|
+
install,
|
|
256
|
+
sourcePath,
|
|
257
|
+
version,
|
|
258
|
+
raw: source,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function parseBindingLine(text = '', { agentId, accountId } = {}) {
|
|
263
|
+
return String(text || '')
|
|
264
|
+
.split('\n')
|
|
265
|
+
.map((line) => line.trim())
|
|
266
|
+
.find((line) => line === `- ${agentId} <- claworld accountId=${accountId}`) || null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function inspectTrackedClaworldPluginInstall(config = {}) {
|
|
270
|
+
const installRecord = ensureObject(config?.plugins?.installs?.claworld);
|
|
271
|
+
const tracked = Object.keys(installRecord).length > 0;
|
|
272
|
+
const source = normalizeText(installRecord.source, null);
|
|
273
|
+
return {
|
|
274
|
+
tracked,
|
|
275
|
+
updateable: tracked && TRACKED_PLUGIN_UPDATEABLE_SOURCES.has(source),
|
|
276
|
+
source,
|
|
277
|
+
spec: normalizeText(installRecord.spec, null),
|
|
278
|
+
resolvedSpec: normalizeText(installRecord.resolvedSpec, null),
|
|
279
|
+
resolvedVersion: normalizeText(installRecord.resolvedVersion, null),
|
|
280
|
+
installPath: normalizeText(installRecord.installPath, null),
|
|
281
|
+
record: tracked ? installRecord : null,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function needsPluginBootstrapConfig(config = {}) {
|
|
286
|
+
if (config?.channels?.claworld) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
return listBindings(config).some((binding) => ensureObject(binding).match?.channel === 'claworld');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function buildPluginBootstrapConfig(config = {}) {
|
|
293
|
+
const next = cloneObject(config);
|
|
294
|
+
|
|
295
|
+
const channels = ensureObject(next.channels);
|
|
296
|
+
if (Object.prototype.hasOwnProperty.call(channels, 'claworld')) {
|
|
297
|
+
delete channels.claworld;
|
|
298
|
+
}
|
|
299
|
+
if (Object.keys(channels).length > 0) {
|
|
300
|
+
next.channels = channels;
|
|
301
|
+
} else {
|
|
302
|
+
delete next.channels;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (Array.isArray(next.bindings)) {
|
|
306
|
+
next.bindings = next.bindings.filter((binding) => ensureObject(binding).match?.channel !== 'claworld');
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return next;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function preparePluginBootstrapConfig({
|
|
313
|
+
configPath = null,
|
|
314
|
+
config = {},
|
|
315
|
+
dryRun = false,
|
|
316
|
+
} = {}) {
|
|
317
|
+
if (!configPath || !needsPluginBootstrapConfig(config)) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (dryRun) {
|
|
322
|
+
return {
|
|
323
|
+
configPath,
|
|
324
|
+
pluginConfig: ensureObject(config.plugins),
|
|
325
|
+
cleanup: async () => {},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'claworld-plugin-bootstrap-'));
|
|
330
|
+
const tempConfigPath = path.join(tempRoot, path.basename(configPath || 'openclaw.json'));
|
|
331
|
+
await writeConfig(tempConfigPath, buildPluginBootstrapConfig(config));
|
|
332
|
+
return {
|
|
333
|
+
configPath: tempConfigPath,
|
|
334
|
+
pluginConfig: null,
|
|
335
|
+
cleanup: async () => {
|
|
336
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function mergePluginMetadata(config = {}, pluginConfig = {}) {
|
|
342
|
+
const extraPlugins = ensureObject(pluginConfig);
|
|
343
|
+
if (Object.keys(extraPlugins).length === 0) {
|
|
344
|
+
return cloneObject(config);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const next = cloneObject(config);
|
|
348
|
+
const existingPlugins = ensureObject(next.plugins);
|
|
349
|
+
next.plugins = {
|
|
350
|
+
...existingPlugins,
|
|
351
|
+
...extraPlugins,
|
|
352
|
+
entries: {
|
|
353
|
+
...ensureObject(existingPlugins.entries),
|
|
354
|
+
...ensureObject(extraPlugins.entries),
|
|
355
|
+
},
|
|
356
|
+
installs: {
|
|
357
|
+
...ensureObject(existingPlugins.installs),
|
|
358
|
+
...ensureObject(extraPlugins.installs),
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
return next;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function defaultCommandRunner({
|
|
365
|
+
bin,
|
|
366
|
+
args,
|
|
367
|
+
cwd = process.cwd(),
|
|
368
|
+
env = process.env,
|
|
369
|
+
dryRun = false,
|
|
370
|
+
capture = true,
|
|
371
|
+
} = {}) {
|
|
372
|
+
const rendered = [bin, ...args].join(' ');
|
|
373
|
+
if (dryRun) {
|
|
374
|
+
return {
|
|
375
|
+
status: 0,
|
|
376
|
+
stdout: '',
|
|
377
|
+
stderr: '',
|
|
378
|
+
rendered,
|
|
379
|
+
dryRun: true,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const result = spawnSync(bin, args, {
|
|
384
|
+
cwd,
|
|
385
|
+
env,
|
|
386
|
+
stdio: capture ? 'pipe' : 'inherit',
|
|
387
|
+
encoding: 'utf8',
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
if (result.error) {
|
|
391
|
+
const error = createInstallerError(
|
|
392
|
+
'openclaw_command_failed',
|
|
393
|
+
`Failed to run "${rendered}": ${result.error.message}`,
|
|
394
|
+
{ rendered },
|
|
395
|
+
);
|
|
396
|
+
error.cause = result.error;
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
status: result.status ?? 0,
|
|
402
|
+
stdout: result.stdout || '',
|
|
403
|
+
stderr: result.stderr || '',
|
|
404
|
+
rendered,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function executeCommand({
|
|
409
|
+
commandRunner = defaultCommandRunner,
|
|
410
|
+
bin,
|
|
411
|
+
args,
|
|
412
|
+
cwd = process.cwd(),
|
|
413
|
+
env = process.env,
|
|
414
|
+
dryRun = false,
|
|
415
|
+
capture = true,
|
|
416
|
+
allowFailure = false,
|
|
417
|
+
} = {}) {
|
|
418
|
+
const result = await Promise.resolve(commandRunner({
|
|
419
|
+
bin,
|
|
420
|
+
args,
|
|
421
|
+
cwd,
|
|
422
|
+
env,
|
|
423
|
+
dryRun,
|
|
424
|
+
capture,
|
|
425
|
+
}));
|
|
426
|
+
if (!allowFailure && result.status !== 0) {
|
|
427
|
+
throw createInstallerError(
|
|
428
|
+
'openclaw_command_failed',
|
|
429
|
+
`Command failed (${result.status}): ${result.rendered || [bin, ...args].join(' ')}`,
|
|
430
|
+
{ result },
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function parseConfigObject(sourceText, sourceLabel = 'openclaw config') {
|
|
437
|
+
const text = String(sourceText || '').replace(/^\uFEFF/, '').trim();
|
|
438
|
+
if (!text) return {};
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
const parsed = JSON.parse(text);
|
|
442
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
443
|
+
throw new Error('config root must be an object');
|
|
444
|
+
}
|
|
445
|
+
return parsed;
|
|
446
|
+
} catch {}
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
const expression = text.replace(/;\s*$/, '');
|
|
450
|
+
const script = new vm.Script(`(${expression}\n)`, { filename: sourceLabel });
|
|
451
|
+
const result = script.runInNewContext(Object.create(null), { timeout: 1000 });
|
|
452
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
453
|
+
throw new Error('config root must evaluate to an object');
|
|
454
|
+
}
|
|
455
|
+
return JSON.parse(JSON.stringify(result));
|
|
456
|
+
} catch (error) {
|
|
457
|
+
throw createInstallerError(
|
|
458
|
+
'invalid_openclaw_config',
|
|
459
|
+
`Failed to parse ${sourceLabel}: ${error.message}`,
|
|
460
|
+
{ sourceLabel },
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export async function loadConfigFromDisk(configPath) {
|
|
466
|
+
try {
|
|
467
|
+
const raw = await fs.readFile(configPath, 'utf8');
|
|
468
|
+
return { existed: true, config: parseConfigObject(raw, configPath) };
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (error && error.code === 'ENOENT') {
|
|
471
|
+
return { existed: false, config: {} };
|
|
472
|
+
}
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export async function backupConfigIfPresent(configPath, existed, dryRun = false) {
|
|
478
|
+
if (!existed) return null;
|
|
479
|
+
const stamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..+$/, '').replace('T', '-');
|
|
480
|
+
const backupPath = `${configPath}.bak.${stamp}`;
|
|
481
|
+
if (dryRun) return backupPath;
|
|
482
|
+
await fs.copyFile(configPath, backupPath);
|
|
483
|
+
return backupPath;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export async function writeConfig(configPath, config, dryRun = false) {
|
|
487
|
+
const rendered = `${JSON.stringify(config, null, 2)}\n`;
|
|
488
|
+
if (dryRun) return rendered;
|
|
489
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
490
|
+
await fs.writeFile(configPath, rendered, 'utf8');
|
|
491
|
+
return rendered;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function buildOpenclawCommandEnv({ configPath, stateDir = null, env = process.env } = {}) {
|
|
495
|
+
return {
|
|
496
|
+
...env,
|
|
497
|
+
...(configPath ? { OPENCLAW_CONFIG_PATH: configPath } : {}),
|
|
498
|
+
...(stateDir ? { OPENCLAW_STATE_DIR: stateDir } : {}),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function inspectManagedClaworldInstall({
|
|
503
|
+
cfg = {},
|
|
504
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
505
|
+
input = {},
|
|
506
|
+
overrides = {},
|
|
507
|
+
} = {}) {
|
|
508
|
+
const configuredAccountIds = listClaworldAccountIds(cfg);
|
|
509
|
+
const hasAnyConfig = configuredAccountIds.length > 0 || cfg?.channels?.claworld != null;
|
|
510
|
+
const managedOptions = resolveClaworldManagedRuntimeOptions({
|
|
511
|
+
cfg,
|
|
512
|
+
accountId,
|
|
513
|
+
input,
|
|
514
|
+
overrides,
|
|
515
|
+
});
|
|
516
|
+
const managedAgentPresent = Boolean(findAgentEntry(cfg, managedOptions.agentId));
|
|
517
|
+
const managedBindingPresent = hasManagedBinding(cfg, managedOptions);
|
|
518
|
+
const toolsReady = isManagedToolAllowlistReady(cfg, managedOptions);
|
|
519
|
+
const managedAccountPresent = configuredAccountIds.includes(managedOptions.accountId);
|
|
520
|
+
const accountStatus = managedAccountPresent
|
|
521
|
+
? inspectClaworldChannelAccount(cfg, managedOptions.accountId)
|
|
522
|
+
: inspectClaworldChannelAccount({}, managedOptions.accountId);
|
|
523
|
+
const managedRuntimeReady = isRelayBootstrapReady(accountStatus);
|
|
524
|
+
const managedReady = Boolean(
|
|
525
|
+
managedAccountPresent
|
|
526
|
+
&& managedRuntimeReady
|
|
527
|
+
&& managedAgentPresent
|
|
528
|
+
&& managedBindingPresent
|
|
529
|
+
&& toolsReady
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
let statusLabel = 'needs setup';
|
|
533
|
+
let selectionHint = 'remote relay world channel';
|
|
534
|
+
if (managedRuntimeReady) {
|
|
535
|
+
statusLabel = managedReady ? 'configured' : 'configured (managed refresh recommended)';
|
|
536
|
+
selectionHint = managedReady ? 'configured · managed runtime' : 'configured · managed refresh';
|
|
537
|
+
} else if (hasAnyConfig) {
|
|
538
|
+
statusLabel = 'needs refresh';
|
|
539
|
+
selectionHint = 'needs refresh · remote relay';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return {
|
|
543
|
+
hasAnyConfig,
|
|
544
|
+
configuredAccountIds,
|
|
545
|
+
defaultAccountId: defaultClaworldAccountId(cfg) || null,
|
|
546
|
+
managedOptions,
|
|
547
|
+
managedAccountPresent,
|
|
548
|
+
managedAgentPresent,
|
|
549
|
+
managedBindingPresent,
|
|
550
|
+
toolsReady,
|
|
551
|
+
accountStatus,
|
|
552
|
+
managedRuntimeReady,
|
|
553
|
+
managedReady,
|
|
554
|
+
reusableAppToken: normalizeText(accountStatus?.appToken, null),
|
|
555
|
+
statusLabel,
|
|
556
|
+
selectionHint,
|
|
557
|
+
quickstartScore: managedRuntimeReady ? 2 : 5,
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function buildManagedOnboardingStatus({ cfg = {}, accountId = DEFAULT_CLAWORLD_ACCOUNT_ID } = {}) {
|
|
562
|
+
const inspection = inspectManagedClaworldInstall({ cfg, accountId });
|
|
563
|
+
const configured = inspection.configuredAccountIds.some((configuredAccountId) =>
|
|
564
|
+
isRelayBootstrapReady(inspectClaworldChannelAccount(cfg, configuredAccountId)),
|
|
565
|
+
);
|
|
566
|
+
return {
|
|
567
|
+
configured,
|
|
568
|
+
statusLines: [`Claworld: ${inspection.statusLabel}`],
|
|
569
|
+
selectionHint: inspection.selectionHint,
|
|
570
|
+
quickstartScore: configured ? 2 : inspection.quickstartScore,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export async function detectOpenclawHost({
|
|
575
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
576
|
+
commandRunner = defaultCommandRunner,
|
|
577
|
+
cwd = process.cwd(),
|
|
578
|
+
env = process.env,
|
|
579
|
+
dryRun = false,
|
|
580
|
+
} = {}) {
|
|
581
|
+
const result = await executeCommand({
|
|
582
|
+
commandRunner,
|
|
583
|
+
bin: openclawBin,
|
|
584
|
+
args: ['--version'],
|
|
585
|
+
cwd,
|
|
586
|
+
env,
|
|
587
|
+
dryRun,
|
|
588
|
+
});
|
|
589
|
+
const version = parseCommandVersion(result.stdout || result.stderr);
|
|
590
|
+
if (!version) {
|
|
591
|
+
throw createInstallerError(
|
|
592
|
+
'openclaw_version_unreadable',
|
|
593
|
+
'Unable to determine the installed OpenClaw version.',
|
|
594
|
+
{ result },
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
version,
|
|
599
|
+
raw: (result.stdout || result.stderr || '').trim(),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export async function inspectClaworldPluginInstall({
|
|
604
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
605
|
+
configPath = null,
|
|
606
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
607
|
+
commandRunner = defaultCommandRunner,
|
|
608
|
+
cwd = process.cwd(),
|
|
609
|
+
env = process.env,
|
|
610
|
+
dryRun = false,
|
|
611
|
+
} = {}) {
|
|
612
|
+
const result = await executeCommand({
|
|
613
|
+
commandRunner,
|
|
614
|
+
bin: openclawBin,
|
|
615
|
+
args: ['plugins', 'info', 'claworld'],
|
|
616
|
+
cwd,
|
|
617
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
618
|
+
dryRun,
|
|
619
|
+
allowFailure: true,
|
|
620
|
+
});
|
|
621
|
+
if (result.status !== 0) {
|
|
622
|
+
return {
|
|
623
|
+
installed: false,
|
|
624
|
+
loaded: false,
|
|
625
|
+
status: null,
|
|
626
|
+
raw: `${result.stdout || ''}${result.stderr || ''}`.trim(),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return parsePluginInfo(`${result.stdout || ''}${result.stderr || ''}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export async function ensureClaworldPluginInstalled({
|
|
633
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
634
|
+
configPath = null,
|
|
635
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
636
|
+
config = {},
|
|
637
|
+
commandRunner = defaultCommandRunner,
|
|
638
|
+
cwd = process.cwd(),
|
|
639
|
+
env = process.env,
|
|
640
|
+
dryRun = false,
|
|
641
|
+
installMode = 'npm',
|
|
642
|
+
installSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
|
|
643
|
+
refresh = false,
|
|
644
|
+
} = {}) {
|
|
645
|
+
const bootstrap = await preparePluginBootstrapConfig({
|
|
646
|
+
configPath,
|
|
647
|
+
config,
|
|
648
|
+
dryRun,
|
|
649
|
+
});
|
|
650
|
+
const pluginConfigPath = bootstrap?.configPath || configPath;
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
const before = await inspectClaworldPluginInstall({
|
|
654
|
+
openclawBin,
|
|
655
|
+
configPath: pluginConfigPath,
|
|
656
|
+
stateDir,
|
|
657
|
+
commandRunner,
|
|
658
|
+
cwd,
|
|
659
|
+
env,
|
|
660
|
+
dryRun,
|
|
661
|
+
});
|
|
662
|
+
if (before.installed && !refresh) {
|
|
663
|
+
return {
|
|
664
|
+
changed: false,
|
|
665
|
+
action: 'reused_existing_plugin_install',
|
|
666
|
+
plugin: before,
|
|
667
|
+
pluginConfig: bootstrap?.pluginConfig || null,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if (installMode === 'skip') {
|
|
672
|
+
return {
|
|
673
|
+
changed: false,
|
|
674
|
+
action: 'skipped_plugin_install',
|
|
675
|
+
plugin: before,
|
|
676
|
+
pluginConfig: bootstrap?.pluginConfig || null,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (before.installed && refresh && installMode === 'copy') {
|
|
681
|
+
await executeCommand({
|
|
682
|
+
commandRunner,
|
|
683
|
+
bin: openclawBin,
|
|
684
|
+
args: ['plugins', 'uninstall', 'claworld', '--force'],
|
|
685
|
+
cwd,
|
|
686
|
+
env: buildOpenclawCommandEnv({ configPath: pluginConfigPath, stateDir, env }),
|
|
687
|
+
dryRun,
|
|
688
|
+
capture: false,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const args = ['plugins', 'install'];
|
|
693
|
+
if (installMode === 'link') args.push('--link');
|
|
694
|
+
args.push(installSource);
|
|
695
|
+
await executeCommand({
|
|
696
|
+
commandRunner,
|
|
697
|
+
bin: openclawBin,
|
|
698
|
+
args,
|
|
699
|
+
cwd,
|
|
700
|
+
env: buildOpenclawCommandEnv({ configPath: pluginConfigPath, stateDir, env }),
|
|
701
|
+
dryRun,
|
|
702
|
+
capture: false,
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const after = await inspectClaworldPluginInstall({
|
|
706
|
+
openclawBin,
|
|
707
|
+
configPath: pluginConfigPath,
|
|
708
|
+
stateDir,
|
|
709
|
+
commandRunner,
|
|
710
|
+
cwd,
|
|
711
|
+
env,
|
|
712
|
+
dryRun,
|
|
713
|
+
});
|
|
714
|
+
if (!after.installed) {
|
|
715
|
+
throw createInstallerError(
|
|
716
|
+
'claworld_plugin_install_failed',
|
|
717
|
+
'OpenClaw did not report the claworld plugin as installed after plugin install.',
|
|
718
|
+
{ installMode, installSource, before, after },
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const pluginConfig = pluginConfigPath
|
|
723
|
+
? (await loadConfigFromDisk(pluginConfigPath)).config?.plugins || null
|
|
724
|
+
: null;
|
|
725
|
+
|
|
726
|
+
return {
|
|
727
|
+
changed: true,
|
|
728
|
+
action: before.installed ? 'refreshed_plugin_install' : 'installed_plugin',
|
|
729
|
+
plugin: after,
|
|
730
|
+
pluginConfig,
|
|
731
|
+
};
|
|
732
|
+
} finally {
|
|
733
|
+
if (bootstrap) {
|
|
734
|
+
await bootstrap.cleanup();
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
export async function updateTrackedClaworldPluginInstall({
|
|
740
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
741
|
+
configPath = null,
|
|
742
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
743
|
+
config = {},
|
|
744
|
+
commandRunner = defaultCommandRunner,
|
|
745
|
+
cwd = process.cwd(),
|
|
746
|
+
env = process.env,
|
|
747
|
+
dryRun = false,
|
|
748
|
+
} = {}) {
|
|
749
|
+
const trackedInstall = inspectTrackedClaworldPluginInstall(config);
|
|
750
|
+
const before = await inspectClaworldPluginInstall({
|
|
751
|
+
openclawBin,
|
|
752
|
+
configPath,
|
|
753
|
+
stateDir,
|
|
754
|
+
commandRunner,
|
|
755
|
+
cwd,
|
|
756
|
+
env,
|
|
757
|
+
dryRun,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
if (!before.installed) {
|
|
761
|
+
throw createInstallerError(
|
|
762
|
+
'claworld_plugin_not_installed',
|
|
763
|
+
'The Claworld plugin is not installed or not discoverable. Run the install command before update.',
|
|
764
|
+
{ plugin: before, trackedInstall },
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (!trackedInstall.tracked) {
|
|
769
|
+
return {
|
|
770
|
+
changed: false,
|
|
771
|
+
action: 'skipped_untracked_plugin_update',
|
|
772
|
+
reason: 'plugins.installs.claworld_missing',
|
|
773
|
+
trackedInstall,
|
|
774
|
+
plugin: before,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (!trackedInstall.updateable) {
|
|
779
|
+
return {
|
|
780
|
+
changed: false,
|
|
781
|
+
action: 'skipped_untracked_plugin_update',
|
|
782
|
+
reason: `plugins.installs.claworld_source_${trackedInstall.source || 'unknown'}_is_not_host_updateable`,
|
|
783
|
+
trackedInstall,
|
|
784
|
+
plugin: before,
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
await executeCommand({
|
|
789
|
+
commandRunner,
|
|
790
|
+
bin: openclawBin,
|
|
791
|
+
args: ['plugins', 'update', 'claworld'],
|
|
792
|
+
cwd,
|
|
793
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
794
|
+
dryRun,
|
|
795
|
+
capture: false,
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
const after = await inspectClaworldPluginInstall({
|
|
799
|
+
openclawBin,
|
|
800
|
+
configPath,
|
|
801
|
+
stateDir,
|
|
802
|
+
commandRunner,
|
|
803
|
+
cwd,
|
|
804
|
+
env,
|
|
805
|
+
dryRun,
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
if (!after.installed) {
|
|
809
|
+
throw createInstallerError(
|
|
810
|
+
'claworld_plugin_update_failed',
|
|
811
|
+
'OpenClaw no longer reported the claworld plugin as installed after plugin update.',
|
|
812
|
+
{ before, after, trackedInstall },
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return {
|
|
817
|
+
changed: true,
|
|
818
|
+
action: dryRun ? 'dry_run_tracked_plugin_update' : 'updated_tracked_plugin',
|
|
819
|
+
trackedInstall,
|
|
820
|
+
plugin: after,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function fetchJson(fetchImpl, url, init = {}) {
|
|
825
|
+
let response;
|
|
826
|
+
try {
|
|
827
|
+
response = await fetchImpl(url, init);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
throw createInstallerError(
|
|
830
|
+
'installer_fetch_failed',
|
|
831
|
+
`Failed to reach ${url}: ${error?.message || String(error)}`,
|
|
832
|
+
{ url, method: init?.method || 'GET' },
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const text = await response.text();
|
|
837
|
+
return {
|
|
838
|
+
ok: response.ok,
|
|
839
|
+
status: response.status,
|
|
840
|
+
body: parseJsonDocument(text, null),
|
|
841
|
+
text,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
export async function fetchInstallManifest({
|
|
846
|
+
serverUrl = DEFAULT_CLAWORLD_SERVER_URL,
|
|
847
|
+
apiKey = null,
|
|
848
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
849
|
+
} = {}) {
|
|
850
|
+
if (typeof fetchImpl !== 'function') {
|
|
851
|
+
throw createInstallerError('missing_fetch', 'Install manifest fetch requires a fetch implementation.');
|
|
852
|
+
}
|
|
853
|
+
const url = `${normalizeRelayHttpBaseUrl(serverUrl)}/v1/meta/install`;
|
|
854
|
+
const response = await fetchJson(fetchImpl, url, {
|
|
855
|
+
method: 'GET',
|
|
856
|
+
headers: buildFetchHeaders({ apiKey }),
|
|
857
|
+
});
|
|
858
|
+
if (!response.ok || !response.body) {
|
|
859
|
+
throw createInstallerError(
|
|
860
|
+
'install_manifest_unavailable',
|
|
861
|
+
`Failed to read Claworld install contract from ${url}.`,
|
|
862
|
+
{ url, response },
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
return response.body;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
export async function activateInstall({
|
|
869
|
+
serverUrl = DEFAULT_CLAWORLD_SERVER_URL,
|
|
870
|
+
apiKey = null,
|
|
871
|
+
appToken = null,
|
|
872
|
+
displayName = null,
|
|
873
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
874
|
+
} = {}) {
|
|
875
|
+
if (typeof fetchImpl !== 'function') {
|
|
876
|
+
throw createInstallerError('missing_fetch', 'Install activation requires a fetch implementation.');
|
|
877
|
+
}
|
|
878
|
+
const url = `${normalizeRelayHttpBaseUrl(serverUrl)}/v1/onboarding/activate`;
|
|
879
|
+
const body = {};
|
|
880
|
+
if (displayName) {
|
|
881
|
+
body.displayName = displayName;
|
|
882
|
+
}
|
|
883
|
+
const response = await fetchJson(fetchImpl, url, {
|
|
884
|
+
method: 'POST',
|
|
885
|
+
headers: buildFetchHeaders({ apiKey, appToken, body: true }),
|
|
886
|
+
body: JSON.stringify(body),
|
|
887
|
+
});
|
|
888
|
+
if (!response.ok || !response.body) {
|
|
889
|
+
throw createInstallerError(
|
|
890
|
+
'install_activation_failed',
|
|
891
|
+
`Failed to activate Claworld install via ${url}.`,
|
|
892
|
+
{ url, response },
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
return response.body;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
export async function readGatewayStatus({
|
|
899
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
900
|
+
configPath = null,
|
|
901
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
902
|
+
commandRunner = defaultCommandRunner,
|
|
903
|
+
cwd = process.cwd(),
|
|
904
|
+
env = process.env,
|
|
905
|
+
dryRun = false,
|
|
906
|
+
} = {}) {
|
|
907
|
+
const result = await executeCommand({
|
|
908
|
+
commandRunner,
|
|
909
|
+
bin: openclawBin,
|
|
910
|
+
args: ['gateway', 'status', '--json', '--no-probe'],
|
|
911
|
+
cwd,
|
|
912
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
913
|
+
dryRun,
|
|
914
|
+
});
|
|
915
|
+
const payload = parseJsonDocument(`${result.stdout || ''}\n${result.stderr || ''}`, null);
|
|
916
|
+
if (!payload) {
|
|
917
|
+
throw createInstallerError(
|
|
918
|
+
'invalid_gateway_status',
|
|
919
|
+
'OpenClaw gateway status did not produce JSON output.',
|
|
920
|
+
{ result },
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
return payload;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
export async function readChannelStatus({
|
|
927
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
928
|
+
configPath = null,
|
|
929
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
930
|
+
commandRunner = defaultCommandRunner,
|
|
931
|
+
cwd = process.cwd(),
|
|
932
|
+
env = process.env,
|
|
933
|
+
dryRun = false,
|
|
934
|
+
} = {}) {
|
|
935
|
+
const result = await executeCommand({
|
|
936
|
+
commandRunner,
|
|
937
|
+
bin: openclawBin,
|
|
938
|
+
args: ['channels', 'status', '--json'],
|
|
939
|
+
cwd,
|
|
940
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
941
|
+
dryRun,
|
|
942
|
+
});
|
|
943
|
+
const output = `${result.stdout || ''}\n${result.stderr || ''}`;
|
|
944
|
+
const payload = parseJsonDocument(output, null) || parseLegacyChannelStatus(output);
|
|
945
|
+
if (!payload) {
|
|
946
|
+
throw createInstallerError(
|
|
947
|
+
'invalid_channel_status',
|
|
948
|
+
'OpenClaw channel status did not produce JSON output.',
|
|
949
|
+
{ result },
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
return payload;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
export async function readAgentBindings({
|
|
956
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
957
|
+
configPath = null,
|
|
958
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
959
|
+
commandRunner = defaultCommandRunner,
|
|
960
|
+
cwd = process.cwd(),
|
|
961
|
+
env = process.env,
|
|
962
|
+
dryRun = false,
|
|
963
|
+
} = {}) {
|
|
964
|
+
return await executeCommand({
|
|
965
|
+
commandRunner,
|
|
966
|
+
bin: openclawBin,
|
|
967
|
+
args: ['agents', 'bindings'],
|
|
968
|
+
cwd,
|
|
969
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
970
|
+
dryRun,
|
|
971
|
+
allowFailure: true,
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
export async function validateOpenclawConfig({
|
|
976
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
977
|
+
configPath = null,
|
|
978
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
979
|
+
commandRunner = defaultCommandRunner,
|
|
980
|
+
cwd = process.cwd(),
|
|
981
|
+
env = process.env,
|
|
982
|
+
dryRun = false,
|
|
983
|
+
} = {}) {
|
|
984
|
+
await executeCommand({
|
|
985
|
+
commandRunner,
|
|
986
|
+
bin: openclawBin,
|
|
987
|
+
args: ['config', 'validate'],
|
|
988
|
+
cwd,
|
|
989
|
+
env: buildOpenclawCommandEnv({ configPath, stateDir, env }),
|
|
990
|
+
dryRun,
|
|
991
|
+
capture: false,
|
|
992
|
+
});
|
|
993
|
+
return { ok: true };
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export async function refreshOpenclawRuntime({
|
|
997
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
998
|
+
configPath = null,
|
|
999
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1000
|
+
commandRunner = defaultCommandRunner,
|
|
1001
|
+
cwd = process.cwd(),
|
|
1002
|
+
env = process.env,
|
|
1003
|
+
dryRun = false,
|
|
1004
|
+
} = {}) {
|
|
1005
|
+
const commandEnv = buildOpenclawCommandEnv({ configPath, stateDir, env });
|
|
1006
|
+
const restart = await executeCommand({
|
|
1007
|
+
commandRunner,
|
|
1008
|
+
bin: openclawBin,
|
|
1009
|
+
args: ['gateway', 'restart'],
|
|
1010
|
+
cwd,
|
|
1011
|
+
env: commandEnv,
|
|
1012
|
+
dryRun,
|
|
1013
|
+
capture: false,
|
|
1014
|
+
allowFailure: true,
|
|
1015
|
+
});
|
|
1016
|
+
if (restart.status === 0) {
|
|
1017
|
+
return { ok: true, action: 'restart', result: restart };
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const start = await executeCommand({
|
|
1021
|
+
commandRunner,
|
|
1022
|
+
bin: openclawBin,
|
|
1023
|
+
args: ['gateway', 'start'],
|
|
1024
|
+
cwd,
|
|
1025
|
+
env: commandEnv,
|
|
1026
|
+
dryRun,
|
|
1027
|
+
capture: false,
|
|
1028
|
+
allowFailure: true,
|
|
1029
|
+
});
|
|
1030
|
+
if (start.status === 0) {
|
|
1031
|
+
return { ok: true, action: 'start', result: start };
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
throw createInstallerError(
|
|
1035
|
+
'openclaw_gateway_refresh_failed',
|
|
1036
|
+
'Failed to restart or start the OpenClaw gateway service.',
|
|
1037
|
+
{ restart, start },
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function wait(delayMs) {
|
|
1042
|
+
return new Promise((resolve) => {
|
|
1043
|
+
setTimeout(resolve, delayMs);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
export async function verifyClaworldInstall({
|
|
1048
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1049
|
+
configPath = null,
|
|
1050
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1051
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1052
|
+
agentId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1053
|
+
commandRunner = defaultCommandRunner,
|
|
1054
|
+
cwd = process.cwd(),
|
|
1055
|
+
env = process.env,
|
|
1056
|
+
dryRun = false,
|
|
1057
|
+
attempts = DEFAULT_VERIFICATION_ATTEMPTS,
|
|
1058
|
+
delayMs = DEFAULT_VERIFICATION_DELAY_MS,
|
|
1059
|
+
} = {}) {
|
|
1060
|
+
let lastResult = null;
|
|
1061
|
+
|
|
1062
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1063
|
+
const [plugin, gatewayStatus, channelStatus, bindings] = await Promise.all([
|
|
1064
|
+
inspectClaworldPluginInstall({
|
|
1065
|
+
openclawBin,
|
|
1066
|
+
configPath,
|
|
1067
|
+
stateDir,
|
|
1068
|
+
commandRunner,
|
|
1069
|
+
cwd,
|
|
1070
|
+
env,
|
|
1071
|
+
dryRun,
|
|
1072
|
+
}),
|
|
1073
|
+
readGatewayStatus({
|
|
1074
|
+
openclawBin,
|
|
1075
|
+
configPath,
|
|
1076
|
+
stateDir,
|
|
1077
|
+
commandRunner,
|
|
1078
|
+
cwd,
|
|
1079
|
+
env,
|
|
1080
|
+
dryRun,
|
|
1081
|
+
}),
|
|
1082
|
+
readChannelStatus({
|
|
1083
|
+
openclawBin,
|
|
1084
|
+
configPath,
|
|
1085
|
+
stateDir,
|
|
1086
|
+
commandRunner,
|
|
1087
|
+
cwd,
|
|
1088
|
+
env,
|
|
1089
|
+
dryRun,
|
|
1090
|
+
}),
|
|
1091
|
+
readAgentBindings({
|
|
1092
|
+
openclawBin,
|
|
1093
|
+
configPath,
|
|
1094
|
+
stateDir,
|
|
1095
|
+
commandRunner,
|
|
1096
|
+
cwd,
|
|
1097
|
+
env,
|
|
1098
|
+
dryRun,
|
|
1099
|
+
}),
|
|
1100
|
+
]);
|
|
1101
|
+
|
|
1102
|
+
const channelAccount = readClaworldAccountStatus(channelStatus, accountId);
|
|
1103
|
+
const gatewayRunning = gatewayStatus?.service?.runtime?.status === 'running';
|
|
1104
|
+
const bindingLine = parseBindingLine(`${bindings.stdout || ''}${bindings.stderr || ''}`, {
|
|
1105
|
+
agentId,
|
|
1106
|
+
accountId,
|
|
1107
|
+
});
|
|
1108
|
+
const channelReady = Boolean(
|
|
1109
|
+
channelAccount
|
|
1110
|
+
&& channelAccount.configured === true
|
|
1111
|
+
&& channelAccount.enabled !== false
|
|
1112
|
+
&& channelAccount.tokenStatus === 'available'
|
|
1113
|
+
);
|
|
1114
|
+
const pluginReady = Boolean(plugin.installed);
|
|
1115
|
+
const bindingReady = Boolean(bindingLine);
|
|
1116
|
+
|
|
1117
|
+
lastResult = {
|
|
1118
|
+
ok: pluginReady && gatewayRunning && channelReady && bindingReady,
|
|
1119
|
+
attempt,
|
|
1120
|
+
plugin,
|
|
1121
|
+
gatewayStatus,
|
|
1122
|
+
channelStatus,
|
|
1123
|
+
channelAccount,
|
|
1124
|
+
bindingLine,
|
|
1125
|
+
gatewayRunning,
|
|
1126
|
+
channelReady,
|
|
1127
|
+
bindingReady,
|
|
1128
|
+
};
|
|
1129
|
+
|
|
1130
|
+
if (lastResult.ok) {
|
|
1131
|
+
return lastResult;
|
|
1132
|
+
}
|
|
1133
|
+
if (attempt < attempts) {
|
|
1134
|
+
await wait(delayMs);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
return lastResult || {
|
|
1139
|
+
ok: false,
|
|
1140
|
+
attempt: 0,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
async function reconcileManagedClaworldRuntime({
|
|
1145
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1146
|
+
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1147
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1148
|
+
currentConfigState = { existed: false, config: {} },
|
|
1149
|
+
currentConfig = {},
|
|
1150
|
+
host = null,
|
|
1151
|
+
serverUrl = null,
|
|
1152
|
+
apiKey = null,
|
|
1153
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1154
|
+
agentId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1155
|
+
workspace = null,
|
|
1156
|
+
displayName = null,
|
|
1157
|
+
toolProfile = null,
|
|
1158
|
+
approvalMode = null,
|
|
1159
|
+
sessionDmScope = null,
|
|
1160
|
+
repoRoot = null,
|
|
1161
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1162
|
+
commandRunner = defaultCommandRunner,
|
|
1163
|
+
cwd = process.cwd(),
|
|
1164
|
+
env = process.env,
|
|
1165
|
+
dryRun = false,
|
|
1166
|
+
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1167
|
+
} = {}) {
|
|
1168
|
+
const existingInstall = inspectManagedClaworldInstall({
|
|
1169
|
+
cfg: currentConfig,
|
|
1170
|
+
accountId,
|
|
1171
|
+
overrides: {
|
|
1172
|
+
agentId,
|
|
1173
|
+
workspace,
|
|
1174
|
+
displayName,
|
|
1175
|
+
toolProfile,
|
|
1176
|
+
approvalMode,
|
|
1177
|
+
sessionDmScope,
|
|
1178
|
+
...(repoRoot ? { repoRoot } : {}),
|
|
1179
|
+
},
|
|
1180
|
+
});
|
|
1181
|
+
const effectiveServerUrl = normalizeText(
|
|
1182
|
+
serverUrl,
|
|
1183
|
+
normalizeText(existingInstall.accountStatus?.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
|
|
1184
|
+
);
|
|
1185
|
+
|
|
1186
|
+
const manifest = await fetchInstallManifest({
|
|
1187
|
+
serverUrl: effectiveServerUrl,
|
|
1188
|
+
apiKey,
|
|
1189
|
+
fetchImpl,
|
|
1190
|
+
});
|
|
1191
|
+
const manifestMinHostVersion = normalizeText(
|
|
1192
|
+
manifest?.installer?.minHostVersion,
|
|
1193
|
+
normalizeText(manifest?.plugin?.minHostVersion, CLAWORLD_OPENCLAW_MIN_HOST_VERSION),
|
|
1194
|
+
);
|
|
1195
|
+
if (host && compareVersionParts(host.version, manifestMinHostVersion) < 0) {
|
|
1196
|
+
throw createInstallerError(
|
|
1197
|
+
'openclaw_version_too_old',
|
|
1198
|
+
`OpenClaw ${host.version} is below the required minimum ${manifestMinHostVersion}.`,
|
|
1199
|
+
{ hostVersion: host.version, minHostVersion: manifestMinHostVersion },
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const installAccountId = normalizeText(
|
|
1204
|
+
accountId,
|
|
1205
|
+
normalizeText(manifest?.setup?.defaultAccountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
1206
|
+
);
|
|
1207
|
+
const installAgentId = normalizeText(
|
|
1208
|
+
agentId,
|
|
1209
|
+
normalizeText(manifest?.setup?.defaultLocalAgentId, installAccountId),
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
const preflight = inspectManagedClaworldInstall({
|
|
1213
|
+
cfg: currentConfig,
|
|
1214
|
+
accountId: installAccountId,
|
|
1215
|
+
overrides: {
|
|
1216
|
+
agentId: installAgentId,
|
|
1217
|
+
workspace,
|
|
1218
|
+
serverUrl: effectiveServerUrl,
|
|
1219
|
+
displayName,
|
|
1220
|
+
toolProfile,
|
|
1221
|
+
approvalMode,
|
|
1222
|
+
sessionDmScope,
|
|
1223
|
+
...(repoRoot ? { repoRoot } : {}),
|
|
1224
|
+
},
|
|
1225
|
+
});
|
|
1226
|
+
const desiredDisplayName = normalizeText(
|
|
1227
|
+
displayName,
|
|
1228
|
+
normalizeText(preflight.managedOptions.displayName, null),
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
let activation = null;
|
|
1232
|
+
let activationMode = 'new_activation';
|
|
1233
|
+
const existingAppToken = normalizeText(preflight.reusableAppToken, null);
|
|
1234
|
+
if (existingAppToken) {
|
|
1235
|
+
try {
|
|
1236
|
+
activation = await activateInstall({
|
|
1237
|
+
serverUrl: effectiveServerUrl,
|
|
1238
|
+
apiKey,
|
|
1239
|
+
appToken: existingAppToken,
|
|
1240
|
+
displayName: desiredDisplayName,
|
|
1241
|
+
fetchImpl,
|
|
1242
|
+
});
|
|
1243
|
+
activationMode = 'reused_existing_token';
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
const status = error?.context?.response?.status;
|
|
1246
|
+
if (status !== 401 && status !== 403) {
|
|
1247
|
+
throw error;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (!activation) {
|
|
1252
|
+
activation = await activateInstall({
|
|
1253
|
+
serverUrl: effectiveServerUrl,
|
|
1254
|
+
apiKey,
|
|
1255
|
+
displayName: desiredDisplayName,
|
|
1256
|
+
fetchImpl,
|
|
1257
|
+
});
|
|
1258
|
+
activationMode = 'created_new_activation';
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const managedOptions = resolveClaworldManagedRuntimeOptions({
|
|
1262
|
+
cfg: currentConfig,
|
|
1263
|
+
accountId: installAccountId,
|
|
1264
|
+
input: {
|
|
1265
|
+
name: desiredDisplayName,
|
|
1266
|
+
appToken: activation.appToken,
|
|
1267
|
+
toolProfile,
|
|
1268
|
+
},
|
|
1269
|
+
overrides: {
|
|
1270
|
+
agentId: installAgentId,
|
|
1271
|
+
workspace,
|
|
1272
|
+
serverUrl: effectiveServerUrl,
|
|
1273
|
+
apiKey,
|
|
1274
|
+
appToken: activation.appToken,
|
|
1275
|
+
displayName: desiredDisplayName,
|
|
1276
|
+
toolProfile,
|
|
1277
|
+
approvalMode,
|
|
1278
|
+
sessionDmScope,
|
|
1279
|
+
replaceManagedRuntime: true,
|
|
1280
|
+
installPlugin: false,
|
|
1281
|
+
pluginInstallMode: 'skip',
|
|
1282
|
+
...(repoRoot ? { repoRoot } : {}),
|
|
1283
|
+
},
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
const transformed = applyClaworldManagedRuntimeConfig(currentConfig, managedOptions);
|
|
1287
|
+
const configChanged = JSON.stringify(currentConfig) !== JSON.stringify(transformed.config);
|
|
1288
|
+
const backupPath = configChanged
|
|
1289
|
+
? await backupConfigIfPresent(configPath, currentConfigState.existed, dryRun)
|
|
1290
|
+
: null;
|
|
1291
|
+
if (configChanged) {
|
|
1292
|
+
await writeConfig(configPath, transformed.config, dryRun);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
const workspaceActions = await seedManagedWorkspace(managedOptions, dryRun);
|
|
1296
|
+
await validateOpenclawConfig({
|
|
1297
|
+
openclawBin,
|
|
1298
|
+
configPath,
|
|
1299
|
+
stateDir,
|
|
1300
|
+
commandRunner,
|
|
1301
|
+
cwd,
|
|
1302
|
+
env,
|
|
1303
|
+
dryRun,
|
|
1304
|
+
});
|
|
1305
|
+
const runtimeRefresh = await refreshOpenclawRuntime({
|
|
1306
|
+
openclawBin,
|
|
1307
|
+
configPath,
|
|
1308
|
+
stateDir,
|
|
1309
|
+
commandRunner,
|
|
1310
|
+
cwd,
|
|
1311
|
+
env,
|
|
1312
|
+
dryRun,
|
|
1313
|
+
});
|
|
1314
|
+
const verification = await verifyClaworldInstall({
|
|
1315
|
+
openclawBin,
|
|
1316
|
+
configPath,
|
|
1317
|
+
stateDir,
|
|
1318
|
+
accountId: installAccountId,
|
|
1319
|
+
agentId: installAgentId,
|
|
1320
|
+
commandRunner,
|
|
1321
|
+
cwd,
|
|
1322
|
+
env,
|
|
1323
|
+
dryRun,
|
|
1324
|
+
delayMs: Math.min(timeoutMs, DEFAULT_VERIFICATION_DELAY_MS),
|
|
1325
|
+
});
|
|
1326
|
+
if (!verification.ok) {
|
|
1327
|
+
throw createInstallerError(
|
|
1328
|
+
'claworld_install_verification_failed',
|
|
1329
|
+
'Claworld install verification did not confirm the managed account binding.',
|
|
1330
|
+
{ verification },
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
return {
|
|
1335
|
+
backupPath,
|
|
1336
|
+
existingInstall,
|
|
1337
|
+
effectiveServerUrl,
|
|
1338
|
+
manifest,
|
|
1339
|
+
preflight,
|
|
1340
|
+
activationMode,
|
|
1341
|
+
activation,
|
|
1342
|
+
managedOptions,
|
|
1343
|
+
transformed,
|
|
1344
|
+
configChanged,
|
|
1345
|
+
workspaceActions,
|
|
1346
|
+
runtimeRefresh,
|
|
1347
|
+
verification,
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
export async function runClaworldInstallerInstall({
|
|
1352
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1353
|
+
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1354
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1355
|
+
serverUrl = null,
|
|
1356
|
+
apiKey = null,
|
|
1357
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1358
|
+
agentId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1359
|
+
workspace = null,
|
|
1360
|
+
displayName = null,
|
|
1361
|
+
toolProfile = null,
|
|
1362
|
+
approvalMode = null,
|
|
1363
|
+
sessionDmScope = null,
|
|
1364
|
+
repoRoot = null,
|
|
1365
|
+
pluginInstallMode = 'npm',
|
|
1366
|
+
pluginInstallSource = CLAWORLD_INSTALLER_PACKAGE_NAME,
|
|
1367
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1368
|
+
commandRunner = defaultCommandRunner,
|
|
1369
|
+
cwd = process.cwd(),
|
|
1370
|
+
env = process.env,
|
|
1371
|
+
dryRun = false,
|
|
1372
|
+
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1373
|
+
} = {}) {
|
|
1374
|
+
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
1375
|
+
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
1376
|
+
const commandEnv = buildOpenclawCommandEnv({
|
|
1377
|
+
configPath: resolvedConfigPath,
|
|
1378
|
+
stateDir: resolvedStateDir,
|
|
1379
|
+
env,
|
|
1380
|
+
});
|
|
1381
|
+
const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1382
|
+
const currentConfigBeforePluginInstall = currentConfigState.config;
|
|
1383
|
+
const host = await detectOpenclawHost({
|
|
1384
|
+
openclawBin,
|
|
1385
|
+
commandRunner,
|
|
1386
|
+
cwd,
|
|
1387
|
+
env: commandEnv,
|
|
1388
|
+
dryRun,
|
|
1389
|
+
});
|
|
1390
|
+
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
1391
|
+
throw createInstallerError(
|
|
1392
|
+
'openclaw_version_too_old',
|
|
1393
|
+
`OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
1394
|
+
{ hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const plugin = await ensureClaworldPluginInstalled({
|
|
1399
|
+
openclawBin,
|
|
1400
|
+
configPath: resolvedConfigPath,
|
|
1401
|
+
stateDir: resolvedStateDir,
|
|
1402
|
+
config: currentConfigBeforePluginInstall,
|
|
1403
|
+
commandRunner,
|
|
1404
|
+
cwd,
|
|
1405
|
+
env,
|
|
1406
|
+
dryRun,
|
|
1407
|
+
installMode: pluginInstallMode,
|
|
1408
|
+
installSource: pluginInstallSource,
|
|
1409
|
+
refresh: false,
|
|
1410
|
+
});
|
|
1411
|
+
const currentConfig = mergePluginMetadata(
|
|
1412
|
+
currentConfigBeforePluginInstall,
|
|
1413
|
+
plugin.pluginConfig,
|
|
1414
|
+
);
|
|
1415
|
+
const lifecycle = await reconcileManagedClaworldRuntime({
|
|
1416
|
+
openclawBin,
|
|
1417
|
+
configPath: resolvedConfigPath,
|
|
1418
|
+
stateDir: resolvedStateDir,
|
|
1419
|
+
currentConfigState,
|
|
1420
|
+
currentConfig,
|
|
1421
|
+
host,
|
|
1422
|
+
serverUrl,
|
|
1423
|
+
apiKey,
|
|
1424
|
+
accountId,
|
|
1425
|
+
agentId,
|
|
1426
|
+
workspace,
|
|
1427
|
+
displayName,
|
|
1428
|
+
toolProfile,
|
|
1429
|
+
approvalMode,
|
|
1430
|
+
sessionDmScope,
|
|
1431
|
+
repoRoot,
|
|
1432
|
+
fetchImpl,
|
|
1433
|
+
commandRunner,
|
|
1434
|
+
cwd,
|
|
1435
|
+
env,
|
|
1436
|
+
dryRun,
|
|
1437
|
+
timeoutMs,
|
|
1438
|
+
});
|
|
1439
|
+
|
|
1440
|
+
return {
|
|
1441
|
+
ok: true,
|
|
1442
|
+
command: CLAWORLD_INSTALLER_COMMAND,
|
|
1443
|
+
configPath: resolvedConfigPath,
|
|
1444
|
+
stateDir: resolvedStateDir,
|
|
1445
|
+
host,
|
|
1446
|
+
plugin,
|
|
1447
|
+
...lifecycle,
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
export async function runClaworldInstallerUpdate({
|
|
1452
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
1453
|
+
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
1454
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
1455
|
+
serverUrl = null,
|
|
1456
|
+
apiKey = null,
|
|
1457
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1458
|
+
agentId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
1459
|
+
workspace = null,
|
|
1460
|
+
displayName = null,
|
|
1461
|
+
toolProfile = null,
|
|
1462
|
+
approvalMode = null,
|
|
1463
|
+
sessionDmScope = null,
|
|
1464
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
1465
|
+
commandRunner = defaultCommandRunner,
|
|
1466
|
+
cwd = process.cwd(),
|
|
1467
|
+
env = process.env,
|
|
1468
|
+
dryRun = false,
|
|
1469
|
+
timeoutMs = DEFAULT_INSTALL_TIMEOUT_MS,
|
|
1470
|
+
} = {}) {
|
|
1471
|
+
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
1472
|
+
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
1473
|
+
const commandEnv = buildOpenclawCommandEnv({
|
|
1474
|
+
configPath: resolvedConfigPath,
|
|
1475
|
+
stateDir: resolvedStateDir,
|
|
1476
|
+
env,
|
|
1477
|
+
});
|
|
1478
|
+
const currentConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1479
|
+
const host = await detectOpenclawHost({
|
|
1480
|
+
openclawBin,
|
|
1481
|
+
commandRunner,
|
|
1482
|
+
cwd,
|
|
1483
|
+
env: commandEnv,
|
|
1484
|
+
dryRun,
|
|
1485
|
+
});
|
|
1486
|
+
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
1487
|
+
throw createInstallerError(
|
|
1488
|
+
'openclaw_version_too_old',
|
|
1489
|
+
`OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
1490
|
+
{ hostVersion: host.version, minHostVersion: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const plugin = await updateTrackedClaworldPluginInstall({
|
|
1495
|
+
openclawBin,
|
|
1496
|
+
configPath: resolvedConfigPath,
|
|
1497
|
+
stateDir: resolvedStateDir,
|
|
1498
|
+
config: currentConfigState.config,
|
|
1499
|
+
commandRunner,
|
|
1500
|
+
cwd,
|
|
1501
|
+
env,
|
|
1502
|
+
dryRun,
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
const refreshedConfigState = await loadConfigFromDisk(resolvedConfigPath);
|
|
1506
|
+
const currentConfig = mergePluginMetadata(refreshedConfigState.config, null);
|
|
1507
|
+
const lifecycle = await reconcileManagedClaworldRuntime({
|
|
1508
|
+
openclawBin,
|
|
1509
|
+
configPath: resolvedConfigPath,
|
|
1510
|
+
stateDir: resolvedStateDir,
|
|
1511
|
+
currentConfigState: refreshedConfigState,
|
|
1512
|
+
currentConfig,
|
|
1513
|
+
host,
|
|
1514
|
+
serverUrl,
|
|
1515
|
+
apiKey,
|
|
1516
|
+
accountId,
|
|
1517
|
+
agentId,
|
|
1518
|
+
workspace,
|
|
1519
|
+
displayName,
|
|
1520
|
+
toolProfile,
|
|
1521
|
+
approvalMode,
|
|
1522
|
+
sessionDmScope,
|
|
1523
|
+
fetchImpl,
|
|
1524
|
+
commandRunner,
|
|
1525
|
+
cwd,
|
|
1526
|
+
env,
|
|
1527
|
+
dryRun,
|
|
1528
|
+
timeoutMs,
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
return {
|
|
1532
|
+
ok: true,
|
|
1533
|
+
command: CLAWORLD_UPDATE_COMMAND,
|
|
1534
|
+
configPath: resolvedConfigPath,
|
|
1535
|
+
stateDir: resolvedStateDir,
|
|
1536
|
+
host,
|
|
1537
|
+
plugin,
|
|
1538
|
+
...lifecycle,
|
|
1539
|
+
};
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
export {
|
|
1543
|
+
compareVersionParts,
|
|
1544
|
+
defaultCommandRunner,
|
|
1545
|
+
findAgentEntry,
|
|
1546
|
+
parseCommandVersion,
|
|
1547
|
+
readClaworldAccountStatus,
|
|
1548
|
+
};
|