@xfxstudio/claworld 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -5
- package/src/openclaw/plugin/claworld-channel-plugin.js +3 -56
- package/src/openclaw/plugin/register-tooling.js +5 -6
- package/src/openclaw/plugin/relay-client.js +1 -1
- package/bin/claworld.mjs +0 -9
- package/src/openclaw/installer/cli.js +0 -406
- package/src/openclaw/installer/constants.js +0 -14
- package/src/openclaw/installer/core.js +0 -2115
- package/src/openclaw/installer/doctor.js +0 -876
- package/src/openclaw/installer/workspace-contract.js +0 -427
|
@@ -1,876 +0,0 @@
|
|
|
1
|
-
import os from 'os';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { inspectClaworldChannelAccount } from '../plugin/config-schema.js';
|
|
4
|
-
import {
|
|
5
|
-
DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
6
|
-
DEFAULT_CLAWORLD_AGENT_ID,
|
|
7
|
-
DEFAULT_CLAWORLD_SERVER_URL,
|
|
8
|
-
expandUserPath,
|
|
9
|
-
getEffectiveAgentSandboxMode,
|
|
10
|
-
MIN_MANAGED_SESSION_VISIBILITY,
|
|
11
|
-
normalizeText,
|
|
12
|
-
resolveClaworldManagedRuntimeOptions,
|
|
13
|
-
sandboxModeNeedsSessionToolsVisibility,
|
|
14
|
-
} from '../plugin/managed-config.js';
|
|
15
|
-
import {
|
|
16
|
-
CLAWORLD_DOCTOR_COMMAND,
|
|
17
|
-
CLAWORLD_INSTALLER_COMMAND,
|
|
18
|
-
CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
19
|
-
} from './constants.js';
|
|
20
|
-
import {
|
|
21
|
-
activateInstall,
|
|
22
|
-
compareVersionParts,
|
|
23
|
-
DEFAULT_OPENCLAW_BIN,
|
|
24
|
-
DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
25
|
-
DEFAULT_OPENCLAW_STATE_DIR,
|
|
26
|
-
detectOpenclawHost,
|
|
27
|
-
fetchInstallManifest,
|
|
28
|
-
inspectClaworldPluginInstall,
|
|
29
|
-
loadConfigFromDisk,
|
|
30
|
-
readAgentBindings,
|
|
31
|
-
readChannelStatus,
|
|
32
|
-
readGatewayStatus,
|
|
33
|
-
} from './core.js';
|
|
34
|
-
import { inspectManagedWorkspaceContract } from './workspace-contract.js';
|
|
35
|
-
|
|
36
|
-
function createCheck({
|
|
37
|
-
id,
|
|
38
|
-
category,
|
|
39
|
-
label,
|
|
40
|
-
status,
|
|
41
|
-
summary,
|
|
42
|
-
action = null,
|
|
43
|
-
details = {},
|
|
44
|
-
} = {}) {
|
|
45
|
-
return {
|
|
46
|
-
id,
|
|
47
|
-
category,
|
|
48
|
-
label,
|
|
49
|
-
status,
|
|
50
|
-
summary,
|
|
51
|
-
action,
|
|
52
|
-
details,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function statusRank(status) {
|
|
57
|
-
if (status === 'fail') return 3;
|
|
58
|
-
if (status === 'warn') return 2;
|
|
59
|
-
return 1;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function overallStatus(checks = []) {
|
|
63
|
-
if (checks.some((check) => check.status === 'fail')) return 'fail';
|
|
64
|
-
if (checks.some((check) => check.status === 'warn')) return 'warn';
|
|
65
|
-
return 'pass';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function formatStatus(status) {
|
|
69
|
-
return `[${String(status || 'pass').toUpperCase()}]`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function findManagedAgentEntry(config = {}, agentId) {
|
|
73
|
-
const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
74
|
-
return list.find((entry) => entry?.id === agentId) || null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function hasConfiguredBinding(config = {}, { agentId, accountId } = {}) {
|
|
78
|
-
const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
|
|
79
|
-
return bindings.some((binding) =>
|
|
80
|
-
binding?.agentId === agentId
|
|
81
|
-
&& binding?.match?.channel === 'claworld'
|
|
82
|
-
&& binding?.match?.accountId === accountId,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function findChannelAccount(channelStatus, accountId) {
|
|
87
|
-
const entries = Array.isArray(channelStatus?.channelAccounts?.claworld)
|
|
88
|
-
? channelStatus.channelAccounts.claworld
|
|
89
|
-
: [];
|
|
90
|
-
return entries.find((entry) => entry?.accountId === accountId) || null;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const SESSION_VISIBILITY_RANK = Object.freeze({
|
|
94
|
-
self: 0,
|
|
95
|
-
tree: 1,
|
|
96
|
-
agent: 2,
|
|
97
|
-
all: 3,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
function resolveConfiguredSessionVisibility(config = {}) {
|
|
101
|
-
const visibility = normalizeText(config?.tools?.sessions?.visibility, null);
|
|
102
|
-
return Object.prototype.hasOwnProperty.call(SESSION_VISIBILITY_RANK, visibility) ? visibility : null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function normalizeGatewayBaseUrl(gatewayStatus) {
|
|
106
|
-
const probeUrl = normalizeText(gatewayStatus?.gateway?.probeUrl, null);
|
|
107
|
-
if (probeUrl) {
|
|
108
|
-
const parsed = new URL(probeUrl);
|
|
109
|
-
parsed.protocol = parsed.protocol === 'wss:' ? 'https:' : 'http:';
|
|
110
|
-
parsed.pathname = '';
|
|
111
|
-
parsed.search = '';
|
|
112
|
-
parsed.hash = '';
|
|
113
|
-
return parsed.toString().replace(/\/$/, '');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const bindHost = normalizeText(gatewayStatus?.gateway?.bindHost, null);
|
|
117
|
-
const port = gatewayStatus?.gateway?.port;
|
|
118
|
-
if (!bindHost || !port) return null;
|
|
119
|
-
const host = bindHost === '0.0.0.0' ? '127.0.0.1' : bindHost;
|
|
120
|
-
return `http://${host}:${port}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function normalizeConfiguredGatewayBaseUrl(config = {}) {
|
|
124
|
-
const configuredPort = Number(config?.gateway?.port);
|
|
125
|
-
if (!Number.isFinite(configuredPort) || configuredPort <= 0) {
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const configuredBind = normalizeText(config?.gateway?.bind, '127.0.0.1');
|
|
130
|
-
const configuredHost = (
|
|
131
|
-
configuredBind === 'loopback'
|
|
132
|
-
|| configuredBind === 'localhost'
|
|
133
|
-
|| configuredBind === '0.0.0.0'
|
|
134
|
-
|| configuredBind === '::'
|
|
135
|
-
)
|
|
136
|
-
? '127.0.0.1'
|
|
137
|
-
: configuredBind;
|
|
138
|
-
return `http://${configuredHost}:${configuredPort}`;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function describePluginStatusRouteTarget({
|
|
142
|
-
config = {},
|
|
143
|
-
gatewayStatus = {},
|
|
144
|
-
} = {}) {
|
|
145
|
-
const configuredBaseUrl = normalizeConfiguredGatewayBaseUrl(config);
|
|
146
|
-
const runtimeBaseUrl = normalizeGatewayBaseUrl(gatewayStatus);
|
|
147
|
-
return {
|
|
148
|
-
configuredBaseUrl,
|
|
149
|
-
runtimeBaseUrl,
|
|
150
|
-
baseUrlMismatch: Boolean(
|
|
151
|
-
configuredBaseUrl
|
|
152
|
-
&& runtimeBaseUrl
|
|
153
|
-
&& configuredBaseUrl !== runtimeBaseUrl
|
|
154
|
-
),
|
|
155
|
-
runtimePortSource: normalizeText(gatewayStatus?.gateway?.portSource, null),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function fetchPluginStatusRoute({
|
|
160
|
-
gatewayBaseUrl,
|
|
161
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
162
|
-
} = {}) {
|
|
163
|
-
if (!gatewayBaseUrl || typeof fetchImpl !== 'function') {
|
|
164
|
-
return {
|
|
165
|
-
ok: false,
|
|
166
|
-
status: null,
|
|
167
|
-
body: null,
|
|
168
|
-
routeUrl: gatewayBaseUrl ? `${gatewayBaseUrl}/plugins/claworld/status` : null,
|
|
169
|
-
error: gatewayBaseUrl ? 'missing_fetch' : 'missing_gateway_base_url',
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const routeUrl = `${gatewayBaseUrl}/plugins/claworld/status`;
|
|
174
|
-
try {
|
|
175
|
-
const response = await fetchImpl(routeUrl, {
|
|
176
|
-
method: 'GET',
|
|
177
|
-
headers: {
|
|
178
|
-
accept: 'application/json',
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
const text = await response.text();
|
|
182
|
-
let body = null;
|
|
183
|
-
try {
|
|
184
|
-
body = text ? JSON.parse(text) : null;
|
|
185
|
-
} catch {
|
|
186
|
-
body = null;
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
ok: response.ok,
|
|
190
|
-
status: response.status,
|
|
191
|
-
body,
|
|
192
|
-
routeUrl,
|
|
193
|
-
error: null,
|
|
194
|
-
};
|
|
195
|
-
} catch (error) {
|
|
196
|
-
return {
|
|
197
|
-
ok: false,
|
|
198
|
-
status: null,
|
|
199
|
-
body: null,
|
|
200
|
-
routeUrl,
|
|
201
|
-
error: error?.message || String(error),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function formatWorkspaceState(workspaceInspection) {
|
|
207
|
-
return workspaceInspection.files.map((file) => `${file.relativePath}=${file.state}`).join(', ');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function runClaworldDoctor({
|
|
211
|
-
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
212
|
-
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
213
|
-
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
214
|
-
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
215
|
-
agentId = DEFAULT_CLAWORLD_AGENT_ID,
|
|
216
|
-
workspace = null,
|
|
217
|
-
serverUrl = null,
|
|
218
|
-
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
219
|
-
commandRunner,
|
|
220
|
-
cwd = process.cwd(),
|
|
221
|
-
env = process.env,
|
|
222
|
-
dryRun = false,
|
|
223
|
-
} = {}) {
|
|
224
|
-
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
225
|
-
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
226
|
-
const configState = await loadConfigFromDisk(resolvedConfigPath);
|
|
227
|
-
const config = configState.config;
|
|
228
|
-
const configuredAccount = inspectClaworldChannelAccount(config, accountId);
|
|
229
|
-
const managedOptions = resolveClaworldManagedRuntimeOptions({
|
|
230
|
-
cfg: config,
|
|
231
|
-
accountId,
|
|
232
|
-
overrides: {
|
|
233
|
-
agentId,
|
|
234
|
-
workspace,
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
const rawManagedAccount = config?.channels?.claworld?.accounts?.[configuredAccount.accountId] || null;
|
|
238
|
-
const configuredApiKey = normalizeText(rawManagedAccount?.apiKey, 'local-test');
|
|
239
|
-
const resolvedAgentId = normalizeText(managedOptions.agentId, DEFAULT_CLAWORLD_AGENT_ID);
|
|
240
|
-
const resolvedWorkspace = normalizeText(managedOptions.workspace, null);
|
|
241
|
-
const effectiveServerUrl = normalizeText(
|
|
242
|
-
serverUrl,
|
|
243
|
-
normalizeText(configuredAccount.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
|
|
244
|
-
);
|
|
245
|
-
const workspaceInspection = managedOptions.manageWorkspace
|
|
246
|
-
? await inspectManagedWorkspaceContract({
|
|
247
|
-
agentId: resolvedAgentId,
|
|
248
|
-
accountId: normalizeText(accountId, configuredAccount.accountId || DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
249
|
-
workspace: resolvedWorkspace,
|
|
250
|
-
serverUrl: effectiveServerUrl,
|
|
251
|
-
appToken: configuredAccount.appToken,
|
|
252
|
-
registrationDisplayName: configuredAccount.registration?.displayName || null,
|
|
253
|
-
defaultTargetAgentId: configuredAccount.relay?.defaultTargetAgentId || null,
|
|
254
|
-
})
|
|
255
|
-
: {
|
|
256
|
-
ok: true,
|
|
257
|
-
workspacePath: resolvedWorkspace,
|
|
258
|
-
metadataPath: null,
|
|
259
|
-
files: [],
|
|
260
|
-
issues: [],
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const checks = [];
|
|
264
|
-
const facts = {
|
|
265
|
-
host: null,
|
|
266
|
-
plugin: null,
|
|
267
|
-
manifest: null,
|
|
268
|
-
activation: null,
|
|
269
|
-
gatewayStatus: null,
|
|
270
|
-
channelStatus: null,
|
|
271
|
-
bindingOutput: null,
|
|
272
|
-
pluginStatusRoute: null,
|
|
273
|
-
workspaceInspection,
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
const host = await detectOpenclawHost({
|
|
278
|
-
openclawBin,
|
|
279
|
-
commandRunner,
|
|
280
|
-
cwd,
|
|
281
|
-
env,
|
|
282
|
-
dryRun,
|
|
283
|
-
});
|
|
284
|
-
facts.host = host;
|
|
285
|
-
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
286
|
-
checks.push(createCheck({
|
|
287
|
-
id: 'host-version',
|
|
288
|
-
category: 'Host and plugin',
|
|
289
|
-
label: 'OpenClaw host version',
|
|
290
|
-
status: 'fail',
|
|
291
|
-
summary: `OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
292
|
-
action: `Upgrade OpenClaw, then rerun \`${CLAWORLD_DOCTOR_COMMAND}\`.`,
|
|
293
|
-
details: {
|
|
294
|
-
version: host.version,
|
|
295
|
-
minimum: CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
296
|
-
requestedBin: host.requestedBin || null,
|
|
297
|
-
binaryPath: host.binaryPath || null,
|
|
298
|
-
binarySource: host.binarySource || null,
|
|
299
|
-
},
|
|
300
|
-
}));
|
|
301
|
-
} else {
|
|
302
|
-
checks.push(createCheck({
|
|
303
|
-
id: 'host-version',
|
|
304
|
-
category: 'Host and plugin',
|
|
305
|
-
label: 'OpenClaw host version',
|
|
306
|
-
status: 'pass',
|
|
307
|
-
summary: `OpenClaw ${host.version} satisfies the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
308
|
-
details: {
|
|
309
|
-
version: host.version,
|
|
310
|
-
minimum: CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
311
|
-
requestedBin: host.requestedBin || null,
|
|
312
|
-
binaryPath: host.binaryPath || null,
|
|
313
|
-
binarySource: host.binarySource || null,
|
|
314
|
-
skippedLocalBinaryPaths: host.skippedLocalBinaryPaths || [],
|
|
315
|
-
},
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
checks.push(createCheck({
|
|
320
|
-
id: 'host-version',
|
|
321
|
-
category: 'Host and plugin',
|
|
322
|
-
label: 'OpenClaw host version',
|
|
323
|
-
status: 'fail',
|
|
324
|
-
summary: 'Unable to determine the installed OpenClaw version.',
|
|
325
|
-
action: 'Install OpenClaw or fix the host CLI path, then rerun the doctor command.',
|
|
326
|
-
details: { message: error?.message || String(error) },
|
|
327
|
-
}));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
facts.plugin = await inspectClaworldPluginInstall({
|
|
332
|
-
openclawBin,
|
|
333
|
-
configPath: resolvedConfigPath,
|
|
334
|
-
stateDir: resolvedStateDir,
|
|
335
|
-
commandRunner,
|
|
336
|
-
cwd,
|
|
337
|
-
env,
|
|
338
|
-
dryRun,
|
|
339
|
-
});
|
|
340
|
-
if (!facts.plugin.installed) {
|
|
341
|
-
checks.push(createCheck({
|
|
342
|
-
id: 'plugin-install',
|
|
343
|
-
category: 'Host and plugin',
|
|
344
|
-
label: 'Claworld plugin install',
|
|
345
|
-
status: 'fail',
|
|
346
|
-
summary: 'The `claworld` plugin is not installed or not discoverable.',
|
|
347
|
-
action: `Run \`${CLAWORLD_INSTALLER_COMMAND}\` to install and configure the managed Claworld runtime.`,
|
|
348
|
-
}));
|
|
349
|
-
} else {
|
|
350
|
-
checks.push(createCheck({
|
|
351
|
-
id: 'plugin-install',
|
|
352
|
-
category: 'Host and plugin',
|
|
353
|
-
label: 'Claworld plugin install',
|
|
354
|
-
status: 'pass',
|
|
355
|
-
summary: `The \`claworld\` plugin is installed (${facts.plugin.install || 'install type unavailable'}).`,
|
|
356
|
-
details: { version: facts.plugin.version || null, sourcePath: facts.plugin.sourcePath || null },
|
|
357
|
-
}));
|
|
358
|
-
checks.push(createCheck({
|
|
359
|
-
id: 'plugin-load',
|
|
360
|
-
category: 'Host and plugin',
|
|
361
|
-
label: 'Claworld plugin load',
|
|
362
|
-
status: facts.plugin.loaded ? 'pass' : 'fail',
|
|
363
|
-
summary: facts.plugin.loaded
|
|
364
|
-
? 'The `claworld` plugin reports a healthy loaded state.'
|
|
365
|
-
: `The \`claworld\` plugin is installed but reports status ${facts.plugin.status || 'unknown'}.`,
|
|
366
|
-
action: facts.plugin.loaded
|
|
367
|
-
? null
|
|
368
|
-
: 'Inspect `openclaw plugins info claworld` and fix the plugin load error before retrying.',
|
|
369
|
-
details: { status: facts.plugin.status || null, raw: facts.plugin.raw || null },
|
|
370
|
-
}));
|
|
371
|
-
}
|
|
372
|
-
} catch (error) {
|
|
373
|
-
checks.push(createCheck({
|
|
374
|
-
id: 'plugin-install',
|
|
375
|
-
category: 'Host and plugin',
|
|
376
|
-
label: 'Claworld plugin install',
|
|
377
|
-
status: 'fail',
|
|
378
|
-
summary: 'Unable to inspect the `claworld` plugin install state.',
|
|
379
|
-
action: 'Confirm the host can run `openclaw plugins info claworld`, then rerun doctor.',
|
|
380
|
-
details: { message: error?.message || String(error) },
|
|
381
|
-
}));
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const agentEntry = findManagedAgentEntry(config, resolvedAgentId);
|
|
385
|
-
const agentWorkspace = normalizeText(agentEntry?.workspace, null);
|
|
386
|
-
const bindingConfigured = hasConfiguredBinding(config, {
|
|
387
|
-
agentId: resolvedAgentId,
|
|
388
|
-
accountId: normalizeText(accountId, configuredAccount.accountId || DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
389
|
-
});
|
|
390
|
-
const defaultAccount = normalizeText(config?.channels?.claworld?.defaultAccount, null);
|
|
391
|
-
|
|
392
|
-
checks.push(createCheck({
|
|
393
|
-
id: 'managed-agent',
|
|
394
|
-
category: 'Managed config',
|
|
395
|
-
label: managedOptions.manageAgentEntry ? 'Managed local agent entry' : 'Bound local agent target',
|
|
396
|
-
status: managedOptions.manageAgentEntry
|
|
397
|
-
? (agentEntry && agentWorkspace === resolvedWorkspace ? 'pass' : 'fail')
|
|
398
|
-
: (agentEntry ? 'pass' : 'warn'),
|
|
399
|
-
summary: managedOptions.manageAgentEntry
|
|
400
|
-
? (
|
|
401
|
-
agentEntry && agentWorkspace === resolvedWorkspace
|
|
402
|
-
? `Managed agent \`${resolvedAgentId}\` points at \`${resolvedWorkspace}\`.`
|
|
403
|
-
: agentEntry
|
|
404
|
-
? `Managed agent \`${resolvedAgentId}\` exists but points at \`${agentWorkspace || 'missing workspace'}\`.`
|
|
405
|
-
: `Managed agent \`${resolvedAgentId}\` is missing from agents.list.`
|
|
406
|
-
)
|
|
407
|
-
: (
|
|
408
|
-
agentEntry
|
|
409
|
-
? `Claworld is bound to existing local agent \`${resolvedAgentId}\` (${agentWorkspace || 'workspace unspecified'}).`
|
|
410
|
-
: `Claworld is configured to bind to local agent \`${resolvedAgentId}\`; this doctor run could not confirm an agents.list entry and will rely on runtime binding visibility instead.`
|
|
411
|
-
),
|
|
412
|
-
action: managedOptions.manageAgentEntry
|
|
413
|
-
? `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to reconcile the managed agent entry.`
|
|
414
|
-
: null,
|
|
415
|
-
details: {
|
|
416
|
-
expectedWorkspace: resolvedWorkspace,
|
|
417
|
-
actualWorkspace: agentWorkspace,
|
|
418
|
-
manageAgentEntry: managedOptions.manageAgentEntry,
|
|
419
|
-
},
|
|
420
|
-
}));
|
|
421
|
-
|
|
422
|
-
checks.push(createCheck({
|
|
423
|
-
id: 'managed-account',
|
|
424
|
-
category: 'Managed config',
|
|
425
|
-
label: 'Managed claworld account',
|
|
426
|
-
status: configuredAccount.configured && configuredAccount.enabled !== false ? 'pass' : 'fail',
|
|
427
|
-
summary: configuredAccount.configured && configuredAccount.enabled !== false
|
|
428
|
-
? `Managed account \`${configuredAccount.accountId}\` is configured and enabled.`
|
|
429
|
-
: `Managed account \`${configuredAccount.accountId}\` is missing required config or disabled.`,
|
|
430
|
-
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to repair the managed account config.`,
|
|
431
|
-
details: {
|
|
432
|
-
configuredStatus: configuredAccount.configuredStatus,
|
|
433
|
-
enabled: configuredAccount.enabled,
|
|
434
|
-
issues: configuredAccount.issues || [],
|
|
435
|
-
},
|
|
436
|
-
}));
|
|
437
|
-
|
|
438
|
-
checks.push(createCheck({
|
|
439
|
-
id: 'default-account',
|
|
440
|
-
category: 'Managed config',
|
|
441
|
-
label: 'Default claworld account',
|
|
442
|
-
status: defaultAccount === configuredAccount.accountId ? 'pass' : 'fail',
|
|
443
|
-
summary: defaultAccount === configuredAccount.accountId
|
|
444
|
-
? `channels.claworld.defaultAccount is correctly set to \`${configuredAccount.accountId}\`.`
|
|
445
|
-
: `channels.claworld.defaultAccount is \`${defaultAccount || 'missing'}\`, expected \`${configuredAccount.accountId}\`.`,
|
|
446
|
-
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to reset the managed default account.`,
|
|
447
|
-
details: { expectedDefaultAccount: configuredAccount.accountId, actualDefaultAccount: defaultAccount },
|
|
448
|
-
}));
|
|
449
|
-
|
|
450
|
-
checks.push(createCheck({
|
|
451
|
-
id: 'managed-binding',
|
|
452
|
-
category: 'Managed config',
|
|
453
|
-
label: 'Managed claworld binding',
|
|
454
|
-
status: bindingConfigured ? 'pass' : 'fail',
|
|
455
|
-
summary: bindingConfigured
|
|
456
|
-
? `Config includes the managed binding ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`
|
|
457
|
-
: `Config is missing the managed binding ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`,
|
|
458
|
-
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to restore the managed binding.`,
|
|
459
|
-
}));
|
|
460
|
-
|
|
461
|
-
checks.push(createCheck({
|
|
462
|
-
id: 'managed-config-shape',
|
|
463
|
-
category: 'Managed config',
|
|
464
|
-
label: 'Managed config shape',
|
|
465
|
-
status: configuredAccount.issues.length === 0 ? 'pass' : 'fail',
|
|
466
|
-
summary: configuredAccount.issues.length === 0
|
|
467
|
-
? 'Managed Claworld config matches the canonical account schema.'
|
|
468
|
-
: `Managed Claworld config still has schema issues: ${configuredAccount.issues.map((issue) => issue.code).join(', ')}.`,
|
|
469
|
-
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to normalize the managed config shape.`,
|
|
470
|
-
details: { issues: configuredAccount.issues },
|
|
471
|
-
}));
|
|
472
|
-
|
|
473
|
-
const configuredSessionVisibility = resolveConfiguredSessionVisibility(config);
|
|
474
|
-
const sessionVisibilityHealthy = (
|
|
475
|
-
configuredSessionVisibility != null
|
|
476
|
-
&& SESSION_VISIBILITY_RANK[configuredSessionVisibility] >= SESSION_VISIBILITY_RANK[MIN_MANAGED_SESSION_VISIBILITY]
|
|
477
|
-
);
|
|
478
|
-
checks.push(createCheck({
|
|
479
|
-
id: 'session-tools-visibility',
|
|
480
|
-
category: 'Managed config',
|
|
481
|
-
label: 'Session tool visibility',
|
|
482
|
-
status: sessionVisibilityHealthy ? 'pass' : 'warn',
|
|
483
|
-
summary: sessionVisibilityHealthy
|
|
484
|
-
? `tools.sessions.visibility is \`${configuredSessionVisibility}\`, which satisfies the managed same-agent minimum \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`
|
|
485
|
-
: `tools.sessions.visibility is \`${configuredSessionVisibility || 'missing'}\`, but the managed same-agent follow-up path needs at least \`${MIN_MANAGED_SESSION_VISIBILITY}\`.`,
|
|
486
|
-
action: sessionVisibilityHealthy
|
|
487
|
-
? null
|
|
488
|
-
: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to raise session visibility for managed same-agent follow-up routing.`,
|
|
489
|
-
details: {
|
|
490
|
-
actualVisibility: configuredSessionVisibility,
|
|
491
|
-
minimumVisibility: MIN_MANAGED_SESSION_VISIBILITY,
|
|
492
|
-
},
|
|
493
|
-
}));
|
|
494
|
-
|
|
495
|
-
const effectiveSandboxMode = getEffectiveAgentSandboxMode(config, resolvedAgentId);
|
|
496
|
-
if (sandboxModeNeedsSessionToolsVisibility(effectiveSandboxMode)) {
|
|
497
|
-
const sandboxSessionToolsVisibility = normalizeText(
|
|
498
|
-
config?.agents?.defaults?.sandbox?.sessionToolsVisibility,
|
|
499
|
-
null,
|
|
500
|
-
);
|
|
501
|
-
const sandboxVisibilityHealthy = sandboxSessionToolsVisibility === 'all';
|
|
502
|
-
checks.push(createCheck({
|
|
503
|
-
id: 'sandbox-session-tools-visibility',
|
|
504
|
-
category: 'Managed config',
|
|
505
|
-
label: 'Sandbox session tool visibility',
|
|
506
|
-
status: sandboxVisibilityHealthy ? 'pass' : 'warn',
|
|
507
|
-
summary: sandboxVisibilityHealthy
|
|
508
|
-
? `Effective sandbox mode is \`${effectiveSandboxMode}\` and agents.defaults.sandbox.sessionToolsVisibility is correctly set to \`all\`.`
|
|
509
|
-
: `Effective sandbox mode is \`${effectiveSandboxMode}\`, so agents.defaults.sandbox.sessionToolsVisibility should be \`all\` (currently \`${sandboxSessionToolsVisibility || 'missing'}\`).`,
|
|
510
|
-
action: sandboxVisibilityHealthy
|
|
511
|
-
? null
|
|
512
|
-
: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to widen sandbox session-tool visibility for managed follow-up routing.`,
|
|
513
|
-
details: {
|
|
514
|
-
effectiveSandboxMode,
|
|
515
|
-
sessionToolsVisibility: sandboxSessionToolsVisibility,
|
|
516
|
-
requiredSessionToolsVisibility: 'all',
|
|
517
|
-
},
|
|
518
|
-
}));
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
checks.push(createCheck({
|
|
522
|
-
id: 'app-token',
|
|
523
|
-
category: 'Credentials and runtime',
|
|
524
|
-
label: 'Managed appToken',
|
|
525
|
-
status: configuredAccount.tokenStatus === 'available' ? 'pass' : 'warn',
|
|
526
|
-
summary: configuredAccount.tokenStatus === 'available'
|
|
527
|
-
? 'Managed account has an available appToken.'
|
|
528
|
-
: `Managed account token status is ${configuredAccount.tokenStatus}; activation is still pending.`,
|
|
529
|
-
action: configuredAccount.tokenStatus === 'available'
|
|
530
|
-
? null
|
|
531
|
-
: 'Open a live OpenClaw session, run `claworld_account` with `action=view`, and then complete `claworld_account` with `action=update_identity` when prompted.',
|
|
532
|
-
details: { tokenSource: configuredAccount.tokenSource, tokenStatus: configuredAccount.tokenStatus },
|
|
533
|
-
}));
|
|
534
|
-
|
|
535
|
-
try {
|
|
536
|
-
facts.manifest = await fetchInstallManifest({
|
|
537
|
-
serverUrl: effectiveServerUrl,
|
|
538
|
-
apiKey: configuredApiKey,
|
|
539
|
-
fetchImpl,
|
|
540
|
-
});
|
|
541
|
-
checks.push(createCheck({
|
|
542
|
-
id: 'backend-reachable',
|
|
543
|
-
category: 'Credentials and runtime',
|
|
544
|
-
label: 'Claworld backend reachability',
|
|
545
|
-
status: 'pass',
|
|
546
|
-
summary: `Reached the Claworld install contract at ${effectiveServerUrl}.`,
|
|
547
|
-
details: { serverUrl: effectiveServerUrl, mode: facts.manifest.mode || null },
|
|
548
|
-
}));
|
|
549
|
-
} catch (error) {
|
|
550
|
-
checks.push(createCheck({
|
|
551
|
-
id: 'backend-reachable',
|
|
552
|
-
category: 'Credentials and runtime',
|
|
553
|
-
label: 'Claworld backend reachability',
|
|
554
|
-
status: 'warn',
|
|
555
|
-
summary: `Unable to reach the Claworld backend at ${effectiveServerUrl}.`,
|
|
556
|
-
action: 'Restore backend reachability before pairing or live Claworld use, then rerun doctor.',
|
|
557
|
-
details: { serverUrl: effectiveServerUrl, message: error?.message || String(error) },
|
|
558
|
-
}));
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
const backendReachable = checks.find((check) => check.id === 'backend-reachable')?.status === 'pass';
|
|
562
|
-
if (configuredAccount.appToken && backendReachable) {
|
|
563
|
-
try {
|
|
564
|
-
facts.activation = await activateInstall({
|
|
565
|
-
serverUrl: effectiveServerUrl,
|
|
566
|
-
apiKey: configuredApiKey,
|
|
567
|
-
appToken: configuredAccount.appToken,
|
|
568
|
-
fetchImpl,
|
|
569
|
-
});
|
|
570
|
-
checks.push(createCheck({
|
|
571
|
-
id: 'stable-agent-binding',
|
|
572
|
-
category: 'Credentials and runtime',
|
|
573
|
-
label: 'Stable relay binding',
|
|
574
|
-
status: 'pass',
|
|
575
|
-
summary: `Backend activation resolved a stable managed agentId: \`${facts.activation.agentId}\`.`,
|
|
576
|
-
details: { agentId: facts.activation.agentId, created: facts.activation.created === true },
|
|
577
|
-
}));
|
|
578
|
-
} catch (error) {
|
|
579
|
-
checks.push(createCheck({
|
|
580
|
-
id: 'stable-agent-binding',
|
|
581
|
-
category: 'Credentials and runtime',
|
|
582
|
-
label: 'Stable relay binding',
|
|
583
|
-
status: 'fail',
|
|
584
|
-
summary: 'Managed appToken could not be resolved into a healthy backend binding.',
|
|
585
|
-
action: 'Run `claworld_account` with `action=view` in a live OpenClaw session to refresh the managed relay binding.',
|
|
586
|
-
details: { message: error?.message || String(error) },
|
|
587
|
-
}));
|
|
588
|
-
}
|
|
589
|
-
} else if (!configuredAccount.appToken) {
|
|
590
|
-
checks.push(createCheck({
|
|
591
|
-
id: 'stable-agent-binding',
|
|
592
|
-
category: 'Credentials and runtime',
|
|
593
|
-
label: 'Stable relay binding',
|
|
594
|
-
status: 'warn',
|
|
595
|
-
summary: 'Doctor could not verify the managed relay binding because activation is still pending.',
|
|
596
|
-
action: 'Open a live OpenClaw session, run `claworld_account` with `action=view`, and then complete `claworld_account` with `action=update_identity` when prompted.',
|
|
597
|
-
}));
|
|
598
|
-
} else {
|
|
599
|
-
checks.push(createCheck({
|
|
600
|
-
id: 'stable-agent-binding',
|
|
601
|
-
category: 'Credentials and runtime',
|
|
602
|
-
label: 'Stable relay binding',
|
|
603
|
-
status: 'warn',
|
|
604
|
-
summary: 'Doctor could not verify the managed relay binding because the backend was not reachable.',
|
|
605
|
-
action: 'Restore backend reachability, then rerun doctor to verify the managed relay binding.',
|
|
606
|
-
}));
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
facts.gatewayStatus = await readGatewayStatus({
|
|
611
|
-
openclawBin,
|
|
612
|
-
configPath: resolvedConfigPath,
|
|
613
|
-
stateDir: resolvedStateDir,
|
|
614
|
-
commandRunner,
|
|
615
|
-
cwd,
|
|
616
|
-
env,
|
|
617
|
-
dryRun,
|
|
618
|
-
});
|
|
619
|
-
const gatewayRunning = facts.gatewayStatus?.service?.runtime?.status === 'running';
|
|
620
|
-
checks.push(createCheck({
|
|
621
|
-
id: 'gateway-runtime',
|
|
622
|
-
category: 'Credentials and runtime',
|
|
623
|
-
label: 'OpenClaw gateway runtime',
|
|
624
|
-
status: gatewayRunning ? 'pass' : 'fail',
|
|
625
|
-
summary: gatewayRunning
|
|
626
|
-
? 'OpenClaw gateway runtime is running.'
|
|
627
|
-
: `OpenClaw gateway runtime reports ${facts.gatewayStatus?.service?.runtime?.status || 'unknown'}.`,
|
|
628
|
-
action: gatewayRunning ? null : 'Start or restart the OpenClaw gateway, then rerun doctor.',
|
|
629
|
-
details: { gateway: facts.gatewayStatus.gateway || null, service: facts.gatewayStatus.service || null },
|
|
630
|
-
}));
|
|
631
|
-
} catch (error) {
|
|
632
|
-
checks.push(createCheck({
|
|
633
|
-
id: 'gateway-runtime',
|
|
634
|
-
category: 'Credentials and runtime',
|
|
635
|
-
label: 'OpenClaw gateway runtime',
|
|
636
|
-
status: 'fail',
|
|
637
|
-
summary: 'Unable to read OpenClaw gateway status.',
|
|
638
|
-
action: 'Confirm the host can run `openclaw gateway status --json --no-probe`, then rerun doctor.',
|
|
639
|
-
details: { message: error?.message || String(error) },
|
|
640
|
-
}));
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
try {
|
|
644
|
-
facts.channelStatus = await readChannelStatus({
|
|
645
|
-
openclawBin,
|
|
646
|
-
configPath: resolvedConfigPath,
|
|
647
|
-
stateDir: resolvedStateDir,
|
|
648
|
-
commandRunner,
|
|
649
|
-
cwd,
|
|
650
|
-
env,
|
|
651
|
-
dryRun,
|
|
652
|
-
});
|
|
653
|
-
const channelAccount = findChannelAccount(facts.channelStatus, configuredAccount.accountId);
|
|
654
|
-
const channelHealthy = Boolean(
|
|
655
|
-
channelAccount
|
|
656
|
-
&& channelAccount.configured === true
|
|
657
|
-
&& channelAccount.enabled !== false
|
|
658
|
-
&& channelAccount.tokenStatus === 'available'
|
|
659
|
-
);
|
|
660
|
-
const channelPendingActivation = Boolean(
|
|
661
|
-
channelAccount
|
|
662
|
-
&& channelAccount.configured === true
|
|
663
|
-
&& channelAccount.enabled !== false
|
|
664
|
-
&& channelAccount.tokenStatus !== 'available'
|
|
665
|
-
);
|
|
666
|
-
checks.push(createCheck({
|
|
667
|
-
id: 'channel-runtime',
|
|
668
|
-
category: 'Credentials and runtime',
|
|
669
|
-
label: 'Claworld channel runtime account',
|
|
670
|
-
status: channelHealthy ? 'pass' : channelPendingActivation ? 'warn' : 'fail',
|
|
671
|
-
summary: channelHealthy
|
|
672
|
-
? `channels status reports managed account \`${configuredAccount.accountId}\` as configured with an available token.`
|
|
673
|
-
: channelPendingActivation
|
|
674
|
-
? `channels status reports managed account \`${configuredAccount.accountId}\` as configured, but activation is still pending (${channelAccount.tokenStatus}).`
|
|
675
|
-
: `channels status does not report a healthy managed account for \`${configuredAccount.accountId}\`.`,
|
|
676
|
-
action: channelHealthy
|
|
677
|
-
? null
|
|
678
|
-
: channelPendingActivation
|
|
679
|
-
? 'Open a live OpenClaw session, run `claworld_account` with `action=view`, and then complete `claworld_account` with `action=update_identity` when prompted.'
|
|
680
|
-
: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` or restart the gateway to recover the managed account runtime.`,
|
|
681
|
-
details: { channelAccount },
|
|
682
|
-
}));
|
|
683
|
-
} catch (error) {
|
|
684
|
-
checks.push(createCheck({
|
|
685
|
-
id: 'channel-runtime',
|
|
686
|
-
category: 'Credentials and runtime',
|
|
687
|
-
label: 'Claworld channel runtime account',
|
|
688
|
-
status: 'fail',
|
|
689
|
-
summary: 'Unable to read `openclaw channels status --json` for the Claworld account.',
|
|
690
|
-
action: 'Confirm the gateway can load channel metadata, then rerun doctor.',
|
|
691
|
-
details: { message: error?.message || String(error) },
|
|
692
|
-
}));
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
try {
|
|
696
|
-
facts.bindingOutput = await readAgentBindings({
|
|
697
|
-
openclawBin,
|
|
698
|
-
configPath: resolvedConfigPath,
|
|
699
|
-
stateDir: resolvedStateDir,
|
|
700
|
-
commandRunner,
|
|
701
|
-
cwd,
|
|
702
|
-
env,
|
|
703
|
-
dryRun,
|
|
704
|
-
});
|
|
705
|
-
const bindingLine = String(`${facts.bindingOutput.stdout || ''}${facts.bindingOutput.stderr || ''}`)
|
|
706
|
-
.split('\n')
|
|
707
|
-
.map((line) => line.trim())
|
|
708
|
-
.find((line) => line === `- ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}`) || null;
|
|
709
|
-
checks.push(createCheck({
|
|
710
|
-
id: 'runtime-binding',
|
|
711
|
-
category: 'Credentials and runtime',
|
|
712
|
-
label: 'Runtime binding visibility',
|
|
713
|
-
status: bindingLine ? 'pass' : 'fail',
|
|
714
|
-
summary: bindingLine
|
|
715
|
-
? `OpenClaw agents bindings exposes ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`
|
|
716
|
-
: `OpenClaw agents bindings does not show ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`,
|
|
717
|
-
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to restore the managed binding.`,
|
|
718
|
-
details: { bindingLine, output: facts.bindingOutput.stdout || facts.bindingOutput.stderr || null },
|
|
719
|
-
}));
|
|
720
|
-
} catch (error) {
|
|
721
|
-
checks.push(createCheck({
|
|
722
|
-
id: 'runtime-binding',
|
|
723
|
-
category: 'Credentials and runtime',
|
|
724
|
-
label: 'Runtime binding visibility',
|
|
725
|
-
status: 'fail',
|
|
726
|
-
summary: 'Unable to read `openclaw agents bindings`.',
|
|
727
|
-
action: 'Confirm the OpenClaw host can inspect bindings, then rerun doctor.',
|
|
728
|
-
details: { message: error?.message || String(error) },
|
|
729
|
-
}));
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
if (facts.gatewayStatus?.service?.runtime?.status === 'running') {
|
|
733
|
-
const routeTarget = describePluginStatusRouteTarget({
|
|
734
|
-
config,
|
|
735
|
-
gatewayStatus: facts.gatewayStatus,
|
|
736
|
-
});
|
|
737
|
-
const gatewayBaseUrl = routeTarget.runtimeBaseUrl;
|
|
738
|
-
facts.pluginStatusRoute = await fetchPluginStatusRoute({
|
|
739
|
-
gatewayBaseUrl,
|
|
740
|
-
fetchImpl,
|
|
741
|
-
});
|
|
742
|
-
const routeStatus = facts.pluginStatusRoute.status;
|
|
743
|
-
const routeReachable = facts.pluginStatusRoute.ok || routeStatus === 401 || routeStatus === 403;
|
|
744
|
-
let status = 'pass';
|
|
745
|
-
let summary = `Reached ${facts.pluginStatusRoute.routeUrl}.`;
|
|
746
|
-
let action = null;
|
|
747
|
-
if (routeTarget.baseUrlMismatch) {
|
|
748
|
-
status = 'warn';
|
|
749
|
-
const runtimeTarget = routeTarget.runtimeBaseUrl || 'the running gateway target';
|
|
750
|
-
const configuredTarget = routeTarget.configuredBaseUrl || 'the inspected config target';
|
|
751
|
-
const responseSummary = routeStatus == null
|
|
752
|
-
? facts.pluginStatusRoute.error || 'no response'
|
|
753
|
-
: `HTTP ${routeStatus}`;
|
|
754
|
-
summary = [
|
|
755
|
-
`OpenClaw gateway status resolved ${runtimeTarget}, but the inspected config declares ${configuredTarget}.`,
|
|
756
|
-
`Treating \`${facts.pluginStatusRoute.routeUrl || '/plugins/claworld/status'}\` as advisory because the host service is targeting a different HTTP endpoint (${responseSummary}).`,
|
|
757
|
-
].join(' ');
|
|
758
|
-
action = 'Align the host gateway service target with the inspected config if you need live plugin-route proof for this config.';
|
|
759
|
-
} else if (!routeReachable) {
|
|
760
|
-
status = 'fail';
|
|
761
|
-
summary = `Unable to reach ${facts.pluginStatusRoute.routeUrl || 'the plugin status route'}.`;
|
|
762
|
-
action = 'Confirm the local OpenClaw gateway HTTP surface is reachable, then rerun doctor.';
|
|
763
|
-
} else if (!facts.pluginStatusRoute.ok) {
|
|
764
|
-
status = 'warn';
|
|
765
|
-
summary = `${facts.pluginStatusRoute.routeUrl} responded with ${routeStatus}, which proves the route exists but still requires gateway auth.`;
|
|
766
|
-
action = 'Provide gateway auth credentials if you need the live plugin status payload.';
|
|
767
|
-
}
|
|
768
|
-
checks.push(createCheck({
|
|
769
|
-
id: 'plugin-status-route',
|
|
770
|
-
category: 'Credentials and runtime',
|
|
771
|
-
label: 'Plugin status HTTP route',
|
|
772
|
-
status,
|
|
773
|
-
summary,
|
|
774
|
-
action,
|
|
775
|
-
details: {
|
|
776
|
-
...facts.pluginStatusRoute,
|
|
777
|
-
configuredBaseUrl: routeTarget.configuredBaseUrl,
|
|
778
|
-
runtimeBaseUrl: routeTarget.runtimeBaseUrl,
|
|
779
|
-
baseUrlMismatch: routeTarget.baseUrlMismatch,
|
|
780
|
-
runtimePortSource: routeTarget.runtimePortSource,
|
|
781
|
-
},
|
|
782
|
-
}));
|
|
783
|
-
} else {
|
|
784
|
-
checks.push(createCheck({
|
|
785
|
-
id: 'plugin-status-route',
|
|
786
|
-
category: 'Credentials and runtime',
|
|
787
|
-
label: 'Plugin status HTTP route',
|
|
788
|
-
status: 'warn',
|
|
789
|
-
summary: 'Skipped the plugin status route check because the OpenClaw gateway runtime is not running.',
|
|
790
|
-
action: 'Start the gateway, then rerun doctor to verify `/plugins/claworld/status`.',
|
|
791
|
-
}));
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const workspaceWorstStatus = workspaceInspection.issues.reduce((current, issue) =>
|
|
795
|
-
statusRank(issue.severity) > statusRank(current) ? issue.severity : current, 'pass');
|
|
796
|
-
checks.push(createCheck({
|
|
797
|
-
id: 'workspace-contract',
|
|
798
|
-
category: 'Workspace',
|
|
799
|
-
label: 'Managed workspace contract',
|
|
800
|
-
status: managedOptions.manageWorkspace ? workspaceWorstStatus : 'pass',
|
|
801
|
-
summary: managedOptions.manageWorkspace
|
|
802
|
-
? (
|
|
803
|
-
workspaceInspection.ok
|
|
804
|
-
? `Managed workspace is present with ${formatWorkspaceState(workspaceInspection)}.`
|
|
805
|
-
: `Managed workspace needs repair: ${workspaceInspection.issues.map((issue) => issue.code).join(', ')}.`
|
|
806
|
-
)
|
|
807
|
-
: `Claworld is attached to existing local agent \`${resolvedAgentId}\`; no dedicated managed workspace is expected.`,
|
|
808
|
-
action: managedOptions.manageWorkspace && !workspaceInspection.ok
|
|
809
|
-
? `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to repair the managed workspace bootstrap.`
|
|
810
|
-
: null,
|
|
811
|
-
details: {
|
|
812
|
-
workspacePath: workspaceInspection.workspacePath,
|
|
813
|
-
metadataPath: workspaceInspection.metadataPath,
|
|
814
|
-
files: workspaceInspection.files.map((file) => ({
|
|
815
|
-
relativePath: file.relativePath,
|
|
816
|
-
policy: file.policy,
|
|
817
|
-
state: file.state,
|
|
818
|
-
exists: file.exists,
|
|
819
|
-
})),
|
|
820
|
-
issues: workspaceInspection.issues,
|
|
821
|
-
manageWorkspace: managedOptions.manageWorkspace,
|
|
822
|
-
},
|
|
823
|
-
}));
|
|
824
|
-
|
|
825
|
-
const status = overallStatus(checks);
|
|
826
|
-
return {
|
|
827
|
-
ok: status !== 'fail',
|
|
828
|
-
status,
|
|
829
|
-
command: CLAWORLD_DOCTOR_COMMAND,
|
|
830
|
-
configPath: resolvedConfigPath,
|
|
831
|
-
stateDir: resolvedStateDir,
|
|
832
|
-
accountId: configuredAccount.accountId || normalizeText(accountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
833
|
-
agentId: resolvedAgentId,
|
|
834
|
-
workspacePath: workspaceInspection.workspacePath,
|
|
835
|
-
checks,
|
|
836
|
-
facts,
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
export function formatClaworldDoctorReport(result = {}) {
|
|
841
|
-
const checks = Array.isArray(result.checks) ? result.checks : [];
|
|
842
|
-
const categoryOrder = [
|
|
843
|
-
'Host and plugin',
|
|
844
|
-
'Managed config',
|
|
845
|
-
'Credentials and runtime',
|
|
846
|
-
'Workspace',
|
|
847
|
-
];
|
|
848
|
-
const lines = [
|
|
849
|
-
`Claworld doctor: ${String(result.status || 'pass').toUpperCase()}`,
|
|
850
|
-
`Command: ${CLAWORLD_DOCTOR_COMMAND}`,
|
|
851
|
-
`Config: ${result.configPath || '(unknown)'}`,
|
|
852
|
-
`Managed account: ${result.accountId || '(unknown)'}`,
|
|
853
|
-
`Bound local agent: ${result.agentId || '(unknown)'}`,
|
|
854
|
-
`Managed workspace: ${result.workspacePath || '(not used)'}`,
|
|
855
|
-
'',
|
|
856
|
-
];
|
|
857
|
-
|
|
858
|
-
for (const category of categoryOrder) {
|
|
859
|
-
const categoryChecks = checks.filter((check) => check.category === category);
|
|
860
|
-
if (categoryChecks.length === 0) continue;
|
|
861
|
-
lines.push(`${category}:`);
|
|
862
|
-
for (const check of categoryChecks) {
|
|
863
|
-
lines.push(`${formatStatus(check.status)} ${check.label}: ${check.summary}`);
|
|
864
|
-
if (check.action) {
|
|
865
|
-
lines.push(` Fix: ${check.action}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
lines.push('');
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (checks.length === 0) {
|
|
872
|
-
lines.push('No checks were collected.');
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return `${lines.join('\n').trim()}\n`;
|
|
876
|
-
}
|