0agent 1.0.50 → 1.0.55
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 +3 -3
- package/bin/chat.js +39 -11
- package/bin/postinstall.js +76 -0
- package/bin/preuninstall.js +62 -0
- package/dist/daemon.mjs +247 -63
- package/package.json +7 -6
package/bin/0agent.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* 0agent improve # self-improvement analysis
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync } from 'node:fs';
|
|
23
23
|
import { resolve, dirname } from 'node:path';
|
|
24
24
|
import { homedir, platform } from 'node:os';
|
|
25
25
|
import { spawn, execSync } from 'node:child_process';
|
|
@@ -431,10 +431,10 @@ async function startDaemon() {
|
|
|
431
431
|
|
|
432
432
|
mkdirSync(resolve(AGENT_DIR, 'logs'), { recursive: true });
|
|
433
433
|
|
|
434
|
-
const
|
|
434
|
+
const logFd = openSync(LOG_PATH, 'w');
|
|
435
435
|
const child = spawn(process.execPath, [startScript], {
|
|
436
436
|
detached: true,
|
|
437
|
-
stdio: ['ignore',
|
|
437
|
+
stdio: ['ignore', logFd, logFd],
|
|
438
438
|
env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH },
|
|
439
439
|
});
|
|
440
440
|
child.unref();
|
package/bin/chat.js
CHANGED
|
@@ -1192,7 +1192,14 @@ process.stdin.on('keypress', (_char, key) => {
|
|
|
1192
1192
|
sessionId = null;
|
|
1193
1193
|
streaming = false;
|
|
1194
1194
|
streamLineCount = 0;
|
|
1195
|
-
messageQueue.length = 0;
|
|
1195
|
+
messageQueue.length = 0;
|
|
1196
|
+
|
|
1197
|
+
// Kill any OS-level processes spawned by GUI/shell capabilities
|
|
1198
|
+
// (python3 GUI scripts, bash subprocesses) so nothing keeps running
|
|
1199
|
+
import('node:child_process').then(({ execSync: _exec }) => {
|
|
1200
|
+
try { _exec('pkill -f "0agent_gui_" 2>/dev/null; pkill -f "0agent-bg-" 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
|
|
1201
|
+
}).catch(() => {});
|
|
1202
|
+
|
|
1196
1203
|
res();
|
|
1197
1204
|
rl.prompt();
|
|
1198
1205
|
}
|
|
@@ -1206,24 +1213,36 @@ connectWS();
|
|
|
1206
1213
|
|
|
1207
1214
|
// ── Startup: ensure fresh daemon + verify LLM ────────────────────────────────
|
|
1208
1215
|
async function _spawnDaemon() {
|
|
1209
|
-
const pkgRoot
|
|
1216
|
+
const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
|
|
1210
1217
|
const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
|
|
1211
|
-
|
|
1218
|
+
const devPath = resolve(pkgRoot, 'packages', 'daemon', 'dist', 'start.js');
|
|
1219
|
+
const daemonScript = existsSync(bundled) ? bundled : existsSync(devPath) ? devPath : null;
|
|
1220
|
+
|
|
1221
|
+
if (!daemonScript) return 'no-bundle';
|
|
1222
|
+
if (!existsSync(CONFIG_PATH)) return 'no-config';
|
|
1223
|
+
|
|
1212
1224
|
const { spawn } = await import('node:child_process');
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1225
|
+
const { openSync: fsOpen, mkdirSync: fsMkdir } = await import('node:fs');
|
|
1226
|
+
const logDir = resolve(AGENT_DIR, 'logs');
|
|
1227
|
+
fsMkdir(logDir, { recursive: true });
|
|
1228
|
+
const logFd = fsOpen(resolve(logDir, 'daemon.log'), 'w');
|
|
1229
|
+
|
|
1230
|
+
const child = spawn(process.execPath, [daemonScript], {
|
|
1231
|
+
detached: true,
|
|
1232
|
+
stdio: ['ignore', logFd, logFd],
|
|
1215
1233
|
env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH },
|
|
1216
1234
|
});
|
|
1217
1235
|
child.unref();
|
|
1236
|
+
|
|
1218
1237
|
// Wait up to 10s for daemon to be ready
|
|
1219
1238
|
for (let i = 0; i < 20; i++) {
|
|
1220
1239
|
await new Promise(r => setTimeout(r, 500));
|
|
1221
1240
|
try {
|
|
1222
1241
|
await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) });
|
|
1223
|
-
return
|
|
1242
|
+
return 'ok';
|
|
1224
1243
|
} catch {}
|
|
1225
1244
|
}
|
|
1226
|
-
return
|
|
1245
|
+
return 'timeout';
|
|
1227
1246
|
}
|
|
1228
1247
|
|
|
1229
1248
|
async function _safeJsonFetch(url, opts) {
|
|
@@ -1256,23 +1275,32 @@ async function _safeJsonFetch(url, opts) {
|
|
|
1256
1275
|
// Daemon not running at all
|
|
1257
1276
|
}
|
|
1258
1277
|
|
|
1278
|
+
let spawnResult = 'ok';
|
|
1259
1279
|
if (needsRestart) {
|
|
1260
1280
|
startSpin.start('Restarting daemon (new version)');
|
|
1261
|
-
// Kill old daemon
|
|
1262
1281
|
try {
|
|
1263
1282
|
const { execSync } = await import('node:child_process');
|
|
1264
1283
|
execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' });
|
|
1265
1284
|
} catch {}
|
|
1266
1285
|
await new Promise(r => setTimeout(r, 800));
|
|
1267
|
-
|
|
1286
|
+
spawnResult = await _spawnDaemon();
|
|
1287
|
+
daemonOk = spawnResult === 'ok';
|
|
1268
1288
|
} else if (!daemonOk) {
|
|
1269
1289
|
startSpin.start('Starting daemon');
|
|
1270
|
-
|
|
1290
|
+
spawnResult = await _spawnDaemon();
|
|
1291
|
+
daemonOk = spawnResult === 'ok';
|
|
1271
1292
|
}
|
|
1272
1293
|
|
|
1273
1294
|
startSpin.stop();
|
|
1274
1295
|
if (!daemonOk) {
|
|
1275
|
-
|
|
1296
|
+
if (spawnResult === 'no-config') {
|
|
1297
|
+
console.log(` ${fmt(C.yellow, '!')} Not configured yet. Run: ${fmt(C.bold, '0agent init')}`);
|
|
1298
|
+
} else if (spawnResult === 'no-bundle') {
|
|
1299
|
+
console.log(` ${fmt(C.red, '✗')} Daemon bundle missing. Reinstall: ${fmt(C.bold, 'npm i -g 0agent@latest')}`);
|
|
1300
|
+
} else {
|
|
1301
|
+
const logPath = resolve(AGENT_DIR, 'logs', 'daemon.log');
|
|
1302
|
+
console.log(` ${fmt(C.red, '✗')} Daemon failed to start. Check logs: ${fmt(C.dim, logPath)}`);
|
|
1303
|
+
}
|
|
1276
1304
|
rl.prompt();
|
|
1277
1305
|
return;
|
|
1278
1306
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall — runs automatically after `npm install -g 0agent`
|
|
4
|
+
*
|
|
5
|
+
* Ensures all runtime dependencies are installed in the package directory.
|
|
6
|
+
* This handles cases where native modules (better-sqlite3) failed to build,
|
|
7
|
+
* or where the package was installed in a non-standard way.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { resolve, dirname } from 'node:path';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
|
|
17
|
+
// Runtime deps that must be present (matches bundle externals in scripts/bundle.mjs)
|
|
18
|
+
const REQUIRED = [
|
|
19
|
+
'better-sqlite3',
|
|
20
|
+
'hono',
|
|
21
|
+
'@hono/node-server',
|
|
22
|
+
'ws',
|
|
23
|
+
'yaml',
|
|
24
|
+
'zod',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function depInstalled(name) {
|
|
28
|
+
// Handle scoped packages like @hono/node-server
|
|
29
|
+
const modPath = resolve(pkgRoot, 'node_modules', name);
|
|
30
|
+
return existsSync(modPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const missing = REQUIRED.filter(d => !depInstalled(d));
|
|
34
|
+
|
|
35
|
+
if (missing.length === 0) {
|
|
36
|
+
// All present — check if better-sqlite3 native binary actually loads
|
|
37
|
+
try {
|
|
38
|
+
const { createRequire } = await import('node:module');
|
|
39
|
+
const req = createRequire(import.meta.url);
|
|
40
|
+
req('better-sqlite3');
|
|
41
|
+
} catch {
|
|
42
|
+
// Binary broken — try rebuild
|
|
43
|
+
try {
|
|
44
|
+
process.stdout.write(' Rebuilding better-sqlite3 for this platform…\n');
|
|
45
|
+
execSync('npm rebuild better-sqlite3', {
|
|
46
|
+
cwd: pkgRoot,
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
timeout: 60_000,
|
|
49
|
+
});
|
|
50
|
+
} catch {
|
|
51
|
+
process.stdout.write(
|
|
52
|
+
' ⚠ Could not rebuild better-sqlite3. Memory persistence will be disabled.\n' +
|
|
53
|
+
' If you need it, install build tools:\n' +
|
|
54
|
+
' macOS: xcode-select --install\n' +
|
|
55
|
+
' Linux: sudo apt-get install build-essential python3\n'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
process.stdout.write(` Installing dependencies: ${missing.join(', ')}\n`);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
execSync(
|
|
66
|
+
`npm install --omit=dev --prefix "${pkgRoot}" ${missing.join(' ')}`,
|
|
67
|
+
{ stdio: 'inherit', timeout: 120_000 }
|
|
68
|
+
);
|
|
69
|
+
process.stdout.write(' ✓ Dependencies installed\n');
|
|
70
|
+
} catch (err) {
|
|
71
|
+
process.stderr.write(
|
|
72
|
+
` ✗ Failed to install some dependencies: ${err.message}\n` +
|
|
73
|
+
` Try manually: npm install --prefix "${pkgRoot}" ${missing.join(' ')}\n`
|
|
74
|
+
);
|
|
75
|
+
// Don't exit non-zero — let the agent start anyway; daemon will log the actual error
|
|
76
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* preuninstall — runs automatically before `npm uninstall -g 0agent`
|
|
4
|
+
*
|
|
5
|
+
* Kills the daemon and ALL processes spawned by 0agent so nothing
|
|
6
|
+
* keeps running (e.g. opening Brave) after the package is removed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'node:child_process';
|
|
10
|
+
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
|
|
14
|
+
const AGENT_DIR = resolve(homedir(), '.0agent');
|
|
15
|
+
const PID_PATH = resolve(AGENT_DIR, 'daemon.pid');
|
|
16
|
+
|
|
17
|
+
function run(cmd) {
|
|
18
|
+
try { execSync(cmd, { stdio: 'ignore', timeout: 5000 }); } catch {}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.stdout.write(' Stopping 0agent daemon and background processes…\n');
|
|
22
|
+
|
|
23
|
+
// 1. Kill daemon by PID file
|
|
24
|
+
if (existsSync(PID_PATH)) {
|
|
25
|
+
try {
|
|
26
|
+
const pid = parseInt(readFileSync(PID_PATH, 'utf8').trim(), 10);
|
|
27
|
+
if (!isNaN(pid)) process.kill(pid, 'SIGTERM');
|
|
28
|
+
} catch {}
|
|
29
|
+
try { unlinkSync(PID_PATH); } catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2. Kill by process name (catches daemons started by chat.js or other means)
|
|
33
|
+
run('pkill -f "daemon.mjs" 2>/dev/null; true');
|
|
34
|
+
|
|
35
|
+
// 3. Kill any GUI Python scripts still running
|
|
36
|
+
run('pkill -f "0agent_gui_" 2>/dev/null; true');
|
|
37
|
+
run('pkill -f "0agent-bg-" 2>/dev/null; true');
|
|
38
|
+
|
|
39
|
+
// 4. Free port 4200 (last resort)
|
|
40
|
+
run('lsof -ti:4200 | xargs kill -9 2>/dev/null; true');
|
|
41
|
+
|
|
42
|
+
// 5. Remove any launchd plists that 0agent may have registered
|
|
43
|
+
// (prevents apps from being re-launched on login)
|
|
44
|
+
const launchAgentsDir = resolve(homedir(), 'Library', 'LaunchAgents');
|
|
45
|
+
if (existsSync(launchAgentsDir)) {
|
|
46
|
+
try {
|
|
47
|
+
const { readdirSync } = await import('node:fs');
|
|
48
|
+
for (const f of readdirSync(launchAgentsDir)) {
|
|
49
|
+
if (f.includes('0agent') || f.includes('zeroagent')) {
|
|
50
|
+
const plistPath = resolve(launchAgentsDir, f);
|
|
51
|
+
run(`launchctl unload "${plistPath}" 2>/dev/null; true`);
|
|
52
|
+
try { unlinkSync(plistPath); } catch {}
|
|
53
|
+
process.stdout.write(` Removed launchd plist: ${f}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 6. Remove 0agent crontab entries (if any were ever added)
|
|
60
|
+
run('crontab -l 2>/dev/null | grep -v "0agent" | crontab - 2>/dev/null; true');
|
|
61
|
+
|
|
62
|
+
process.stdout.write(' ✓ 0agent stopped and cleaned up\n');
|
package/dist/daemon.mjs
CHANGED
|
@@ -2348,7 +2348,7 @@ var ShellCapability;
|
|
|
2348
2348
|
var init_ShellCapability = __esm({
|
|
2349
2349
|
"packages/daemon/src/capabilities/ShellCapability.ts"() {
|
|
2350
2350
|
"use strict";
|
|
2351
|
-
ShellCapability = class {
|
|
2351
|
+
ShellCapability = class _ShellCapability {
|
|
2352
2352
|
name = "shell_exec";
|
|
2353
2353
|
description = "Execute shell commands in the working directory.";
|
|
2354
2354
|
toolDefinition = {
|
|
@@ -2363,10 +2363,24 @@ var init_ShellCapability = __esm({
|
|
|
2363
2363
|
required: ["command"]
|
|
2364
2364
|
}
|
|
2365
2365
|
};
|
|
2366
|
-
|
|
2366
|
+
// Commands that create persistent OS-level scheduled tasks.
|
|
2367
|
+
// These must never run autonomously — they survive uninstall and can
|
|
2368
|
+
// re-open apps (e.g. Brave) on every login or on a timer.
|
|
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
|
+
async execute(input, cwd, signal) {
|
|
2367
2371
|
let command = String(input.command ?? "");
|
|
2368
2372
|
const timeout = Number(input.timeout_ms ?? 3e4);
|
|
2369
2373
|
const start = Date.now();
|
|
2374
|
+
if (_ShellCapability.PERSISTENT_TASK_PATTERN.test(command)) {
|
|
2375
|
+
return {
|
|
2376
|
+
success: false,
|
|
2377
|
+
output: `Blocked: "${command.slice(0, 80)}" creates a persistent scheduled task (cron/launchd/login item). These survive uninstall and can keep launching apps autonomously. Ask the user explicitly before scheduling any persistent OS task.`,
|
|
2378
|
+
duration_ms: 0
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
if (signal?.aborted) {
|
|
2382
|
+
return { success: false, output: "Cancelled.", duration_ms: 0 };
|
|
2383
|
+
}
|
|
2370
2384
|
if (/&\s*$/.test(command) && !/[>|].*&\s*$/.test(command)) {
|
|
2371
2385
|
const logFile = `/tmp/0agent-bg-${Date.now()}.log`;
|
|
2372
2386
|
command = command.replace(/\s*&\s*$/, ` > ${logFile} 2>&1 &`);
|
|
@@ -2374,30 +2388,43 @@ var init_ShellCapability = __esm({
|
|
|
2374
2388
|
return new Promise((resolve_) => {
|
|
2375
2389
|
const chunks = [];
|
|
2376
2390
|
let settled = false;
|
|
2377
|
-
const done = (code,
|
|
2391
|
+
const done = (code, killedBySignal) => {
|
|
2378
2392
|
if (settled) return;
|
|
2379
2393
|
settled = true;
|
|
2394
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2380
2395
|
clearTimeout(timer);
|
|
2381
2396
|
const output = chunks.join("").trim();
|
|
2382
|
-
const success = code === 0 || code === null && !!
|
|
2397
|
+
const success = code === 0 || code === null && !!killedBySignal;
|
|
2383
2398
|
resolve_({
|
|
2384
2399
|
success,
|
|
2385
|
-
output: output || (success ? "(no output)" : `exit ${code ??
|
|
2400
|
+
output: output || (success ? "(no output)" : `exit ${code ?? killedBySignal}`),
|
|
2386
2401
|
duration_ms: Date.now() - start,
|
|
2387
|
-
...!success && { error: `exit ${code ??
|
|
2402
|
+
...!success && { error: `exit ${code ?? killedBySignal}` }
|
|
2388
2403
|
});
|
|
2389
2404
|
};
|
|
2390
2405
|
const proc = spawn2("bash", ["-c", command], {
|
|
2391
2406
|
cwd,
|
|
2392
2407
|
env: { ...process.env, TERM: "dumb" }
|
|
2393
|
-
// DO NOT set `timeout` here — we manage it manually via timer
|
|
2394
|
-
// so we can resolve on `exit` rather than waiting for `close`
|
|
2395
2408
|
});
|
|
2409
|
+
const onAbort = () => {
|
|
2410
|
+
try {
|
|
2411
|
+
proc.kill("SIGKILL");
|
|
2412
|
+
} catch {
|
|
2413
|
+
}
|
|
2414
|
+
if (!settled) {
|
|
2415
|
+
settled = true;
|
|
2416
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2417
|
+
clearTimeout(timer);
|
|
2418
|
+
resolve_({ success: false, output: chunks.join("").trim() || "Cancelled.", duration_ms: Date.now() - start });
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2396
2422
|
proc.stdout.on("data", (d) => chunks.push(d.toString()));
|
|
2397
2423
|
proc.stderr.on("data", (d) => chunks.push(d.toString()));
|
|
2398
2424
|
proc.on("exit", (code) => done(code));
|
|
2399
2425
|
proc.on("error", (err) => {
|
|
2400
2426
|
settled = true;
|
|
2427
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2401
2428
|
clearTimeout(timer);
|
|
2402
2429
|
resolve_({ success: false, output: err.message, error: err.message, duration_ms: Date.now() - start });
|
|
2403
2430
|
});
|
|
@@ -2408,6 +2435,7 @@ var init_ShellCapability = __esm({
|
|
|
2408
2435
|
} catch {
|
|
2409
2436
|
}
|
|
2410
2437
|
settled = true;
|
|
2438
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2411
2439
|
const output = chunks.join("").trim();
|
|
2412
2440
|
resolve_({
|
|
2413
2441
|
success: false,
|
|
@@ -2558,7 +2586,7 @@ var init_MemoryCapability = __esm({
|
|
|
2558
2586
|
});
|
|
2559
2587
|
|
|
2560
2588
|
// packages/daemon/src/capabilities/GUICapability.ts
|
|
2561
|
-
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
2589
|
+
import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
|
|
2562
2590
|
import { writeFileSync as writeFileSync2, unlinkSync } from "node:fs";
|
|
2563
2591
|
import { resolve as resolve3 } from "node:path";
|
|
2564
2592
|
import { tmpdir, platform as platform2 } from "node:os";
|
|
@@ -2571,13 +2599,13 @@ var init_GUICapability = __esm({
|
|
|
2571
2599
|
description = "Automate desktop GUI \u2014 click, type, screenshot, hotkeys, find text on screen.";
|
|
2572
2600
|
toolDefinition = {
|
|
2573
2601
|
name: "gui_automation",
|
|
2574
|
-
description: 'Automate desktop GUI interactions. Take screenshots to see the current screen state, click on buttons/links/fields, type text, press keyboard shortcuts, scroll, open apps.
|
|
2602
|
+
description: 'Automate desktop GUI interactions. Take screenshots to see the current screen state, click on buttons/links/fields, type text, press keyboard shortcuts, scroll, open apps. IMPORTANT: Limit screenshots to at most 3 per task \u2014 avoid re-screenshotting if you already know the layout. Prefer targeted actions (click, find_and_click, hotkey) over repeated screenshots. Use get_cursor_pos to check cursor position without a full screenshot. To open a website, ALWAYS use action="open_url" \u2014 never open_app + new tab, which creates duplicate windows.',
|
|
2575
2603
|
input_schema: {
|
|
2576
2604
|
type: "object",
|
|
2577
2605
|
properties: {
|
|
2578
2606
|
action: {
|
|
2579
2607
|
type: "string",
|
|
2580
|
-
description: '"screenshot" | "click" | "double_click" | "right_click" | "move" | "type" | "hotkey" | "scroll" | "drag" | "find_and_click" | "get_screen_size" | "open_app"'
|
|
2608
|
+
description: '"screenshot" | "click" | "double_click" | "right_click" | "move" | "type" | "hotkey" | "scroll" | "drag" | "find_and_click" | "get_screen_size" | "get_cursor_pos" | "open_url" | "open_app"'
|
|
2581
2609
|
},
|
|
2582
2610
|
x: { type: "number", description: "X coordinate (pixels from left)" },
|
|
2583
2611
|
y: { type: "number", description: "Y coordinate (pixels from top)" },
|
|
@@ -2588,28 +2616,67 @@ var init_GUICapability = __esm({
|
|
|
2588
2616
|
direction: { type: "string", description: '"up" | "down" | "left" | "right" for scroll' },
|
|
2589
2617
|
amount: { type: "number", description: "Scroll clicks (default 3)" },
|
|
2590
2618
|
app: { type: "string", description: 'App name to open e.g. "Safari", "Terminal", "Chrome"' },
|
|
2619
|
+
url: { type: "string", description: 'URL to open e.g. "https://example.com" (use with open_url)' },
|
|
2591
2620
|
interval: { type: "number", description: "Seconds to wait between actions (default 0.05)" },
|
|
2592
2621
|
duration: { type: "number", description: "Seconds for mouse movement animation (default 0.2)" }
|
|
2593
2622
|
},
|
|
2594
2623
|
required: ["action"]
|
|
2595
2624
|
}
|
|
2596
2625
|
};
|
|
2597
|
-
async execute(input, _cwd) {
|
|
2626
|
+
async execute(input, _cwd, signal) {
|
|
2598
2627
|
const action = String(input.action ?? "").toLowerCase().trim();
|
|
2599
2628
|
const start = Date.now();
|
|
2600
2629
|
const script = this._buildScript(action, input);
|
|
2601
2630
|
if (!script) {
|
|
2602
|
-
return { success: false, output: `Unknown GUI action: "${action}". Valid: screenshot, click, double_click, right_click, move, type, hotkey, scroll, drag, find_and_click, get_screen_size, open_app`, duration_ms: 0 };
|
|
2631
|
+
return { success: false, output: `Unknown GUI action: "${action}". Valid: screenshot, click, double_click, right_click, move, type, hotkey, scroll, drag, find_and_click, get_screen_size, get_cursor_pos, open_url, open_app`, duration_ms: 0 };
|
|
2632
|
+
}
|
|
2633
|
+
if (signal?.aborted) {
|
|
2634
|
+
return { success: false, output: "Cancelled.", duration_ms: 0 };
|
|
2603
2635
|
}
|
|
2604
2636
|
const tmpFile = resolve3(tmpdir(), `0agent_gui_${Date.now()}.py`);
|
|
2605
2637
|
writeFileSync2(tmpFile, script, "utf8");
|
|
2606
|
-
const
|
|
2638
|
+
const runPy = (file) => new Promise((res) => {
|
|
2639
|
+
const proc = spawn3("python3", [file], { env: process.env });
|
|
2640
|
+
const out = [];
|
|
2641
|
+
const err = [];
|
|
2642
|
+
let settled = false;
|
|
2643
|
+
const finish = (code) => {
|
|
2644
|
+
if (settled) return;
|
|
2645
|
+
settled = true;
|
|
2646
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2647
|
+
clearTimeout(timer);
|
|
2648
|
+
res({ stdout: out.join(""), stderr: err.join(""), code });
|
|
2649
|
+
};
|
|
2650
|
+
const onAbort = () => {
|
|
2651
|
+
try {
|
|
2652
|
+
proc.kill("SIGKILL");
|
|
2653
|
+
} catch {
|
|
2654
|
+
}
|
|
2655
|
+
finish(null);
|
|
2656
|
+
};
|
|
2657
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2658
|
+
proc.stdout.on("data", (d) => out.push(d.toString()));
|
|
2659
|
+
proc.stderr.on("data", (d) => err.push(d.toString()));
|
|
2660
|
+
proc.on("exit", finish);
|
|
2661
|
+
proc.on("error", () => finish(-1));
|
|
2662
|
+
const timer = setTimeout(() => {
|
|
2663
|
+
try {
|
|
2664
|
+
proc.kill("SIGKILL");
|
|
2665
|
+
} catch {
|
|
2666
|
+
}
|
|
2667
|
+
finish(null);
|
|
2668
|
+
}, 3e4);
|
|
2669
|
+
});
|
|
2670
|
+
let result = await runPy(tmpFile);
|
|
2607
2671
|
try {
|
|
2608
2672
|
unlinkSync(tmpFile);
|
|
2609
2673
|
} catch {
|
|
2610
2674
|
}
|
|
2611
|
-
if (
|
|
2612
|
-
|
|
2675
|
+
if (signal?.aborted) {
|
|
2676
|
+
return { success: false, output: "Cancelled.", duration_ms: Date.now() - start };
|
|
2677
|
+
}
|
|
2678
|
+
if (result.code !== 0 && result.code !== null) {
|
|
2679
|
+
const err = result.stderr.trim();
|
|
2613
2680
|
if (err.includes("No module named") || err.includes("ModuleNotFoundError")) {
|
|
2614
2681
|
const missing = err.includes("pyautogui") ? "pyautogui pillow pytesseract" : err.includes("PIL") ? "pillow" : err.includes("tesseract") ? "pytesseract" : "pyautogui pillow";
|
|
2615
2682
|
const install = spawnSync4("pip3", ["install", ...missing.split(" "), "-q"], {
|
|
@@ -2619,17 +2686,15 @@ var init_GUICapability = __esm({
|
|
|
2619
2686
|
if (install.status !== 0) {
|
|
2620
2687
|
return { success: false, output: `Auto-install failed: ${install.stderr?.slice(0, 200)}. Run: pip3 install ${missing}`, duration_ms: Date.now() - start };
|
|
2621
2688
|
}
|
|
2622
|
-
const retry = spawnSync4("python3", [tmpFile], { timeout: 3e4, encoding: "utf8" });
|
|
2623
2689
|
writeFileSync2(tmpFile, script, "utf8");
|
|
2624
|
-
|
|
2690
|
+
result = await runPy(tmpFile);
|
|
2625
2691
|
try {
|
|
2626
2692
|
unlinkSync(tmpFile);
|
|
2627
2693
|
} catch {
|
|
2628
2694
|
}
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
}
|
|
2632
|
-
return { success: false, output: retry2.stderr?.trim() || "Unknown error after install", duration_ms: Date.now() - start };
|
|
2695
|
+
if (signal?.aborted) return { success: false, output: "Cancelled.", duration_ms: Date.now() - start };
|
|
2696
|
+
if (result.code === 0) return { success: true, output: result.stdout.trim() || "Done", duration_ms: Date.now() - start };
|
|
2697
|
+
return { success: false, output: result.stderr.trim() || "Unknown error after install", duration_ms: Date.now() - start };
|
|
2633
2698
|
}
|
|
2634
2699
|
if (err.includes("accessibility") || err.includes("permission") || err.includes("AXIsProcessTrusted")) {
|
|
2635
2700
|
return {
|
|
@@ -2652,6 +2717,7 @@ var init_GUICapability = __esm({
|
|
|
2652
2717
|
const dir = input.direction != null ? String(input.direction) : "down";
|
|
2653
2718
|
const amount = input.amount != null ? Number(input.amount) : 3;
|
|
2654
2719
|
const app = input.app != null ? String(input.app) : "";
|
|
2720
|
+
const url = input.url != null ? String(input.url) : "";
|
|
2655
2721
|
const interval = input.interval != null ? Number(input.interval) : 0.05;
|
|
2656
2722
|
const duration = input.duration != null ? Number(input.duration) : 0.2;
|
|
2657
2723
|
const header = `
|
|
@@ -2666,6 +2732,11 @@ pyautogui.PAUSE = ${interval}
|
|
|
2666
2732
|
return header + `
|
|
2667
2733
|
w, h = pyautogui.size()
|
|
2668
2734
|
print(f"Screen size: {w} x {h}")
|
|
2735
|
+
`;
|
|
2736
|
+
case "get_cursor_pos":
|
|
2737
|
+
return header + `
|
|
2738
|
+
x, y = pyautogui.position()
|
|
2739
|
+
print(f"Cursor position: ({x}, {y})")
|
|
2669
2740
|
`;
|
|
2670
2741
|
case "screenshot": {
|
|
2671
2742
|
return header + `
|
|
@@ -2705,10 +2776,13 @@ try:
|
|
|
2705
2776
|
print("\\n".join(hits[:40]))
|
|
2706
2777
|
except ImportError:
|
|
2707
2778
|
print("(pytesseract not installed \u2014 install it for OCR: pip3 install pytesseract)")
|
|
2708
|
-
print(f"Screenshot saved: {shot_path}")
|
|
2709
2779
|
except Exception as e:
|
|
2710
2780
|
print(f"OCR failed: {e}")
|
|
2711
|
-
|
|
2781
|
+
finally:
|
|
2782
|
+
try:
|
|
2783
|
+
os.remove(shot_path)
|
|
2784
|
+
except Exception:
|
|
2785
|
+
pass
|
|
2712
2786
|
`;
|
|
2713
2787
|
}
|
|
2714
2788
|
case "click":
|
|
@@ -2790,13 +2864,107 @@ for i, word in enumerate(data['text']):
|
|
|
2790
2864
|
cy = data['top'][i] + data['height'][i] // 2
|
|
2791
2865
|
found.append((cx, cy, word))
|
|
2792
2866
|
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2867
|
+
try:
|
|
2868
|
+
if found:
|
|
2869
|
+
cx, cy, word = found[0]
|
|
2870
|
+
pyautogui.click(cx, cy, duration=${duration})
|
|
2871
|
+
print(f"Found '{word}' at ({cx},{cy}) \u2014 clicked")
|
|
2872
|
+
else:
|
|
2873
|
+
print(f"Text '${safeText}' not found on screen. Take a screenshot to see current state.")
|
|
2874
|
+
sys.exit(1)
|
|
2875
|
+
finally:
|
|
2876
|
+
try:
|
|
2877
|
+
os.remove(shot_path)
|
|
2878
|
+
except Exception:
|
|
2879
|
+
pass
|
|
2880
|
+
`;
|
|
2881
|
+
}
|
|
2882
|
+
case "open_url": {
|
|
2883
|
+
if (!url) return null;
|
|
2884
|
+
const safeUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2885
|
+
const osName = platform2();
|
|
2886
|
+
if (osName === "darwin") {
|
|
2887
|
+
return header + `
|
|
2888
|
+
import subprocess
|
|
2889
|
+
|
|
2890
|
+
url = '${safeUrl}'
|
|
2891
|
+
|
|
2892
|
+
# Check if Chrome is running
|
|
2893
|
+
chrome_running = subprocess.run(['pgrep', '-x', 'Google Chrome'], capture_output=True).returncode == 0
|
|
2894
|
+
firefox_running = subprocess.run(['pgrep', '-x', 'firefox'], capture_output=True).returncode == 0
|
|
2895
|
+
safari_running = subprocess.run(['pgrep', '-x', 'Safari'], capture_output=True).returncode == 0
|
|
2896
|
+
|
|
2897
|
+
import urllib.parse
|
|
2898
|
+
domain = urllib.parse.urlparse(url).netloc
|
|
2899
|
+
|
|
2900
|
+
if chrome_running:
|
|
2901
|
+
# Check if URL domain is already open in an existing tab \u2014 switch to it instead of opening new tab
|
|
2902
|
+
check_script = f"""
|
|
2903
|
+
tell application "Google Chrome"
|
|
2904
|
+
set foundTab to false
|
|
2905
|
+
repeat with w in every window
|
|
2906
|
+
set tabIdx to 1
|
|
2907
|
+
repeat with t in every tab of w
|
|
2908
|
+
if URL of t contains "{domain}" then
|
|
2909
|
+
set active tab index of w to tabIdx
|
|
2910
|
+
set index of w to 1
|
|
2911
|
+
set foundTab to true
|
|
2912
|
+
exit repeat
|
|
2913
|
+
end if
|
|
2914
|
+
set tabIdx to tabIdx + 1
|
|
2915
|
+
end repeat
|
|
2916
|
+
if foundTab then exit repeat
|
|
2917
|
+
end repeat
|
|
2918
|
+
if foundTab then
|
|
2919
|
+
activate
|
|
2920
|
+
return "switched"
|
|
2921
|
+
else
|
|
2922
|
+
tell front window to make new tab with properties {{URL:"{url}"}}
|
|
2923
|
+
activate
|
|
2924
|
+
return "new-tab"
|
|
2925
|
+
end if
|
|
2926
|
+
end tell"""
|
|
2927
|
+
r = subprocess.run(['osascript', '-e', check_script], capture_output=True, text=True)
|
|
2928
|
+
if r.stdout.strip() == "switched":
|
|
2929
|
+
print(f"Switched to existing Chrome tab: {url}")
|
|
2930
|
+
else:
|
|
2931
|
+
print(f"Opened new Chrome tab: {url}")
|
|
2932
|
+
elif firefox_running:
|
|
2933
|
+
script = f'tell application "Firefox" to open location "{url}"'
|
|
2934
|
+
subprocess.run(['osascript', '-e', script])
|
|
2935
|
+
subprocess.run(['osascript', '-e', 'tell application "Firefox" to activate'])
|
|
2936
|
+
print(f"Navigated Firefox to: {url}")
|
|
2937
|
+
elif safari_running:
|
|
2938
|
+
script = f'tell application "Safari" to open location "{url}"'
|
|
2939
|
+
subprocess.run(['osascript', '-e', script])
|
|
2940
|
+
subprocess.run(['osascript', '-e', 'tell application "Safari" to activate'])
|
|
2941
|
+
print(f"Navigated Safari to: {url}")
|
|
2797
2942
|
else:
|
|
2798
|
-
|
|
2799
|
-
|
|
2943
|
+
# No browser open \u2014 launch default browser with the URL
|
|
2944
|
+
subprocess.run(['open', url])
|
|
2945
|
+
print(f"Launched browser with: {url}")
|
|
2946
|
+
time.sleep(1.0)
|
|
2947
|
+
`;
|
|
2948
|
+
}
|
|
2949
|
+
return header + `
|
|
2950
|
+
import subprocess
|
|
2951
|
+
|
|
2952
|
+
url = '${safeUrl}'
|
|
2953
|
+
|
|
2954
|
+
# Try to reuse existing browser via wmctrl/xdotool, fall back to xdg-open
|
|
2955
|
+
chrome_pid = subprocess.run(['pgrep', '-x', 'chrome'], capture_output=True)
|
|
2956
|
+
firefox_pid = subprocess.run(['pgrep', '-x', 'firefox'], capture_output=True)
|
|
2957
|
+
|
|
2958
|
+
if chrome_pid.returncode == 0:
|
|
2959
|
+
subprocess.Popen(['google-chrome', '--new-tab', url])
|
|
2960
|
+
print(f"Opened in Chrome tab: {url}")
|
|
2961
|
+
elif firefox_pid.returncode == 0:
|
|
2962
|
+
subprocess.Popen(['firefox', '--new-tab', url])
|
|
2963
|
+
print(f"Opened in Firefox tab: {url}")
|
|
2964
|
+
else:
|
|
2965
|
+
subprocess.Popen(['xdg-open', url])
|
|
2966
|
+
print(f"Opened with default browser: {url}")
|
|
2967
|
+
time.sleep(1.0)
|
|
2800
2968
|
`;
|
|
2801
2969
|
}
|
|
2802
2970
|
case "open_app": {
|
|
@@ -2963,13 +3131,13 @@ var init_CapabilityRegistry = __esm({
|
|
|
2963
3131
|
getToolDefinitions() {
|
|
2964
3132
|
return [...this.capabilities.values()].map((c) => c.toolDefinition);
|
|
2965
3133
|
}
|
|
2966
|
-
async execute(toolName, input, cwd) {
|
|
3134
|
+
async execute(toolName, input, cwd, signal) {
|
|
2967
3135
|
const cap = this.capabilities.get(toolName);
|
|
2968
3136
|
if (!cap) {
|
|
2969
3137
|
return { success: false, output: `Unknown capability: ${toolName}`, duration_ms: 0 };
|
|
2970
3138
|
}
|
|
2971
3139
|
try {
|
|
2972
|
-
return await cap.execute(input, cwd);
|
|
3140
|
+
return await cap.execute(input, cwd, signal);
|
|
2973
3141
|
} catch (err) {
|
|
2974
3142
|
return {
|
|
2975
3143
|
success: false,
|
|
@@ -2999,7 +3167,7 @@ var init_capabilities = __esm({
|
|
|
2999
3167
|
});
|
|
3000
3168
|
|
|
3001
3169
|
// packages/daemon/src/AgentExecutor.ts
|
|
3002
|
-
import { spawn as
|
|
3170
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
3003
3171
|
import { writeFileSync as writeFileSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "node:fs";
|
|
3004
3172
|
import { resolve as resolve4, dirname as dirname2, relative } from "node:path";
|
|
3005
3173
|
var SELF_MOD_PATTERN, AgentExecutor;
|
|
@@ -3009,8 +3177,8 @@ var init_AgentExecutor = __esm({
|
|
|
3009
3177
|
init_capabilities();
|
|
3010
3178
|
SELF_MOD_PATTERN = /\b(yourself|the agent|this agent|this cli|0agent|your code|your source|agent cli|improve.*agent|update.*agent|add.*to.*agent|fix.*agent|self.?improv)\b/i;
|
|
3011
3179
|
AgentExecutor = class {
|
|
3012
|
-
constructor(
|
|
3013
|
-
this.llm =
|
|
3180
|
+
constructor(llm, config, onStep, onToken) {
|
|
3181
|
+
this.llm = llm;
|
|
3014
3182
|
this.config = config;
|
|
3015
3183
|
this.onStep = onStep;
|
|
3016
3184
|
this.onToken = onToken;
|
|
@@ -3025,7 +3193,7 @@ var init_AgentExecutor = __esm({
|
|
|
3025
3193
|
maxCommandMs;
|
|
3026
3194
|
registry;
|
|
3027
3195
|
agentRoot;
|
|
3028
|
-
async execute(task, systemContext) {
|
|
3196
|
+
async execute(task, systemContext, signal) {
|
|
3029
3197
|
const filesWritten = [];
|
|
3030
3198
|
const commandsRun = [];
|
|
3031
3199
|
let totalTokens = 0;
|
|
@@ -3041,6 +3209,10 @@ var init_AgentExecutor = __esm({
|
|
|
3041
3209
|
}
|
|
3042
3210
|
let finalOutput = "";
|
|
3043
3211
|
for (let i = 0; i < this.maxIterations; i++) {
|
|
3212
|
+
if (signal?.aborted) {
|
|
3213
|
+
finalOutput = "Cancelled.";
|
|
3214
|
+
break;
|
|
3215
|
+
}
|
|
3044
3216
|
this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
|
|
3045
3217
|
let response;
|
|
3046
3218
|
let llmFailed = false;
|
|
@@ -3056,7 +3228,8 @@ var init_AgentExecutor = __esm({
|
|
|
3056
3228
|
(token) => {
|
|
3057
3229
|
this.onToken(token);
|
|
3058
3230
|
finalOutput += token;
|
|
3059
|
-
}
|
|
3231
|
+
},
|
|
3232
|
+
signal
|
|
3060
3233
|
);
|
|
3061
3234
|
break;
|
|
3062
3235
|
} catch (err) {
|
|
@@ -3092,7 +3265,7 @@ var init_AgentExecutor = __esm({
|
|
|
3092
3265
|
this.onStep(`\u25B6 ${tc.name}(${this.summariseInput(tc.name, tc.input)})`);
|
|
3093
3266
|
let result;
|
|
3094
3267
|
try {
|
|
3095
|
-
const capResult = await this.registry.execute(tc.name, tc.input, this.cwd);
|
|
3268
|
+
const capResult = await this.registry.execute(tc.name, tc.input, this.cwd, signal);
|
|
3096
3269
|
result = capResult.output;
|
|
3097
3270
|
if (capResult.fallback_used) {
|
|
3098
3271
|
this.onStep(` (used fallback: ${capResult.fallback_used})`);
|
|
@@ -3156,7 +3329,7 @@ var init_AgentExecutor = __esm({
|
|
|
3156
3329
|
shellExec(command, timeoutMs) {
|
|
3157
3330
|
return new Promise((resolve16) => {
|
|
3158
3331
|
const chunks = [];
|
|
3159
|
-
const proc =
|
|
3332
|
+
const proc = spawn4("bash", ["-c", command], {
|
|
3160
3333
|
cwd: this.cwd,
|
|
3161
3334
|
env: { ...process.env, TERM: "dumb" },
|
|
3162
3335
|
timeout: timeoutMs
|
|
@@ -3475,7 +3648,7 @@ var init_ExecutionVerifier = __esm({
|
|
|
3475
3648
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
|
|
3476
3649
|
import { resolve as resolve6, dirname as dirname3 } from "node:path";
|
|
3477
3650
|
import { fileURLToPath } from "node:url";
|
|
3478
|
-
import { execSync as execSync4, spawn as
|
|
3651
|
+
import { execSync as execSync4, spawn as spawn5 } from "node:child_process";
|
|
3479
3652
|
function isRuntimeBug(error) {
|
|
3480
3653
|
if (TASK_FAILURE_PATTERNS.some((p) => p.test(error))) return false;
|
|
3481
3654
|
return RUNTIME_BUG_PATTERNS.some((p) => p.test(error));
|
|
@@ -3541,8 +3714,8 @@ var init_RuntimeSelfHeal = __esm({
|
|
|
3541
3714
|
// network issue
|
|
3542
3715
|
];
|
|
3543
3716
|
RuntimeSelfHeal = class {
|
|
3544
|
-
constructor(
|
|
3545
|
-
this.llm =
|
|
3717
|
+
constructor(llm, eventBus) {
|
|
3718
|
+
this.llm = llm;
|
|
3546
3719
|
this.eventBus = eventBus;
|
|
3547
3720
|
let dir = dirname3(fileURLToPath(import.meta.url));
|
|
3548
3721
|
while (dir !== "/" && !existsSync6(resolve6(dir, "package.json"))) {
|
|
@@ -3710,7 +3883,7 @@ Rules:
|
|
|
3710
3883
|
restartDaemon() {
|
|
3711
3884
|
const bundlePath = resolve6(this.projectRoot, "dist", "daemon.mjs");
|
|
3712
3885
|
if (existsSync6(bundlePath)) {
|
|
3713
|
-
const child =
|
|
3886
|
+
const child = spawn5(process.execPath, [bundlePath], {
|
|
3714
3887
|
detached: true,
|
|
3715
3888
|
stdio: "ignore",
|
|
3716
3889
|
env: process.env
|
|
@@ -3736,8 +3909,8 @@ var init_SelfHealLoop = __esm({
|
|
|
3736
3909
|
init_ExecutionVerifier();
|
|
3737
3910
|
init_RuntimeSelfHeal();
|
|
3738
3911
|
SelfHealLoop = class {
|
|
3739
|
-
constructor(
|
|
3740
|
-
this.llm =
|
|
3912
|
+
constructor(llm, config, onStep, onToken, maxAttempts = 3, runtimeHealer) {
|
|
3913
|
+
this.llm = llm;
|
|
3741
3914
|
this.config = config;
|
|
3742
3915
|
this.onStep = onStep;
|
|
3743
3916
|
this.onToken = onToken;
|
|
@@ -3746,17 +3919,18 @@ var init_SelfHealLoop = __esm({
|
|
|
3746
3919
|
this.verifier = new ExecutionVerifier(config.cwd);
|
|
3747
3920
|
}
|
|
3748
3921
|
verifier;
|
|
3749
|
-
async executeWithHealing(task, systemContext) {
|
|
3922
|
+
async executeWithHealing(task, systemContext, signal) {
|
|
3750
3923
|
const attempts = [];
|
|
3751
3924
|
let currentContext = systemContext;
|
|
3752
3925
|
let finalResult = null;
|
|
3753
3926
|
let lastVerification = { success: true, method: "none", details: "", retryable: false, elapsed_ms: 0 };
|
|
3754
3927
|
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
3928
|
+
if (signal?.aborted) break;
|
|
3755
3929
|
if (attempt > 1) {
|
|
3756
3930
|
this.onStep(`\u21BA Self-healing (attempt ${attempt}/${this.maxAttempts}): ${lastVerification.details}`);
|
|
3757
3931
|
}
|
|
3758
3932
|
const executor = new AgentExecutor(this.llm, this.config, this.onStep, this.onToken);
|
|
3759
|
-
const result = await executor.execute(task, currentContext);
|
|
3933
|
+
const result = await executor.execute(task, currentContext, signal);
|
|
3760
3934
|
finalResult = result;
|
|
3761
3935
|
lastVerification = await this.verifier.verify(result);
|
|
3762
3936
|
attempts.push({ attempt_number: attempt, error_context: currentContext ?? "", result, verification: lastVerification });
|
|
@@ -4207,24 +4381,24 @@ var LLMExecutor = class {
|
|
|
4207
4381
|
return { content: res.content, tokens_used: res.tokens_used, model: res.model };
|
|
4208
4382
|
}
|
|
4209
4383
|
// ─── Tool-calling completion with optional streaming ─────────────────────
|
|
4210
|
-
async completeWithTools(messages, tools, system, onToken) {
|
|
4384
|
+
async completeWithTools(messages, tools, system, onToken, signal) {
|
|
4211
4385
|
switch (this.config.provider) {
|
|
4212
4386
|
case "anthropic":
|
|
4213
|
-
return this.anthropic(messages, tools, system, onToken);
|
|
4387
|
+
return this.anthropic(messages, tools, system, onToken, signal);
|
|
4214
4388
|
case "openai":
|
|
4215
|
-
return this.openai(messages, tools, system, onToken);
|
|
4389
|
+
return this.openai(messages, tools, system, onToken, void 0, signal);
|
|
4216
4390
|
case "xai":
|
|
4217
|
-
return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
|
|
4391
|
+
return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1", signal);
|
|
4218
4392
|
case "gemini":
|
|
4219
|
-
return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
|
|
4393
|
+
return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai", signal);
|
|
4220
4394
|
case "ollama":
|
|
4221
4395
|
return this.ollama(messages, system, onToken);
|
|
4222
4396
|
default:
|
|
4223
|
-
return this.openai(messages, tools, system, onToken);
|
|
4397
|
+
return this.openai(messages, tools, system, onToken, void 0, signal);
|
|
4224
4398
|
}
|
|
4225
4399
|
}
|
|
4226
4400
|
// ─── Anthropic ───────────────────────────────────────────────────────────
|
|
4227
|
-
async anthropic(messages, tools, system, onToken) {
|
|
4401
|
+
async anthropic(messages, tools, system, onToken, signal) {
|
|
4228
4402
|
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
4229
4403
|
const filtered = messages.filter((m) => m.role !== "system");
|
|
4230
4404
|
const anthropicMsgs = filtered.map((m) => {
|
|
@@ -4272,8 +4446,7 @@ var LLMExecutor = class {
|
|
|
4272
4446
|
"anthropic-version": "2023-06-01"
|
|
4273
4447
|
},
|
|
4274
4448
|
body: JSON.stringify(body),
|
|
4275
|
-
signal: AbortSignal.timeout(12e4)
|
|
4276
|
-
// 60s timeout
|
|
4449
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
|
|
4277
4450
|
});
|
|
4278
4451
|
if (!res.ok) {
|
|
4279
4452
|
const err = await res.text();
|
|
@@ -4356,7 +4529,7 @@ var LLMExecutor = class {
|
|
|
4356
4529
|
};
|
|
4357
4530
|
}
|
|
4358
4531
|
// ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
|
|
4359
|
-
async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
|
|
4532
|
+
async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1", signal) {
|
|
4360
4533
|
const allMessages = [];
|
|
4361
4534
|
const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
|
|
4362
4535
|
if (sysContent) allMessages.push({ role: "system", content: sysContent });
|
|
@@ -4397,7 +4570,7 @@ var LLMExecutor = class {
|
|
|
4397
4570
|
"Authorization": `Bearer ${this.config.api_key}`
|
|
4398
4571
|
},
|
|
4399
4572
|
body: JSON.stringify(body),
|
|
4400
|
-
signal: AbortSignal.timeout(12e4)
|
|
4573
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
|
|
4401
4574
|
});
|
|
4402
4575
|
if (!res.ok) {
|
|
4403
4576
|
const err = await res.text();
|
|
@@ -4816,6 +4989,7 @@ import { homedir as homedir2 } from "node:os";
|
|
|
4816
4989
|
import YAML2 from "yaml";
|
|
4817
4990
|
var SessionManager = class {
|
|
4818
4991
|
sessions = /* @__PURE__ */ new Map();
|
|
4992
|
+
abortControllers = /* @__PURE__ */ new Map();
|
|
4819
4993
|
inferenceEngine;
|
|
4820
4994
|
eventBus;
|
|
4821
4995
|
graph;
|
|
@@ -4944,6 +5118,11 @@ var SessionManager = class {
|
|
|
4944
5118
|
session.status = "cancelled";
|
|
4945
5119
|
session.completed_at = Date.now();
|
|
4946
5120
|
session.error = "cancelled";
|
|
5121
|
+
const controller = this.abortControllers.get(id);
|
|
5122
|
+
if (controller) {
|
|
5123
|
+
controller.abort();
|
|
5124
|
+
this.abortControllers.delete(id);
|
|
5125
|
+
}
|
|
4947
5126
|
this.emit({
|
|
4948
5127
|
type: "session.failed",
|
|
4949
5128
|
session_id: id,
|
|
@@ -5005,6 +5184,9 @@ var SessionManager = class {
|
|
|
5005
5184
|
* All callers must have created the session first.
|
|
5006
5185
|
*/
|
|
5007
5186
|
async _executeSession(sessionId, enrichedReq) {
|
|
5187
|
+
const abortController = new AbortController();
|
|
5188
|
+
this.abortControllers.set(sessionId, abortController);
|
|
5189
|
+
const signal = abortController.signal;
|
|
5008
5190
|
try {
|
|
5009
5191
|
await this.startSession(sessionId);
|
|
5010
5192
|
this.addStep(sessionId, `Extracting entities from: "${enrichedReq.task.slice(0, 60)}${enrichedReq.task.length > 60 ? "\u2026" : ""}"`);
|
|
@@ -5082,9 +5264,9 @@ Current task:`;
|
|
|
5082
5264
|
(step) => this.addStep(sessionId, step),
|
|
5083
5265
|
(token) => this.emit({ type: "session.token", session_id: sessionId, token })
|
|
5084
5266
|
);
|
|
5085
|
-
agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext);
|
|
5267
|
+
agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext, signal);
|
|
5086
5268
|
} catch {
|
|
5087
|
-
agentResult = await executor.execute(enrichedReq.task, systemContext);
|
|
5269
|
+
agentResult = await executor.execute(enrichedReq.task, systemContext, signal);
|
|
5088
5270
|
}
|
|
5089
5271
|
if (this.conversationStore && userEntityId) {
|
|
5090
5272
|
const now = Date.now();
|
|
@@ -5183,6 +5365,8 @@ Current task:`;
|
|
|
5183
5365
|
} catch (err) {
|
|
5184
5366
|
const message = err instanceof Error ? err.message : String(err);
|
|
5185
5367
|
this.failSession(sessionId, message);
|
|
5368
|
+
} finally {
|
|
5369
|
+
this.abortControllers.delete(sessionId);
|
|
5186
5370
|
}
|
|
5187
5371
|
return this.sessions.get(sessionId);
|
|
5188
5372
|
}
|
|
@@ -5288,7 +5472,7 @@ Conversation:
|
|
|
5288
5472
|
User: ${task.slice(0, 600)}
|
|
5289
5473
|
Agent: ${output.slice(0, 500)}`;
|
|
5290
5474
|
try {
|
|
5291
|
-
const resp = await
|
|
5475
|
+
const resp = await extractLLM.complete(
|
|
5292
5476
|
[{ role: "user", content: prompt }],
|
|
5293
5477
|
"You are a memory extraction system. Be concise. Extract only factual, durable information. Return valid JSON only."
|
|
5294
5478
|
);
|
|
@@ -7202,7 +7386,7 @@ git checkout <commit> graph/ # restore graph files
|
|
|
7202
7386
|
};
|
|
7203
7387
|
|
|
7204
7388
|
// packages/daemon/src/CodespaceManager.ts
|
|
7205
|
-
import { execSync as execSync5, spawn as
|
|
7389
|
+
import { execSync as execSync5, spawn as spawn6 } from "node:child_process";
|
|
7206
7390
|
var BROWSER_PORT_REMOTE = 3e3;
|
|
7207
7391
|
var BROWSER_PORT_LOCAL = 3001;
|
|
7208
7392
|
var DISPLAY_NAME = "0agent-browser";
|
|
@@ -7297,7 +7481,7 @@ var CodespaceManager = class {
|
|
|
7297
7481
|
async openTunnel(name) {
|
|
7298
7482
|
this.closeTunnel();
|
|
7299
7483
|
console.log(`[Codespace] Opening tunnel port ${BROWSER_PORT_REMOTE} \u2192 localhost:${BROWSER_PORT_LOCAL}...`);
|
|
7300
|
-
this.forwardProcess =
|
|
7484
|
+
this.forwardProcess = spawn6(
|
|
7301
7485
|
"gh",
|
|
7302
7486
|
["codespace", "ports", "forward", `${BROWSER_PORT_REMOTE}:${BROWSER_PORT_LOCAL}`, "--codespace", name],
|
|
7303
7487
|
{ stdio: ["ignore", "ignore", "ignore"] }
|
package/package.json
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "0agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.55",
|
|
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",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
|
-
"0agent": "
|
|
10
|
-
"0agent-chat": "
|
|
9
|
+
"0agent": "bin/0agent.js",
|
|
10
|
+
"0agent-chat": "bin/chat.js"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"bin/",
|
|
14
14
|
"dist/",
|
|
15
15
|
"skills/",
|
|
16
|
-
"seeds/"
|
|
17
|
-
"bin/chat.js"
|
|
16
|
+
"seeds/"
|
|
18
17
|
],
|
|
19
18
|
"scripts": {
|
|
20
19
|
"build": "turbo run build",
|
|
21
20
|
"bundle": "node scripts/bundle.mjs",
|
|
22
21
|
"test": "turbo run test",
|
|
23
22
|
"lint": "turbo run lint",
|
|
23
|
+
"postinstall": "node bin/postinstall.js",
|
|
24
|
+
"preuninstall": "node bin/preuninstall.js",
|
|
24
25
|
"prepublishOnly": "pnpm build --filter='!@0agent/dashboard' --filter='!@0agent/core-native' && node scripts/bundle.mjs"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
@@ -53,7 +54,7 @@
|
|
|
53
54
|
],
|
|
54
55
|
"repository": {
|
|
55
56
|
"type": "git",
|
|
56
|
-
"url": "https://github.com/cadetmaze/0agentv1"
|
|
57
|
+
"url": "git+https://github.com/cadetmaze/0agentv1.git"
|
|
57
58
|
},
|
|
58
59
|
"homepage": "https://github.com/cadetmaze/0agentv1#readme",
|
|
59
60
|
"packageManager": "pnpm@9.12.0"
|