0agent 1.0.37 → 1.0.39
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/chat.js +98 -33
- package/package.json +1 -1
package/bin/chat.js
CHANGED
|
@@ -23,33 +23,64 @@ class Spinner {
|
|
|
23
23
|
this._msg = msg;
|
|
24
24
|
this._frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
25
25
|
this._i = 0; this._timer = null; this._active = false;
|
|
26
|
+
this._sessionMode = false; // true during agent sessions — no \r animation
|
|
26
27
|
}
|
|
28
|
+
|
|
29
|
+
// Animated spinner — safe only at startup when user cannot type yet.
|
|
30
|
+
// Uses \r which conflicts with readline when user is typing.
|
|
27
31
|
start(msg) {
|
|
28
32
|
if (this._active) return;
|
|
29
33
|
if (msg) this._msg = msg;
|
|
30
34
|
this._active = true;
|
|
35
|
+
this._sessionMode = false;
|
|
31
36
|
this._timer = setInterval(() => {
|
|
32
37
|
process.stdout.write(`\r \x1b[36m${this._frames[this._i++ % this._frames.length]}\x1b[0m \x1b[2m${this._msg}\x1b[0m `);
|
|
33
38
|
}, 80);
|
|
34
39
|
}
|
|
40
|
+
|
|
41
|
+
// Session mode — prints a one-time static status line, NO \r animation.
|
|
42
|
+
// readline owns the cursor; call rl.prompt(true) after this to show › .
|
|
43
|
+
startSession(msg) {
|
|
44
|
+
if (this._active) return;
|
|
45
|
+
if (msg) this._msg = msg;
|
|
46
|
+
this._active = true;
|
|
47
|
+
this._sessionMode = true;
|
|
48
|
+
process.stdout.write(` \x1b[2m⠋ ${this._msg}...\x1b[0m\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
update(msg) { this._msg = msg; }
|
|
52
|
+
|
|
36
53
|
stop(clearIt = true) {
|
|
37
54
|
if (!this._active) return;
|
|
38
|
-
clearInterval(this._timer); this._timer = null;
|
|
39
|
-
|
|
55
|
+
if (this._timer) { clearInterval(this._timer); this._timer = null; }
|
|
56
|
+
const wasSession = this._sessionMode;
|
|
57
|
+
this._active = false;
|
|
58
|
+
this._sessionMode = false;
|
|
59
|
+
// Only clear the \r-based animation line; session mode output flows naturally
|
|
60
|
+
if (clearIt && !wasSession) {
|
|
61
|
+
process.stdout.write('\r\x1b[2K');
|
|
62
|
+
}
|
|
40
63
|
}
|
|
64
|
+
|
|
41
65
|
get active() { return this._active; }
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
* Pause spinner, run fn() (which may print lines), then resume.
|
|
45
|
-
* Prevents spinner from overwriting printed content.
|
|
46
|
-
*/
|
|
67
|
+
// Pause to print something cleanly, then resume.
|
|
47
68
|
pauseFor(fn) {
|
|
48
|
-
const wasActive
|
|
49
|
-
const
|
|
50
|
-
|
|
69
|
+
const wasActive = this._active;
|
|
70
|
+
const wasSession = this._sessionMode;
|
|
71
|
+
const savedMsg = this._msg;
|
|
72
|
+
this.stop(!wasSession); // clear animated spinner; session mode: just deactivate
|
|
51
73
|
fn();
|
|
52
|
-
if (wasActive)
|
|
74
|
+
if (wasActive) {
|
|
75
|
+
if (wasSession) {
|
|
76
|
+
// Re-mark active without printing again — readline is showing the prompt
|
|
77
|
+
this._active = true;
|
|
78
|
+
this._sessionMode = true;
|
|
79
|
+
this._msg = savedMsg;
|
|
80
|
+
} else {
|
|
81
|
+
this.start(savedMsg);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
53
84
|
}
|
|
54
85
|
}
|
|
55
86
|
|
|
@@ -192,13 +223,20 @@ function handleWsEvent(event) {
|
|
|
192
223
|
case 'session.step': {
|
|
193
224
|
spinner.stop();
|
|
194
225
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
226
|
+
// Clear current readline line, print step, then restore › prompt
|
|
227
|
+
process.stdout.write('\r\x1b[2K');
|
|
195
228
|
console.log(` ${fmt(C.dim, '›')} ${event.step}`);
|
|
196
|
-
spinner.
|
|
229
|
+
spinner.startSession(event.step.slice(0, 50));
|
|
230
|
+
rl.prompt(true); // restore › so user can keep typing
|
|
197
231
|
break;
|
|
198
232
|
}
|
|
199
233
|
case 'session.token': {
|
|
200
234
|
spinner.stop();
|
|
201
|
-
if (!streaming) {
|
|
235
|
+
if (!streaming) {
|
|
236
|
+
// Clear › prompt line before streaming response
|
|
237
|
+
process.stdout.write('\r\x1b[2K\n ');
|
|
238
|
+
streaming = true;
|
|
239
|
+
}
|
|
202
240
|
process.stdout.write(event.token);
|
|
203
241
|
lineBuffer += event.token;
|
|
204
242
|
break;
|
|
@@ -383,9 +421,10 @@ async function runTask(input) {
|
|
|
383
421
|
});
|
|
384
422
|
const s = await res.json();
|
|
385
423
|
sessionId = s.session_id ?? s.id;
|
|
386
|
-
//
|
|
387
|
-
process.stdout.write(
|
|
388
|
-
spinner.
|
|
424
|
+
// Start session-mode status (no \r animation) then restore › so user can type
|
|
425
|
+
process.stdout.write('\n');
|
|
426
|
+
spinner.startSession('Thinking');
|
|
427
|
+
rl.prompt(true); // keep › visible — user can queue next message while agent works
|
|
389
428
|
|
|
390
429
|
// Polling fallback — runs concurrently with WS events.
|
|
391
430
|
// Catches completion when WS is disconnected (e.g. daemon just restarted).
|
|
@@ -416,8 +455,10 @@ async function runTask(input) {
|
|
|
416
455
|
const steps = session.steps ?? [];
|
|
417
456
|
for (let j = lastPolledStep; j < steps.length; j++) {
|
|
418
457
|
spinner.stop();
|
|
458
|
+
process.stdout.write('\r\x1b[2K');
|
|
419
459
|
console.log(` \x1b[2m›\x1b[0m ${steps[j].description}`);
|
|
420
|
-
spinner.
|
|
460
|
+
spinner.startSession(steps[j].description.slice(0, 50));
|
|
461
|
+
rl.prompt(true);
|
|
421
462
|
}
|
|
422
463
|
lastPolledStep = steps.length;
|
|
423
464
|
|
|
@@ -563,10 +604,7 @@ async function handleCommand(input) {
|
|
|
563
604
|
const { execSync: exs } = await import('node:child_process');
|
|
564
605
|
exs('npm install -g 0agent@latest --silent', { stdio: 'ignore', timeout: 120_000 });
|
|
565
606
|
process.stdout.write(` ${fmt(C.green, '✓')} Updated to ${latest} — restarting...\n\n`);
|
|
566
|
-
|
|
567
|
-
const child = sp(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
|
|
568
|
-
child.on('close', (code) => process.exit(code ?? 0));
|
|
569
|
-
process.stdin.pause();
|
|
607
|
+
await restartWithLatest();
|
|
570
608
|
} catch (e) {
|
|
571
609
|
console.log(` ${fmt(C.red, '✗')} Update failed: ${e.message}\n`);
|
|
572
610
|
}
|
|
@@ -887,11 +925,7 @@ async function _safeJsonFetch(url, opts) {
|
|
|
887
925
|
exs('npm install -g 0agent@latest --silent', { stdio: 'ignore', timeout: 120_000 });
|
|
888
926
|
process.stdout.write(` ${fmt(C.green, '✓')} Updated to ${latest} — restarting...\n\n`);
|
|
889
927
|
|
|
890
|
-
|
|
891
|
-
const { spawn: sp } = await import('node:child_process');
|
|
892
|
-
const child = sp(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
|
|
893
|
-
child.on('close', (code) => process.exit(code ?? 0));
|
|
894
|
-
process.stdin.pause();
|
|
928
|
+
await restartWithLatest();
|
|
895
929
|
} catch {
|
|
896
930
|
// Non-fatal — update failure never crashes the agent
|
|
897
931
|
}
|
|
@@ -900,6 +934,35 @@ async function _safeJsonFetch(url, opts) {
|
|
|
900
934
|
rl.prompt();
|
|
901
935
|
})();
|
|
902
936
|
|
|
937
|
+
// Restart using the GLOBAL install, not the npx cache that's currently running.
|
|
938
|
+
// After `npm install -g 0agent@latest`, the new binary is in npm's global bin dir.
|
|
939
|
+
// Using process.argv would re-run the OLD npx-cached file → infinite loop.
|
|
940
|
+
async function restartWithLatest() {
|
|
941
|
+
try {
|
|
942
|
+
const { execSync: ex } = await import('node:child_process');
|
|
943
|
+
const { resolve: res } = await import('node:path');
|
|
944
|
+
const { existsSync: ef } = await import('node:fs');
|
|
945
|
+
const { spawn: sp } = await import('node:child_process');
|
|
946
|
+
|
|
947
|
+
// Find the global npm bin directory (e.g. ~/.nvm/.../bin or /usr/local/bin)
|
|
948
|
+
const globalBin = ex('npm bin -g 2>/dev/null || true', { encoding: 'utf8' }).trim().split('\n')[0];
|
|
949
|
+
const newBin = globalBin ? res(globalBin, '0agent') : null;
|
|
950
|
+
|
|
951
|
+
let child;
|
|
952
|
+
if (newBin && ef(newBin)) {
|
|
953
|
+
// Run the freshly installed global script
|
|
954
|
+
child = sp(process.execPath, [newBin], { stdio: 'inherit' });
|
|
955
|
+
} else {
|
|
956
|
+
// Fallback: let the shell find 0agent in PATH
|
|
957
|
+
child = sp('0agent', [], { stdio: 'inherit', shell: true });
|
|
958
|
+
}
|
|
959
|
+
child.on('close', (code) => process.exit(code ?? 0));
|
|
960
|
+
process.stdin.pause();
|
|
961
|
+
} catch {
|
|
962
|
+
process.exit(0); // just exit; user can re-open
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
903
966
|
function isNewerVersion(a, b) {
|
|
904
967
|
const pa = a.split('.').map(Number);
|
|
905
968
|
const pb = b.split('.').map(Number);
|
|
@@ -946,14 +1009,16 @@ rl.on('line', async (input) => {
|
|
|
946
1009
|
if (pendingResolve) {
|
|
947
1010
|
messageQueue.push(line);
|
|
948
1011
|
const qLen = messageQueue.length;
|
|
949
|
-
spinner.pauseFor(
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1012
|
+
// No spinner.pauseFor() needed — session mode has no \r animation
|
|
1013
|
+
// Just print the queued confirmation and restore the › prompt
|
|
1014
|
+
process.stdout.write('\r\x1b[2K');
|
|
1015
|
+
process.stdout.write(
|
|
1016
|
+
` ${fmt(C.magenta, '↳')} ${fmt(C.bold, `[queued #${qLen}]`)} ${fmt(C.dim, line.slice(0, 70))}\n`
|
|
1017
|
+
);
|
|
1018
|
+
if (qLen > 1) {
|
|
1019
|
+
process.stdout.write(` ${fmt(C.dim, `${qLen} tasks waiting`)}\n`);
|
|
1020
|
+
}
|
|
1021
|
+
rl.prompt(true); // keep › visible
|
|
957
1022
|
return;
|
|
958
1023
|
}
|
|
959
1024
|
|