0agent 1.0.29 → 1.0.31

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.
Files changed (3) hide show
  1. package/bin/chat.js +122 -4
  2. package/dist/daemon.mjs +416 -90
  3. package/package.json +1 -1
package/bin/chat.js CHANGED
@@ -166,7 +166,7 @@ async function connectWS() {
166
166
  ws.send(JSON.stringify({ type: 'subscribe', topics: ['sessions', 'graph', 'insights'] }));
167
167
  });
168
168
  ws.on('message', data => handleWsEvent(JSON.parse(data.toString())));
169
- ws.on('close', () => { wsReady = false; setTimeout(connectWS, 2000); });
169
+ ws.on('close', () => { wsReady = false; setTimeout(connectWS, 800); }); // faster reconnect
170
170
  ws.on('error', () => { wsReady = false; });
171
171
  } catch {}
172
172
  }
@@ -189,6 +189,79 @@ function handleWsEvent(event) {
189
189
  lineBuffer += event.token;
190
190
  break;
191
191
  }
192
+ case 'runtime.heal_proposal': {
193
+ // Daemon found a code bug and is proposing a fix — requires human y/n
194
+ const p = event.proposal ?? {};
195
+ process.stdout.write('\n');
196
+ process.stdout.write(` ${fmt(C.yellow, '🔧 Runtime code bug detected')}\n`);
197
+ process.stdout.write(` ${fmt(C.dim, p.error_summary ?? '')}\n`);
198
+ process.stdout.write(` ${fmt(C.dim, 'File: ' + (p.location?.relPath ?? 'unknown'))}\n\n`);
199
+
200
+ if (p.explanation) {
201
+ process.stdout.write(` ${fmt(C.bold, 'Diagnosis:')} ${p.explanation}\n\n`);
202
+ }
203
+
204
+ if (p.diff) {
205
+ process.stdout.write(` ${fmt(C.bold, 'Proposed fix:')}\n`);
206
+ const diffLines = String(p.diff).split('\n');
207
+ for (const line of diffLines) {
208
+ if (line.startsWith('-')) process.stdout.write(` ${fmt(C.red, line)}\n`);
209
+ else if (line.startsWith('+')) process.stdout.write(` ${fmt(C.green, line)}\n`);
210
+ else process.stdout.write(` ${fmt(C.dim, line)}\n`);
211
+ }
212
+ process.stdout.write('\n');
213
+ }
214
+
215
+ const confidence = p.confidence ?? 'medium';
216
+ const confColor = confidence === 'high' ? C.green : confidence === 'medium' ? C.yellow : C.red;
217
+ process.stdout.write(` Confidence: ${fmt(confColor, confidence.toUpperCase())}\n\n`);
218
+
219
+ // Ask for approval — use readline in raw mode for single keypress
220
+ process.stdout.write(` Apply this fix and restart daemon? ${fmt(C.bold, '[y/N]')} `);
221
+
222
+ const handleHealApproval = async (key) => {
223
+ process.stdin.removeListener('keypress', handleHealApproval);
224
+ const answer = key?.toLowerCase() ?? 'n';
225
+ process.stdout.write(answer + '\n\n');
226
+
227
+ if (answer === 'y') {
228
+ // Store proposal then approve
229
+ await fetch(`${BASE_URL}/api/runtime/proposals`, {
230
+ method: 'POST',
231
+ headers: { 'Content-Type': 'application/json' },
232
+ body: JSON.stringify(p),
233
+ }).catch(() => {});
234
+
235
+ const approveRes = await fetch(`${BASE_URL}/api/runtime/proposals/${p.proposal_id}/approve`, {
236
+ method: 'POST',
237
+ }).catch(() => null);
238
+ const data = approveRes?.ok ? await approveRes.json().catch(() => null) : null;
239
+
240
+ if (data?.applied) {
241
+ process.stdout.write(` ${fmt(C.green, '✓')} Patch applied. ${data.message}\n`);
242
+ process.stdout.write(` ${fmt(C.dim, 'Daemon restarting — reconnecting in 5s...')}\n\n`);
243
+ } else {
244
+ process.stdout.write(` ${fmt(C.red, '✗')} Could not apply: ${data?.message ?? 'unknown error'}\n\n`);
245
+ rl.prompt();
246
+ }
247
+ } else {
248
+ await fetch(`${BASE_URL}/api/runtime/proposals/${p.proposal_id}`, { method: 'DELETE' }).catch(() => {});
249
+ process.stdout.write(` ${fmt(C.dim, 'Fix rejected. The bug remains.')}\n\n`);
250
+ rl.prompt();
251
+ }
252
+ };
253
+
254
+ // Listen for a single keypress
255
+ if (process.stdin.isTTY) {
256
+ process.stdin.once('data', (buf) => {
257
+ handleHealApproval(buf.toString().trim().toLowerCase());
258
+ });
259
+ } else {
260
+ rl.once('line', (line) => handleHealApproval(line.trim().toLowerCase()));
261
+ }
262
+ break;
263
+ }
264
+
192
265
  case 'schedule.fired': {
193
266
  // Show when a scheduled job fires — even if user is idle
194
267
  const ts = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
@@ -276,6 +349,50 @@ async function runTask(input) {
276
349
  const s = await res.json();
277
350
  sessionId = s.session_id ?? s.id;
278
351
  spinner.start('Thinking'); // show immediately after session created
352
+
353
+ // Polling fallback — runs concurrently with WS events.
354
+ // Catches completion when WS is disconnected (e.g. daemon just restarted).
355
+ let lastPolledStep = 0;
356
+ const sid = sessionId;
357
+ const pollTimer = setInterval(async () => {
358
+ if (!pendingResolve || sessionId !== sid) { clearInterval(pollTimer); return; }
359
+ try {
360
+ const r = await fetch(`${BASE_URL}/api/sessions/${sid}`, { signal: AbortSignal.timeout(2000) });
361
+ const session = await r.json();
362
+
363
+ // Show any new steps not yet shown via WS
364
+ const steps = session.steps ?? [];
365
+ for (let j = lastPolledStep; j < steps.length; j++) {
366
+ spinner.stop();
367
+ console.log(` \x1b[2m›\x1b[0m ${steps[j].description}`);
368
+ spinner.start(steps[j].description.slice(0, 50));
369
+ }
370
+ lastPolledStep = steps.length;
371
+
372
+ if (session.status === 'completed' || session.status === 'failed') {
373
+ clearInterval(pollTimer);
374
+ if (!pendingResolve) return; // WS already handled it
375
+ spinner.stop();
376
+ if (session.status === 'completed') {
377
+ const out = session.result?.output;
378
+ if (out && typeof out === 'string') {
379
+ console.log(`\n ${out}`);
380
+ }
381
+ if (session.result?.files_written?.length) console.log(` \x1b[32m✓\x1b[0m Files: ${session.result.files_written.join(', ')}`);
382
+ if (session.result?.tokens_used) console.log(` \x1b[2m${session.result.tokens_used} tokens\x1b[0m`);
383
+ console.log(`\n \x1b[32m✓ Done\x1b[0m\n`);
384
+ } else {
385
+ console.log(`\n \x1b[31m✗ Failed:\x1b[0m ${session.error}\n`);
386
+ }
387
+ const resolve_ = pendingResolve;
388
+ pendingResolve = null;
389
+ sessionId = null;
390
+ resolve_();
391
+ rl.prompt();
392
+ }
393
+ } catch {}
394
+ }, 1500);
395
+
279
396
  return new Promise(resolve => { pendingResolve = resolve; });
280
397
  } catch (e) {
281
398
  console.log(` ${fmt(C.red, '✗')} ${e.message}`);
@@ -654,9 +771,10 @@ rl.on('line', async (input) => {
654
771
  rl.prompt();
655
772
  } else {
656
773
  await runTask(line);
657
- // prompt() is called from WS handler after session.completed
658
- // but fall back if WS not connected
659
- if (!wsReady) rl.prompt();
774
+ // runTask resolves when session completes (via WS or polling fallback)
775
+ // rl.prompt() may already have been called by the handler that resolved it,
776
+ // but calling it again is a safe no-op if readline is already prompting
777
+ if (!streaming) rl.prompt();
660
778
  }
661
779
  });
662
780
 
package/dist/daemon.mjs CHANGED
@@ -1501,7 +1501,7 @@ var init_EdgeWeightUpdater = __esm({
1501
1501
  this.weightLog.append(event);
1502
1502
  }
1503
1503
  sleep(ms) {
1504
- return new Promise((resolve14) => setTimeout(resolve14, ms));
1504
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
1505
1505
  }
1506
1506
  };
1507
1507
  }
@@ -2717,7 +2717,7 @@ var init_AgentExecutor = __esm({
2717
2717
  }
2718
2718
  }
2719
2719
  shellExec(command, timeoutMs) {
2720
- return new Promise((resolve14) => {
2720
+ return new Promise((resolve15) => {
2721
2721
  const chunks = [];
2722
2722
  const proc = spawn2("bash", ["-c", command], {
2723
2723
  cwd: this.cwd,
@@ -2728,10 +2728,10 @@ var init_AgentExecutor = __esm({
2728
2728
  proc.stderr.on("data", (d) => chunks.push(d.toString()));
2729
2729
  proc.on("close", (code) => {
2730
2730
  const output = chunks.join("").trim();
2731
- resolve14(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2731
+ resolve15(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
2732
2732
  });
2733
2733
  proc.on("error", (err) => {
2734
- resolve14(`Error: ${err.message}`);
2734
+ resolve15(`Error: ${err.message}`);
2735
2735
  });
2736
2736
  });
2737
2737
  }
@@ -2960,6 +2960,258 @@ var init_ExecutionVerifier = __esm({
2960
2960
  }
2961
2961
  });
2962
2962
 
2963
+ // packages/daemon/src/RuntimeSelfHeal.ts
2964
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "node:fs";
2965
+ import { resolve as resolve5, dirname as dirname3 } from "node:path";
2966
+ import { fileURLToPath } from "node:url";
2967
+ import { execSync as execSync4, spawn as spawn3 } from "node:child_process";
2968
+ function isRuntimeBug(error) {
2969
+ if (TASK_FAILURE_PATTERNS.some((p) => p.test(error))) return false;
2970
+ return RUNTIME_BUG_PATTERNS.some((p) => p.test(error));
2971
+ }
2972
+ function parseStackTrace(stack) {
2973
+ const lines = stack.split("\n");
2974
+ for (const line of lines) {
2975
+ const match = line.match(/\((.+):(\d+):(\d+)\)/) ?? line.match(/at (.+):(\d+):(\d+)$/);
2976
+ if (!match) continue;
2977
+ let filePath = match[1];
2978
+ if (filePath.startsWith("file://")) filePath = fileURLToPath(filePath);
2979
+ if (!filePath.includes("packages") && !filePath.includes("dist/daemon")) continue;
2980
+ return {
2981
+ file: filePath,
2982
+ line: parseInt(match[2], 10),
2983
+ col: parseInt(match[3], 10),
2984
+ relPath: filePath.replace(/.*\/0agent[^/]*\//, "")
2985
+ };
2986
+ }
2987
+ return null;
2988
+ }
2989
+ function extractContext(filePath, errorLine, contextLines = 30) {
2990
+ try {
2991
+ if (!existsSync6(filePath)) return null;
2992
+ const content = readFileSync5(filePath, "utf8");
2993
+ const lines = content.split("\n");
2994
+ const start = Math.max(0, errorLine - contextLines);
2995
+ const end = Math.min(lines.length, errorLine + contextLines);
2996
+ return lines.slice(start, end).map((l, i) => {
2997
+ const lineNum = start + i + 1;
2998
+ const marker = lineNum === errorLine ? ">>>" : " ";
2999
+ return `${marker} ${String(lineNum).padStart(4)} | ${l}`;
3000
+ }).join("\n");
3001
+ } catch {
3002
+ return null;
3003
+ }
3004
+ }
3005
+ var RUNTIME_BUG_PATTERNS, TASK_FAILURE_PATTERNS, RuntimeSelfHeal;
3006
+ var init_RuntimeSelfHeal = __esm({
3007
+ "packages/daemon/src/RuntimeSelfHeal.ts"() {
3008
+ "use strict";
3009
+ RUNTIME_BUG_PATTERNS = [
3010
+ /packages\/(daemon|core|subagent|mcp-hub)\/src\//,
3011
+ /dist\/daemon\.mjs/,
3012
+ /TypeError: Cannot read propert/,
3013
+ /TypeError: .+ is not a function/,
3014
+ /ReferenceError: .+ is not defined/,
3015
+ /TypeError: Cannot set propert/,
3016
+ /TypeError: .+ is undefined/
3017
+ ];
3018
+ TASK_FAILURE_PATTERNS = [
3019
+ /Anthropic \d{3}:/,
3020
+ // API error — not our bug
3021
+ /HTTP \d{3}/,
3022
+ // HTTP error
3023
+ /ENOENT/,
3024
+ // file not found
3025
+ /EACCES/,
3026
+ // permission denied
3027
+ /AbortError/,
3028
+ // timeout
3029
+ /network/i
3030
+ // network issue
3031
+ ];
3032
+ RuntimeSelfHeal = class {
3033
+ constructor(llm, eventBus) {
3034
+ this.llm = llm;
3035
+ this.eventBus = eventBus;
3036
+ let dir = dirname3(fileURLToPath(import.meta.url));
3037
+ while (dir !== "/" && !existsSync6(resolve5(dir, "package.json"))) {
3038
+ dir = resolve5(dir, "..");
3039
+ }
3040
+ this.projectRoot = dir;
3041
+ }
3042
+ projectRoot;
3043
+ /**
3044
+ * Analyze an error and propose a code fix.
3045
+ * Returns null if the error is a task failure (not a code bug).
3046
+ */
3047
+ async analyze(error, task) {
3048
+ if (!isRuntimeBug(error)) return null;
3049
+ const location = parseStackTrace(error);
3050
+ if (!location) return null;
3051
+ const tsPath = this.findSourceFile(location);
3052
+ const codePath = tsPath ?? location.file;
3053
+ const context = extractContext(codePath, location.line);
3054
+ if (!context) return null;
3055
+ const proposal = await this.proposeFix(error, task, codePath, location.line, context);
3056
+ return proposal;
3057
+ }
3058
+ /**
3059
+ * Emit a WS event to notify the chat TUI that a heal proposal is ready.
3060
+ * The human must call applyPatch() after approving.
3061
+ */
3062
+ emitProposal(proposal) {
3063
+ this.eventBus.emit({
3064
+ type: "runtime.heal_proposal",
3065
+ proposal
3066
+ });
3067
+ }
3068
+ /**
3069
+ * Apply an approved patch to the source file, rebuild bundle, restart daemon.
3070
+ * Called only after human approval.
3071
+ */
3072
+ async applyPatch(proposal) {
3073
+ const tsPath = this.findSourceFile(proposal.location);
3074
+ if (!tsPath || !existsSync6(tsPath)) {
3075
+ return {
3076
+ applied: false,
3077
+ restarted: false,
3078
+ message: "Source files not found. If running from source, apply the fix manually and rebuild."
3079
+ };
3080
+ }
3081
+ try {
3082
+ const original = readFileSync5(tsPath, "utf8");
3083
+ const backup = tsPath + ".bak";
3084
+ writeFileSync3(backup, original, "utf8");
3085
+ if (!original.includes(proposal.original_code.trim())) {
3086
+ return {
3087
+ applied: false,
3088
+ restarted: false,
3089
+ message: "Could not locate the code section to patch. The file may have changed."
3090
+ };
3091
+ }
3092
+ const patched = original.replace(proposal.original_code, proposal.proposed_code);
3093
+ writeFileSync3(tsPath, patched, "utf8");
3094
+ const bundleScript = resolve5(this.projectRoot, "scripts", "bundle.mjs");
3095
+ if (existsSync6(bundleScript)) {
3096
+ try {
3097
+ execSync4(`node "${bundleScript}"`, {
3098
+ cwd: this.projectRoot,
3099
+ timeout: 6e4,
3100
+ stdio: "ignore"
3101
+ });
3102
+ } catch {
3103
+ writeFileSync3(tsPath, original, "utf8");
3104
+ return {
3105
+ applied: false,
3106
+ restarted: false,
3107
+ message: "Bundle rebuild failed. Backup restored. Fix may have introduced a syntax error."
3108
+ };
3109
+ }
3110
+ }
3111
+ setTimeout(() => this.restartDaemon(), 1500);
3112
+ return {
3113
+ applied: true,
3114
+ restarted: true,
3115
+ message: `Patched ${proposal.location.relPath}. Restarting daemon...`
3116
+ };
3117
+ } catch (err) {
3118
+ return {
3119
+ applied: false,
3120
+ restarted: false,
3121
+ message: `Failed to apply patch: ${err instanceof Error ? err.message : String(err)}`
3122
+ };
3123
+ }
3124
+ }
3125
+ // ─── Private helpers ───────────────────────────────────────────────────────
3126
+ findSourceFile(location) {
3127
+ const candidates = [
3128
+ resolve5(this.projectRoot, location.relPath),
3129
+ // If relPath starts with dist/, look in src/
3130
+ resolve5(this.projectRoot, location.relPath.replace(/^dist\//, "src/").replace(/\.js$/, ".ts")),
3131
+ resolve5(this.projectRoot, "packages", "daemon", "src", location.relPath.replace(/.*src\//, "")),
3132
+ resolve5(this.projectRoot, "packages", "core", "src", location.relPath.replace(/.*src\//, ""))
3133
+ ];
3134
+ for (const p of candidates) {
3135
+ if (existsSync6(p)) return p;
3136
+ }
3137
+ return null;
3138
+ }
3139
+ async proposeFix(error, task, filePath, errorLine, codeContext) {
3140
+ const systemPrompt = `You are an expert TypeScript/Node.js debugger analyzing a runtime error in the 0agent codebase.
3141
+ Your job: identify the exact bug and propose a minimal, surgical fix.
3142
+ Return ONLY valid JSON \u2014 no markdown, no explanation outside the JSON.`;
3143
+ const userPrompt = `Runtime error in the 0agent daemon:
3144
+
3145
+ ERROR:
3146
+ ${error.slice(0, 1e3)}
3147
+
3148
+ TASK THAT TRIGGERED IT:
3149
+ ${task.slice(0, 200)}
3150
+
3151
+ CODE CONTEXT (>>> marks error line):
3152
+ ${codeContext}
3153
+
3154
+ FILE: ${filePath}
3155
+
3156
+ Respond with this exact JSON structure:
3157
+ {
3158
+ "explanation": "one-sentence explanation of the bug",
3159
+ "confidence": "high|medium|low",
3160
+ "original_code": "exact code string to replace (must match file exactly)",
3161
+ "proposed_code": "fixed replacement code",
3162
+ "diff": "human-readable before/after showing the change"
3163
+ }
3164
+
3165
+ Rules:
3166
+ - original_code must be an EXACT substring of the file (copy-paste from context above)
3167
+ - proposed_code must be syntactically valid TypeScript
3168
+ - Make the minimal change that fixes the bug
3169
+ - If you cannot determine a safe fix, set confidence to "low" and explain why`;
3170
+ try {
3171
+ const response = await this.llm.complete(
3172
+ [{ role: "user", content: userPrompt }],
3173
+ systemPrompt
3174
+ );
3175
+ const json = JSON.parse(response.content.trim().replace(/^```json\n?/, "").replace(/\n?```$/, ""));
3176
+ return {
3177
+ proposal_id: crypto.randomUUID().slice(0, 8),
3178
+ error_summary: error.split("\n")[0].slice(0, 120),
3179
+ location: { file: filePath, line: errorLine, col: 0, relPath: filePath.replace(this.projectRoot + "/", "") },
3180
+ original_code: json.original_code ?? "",
3181
+ proposed_code: json.proposed_code ?? "",
3182
+ diff: json.diff ?? "",
3183
+ explanation: json.explanation ?? "",
3184
+ confidence: json.confidence ?? "medium"
3185
+ };
3186
+ } catch {
3187
+ return {
3188
+ proposal_id: crypto.randomUUID().slice(0, 8),
3189
+ error_summary: error.split("\n")[0].slice(0, 120),
3190
+ location: { file: filePath, line: errorLine, col: 0, relPath: filePath.replace(this.projectRoot + "/", "") },
3191
+ original_code: "",
3192
+ proposed_code: "",
3193
+ diff: "",
3194
+ explanation: "Could not generate a fix proposal.",
3195
+ confidence: "low"
3196
+ };
3197
+ }
3198
+ }
3199
+ restartDaemon() {
3200
+ const bundlePath = resolve5(this.projectRoot, "dist", "daemon.mjs");
3201
+ if (existsSync6(bundlePath)) {
3202
+ const child = spawn3(process.execPath, [bundlePath], {
3203
+ detached: true,
3204
+ stdio: "ignore",
3205
+ env: process.env
3206
+ });
3207
+ child.unref();
3208
+ }
3209
+ setTimeout(() => process.exit(0), 200);
3210
+ }
3211
+ };
3212
+ }
3213
+ });
3214
+
2963
3215
  // packages/daemon/src/SelfHealLoop.ts
2964
3216
  var SelfHealLoop_exports = {};
2965
3217
  __export(SelfHealLoop_exports, {
@@ -2971,13 +3223,15 @@ var init_SelfHealLoop = __esm({
2971
3223
  "use strict";
2972
3224
  init_AgentExecutor();
2973
3225
  init_ExecutionVerifier();
3226
+ init_RuntimeSelfHeal();
2974
3227
  SelfHealLoop = class {
2975
- constructor(llm, config, onStep, onToken, maxAttempts = 3) {
3228
+ constructor(llm, config, onStep, onToken, maxAttempts = 3, runtimeHealer) {
2976
3229
  this.llm = llm;
2977
3230
  this.config = config;
2978
3231
  this.onStep = onStep;
2979
3232
  this.onToken = onToken;
2980
3233
  this.maxAttempts = maxAttempts;
3234
+ this.runtimeHealer = runtimeHealer;
2981
3235
  this.verifier = new ExecutionVerifier(config.cwd);
2982
3236
  }
2983
3237
  verifier;
@@ -3005,6 +3259,17 @@ var init_SelfHealLoop = __esm({
3005
3259
  if (!lastVerification.success) {
3006
3260
  this.onStep(`\u26A0 Verification: ${lastVerification.details}`);
3007
3261
  }
3262
+ const lastError = finalResult?.output ?? "";
3263
+ if (this.runtimeHealer && isRuntimeBug(lastError)) {
3264
+ this.onStep("\u{1F527} This looks like a runtime code bug \u2014 analyzing for self-fix...");
3265
+ this.runtimeHealer.analyze(lastError, "session task").then((proposal) => {
3266
+ if (proposal) {
3267
+ this.onStep(`\u{1F527} Fix proposed for ${proposal.location.relPath}:${proposal.location.line} \u2014 awaiting your approval in terminal`);
3268
+ this.runtimeHealer.emitProposal(proposal);
3269
+ }
3270
+ }).catch(() => {
3271
+ });
3272
+ }
3008
3273
  return { ...finalResult, heal_attempts: attempts, healed: false };
3009
3274
  }
3010
3275
  buildHealContext(attempt, verification, result, originalContext) {
@@ -3036,9 +3301,9 @@ var ProactiveSurface_exports = {};
3036
3301
  __export(ProactiveSurface_exports, {
3037
3302
  ProactiveSurface: () => ProactiveSurface
3038
3303
  });
3039
- import { execSync as execSync5 } from "node:child_process";
3040
- import { existsSync as existsSync12, readFileSync as readFileSync12, statSync, readdirSync as readdirSync5 } from "node:fs";
3041
- import { resolve as resolve11, join as join3 } from "node:path";
3304
+ import { execSync as execSync6 } from "node:child_process";
3305
+ import { existsSync as existsSync13, readFileSync as readFileSync13, statSync, readdirSync as readdirSync5 } from "node:fs";
3306
+ import { resolve as resolve12, join as join3 } from "node:path";
3042
3307
  function readdirSafe(dir) {
3043
3308
  try {
3044
3309
  return readdirSync5(dir);
@@ -3087,7 +3352,7 @@ var init_ProactiveSurface = __esm({
3087
3352
  return [...this.insights];
3088
3353
  }
3089
3354
  async poll() {
3090
- if (!existsSync12(resolve11(this.cwd, ".git"))) return;
3355
+ if (!existsSync13(resolve12(this.cwd, ".git"))) return;
3091
3356
  const newInsights = [];
3092
3357
  const gitInsight = this.checkGitActivity();
3093
3358
  if (gitInsight) newInsights.push(gitInsight);
@@ -3105,7 +3370,7 @@ var init_ProactiveSurface = __esm({
3105
3370
  try {
3106
3371
  const currentHead = this.getGitHead();
3107
3372
  if (!currentHead || currentHead === this.lastKnownHead) return null;
3108
- const log = execSync5(
3373
+ const log = execSync6(
3109
3374
  `git log ${this.lastKnownHead}..${currentHead} --oneline --stat`,
3110
3375
  { cwd: this.cwd, timeout: 3e3, encoding: "utf8" }
3111
3376
  ).trim();
@@ -3131,13 +3396,13 @@ var init_ProactiveSurface = __esm({
3131
3396
  ];
3132
3397
  for (const dir of outputPaths) {
3133
3398
  try {
3134
- if (!existsSync12(dir)) continue;
3399
+ if (!existsSync13(dir)) continue;
3135
3400
  const xmlFiles = readdirSafe(dir).filter((f) => f.endsWith(".xml"));
3136
3401
  for (const xml of xmlFiles) {
3137
3402
  const path = join3(dir, xml);
3138
3403
  const stat = statSync(path);
3139
3404
  if (stat.mtimeMs < this.lastPollAt) continue;
3140
- const content = readFileSync12(path, "utf8");
3405
+ const content = readFileSync13(path, "utf8");
3141
3406
  const failures = [...content.matchAll(/<failure[^>]*message="([^"]+)"/g)].length;
3142
3407
  if (failures > 0) {
3143
3408
  return this.makeInsight(
@@ -3181,7 +3446,7 @@ var init_ProactiveSurface = __esm({
3181
3446
  }
3182
3447
  getGitHead() {
3183
3448
  try {
3184
- return execSync5("git rev-parse HEAD", { cwd: this.cwd, timeout: 1e3, encoding: "utf8" }).trim();
3449
+ return execSync6("git rev-parse HEAD", { cwd: this.cwd, timeout: 1e3, encoding: "utf8" }).trim();
3185
3450
  } catch {
3186
3451
  return "";
3187
3452
  }
@@ -3192,8 +3457,8 @@ var init_ProactiveSurface = __esm({
3192
3457
 
3193
3458
  // packages/daemon/src/ZeroAgentDaemon.ts
3194
3459
  init_src();
3195
- import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync2, existsSync as existsSync13, mkdirSync as mkdirSync6 } from "node:fs";
3196
- import { resolve as resolve12 } from "node:path";
3460
+ import { writeFileSync as writeFileSync8, unlinkSync as unlinkSync2, existsSync as existsSync14, mkdirSync as mkdirSync6 } from "node:fs";
3461
+ import { resolve as resolve13 } from "node:path";
3197
3462
  import { homedir as homedir8 } from "node:os";
3198
3463
 
3199
3464
  // packages/daemon/src/config/DaemonConfig.ts
@@ -3946,19 +4211,19 @@ var ProjectScanner = class {
3946
4211
  async getRunningPorts() {
3947
4212
  const open = [];
3948
4213
  await Promise.all(PORTS_TO_CHECK.map(
3949
- (port) => new Promise((resolve14) => {
4214
+ (port) => new Promise((resolve15) => {
3950
4215
  const s = createServer();
3951
4216
  s.listen(port, "127.0.0.1", () => {
3952
4217
  s.close();
3953
- resolve14();
4218
+ resolve15();
3954
4219
  });
3955
4220
  s.on("error", () => {
3956
4221
  open.push(port);
3957
- resolve14();
4222
+ resolve15();
3958
4223
  });
3959
4224
  setTimeout(() => {
3960
4225
  s.close();
3961
- resolve14();
4226
+ resolve15();
3962
4227
  }, 200);
3963
4228
  })
3964
4229
  ));
@@ -4034,8 +4299,8 @@ var ConversationStore = class {
4034
4299
  };
4035
4300
 
4036
4301
  // packages/daemon/src/SessionManager.ts
4037
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "node:fs";
4038
- import { resolve as resolve5 } from "node:path";
4302
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "node:fs";
4303
+ import { resolve as resolve6 } from "node:path";
4039
4304
  import { homedir as homedir2 } from "node:os";
4040
4305
  import YAML2 from "yaml";
4041
4306
  var SessionManager = class {
@@ -4357,7 +4622,7 @@ Current task:`;
4357
4622
  model: agentResult.model
4358
4623
  });
4359
4624
  } else {
4360
- const cfgPath = resolve5(homedir2(), ".0agent", "config.yaml");
4625
+ const cfgPath = resolve6(homedir2(), ".0agent", "config.yaml");
4361
4626
  const output = `No LLM API key found. Add one to ${cfgPath} or run: 0agent init`;
4362
4627
  this.addStep(sessionId, "\u26A0 No LLM API key configured \u2014 run: 0agent init");
4363
4628
  this.completeSession(sessionId, { output });
@@ -4398,9 +4663,9 @@ Current task:`;
4398
4663
  */
4399
4664
  getFreshLLM() {
4400
4665
  try {
4401
- const configPath = resolve5(homedir2(), ".0agent", "config.yaml");
4402
- if (!existsSync6(configPath)) return this.llm;
4403
- const raw = readFileSync5(configPath, "utf8");
4666
+ const configPath = resolve6(homedir2(), ".0agent", "config.yaml");
4667
+ if (!existsSync7(configPath)) return this.llm;
4668
+ const raw = readFileSync6(configPath, "utf8");
4404
4669
  const cfg = YAML2.parse(raw);
4405
4670
  const providers = cfg.llm_providers;
4406
4671
  if (!providers?.length) return this.llm;
@@ -4660,7 +4925,7 @@ var BackgroundWorkers = class {
4660
4925
  };
4661
4926
 
4662
4927
  // packages/daemon/src/SkillRegistry.ts
4663
- import { readFileSync as readFileSync6, readdirSync as readdirSync3, existsSync as existsSync7, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync3 } from "node:fs";
4928
+ import { readFileSync as readFileSync7, readdirSync as readdirSync3, existsSync as existsSync8, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync3 } from "node:fs";
4664
4929
  import { join as join2 } from "node:path";
4665
4930
  import { homedir as homedir3 } from "node:os";
4666
4931
  import YAML3 from "yaml";
@@ -4683,11 +4948,11 @@ var SkillRegistry = class {
4683
4948
  this.loadFromDir(this.customDir, false);
4684
4949
  }
4685
4950
  loadFromDir(dir, isBuiltin) {
4686
- if (!existsSync7(dir)) return;
4951
+ if (!existsSync8(dir)) return;
4687
4952
  const files = readdirSync3(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
4688
4953
  for (const file of files) {
4689
4954
  try {
4690
- const raw = readFileSync6(join2(dir, file), "utf8");
4955
+ const raw = readFileSync7(join2(dir, file), "utf8");
4691
4956
  const skill = YAML3.parse(raw);
4692
4957
  if (skill.name) {
4693
4958
  this.skills.set(skill.name, skill);
@@ -4724,7 +4989,7 @@ var SkillRegistry = class {
4724
4989
  }
4725
4990
  mkdirSync3(this.customDir, { recursive: true });
4726
4991
  const filePath = join2(this.customDir, `${name}.yaml`);
4727
- writeFileSync3(filePath, yamlContent, "utf8");
4992
+ writeFileSync4(filePath, yamlContent, "utf8");
4728
4993
  const skill = YAML3.parse(yamlContent);
4729
4994
  this.skills.set(name, skill);
4730
4995
  return skill;
@@ -4737,7 +5002,7 @@ var SkillRegistry = class {
4737
5002
  throw new Error(`Cannot delete built-in skill: ${name}`);
4738
5003
  }
4739
5004
  const filePath = join2(this.customDir, `${name}.yaml`);
4740
- if (existsSync7(filePath)) {
5005
+ if (existsSync8(filePath)) {
4741
5006
  unlinkSync(filePath);
4742
5007
  }
4743
5008
  this.skills.delete(name);
@@ -4748,11 +5013,11 @@ var SkillRegistry = class {
4748
5013
  };
4749
5014
 
4750
5015
  // packages/daemon/src/HTTPServer.ts
4751
- import { Hono as Hono13 } from "hono";
5016
+ import { Hono as Hono14 } from "hono";
4752
5017
  import { serve } from "@hono/node-server";
4753
- import { readFileSync as readFileSync8 } from "node:fs";
4754
- import { resolve as resolve7, dirname as dirname3 } from "node:path";
4755
- import { fileURLToPath } from "node:url";
5018
+ import { readFileSync as readFileSync9 } from "node:fs";
5019
+ import { resolve as resolve8, dirname as dirname4 } from "node:path";
5020
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
4756
5021
 
4757
5022
  // packages/daemon/src/routes/health.ts
4758
5023
  import { Hono } from "hono";
@@ -5042,8 +5307,8 @@ function memoryRoutes(deps) {
5042
5307
 
5043
5308
  // packages/daemon/src/routes/llm.ts
5044
5309
  import { Hono as Hono10 } from "hono";
5045
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "node:fs";
5046
- import { resolve as resolve6 } from "node:path";
5310
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "node:fs";
5311
+ import { resolve as resolve7 } from "node:path";
5047
5312
  import { homedir as homedir4 } from "node:os";
5048
5313
  import YAML4 from "yaml";
5049
5314
  function llmRoutes() {
@@ -5051,11 +5316,11 @@ function llmRoutes() {
5051
5316
  app.post("/ping", async (c) => {
5052
5317
  const start = Date.now();
5053
5318
  try {
5054
- const configPath = resolve6(homedir4(), ".0agent", "config.yaml");
5055
- if (!existsSync8(configPath)) {
5319
+ const configPath = resolve7(homedir4(), ".0agent", "config.yaml");
5320
+ if (!existsSync9(configPath)) {
5056
5321
  return c.json({ ok: false, error: "Config not found. Run: 0agent init" });
5057
5322
  }
5058
- const cfg = YAML4.parse(readFileSync7(configPath, "utf8"));
5323
+ const cfg = YAML4.parse(readFileSync8(configPath, "utf8"));
5059
5324
  const providers = cfg.llm_providers;
5060
5325
  const def = providers?.find((p) => p.is_default) ?? providers?.[0];
5061
5326
  if (!def) {
@@ -5529,18 +5794,56 @@ function scheduleRoutes(deps) {
5529
5794
  return app;
5530
5795
  }
5531
5796
 
5797
+ // packages/daemon/src/routes/runtime.ts
5798
+ import { Hono as Hono13 } from "hono";
5799
+ var pendingProposals = /* @__PURE__ */ new Map();
5800
+ function runtimeRoutes(deps) {
5801
+ const app = new Hono13();
5802
+ app.post("/proposals", async (c) => {
5803
+ const proposal = await c.req.json();
5804
+ pendingProposals.set(proposal.proposal_id, {
5805
+ proposal,
5806
+ expires: Date.now() + 10 * 6e4
5807
+ });
5808
+ return c.json({ ok: true, proposal_id: proposal.proposal_id });
5809
+ });
5810
+ app.post("/proposals/:id/approve", async (c) => {
5811
+ if (!deps.healer) return c.json({ ok: false, error: "Self-heal not available" }, 503);
5812
+ const id = c.req.param("id");
5813
+ const entry = pendingProposals.get(id);
5814
+ if (!entry) return c.json({ ok: false, error: "Proposal not found or expired" }, 404);
5815
+ if (Date.now() > entry.expires) {
5816
+ pendingProposals.delete(id);
5817
+ return c.json({ ok: false, error: "Proposal expired" }, 410);
5818
+ }
5819
+ pendingProposals.delete(id);
5820
+ const result = await deps.healer.applyPatch(entry.proposal);
5821
+ return c.json(result);
5822
+ });
5823
+ app.delete("/proposals/:id", (c) => {
5824
+ pendingProposals.delete(c.req.param("id"));
5825
+ return c.json({ ok: true });
5826
+ });
5827
+ app.get("/proposals", (c) => {
5828
+ const now = Date.now();
5829
+ const active = [...pendingProposals.entries()].filter(([, v]) => v.expires > now).map(([, v]) => v.proposal);
5830
+ return c.json(active);
5831
+ });
5832
+ return app;
5833
+ }
5834
+
5532
5835
  // packages/daemon/src/HTTPServer.ts
5533
5836
  function findGraphHtml() {
5534
5837
  const candidates = [
5535
- resolve7(dirname3(fileURLToPath(import.meta.url)), "graph.html"),
5838
+ resolve8(dirname4(fileURLToPath2(import.meta.url)), "graph.html"),
5536
5839
  // dev (src/)
5537
- resolve7(dirname3(fileURLToPath(import.meta.url)), "..", "graph.html"),
5840
+ resolve8(dirname4(fileURLToPath2(import.meta.url)), "..", "graph.html"),
5538
5841
  // bundled (dist/../)
5539
- resolve7(dirname3(fileURLToPath(import.meta.url)), "..", "dist", "graph.html")
5842
+ resolve8(dirname4(fileURLToPath2(import.meta.url)), "..", "dist", "graph.html")
5540
5843
  ];
5541
5844
  for (const p of candidates) {
5542
5845
  try {
5543
- readFileSync8(p);
5846
+ readFileSync9(p);
5544
5847
  return p;
5545
5848
  } catch {
5546
5849
  }
@@ -5554,7 +5857,7 @@ var HTTPServer = class {
5554
5857
  deps;
5555
5858
  constructor(deps) {
5556
5859
  this.deps = deps;
5557
- this.app = new Hono13();
5860
+ this.app = new Hono14();
5558
5861
  this.app.route("/api/health", healthRoutes({ getStatus: deps.getStatus }));
5559
5862
  this.app.route("/api/sessions", sessionRoutes({ sessions: deps.sessions }));
5560
5863
  this.app.route("/api/graph", graphRoutes({ graph: deps.graph }));
@@ -5566,13 +5869,14 @@ var HTTPServer = class {
5566
5869
  this.app.route("/api/memory", memoryRoutes({ getSync: deps.getMemorySync ?? (() => null) }));
5567
5870
  this.app.route("/api/llm", llmRoutes());
5568
5871
  this.app.route("/api/schedule", scheduleRoutes({ scheduler: deps.scheduler ?? null }));
5872
+ this.app.route("/api/runtime", runtimeRoutes({ healer: deps.healer ?? null }));
5569
5873
  this.app.route("/api/codespace", codespaceRoutes({
5570
5874
  getManager: deps.getCodespaceManager ?? (() => null),
5571
5875
  setup: deps.setupCodespace ?? (async () => ({ started: false, error: "Not configured" }))
5572
5876
  }));
5573
5877
  const serveGraph = (c) => {
5574
5878
  try {
5575
- const html = readFileSync8(GRAPH_HTML_PATH, "utf8");
5879
+ const html = readFileSync9(GRAPH_HTML_PATH, "utf8");
5576
5880
  return c.html(html);
5577
5881
  } catch {
5578
5882
  return c.html("<p>Graph UI not found. Run: pnpm build</p>");
@@ -5582,7 +5886,7 @@ var HTTPServer = class {
5582
5886
  this.app.get("/graph", serveGraph);
5583
5887
  }
5584
5888
  start() {
5585
- return new Promise((resolve14) => {
5889
+ return new Promise((resolve15) => {
5586
5890
  this.server = serve(
5587
5891
  {
5588
5892
  fetch: this.app.fetch,
@@ -5590,20 +5894,20 @@ var HTTPServer = class {
5590
5894
  hostname: this.deps.host
5591
5895
  },
5592
5896
  () => {
5593
- resolve14();
5897
+ resolve15();
5594
5898
  }
5595
5899
  );
5596
5900
  });
5597
5901
  }
5598
5902
  stop() {
5599
- return new Promise((resolve14, reject) => {
5903
+ return new Promise((resolve15, reject) => {
5600
5904
  if (!this.server) {
5601
- resolve14();
5905
+ resolve15();
5602
5906
  return;
5603
5907
  }
5604
5908
  this.server.close((err) => {
5605
5909
  if (err) reject(err);
5606
- else resolve14();
5910
+ else resolve15();
5607
5911
  });
5608
5912
  });
5609
5913
  }
@@ -5614,11 +5918,11 @@ var HTTPServer = class {
5614
5918
 
5615
5919
  // packages/daemon/src/IdentityManager.ts
5616
5920
  init_src();
5617
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
5618
- import { resolve as resolve8, dirname as dirname4 } from "node:path";
5921
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "node:fs";
5922
+ import { resolve as resolve9, dirname as dirname5 } from "node:path";
5619
5923
  import { homedir as homedir5, hostname } from "node:os";
5620
5924
  import YAML5 from "yaml";
5621
- var IDENTITY_PATH = resolve8(homedir5(), ".0agent", "identity.yaml");
5925
+ var IDENTITY_PATH = resolve9(homedir5(), ".0agent", "identity.yaml");
5622
5926
  var DEFAULT_IDENTITY = {
5623
5927
  name: "User",
5624
5928
  device_id: `unknown-device`,
@@ -5634,8 +5938,8 @@ var IdentityManager = class {
5634
5938
  * Load or create identity. Call once at daemon startup.
5635
5939
  */
5636
5940
  async init() {
5637
- if (existsSync9(IDENTITY_PATH)) {
5638
- const raw = readFileSync9(IDENTITY_PATH, "utf8");
5941
+ if (existsSync10(IDENTITY_PATH)) {
5942
+ const raw = readFileSync10(IDENTITY_PATH, "utf8");
5639
5943
  this.identity = YAML5.parse(raw);
5640
5944
  } else {
5641
5945
  this.identity = {
@@ -5686,25 +5990,25 @@ var IdentityManager = class {
5686
5990
  return lines.join(" ");
5687
5991
  }
5688
5992
  save() {
5689
- const dir = dirname4(IDENTITY_PATH);
5690
- if (!existsSync9(dir)) {
5993
+ const dir = dirname5(IDENTITY_PATH);
5994
+ if (!existsSync10(dir)) {
5691
5995
  mkdirSync4(dir, { recursive: true });
5692
5996
  }
5693
- writeFileSync4(IDENTITY_PATH, YAML5.stringify(this.identity), "utf8");
5997
+ writeFileSync5(IDENTITY_PATH, YAML5.stringify(this.identity), "utf8");
5694
5998
  }
5695
5999
  };
5696
6000
 
5697
6001
  // packages/daemon/src/TeamManager.ts
5698
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync5 } from "node:fs";
5699
- import { resolve as resolve9 } from "node:path";
6002
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync5 } from "node:fs";
6003
+ import { resolve as resolve10 } from "node:path";
5700
6004
  import { homedir as homedir6 } from "node:os";
5701
6005
  import YAML6 from "yaml";
5702
- var TEAMS_PATH = resolve9(homedir6(), ".0agent", "teams.yaml");
6006
+ var TEAMS_PATH = resolve10(homedir6(), ".0agent", "teams.yaml");
5703
6007
  var TeamManager = class {
5704
6008
  config;
5705
6009
  constructor() {
5706
- if (existsSync10(TEAMS_PATH)) {
5707
- this.config = YAML6.parse(readFileSync10(TEAMS_PATH, "utf8"));
6010
+ if (existsSync11(TEAMS_PATH)) {
6011
+ this.config = YAML6.parse(readFileSync11(TEAMS_PATH, "utf8"));
5708
6012
  } else {
5709
6013
  this.config = { memberships: [] };
5710
6014
  }
@@ -5759,8 +6063,8 @@ var TeamManager = class {
5759
6063
  }
5760
6064
  }
5761
6065
  save() {
5762
- mkdirSync5(resolve9(homedir6(), ".0agent"), { recursive: true });
5763
- writeFileSync5(TEAMS_PATH, YAML6.stringify(this.config), "utf8");
6066
+ mkdirSync5(resolve10(homedir6(), ".0agent"), { recursive: true });
6067
+ writeFileSync6(TEAMS_PATH, YAML6.stringify(this.config), "utf8");
5764
6068
  }
5765
6069
  };
5766
6070
 
@@ -5843,8 +6147,8 @@ var TeamSync = class {
5843
6147
  };
5844
6148
 
5845
6149
  // packages/daemon/src/GitHubMemorySync.ts
5846
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
5847
- import { resolve as resolve10 } from "node:path";
6150
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync12, readdirSync as readdirSync4 } from "node:fs";
6151
+ import { resolve as resolve11 } from "node:path";
5848
6152
  import { homedir as homedir7 } from "node:os";
5849
6153
  var GITHUB_API = "https://api.github.com";
5850
6154
  async function ghFetch(path, token, opts) {
@@ -5964,10 +6268,10 @@ var GitHubMemorySync = class {
5964
6268
  )
5965
6269
  );
5966
6270
  }
5967
- const customSkillsDir = resolve10(homedir7(), ".0agent", "skills", "custom");
5968
- if (existsSync11(customSkillsDir)) {
6271
+ const customSkillsDir = resolve11(homedir7(), ".0agent", "skills", "custom");
6272
+ if (existsSync12(customSkillsDir)) {
5969
6273
  for (const file of readdirSync4(customSkillsDir).filter((f) => f.endsWith(".yaml"))) {
5970
- const content = readFileSync11(resolve10(customSkillsDir, file), "utf8");
6274
+ const content = readFileSync12(resolve11(customSkillsDir, file), "utf8");
5971
6275
  pushes.push(putFile(token, owner, repo, `skills/custom/${file}`, content, commitMsg));
5972
6276
  }
5973
6277
  }
@@ -6153,7 +6457,7 @@ var GitHubMemorySync = class {
6153
6457
  }
6154
6458
  async pullCustomSkills() {
6155
6459
  const { token, owner, repo } = this.config;
6156
- const dir = resolve10(homedir7(), ".0agent", "skills", "custom");
6460
+ const dir = resolve11(homedir7(), ".0agent", "skills", "custom");
6157
6461
  try {
6158
6462
  const res = await ghFetch(`/repos/${owner}/${repo}/contents/skills/custom`, token);
6159
6463
  if (!res.ok) return;
@@ -6163,7 +6467,7 @@ var GitHubMemorySync = class {
6163
6467
  if (content) {
6164
6468
  const { mkdirSync: mkdirSync7 } = await import("node:fs");
6165
6469
  mkdirSync7(dir, { recursive: true });
6166
- writeFileSync6(resolve10(dir, file.name), content, "utf8");
6470
+ writeFileSync7(resolve11(dir, file.name), content, "utf8");
6167
6471
  }
6168
6472
  }
6169
6473
  } catch {
@@ -6240,7 +6544,7 @@ git checkout <commit> graph/ # restore graph files
6240
6544
  };
6241
6545
 
6242
6546
  // packages/daemon/src/CodespaceManager.ts
6243
- import { execSync as execSync4, spawn as spawn3 } from "node:child_process";
6547
+ import { execSync as execSync5, spawn as spawn4 } from "node:child_process";
6244
6548
  var BROWSER_PORT_REMOTE = 3e3;
6245
6549
  var BROWSER_PORT_LOCAL = 3001;
6246
6550
  var DISPLAY_NAME = "0agent-browser";
@@ -6282,7 +6586,7 @@ var CodespaceManager = class {
6282
6586
  console.log(`[Codespace] Creating browser codespace from ${this.memoryRepo}...`);
6283
6587
  console.log("[Codespace] First time: ~2-3 minutes. Subsequent starts: ~30 seconds.");
6284
6588
  try {
6285
- const result = execSync4(
6589
+ const result = execSync5(
6286
6590
  `gh codespace create --repo "${this.memoryRepo}" --machine basicLinux32gb --display-name "${DISPLAY_NAME}" --json name`,
6287
6591
  { encoding: "utf8", timeout: 3e5 }
6288
6592
  );
@@ -6296,7 +6600,7 @@ var CodespaceManager = class {
6296
6600
  /** Find the 0agent-browser codespace by display name. */
6297
6601
  findExisting() {
6298
6602
  try {
6299
- const out = execSync4("gh codespace list --json name,state,displayName,repository", {
6603
+ const out = execSync5("gh codespace list --json name,state,displayName,repository", {
6300
6604
  encoding: "utf8",
6301
6605
  timeout: 1e4
6302
6606
  });
@@ -6312,7 +6616,7 @@ var CodespaceManager = class {
6312
6616
  const info = this.findExisting();
6313
6617
  if (info?.state === "Shutdown") {
6314
6618
  console.log("[Codespace] Starting stopped codespace (~30s)...");
6315
- execSync4(`gh codespace start --codespace "${name}"`, { timeout: 12e4 });
6619
+ execSync5(`gh codespace start --codespace "${name}"`, { timeout: 12e4 });
6316
6620
  await this.waitForState(name, "Available", 60);
6317
6621
  console.log("[Codespace] Codespace is running");
6318
6622
  } else if (info?.state === "Starting") {
@@ -6324,7 +6628,7 @@ var CodespaceManager = class {
6324
6628
  /** Start the browser server inside the codespace (idempotent). */
6325
6629
  async startBrowserServer(name) {
6326
6630
  try {
6327
- execSync4(
6631
+ execSync5(
6328
6632
  `gh codespace exec --codespace "${name}" -- bash -c "pgrep -f 'node server.js' > /dev/null 2>&1 || (cd /workspaces && nohup node server.js > /tmp/browser-server.log 2>&1 &)"`,
6329
6633
  { timeout: 3e4 }
6330
6634
  );
@@ -6335,7 +6639,7 @@ var CodespaceManager = class {
6335
6639
  async openTunnel(name) {
6336
6640
  this.closeTunnel();
6337
6641
  console.log(`[Codespace] Opening tunnel port ${BROWSER_PORT_REMOTE} \u2192 localhost:${BROWSER_PORT_LOCAL}...`);
6338
- this.forwardProcess = spawn3(
6642
+ this.forwardProcess = spawn4(
6339
6643
  "gh",
6340
6644
  ["codespace", "ports", "forward", `${BROWSER_PORT_REMOTE}:${BROWSER_PORT_LOCAL}`, "--codespace", name],
6341
6645
  { stdio: ["ignore", "ignore", "ignore"] }
@@ -6379,7 +6683,7 @@ var CodespaceManager = class {
6379
6683
  this.closeTunnel();
6380
6684
  const info = this.findExisting();
6381
6685
  if (info?.state === "Available") {
6382
- execSync4(`gh codespace stop --codespace "${info.name}"`, { timeout: 3e4 });
6686
+ execSync5(`gh codespace stop --codespace "${info.name}"`, { timeout: 3e4 });
6383
6687
  console.log("[Codespace] Stopped (state preserved, restarts in 30s when needed)");
6384
6688
  }
6385
6689
  }
@@ -6388,7 +6692,7 @@ var CodespaceManager = class {
6388
6692
  this.closeTunnel();
6389
6693
  const info = this.findExisting();
6390
6694
  if (info) {
6391
- execSync4(`gh codespace delete --codespace "${info.name}" --force`, { timeout: 3e4 });
6695
+ execSync5(`gh codespace delete --codespace "${info.name}" --force`, { timeout: 3e4 });
6392
6696
  console.log("[Codespace] Deleted");
6393
6697
  }
6394
6698
  }
@@ -6414,7 +6718,7 @@ var CodespaceManager = class {
6414
6718
  /** Check if gh CLI is installed and authenticated. */
6415
6719
  static isAvailable() {
6416
6720
  try {
6417
- execSync4("gh auth status", { stdio: "ignore", timeout: 5e3 });
6721
+ execSync5("gh auth status", { stdio: "ignore", timeout: 5e3 });
6418
6722
  return true;
6419
6723
  } catch {
6420
6724
  return false;
@@ -6423,6 +6727,7 @@ var CodespaceManager = class {
6423
6727
  };
6424
6728
 
6425
6729
  // packages/daemon/src/ZeroAgentDaemon.ts
6730
+ init_RuntimeSelfHeal();
6426
6731
  var ZeroAgentDaemon = class {
6427
6732
  config = null;
6428
6733
  adapter = null;
@@ -6439,15 +6744,16 @@ var ZeroAgentDaemon = class {
6439
6744
  proactiveSurfaceInstance = null;
6440
6745
  codespaceManager = null;
6441
6746
  schedulerManager = null;
6747
+ runtimeHealer = null;
6442
6748
  startedAt = 0;
6443
6749
  pidFilePath;
6444
6750
  constructor() {
6445
- this.pidFilePath = resolve12(homedir8(), ".0agent", "daemon.pid");
6751
+ this.pidFilePath = resolve13(homedir8(), ".0agent", "daemon.pid");
6446
6752
  }
6447
6753
  async start(opts) {
6448
6754
  this.config = await loadConfig(opts?.config_path);
6449
- const dotDir = resolve12(homedir8(), ".0agent");
6450
- if (!existsSync13(dotDir)) {
6755
+ const dotDir = resolve13(homedir8(), ".0agent");
6756
+ if (!existsSync14(dotDir)) {
6451
6757
  mkdirSync6(dotDir, { recursive: true });
6452
6758
  }
6453
6759
  this.adapter = new SQLiteAdapter({ db_path: this.config.graph.db_path });
@@ -6538,6 +6844,25 @@ var ZeroAgentDaemon = class {
6538
6844
  proactiveSurface = new ProactiveSurface2(this.graph, this.eventBus, cwd);
6539
6845
  } catch {
6540
6846
  }
6847
+ if (llmExecutor?.isConfigured) {
6848
+ this.runtimeHealer = new RuntimeSelfHeal(llmExecutor, this.eventBus);
6849
+ process.on("uncaughtException", (err) => {
6850
+ const stack = err.stack ?? err.message;
6851
+ console.error("[0agent] Uncaught exception:", stack);
6852
+ this.runtimeHealer?.analyze(stack, "daemon runtime").then((proposal) => {
6853
+ if (proposal && this.eventBus) {
6854
+ this.runtimeHealer?.emitProposal(proposal);
6855
+ fetch(`http://127.0.0.1:${this.config.server.port}/api/runtime/proposals`, {
6856
+ method: "POST",
6857
+ headers: { "Content-Type": "application/json" },
6858
+ body: JSON.stringify(proposal)
6859
+ }).catch(() => {
6860
+ });
6861
+ }
6862
+ }).catch(() => {
6863
+ });
6864
+ });
6865
+ }
6541
6866
  this.schedulerManager = new SchedulerManager(this.adapter, this.sessionManager, this.eventBus);
6542
6867
  this.schedulerManager.start();
6543
6868
  this.backgroundWorkers = new BackgroundWorkers({
@@ -6565,6 +6890,7 @@ var ZeroAgentDaemon = class {
6565
6890
  proactiveSurface,
6566
6891
  getCodespaceManager: () => this.codespaceManager,
6567
6892
  scheduler: this.schedulerManager,
6893
+ healer: this.runtimeHealer,
6568
6894
  setupCodespace: async () => {
6569
6895
  if (!this.codespaceManager) return { started: false, error: "GitHub memory not configured. Run: 0agent memory connect github" };
6570
6896
  try {
@@ -6576,7 +6902,7 @@ var ZeroAgentDaemon = class {
6576
6902
  }
6577
6903
  });
6578
6904
  await this.httpServer.start();
6579
- writeFileSync7(this.pidFilePath, String(process.pid), "utf8");
6905
+ writeFileSync8(this.pidFilePath, String(process.pid), "utf8");
6580
6906
  console.log(
6581
6907
  `[0agent] Daemon started on ${this.config.server.host}:${this.config.server.port} (PID: ${process.pid})`
6582
6908
  );
@@ -6623,7 +6949,7 @@ var ZeroAgentDaemon = class {
6623
6949
  this.graph = null;
6624
6950
  }
6625
6951
  this.adapter = null;
6626
- if (existsSync13(this.pidFilePath)) {
6952
+ if (existsSync14(this.pidFilePath)) {
6627
6953
  try {
6628
6954
  unlinkSync2(this.pidFilePath);
6629
6955
  } catch {
@@ -6653,11 +6979,11 @@ var ZeroAgentDaemon = class {
6653
6979
  };
6654
6980
 
6655
6981
  // packages/daemon/src/start.ts
6656
- import { resolve as resolve13 } from "node:path";
6982
+ import { resolve as resolve14 } from "node:path";
6657
6983
  import { homedir as homedir9 } from "node:os";
6658
- import { existsSync as existsSync14 } from "node:fs";
6659
- var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve13(homedir9(), ".0agent", "config.yaml");
6660
- if (!existsSync14(CONFIG_PATH)) {
6984
+ import { existsSync as existsSync15 } from "node:fs";
6985
+ var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ?? resolve14(homedir9(), ".0agent", "config.yaml");
6986
+ if (!existsSync15(CONFIG_PATH)) {
6661
6987
  console.error(`
6662
6988
  0agent is not initialised.
6663
6989
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "0agent",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
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",