@xfxstudio/claworld 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +45 -19
  2. package/index.js +0 -1
  3. package/openclaw.plugin.json +1 -1
  4. package/package.json +1 -5
  5. package/skills/claworld-help/SKILL.md +84 -91
  6. package/skills/claworld-join-and-chat/SKILL.md +9 -9
  7. package/src/openclaw/index.js +0 -3
  8. package/src/openclaw/plugin/account-identity.js +0 -1
  9. package/src/openclaw/plugin/claworld-channel-plugin.js +8 -253
  10. package/src/openclaw/plugin/managed-config.js +1 -7
  11. package/src/openclaw/plugin/onboarding.js +128 -103
  12. package/src/openclaw/plugin/register.js +183 -232
  13. package/src/openclaw/plugin/relay-client.js +8 -5
  14. package/src/openclaw/runtime/product-shell-helper.js +11 -364
  15. package/src/openclaw/runtime/tool-contracts.js +0 -182
  16. package/src/openclaw/runtime/tool-inventory.js +4 -27
  17. package/bin/claworld.mjs +0 -9
  18. package/src/lib/agent-profile.js +0 -74
  19. package/src/lib/http-auth.js +0 -151
  20. package/src/lib/policy.js +0 -114
  21. package/src/openclaw/installer/cli.js +0 -406
  22. package/src/openclaw/installer/constants.js +0 -14
  23. package/src/openclaw/installer/core.js +0 -2122
  24. package/src/openclaw/installer/doctor.js +0 -876
  25. package/src/openclaw/installer/workspace-contract.js +0 -427
  26. package/src/product-shell/agent-cards/card-routes.js +0 -64
  27. package/src/product-shell/agent-cards/card-service.js +0 -287
  28. package/src/product-shell/agent-cards/spec-builder.js +0 -167
  29. package/src/product-shell/agent-cards/storage/image-host-storage.js +0 -192
  30. package/src/product-shell/agent-cards/storage/local-public-storage.js +0 -74
  31. package/src/product-shell/agent-cards/svg-renderer.js +0 -325
  32. package/src/product-shell/agent-cards/template-registry.js +0 -131
  33. package/src/product-shell/catalog/default-world-catalog.js +0 -38
  34. package/src/product-shell/contracts/candidate-feed.js +0 -393
  35. package/src/product-shell/contracts/world-manifest.js +0 -369
  36. package/src/product-shell/conversation-feedback/conversation-feedback-service.js +0 -261
  37. package/src/product-shell/feedback/feedback-contract.js +0 -13
  38. package/src/product-shell/feedback/feedback-routes.js +0 -98
  39. package/src/product-shell/feedback/feedback-service.js +0 -252
  40. package/src/product-shell/index.js +0 -212
  41. package/src/product-shell/matching/matchmaking-service.js +0 -395
  42. package/src/product-shell/membership/membership-service.js +0 -284
  43. package/src/product-shell/onboarding/onboarding-routes.js +0 -37
  44. package/src/product-shell/onboarding/onboarding-service.js +0 -220
  45. package/src/product-shell/orchestration/world-conversation-orchestrator.js +0 -28
  46. package/src/product-shell/profile/profile-service.js +0 -142
  47. package/src/product-shell/profile/public-identity-routes.js +0 -160
  48. package/src/product-shell/profile/public-identity-service.js +0 -192
  49. package/src/product-shell/search/search-service.js +0 -393
  50. package/src/product-shell/social/chat-request-approval-policy.js +0 -332
  51. package/src/product-shell/social/chat-request-routes.js +0 -130
  52. package/src/product-shell/social/chat-request-service.js +0 -723
  53. package/src/product-shell/social/friend-routes.js +0 -82
  54. package/src/product-shell/social/friend-service.js +0 -557
  55. package/src/product-shell/social/social-routes.js +0 -21
  56. package/src/product-shell/social/social-service.js +0 -136
  57. package/src/product-shell/worlds/world-admin-service.js +0 -486
  58. package/src/product-shell/worlds/world-authorization.js +0 -136
  59. package/src/product-shell/worlds/world-broadcast-service.js +0 -296
  60. package/src/product-shell/worlds/world-routes.js +0 -403
  61. package/src/product-shell/worlds/world-service.js +0 -89
  62. package/src/product-shell/worlds/world-text.js +0 -75
@@ -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_pair_agent`, and then complete `claworld_profile` 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_pair_agent` 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_pair_agent`, and then complete `claworld_profile` 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_pair_agent`, and then complete `claworld_profile` 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
- }