0agent 1.0.40 → 1.0.42

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
@@ -278,7 +278,16 @@ async function runInit() {
278
278
  }
279
279
  }
280
280
 
281
- // ── Step 3: Embedding ────────────────────────────────────────────────────
281
+ // ── Step 3: Workspace folder ─────────────────────────────────────────────
282
+ const defaultWorkspace = resolve(homedir(), '0agent-workspace');
283
+ console.log(`\n \x1b[1mWorkspace folder\x1b[0m`);
284
+ console.log(` \x1b[2mAll files the agent creates will go here. You own it.\x1b[0m`);
285
+ const wsRaw = await ask(` Path [${defaultWorkspace}]: `);
286
+ const workspacePath = wsRaw.trim() || defaultWorkspace;
287
+ mkdirSync(workspacePath, { recursive: true });
288
+ console.log(` \x1b[32m✓\x1b[0m Workspace: \x1b[36m${workspacePath}\x1b[0m`);
289
+
290
+ // ── Step 4: Embedding ────────────────────────────────────────────────────
282
291
  const embIdx = await arrowSelect('Embeddings (for semantic memory search)?', [
283
292
  'Local via Ollama (nomic-embed-text) — free, private',
284
293
  'OpenAI text-embedding-3-small — cloud',
@@ -286,12 +295,12 @@ async function runInit() {
286
295
  ], 0);
287
296
  const embeddingProvider = ['nomic-ollama', 'openai', 'none'][embIdx];
288
297
 
289
- // ── Step 4: Sandbox ──────────────────────────────────────────────────────
298
+ // ── Step 5: Sandbox ──────────────────────────────────────────────────────
290
299
  const sandboxes = detectSandboxes();
291
300
  const sandboxChoice = sandboxes[0] ?? 'process';
292
301
  console.log(`\n Sandbox: \x1b[32m${sandboxChoice}\x1b[0m detected`);
293
302
 
294
- // ── Step 5: Seed graph ───────────────────────────────────────────────────
303
+ // ── Step 6: Seed graph ───────────────────────────────────────────────────
295
304
  const seedIdx = await arrowSelect('Starting knowledge?', [
296
305
  'software-engineering — sprint workflow + 15 skills ← recommended',
297
306
  'Start from scratch',
@@ -300,11 +309,12 @@ async function runInit() {
300
309
 
301
310
  // ── Summary ───────────────────────────────────────────────────────────────
302
311
  console.log('\n \x1b[1mReady to launch\x1b[0m\n');
303
- console.log(` LLM: \x1b[36m${providerKey}/${model}\x1b[0m`);
304
- console.log(` API Key: ${apiKey ? '\x1b[32m✓ set\x1b[0m (' + apiKey.slice(0, 8) + '••••)' : '\x1b[33mnot set\x1b[0m'}`);
305
- console.log(` Memory: ${ghToken ? `\x1b[32mgithub.com/${ghOwner}/0agent-memory\x1b[0m` : '\x1b[2mlocal only\x1b[0m'}`);
306
- console.log(` Sandbox: \x1b[36m${sandboxChoice}\x1b[0m`);
307
- console.log(` Seed: \x1b[36m${seedName ?? 'scratch'}\x1b[0m\n`);
312
+ console.log(` LLM: \x1b[36m${providerKey}/${model}\x1b[0m`);
313
+ console.log(` API Key: ${apiKey ? '\x1b[32m✓ set\x1b[0m (' + apiKey.slice(0, 8) + '••••)' : '\x1b[33mnot set\x1b[0m'}`);
314
+ console.log(` Memory: ${ghToken ? `\x1b[32mgithub.com/${ghOwner}/0agent-memory\x1b[0m` : '\x1b[2mlocal only\x1b[0m'}`);
315
+ console.log(` Workspace: \x1b[36m${workspacePath}\x1b[0m`);
316
+ console.log(` Sandbox: \x1b[36m${sandboxChoice}\x1b[0m`);
317
+ console.log(` Seed: \x1b[36m${seedName ?? 'scratch'}\x1b[0m\n`);
308
318
 
309
319
  // Write config
310
320
  const dbPath = resolve(AGENT_DIR, 'graph.db');
@@ -329,6 +339,9 @@ embedding:
329
339
  sandbox:
330
340
  backend: ${sandboxChoice}
331
341
 
342
+ workspace:
343
+ path: "${workspacePath}"
344
+
332
345
  mcp_servers: []
333
346
 
334
347
  server:
@@ -356,11 +369,20 @@ ${ghToken && ghOwner ? `\ngithub_memory:\n enabled: true\n token: "${ghToken}"
356
369
  console.log(' ✓ Built-in skills installed');
357
370
  }
358
371
 
359
- // Always kill any existing daemon so the new config (with API key) is loaded
360
- try { stopDaemon(); await sleep(800); } catch {}
372
+ // Force-kill any running daemon so new config is loaded fresh
373
+ await forceStopDaemon();
361
374
 
362
- console.log('\n Starting daemon...\n');
375
+ // Start fresh daemon with new config
376
+ console.log('\n Starting...');
363
377
  await startDaemon();
378
+
379
+ // Open chat TUI immediately — no manual steps needed
380
+ const chatSc = resolve(dirname(new URL(import.meta.url).pathname), 'chat.js');
381
+ if (existsSync(chatSc)) {
382
+ const { spawn: sp } = await import('node:child_process');
383
+ const p = sp(process.execPath, [chatSc], { stdio: 'inherit' });
384
+ await new Promise(r => p.on('close', r));
385
+ }
364
386
  }
365
387
 
366
388
  function detectSandboxes() {
@@ -378,9 +400,17 @@ function detectSandboxes() {
378
400
  // ─── Daemon lifecycle ─────────────────────────────────────────────────────
379
401
 
380
402
  async function startDaemon() {
403
+ // If still running (e.g. stop was called just before), wait a bit
381
404
  if (await isDaemonRunning()) {
382
- console.log(' Daemon already running on port 4200. Run `0agent status`.');
383
- return;
405
+ // Give it up to 3s to die before giving up
406
+ for (let i = 0; i < 6; i++) {
407
+ await sleep(500);
408
+ if (!await isDaemonRunning()) break;
409
+ }
410
+ if (await isDaemonRunning()) {
411
+ console.log(' Daemon already running on port 4200. Run `0agent status`.');
412
+ return;
413
+ }
384
414
  }
385
415
 
386
416
  if (!existsSync(CONFIG_PATH)) {
@@ -410,33 +440,42 @@ async function startDaemon() {
410
440
  child.unref();
411
441
 
412
442
  // Wait for daemon to be ready (poll /api/health)
413
- process.stdout.write(' Starting');
414
443
  for (let i = 0; i < 30; i++) {
415
444
  await sleep(500);
416
- process.stdout.write('.');
417
445
  if (await isDaemonRunning()) {
418
- console.log(' ✓\n');
419
- console.log(` Daemon running on http://localhost:4200`);
420
- console.log(` Dashboard: http://localhost:4200`);
421
- console.log(` Run: 0agent run "your task"\n`);
446
+ process.stdout.write(` \x1b[32m✓\x1b[0m Daemon ready\n`);
422
447
  return;
423
448
  }
424
449
  }
425
- console.log('\n Daemon did not start. Check logs: 0agent logs');
450
+ console.log(' Daemon did not start in time. Check: 0agent logs');
426
451
  }
427
452
 
428
453
  function stopDaemon() {
429
- if (!existsSync(PID_PATH)) {
454
+ // Try PID file first
455
+ if (existsSync(PID_PATH)) {
456
+ const pid = parseInt(readFileSync(PID_PATH, 'utf8').trim(), 10);
457
+ try { process.kill(pid, 'SIGTERM'); } catch {}
458
+ } else {
430
459
  console.log(' No daemon PID file found. Is it running?');
431
- return;
432
460
  }
433
- const pid = parseInt(readFileSync(PID_PATH, 'utf8').trim(), 10);
434
- try {
435
- process.kill(pid, 'SIGTERM');
436
- console.log(` Sent SIGTERM to daemon (pid ${pid}). Shutting down...`);
437
- } catch (e) {
438
- console.log(` Could not stop daemon: ${e instanceof Error ? e.message : e}`);
461
+ // Also kill by process name as a fallback
462
+ try { execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
463
+ }
464
+
465
+ // Forcefully kill any running daemon regardless of PID file state.
466
+ // Used after init to ensure fresh config is picked up.
467
+ async function forceStopDaemon() {
468
+ // Kill by PID file if present
469
+ if (existsSync(PID_PATH)) {
470
+ const pid = parseInt(readFileSync(PID_PATH, 'utf8').trim(), 10);
471
+ try { process.kill(pid, 'SIGTERM'); } catch {}
439
472
  }
473
+ // Kill by process name (catches daemons started by chat.js or other means)
474
+ try { execSync('pkill -f "daemon.mjs" 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
475
+ // Kill by port 4200 (last resort)
476
+ try { execSync('lsof -ti:4200 | xargs kill -9 2>/dev/null; true', { stdio: 'ignore' }); } catch {}
477
+ // Wait for port to be free
478
+ await sleep(1200);
440
479
  }
441
480
 
442
481
  async function showStatus() {
package/bin/chat.js CHANGED
@@ -879,6 +879,64 @@ async function _safeJsonFetch(url, opts) {
879
879
  console.log(` ${fmt(C.yellow, '⚠')} LLM check failed: ${e.message}\n`);
880
880
  }
881
881
 
882
+ // ── Step 3: Workspace folder check ───────────────────────────────────────
883
+ // If no workspace is configured, ask the user to set one now.
884
+ // Then export memory (graph nodes) to that folder.
885
+ await (async () => {
886
+ const wsPath = cfg?.workspace?.path;
887
+ if (wsPath) {
888
+ // Already configured — just ensure the folder exists
889
+ try { mkdirSync(wsPath, { recursive: true }); } catch {}
890
+ return;
891
+ }
892
+
893
+ // No workspace configured — ask inline
894
+ const { homedir: hd } = await import('node:os');
895
+ const defaultWs = resolve(hd(), '0agent-workspace');
896
+
897
+ process.stdout.write(`\n ${fmt(C.yellow, '⚠')} No workspace folder configured.\n`);
898
+ process.stdout.write(` ${fmt(C.dim, 'The agent needs a folder to create and store files.')}\n\n`);
899
+
900
+ // Temporarily close readline so we can read a raw line
901
+ const wsInput = await new Promise((res) => {
902
+ process.stdout.write(` ${fmt(C.bold, 'Workspace path')} ${fmt(C.dim, `[${defaultWs}]`)}: `);
903
+ rl.once('line', (line) => res(line.trim() || defaultWs));
904
+ });
905
+
906
+ const chosenPath = resolve(wsInput.replace(/^~/, hd()));
907
+ try {
908
+ mkdirSync(chosenPath, { recursive: true });
909
+ process.stdout.write(` ${fmt(C.green, '✓')} Created: ${fmt(C.cyan, chosenPath)}\n`);
910
+ } catch (e) {
911
+ process.stdout.write(` ${fmt(C.red, '✗')} Could not create folder: ${e.message}\n`);
912
+ return;
913
+ }
914
+
915
+ // Save workspace path to config
916
+ if (!cfg) cfg = {};
917
+ cfg.workspace = { path: chosenPath };
918
+ saveConfig(cfg);
919
+ process.stdout.write(` ${fmt(C.green, '✓')} Workspace saved to config\n`);
920
+
921
+ // Export memory (graph nodes) to workspace as a JSON snapshot
922
+ try {
923
+ const nodesRes = await fetch(`${BASE_URL}/api/graph/nodes?limit=9999`, {
924
+ signal: AbortSignal.timeout(5000),
925
+ });
926
+ if (nodesRes.ok) {
927
+ const nodes = await nodesRes.json();
928
+ const count = Array.isArray(nodes) ? nodes.length : 0;
929
+ if (count > 0) {
930
+ const exportPath = resolve(chosenPath, '.0agent-memory.json');
931
+ writeFileSync(exportPath, JSON.stringify({ exported_at: new Date().toISOString(), nodes }, null, 2), 'utf8');
932
+ process.stdout.write(` ${fmt(C.green, '✓')} Memory exported: ${fmt(C.dim, `${count} nodes → .0agent-memory.json`)}\n`);
933
+ }
934
+ }
935
+ } catch {}
936
+
937
+ process.stdout.write('\n');
938
+ })();
939
+
882
940
  // ── Auto-update: check npm, update silently, restart ─────────────────────
883
941
  // Runs in background after prompt — never blocks startup.
884
942
  // If update found: counts down 3s (press any key to skip), then auto-installs.
@@ -935,8 +993,8 @@ async function _safeJsonFetch(url, opts) {
935
993
  })();
936
994
 
937
995
  // Restart using the GLOBAL install, not the npx cache that's currently running.
938
- // After `npm install -g 0agent@latest`, the new binary is in npm's global bin dir.
939
- // Using process.argv would re-run the OLD npx-cached file infinite loop.
996
+ // After `npm install -g 0agent@latest`, use `npm root -g` to find the module root.
997
+ // `npm bin -g` is deprecated and unreliable use the module root instead.
940
998
  async function restartWithLatest() {
941
999
  try {
942
1000
  const { execSync: ex } = await import('node:child_process');
@@ -944,22 +1002,39 @@ async function restartWithLatest() {
944
1002
  const { existsSync: ef } = await import('node:fs');
945
1003
  const { spawn: sp } = await import('node:child_process');
946
1004
 
947
- // Find the global npm bin directory (e.g. ~/.nvm/.../bin or /usr/local/bin)
948
- const globalBin = ex('npm bin -g 2>/dev/null || true', { encoding: 'utf8' }).trim().split('\n')[0];
949
- const newBin = globalBin ? res(globalBin, '0agent') : null;
1005
+ // npm root -g e.g. /Users/sahil/.nvm/versions/node/v20/lib/node_modules
1006
+ // The 0agent entry is at {root}/0agent/bin/0agent.js
1007
+ let newBin = null;
1008
+ try {
1009
+ const npmRoot = ex('npm root -g 2>/dev/null', { encoding: 'utf8' }).trim().split('\n')[0];
1010
+ if (npmRoot) {
1011
+ const candidate = res(npmRoot, '0agent', 'bin', '0agent.js');
1012
+ if (ef(candidate)) newBin = candidate;
1013
+ }
1014
+ } catch {}
1015
+
1016
+ if (!newBin) {
1017
+ // Fallback: npm prefix -g gives the prefix, bin is {prefix}/bin/0agent
1018
+ try {
1019
+ const prefix = ex('npm prefix -g 2>/dev/null', { encoding: 'utf8' }).trim().split('\n')[0];
1020
+ // The script wrapper lives in {prefix}/bin; the actual JS is in lib/node_modules
1021
+ const candidate = prefix ? res(prefix, 'lib', 'node_modules', '0agent', 'bin', '0agent.js') : null;
1022
+ if (candidate && ef(candidate)) newBin = candidate;
1023
+ } catch {}
1024
+ }
950
1025
 
951
- let child;
952
- if (newBin && ef(newBin)) {
953
- // Run the freshly installed global script
954
- child = sp(process.execPath, [newBin], { stdio: 'inherit' });
955
- } else {
956
- // Fallback: let the shell find 0agent in PATH
957
- child = sp('0agent', [], { stdio: 'inherit', shell: true });
1026
+ if (!newBin) {
1027
+ // Cannot locate the new binary — exit so user restarts manually
1028
+ process.stdout.write(` ${fmt(C.dim, 'Restart manually: 0agent')}\n`);
1029
+ process.exit(0);
1030
+ return;
958
1031
  }
1032
+
1033
+ const child = sp(process.execPath, [newBin], { stdio: 'inherit' });
959
1034
  child.on('close', (code) => process.exit(code ?? 0));
960
1035
  process.stdin.pause();
961
1036
  } catch {
962
- process.exit(0); // just exit; user can re-open
1037
+ process.exit(0);
963
1038
  }
964
1039
  }
965
1040
 
package/dist/daemon.mjs CHANGED
@@ -2111,22 +2111,39 @@ var init_WebSearchCapability = __esm({
2111
2111
  });
2112
2112
 
2113
2113
  // packages/daemon/src/capabilities/BrowserCapability.ts
2114
- import { spawnSync as spawnSync2, execSync as execSync2 } from "node:child_process";
2114
+ import { spawnSync as spawnSync2, execSync as execSync2, spawn } from "node:child_process";
2115
+ import { platform } from "node:os";
2116
+ function findSystemChrome() {
2117
+ const candidates = platform() === "darwin" ? [
2118
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
2119
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
2120
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
2121
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
2122
+ ] : platform() === "linux" ? ["google-chrome", "chromium-browser", "chromium", "microsoft-edge", "brave-browser"] : ["C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"];
2123
+ for (const c of candidates) {
2124
+ try {
2125
+ execSync2(`test -f "${c}" 2>/dev/null || which "${c}" 2>/dev/null`, { stdio: "pipe" });
2126
+ return c;
2127
+ } catch {
2128
+ }
2129
+ }
2130
+ return null;
2131
+ }
2115
2132
  var BrowserCapability;
2116
2133
  var init_BrowserCapability = __esm({
2117
2134
  "packages/daemon/src/capabilities/BrowserCapability.ts"() {
2118
2135
  "use strict";
2119
2136
  BrowserCapability = class {
2120
2137
  name = "browser_open";
2121
- description = "Open a URL in a real browser. Returns page content, can take screenshots. Use when scrape_url fails on JS-heavy pages.";
2138
+ description = "Open a URL in the system browser or extract page content. Use when scrape_url fails on JS-heavy pages.";
2122
2139
  toolDefinition = {
2123
2140
  name: "browser_open",
2124
- description: "Open a URL in a real headless browser. Handles JavaScript-rendered pages, SPAs, login flows. Use when scrape_url fails.",
2141
+ description: `Open or read a URL using the system browser. Use action="open" to launch the URL visibly in the user's default browser. Use action="read" (default) to extract page content headlessly. Use when scrape_url fails on JS-heavy pages.`,
2125
2142
  input_schema: {
2126
2143
  type: "object",
2127
2144
  properties: {
2128
2145
  url: { type: "string", description: "URL to open" },
2129
- action: { type: "string", description: 'What to do: "read" (default), "screenshot", "click <selector>", "fill <selector> <value>"' },
2146
+ action: { type: "string", description: '"open" \u2014 launch in system browser (visible); "read" \u2014 extract text content (default); "screenshot" \u2014 take screenshot' },
2130
2147
  wait_for: { type: "string", description: "CSS selector to wait for before extracting content" },
2131
2148
  extract: { type: "string", description: "CSS selector to extract specific element text" }
2132
2149
  },
@@ -2142,23 +2159,27 @@ var init_BrowserCapability = __esm({
2142
2159
  if (!url.startsWith("http")) {
2143
2160
  return { success: false, output: "URL must start with http:// or https://", duration_ms: 0 };
2144
2161
  }
2145
- try {
2146
- const output = await this.playwrightFetch(url, action, waitFor, extract);
2147
- return { success: true, output, duration_ms: Date.now() - start };
2148
- } catch {
2162
+ if (action === "open") {
2163
+ return this.openInSystemBrowser(url, start);
2164
+ }
2165
+ const sysChromeExe = findSystemChrome();
2166
+ if (sysChromeExe) {
2167
+ try {
2168
+ const output = await this.chromeFetch(sysChromeExe, url);
2169
+ return { success: true, output, duration_ms: Date.now() - start };
2170
+ } catch {
2171
+ }
2149
2172
  }
2150
2173
  try {
2151
- const output = await this.chromeFetch(url);
2152
- return { success: true, output, fallback_used: "system-chrome", duration_ms: Date.now() - start };
2174
+ const output = await this.playwrightFetch(url, action, waitFor, extract);
2175
+ return { success: true, output, fallback_used: "playwright", duration_ms: Date.now() - start };
2153
2176
  } catch {
2154
2177
  }
2155
2178
  try {
2156
2179
  const res = await fetch(url, {
2157
2180
  headers: {
2158
2181
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/120.0 Safari/537.36",
2159
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
2160
- "Accept-Language": "en-US,en;q=0.9",
2161
- "Accept-Encoding": "gzip, deflate, br"
2182
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
2162
2183
  },
2163
2184
  signal: AbortSignal.timeout(15e3)
2164
2185
  });
@@ -2168,19 +2189,33 @@ var init_BrowserCapability = __esm({
2168
2189
  } catch (err) {
2169
2190
  return {
2170
2191
  success: false,
2171
- output: `Browser unavailable. Install Playwright: npx playwright install chromium`,
2192
+ output: `Browser unavailable. No system Chrome/Chromium found and Playwright not installed.`,
2172
2193
  error: err instanceof Error ? err.message : String(err),
2173
2194
  duration_ms: Date.now() - start
2174
2195
  };
2175
2196
  }
2176
2197
  }
2198
+ openInSystemBrowser(url, start) {
2199
+ try {
2200
+ const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
2201
+ spawn(cmd, [url], { detached: true, stdio: "ignore" }).unref();
2202
+ return { success: true, output: `Opened in default browser: ${url}`, duration_ms: Date.now() - start };
2203
+ } catch (err) {
2204
+ return { success: false, output: `Failed to open browser: ${err instanceof Error ? err.message : String(err)}`, duration_ms: Date.now() - start };
2205
+ }
2206
+ }
2177
2207
  async playwrightFetch(url, action, waitFor, extract) {
2178
2208
  const waitLine = waitFor ? `await p.waitForSelector('${waitFor}', { timeout: 8000 }).catch(() => {});` : "";
2179
2209
  const extractLine = extract ? `const text = await p.$eval('${extract}', el => el.innerText).catch(() => page_text);` : `const text = page_text;`;
2180
2210
  const script = `
2181
2211
  const { chromium } = require('playwright');
2182
2212
  (async () => {
2183
- const b = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
2213
+ let b;
2214
+ try {
2215
+ b = await chromium.launch({ channel: 'chrome', headless: true, args: ['--no-sandbox'] });
2216
+ } catch {
2217
+ b = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
2218
+ }
2184
2219
  const p = await b.newPage();
2185
2220
  await p.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36');
2186
2221
  await p.goto('${url}', { waitUntil: 'domcontentloaded', timeout: 20000 });
@@ -2195,27 +2230,17 @@ var init_BrowserCapability = __esm({
2195
2230
  if (result.status !== 0) throw new Error(result.stderr || "Playwright failed");
2196
2231
  return result.stdout.trim();
2197
2232
  }
2198
- async chromeFetch(url) {
2199
- const candidates = [
2200
- "google-chrome",
2201
- "chromium-browser",
2202
- "chromium",
2203
- "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
2204
- ];
2205
- for (const chrome of candidates) {
2206
- try {
2207
- execSync2(`which "${chrome}" 2>/dev/null || test -f "${chrome}"`, { stdio: "pipe" });
2208
- const result = spawnSync2(chrome, ["--headless", "--no-sandbox", "--disable-gpu", "--dump-dom", url], {
2209
- timeout: 15e3,
2210
- encoding: "utf8"
2211
- });
2212
- if (result.status === 0 && result.stdout) {
2213
- return result.stdout.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 5e3);
2214
- }
2215
- } catch {
2216
- }
2217
- }
2218
- throw new Error("No system Chrome found");
2233
+ async chromeFetch(executablePath, url) {
2234
+ const result = spawnSync2(executablePath, [
2235
+ "--headless=new",
2236
+ "--no-sandbox",
2237
+ "--disable-gpu",
2238
+ "--disable-dev-shm-usage",
2239
+ "--dump-dom",
2240
+ url
2241
+ ], { timeout: 15e3, encoding: "utf8" });
2242
+ if (result.status !== 0 || !result.stdout) throw new Error("Chrome headless failed");
2243
+ return result.stdout.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 5e3);
2219
2244
  }
2220
2245
  };
2221
2246
  }
@@ -2318,7 +2343,7 @@ else:
2318
2343
  });
2319
2344
 
2320
2345
  // packages/daemon/src/capabilities/ShellCapability.ts
2321
- import { spawn } from "node:child_process";
2346
+ import { spawn as spawn2 } from "node:child_process";
2322
2347
  var ShellCapability;
2323
2348
  var init_ShellCapability = __esm({
2324
2349
  "packages/daemon/src/capabilities/ShellCapability.ts"() {
@@ -2362,7 +2387,7 @@ var init_ShellCapability = __esm({
2362
2387
  ...!success && { error: `exit ${code ?? signal}` }
2363
2388
  });
2364
2389
  };
2365
- const proc = spawn("bash", ["-c", command], {
2390
+ const proc = spawn2("bash", ["-c", command], {
2366
2391
  cwd,
2367
2392
  env: { ...process.env, TERM: "dumb" }
2368
2393
  // DO NOT set `timeout` here — we manage it manually via timer
@@ -2690,7 +2715,7 @@ var init_capabilities = __esm({
2690
2715
  });
2691
2716
 
2692
2717
  // packages/daemon/src/AgentExecutor.ts
2693
- import { spawn as spawn2 } from "node:child_process";
2718
+ import { spawn as spawn3 } from "node:child_process";
2694
2719
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "node:fs";
2695
2720
  import { resolve as resolve3, dirname as dirname2, relative } from "node:path";
2696
2721
  var SELF_MOD_PATTERN, AgentExecutor;
@@ -2847,7 +2872,7 @@ var init_AgentExecutor = __esm({
2847
2872
  shellExec(command, timeoutMs) {
2848
2873
  return new Promise((resolve15) => {
2849
2874
  const chunks = [];
2850
- const proc = spawn2("bash", ["-c", command], {
2875
+ const proc = spawn3("bash", ["-c", command], {
2851
2876
  cwd: this.cwd,
2852
2877
  env: { ...process.env, TERM: "dumb" },
2853
2878
  timeout: timeoutMs
@@ -3139,7 +3164,7 @@ var init_ExecutionVerifier = __esm({
3139
3164
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "node:fs";
3140
3165
  import { resolve as resolve5, dirname as dirname3 } from "node:path";
3141
3166
  import { fileURLToPath } from "node:url";
3142
- import { execSync as execSync4, spawn as spawn3 } from "node:child_process";
3167
+ import { execSync as execSync4, spawn as spawn4 } from "node:child_process";
3143
3168
  function isRuntimeBug(error) {
3144
3169
  if (TASK_FAILURE_PATTERNS.some((p) => p.test(error))) return false;
3145
3170
  return RUNTIME_BUG_PATTERNS.some((p) => p.test(error));
@@ -3374,7 +3399,7 @@ Rules:
3374
3399
  restartDaemon() {
3375
3400
  const bundlePath = resolve5(this.projectRoot, "dist", "daemon.mjs");
3376
3401
  if (existsSync6(bundlePath)) {
3377
- const child = spawn3(process.execPath, [bundlePath], {
3402
+ const child = spawn4(process.execPath, [bundlePath], {
3378
3403
  detached: true,
3379
3404
  stdio: "ignore",
3380
3405
  env: process.env
@@ -6721,7 +6746,7 @@ git checkout <commit> graph/ # restore graph files
6721
6746
  };
6722
6747
 
6723
6748
  // packages/daemon/src/CodespaceManager.ts
6724
- import { execSync as execSync5, spawn as spawn4 } from "node:child_process";
6749
+ import { execSync as execSync5, spawn as spawn5 } from "node:child_process";
6725
6750
  var BROWSER_PORT_REMOTE = 3e3;
6726
6751
  var BROWSER_PORT_LOCAL = 3001;
6727
6752
  var DISPLAY_NAME = "0agent-browser";
@@ -6816,7 +6841,7 @@ var CodespaceManager = class {
6816
6841
  async openTunnel(name) {
6817
6842
  this.closeTunnel();
6818
6843
  console.log(`[Codespace] Opening tunnel port ${BROWSER_PORT_REMOTE} \u2192 localhost:${BROWSER_PORT_LOCAL}...`);
6819
- this.forwardProcess = spawn4(
6844
+ this.forwardProcess = spawn5(
6820
6845
  "gh",
6821
6846
  ["codespace", "ports", "forward", `${BROWSER_PORT_REMOTE}:${BROWSER_PORT_LOCAL}`, "--codespace", name],
6822
6847
  { stdio: ["ignore", "ignore", "ignore"] }
@@ -6976,7 +7001,14 @@ var ZeroAgentDaemon = class {
6976
7001
  }).catch(() => {
6977
7002
  });
6978
7003
  }
6979
- const cwd = process.env["ZEROAGENT_CWD"] ?? process.cwd();
7004
+ const workspaceCfg = this.config["workspace"];
7005
+ const configuredWorkspace = workspaceCfg?.path;
7006
+ const cwd = process.env["ZEROAGENT_CWD"] ?? configuredWorkspace ?? process.cwd();
7007
+ if (configuredWorkspace) {
7008
+ const { mkdirSync: mks } = await import("node:fs");
7009
+ mks(configuredWorkspace, { recursive: true });
7010
+ console.log(`[0agent] Workspace: ${configuredWorkspace}`);
7011
+ }
6980
7012
  const identityManager = new IdentityManager(this.graph);
6981
7013
  const identity = await identityManager.init().catch(() => null);
6982
7014
  if (identity) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.40",
3
+ "version": "1.0.42",
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",