0agent 1.0.39 → 1.0.41

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:
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
@@ -2406,14 +2431,14 @@ var init_FileCapability = __esm({
2406
2431
  "use strict";
2407
2432
  FileCapability = class {
2408
2433
  name = "file_op";
2409
- description = "Read, write, or list files. Scoped to working directory.";
2434
+ description = "Read, write, list files, or create directories. Scoped to working directory.";
2410
2435
  toolDefinition = {
2411
2436
  name: "file_op",
2412
- description: "Read, write, or list files in the working directory.",
2437
+ description: "Read, write, list files, or create directories in the working directory.",
2413
2438
  input_schema: {
2414
2439
  type: "object",
2415
2440
  properties: {
2416
- op: { type: "string", description: '"read", "write", or "list"' },
2441
+ op: { type: "string", description: '"read", "write", "list", or "mkdir"' },
2417
2442
  path: { type: "string", description: "File or directory path (relative to cwd)" },
2418
2443
  content: { type: "string", description: "Content for write operation" }
2419
2444
  },
@@ -2448,7 +2473,11 @@ var init_FileCapability = __esm({
2448
2473
  const entries = readdirSync(safe, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
2449
2474
  return { success: true, output: entries || "(empty)", duration_ms: Date.now() - start };
2450
2475
  }
2451
- return { success: false, output: `Unknown op: ${op}`, duration_ms: Date.now() - start };
2476
+ if (op === "mkdir") {
2477
+ mkdirSync(safe, { recursive: true });
2478
+ return { success: true, output: `Directory created: ${rel}`, duration_ms: Date.now() - start };
2479
+ }
2480
+ return { success: false, output: `Unknown op: ${op}. Use "read", "write", "list", or "mkdir"`, duration_ms: Date.now() - start };
2452
2481
  } catch (err) {
2453
2482
  return { success: false, output: `Error: ${err instanceof Error ? err.message : String(err)}`, duration_ms: Date.now() - start };
2454
2483
  }
@@ -2457,6 +2486,74 @@ var init_FileCapability = __esm({
2457
2486
  }
2458
2487
  });
2459
2488
 
2489
+ // packages/daemon/src/capabilities/MemoryCapability.ts
2490
+ var MemoryCapability;
2491
+ var init_MemoryCapability = __esm({
2492
+ "packages/daemon/src/capabilities/MemoryCapability.ts"() {
2493
+ "use strict";
2494
+ init_src();
2495
+ MemoryCapability = class {
2496
+ constructor(graph) {
2497
+ this.graph = graph;
2498
+ }
2499
+ name = "memory_write";
2500
+ description = "Persist a discovered fact to long-term memory so it survives across sessions.";
2501
+ toolDefinition = {
2502
+ name: "memory_write",
2503
+ description: 'Write an important fact to long-term memory. Call this whenever you discover something worth remembering: URLs (ngrok, live servers), file paths, port numbers, API endpoints, configuration values, decisions made, or task outcomes. Examples: memory_write({label:"ngrok_url", content:"https://abc.ngrok.io", type:"url"}) or memory_write({label:"project_port", content:"3000", type:"config"})',
2504
+ input_schema: {
2505
+ type: "object",
2506
+ properties: {
2507
+ label: { type: "string", description: 'Short name for this fact (e.g. "ngrok_url", "project_port")' },
2508
+ content: { type: "string", description: "The value to remember" },
2509
+ type: { type: "string", description: 'Category: "url", "path", "config", "note", "outcome"' }
2510
+ },
2511
+ required: ["label", "content"]
2512
+ }
2513
+ };
2514
+ async execute(input, _cwd) {
2515
+ const label = String(input.label ?? "").trim();
2516
+ const content = String(input.content ?? "").trim();
2517
+ const type = String(input.type ?? "note").trim();
2518
+ const start = Date.now();
2519
+ if (!label || !content) {
2520
+ return { success: false, output: "label and content are required", duration_ms: 0 };
2521
+ }
2522
+ try {
2523
+ const nodeId = `memory:${label.toLowerCase().replace(/[^a-z0-9_]/g, "_")}`;
2524
+ const existing = this.graph.getNode(nodeId);
2525
+ if (existing) {
2526
+ this.graph.updateNode(nodeId, {
2527
+ label,
2528
+ metadata: { ...existing.metadata, content, type, updated_at: (/* @__PURE__ */ new Date()).toISOString() }
2529
+ });
2530
+ } else {
2531
+ const node = createNode({
2532
+ id: nodeId,
2533
+ graph_id: "root",
2534
+ label,
2535
+ type: "context" /* CONTEXT */,
2536
+ metadata: { content, type, saved_at: (/* @__PURE__ */ new Date()).toISOString() }
2537
+ });
2538
+ this.graph.addNode(node);
2539
+ }
2540
+ return {
2541
+ success: true,
2542
+ output: `Remembered: "${label}" = ${content.slice(0, 120)}${content.length > 120 ? "\u2026" : ""}`,
2543
+ duration_ms: Date.now() - start
2544
+ };
2545
+ } catch (err) {
2546
+ return {
2547
+ success: false,
2548
+ output: `Memory write failed: ${err instanceof Error ? err.message : String(err)}`,
2549
+ duration_ms: Date.now() - start
2550
+ };
2551
+ }
2552
+ }
2553
+ };
2554
+ }
2555
+ });
2556
+
2460
2557
  // packages/daemon/src/capabilities/CodespaceBrowserCapability.ts
2461
2558
  var CodespaceBrowserCapability_exports = {};
2462
2559
  __export(CodespaceBrowserCapability_exports, {
@@ -2541,6 +2638,7 @@ var init_CapabilityRegistry = __esm({
2541
2638
  init_ScraperCapability();
2542
2639
  init_ShellCapability();
2543
2640
  init_FileCapability();
2641
+ init_MemoryCapability();
2544
2642
  CapabilityRegistry = class {
2545
2643
  capabilities = /* @__PURE__ */ new Map();
2546
2644
  /**
@@ -2553,7 +2651,7 @@ var init_CapabilityRegistry = __esm({
2553
2651
  * task_type: browser_task). The main agent does NOT have direct access
2554
2652
  * to browser_open without going through a subagent spawn.
2555
2653
  */
2556
- constructor(codespaceManager) {
2654
+ constructor(codespaceManager, graph) {
2557
2655
  this.register(new WebSearchCapability());
2558
2656
  if (codespaceManager) {
2559
2657
  try {
@@ -2568,6 +2666,9 @@ var init_CapabilityRegistry = __esm({
2568
2666
  this.register(new ScraperCapability());
2569
2667
  this.register(new ShellCapability());
2570
2668
  this.register(new FileCapability());
2669
+ if (graph) {
2670
+ this.register(new MemoryCapability(graph));
2671
+ }
2571
2672
  }
2572
2673
  register(cap) {
2573
2674
  this.capabilities.set(cap.name, cap);
@@ -2614,7 +2715,7 @@ var init_capabilities = __esm({
2614
2715
  });
2615
2716
 
2616
2717
  // packages/daemon/src/AgentExecutor.ts
2617
- import { spawn as spawn2 } from "node:child_process";
2718
+ import { spawn as spawn3 } from "node:child_process";
2618
2719
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "node:fs";
2619
2720
  import { resolve as resolve3, dirname as dirname2, relative } from "node:path";
2620
2721
  var SELF_MOD_PATTERN, AgentExecutor;
@@ -2633,7 +2734,7 @@ var init_AgentExecutor = __esm({
2633
2734
  this.maxIterations = config.max_iterations ?? 20;
2634
2735
  this.maxCommandMs = config.max_command_ms ?? 3e4;
2635
2736
  this.agentRoot = config.agent_root;
2636
- this.registry = new CapabilityRegistry();
2737
+ this.registry = new CapabilityRegistry(void 0, config.graph);
2637
2738
  }
2638
2739
  cwd;
2639
2740
  maxIterations;
@@ -2658,23 +2759,39 @@ var init_AgentExecutor = __esm({
2658
2759
  for (let i = 0; i < this.maxIterations; i++) {
2659
2760
  this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
2660
2761
  let response;
2661
- try {
2662
- response = await this.llm.completeWithTools(
2663
- messages,
2664
- this.registry.getToolDefinitions(),
2665
- systemPrompt,
2666
- // Only stream tokens on the final (non-tool) turn
2667
- (token) => {
2668
- this.onToken(token);
2669
- finalOutput += token;
2762
+ let llmFailed = false;
2763
+ {
2764
+ let llmRetry = 0;
2765
+ while (true) {
2766
+ try {
2767
+ response = await this.llm.completeWithTools(
2768
+ messages,
2769
+ this.registry.getToolDefinitions(),
2770
+ systemPrompt,
2771
+ // Only stream tokens on the final (non-tool) turn
2772
+ (token) => {
2773
+ this.onToken(token);
2774
+ finalOutput += token;
2775
+ }
2776
+ );
2777
+ break;
2778
+ } catch (err) {
2779
+ const msg = err instanceof Error ? err.message : String(err);
2780
+ const isTimeout = /timeout|AbortError|aborted/i.test(msg);
2781
+ if (isTimeout && llmRetry < 2) {
2782
+ llmRetry++;
2783
+ this.onStep(`LLM timeout \u2014 retrying (${llmRetry}/2)\u2026`);
2784
+ await new Promise((r) => setTimeout(r, 2e3 * llmRetry));
2785
+ continue;
2786
+ }
2787
+ this.onStep(`LLM error: ${msg}`);
2788
+ finalOutput = `Error: ${msg}`;
2789
+ llmFailed = true;
2790
+ break;
2670
2791
  }
2671
- );
2672
- } catch (err) {
2673
- const msg = err instanceof Error ? err.message : String(err);
2674
- this.onStep(`LLM error: ${msg}`);
2675
- finalOutput = `Error: ${msg}`;
2676
- break;
2792
+ }
2677
2793
  }
2794
+ if (llmFailed) break;
2678
2795
  totalTokens += response.tokens_used;
2679
2796
  modelName = response.model;
2680
2797
  if (response.stop_reason === "end_turn" || !response.tool_calls?.length) {
@@ -2755,7 +2872,7 @@ var init_AgentExecutor = __esm({
2755
2872
  shellExec(command, timeoutMs) {
2756
2873
  return new Promise((resolve15) => {
2757
2874
  const chunks = [];
2758
- const proc = spawn2("bash", ["-c", command], {
2875
+ const proc = spawn3("bash", ["-c", command], {
2759
2876
  cwd: this.cwd,
2760
2877
  env: { ...process.env, TERM: "dumb" },
2761
2878
  timeout: timeoutMs
@@ -2889,6 +3006,7 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
2889
3006
  }
2890
3007
  buildSystemPrompt(extra, task) {
2891
3008
  const isSelfMod = !!(task && SELF_MOD_PATTERN.test(task));
3009
+ const hasMemory = !!this.config.graph;
2892
3010
  const lines = [
2893
3011
  `You are 0agent, an AI software engineer. You can execute shell commands and manage files.`,
2894
3012
  `Working directory: ${this.cwd}`,
@@ -2899,12 +3017,24 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
2899
3017
  ` cmd > /tmp/0agent-server.log 2>&1 &`,
2900
3018
  ` Example: python3 -m http.server 3000 > /tmp/0agent-server.log 2>&1 &`,
2901
3019
  ` NEVER run background commands without redirecting output.`,
2902
- `- For npm/node projects: check package.json first with read_file or list_dir`,
2903
- `- After write_file, verify with read_file if needed`,
3020
+ `- To create a folder: use file_op with op="mkdir" and path="folder/name"`,
3021
+ `- To create a file (and its parent folders): use file_op with op="write" \u2014 parent dirs are created automatically`,
3022
+ `- For npm/node projects: check package.json first with file_op op="list"`,
3023
+ `- After writing files, verify with file_op op="read" if needed`,
2904
3024
  `- After shell_exec, check output for errors and retry if needed`,
2905
3025
  `- For research tasks: use web_search first, then scrape_url for full page content`,
2906
3026
  `- Use relative paths from the working directory`,
2907
- `- Be concise in your final response: state what was done and where to find it`
3027
+ `- Be concise in your final response: state what was done and where to find it`,
3028
+ ...hasMemory ? [
3029
+ ``,
3030
+ `Memory (IMPORTANT):`,
3031
+ `- ALWAYS call memory_write after discovering important facts:`,
3032
+ ` \xB7 Live URLs (ngrok, deployed apps, APIs): memory_write({label:"ngrok_url", content:"https://...", type:"url"})`,
3033
+ ` \xB7 Server ports: memory_write({label:"dev_server_port", content:"3000", type:"config"})`,
3034
+ ` \xB7 File paths of created projects: memory_write({label:"project_path", content:"/path/to/project", type:"path"})`,
3035
+ ` \xB7 Task outcomes: memory_write({label:"last_task_result", content:"...", type:"outcome"})`,
3036
+ `- Call memory_write immediately when you find the value, not just at the end`
3037
+ ] : []
2908
3038
  ];
2909
3039
  if (isSelfMod && this.agentRoot) {
2910
3040
  lines.push(
@@ -3034,7 +3164,7 @@ var init_ExecutionVerifier = __esm({
3034
3164
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "node:fs";
3035
3165
  import { resolve as resolve5, dirname as dirname3 } from "node:path";
3036
3166
  import { fileURLToPath } from "node:url";
3037
- import { execSync as execSync4, spawn as spawn3 } from "node:child_process";
3167
+ import { execSync as execSync4, spawn as spawn4 } from "node:child_process";
3038
3168
  function isRuntimeBug(error) {
3039
3169
  if (TASK_FAILURE_PATTERNS.some((p) => p.test(error))) return false;
3040
3170
  return RUNTIME_BUG_PATTERNS.some((p) => p.test(error));
@@ -3269,7 +3399,7 @@ Rules:
3269
3399
  restartDaemon() {
3270
3400
  const bundlePath = resolve5(this.projectRoot, "dist", "daemon.mjs");
3271
3401
  if (existsSync6(bundlePath)) {
3272
- const child = spawn3(process.execPath, [bundlePath], {
3402
+ const child = spawn4(process.execPath, [bundlePath], {
3273
3403
  detached: true,
3274
3404
  stdio: "ignore",
3275
3405
  env: process.env
@@ -4597,7 +4727,7 @@ var SessionManager = class {
4597
4727
  if (activeLLM?.isConfigured) {
4598
4728
  const executor = new AgentExecutor(
4599
4729
  activeLLM,
4600
- { cwd: this.cwd, agent_root: this.agentRoot },
4730
+ { cwd: this.cwd, agent_root: this.agentRoot, graph: this.graph },
4601
4731
  // step callback → emit session.step events
4602
4732
  (step) => this.addStep(sessionId, step),
4603
4733
  // token callback → emit session.token events
@@ -6616,7 +6746,7 @@ git checkout <commit> graph/ # restore graph files
6616
6746
  };
6617
6747
 
6618
6748
  // packages/daemon/src/CodespaceManager.ts
6619
- import { execSync as execSync5, spawn as spawn4 } from "node:child_process";
6749
+ import { execSync as execSync5, spawn as spawn5 } from "node:child_process";
6620
6750
  var BROWSER_PORT_REMOTE = 3e3;
6621
6751
  var BROWSER_PORT_LOCAL = 3001;
6622
6752
  var DISPLAY_NAME = "0agent-browser";
@@ -6711,7 +6841,7 @@ var CodespaceManager = class {
6711
6841
  async openTunnel(name) {
6712
6842
  this.closeTunnel();
6713
6843
  console.log(`[Codespace] Opening tunnel port ${BROWSER_PORT_REMOTE} \u2192 localhost:${BROWSER_PORT_LOCAL}...`);
6714
- this.forwardProcess = spawn4(
6844
+ this.forwardProcess = spawn5(
6715
6845
  "gh",
6716
6846
  ["codespace", "ports", "forward", `${BROWSER_PORT_REMOTE}:${BROWSER_PORT_LOCAL}`, "--codespace", name],
6717
6847
  { stdio: ["ignore", "ignore", "ignore"] }
@@ -6871,7 +7001,14 @@ var ZeroAgentDaemon = class {
6871
7001
  }).catch(() => {
6872
7002
  });
6873
7003
  }
6874
- 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
+ }
6875
7012
  const identityManager = new IdentityManager(this.graph);
6876
7013
  const identity = await identityManager.init().catch(() => null);
6877
7014
  if (identity) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.39",
3
+ "version": "1.0.41",
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",