0agent 1.0.26 → 1.0.27

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 (3) hide show
  1. package/bin/0agent.js +19 -1
  2. package/bin/chat.js +100 -28
  3. package/package.json +1 -1
package/bin/0agent.js CHANGED
@@ -1318,8 +1318,26 @@ async function isDaemonRunning() {
1318
1318
  }
1319
1319
  }
1320
1320
 
1321
+ async function isDaemonFresh() {
1322
+ // Check if the running daemon has the /api/llm/ping route (1.0.26+)
1323
+ try {
1324
+ const res = await fetch(`${BASE_URL}/api/llm/ping`, { method: 'POST', signal: AbortSignal.timeout(2000) });
1325
+ return res.status !== 404;
1326
+ } catch { return false; }
1327
+ }
1328
+
1321
1329
  async function requireDaemon() {
1322
- if (await isDaemonRunning()) return;
1330
+ if (await isDaemonRunning()) {
1331
+ // Kill and restart if it's an old version without key routes
1332
+ if (!(await isDaemonFresh())) {
1333
+ process.stdout.write(' Updating daemon...');
1334
+ try { execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
1335
+ await sleep(800);
1336
+ // fall through to start fresh daemon below
1337
+ } else {
1338
+ return; // up to date, no action needed
1339
+ }
1340
+ }
1323
1341
 
1324
1342
  // Auto-start if config exists — no manual `0agent start` needed
1325
1343
  if (!existsSync(CONFIG_PATH)) {
package/bin/chat.js CHANGED
@@ -457,50 +457,104 @@ printInsights();
457
457
  // Connect WebSocket for live events
458
458
  connectWS();
459
459
 
460
- // ── Startup: check daemon + LLM connection ──────────────────────────────────
460
+ // ── Startup: ensure fresh daemon + verify LLM ────────────────────────────────
461
+ async function _spawnDaemon() {
462
+ const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
463
+ const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
464
+ if (!existsSync(bundled) || !existsSync(CONFIG_PATH)) return false;
465
+ const { spawn } = await import('node:child_process');
466
+ const child = spawn(process.execPath, [bundled], {
467
+ detached: true, stdio: 'ignore',
468
+ env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH },
469
+ });
470
+ child.unref();
471
+ // Wait up to 10s for daemon to be ready
472
+ for (let i = 0; i < 20; i++) {
473
+ await new Promise(r => setTimeout(r, 500));
474
+ try {
475
+ await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) });
476
+ return true;
477
+ } catch {}
478
+ }
479
+ return false;
480
+ }
481
+
482
+ async function _safeJsonFetch(url, opts) {
483
+ const res = await fetch(url, opts);
484
+ const text = await res.text();
485
+ try { return { status: res.status, data: JSON.parse(text) }; }
486
+ catch { return { status: res.status, data: null, raw: text }; }
487
+ }
488
+
461
489
  (async () => {
462
490
  const startSpin = new Spinner('Starting daemon');
463
491
 
464
- // Step 1: ensure daemon is running
492
+ // Step 1: Check if daemon is running AND up-to-date (has /api/llm/ping)
465
493
  let daemonOk = false;
494
+ let needsRestart = false;
495
+
466
496
  try {
467
497
  await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) });
468
- daemonOk = true;
469
- } catch {
470
- // Auto-start
471
- startSpin.start();
472
- const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
473
- const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
474
- if (existsSync(bundled) && existsSync(CONFIG_PATH)) {
475
- const { spawn } = await import('node:child_process');
476
- const child = spawn(process.execPath, [bundled], { detached: true, stdio: 'ignore', env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH } });
477
- child.unref();
478
- for (let i = 0; i < 20; i++) {
479
- await new Promise(r => setTimeout(r, 500));
480
- try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); daemonOk = true; break; } catch {}
481
- }
498
+ // Daemon running — check if it has the new /api/llm/ping route
499
+ const probe = await _safeJsonFetch(`${BASE_URL}/api/llm/ping`, {
500
+ method: 'POST', signal: AbortSignal.timeout(3000),
501
+ });
502
+ if (probe.status === 404 || probe.data === null) {
503
+ // Old daemon without this route — needs restart
504
+ needsRestart = true;
505
+ } else {
506
+ daemonOk = true;
482
507
  }
483
- startSpin.stop();
484
- if (daemonOk) process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
485
- else { console.log(` ${fmt(C.red, '✗')} Daemon failed. Run: 0agent start`); rl.prompt(); return; }
508
+ } catch {
509
+ // Daemon not running at all
510
+ }
511
+
512
+ if (needsRestart) {
513
+ startSpin.start('Restarting daemon (new version)');
514
+ // Kill old daemon
515
+ try {
516
+ const { execSync } = await import('node:child_process');
517
+ execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' });
518
+ } catch {}
519
+ await new Promise(r => setTimeout(r, 800));
520
+ daemonOk = await _spawnDaemon();
521
+ } else if (!daemonOk) {
522
+ startSpin.start('Starting daemon');
523
+ daemonOk = await _spawnDaemon();
524
+ }
525
+
526
+ startSpin.stop();
527
+ if (!daemonOk) {
528
+ console.log(` ${fmt(C.red, '✗')} Daemon failed to start. Run: 0agent start`);
529
+ rl.prompt();
530
+ return;
531
+ }
532
+ if (needsRestart) {
533
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon updated\n`);
534
+ } else if (!daemonOk) {
535
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
536
+ } else {
537
+ // Was already running and up to date — show nothing (already running)
538
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
486
539
  }
487
540
 
488
- // Step 2: test LLM via the DAEMON (proves the daemon can reach the API, not just the CLI)
541
+ // Step 2: LLM check via daemon (not direct this proves daemon↔API works)
489
542
  const provider = getCurrentProvider(cfg);
490
543
  const llmSpin = new Spinner(`Checking ${provider?.provider ?? 'LLM'}/${provider?.model ?? '...'}`);
491
544
  llmSpin.start();
492
545
  try {
493
- const res = await fetch(`${BASE_URL}/api/llm/ping`, {
546
+ const { data } = await _safeJsonFetch(`${BASE_URL}/api/llm/ping`, {
494
547
  method: 'POST',
495
548
  signal: AbortSignal.timeout(15_000),
496
549
  });
497
- const data = await res.json();
498
550
  llmSpin.stop();
499
- if (data.ok) {
500
- console.log(` ${fmt(C.green, '✓')} ${fmt(C.cyan, data.provider + '/' + data.model)} — ${data.latency_ms}ms\n`);
551
+ if (data?.ok) {
552
+ console.log(` ${fmt(C.green, '✓')} ${fmt(C.cyan, (data.provider ?? '') + '/' + (data.model ?? ''))} — ${data.latency_ms}ms\n`);
553
+ } else if (data) {
554
+ console.log(` ${fmt(C.red, '✗')} LLM error: ${data.error}`);
555
+ console.log(` ${fmt(C.dim, 'Fix: /key ' + (provider?.provider ?? 'anthropic') + ' <api-key>')}\n`);
501
556
  } else {
502
- console.log(` ${fmt(C.red, '')} LLM unreachable: ${data.error}`);
503
- console.log(` ${fmt(C.dim, 'Fix: /key ' + (provider?.provider ?? 'anthropic') + ' <your-api-key>')}\n`);
557
+ console.log(` ${fmt(C.yellow, '')} LLM ping returned unexpected response\n`);
504
558
  }
505
559
  } catch (e) {
506
560
  llmSpin.stop();
@@ -532,6 +586,24 @@ rl.on('close', () => {
532
586
  });
533
587
 
534
588
  process.on('SIGINT', () => {
535
- console.log(`\n ${fmt(C.dim, 'Ctrl+C — type /help for commands or Ctrl+C again to exit')}\n`);
536
- rl.prompt();
589
+ if (pendingResolve) {
590
+ // Session in progress — cancel it, don't exit
591
+ process.stdout.write(`\n ${fmt(C.yellow, '↩')} Cancelled\n`);
592
+ spinner.stop();
593
+ if (sessionId) {
594
+ fetch(`${BASE_URL}/api/sessions/${sessionId}`, { method: 'DELETE' }).catch(() => {});
595
+ }
596
+ const resolve_ = pendingResolve;
597
+ pendingResolve = null;
598
+ sessionId = null;
599
+ resolve_();
600
+ rl.prompt();
601
+ } else {
602
+ // Not busy — show hint on first press
603
+ process.stdout.write(`\n ${fmt(C.dim, 'Press Ctrl+C again to exit')}\n`);
604
+ rl.prompt();
605
+ // Second Ctrl+C within 1.5s exits
606
+ const timeout = setTimeout(() => {}, 1500);
607
+ process.once('SIGINT', () => { clearTimeout(timeout); process.exit(0); });
608
+ }
537
609
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "A persistent, learning AI agent that runs on your machine. An agent that learns.",
5
5
  "private": false,
6
6
  "license": "Apache-2.0",