0agent 1.0.21 → 1.0.23
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 +128 -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',
|
|
@@ -33,6 +57,55 @@ const C = {
|
|
|
33
57
|
const fmt = (color, text) => `${color}${text}${C.reset}`;
|
|
34
58
|
const clearLine = () => process.stdout.write('\r\x1b[2K');
|
|
35
59
|
|
|
60
|
+
// ─── LLM ping — direct 1-token call, bypasses daemon, instant ────────────────
|
|
61
|
+
async function pingLLM(provider) {
|
|
62
|
+
const key = provider.api_key ?? '';
|
|
63
|
+
const model = provider.model;
|
|
64
|
+
const sig = AbortSignal.timeout(8000);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
if (provider.provider === 'anthropic') {
|
|
68
|
+
const r = await fetch('https://api.anthropic.com/v1/messages', {
|
|
69
|
+
method: 'POST', signal: sig,
|
|
70
|
+
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01', 'Content-Type': 'application/json' },
|
|
71
|
+
body: JSON.stringify({ model, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }),
|
|
72
|
+
});
|
|
73
|
+
const d = await r.json();
|
|
74
|
+
if (!r.ok) return { ok: false, error: d.error?.message ?? `HTTP ${r.status}` };
|
|
75
|
+
return { ok: true, model: d.model };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (['openai','xai','gemini'].includes(provider.provider)) {
|
|
79
|
+
const base = provider.provider === 'xai' ? 'https://api.x.ai/v1'
|
|
80
|
+
: provider.provider === 'gemini' ? 'https://generativelanguage.googleapis.com/v1beta/openai'
|
|
81
|
+
: 'https://api.openai.com/v1';
|
|
82
|
+
const r = await fetch(`${base}/chat/completions`, {
|
|
83
|
+
method: 'POST', signal: sig,
|
|
84
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify({ model, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }] }),
|
|
86
|
+
});
|
|
87
|
+
const d = await r.json();
|
|
88
|
+
if (!r.ok) return { ok: false, error: d.error?.message ?? `HTTP ${r.status}` };
|
|
89
|
+
return { ok: true, model: d.model };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (provider.provider === 'ollama') {
|
|
93
|
+
const base = provider.base_url ?? 'http://localhost:11434';
|
|
94
|
+
const r = await fetch(`${base}/api/generate`, {
|
|
95
|
+
method: 'POST', signal: sig,
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
body: JSON.stringify({ model, prompt: 'hi', stream: false }),
|
|
98
|
+
});
|
|
99
|
+
if (!r.ok) return { ok: false, error: `Ollama HTTP ${r.status}` };
|
|
100
|
+
return { ok: true, model };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { ok: true, model }; // unknown provider — skip check
|
|
104
|
+
} catch (e) {
|
|
105
|
+
return { ok: false, error: e.message };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
36
109
|
// ─── Config management ────────────────────────────────────────────────────────
|
|
37
110
|
function loadConfig() {
|
|
38
111
|
if (!existsSync(CONFIG_PATH)) return null;
|
|
@@ -50,12 +123,13 @@ function getCurrentProvider(cfg) {
|
|
|
50
123
|
|
|
51
124
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
52
125
|
let cfg = loadConfig();
|
|
53
|
-
let sessionId
|
|
54
|
-
let streaming
|
|
55
|
-
let ws
|
|
56
|
-
let wsReady
|
|
126
|
+
let sessionId = null;
|
|
127
|
+
let streaming = false;
|
|
128
|
+
let ws = null;
|
|
129
|
+
let wsReady = false;
|
|
57
130
|
let pendingResolve = null;
|
|
58
|
-
let lineBuffer
|
|
131
|
+
let lineBuffer = '';
|
|
132
|
+
const spinner = new Spinner('Thinking');
|
|
59
133
|
const history = []; // command history for arrow keys
|
|
60
134
|
|
|
61
135
|
// ─── Header ──────────────────────────────────────────────────────────────────
|
|
@@ -102,17 +176,21 @@ function handleWsEvent(event) {
|
|
|
102
176
|
|
|
103
177
|
switch (event.type) {
|
|
104
178
|
case 'session.step': {
|
|
179
|
+
spinner.stop();
|
|
105
180
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
106
181
|
console.log(` ${fmt(C.dim, '›')} ${event.step}`);
|
|
182
|
+
spinner.start(event.step.slice(0, 50)); // resume with current step label
|
|
107
183
|
break;
|
|
108
184
|
}
|
|
109
185
|
case 'session.token': {
|
|
186
|
+
spinner.stop();
|
|
110
187
|
if (!streaming) { process.stdout.write('\n '); streaming = true; }
|
|
111
188
|
process.stdout.write(event.token);
|
|
112
189
|
lineBuffer += event.token;
|
|
113
190
|
break;
|
|
114
191
|
}
|
|
115
192
|
case 'session.completed': {
|
|
193
|
+
spinner.stop();
|
|
116
194
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
117
195
|
const r = event.result ?? {};
|
|
118
196
|
if (r.files_written?.length) console.log(`\n ${fmt(C.green, '✓')} ${r.files_written.join(', ')}`);
|
|
@@ -126,6 +204,7 @@ function handleWsEvent(event) {
|
|
|
126
204
|
break;
|
|
127
205
|
}
|
|
128
206
|
case 'session.failed': {
|
|
207
|
+
spinner.stop();
|
|
129
208
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
130
209
|
console.log(`\n ${fmt(C.red, '✗')} ${event.error}\n`);
|
|
131
210
|
lineBuffer = '';
|
|
@@ -189,6 +268,7 @@ async function runTask(input) {
|
|
|
189
268
|
});
|
|
190
269
|
const s = await res.json();
|
|
191
270
|
sessionId = s.session_id ?? s.id;
|
|
271
|
+
spinner.start('Thinking'); // show immediately after session created
|
|
192
272
|
return new Promise(resolve => { pendingResolve = resolve; });
|
|
193
273
|
} catch (e) {
|
|
194
274
|
console.log(` ${fmt(C.red, '✗')} ${e.message}`);
|
|
@@ -377,12 +457,18 @@ printInsights();
|
|
|
377
457
|
// Connect WebSocket for live events
|
|
378
458
|
connectWS();
|
|
379
459
|
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
460
|
+
// ── Startup: check daemon + LLM connection ──────────────────────────────────
|
|
461
|
+
(async () => {
|
|
462
|
+
const startSpin = new Spinner('Starting daemon');
|
|
463
|
+
|
|
464
|
+
// Step 1: ensure daemon is running
|
|
465
|
+
let daemonOk = false;
|
|
466
|
+
try {
|
|
467
|
+
await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) });
|
|
468
|
+
daemonOk = true;
|
|
469
|
+
} catch {
|
|
470
|
+
// Auto-start
|
|
471
|
+
startSpin.start();
|
|
386
472
|
const { resolve: res, existsSync: ef } = await import('node:path').then(m => m);
|
|
387
473
|
const pkgRoot = res(new URL(import.meta.url).pathname, '..', '..');
|
|
388
474
|
const bundled = res(pkgRoot, 'dist', 'daemon.mjs');
|
|
@@ -392,14 +478,39 @@ fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(1500) })
|
|
|
392
478
|
child.unref();
|
|
393
479
|
for (let i = 0; i < 20; i++) {
|
|
394
480
|
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 {}
|
|
481
|
+
try { await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) }); daemonOk = true; break; } catch {}
|
|
397
482
|
}
|
|
398
|
-
} else {
|
|
399
|
-
console.log(`\n ${fmt(C.dim, 'Run: 0agent start')}\n`);
|
|
400
483
|
}
|
|
401
|
-
|
|
402
|
-
});
|
|
484
|
+
startSpin.stop();
|
|
485
|
+
if (daemonOk) process.stdout.write(` ${fmt(C.green, '✓')} Daemon ready\n`);
|
|
486
|
+
else { console.log(` ${fmt(C.red, '✗')} Daemon failed. Run: 0agent start`); rl.prompt(); return; }
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Step 2: lightweight direct API ping (1 token — fast, no daemon involved)
|
|
490
|
+
const provider = getCurrentProvider(cfg);
|
|
491
|
+
if (!provider?.api_key?.trim() && provider?.provider !== 'ollama') {
|
|
492
|
+
console.log(` ${fmt(C.yellow, '⚠')} No API key. Use: ${fmt(C.cyan, '/key ' + (provider?.provider ?? 'anthropic') + ' <key>')}\n`);
|
|
493
|
+
} else {
|
|
494
|
+
const llmSpin = new Spinner(`Checking ${provider.provider}/${provider.model}`);
|
|
495
|
+
llmSpin.start();
|
|
496
|
+
try {
|
|
497
|
+
const result = await pingLLM(provider);
|
|
498
|
+
llmSpin.stop();
|
|
499
|
+
if (result.ok) {
|
|
500
|
+
console.log(` ${fmt(C.green, '✓')} ${fmt(C.cyan, result.model ?? provider.model)} is ready\n`);
|
|
501
|
+
} else {
|
|
502
|
+
console.log(` ${fmt(C.red, '✗')} LLM error: ${result.error}`);
|
|
503
|
+
console.log(` ${fmt(C.dim, 'Fix with: /key ' + provider.provider + ' <new-key>')}\n`);
|
|
504
|
+
}
|
|
505
|
+
} catch (e) {
|
|
506
|
+
llmSpin.stop();
|
|
507
|
+
console.log(` ${fmt(C.yellow, '⚠')} Could not reach ${provider.provider}: ${e.message}\n`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
rl.prompt();
|
|
512
|
+
})();
|
|
513
|
+
|
|
403
514
|
|
|
404
515
|
rl.on('line', async (input) => {
|
|
405
516
|
const line = input.trim();
|