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.
Files changed (2) hide show
  1. package/lib/wizard.js +82 -19
  2. 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
- // Router: verify it's responding on localhost:3838
308
- if (results.service) {
342
+ if (results.router) {
309
343
  blank();
310
344
  step('Verifying Router is responding...');
311
345
 
312
- let healthy = false;
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
- success(`Router healthy (v${data.version || 'unknown'})`);
323
- healthy = true;
358
+ healthData = data;
324
359
  break;
325
360
  }
326
361
  }
327
- } catch {
328
- // Wait before retrying
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
- if (!healthy) {
334
- warn('Router not responding yet — it may need a few more seconds to start');
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
- source: 'wizard',
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robot-resources",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
4
4
  "description": "Robot Resources — AI agent tools. One command to install everything.",
5
5
  "type": "module",
6
6
  "bin": {