0agent 1.0.58 → 1.0.60

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 CHANGED
@@ -175,6 +175,89 @@ function renderMarkdown(text) {
175
175
  return out.join('\n');
176
176
  }
177
177
 
178
+ // ─── Human-readable active task summary ───────────────────────────────────────
179
+ // Translates raw AgentExecutor step labels into a plain-English 1-liner shown
180
+ // as the live "what is happening right now" status line.
181
+ function stepToHuman(step) {
182
+ // Tool invocations: "▶ tool_name(args...)"
183
+ const toolMatch = step.match(/^▶\s+(\w+)\((.{0,200})\)/);
184
+ if (toolMatch) {
185
+ const [, tool, args] = toolMatch;
186
+ // gui_automation — decode action
187
+ if (tool === 'gui_automation') {
188
+ const act = (args.match(/action[=:]\s*["']?(\w+)["']?/i) || [])[1] ?? '';
189
+ const txt = (args.match(/text[=:]\s*["']([^"']{0,40})["']/i) || [])[1] ?? '';
190
+ const url = (args.match(/url[=:]\s*["']([^"']{0,60})["']/i) || [])[1] ?? '';
191
+ const secs = (args.match(/seconds[=:]\s*([\d.]+)/i) || [])[1] ?? '';
192
+ const x = (args.match(/\bx[=:]\s*(\d+)/i) || [])[1] ?? '';
193
+ const y = (args.match(/\by[=:]\s*(\d+)/i) || [])[1] ?? '';
194
+ const map = {
195
+ screenshot: 'Taking screenshot of screen',
196
+ click: x ? `Clicking at (${x}, ${y})` : 'Clicking',
197
+ double_click: x ? `Double-clicking at (${x}, ${y})` : 'Double-clicking',
198
+ right_click: 'Right-clicking',
199
+ move: x ? `Moving cursor to (${x}, ${y})` : 'Moving cursor',
200
+ type: txt ? `Typing: "${txt.slice(0,30)}"` : 'Typing text',
201
+ hotkey: txt ? `Pressing ${txt}` : 'Pressing hotkey',
202
+ scroll: 'Scrolling',
203
+ drag: 'Dragging',
204
+ find_and_click: txt ? `Clicking "${txt}"` : 'Finding and clicking',
205
+ open_url: url ? `Opening ${url}` : 'Opening URL in browser',
206
+ open_app: txt ? `Opening ${txt}` : 'Opening app',
207
+ get_screen_size: 'Getting screen size',
208
+ get_cursor_pos: 'Getting cursor position',
209
+ wait: secs ? `Waiting ${secs}s for UI to load` : 'Waiting for UI',
210
+ };
211
+ return map[act] ?? `GUI: ${act}`;
212
+ }
213
+ // shell_exec
214
+ if (tool === 'shell_exec') {
215
+ const cmd = args.replace(/^["']|["']$/g, '').replace(/\\n/g, ' ').trim().slice(0, 60);
216
+ return `Running: ${cmd}`;
217
+ }
218
+ // web_search
219
+ if (tool === 'web_search') {
220
+ const q = (args.match(/["']([^"']{1,60})["']/) || [])[1] ?? args.slice(0,50);
221
+ return `Searching: ${q}`;
222
+ }
223
+ // file_op
224
+ if (tool === 'file_op') {
225
+ const op = (args.match(/op[=:]\s*["']?(\w+)["']?/i) || [])[1] ?? '';
226
+ const path = (args.match(/path[=:]\s*["']([^"']{1,50})["']/i) || [])[1] ?? '';
227
+ const opMap = { write: 'Writing', read: 'Reading', list: 'Listing', mkdir: 'Creating folder', delete: 'Deleting' };
228
+ return `${opMap[op] ?? op}: ${path}`;
229
+ }
230
+ // browser_open / scrape_url
231
+ if (tool === 'browser_open' || tool === 'scrape_url') {
232
+ const url = (args.match(/["']([^"']{1,60})["']/) || [])[1] ?? args.slice(0,50);
233
+ return `Reading page: ${url}`;
234
+ }
235
+ // memory_write
236
+ if (tool === 'memory_write') {
237
+ const label = (args.match(/label[=:]\s*["']([^"']{1,40})["']/i) || [])[1] ?? '';
238
+ return `Saving to memory: ${label}`;
239
+ }
240
+ // generic fallback
241
+ const cleanArgs = args.replace(/^["'](.*)["']$/, '$1').replace(/\\n/g, ' ').slice(0, 50);
242
+ return `${tool}: ${cleanArgs}`;
243
+ }
244
+
245
+ // Result line: " ↳ text" — show briefly as status, not in history
246
+ if (/^\s*↳/.test(step)) {
247
+ return step.replace(/^\s*↳\s*/, '').slice(0, 80);
248
+ }
249
+
250
+ // Thinking / Continuing
251
+ if (/^Thinking/.test(step)) return 'Thinking…';
252
+ if (/^Continuing/.test(step)) return 'Working on it…';
253
+
254
+ // Self-heal, skill, misc
255
+ if (/^↺/.test(step)) return step.slice(0, 80);
256
+ if (/^✓/.test(step)) return step.slice(0, 80);
257
+
258
+ return step.slice(0, 80);
259
+ }
260
+
178
261
  // ─── Step formatter ───────────────────────────────────────────────────────────
179
262
  // Converts raw step labels from AgentExecutor into icon + clean readable form.
180
263
  function formatStep(step) {
@@ -370,12 +453,29 @@ function handleWsEvent(event) {
370
453
  case 'session.step': {
371
454
  spinner.stop();
372
455
  if (streaming) { process.stdout.write('\n'); streaming = false; streamLineCount = 0; }
373
- const formatted = formatStep(event.step);
374
- if (formatted !== null) {
375
- process.stdout.write('\r\x1b[2K');
376
- console.log(formatted);
456
+
457
+ const step = event.step;
458
+ const isToolCall = /^▶\s+\w+\(/.test(step); // ▶ tool(args)
459
+ const isResult = /^\s*↳/.test(step); // ↳ result
460
+ const isThinking = /^(Thinking|Continuing)/.test(step);
461
+ const isSummary = /^(Done|Files|Commands|Matched|Fetching|Loaded|Selected|Extracting|Querying|↺|✓)/.test(step);
462
+
463
+ // Print tool invocations and summary milestones to scrolling history
464
+ if (isToolCall || isSummary) {
465
+ const formatted = formatStep(step);
466
+ if (formatted !== null) {
467
+ process.stdout.write('\r\x1b[2K');
468
+ console.log(formatted);
469
+ }
377
470
  }
378
- spinner.startSession(event.step.slice(0, 50));
471
+
472
+ // Always update the live 1-liner status (overwrites current line)
473
+ const human = stepToHuman(step);
474
+ if (human) {
475
+ const icon = isThinking ? fmt(C.dim, '⠋') : isResult ? fmt(C.dim, '↳') : fmt(C.cyan, '⚡');
476
+ process.stdout.write(`\r\x1b[2K ${icon} ${fmt(C.dim, human)}`);
477
+ }
478
+
379
479
  rl.prompt(true);
380
480
  break;
381
481
  }
@@ -653,6 +753,7 @@ async function runTask(input) {
653
753
  try {
654
754
  const r = await fetch(`${BASE_URL}/api/sessions/${sid}`, { signal: AbortSignal.timeout(2000) });
655
755
  const session = await r.json();
756
+ globalThis._daemonMisses = 0; // daemon responded — reset miss counter
656
757
 
657
758
  // Show any new steps not yet shown via WS
658
759
  const steps = session.steps ?? [];
@@ -689,7 +790,26 @@ async function runTask(input) {
689
790
  resolve_?.();
690
791
  drainQueue(); // auto-process queued messages
691
792
  }
692
- } catch {}
793
+ } catch {
794
+ // Daemon not responding — track misses
795
+ if (typeof _daemonMisses === 'undefined') globalThis._daemonMisses = 0;
796
+ globalThis._daemonMisses = (globalThis._daemonMisses ?? 0) + 1;
797
+ if (globalThis._daemonMisses >= 4) {
798
+ globalThis._daemonMisses = 0;
799
+ clearInterval(pollTimer);
800
+ spinner.stop();
801
+ process.stdout.write(`\r\x1b[2K\n ${fmt(C.yellow, '⚠')} Daemon stopped — restarting…\n\n`);
802
+ const r = await _spawnDaemon();
803
+ if (r === 'ok') {
804
+ process.stdout.write(` ${fmt(C.green, '✓')} Daemon restarted. Re-send your message.\n\n`);
805
+ } else {
806
+ process.stdout.write(` ${fmt(C.red, '✗')} Could not restart daemon. Run: ${fmt(C.dim, '0agent start')}\n\n`);
807
+ }
808
+ if (pendingResolve) { pendingResolve(); pendingResolve = null; }
809
+ sessionId = null; streaming = false; streamLineCount = 0;
810
+ rl.prompt();
811
+ }
812
+ }
693
813
  }, 800);
694
814
 
695
815
  return new Promise(resolve => { pendingResolve = resolve; });
package/dist/daemon.mjs CHANGED
@@ -2367,6 +2367,8 @@ var init_ShellCapability = __esm({
2367
2367
  // These must never run autonomously — they survive uninstall and can
2368
2368
  // re-open apps (e.g. Brave) on every login or on a timer.
2369
2369
  static PERSISTENT_TASK_PATTERN = /crontab\s+-[eilr]|launchctl\s+load|launchctl\s+bootstrap|systemctl\s+enable|at\s+\d|make\s+login\s+item|LaunchAgents|LaunchDaemons|loginitems/i;
2370
+ // Commands that make irreversible external state changes — require explicit user confirmation
2371
+ static DESTRUCTIVE_PATTERN = /\bcurl\s+[^|&]*-[A-Za-z]*[XD]\s+(DELETE|POST|PUT|PATCH)\b|\bcurl\s+[^|&]*--(request|data)[=\s]+(DELETE|POST|PUT|PATCH)\b|rm\s+-[rf]{1,3}\s+[^|&;]{3}|DROP\s+TABLE|DELETE\s+FROM\s+\w/i;
2370
2372
  async execute(input, cwd, signal) {
2371
2373
  let command = String(input.command ?? "");
2372
2374
  const timeout = Number(input.timeout_ms ?? 3e4);
@@ -2378,6 +2380,13 @@ var init_ShellCapability = __esm({
2378
2380
  duration_ms: 0
2379
2381
  };
2380
2382
  }
2383
+ if (_ShellCapability.DESTRUCTIVE_PATTERN.test(command)) {
2384
+ return {
2385
+ success: false,
2386
+ output: `CONFIRM_REQUIRED: The command "${command.slice(0, 100)}" will make an irreversible change. Tell the user exactly what this will do and ask them to reply with explicit confirmation before you run it.`,
2387
+ duration_ms: 0
2388
+ };
2389
+ }
2381
2390
  if (signal?.aborted) {
2382
2391
  return { success: false, output: "Cancelled.", duration_ms: 0 };
2383
2392
  }
@@ -2698,9 +2707,12 @@ var init_GUICapability = __esm({
2698
2707
  return { success: false, output: result.stderr.trim() || "Unknown error after install", duration_ms: Date.now() - start };
2699
2708
  }
2700
2709
  if (err.includes("accessibility") || err.includes("permission") || err.includes("AXIsProcessTrusted")) {
2710
+ if (platform2() === "darwin") {
2711
+ spawnSync4("open", ["x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"], { timeout: 3e3 });
2712
+ }
2701
2713
  return {
2702
2714
  success: false,
2703
- output: "macOS accessibility permission required. Go to: System Preferences \u2192 Privacy & Security \u2192 Accessibility \u2192 add Terminal (or the app running 0agent)",
2715
+ output: "macOS Accessibility permission required for GUI automation.\n\u2192 System Settings has been opened automatically.\n\u2192 Go to: Privacy & Security \u2192 Accessibility \u2192 enable Terminal (or iTerm2 / the app running 0agent)\n\u2192 Then re-run your task.",
2704
2716
  duration_ms: Date.now() - start
2705
2717
  };
2706
2718
  }
@@ -2871,19 +2883,32 @@ for i, word in enumerate(data['text']):
2871
2883
  cy = data['top'][i] + data['height'][i] // 2
2872
2884
  found.append((cx, cy, word))
2873
2885
 
2874
- try:
2875
- if found:
2876
- cx, cy, word = found[0]
2877
- pyautogui.click(cx, cy, duration=${duration})
2878
- print(f"Found '{word}' at ({cx},{cy}) \u2014 clicked")
2886
+ if found:
2887
+ cx, cy, word = found[0]
2888
+ pyautogui.click(cx, cy, duration=${duration})
2889
+ print(f"Found '{word}' at ({cx},{cy}) \u2014 clicked")
2890
+ else:
2891
+ # Retry once after a brief wait (element may still be loading)
2892
+ time.sleep(1.5)
2893
+ img2 = pyautogui.screenshot()
2894
+ data2 = pytesseract.image_to_data(img2, output_type=pytesseract.Output.DICT)
2895
+ found2 = []
2896
+ for i, word in enumerate(data2['text']):
2897
+ if target in word.lower() and int(data2['conf'][i]) > 40:
2898
+ cx2 = data2['left'][i] + data2['width'][i] // 2
2899
+ cy2 = data2['top'][i] + data2['height'][i] // 2
2900
+ found2.append((cx2, cy2, word))
2901
+ if found2:
2902
+ cx2, cy2, word2 = found2[0]
2903
+ pyautogui.click(cx2, cy2, duration=${duration})
2904
+ print(f"Found '{word2}' at ({cx2},{cy2}) after retry \u2014 clicked")
2879
2905
  else:
2880
- print(f"Text '${safeText}' not found on screen. Take a screenshot to see current state.")
2906
+ print(f"Text '${safeText}' not found on screen after retry. Take a screenshot to see what changed.")
2881
2907
  sys.exit(1)
2882
- finally:
2883
- try:
2884
- os.remove(shot_path)
2885
- except Exception:
2886
- pass
2908
+ try:
2909
+ os.remove(shot_path)
2910
+ except Exception:
2911
+ pass
2887
2912
  `;
2888
2913
  }
2889
2914
  case "open_url": {
@@ -3221,6 +3246,7 @@ var init_AgentExecutor = __esm({
3221
3246
  break;
3222
3247
  }
3223
3248
  this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
3249
+ if (messages.length > 28) this._compressHistory(messages);
3224
3250
  let response;
3225
3251
  let llmFailed = false;
3226
3252
  {
@@ -3241,6 +3267,14 @@ var init_AgentExecutor = __esm({
3241
3267
  break;
3242
3268
  } catch (err) {
3243
3269
  const msg = err instanceof Error ? err.message : String(err);
3270
+ const isRateLimit = /RateLimit:\d+/.test(msg);
3271
+ if (isRateLimit) {
3272
+ const waitSec = parseInt(msg.split(":")[1] ?? "30", 10);
3273
+ const waitMs = Math.min(waitSec * 1e3, 12e4);
3274
+ this.onStep(`Rate limited \u2014 waiting ${waitSec}s before retry\u2026`);
3275
+ await new Promise((r) => setTimeout(r, waitMs));
3276
+ continue;
3277
+ }
3244
3278
  const isTimeout = /timeout|AbortError|aborted/i.test(msg);
3245
3279
  if (isTimeout && llmRetry < 2) {
3246
3280
  llmRetry++;
@@ -3274,6 +3308,11 @@ var init_AgentExecutor = __esm({
3274
3308
  try {
3275
3309
  const capResult = await this.registry.execute(tc.name, tc.input, this.cwd, signal);
3276
3310
  result = capResult.output;
3311
+ const MAX_TOOL_OUTPUT = 4e3;
3312
+ if (result.length > MAX_TOOL_OUTPUT) {
3313
+ result = result.slice(0, MAX_TOOL_OUTPUT) + `
3314
+ [...${result.length - MAX_TOOL_OUTPUT} chars truncated]`;
3315
+ }
3277
3316
  if (capResult.fallback_used) {
3278
3317
  this.onStep(` (used fallback: ${capResult.fallback_used})`);
3279
3318
  }
@@ -3503,6 +3542,9 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
3503
3542
  `- For research tasks: use web_search first, then scrape_url for full page content`,
3504
3543
  `- Use relative paths from the working directory`,
3505
3544
  `- Be concise in your final response: state what was done and where to find it`,
3545
+ `- For tasks with 3+ distinct steps or multiple apps/services, BRIEFLY LIST the steps first, then execute one at a time`,
3546
+ `- CONFIRM BEFORE SENDING: Before sending any message (WhatsApp, email, Slack, SMS, tweet), show the user the exact text and recipient and wait for explicit confirmation`,
3547
+ `- CONFIRM BEFORE DELETING: Before deleting files, database records, or any data, state what will be deleted and confirm with the user`,
3506
3548
  ``,
3507
3549
  `\u2550\u2550\u2550 EXECUTION DISCIPLINE \u2014 follow strictly \u2550\u2550\u2550`,
3508
3550
  `- SEQUENTIAL: complete each step fully before starting the next. Never start step 2 while step 1 is still in progress.`,
@@ -3571,6 +3613,19 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
3571
3613
  if (extra) lines.push(``, `Context:`, extra);
3572
3614
  return lines.join("\n");
3573
3615
  }
3616
+ _compressHistory(messages) {
3617
+ const KEEP_TAIL = 14;
3618
+ if (messages.length <= KEEP_TAIL + 2) return;
3619
+ const head = messages.slice(0, 1);
3620
+ const tail = messages.slice(-KEEP_TAIL);
3621
+ const middle = messages.slice(1, -KEEP_TAIL);
3622
+ const toolResults = middle.filter((m) => m.role === "tool").map((m) => String(m.content).slice(0, 120).replace(/\n/g, " ")).join(" | ");
3623
+ const summary = {
3624
+ role: "user",
3625
+ content: `[Earlier context compressed \u2014 ${middle.length} messages. Key tool results: ${toolResults.slice(0, 600)}]`
3626
+ };
3627
+ messages.splice(0, messages.length, ...head, summary, ...tail);
3628
+ }
3574
3629
  /** Returns true if task is a self-modification request. Self-mod tasks get longer LLM timeouts. */
3575
3630
  isSelfModTask(task) {
3576
3631
  return SELF_MOD_PATTERN.test(task);
@@ -4471,6 +4526,10 @@ var LLMExecutor = class {
4471
4526
  signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
4472
4527
  });
4473
4528
  if (!res.ok) {
4529
+ if (res.status === 429) {
4530
+ const retryAfter = parseInt(res.headers.get("retry-after") ?? res.headers.get("x-ratelimit-reset-requests") ?? "30", 10);
4531
+ throw new Error(`RateLimit:${Math.min(retryAfter, 120)}`);
4532
+ }
4474
4533
  const err = await res.text();
4475
4534
  throw new Error(`Anthropic ${res.status}: ${err}`);
4476
4535
  }
@@ -4595,6 +4654,10 @@ var LLMExecutor = class {
4595
4654
  signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
4596
4655
  });
4597
4656
  if (!res.ok) {
4657
+ if (res.status === 429) {
4658
+ const retryAfter = parseInt(res.headers.get("retry-after") ?? "30", 10);
4659
+ throw new Error(`RateLimit:${Math.min(retryAfter, 120)}`);
4660
+ }
4598
4661
  const err = await res.text();
4599
4662
  throw new Error(`OpenAI ${res.status}: ${err}`);
4600
4663
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
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",