robot-resources 1.9.4 → 1.9.5
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/lib/wizard.js +82 -19
- package/package.json +1 -1
package/lib/wizard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { homedir, hostname } from 'node:os';
|
|
3
|
+
import { homedir, hostname, release as osRelease } from 'node:os';
|
|
4
4
|
import { readConfig, writeConfig } from '@robot-resources/cli-core/config.mjs';
|
|
5
5
|
import { findPython, isPortAvailable, isHeadless, isOpenClawInstalled } from './detect.js';
|
|
6
6
|
import { getOrCreateMachineId } from './machine-id.js';
|
|
@@ -69,6 +69,8 @@ function classifyRouterError(err) {
|
|
|
69
69
|
export async function runWizard({ nonInteractive = false } = {}) {
|
|
70
70
|
header();
|
|
71
71
|
|
|
72
|
+
const wizardStartMs = Date.now();
|
|
73
|
+
|
|
72
74
|
const results = {
|
|
73
75
|
auth: false,
|
|
74
76
|
authMethod: null, // 'config' | 'apikey' | 'github'
|
|
@@ -76,6 +78,16 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
76
78
|
routerError: null,
|
|
77
79
|
providerKeys: false,
|
|
78
80
|
service: false,
|
|
81
|
+
// Diagnostic fields populated as the wizard progresses. All are sent
|
|
82
|
+
// in install_complete (success AND failure) so we can distinguish
|
|
83
|
+
// "pip installed but router never served a request" from a real
|
|
84
|
+
// working setup in post-hoc telemetry.
|
|
85
|
+
serviceType: null,
|
|
86
|
+
pluginInstalled: false,
|
|
87
|
+
openclawDetected: false,
|
|
88
|
+
openclawConfigPatched: false,
|
|
89
|
+
scraperMcpRegistered: false,
|
|
90
|
+
healthCheck: { attempted: false },
|
|
79
91
|
};
|
|
80
92
|
|
|
81
93
|
// ── Step 0: Provision API key (before anything else) ────────────────────
|
|
@@ -222,6 +234,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
222
234
|
|
|
223
235
|
try {
|
|
224
236
|
const svc = installService(getVenvPythonPath());
|
|
237
|
+
results.serviceType = svc.type || null;
|
|
225
238
|
if (svc.type === 'skipped') {
|
|
226
239
|
warn(svc.reason);
|
|
227
240
|
results.service = false;
|
|
@@ -247,6 +260,15 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
247
260
|
const toolResults = configureToolRouting();
|
|
248
261
|
results.tools = toolResults;
|
|
249
262
|
|
|
263
|
+
// Surface OC-specific signals for install_complete diagnostics.
|
|
264
|
+
results.openclawDetected = isOpenClawInstalled();
|
|
265
|
+
const ocResult = toolResults.find((r) => r.name === 'OpenClaw');
|
|
266
|
+
if (ocResult) {
|
|
267
|
+
results.pluginInstalled =
|
|
268
|
+
ocResult.action === 'installed' || ocResult.action === 'already_configured';
|
|
269
|
+
results.openclawConfigPatched = Boolean(ocResult.configActivated);
|
|
270
|
+
}
|
|
271
|
+
|
|
250
272
|
if (toolResults.length === 0) {
|
|
251
273
|
info('No supported AI tools detected');
|
|
252
274
|
info('Point your tool at http://localhost:3838 to enable cost optimization');
|
|
@@ -289,6 +311,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
289
311
|
if (scraperRegistered) {
|
|
290
312
|
success('Scraper MCP registered in OpenClaw — scraper_compress_url(url) available');
|
|
291
313
|
results.scraper = true;
|
|
314
|
+
results.scraperMcpRegistered = true;
|
|
292
315
|
} else {
|
|
293
316
|
// Either already registered, or no openclaw.json
|
|
294
317
|
try {
|
|
@@ -296,6 +319,7 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
296
319
|
if (ocConfig?.mcp?.servers?.['robot-resources-scraper']) {
|
|
297
320
|
success('Scraper MCP already registered in OpenClaw');
|
|
298
321
|
results.scraper = true;
|
|
322
|
+
results.scraperMcpRegistered = true;
|
|
299
323
|
}
|
|
300
324
|
} catch {
|
|
301
325
|
// No openclaw.json — not on OC, skip
|
|
@@ -303,13 +327,25 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
303
327
|
}
|
|
304
328
|
|
|
305
329
|
// ── Step 4.5: Router Healthcheck ──────────────────────────────────────
|
|
330
|
+
//
|
|
331
|
+
// Verify the router is actually serving /health — not just that pip
|
|
332
|
+
// exited 0. Runs regardless of whether service registration succeeded:
|
|
333
|
+
// a router started by the wizard's spawn (or by a running OC) still
|
|
334
|
+
// deserves to be probed, and a router that pip-installed but fails to
|
|
335
|
+
// respond means the install is NOT actually complete.
|
|
336
|
+
//
|
|
337
|
+
// If we declared router=true from Step 1 (pip success) but /health
|
|
338
|
+
// won't answer, downgrade router→false with a dedicated error reason.
|
|
339
|
+
// This closes the "install looks green but nothing works" gap that
|
|
340
|
+
// produced 34 silent-after-install real users with no diagnostics.
|
|
306
341
|
|
|
307
|
-
|
|
308
|
-
if (results.service) {
|
|
342
|
+
if (results.router) {
|
|
309
343
|
blank();
|
|
310
344
|
step('Verifying Router is responding...');
|
|
311
345
|
|
|
312
|
-
|
|
346
|
+
const checkStart = Date.now();
|
|
347
|
+
let healthData = null;
|
|
348
|
+
let lastErr = null;
|
|
313
349
|
// Retry a few times — the service may need a moment to start
|
|
314
350
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
315
351
|
try {
|
|
@@ -319,20 +355,38 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
319
355
|
if (res.ok) {
|
|
320
356
|
const data = await res.json();
|
|
321
357
|
if (data.status === 'healthy' || data.status === 'degraded') {
|
|
322
|
-
|
|
323
|
-
healthy = true;
|
|
358
|
+
healthData = data;
|
|
324
359
|
break;
|
|
325
360
|
}
|
|
326
361
|
}
|
|
327
|
-
} catch {
|
|
328
|
-
|
|
329
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
362
|
+
} catch (err) {
|
|
363
|
+
lastErr = err?.message || String(err);
|
|
330
364
|
}
|
|
365
|
+
if (attempt < 2) await new Promise((r) => setTimeout(r, 2000));
|
|
331
366
|
}
|
|
332
367
|
|
|
333
|
-
|
|
334
|
-
|
|
368
|
+
results.healthCheck = {
|
|
369
|
+
attempted: true,
|
|
370
|
+
passed: Boolean(healthData),
|
|
371
|
+
version: healthData?.version ?? null,
|
|
372
|
+
status: healthData?.status ?? null,
|
|
373
|
+
latencyMs: Date.now() - checkStart,
|
|
374
|
+
error: healthData ? null : lastErr,
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
if (healthData) {
|
|
378
|
+
success(`Router healthy (v${healthData.version || 'unknown'})`);
|
|
379
|
+
} else {
|
|
380
|
+
warn('Router not responding — marking install as failed.');
|
|
335
381
|
info('Check manually: curl http://localhost:3838/health');
|
|
382
|
+
// Bug fix: previously we left router=true here. Now we downgrade
|
|
383
|
+
// so install_complete reflects reality and the error is classified.
|
|
384
|
+
results.router = false;
|
|
385
|
+
results.routerError = {
|
|
386
|
+
reason: 'health_check_failed',
|
|
387
|
+
detail: (lastErr || 'no response').slice(-500),
|
|
388
|
+
exitCode: null,
|
|
389
|
+
};
|
|
336
390
|
}
|
|
337
391
|
}
|
|
338
392
|
|
|
@@ -349,22 +403,31 @@ export async function runWizard({ nonInteractive = false } = {}) {
|
|
|
349
403
|
try {
|
|
350
404
|
const config = readConfig();
|
|
351
405
|
const platformUrl = process.env.RR_PLATFORM_URL || 'https://api.robotresources.ai';
|
|
406
|
+
// Everything populated unconditionally so success installs carry
|
|
407
|
+
// the same diagnostic weight as failures. Prior versions only
|
|
408
|
+
// captured routerError+platform on failure, leaving 34 "successful"
|
|
409
|
+
// installs with no post-hoc signal to explain why they never emit
|
|
410
|
+
// another event.
|
|
352
411
|
const installPayload = {
|
|
412
|
+
source: 'wizard',
|
|
353
413
|
router: results.router || false,
|
|
354
414
|
service: results.service || false,
|
|
355
415
|
scraper: results.scraper || false,
|
|
356
|
-
|
|
416
|
+
platform: process.platform,
|
|
417
|
+
os_release: osRelease(),
|
|
418
|
+
node_version: process.version,
|
|
419
|
+
install_duration_ms: Date.now() - wizardStartMs,
|
|
420
|
+
python_source: results.pythonSource ?? null,
|
|
421
|
+
service_type: results.serviceType ?? null,
|
|
422
|
+
health_check: results.healthCheck,
|
|
423
|
+
plugin_installed: results.pluginInstalled,
|
|
424
|
+
openclaw_detected: results.openclawDetected,
|
|
425
|
+
openclaw_config_patched: results.openclawConfigPatched,
|
|
426
|
+
scraper_mcp_registered: results.scraperMcpRegistered,
|
|
357
427
|
};
|
|
358
|
-
if (results.pythonSource) {
|
|
359
|
-
// 'system' when the user had Python installed, 'uv' when we
|
|
360
|
-
// auto-bootstrapped one. Lets us measure how many installs were
|
|
361
|
-
// rescued by the uv fallback.
|
|
362
|
-
installPayload.pythonSource = results.pythonSource;
|
|
363
|
-
}
|
|
364
428
|
if (results.routerError && typeof results.routerError === 'object') {
|
|
365
429
|
installPayload.routerError = results.routerError.reason;
|
|
366
430
|
installPayload.routerErrorDetail = results.routerError.detail;
|
|
367
|
-
installPayload.platform = process.platform;
|
|
368
431
|
}
|
|
369
432
|
const body = JSON.stringify({
|
|
370
433
|
product: 'cli',
|