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.
- package/bin/chat.js +122 -4
- package/dist/daemon.mjs +416 -90
- 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,
|
|
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
|
-
//
|
|
658
|
-
//
|
|
659
|
-
if
|
|
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((
|
|
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((
|
|
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
|
-
|
|
2731
|
+
resolve15(output || (code === 0 ? "(command completed, no output)" : `exit code ${code}`));
|
|
2732
2732
|
});
|
|
2733
2733
|
proc.on("error", (err) => {
|
|
2734
|
-
|
|
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
|
|
3040
|
-
import { existsSync as
|
|
3041
|
-
import { resolve as
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
|
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
|
|
3196
|
-
import { resolve as
|
|
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((
|
|
4214
|
+
(port) => new Promise((resolve15) => {
|
|
3950
4215
|
const s = createServer();
|
|
3951
4216
|
s.listen(port, "127.0.0.1", () => {
|
|
3952
4217
|
s.close();
|
|
3953
|
-
|
|
4218
|
+
resolve15();
|
|
3954
4219
|
});
|
|
3955
4220
|
s.on("error", () => {
|
|
3956
4221
|
open.push(port);
|
|
3957
|
-
|
|
4222
|
+
resolve15();
|
|
3958
4223
|
});
|
|
3959
4224
|
setTimeout(() => {
|
|
3960
4225
|
s.close();
|
|
3961
|
-
|
|
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
|
|
4038
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
4402
|
-
if (!
|
|
4403
|
-
const raw =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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
|
|
5016
|
+
import { Hono as Hono14 } from "hono";
|
|
4752
5017
|
import { serve } from "@hono/node-server";
|
|
4753
|
-
import { readFileSync as
|
|
4754
|
-
import { resolve as
|
|
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
|
|
5046
|
-
import { resolve as
|
|
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 =
|
|
5055
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
5838
|
+
resolve8(dirname4(fileURLToPath2(import.meta.url)), "graph.html"),
|
|
5536
5839
|
// dev (src/)
|
|
5537
|
-
|
|
5840
|
+
resolve8(dirname4(fileURLToPath2(import.meta.url)), "..", "graph.html"),
|
|
5538
5841
|
// bundled (dist/../)
|
|
5539
|
-
|
|
5842
|
+
resolve8(dirname4(fileURLToPath2(import.meta.url)), "..", "dist", "graph.html")
|
|
5540
5843
|
];
|
|
5541
5844
|
for (const p of candidates) {
|
|
5542
5845
|
try {
|
|
5543
|
-
|
|
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
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
5897
|
+
resolve15();
|
|
5594
5898
|
}
|
|
5595
5899
|
);
|
|
5596
5900
|
});
|
|
5597
5901
|
}
|
|
5598
5902
|
stop() {
|
|
5599
|
-
return new Promise((
|
|
5903
|
+
return new Promise((resolve15, reject) => {
|
|
5600
5904
|
if (!this.server) {
|
|
5601
|
-
|
|
5905
|
+
resolve15();
|
|
5602
5906
|
return;
|
|
5603
5907
|
}
|
|
5604
5908
|
this.server.close((err) => {
|
|
5605
5909
|
if (err) reject(err);
|
|
5606
|
-
else
|
|
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
|
|
5618
|
-
import { resolve as
|
|
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 =
|
|
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 (
|
|
5638
|
-
const raw =
|
|
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 =
|
|
5690
|
-
if (!
|
|
5993
|
+
const dir = dirname5(IDENTITY_PATH);
|
|
5994
|
+
if (!existsSync10(dir)) {
|
|
5691
5995
|
mkdirSync4(dir, { recursive: true });
|
|
5692
5996
|
}
|
|
5693
|
-
|
|
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
|
|
5699
|
-
import { resolve as
|
|
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 =
|
|
6006
|
+
var TEAMS_PATH = resolve10(homedir6(), ".0agent", "teams.yaml");
|
|
5703
6007
|
var TeamManager = class {
|
|
5704
6008
|
config;
|
|
5705
6009
|
constructor() {
|
|
5706
|
-
if (
|
|
5707
|
-
this.config = YAML6.parse(
|
|
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(
|
|
5763
|
-
|
|
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
|
|
5847
|
-
import { resolve as
|
|
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 =
|
|
5968
|
-
if (
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
6450
|
-
if (!
|
|
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
|
-
|
|
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 (
|
|
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
|
|
6982
|
+
import { resolve as resolve14 } from "node:path";
|
|
6657
6983
|
import { homedir as homedir9 } from "node:os";
|
|
6658
|
-
import { existsSync as
|
|
6659
|
-
var CONFIG_PATH = process.env["ZEROAGENT_CONFIG"] ??
|
|
6660
|
-
if (!
|
|
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
|
|