@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,690 @@
|
|
|
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_SERVER_URL,
|
|
7
|
+
expandUserPath,
|
|
8
|
+
normalizeText,
|
|
9
|
+
} from '../plugin/managed-config.js';
|
|
10
|
+
import {
|
|
11
|
+
CLAWORLD_DOCTOR_COMMAND,
|
|
12
|
+
CLAWORLD_INSTALLER_COMMAND,
|
|
13
|
+
CLAWORLD_OPENCLAW_MIN_HOST_VERSION,
|
|
14
|
+
} from './constants.js';
|
|
15
|
+
import {
|
|
16
|
+
activateInstall,
|
|
17
|
+
compareVersionParts,
|
|
18
|
+
DEFAULT_OPENCLAW_BIN,
|
|
19
|
+
DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
20
|
+
DEFAULT_OPENCLAW_STATE_DIR,
|
|
21
|
+
detectOpenclawHost,
|
|
22
|
+
fetchInstallManifest,
|
|
23
|
+
inspectClaworldPluginInstall,
|
|
24
|
+
loadConfigFromDisk,
|
|
25
|
+
readAgentBindings,
|
|
26
|
+
readChannelStatus,
|
|
27
|
+
readGatewayStatus,
|
|
28
|
+
} from './core.js';
|
|
29
|
+
import { inspectManagedWorkspaceContract } from './workspace-contract.js';
|
|
30
|
+
|
|
31
|
+
function createCheck({
|
|
32
|
+
id,
|
|
33
|
+
category,
|
|
34
|
+
label,
|
|
35
|
+
status,
|
|
36
|
+
summary,
|
|
37
|
+
action = null,
|
|
38
|
+
details = {},
|
|
39
|
+
} = {}) {
|
|
40
|
+
return {
|
|
41
|
+
id,
|
|
42
|
+
category,
|
|
43
|
+
label,
|
|
44
|
+
status,
|
|
45
|
+
summary,
|
|
46
|
+
action,
|
|
47
|
+
details,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function statusRank(status) {
|
|
52
|
+
if (status === 'fail') return 3;
|
|
53
|
+
if (status === 'warn') return 2;
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function overallStatus(checks = []) {
|
|
58
|
+
if (checks.some((check) => check.status === 'fail')) return 'fail';
|
|
59
|
+
if (checks.some((check) => check.status === 'warn')) return 'warn';
|
|
60
|
+
return 'pass';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatStatus(status) {
|
|
64
|
+
return `[${String(status || 'pass').toUpperCase()}]`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findManagedAgentEntry(config = {}, agentId) {
|
|
68
|
+
const list = Array.isArray(config?.agents?.list) ? config.agents.list : [];
|
|
69
|
+
return list.find((entry) => entry?.id === agentId) || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasConfiguredBinding(config = {}, { agentId, accountId } = {}) {
|
|
73
|
+
const bindings = Array.isArray(config?.bindings) ? config.bindings : [];
|
|
74
|
+
return bindings.some((binding) =>
|
|
75
|
+
binding?.agentId === agentId
|
|
76
|
+
&& binding?.match?.channel === 'claworld'
|
|
77
|
+
&& binding?.match?.accountId === accountId,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function findChannelAccount(channelStatus, accountId) {
|
|
82
|
+
const entries = Array.isArray(channelStatus?.channelAccounts?.claworld)
|
|
83
|
+
? channelStatus.channelAccounts.claworld
|
|
84
|
+
: [];
|
|
85
|
+
return entries.find((entry) => entry?.accountId === accountId) || null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeGatewayBaseUrl(gatewayStatus) {
|
|
89
|
+
const probeUrl = normalizeText(gatewayStatus?.gateway?.probeUrl, null);
|
|
90
|
+
if (probeUrl) {
|
|
91
|
+
const parsed = new URL(probeUrl);
|
|
92
|
+
parsed.protocol = parsed.protocol === 'wss:' ? 'https:' : 'http:';
|
|
93
|
+
parsed.pathname = '';
|
|
94
|
+
parsed.search = '';
|
|
95
|
+
parsed.hash = '';
|
|
96
|
+
return parsed.toString().replace(/\/$/, '');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const bindHost = normalizeText(gatewayStatus?.gateway?.bindHost, null);
|
|
100
|
+
const port = gatewayStatus?.gateway?.port;
|
|
101
|
+
if (!bindHost || !port) return null;
|
|
102
|
+
const host = bindHost === '0.0.0.0' ? '127.0.0.1' : bindHost;
|
|
103
|
+
return `http://${host}:${port}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function fetchPluginStatusRoute({
|
|
107
|
+
gatewayBaseUrl,
|
|
108
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
109
|
+
} = {}) {
|
|
110
|
+
if (!gatewayBaseUrl || typeof fetchImpl !== 'function') {
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
status: null,
|
|
114
|
+
body: null,
|
|
115
|
+
routeUrl: gatewayBaseUrl ? `${gatewayBaseUrl}/plugins/claworld/status` : null,
|
|
116
|
+
error: gatewayBaseUrl ? 'missing_fetch' : 'missing_gateway_base_url',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const routeUrl = `${gatewayBaseUrl}/plugins/claworld/status`;
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetchImpl(routeUrl, {
|
|
123
|
+
method: 'GET',
|
|
124
|
+
headers: {
|
|
125
|
+
accept: 'application/json',
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
const text = await response.text();
|
|
129
|
+
let body = null;
|
|
130
|
+
try {
|
|
131
|
+
body = text ? JSON.parse(text) : null;
|
|
132
|
+
} catch {
|
|
133
|
+
body = null;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
ok: response.ok,
|
|
137
|
+
status: response.status,
|
|
138
|
+
body,
|
|
139
|
+
routeUrl,
|
|
140
|
+
error: null,
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
ok: false,
|
|
145
|
+
status: null,
|
|
146
|
+
body: null,
|
|
147
|
+
routeUrl,
|
|
148
|
+
error: error?.message || String(error),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatWorkspaceState(workspaceInspection) {
|
|
154
|
+
return workspaceInspection.files.map((file) => `${file.relativePath}=${file.state}`).join(', ');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function runClaworldDoctor({
|
|
158
|
+
openclawBin = DEFAULT_OPENCLAW_BIN,
|
|
159
|
+
configPath = DEFAULT_OPENCLAW_CONFIG_PATH,
|
|
160
|
+
stateDir = DEFAULT_OPENCLAW_STATE_DIR,
|
|
161
|
+
accountId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
162
|
+
agentId = DEFAULT_CLAWORLD_ACCOUNT_ID,
|
|
163
|
+
workspace = null,
|
|
164
|
+
serverUrl = null,
|
|
165
|
+
fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
166
|
+
commandRunner,
|
|
167
|
+
cwd = process.cwd(),
|
|
168
|
+
env = process.env,
|
|
169
|
+
dryRun = false,
|
|
170
|
+
} = {}) {
|
|
171
|
+
const resolvedConfigPath = path.resolve(expandUserPath(configPath, os.homedir()));
|
|
172
|
+
const resolvedStateDir = stateDir ? path.resolve(expandUserPath(stateDir, os.homedir())) : null;
|
|
173
|
+
const configState = await loadConfigFromDisk(resolvedConfigPath);
|
|
174
|
+
const config = configState.config;
|
|
175
|
+
const configuredAccount = inspectClaworldChannelAccount(config, accountId);
|
|
176
|
+
const rawManagedAccount = config?.channels?.claworld?.accounts?.[configuredAccount.accountId] || null;
|
|
177
|
+
const configuredApiKey = normalizeText(rawManagedAccount?.apiKey, 'local-test');
|
|
178
|
+
const resolvedAgentId = normalizeText(agentId, normalizeText(configuredAccount.accountId, DEFAULT_CLAWORLD_ACCOUNT_ID));
|
|
179
|
+
const resolvedWorkspace = normalizeText(
|
|
180
|
+
workspace,
|
|
181
|
+
normalizeText(findManagedAgentEntry(config, resolvedAgentId)?.workspace, `~/.openclaw/workspace-${resolvedAgentId}`),
|
|
182
|
+
);
|
|
183
|
+
const effectiveServerUrl = normalizeText(
|
|
184
|
+
serverUrl,
|
|
185
|
+
normalizeText(configuredAccount.serverUrl, DEFAULT_CLAWORLD_SERVER_URL),
|
|
186
|
+
);
|
|
187
|
+
const workspaceInspection = await inspectManagedWorkspaceContract({
|
|
188
|
+
agentId: resolvedAgentId,
|
|
189
|
+
accountId: normalizeText(accountId, configuredAccount.accountId || DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
190
|
+
workspace: resolvedWorkspace,
|
|
191
|
+
serverUrl: effectiveServerUrl,
|
|
192
|
+
appToken: configuredAccount.appToken,
|
|
193
|
+
registrationAgentCode: configuredAccount.registration?.agentCode || null,
|
|
194
|
+
defaultToAddress: configuredAccount.relay?.defaultToAddress || null,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const checks = [];
|
|
198
|
+
const facts = {
|
|
199
|
+
host: null,
|
|
200
|
+
plugin: null,
|
|
201
|
+
manifest: null,
|
|
202
|
+
activation: null,
|
|
203
|
+
gatewayStatus: null,
|
|
204
|
+
channelStatus: null,
|
|
205
|
+
bindingOutput: null,
|
|
206
|
+
pluginStatusRoute: null,
|
|
207
|
+
workspaceInspection,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const host = await detectOpenclawHost({
|
|
212
|
+
openclawBin,
|
|
213
|
+
commandRunner,
|
|
214
|
+
cwd,
|
|
215
|
+
env,
|
|
216
|
+
dryRun,
|
|
217
|
+
});
|
|
218
|
+
facts.host = host;
|
|
219
|
+
if (compareVersionParts(host.version, CLAWORLD_OPENCLAW_MIN_HOST_VERSION) < 0) {
|
|
220
|
+
checks.push(createCheck({
|
|
221
|
+
id: 'host-version',
|
|
222
|
+
category: 'Host and plugin',
|
|
223
|
+
label: 'OpenClaw host version',
|
|
224
|
+
status: 'fail',
|
|
225
|
+
summary: `OpenClaw ${host.version} is below the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
226
|
+
action: `Upgrade OpenClaw, then rerun \`${CLAWORLD_DOCTOR_COMMAND}\`.`,
|
|
227
|
+
details: { version: host.version, minimum: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
228
|
+
}));
|
|
229
|
+
} else {
|
|
230
|
+
checks.push(createCheck({
|
|
231
|
+
id: 'host-version',
|
|
232
|
+
category: 'Host and plugin',
|
|
233
|
+
label: 'OpenClaw host version',
|
|
234
|
+
status: 'pass',
|
|
235
|
+
summary: `OpenClaw ${host.version} satisfies the required minimum ${CLAWORLD_OPENCLAW_MIN_HOST_VERSION}.`,
|
|
236
|
+
details: { version: host.version, minimum: CLAWORLD_OPENCLAW_MIN_HOST_VERSION },
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
checks.push(createCheck({
|
|
241
|
+
id: 'host-version',
|
|
242
|
+
category: 'Host and plugin',
|
|
243
|
+
label: 'OpenClaw host version',
|
|
244
|
+
status: 'fail',
|
|
245
|
+
summary: 'Unable to determine the installed OpenClaw version.',
|
|
246
|
+
action: 'Install OpenClaw or fix the host CLI path, then rerun the doctor command.',
|
|
247
|
+
details: { message: error?.message || String(error) },
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
facts.plugin = await inspectClaworldPluginInstall({
|
|
253
|
+
openclawBin,
|
|
254
|
+
configPath: resolvedConfigPath,
|
|
255
|
+
stateDir: resolvedStateDir,
|
|
256
|
+
commandRunner,
|
|
257
|
+
cwd,
|
|
258
|
+
env,
|
|
259
|
+
dryRun,
|
|
260
|
+
});
|
|
261
|
+
if (!facts.plugin.installed) {
|
|
262
|
+
checks.push(createCheck({
|
|
263
|
+
id: 'plugin-install',
|
|
264
|
+
category: 'Host and plugin',
|
|
265
|
+
label: 'Claworld plugin install',
|
|
266
|
+
status: 'fail',
|
|
267
|
+
summary: 'The `claworld` plugin is not installed or not discoverable.',
|
|
268
|
+
action: `Run \`${CLAWORLD_INSTALLER_COMMAND}\` to install and configure the managed Claworld runtime.`,
|
|
269
|
+
}));
|
|
270
|
+
} else {
|
|
271
|
+
checks.push(createCheck({
|
|
272
|
+
id: 'plugin-install',
|
|
273
|
+
category: 'Host and plugin',
|
|
274
|
+
label: 'Claworld plugin install',
|
|
275
|
+
status: 'pass',
|
|
276
|
+
summary: `The \`claworld\` plugin is installed (${facts.plugin.install || 'install type unavailable'}).`,
|
|
277
|
+
details: { version: facts.plugin.version || null, sourcePath: facts.plugin.sourcePath || null },
|
|
278
|
+
}));
|
|
279
|
+
checks.push(createCheck({
|
|
280
|
+
id: 'plugin-load',
|
|
281
|
+
category: 'Host and plugin',
|
|
282
|
+
label: 'Claworld plugin load',
|
|
283
|
+
status: facts.plugin.loaded ? 'pass' : 'fail',
|
|
284
|
+
summary: facts.plugin.loaded
|
|
285
|
+
? 'The `claworld` plugin reports a healthy loaded state.'
|
|
286
|
+
: `The \`claworld\` plugin is installed but reports status ${facts.plugin.status || 'unknown'}.`,
|
|
287
|
+
action: facts.plugin.loaded
|
|
288
|
+
? null
|
|
289
|
+
: 'Inspect `openclaw plugins info claworld` and fix the plugin load error before retrying.',
|
|
290
|
+
details: { status: facts.plugin.status || null, raw: facts.plugin.raw || null },
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
checks.push(createCheck({
|
|
295
|
+
id: 'plugin-install',
|
|
296
|
+
category: 'Host and plugin',
|
|
297
|
+
label: 'Claworld plugin install',
|
|
298
|
+
status: 'fail',
|
|
299
|
+
summary: 'Unable to inspect the `claworld` plugin install state.',
|
|
300
|
+
action: 'Confirm the host can run `openclaw plugins info claworld`, then rerun doctor.',
|
|
301
|
+
details: { message: error?.message || String(error) },
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const agentEntry = findManagedAgentEntry(config, resolvedAgentId);
|
|
306
|
+
const agentWorkspace = normalizeText(agentEntry?.workspace, null);
|
|
307
|
+
const bindingConfigured = hasConfiguredBinding(config, {
|
|
308
|
+
agentId: resolvedAgentId,
|
|
309
|
+
accountId: normalizeText(accountId, configuredAccount.accountId || DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
310
|
+
});
|
|
311
|
+
const defaultAccount = normalizeText(config?.channels?.claworld?.defaultAccount, null);
|
|
312
|
+
|
|
313
|
+
checks.push(createCheck({
|
|
314
|
+
id: 'managed-agent',
|
|
315
|
+
category: 'Managed config',
|
|
316
|
+
label: 'Managed local agent entry',
|
|
317
|
+
status: agentEntry && agentWorkspace === resolvedWorkspace ? 'pass' : 'fail',
|
|
318
|
+
summary: agentEntry && agentWorkspace === resolvedWorkspace
|
|
319
|
+
? `Managed agent \`${resolvedAgentId}\` points at \`${resolvedWorkspace}\`.`
|
|
320
|
+
: agentEntry
|
|
321
|
+
? `Managed agent \`${resolvedAgentId}\` exists but points at \`${agentWorkspace || 'missing workspace'}\`.`
|
|
322
|
+
: `Managed agent \`${resolvedAgentId}\` is missing from agents.list.`,
|
|
323
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to reconcile the managed agent entry.`,
|
|
324
|
+
details: { expectedWorkspace: resolvedWorkspace, actualWorkspace: agentWorkspace },
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
checks.push(createCheck({
|
|
328
|
+
id: 'managed-account',
|
|
329
|
+
category: 'Managed config',
|
|
330
|
+
label: 'Managed claworld account',
|
|
331
|
+
status: configuredAccount.configured && configuredAccount.enabled !== false ? 'pass' : 'fail',
|
|
332
|
+
summary: configuredAccount.configured && configuredAccount.enabled !== false
|
|
333
|
+
? `Managed account \`${configuredAccount.accountId}\` is configured and enabled.`
|
|
334
|
+
: `Managed account \`${configuredAccount.accountId}\` is missing required config or disabled.`,
|
|
335
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to repair the managed account config.`,
|
|
336
|
+
details: {
|
|
337
|
+
configuredStatus: configuredAccount.configuredStatus,
|
|
338
|
+
enabled: configuredAccount.enabled,
|
|
339
|
+
issues: configuredAccount.issues || [],
|
|
340
|
+
},
|
|
341
|
+
}));
|
|
342
|
+
|
|
343
|
+
checks.push(createCheck({
|
|
344
|
+
id: 'default-account',
|
|
345
|
+
category: 'Managed config',
|
|
346
|
+
label: 'Default claworld account',
|
|
347
|
+
status: defaultAccount === configuredAccount.accountId ? 'pass' : 'fail',
|
|
348
|
+
summary: defaultAccount === configuredAccount.accountId
|
|
349
|
+
? `channels.claworld.defaultAccount is correctly set to \`${configuredAccount.accountId}\`.`
|
|
350
|
+
: `channels.claworld.defaultAccount is \`${defaultAccount || 'missing'}\`, expected \`${configuredAccount.accountId}\`.`,
|
|
351
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to reset the managed default account.`,
|
|
352
|
+
details: { expectedDefaultAccount: configuredAccount.accountId, actualDefaultAccount: defaultAccount },
|
|
353
|
+
}));
|
|
354
|
+
|
|
355
|
+
checks.push(createCheck({
|
|
356
|
+
id: 'managed-binding',
|
|
357
|
+
category: 'Managed config',
|
|
358
|
+
label: 'Managed claworld binding',
|
|
359
|
+
status: bindingConfigured ? 'pass' : 'fail',
|
|
360
|
+
summary: bindingConfigured
|
|
361
|
+
? `Config includes the managed binding ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`
|
|
362
|
+
: `Config is missing the managed binding ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`,
|
|
363
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to restore the managed binding.`,
|
|
364
|
+
}));
|
|
365
|
+
|
|
366
|
+
checks.push(createCheck({
|
|
367
|
+
id: 'managed-config-shape',
|
|
368
|
+
category: 'Managed config',
|
|
369
|
+
label: 'Managed config shape',
|
|
370
|
+
status: configuredAccount.issues.length === 0 ? 'pass' : 'fail',
|
|
371
|
+
summary: configuredAccount.issues.length === 0
|
|
372
|
+
? 'Managed Claworld config matches the canonical account schema.'
|
|
373
|
+
: `Managed Claworld config still has schema issues: ${configuredAccount.issues.map((issue) => issue.code).join(', ')}.`,
|
|
374
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to normalize the managed config shape.`,
|
|
375
|
+
details: { issues: configuredAccount.issues },
|
|
376
|
+
}));
|
|
377
|
+
|
|
378
|
+
checks.push(createCheck({
|
|
379
|
+
id: 'app-token',
|
|
380
|
+
category: 'Credentials and runtime',
|
|
381
|
+
label: 'Managed appToken',
|
|
382
|
+
status: configuredAccount.tokenStatus === 'available' ? 'pass' : 'fail',
|
|
383
|
+
summary: configuredAccount.tokenStatus === 'available'
|
|
384
|
+
? 'Managed account has an available appToken.'
|
|
385
|
+
: `Managed account token status is ${configuredAccount.tokenStatus}.`,
|
|
386
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to refresh activation and persist a healthy appToken.`,
|
|
387
|
+
details: { tokenSource: configuredAccount.tokenSource, tokenStatus: configuredAccount.tokenStatus },
|
|
388
|
+
}));
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
facts.manifest = await fetchInstallManifest({
|
|
392
|
+
serverUrl: effectiveServerUrl,
|
|
393
|
+
apiKey: configuredApiKey,
|
|
394
|
+
fetchImpl,
|
|
395
|
+
});
|
|
396
|
+
checks.push(createCheck({
|
|
397
|
+
id: 'backend-reachable',
|
|
398
|
+
category: 'Credentials and runtime',
|
|
399
|
+
label: 'Claworld backend reachability',
|
|
400
|
+
status: 'pass',
|
|
401
|
+
summary: `Reached the Claworld install contract at ${effectiveServerUrl}.`,
|
|
402
|
+
details: { serverUrl: effectiveServerUrl, mode: facts.manifest.mode || null },
|
|
403
|
+
}));
|
|
404
|
+
} catch (error) {
|
|
405
|
+
checks.push(createCheck({
|
|
406
|
+
id: 'backend-reachable',
|
|
407
|
+
category: 'Credentials and runtime',
|
|
408
|
+
label: 'Claworld backend reachability',
|
|
409
|
+
status: 'fail',
|
|
410
|
+
summary: `Unable to read the Claworld install contract from ${effectiveServerUrl}.`,
|
|
411
|
+
action: 'Confirm the backend URL is correct and reachable, then rerun doctor.',
|
|
412
|
+
details: { serverUrl: effectiveServerUrl, message: error?.message || String(error) },
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const backendReachable = checks.find((check) => check.id === 'backend-reachable')?.status === 'pass';
|
|
417
|
+
if (configuredAccount.appToken && backendReachable) {
|
|
418
|
+
try {
|
|
419
|
+
facts.activation = await activateInstall({
|
|
420
|
+
serverUrl: effectiveServerUrl,
|
|
421
|
+
apiKey: configuredApiKey,
|
|
422
|
+
appToken: configuredAccount.appToken,
|
|
423
|
+
fetchImpl,
|
|
424
|
+
});
|
|
425
|
+
checks.push(createCheck({
|
|
426
|
+
id: 'stable-agent-binding',
|
|
427
|
+
category: 'Credentials and runtime',
|
|
428
|
+
label: 'Stable relay binding',
|
|
429
|
+
status: 'pass',
|
|
430
|
+
summary: `Backend activation resolved a stable managed agentId: \`${facts.activation.agentId}\`.`,
|
|
431
|
+
details: { agentId: facts.activation.agentId, created: facts.activation.created === true },
|
|
432
|
+
}));
|
|
433
|
+
} catch (error) {
|
|
434
|
+
checks.push(createCheck({
|
|
435
|
+
id: 'stable-agent-binding',
|
|
436
|
+
category: 'Credentials and runtime',
|
|
437
|
+
label: 'Stable relay binding',
|
|
438
|
+
status: 'fail',
|
|
439
|
+
summary: 'Managed appToken could not be resolved into a healthy backend binding.',
|
|
440
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to refresh the managed credential and binding.`,
|
|
441
|
+
details: { message: error?.message || String(error) },
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
} else if (!configuredAccount.appToken) {
|
|
445
|
+
checks.push(createCheck({
|
|
446
|
+
id: 'stable-agent-binding',
|
|
447
|
+
category: 'Credentials and runtime',
|
|
448
|
+
label: 'Stable relay binding',
|
|
449
|
+
status: 'fail',
|
|
450
|
+
summary: 'Doctor could not verify the managed relay binding because no reusable appToken is configured.',
|
|
451
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to finish activation and persist an appToken.`,
|
|
452
|
+
}));
|
|
453
|
+
} else {
|
|
454
|
+
checks.push(createCheck({
|
|
455
|
+
id: 'stable-agent-binding',
|
|
456
|
+
category: 'Credentials and runtime',
|
|
457
|
+
label: 'Stable relay binding',
|
|
458
|
+
status: 'fail',
|
|
459
|
+
summary: 'Doctor could not verify the managed relay binding because the backend install contract was not reachable.',
|
|
460
|
+
action: 'Restore backend reachability, then rerun doctor to verify the managed relay binding.',
|
|
461
|
+
}));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
facts.gatewayStatus = await readGatewayStatus({
|
|
466
|
+
openclawBin,
|
|
467
|
+
configPath: resolvedConfigPath,
|
|
468
|
+
stateDir: resolvedStateDir,
|
|
469
|
+
commandRunner,
|
|
470
|
+
cwd,
|
|
471
|
+
env,
|
|
472
|
+
dryRun,
|
|
473
|
+
});
|
|
474
|
+
const gatewayRunning = facts.gatewayStatus?.service?.runtime?.status === 'running';
|
|
475
|
+
checks.push(createCheck({
|
|
476
|
+
id: 'gateway-runtime',
|
|
477
|
+
category: 'Credentials and runtime',
|
|
478
|
+
label: 'OpenClaw gateway runtime',
|
|
479
|
+
status: gatewayRunning ? 'pass' : 'fail',
|
|
480
|
+
summary: gatewayRunning
|
|
481
|
+
? 'OpenClaw gateway runtime is running.'
|
|
482
|
+
: `OpenClaw gateway runtime reports ${facts.gatewayStatus?.service?.runtime?.status || 'unknown'}.`,
|
|
483
|
+
action: gatewayRunning ? null : 'Start or restart the OpenClaw gateway, then rerun doctor.',
|
|
484
|
+
details: { gateway: facts.gatewayStatus.gateway || null, service: facts.gatewayStatus.service || null },
|
|
485
|
+
}));
|
|
486
|
+
} catch (error) {
|
|
487
|
+
checks.push(createCheck({
|
|
488
|
+
id: 'gateway-runtime',
|
|
489
|
+
category: 'Credentials and runtime',
|
|
490
|
+
label: 'OpenClaw gateway runtime',
|
|
491
|
+
status: 'fail',
|
|
492
|
+
summary: 'Unable to read OpenClaw gateway status.',
|
|
493
|
+
action: 'Confirm the host can run `openclaw gateway status --json --no-probe`, then rerun doctor.',
|
|
494
|
+
details: { message: error?.message || String(error) },
|
|
495
|
+
}));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
try {
|
|
499
|
+
facts.channelStatus = await readChannelStatus({
|
|
500
|
+
openclawBin,
|
|
501
|
+
configPath: resolvedConfigPath,
|
|
502
|
+
stateDir: resolvedStateDir,
|
|
503
|
+
commandRunner,
|
|
504
|
+
cwd,
|
|
505
|
+
env,
|
|
506
|
+
dryRun,
|
|
507
|
+
});
|
|
508
|
+
const channelAccount = findChannelAccount(facts.channelStatus, configuredAccount.accountId);
|
|
509
|
+
const channelHealthy = Boolean(
|
|
510
|
+
channelAccount
|
|
511
|
+
&& channelAccount.configured === true
|
|
512
|
+
&& channelAccount.enabled !== false
|
|
513
|
+
&& channelAccount.tokenStatus === 'available'
|
|
514
|
+
);
|
|
515
|
+
checks.push(createCheck({
|
|
516
|
+
id: 'channel-runtime',
|
|
517
|
+
category: 'Credentials and runtime',
|
|
518
|
+
label: 'Claworld channel runtime account',
|
|
519
|
+
status: channelHealthy ? 'pass' : 'fail',
|
|
520
|
+
summary: channelHealthy
|
|
521
|
+
? `channels status reports managed account \`${configuredAccount.accountId}\` as configured with an available token.`
|
|
522
|
+
: `channels status does not report a healthy managed account for \`${configuredAccount.accountId}\`.`,
|
|
523
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` or restart the gateway to recover the managed account runtime.`,
|
|
524
|
+
details: { channelAccount },
|
|
525
|
+
}));
|
|
526
|
+
} catch (error) {
|
|
527
|
+
checks.push(createCheck({
|
|
528
|
+
id: 'channel-runtime',
|
|
529
|
+
category: 'Credentials and runtime',
|
|
530
|
+
label: 'Claworld channel runtime account',
|
|
531
|
+
status: 'fail',
|
|
532
|
+
summary: 'Unable to read `openclaw channels status --json` for the Claworld account.',
|
|
533
|
+
action: 'Confirm the gateway can load channel metadata, then rerun doctor.',
|
|
534
|
+
details: { message: error?.message || String(error) },
|
|
535
|
+
}));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
facts.bindingOutput = await readAgentBindings({
|
|
540
|
+
openclawBin,
|
|
541
|
+
configPath: resolvedConfigPath,
|
|
542
|
+
stateDir: resolvedStateDir,
|
|
543
|
+
commandRunner,
|
|
544
|
+
cwd,
|
|
545
|
+
env,
|
|
546
|
+
dryRun,
|
|
547
|
+
});
|
|
548
|
+
const bindingLine = String(`${facts.bindingOutput.stdout || ''}${facts.bindingOutput.stderr || ''}`)
|
|
549
|
+
.split('\n')
|
|
550
|
+
.map((line) => line.trim())
|
|
551
|
+
.find((line) => line === `- ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}`) || null;
|
|
552
|
+
checks.push(createCheck({
|
|
553
|
+
id: 'runtime-binding',
|
|
554
|
+
category: 'Credentials and runtime',
|
|
555
|
+
label: 'Runtime binding visibility',
|
|
556
|
+
status: bindingLine ? 'pass' : 'fail',
|
|
557
|
+
summary: bindingLine
|
|
558
|
+
? `OpenClaw agents bindings exposes ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`
|
|
559
|
+
: `OpenClaw agents bindings does not show ${resolvedAgentId} <- claworld accountId=${configuredAccount.accountId}.`,
|
|
560
|
+
action: `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to restore the managed binding.`,
|
|
561
|
+
details: { bindingLine, output: facts.bindingOutput.stdout || facts.bindingOutput.stderr || null },
|
|
562
|
+
}));
|
|
563
|
+
} catch (error) {
|
|
564
|
+
checks.push(createCheck({
|
|
565
|
+
id: 'runtime-binding',
|
|
566
|
+
category: 'Credentials and runtime',
|
|
567
|
+
label: 'Runtime binding visibility',
|
|
568
|
+
status: 'fail',
|
|
569
|
+
summary: 'Unable to read `openclaw agents bindings`.',
|
|
570
|
+
action: 'Confirm the OpenClaw host can inspect bindings, then rerun doctor.',
|
|
571
|
+
details: { message: error?.message || String(error) },
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (facts.gatewayStatus?.service?.runtime?.status === 'running') {
|
|
576
|
+
const gatewayBaseUrl = normalizeGatewayBaseUrl(facts.gatewayStatus);
|
|
577
|
+
facts.pluginStatusRoute = await fetchPluginStatusRoute({
|
|
578
|
+
gatewayBaseUrl,
|
|
579
|
+
fetchImpl,
|
|
580
|
+
});
|
|
581
|
+
const routeStatus = facts.pluginStatusRoute.status;
|
|
582
|
+
const routeReachable = facts.pluginStatusRoute.ok || routeStatus === 401 || routeStatus === 403;
|
|
583
|
+
let status = 'pass';
|
|
584
|
+
let summary = `Reached ${facts.pluginStatusRoute.routeUrl}.`;
|
|
585
|
+
let action = null;
|
|
586
|
+
if (!routeReachable) {
|
|
587
|
+
status = 'fail';
|
|
588
|
+
summary = `Unable to reach ${facts.pluginStatusRoute.routeUrl || 'the plugin status route'}.`;
|
|
589
|
+
action = 'Confirm the local OpenClaw gateway HTTP surface is reachable, then rerun doctor.';
|
|
590
|
+
} else if (!facts.pluginStatusRoute.ok) {
|
|
591
|
+
status = 'warn';
|
|
592
|
+
summary = `${facts.pluginStatusRoute.routeUrl} responded with ${routeStatus}, which proves the route exists but still requires gateway auth.`;
|
|
593
|
+
action = 'Provide gateway auth credentials if you need the live plugin status payload.';
|
|
594
|
+
}
|
|
595
|
+
checks.push(createCheck({
|
|
596
|
+
id: 'plugin-status-route',
|
|
597
|
+
category: 'Credentials and runtime',
|
|
598
|
+
label: 'Plugin status HTTP route',
|
|
599
|
+
status,
|
|
600
|
+
summary,
|
|
601
|
+
action,
|
|
602
|
+
details: facts.pluginStatusRoute,
|
|
603
|
+
}));
|
|
604
|
+
} else {
|
|
605
|
+
checks.push(createCheck({
|
|
606
|
+
id: 'plugin-status-route',
|
|
607
|
+
category: 'Credentials and runtime',
|
|
608
|
+
label: 'Plugin status HTTP route',
|
|
609
|
+
status: 'warn',
|
|
610
|
+
summary: 'Skipped the plugin status route check because the OpenClaw gateway runtime is not running.',
|
|
611
|
+
action: 'Start the gateway, then rerun doctor to verify `/plugins/claworld/status`.',
|
|
612
|
+
}));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const workspaceWorstStatus = workspaceInspection.issues.reduce((current, issue) =>
|
|
616
|
+
statusRank(issue.severity) > statusRank(current) ? issue.severity : current, 'pass');
|
|
617
|
+
checks.push(createCheck({
|
|
618
|
+
id: 'workspace-contract',
|
|
619
|
+
category: 'Workspace',
|
|
620
|
+
label: 'Managed workspace contract',
|
|
621
|
+
status: workspaceWorstStatus,
|
|
622
|
+
summary: workspaceInspection.ok
|
|
623
|
+
? `Managed workspace is present with ${formatWorkspaceState(workspaceInspection)}.`
|
|
624
|
+
: `Managed workspace needs repair: ${workspaceInspection.issues.map((issue) => issue.code).join(', ')}.`,
|
|
625
|
+
action: workspaceInspection.ok ? null : `Rerun \`${CLAWORLD_INSTALLER_COMMAND}\` to repair the managed workspace bootstrap.`,
|
|
626
|
+
details: {
|
|
627
|
+
workspacePath: workspaceInspection.workspacePath,
|
|
628
|
+
metadataPath: workspaceInspection.metadataPath,
|
|
629
|
+
files: workspaceInspection.files.map((file) => ({
|
|
630
|
+
relativePath: file.relativePath,
|
|
631
|
+
policy: file.policy,
|
|
632
|
+
state: file.state,
|
|
633
|
+
exists: file.exists,
|
|
634
|
+
})),
|
|
635
|
+
issues: workspaceInspection.issues,
|
|
636
|
+
},
|
|
637
|
+
}));
|
|
638
|
+
|
|
639
|
+
const status = overallStatus(checks);
|
|
640
|
+
return {
|
|
641
|
+
ok: status !== 'fail',
|
|
642
|
+
status,
|
|
643
|
+
command: CLAWORLD_DOCTOR_COMMAND,
|
|
644
|
+
configPath: resolvedConfigPath,
|
|
645
|
+
stateDir: resolvedStateDir,
|
|
646
|
+
accountId: configuredAccount.accountId || normalizeText(accountId, DEFAULT_CLAWORLD_ACCOUNT_ID),
|
|
647
|
+
agentId: resolvedAgentId,
|
|
648
|
+
workspacePath: workspaceInspection.workspacePath,
|
|
649
|
+
checks,
|
|
650
|
+
facts,
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function formatClaworldDoctorReport(result = {}) {
|
|
655
|
+
const checks = Array.isArray(result.checks) ? result.checks : [];
|
|
656
|
+
const categoryOrder = [
|
|
657
|
+
'Host and plugin',
|
|
658
|
+
'Managed config',
|
|
659
|
+
'Credentials and runtime',
|
|
660
|
+
'Workspace',
|
|
661
|
+
];
|
|
662
|
+
const lines = [
|
|
663
|
+
`Claworld doctor: ${String(result.status || 'pass').toUpperCase()}`,
|
|
664
|
+
`Command: ${CLAWORLD_DOCTOR_COMMAND}`,
|
|
665
|
+
`Config: ${result.configPath || '(unknown)'}`,
|
|
666
|
+
`Managed account: ${result.accountId || '(unknown)'}`,
|
|
667
|
+
`Managed agent: ${result.agentId || '(unknown)'}`,
|
|
668
|
+
`Managed workspace: ${result.workspacePath || '(unknown)'}`,
|
|
669
|
+
'',
|
|
670
|
+
];
|
|
671
|
+
|
|
672
|
+
for (const category of categoryOrder) {
|
|
673
|
+
const categoryChecks = checks.filter((check) => check.category === category);
|
|
674
|
+
if (categoryChecks.length === 0) continue;
|
|
675
|
+
lines.push(`${category}:`);
|
|
676
|
+
for (const check of categoryChecks) {
|
|
677
|
+
lines.push(`${formatStatus(check.status)} ${check.label}: ${check.summary}`);
|
|
678
|
+
if (check.action) {
|
|
679
|
+
lines.push(` Fix: ${check.action}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
lines.push('');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
if (checks.length === 0) {
|
|
686
|
+
lines.push('No checks were collected.');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return `${lines.join('\n').trim()}\n`;
|
|
690
|
+
}
|