0agent 1.0.21 → 1.0.22
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 +60 -15
- package/bin/chat.js +100 -17
- package/package.json +1 -1
package/bin/0agent.js
CHANGED
|
@@ -512,10 +512,12 @@ async function streamSession(sessionId) {
|
|
|
512
512
|
|
|
513
513
|
return new Promise((resolve) => {
|
|
514
514
|
const ws = new WS(`ws://localhost:4200/ws`);
|
|
515
|
-
let streaming = false;
|
|
515
|
+
let streaming = false;
|
|
516
|
+
const spinner = new Spinner('Thinking');
|
|
516
517
|
|
|
517
518
|
ws.on('open', () => {
|
|
518
519
|
ws.send(JSON.stringify({ type: 'subscribe', topics: ['sessions'] }));
|
|
520
|
+
spinner.start(); // show immediately on connect
|
|
519
521
|
});
|
|
520
522
|
|
|
521
523
|
ws.on('message', async (data) => {
|
|
@@ -525,28 +527,31 @@ async function streamSession(sessionId) {
|
|
|
525
527
|
|
|
526
528
|
switch (event.type) {
|
|
527
529
|
case 'session.step':
|
|
528
|
-
|
|
530
|
+
spinner.stop();
|
|
529
531
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
530
532
|
console.log(` \x1b[2m›\x1b[0m ${event.step}`);
|
|
533
|
+
spinner.start(event.step.slice(0, 50)); // show next spinner with current step
|
|
531
534
|
break;
|
|
532
535
|
case 'session.token':
|
|
533
|
-
//
|
|
536
|
+
spinner.stop(); // stop spinner before first token
|
|
534
537
|
if (!streaming) { process.stdout.write('\n '); streaming = true; }
|
|
535
538
|
process.stdout.write(event.token);
|
|
536
539
|
break;
|
|
537
540
|
case 'session.completed': {
|
|
541
|
+
spinner.stop();
|
|
538
542
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
539
543
|
const r = event.result ?? {};
|
|
540
544
|
if (r.files_written?.length) console.log(`\n \x1b[32m✓\x1b[0m Files: ${r.files_written.join(', ')}`);
|
|
541
545
|
if (r.commands_run?.length) console.log(` \x1b[32m✓\x1b[0m Commands run: ${r.commands_run.length}`);
|
|
542
546
|
if (r.tokens_used) console.log(` \x1b[2m${r.tokens_used} tokens · ${r.model}\x1b[0m`);
|
|
543
547
|
console.log('\n \x1b[32m✓ Done\x1b[0m\n');
|
|
544
|
-
await showResultPreview(r);
|
|
548
|
+
await showResultPreview(r);
|
|
545
549
|
ws.close();
|
|
546
550
|
resolve();
|
|
547
551
|
break;
|
|
548
552
|
}
|
|
549
553
|
case 'session.failed':
|
|
554
|
+
spinner.stop();
|
|
550
555
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
551
556
|
console.log(`\n \x1b[31m✗ Failed:\x1b[0m ${event.error}\n`);
|
|
552
557
|
ws.close();
|
|
@@ -557,7 +562,7 @@ async function streamSession(sessionId) {
|
|
|
557
562
|
});
|
|
558
563
|
|
|
559
564
|
ws.on('error', () => {
|
|
560
|
-
|
|
565
|
+
spinner.stop();
|
|
561
566
|
ws.close();
|
|
562
567
|
pollSession(sessionId).then(resolve);
|
|
563
568
|
});
|
|
@@ -569,31 +574,39 @@ async function streamSession(sessionId) {
|
|
|
569
574
|
|
|
570
575
|
async function pollSession(sessionId) {
|
|
571
576
|
let lastStepCount = 0;
|
|
577
|
+
const spinner = new Spinner('Thinking');
|
|
578
|
+
spinner.start();
|
|
579
|
+
|
|
572
580
|
for (let i = 0; i < 300; i++) {
|
|
573
581
|
await sleep(600);
|
|
574
582
|
const res = await fetch(`${BASE_URL}/api/sessions/${sessionId}`);
|
|
575
583
|
const s = await res.json();
|
|
576
584
|
|
|
577
|
-
// Print
|
|
585
|
+
// Print new steps
|
|
578
586
|
const steps = s.steps ?? [];
|
|
579
587
|
for (let j = lastStepCount; j < steps.length; j++) {
|
|
580
|
-
|
|
588
|
+
spinner.stop();
|
|
589
|
+
console.log(` \x1b[2m›\x1b[0m ${steps[j].description}`);
|
|
590
|
+
spinner.start(steps[j].description.slice(0, 50));
|
|
581
591
|
}
|
|
582
592
|
lastStepCount = steps.length;
|
|
583
593
|
|
|
584
594
|
if (s.status === 'completed') {
|
|
585
|
-
|
|
595
|
+
spinner.stop();
|
|
596
|
+
console.log('\n \x1b[32m✓ Done\x1b[0m\n');
|
|
586
597
|
const out = s.result?.output ?? s.result;
|
|
587
598
|
if (out && typeof out === 'string') console.log(` ${out}\n`);
|
|
588
599
|
await showResultPreview(s.result ?? {});
|
|
589
600
|
return;
|
|
590
601
|
}
|
|
591
602
|
if (s.status === 'failed') {
|
|
592
|
-
|
|
603
|
+
spinner.stop();
|
|
604
|
+
console.log(`\n \x1b[31m✗ Failed:\x1b[0m ${s.error}\n`);
|
|
593
605
|
return;
|
|
594
606
|
}
|
|
595
607
|
}
|
|
596
|
-
|
|
608
|
+
spinner.stop();
|
|
609
|
+
console.log('\n Timed out.\n');
|
|
597
610
|
}
|
|
598
611
|
|
|
599
612
|
// ─── Chat REPL ───────────────────────────────────────────────────────────
|
|
@@ -1311,19 +1324,20 @@ async function requireDaemon() {
|
|
|
1311
1324
|
process.exit(1);
|
|
1312
1325
|
}
|
|
1313
1326
|
|
|
1314
|
-
|
|
1327
|
+
const startSpinner = new Spinner('Starting daemon');
|
|
1328
|
+
startSpinner.start();
|
|
1315
1329
|
await _startDaemonBackground();
|
|
1316
1330
|
|
|
1317
1331
|
for (let i = 0; i < 24; i++) {
|
|
1318
1332
|
await sleep(500);
|
|
1319
|
-
process.stdout.write('.');
|
|
1320
1333
|
if (await isDaemonRunning()) {
|
|
1321
|
-
|
|
1334
|
+
startSpinner.stop();
|
|
1335
|
+
process.stdout.write(' \x1b[32m✓\x1b[0m Daemon ready\n\n');
|
|
1322
1336
|
return;
|
|
1323
1337
|
}
|
|
1324
1338
|
}
|
|
1325
|
-
|
|
1326
|
-
console.log(' Daemon failed to start. Check: 0agent logs\n');
|
|
1339
|
+
startSpinner.stop();
|
|
1340
|
+
console.log(' \x1b[31m✗\x1b[0m Daemon failed to start. Check: 0agent logs\n');
|
|
1327
1341
|
process.exit(1);
|
|
1328
1342
|
}
|
|
1329
1343
|
|
|
@@ -1359,6 +1373,37 @@ function sleep(ms) {
|
|
|
1359
1373
|
return new Promise(r => setTimeout(r, ms));
|
|
1360
1374
|
}
|
|
1361
1375
|
|
|
1376
|
+
// ─── Spinner — shows when agent is thinking/loading ───────────────────────────
|
|
1377
|
+
class Spinner {
|
|
1378
|
+
constructor(msg = 'Thinking') {
|
|
1379
|
+
this._msg = msg;
|
|
1380
|
+
this._frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
1381
|
+
this._i = 0;
|
|
1382
|
+
this._timer = null;
|
|
1383
|
+
this._active = false;
|
|
1384
|
+
}
|
|
1385
|
+
start(msg) {
|
|
1386
|
+
if (this._active) return;
|
|
1387
|
+
if (msg) this._msg = msg;
|
|
1388
|
+
this._active = true;
|
|
1389
|
+
process.stdout.write('\n');
|
|
1390
|
+
this._timer = setInterval(() => {
|
|
1391
|
+
process.stdout.write(`\r \x1b[36m${this._frames[this._i++ % this._frames.length]}\x1b[0m \x1b[2m${this._msg}\x1b[0m `);
|
|
1392
|
+
}, 80);
|
|
1393
|
+
}
|
|
1394
|
+
update(msg) {
|
|
1395
|
+
this._msg = msg;
|
|
1396
|
+
}
|
|
1397
|
+
stop(clearIt = true) {
|
|
1398
|
+
if (!this._active) return;
|
|
1399
|
+
clearInterval(this._timer);
|
|
1400
|
+
this._timer = null;
|
|
1401
|
+
this._active = false;
|
|
1402
|
+
if (clearIt) process.stdout.write('\r\x1b[2K');
|
|
1403
|
+
}
|
|
1404
|
+
get active() { return this._active; }
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1362
1407
|
function ask(question) {
|
|
1363
1408
|
return new Promise(resolve => {
|
|
1364
1409
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
package/bin/chat.js
CHANGED
|
@@ -17,6 +17,30 @@ const AGENT_DIR = resolve(homedir(), '.0agent');
|
|
|
17
17
|
const CONFIG_PATH = resolve(AGENT_DIR, 'config.yaml');
|
|
18
18
|
const BASE_URL = process.env['ZEROAGENT_URL'] ?? 'http://localhost:4200';
|
|
19
19
|
|
|
20
|
+
// ─── Spinner ──────────────────────────────────────────────────────────────────
|
|
21
|
+
class Spinner {
|
|
22
|
+
constructor(msg = 'Thinking') {
|
|
23
|
+
this._msg = msg;
|
|
24
|
+
this._frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
25
|
+
this._i = 0; this._timer = null; this._active = false;
|
|
26
|
+
}
|
|
27
|
+
start(msg) {
|
|
28
|
+
if (this._active) return;
|
|
29
|
+
if (msg) this._msg = msg;
|
|
30
|
+
this._active = true;
|
|
31
|
+
this._timer = setInterval(() => {
|
|
32
|
+
process.stdout.write(`\r \x1b[36m${this._frames[this._i++ % this._frames.length]}\x1b[0m \x1b[2m${this._msg}\x1b[0m `);
|
|
33
|
+
}, 80);
|
|
34
|
+
}
|
|
35
|
+
update(msg) { this._msg = msg; }
|
|
36
|
+
stop(clearIt = true) {
|
|
37
|
+
if (!this._active) return;
|
|
38
|
+
clearInterval(this._timer); this._timer = null; this._active = false;
|
|
39
|
+
if (clearIt) process.stdout.write('\r\x1b[2K');
|
|
40
|
+
}
|
|
41
|
+
get active() { return this._active; }
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
// ─── ANSI helpers ─────────────────────────────────────────────────────────────
|
|
21
45
|
const C = {
|
|
22
46
|
reset: '\x1b[0m',
|
|
@@ -50,12 +74,13 @@ function getCurrentProvider(cfg) {
|
|
|
50
74
|
|
|
51
75
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
52
76
|
let cfg = loadConfig();
|
|
53
|
-
let sessionId
|
|
54
|
-
let streaming
|
|
55
|
-
let ws
|
|
56
|
-
let wsReady
|
|
77
|
+
let sessionId = null;
|
|
78
|
+
let streaming = false;
|
|
79
|
+
let ws = null;
|
|
80
|
+
let wsReady = false;
|
|
57
81
|
let pendingResolve = null;
|
|
58
|
-
let lineBuffer
|
|
82
|
+
let lineBuffer = '';
|
|
83
|
+
const spinner = new Spinner('Thinking');
|
|
59
84
|
const history = []; // command history for arrow keys
|
|
60
85
|
|
|
61
86
|
// ─── Header ──────────────────────────────────────────────────────────────────
|
|
@@ -102,17 +127,21 @@ function handleWsEvent(event) {
|
|
|
102
127
|
|
|
103
128
|
switch (event.type) {
|
|
104
129
|
case 'session.step': {
|
|
130
|
+
spinner.stop();
|
|
105
131
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
106
132
|
console.log(` ${fmt(C.dim, '›')} ${event.step}`);
|
|
133
|
+
spinner.start(event.step.slice(0, 50)); // resume with current step label
|
|
107
134
|
break;
|
|
108
135
|
}
|
|
109
136
|
case 'session.token': {
|
|
137
|
+
spinner.stop();
|
|
110
138
|
if (!streaming) { process.stdout.write('\n '); streaming = true; }
|
|
111
139
|
process.stdout.write(event.token);
|
|
112
140
|
lineBuffer += event.token;
|
|
113
141
|
break;
|
|
114
142
|
}
|
|
115
143
|
case 'session.completed': {
|
|
144
|
+
spinner.stop();
|
|
116
145
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
117
146
|
const r = event.result ?? {};
|
|
118
147
|
if (r.files_written?.length) console.log(`\n ${fmt(C.green, '✓')} ${r.files_written.join(', ')}`);
|
|
@@ -126,6 +155,7 @@ function handleWsEvent(event) {
|
|
|
126
155
|
break;
|
|
127
156
|
}
|
|
128
157
|
case 'session.failed': {
|
|
158
|
+
spinner.stop();
|
|
129
159
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
130
160
|
console.log(`\n ${fmt(C.red, '✗')} ${event.error}\n`);
|
|
131
161
|
lineBuffer = '';
|
|
@@ -189,6 +219,7 @@ async function runTask(input) {
|
|
|
189
219
|
});
|
|
190
220
|
const s = await res.json();
|
|
191
221
|
sessionId = s.session_id ?? s.id;
|
|
222
|
+
spinner.start('Thinking'); // show immediately after session created
|
|
192
223
|
return new Promise(resolve => { pendingResolve = resolve; });
|
|
193
224
|
} catch (e) {
|
|
194
225
|
console.log(` ${fmt(C.red, '✗')} ${e.message}`);
|
|
@@ -377,12 +408,18 @@ printInsights();
|
|
|
377
408
|
// Connect WebSocket for live events
|
|
378
409
|
connectWS();
|
|
379
410
|
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
411
|
+
// ── Startup: check daemon + LLM connection ──────────────────────────────────
|
|
412
|
+
(async () => {
|
|
413
|
+
const startSpin = new Spinner('Starting daemon');
|
|
414
|
+
|
|
415
|
+
// Step 1: ensure daemon is running
|
|
416
|
+
let daemonOk = false;
|
|
417
|
+
try {
|
|
418
|
+
await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) });
|
|
419
|
+
daemonOk = true;
|
|
420
|
+
} catch {
|
|
421
|
+
// Auto-start
|
|
422
|
+
startSpin.start();
|
|
386
423
|
const { resolve: res, existsSync: ef } = await import('node:path').then(m => m);
|
|
387
424
|
const pkgRoot = res(new URL(import.meta.url).pathname, '..', '..');
|
|
388
425
|
const bundled = res(pkgRoot, 'dist', 'daemon.mjs');
|
|
@@ -392,14 +429,60 @@ fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) })
|
|
|
392
429
|
child.unref();
|
|
393
430
|
for (let i = 0; i < 20; i++) {
|
|
394
431
|
await new Promise(r => setTimeout(r, 500));
|
|
395
|
-
|
|
396
|
-
try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); process.stdout.write(fmt(C.green, ' ✓\n\n')); break; } catch {}
|
|
432
|
+
try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); daemonOk = true; break; } catch {}
|
|
397
433
|
}
|
|
398
|
-
} else {
|
|
399
|
-
console.log(`\n ${fmt(C.dim, 'Run: 0agent start')}\n`);
|
|
400
434
|
}
|
|
401
|
-
|
|
402
|
-
});
|
|
435
|
+
startSpin.stop();
|
|
436
|
+
if (daemonOk) process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
|
|
437
|
+
else { console.log(` ${fmt(C.red, '✗')} Daemon failed. Run: 0agent start`); rl.prompt(); return; }
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Step 2: verify LLM is reachable with a quick "hello" check
|
|
441
|
+
const provider = getCurrentProvider(cfg);
|
|
442
|
+
if (provider?.api_key?.trim() || provider?.provider === 'ollama') {
|
|
443
|
+
const llmSpin = new Spinner(`Connecting to ${provider.provider}/${provider.model}`);
|
|
444
|
+
llmSpin.start();
|
|
445
|
+
try {
|
|
446
|
+
// Ask the daemon to run a minimal LLM ping via the session API
|
|
447
|
+
const res = await fetch(`${BASE_URL}/api/sessions`, {
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: { 'Content-Type': 'application/json' },
|
|
450
|
+
body: JSON.stringify({ task: 'Reply with exactly: ready', options: { max_steps: 1 } }),
|
|
451
|
+
});
|
|
452
|
+
const s = await res.json();
|
|
453
|
+
const sid = s.session_id ?? s.id;
|
|
454
|
+
// Poll briefly for completion
|
|
455
|
+
for (let i = 0; i < 20; i++) {
|
|
456
|
+
await new Promise(r => setTimeout(r, 500));
|
|
457
|
+
const check = await fetch(`${BASE_URL}/api/sessions/${sid}`).then(r => r.json()).catch(() => null);
|
|
458
|
+
if (check?.status === 'completed') {
|
|
459
|
+
llmSpin.stop();
|
|
460
|
+
const model = check.result?.model ?? provider.model;
|
|
461
|
+
console.log(` ${fmt(C.green, '✓')} LLM connected — ${fmt(C.cyan, model)}\n`);
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (check?.status === 'failed') {
|
|
465
|
+
llmSpin.stop();
|
|
466
|
+
console.log(` ${fmt(C.red, '✗')} LLM error: ${check.error}`);
|
|
467
|
+
console.log(` ${fmt(C.dim, 'Check your API key with: /key ' + provider.provider)}\n`);
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (llmSpin.active) {
|
|
472
|
+
llmSpin.stop();
|
|
473
|
+
console.log(` ${fmt(C.yellow, '⚠')} LLM check timed out — it may still work\n`);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
llmSpin.stop();
|
|
477
|
+
console.log(` ${fmt(C.yellow, '⚠')} Could not verify LLM connection\n`);
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
console.log(` ${fmt(C.yellow, '⚠')} No API key set. Use: ${fmt(C.cyan, '/key ' + (provider?.provider ?? 'anthropic') + ' <your-key>')}\n`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
rl.prompt();
|
|
484
|
+
})();
|
|
485
|
+
|
|
403
486
|
|
|
404
487
|
rl.on('line', async (input) => {
|
|
405
488
|
const line = input.trim();
|