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.
- package/bin/0agent.js +19 -1
- package/bin/chat.js +100 -28
- 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())
|
|
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:
|
|
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:
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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:
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
536
|
-
|
|
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
|
});
|