0agent 1.0.50 → 1.0.54

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 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 logFile = writeFileSync(LOG_PATH, '', 'utf8');
434
+ const logFd = openSync(LOG_PATH, 'w');
435
435
  const child = spawn(process.execPath, [startScript], {
436
436
  detached: true,
437
- stdio: ['ignore', 'ignore', '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
@@ -1206,24 +1206,36 @@ connectWS();
1206
1206
 
1207
1207
  // ── Startup: ensure fresh daemon + verify LLM ────────────────────────────────
1208
1208
  async function _spawnDaemon() {
1209
- const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
1209
+ const pkgRoot = resolve(new URL(import.meta.url).pathname, '..', '..');
1210
1210
  const bundled = resolve(pkgRoot, 'dist', 'daemon.mjs');
1211
- if (!existsSync(bundled) || !existsSync(CONFIG_PATH)) return false;
1211
+ const devPath = resolve(pkgRoot, 'packages', 'daemon', 'dist', 'start.js');
1212
+ const daemonScript = existsSync(bundled) ? bundled : existsSync(devPath) ? devPath : null;
1213
+
1214
+ if (!daemonScript) return 'no-bundle';
1215
+ if (!existsSync(CONFIG_PATH)) return 'no-config';
1216
+
1212
1217
  const { spawn } = await import('node:child_process');
1213
- const child = spawn(process.execPath, [bundled], {
1214
- detached: true, stdio: 'ignore',
1218
+ const { openSync: fsOpen, mkdirSync: fsMkdir } = await import('node:fs');
1219
+ const logDir = resolve(AGENT_DIR, 'logs');
1220
+ fsMkdir(logDir, { recursive: true });
1221
+ const logFd = fsOpen(resolve(logDir, 'daemon.log'), 'w');
1222
+
1223
+ const child = spawn(process.execPath, [daemonScript], {
1224
+ detached: true,
1225
+ stdio: ['ignore', logFd, logFd],
1215
1226
  env: { ...process.env, ZEROAGENT_CONFIG: CONFIG_PATH },
1216
1227
  });
1217
1228
  child.unref();
1229
+
1218
1230
  // Wait up to 10s for daemon to be ready
1219
1231
  for (let i = 0; i < 20; i++) {
1220
1232
  await new Promise(r => setTimeout(r, 500));
1221
1233
  try {
1222
1234
  await fetch(`${BASE_URL}/api/health`, { signal: AbortSignal.timeout(500) });
1223
- return true;
1235
+ return 'ok';
1224
1236
  } catch {}
1225
1237
  }
1226
- return false;
1238
+ return 'timeout';
1227
1239
  }
1228
1240
 
1229
1241
  async function _safeJsonFetch(url, opts) {
@@ -1256,23 +1268,32 @@ async function _safeJsonFetch(url, opts) {
1256
1268
  // Daemon not running at all
1257
1269
  }
1258
1270
 
1271
+ let spawnResult = 'ok';
1259
1272
  if (needsRestart) {
1260
1273
  startSpin.start('Restarting daemon (new version)');
1261
- // Kill old daemon
1262
1274
  try {
1263
1275
  const { execSync } = await import('node:child_process');
1264
1276
  execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' });
1265
1277
  } catch {}
1266
1278
  await new Promise(r => setTimeout(r, 800));
1267
- daemonOk = await _spawnDaemon();
1279
+ spawnResult = await _spawnDaemon();
1280
+ daemonOk = spawnResult === 'ok';
1268
1281
  } else if (!daemonOk) {
1269
1282
  startSpin.start('Starting daemon');
1270
- daemonOk = await _spawnDaemon();
1283
+ spawnResult = await _spawnDaemon();
1284
+ daemonOk = spawnResult === 'ok';
1271
1285
  }
1272
1286
 
1273
1287
  startSpin.stop();
1274
1288
  if (!daemonOk) {
1275
- console.log(` ${fmt(C.red, '')} Daemon failed to start. Run: 0agent start`);
1289
+ if (spawnResult === 'no-config') {
1290
+ console.log(` ${fmt(C.yellow, '!')} Not configured yet. Run: ${fmt(C.bold, '0agent init')}`);
1291
+ } else if (spawnResult === 'no-bundle') {
1292
+ console.log(` ${fmt(C.red, '✗')} Daemon bundle missing. Reinstall: ${fmt(C.bold, 'npm i -g 0agent@latest')}`);
1293
+ } else {
1294
+ const logPath = resolve(AGENT_DIR, 'logs', 'daemon.log');
1295
+ console.log(` ${fmt(C.red, '✗')} Daemon failed to start. Check logs: ${fmt(C.dim, logPath)}`);
1296
+ }
1276
1297
  rl.prompt();
1277
1298
  return;
1278
1299
  }
@@ -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
+ }
package/dist/daemon.mjs CHANGED
@@ -2571,13 +2571,13 @@ var init_GUICapability = __esm({
2571
2571
  description = "Automate desktop GUI \u2014 click, type, screenshot, hotkeys, find text on screen.";
2572
2572
  toolDefinition = {
2573
2573
  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. Always start with action="screenshot" to see what is on screen before clicking.',
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. 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
2575
  input_schema: {
2576
2576
  type: "object",
2577
2577
  properties: {
2578
2578
  action: {
2579
2579
  type: "string",
2580
- description: '"screenshot" | "click" | "double_click" | "right_click" | "move" | "type" | "hotkey" | "scroll" | "drag" | "find_and_click" | "get_screen_size" | "open_app"'
2580
+ 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
2581
  },
2582
2582
  x: { type: "number", description: "X coordinate (pixels from left)" },
2583
2583
  y: { type: "number", description: "Y coordinate (pixels from top)" },
@@ -2588,6 +2588,7 @@ var init_GUICapability = __esm({
2588
2588
  direction: { type: "string", description: '"up" | "down" | "left" | "right" for scroll' },
2589
2589
  amount: { type: "number", description: "Scroll clicks (default 3)" },
2590
2590
  app: { type: "string", description: 'App name to open e.g. "Safari", "Terminal", "Chrome"' },
2591
+ url: { type: "string", description: 'URL to open e.g. "https://example.com" (use with open_url)' },
2591
2592
  interval: { type: "number", description: "Seconds to wait between actions (default 0.05)" },
2592
2593
  duration: { type: "number", description: "Seconds for mouse movement animation (default 0.2)" }
2593
2594
  },
@@ -2599,7 +2600,7 @@ var init_GUICapability = __esm({
2599
2600
  const start = Date.now();
2600
2601
  const script = this._buildScript(action, input);
2601
2602
  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 };
2603
+ 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 };
2603
2604
  }
2604
2605
  const tmpFile = resolve3(tmpdir(), `0agent_gui_${Date.now()}.py`);
2605
2606
  writeFileSync2(tmpFile, script, "utf8");
@@ -2652,6 +2653,7 @@ var init_GUICapability = __esm({
2652
2653
  const dir = input.direction != null ? String(input.direction) : "down";
2653
2654
  const amount = input.amount != null ? Number(input.amount) : 3;
2654
2655
  const app = input.app != null ? String(input.app) : "";
2656
+ const url = input.url != null ? String(input.url) : "";
2655
2657
  const interval = input.interval != null ? Number(input.interval) : 0.05;
2656
2658
  const duration = input.duration != null ? Number(input.duration) : 0.2;
2657
2659
  const header = `
@@ -2666,6 +2668,11 @@ pyautogui.PAUSE = ${interval}
2666
2668
  return header + `
2667
2669
  w, h = pyautogui.size()
2668
2670
  print(f"Screen size: {w} x {h}")
2671
+ `;
2672
+ case "get_cursor_pos":
2673
+ return header + `
2674
+ x, y = pyautogui.position()
2675
+ print(f"Cursor position: ({x}, {y})")
2669
2676
  `;
2670
2677
  case "screenshot": {
2671
2678
  return header + `
@@ -2705,10 +2712,13 @@ try:
2705
2712
  print("\\n".join(hits[:40]))
2706
2713
  except ImportError:
2707
2714
  print("(pytesseract not installed \u2014 install it for OCR: pip3 install pytesseract)")
2708
- print(f"Screenshot saved: {shot_path}")
2709
2715
  except Exception as e:
2710
2716
  print(f"OCR failed: {e}")
2711
- print(f"Screenshot saved: {shot_path}")
2717
+ finally:
2718
+ try:
2719
+ os.remove(shot_path)
2720
+ except Exception:
2721
+ pass
2712
2722
  `;
2713
2723
  }
2714
2724
  case "click":
@@ -2790,13 +2800,78 @@ for i, word in enumerate(data['text']):
2790
2800
  cy = data['top'][i] + data['height'][i] // 2
2791
2801
  found.append((cx, cy, word))
2792
2802
 
2793
- if found:
2794
- cx, cy, word = found[0]
2795
- pyautogui.click(cx, cy, duration=${duration})
2796
- print(f"Found '{word}' at ({cx},{cy}) \u2014 clicked")
2803
+ try:
2804
+ if found:
2805
+ cx, cy, word = found[0]
2806
+ pyautogui.click(cx, cy, duration=${duration})
2807
+ print(f"Found '{word}' at ({cx},{cy}) \u2014 clicked")
2808
+ else:
2809
+ print(f"Text '${safeText}' not found on screen. Take a screenshot to see current state.")
2810
+ sys.exit(1)
2811
+ finally:
2812
+ try:
2813
+ os.remove(shot_path)
2814
+ except Exception:
2815
+ pass
2816
+ `;
2817
+ }
2818
+ case "open_url": {
2819
+ if (!url) return null;
2820
+ const safeUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2821
+ const osName = platform2();
2822
+ if (osName === "darwin") {
2823
+ return header + `
2824
+ import subprocess
2825
+
2826
+ url = '${safeUrl}'
2827
+
2828
+ # Check if Chrome is running
2829
+ chrome_running = subprocess.run(['pgrep', '-x', 'Google Chrome'], capture_output=True).returncode == 0
2830
+ firefox_running = subprocess.run(['pgrep', '-x', 'firefox'], capture_output=True).returncode == 0
2831
+ safari_running = subprocess.run(['pgrep', '-x', 'Safari'], capture_output=True).returncode == 0
2832
+
2833
+ if chrome_running:
2834
+ # Open in existing Chrome window \u2014 no new window created
2835
+ script = f'tell application "Google Chrome" to open location "{url}"'
2836
+ subprocess.run(['osascript', '-e', script])
2837
+ subprocess.run(['osascript', '-e', 'tell application "Google Chrome" to activate'])
2838
+ print(f"Navigated Chrome to: {url}")
2839
+ elif firefox_running:
2840
+ script = f'tell application "Firefox" to open location "{url}"'
2841
+ subprocess.run(['osascript', '-e', script])
2842
+ subprocess.run(['osascript', '-e', 'tell application "Firefox" to activate'])
2843
+ print(f"Navigated Firefox to: {url}")
2844
+ elif safari_running:
2845
+ script = f'tell application "Safari" to open location "{url}"'
2846
+ subprocess.run(['osascript', '-e', script])
2847
+ subprocess.run(['osascript', '-e', 'tell application "Safari" to activate'])
2848
+ print(f"Navigated Safari to: {url}")
2797
2849
  else:
2798
- print(f"Text '${safeText}' not found on screen. Take a screenshot to see current state.")
2799
- sys.exit(1)
2850
+ # No browser open \u2014 launch default browser with the URL
2851
+ subprocess.run(['open', url])
2852
+ print(f"Launched browser with: {url}")
2853
+ time.sleep(1.0)
2854
+ `;
2855
+ }
2856
+ return header + `
2857
+ import subprocess
2858
+
2859
+ url = '${safeUrl}'
2860
+
2861
+ # Try to reuse existing browser via wmctrl/xdotool, fall back to xdg-open
2862
+ chrome_pid = subprocess.run(['pgrep', '-x', 'chrome'], capture_output=True)
2863
+ firefox_pid = subprocess.run(['pgrep', '-x', 'firefox'], capture_output=True)
2864
+
2865
+ if chrome_pid.returncode == 0:
2866
+ subprocess.Popen(['google-chrome', '--new-tab', url])
2867
+ print(f"Opened in Chrome tab: {url}")
2868
+ elif firefox_pid.returncode == 0:
2869
+ subprocess.Popen(['firefox', '--new-tab', url])
2870
+ print(f"Opened in Firefox tab: {url}")
2871
+ else:
2872
+ subprocess.Popen(['xdg-open', url])
2873
+ print(f"Opened with default browser: {url}")
2874
+ time.sleep(1.0)
2800
2875
  `;
2801
2876
  }
2802
2877
  case "open_app": {
@@ -3009,8 +3084,8 @@ var init_AgentExecutor = __esm({
3009
3084
  init_capabilities();
3010
3085
  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
3086
  AgentExecutor = class {
3012
- constructor(llm2, config, onStep, onToken) {
3013
- this.llm = llm2;
3087
+ constructor(llm, config, onStep, onToken) {
3088
+ this.llm = llm;
3014
3089
  this.config = config;
3015
3090
  this.onStep = onStep;
3016
3091
  this.onToken = onToken;
@@ -3025,7 +3100,7 @@ var init_AgentExecutor = __esm({
3025
3100
  maxCommandMs;
3026
3101
  registry;
3027
3102
  agentRoot;
3028
- async execute(task, systemContext) {
3103
+ async execute(task, systemContext, signal) {
3029
3104
  const filesWritten = [];
3030
3105
  const commandsRun = [];
3031
3106
  let totalTokens = 0;
@@ -3041,6 +3116,10 @@ var init_AgentExecutor = __esm({
3041
3116
  }
3042
3117
  let finalOutput = "";
3043
3118
  for (let i = 0; i < this.maxIterations; i++) {
3119
+ if (signal?.aborted) {
3120
+ finalOutput = "Cancelled.";
3121
+ break;
3122
+ }
3044
3123
  this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
3045
3124
  let response;
3046
3125
  let llmFailed = false;
@@ -3056,7 +3135,8 @@ var init_AgentExecutor = __esm({
3056
3135
  (token) => {
3057
3136
  this.onToken(token);
3058
3137
  finalOutput += token;
3059
- }
3138
+ },
3139
+ signal
3060
3140
  );
3061
3141
  break;
3062
3142
  } catch (err) {
@@ -3541,8 +3621,8 @@ var init_RuntimeSelfHeal = __esm({
3541
3621
  // network issue
3542
3622
  ];
3543
3623
  RuntimeSelfHeal = class {
3544
- constructor(llm2, eventBus) {
3545
- this.llm = llm2;
3624
+ constructor(llm, eventBus) {
3625
+ this.llm = llm;
3546
3626
  this.eventBus = eventBus;
3547
3627
  let dir = dirname3(fileURLToPath(import.meta.url));
3548
3628
  while (dir !== "/" && !existsSync6(resolve6(dir, "package.json"))) {
@@ -3736,8 +3816,8 @@ var init_SelfHealLoop = __esm({
3736
3816
  init_ExecutionVerifier();
3737
3817
  init_RuntimeSelfHeal();
3738
3818
  SelfHealLoop = class {
3739
- constructor(llm2, config, onStep, onToken, maxAttempts = 3, runtimeHealer) {
3740
- this.llm = llm2;
3819
+ constructor(llm, config, onStep, onToken, maxAttempts = 3, runtimeHealer) {
3820
+ this.llm = llm;
3741
3821
  this.config = config;
3742
3822
  this.onStep = onStep;
3743
3823
  this.onToken = onToken;
@@ -3746,17 +3826,18 @@ var init_SelfHealLoop = __esm({
3746
3826
  this.verifier = new ExecutionVerifier(config.cwd);
3747
3827
  }
3748
3828
  verifier;
3749
- async executeWithHealing(task, systemContext) {
3829
+ async executeWithHealing(task, systemContext, signal) {
3750
3830
  const attempts = [];
3751
3831
  let currentContext = systemContext;
3752
3832
  let finalResult = null;
3753
3833
  let lastVerification = { success: true, method: "none", details: "", retryable: false, elapsed_ms: 0 };
3754
3834
  for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
3835
+ if (signal?.aborted) break;
3755
3836
  if (attempt > 1) {
3756
3837
  this.onStep(`\u21BA Self-healing (attempt ${attempt}/${this.maxAttempts}): ${lastVerification.details}`);
3757
3838
  }
3758
3839
  const executor = new AgentExecutor(this.llm, this.config, this.onStep, this.onToken);
3759
- const result = await executor.execute(task, currentContext);
3840
+ const result = await executor.execute(task, currentContext, signal);
3760
3841
  finalResult = result;
3761
3842
  lastVerification = await this.verifier.verify(result);
3762
3843
  attempts.push({ attempt_number: attempt, error_context: currentContext ?? "", result, verification: lastVerification });
@@ -4207,24 +4288,24 @@ var LLMExecutor = class {
4207
4288
  return { content: res.content, tokens_used: res.tokens_used, model: res.model };
4208
4289
  }
4209
4290
  // ─── Tool-calling completion with optional streaming ─────────────────────
4210
- async completeWithTools(messages, tools, system, onToken) {
4291
+ async completeWithTools(messages, tools, system, onToken, signal) {
4211
4292
  switch (this.config.provider) {
4212
4293
  case "anthropic":
4213
- return this.anthropic(messages, tools, system, onToken);
4294
+ return this.anthropic(messages, tools, system, onToken, signal);
4214
4295
  case "openai":
4215
- return this.openai(messages, tools, system, onToken);
4296
+ return this.openai(messages, tools, system, onToken, void 0, signal);
4216
4297
  case "xai":
4217
- return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1");
4298
+ return this.openai(messages, tools, system, onToken, "https://api.x.ai/v1", signal);
4218
4299
  case "gemini":
4219
- return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai");
4300
+ return this.openai(messages, tools, system, onToken, "https://generativelanguage.googleapis.com/v1beta/openai", signal);
4220
4301
  case "ollama":
4221
4302
  return this.ollama(messages, system, onToken);
4222
4303
  default:
4223
- return this.openai(messages, tools, system, onToken);
4304
+ return this.openai(messages, tools, system, onToken, void 0, signal);
4224
4305
  }
4225
4306
  }
4226
4307
  // ─── Anthropic ───────────────────────────────────────────────────────────
4227
- async anthropic(messages, tools, system, onToken) {
4308
+ async anthropic(messages, tools, system, onToken, signal) {
4228
4309
  const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
4229
4310
  const filtered = messages.filter((m) => m.role !== "system");
4230
4311
  const anthropicMsgs = filtered.map((m) => {
@@ -4272,8 +4353,7 @@ var LLMExecutor = class {
4272
4353
  "anthropic-version": "2023-06-01"
4273
4354
  },
4274
4355
  body: JSON.stringify(body),
4275
- signal: AbortSignal.timeout(12e4)
4276
- // 60s timeout
4356
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
4277
4357
  });
4278
4358
  if (!res.ok) {
4279
4359
  const err = await res.text();
@@ -4356,7 +4436,7 @@ var LLMExecutor = class {
4356
4436
  };
4357
4437
  }
4358
4438
  // ─── OpenAI (also xAI, Gemini) ───────────────────────────────────────────
4359
- async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1") {
4439
+ async openai(messages, tools, system, onToken, baseUrl = "https://api.openai.com/v1", signal) {
4360
4440
  const allMessages = [];
4361
4441
  const sysContent = system ?? messages.find((m) => m.role === "system")?.content;
4362
4442
  if (sysContent) allMessages.push({ role: "system", content: sysContent });
@@ -4397,7 +4477,7 @@ var LLMExecutor = class {
4397
4477
  "Authorization": `Bearer ${this.config.api_key}`
4398
4478
  },
4399
4479
  body: JSON.stringify(body),
4400
- signal: AbortSignal.timeout(12e4)
4480
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12e4)]) : AbortSignal.timeout(12e4)
4401
4481
  });
4402
4482
  if (!res.ok) {
4403
4483
  const err = await res.text();
@@ -4816,6 +4896,7 @@ import { homedir as homedir2 } from "node:os";
4816
4896
  import YAML2 from "yaml";
4817
4897
  var SessionManager = class {
4818
4898
  sessions = /* @__PURE__ */ new Map();
4899
+ abortControllers = /* @__PURE__ */ new Map();
4819
4900
  inferenceEngine;
4820
4901
  eventBus;
4821
4902
  graph;
@@ -4944,6 +5025,11 @@ var SessionManager = class {
4944
5025
  session.status = "cancelled";
4945
5026
  session.completed_at = Date.now();
4946
5027
  session.error = "cancelled";
5028
+ const controller = this.abortControllers.get(id);
5029
+ if (controller) {
5030
+ controller.abort();
5031
+ this.abortControllers.delete(id);
5032
+ }
4947
5033
  this.emit({
4948
5034
  type: "session.failed",
4949
5035
  session_id: id,
@@ -5005,6 +5091,9 @@ var SessionManager = class {
5005
5091
  * All callers must have created the session first.
5006
5092
  */
5007
5093
  async _executeSession(sessionId, enrichedReq) {
5094
+ const abortController = new AbortController();
5095
+ this.abortControllers.set(sessionId, abortController);
5096
+ const signal = abortController.signal;
5008
5097
  try {
5009
5098
  await this.startSession(sessionId);
5010
5099
  this.addStep(sessionId, `Extracting entities from: "${enrichedReq.task.slice(0, 60)}${enrichedReq.task.length > 60 ? "\u2026" : ""}"`);
@@ -5082,9 +5171,9 @@ Current task:`;
5082
5171
  (step) => this.addStep(sessionId, step),
5083
5172
  (token) => this.emit({ type: "session.token", session_id: sessionId, token })
5084
5173
  );
5085
- agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext);
5174
+ agentResult = await healLoop.executeWithHealing(enrichedReq.task, systemContext, signal);
5086
5175
  } catch {
5087
- agentResult = await executor.execute(enrichedReq.task, systemContext);
5176
+ agentResult = await executor.execute(enrichedReq.task, systemContext, signal);
5088
5177
  }
5089
5178
  if (this.conversationStore && userEntityId) {
5090
5179
  const now = Date.now();
@@ -5183,6 +5272,8 @@ Current task:`;
5183
5272
  } catch (err) {
5184
5273
  const message = err instanceof Error ? err.message : String(err);
5185
5274
  this.failSession(sessionId, message);
5275
+ } finally {
5276
+ this.abortControllers.delete(sessionId);
5186
5277
  }
5187
5278
  return this.sessions.get(sessionId);
5188
5279
  }
@@ -5288,7 +5379,7 @@ Conversation:
5288
5379
  User: ${task.slice(0, 600)}
5289
5380
  Agent: ${output.slice(0, 500)}`;
5290
5381
  try {
5291
- const resp = await llm.complete(
5382
+ const resp = await extractLLM.complete(
5292
5383
  [{ role: "user", content: prompt }],
5293
5384
  "You are a memory extraction system. Be concise. Extract only factual, durable information. Return valid JSON only."
5294
5385
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.50",
3
+ "version": "1.0.54",
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",
@@ -13,14 +13,14 @@
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
24
  "prepublishOnly": "pnpm build --filter='!@0agent/dashboard' --filter='!@0agent/core-native' && node scripts/bundle.mjs"
25
25
  },
26
26
  "dependencies": {