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 +126 -6
- package/dist/daemon.mjs +75 -12
- package/package.json +1 -1
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
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
|
|
2906
|
+
print(f"Text '${safeText}' not found on screen after retry. Take a screenshot to see what changed.")
|
|
2881
2907
|
sys.exit(1)
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
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
|
}
|