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.
Files changed (2) hide show
  1. package/bin/chat.js +98 -33
  2. 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; this._active = false;
39
- if (clearIt) process.stdout.write('\r\x1b[2K');
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 = this._active;
49
- const savedMsg = this._msg;
50
- if (wasActive) this.stop(true);
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) this.start(savedMsg);
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.start(event.step.slice(0, 50)); // resume with current step label
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) { process.stdout.write('\n '); streaming = true; }
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
- // Show affordance so user knows they can queue messages while agent works
387
- process.stdout.write(`\n \x1b[2m(type and press Enter to queue next message)\x1b[0m\n`);
388
- spinner.start('Thinking');
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.start(steps[j].description.slice(0, 50));
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
- const { spawn: sp } = await import('node:child_process');
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
- // Restart cleanly
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
- process.stdout.write(
951
- ` ${fmt(C.magenta, '')} ${fmt(C.bold, `[queued #${qLen}]`)} ${fmt(C.dim, line.slice(0, 70))}\n`
952
- );
953
- if (qLen > 1) {
954
- process.stdout.write(` ${fmt(C.dim, `${qLen} tasks waiting`)}\n`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
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",