@dmrlzz/davecode 0.5.3 → 0.6.0
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/README.md +1 -0
- package/dist/{ChatApp-IF5D7XNH.js → ChatApp-RQZ2IZFQ.js} +6 -6
- package/dist/{account-KBXJ5KR7.js → account-FY6THFLW.js} +3 -3
- package/dist/{accounts-XUJNDCFH.js → accounts-ZV3KMLC5.js} +5 -5
- package/dist/{budget-PHV4NRUS.js → budget-OUOEMMOM.js} +4 -4
- package/dist/{chat-HGTX76DU.js → chat-OHVP4BS3.js} +8 -8
- package/dist/{chunk-AFCQURO5.js → chunk-2MM5WT5J.js} +3 -3
- package/dist/{chunk-EBV5A33R.js → chunk-2P5DJWIJ.js} +4 -4
- package/dist/{chunk-ING5HCIO.js → chunk-2XGDILCW.js} +1677 -133
- package/dist/chunk-2XGDILCW.js.map +1 -0
- package/dist/{chunk-FZJNOG2T.js → chunk-FFB2QA5I.js} +2 -2
- package/dist/{goal-ETZWLVKO.js → chunk-HL4RIXPM.js} +8 -8
- package/dist/chunk-HL4RIXPM.js.map +1 -0
- package/dist/{chunk-FPSLYBLU.js → chunk-L7NEDOA3.js} +2 -2
- package/dist/{chunk-DFQW3MRD.js → chunk-NKRI6SYR.js} +2 -2
- package/dist/{chunk-YSD6W27K.js → chunk-TP3ORSHY.js} +134 -4
- package/dist/chunk-TP3ORSHY.js.map +1 -0
- package/dist/{chunk-ZVNX7CSO.js → chunk-U5CI774J.js} +3 -3
- package/dist/{config-DG2E4VDW.js → config-ZLO3YOUX.js} +3 -3
- package/dist/dashboard/assets/Accounts-CIhOt9FH.js +1 -0
- package/dist/dashboard/assets/{Callout-1E8Wsl9W.js → Callout-CqdcH6dC.js} +1 -1
- package/dist/dashboard/assets/Goals-CITP2GqG.js +1 -0
- package/dist/dashboard/assets/{Library-DZxBVUo_.js → Library-DnCIb76j.js} +1 -1
- package/dist/dashboard/assets/{Panes-C-TJekjs.js → Panes-DBqKYuaE.js} +3 -3
- package/dist/dashboard/assets/RoutesPage-BNVmKhyU.js +4 -0
- package/dist/dashboard/assets/Runner-KR35ZWUE.js +1 -0
- package/dist/dashboard/assets/Settings-B5qE-a5O.js +1 -0
- package/dist/dashboard/assets/Tasks-bJLfNJmS.js +2 -0
- package/dist/dashboard/assets/Traffic-CGfilrRx.js +1 -0
- package/dist/dashboard/assets/Workspace-Dc3cvct_.js +1 -0
- package/dist/dashboard/assets/format-328qIBHX.js +111 -0
- package/dist/dashboard/assets/index-C-bwzlsl.js +50 -0
- package/dist/dashboard/assets/index-D9FCfPmM.css +2 -0
- package/dist/dashboard/assets/task-errors-DK4-900l.js +1 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/{dist-IPMBGFJD.js → dist-56V3DLJ4.js} +16 -2
- package/dist/{doctor-5APOWJ5D.js → doctor-DUOKUSJL.js} +3 -3
- package/dist/goal-WG37XHUY.js +26 -0
- package/dist/index.js +64 -31
- package/dist/index.js.map +1 -1
- package/dist/{init-YDLNA3GD.js → init-WW4XPFMM.js} +3 -3
- package/dist/orchestrate-7YQDBS3E.js +382 -0
- package/dist/orchestrate-7YQDBS3E.js.map +1 -0
- package/dist/{run-I6N7TLHV.js → run-QB4SGPEC.js} +3 -3
- package/dist/runtime.js +6 -2
- package/dist/{start-ECLELJGJ.js → start-U4RL377T.js} +3 -3
- package/dist/{status-QQPP67I4.js → status-GHVMGZI5.js} +4 -4
- package/dist/status-GHVMGZI5.js.map +1 -0
- package/dist/{tasks-RNMC2XFN.js → tasks-VTBJ4B2G.js} +4 -4
- package/package.json +4 -4
- package/dist/chunk-ING5HCIO.js.map +0 -1
- package/dist/chunk-YSD6W27K.js.map +0 -1
- package/dist/dashboard/assets/Accounts-DXiGsutB.js +0 -1
- package/dist/dashboard/assets/Badge-DUGKlx3Z.js +0 -55
- package/dist/dashboard/assets/Goals-kL4VAxw3.js +0 -1
- package/dist/dashboard/assets/RoutesPage-DWYm9tW8.js +0 -4
- package/dist/dashboard/assets/Runner-CmHBjECR.js +0 -1
- package/dist/dashboard/assets/Settings-BANphxva.js +0 -1
- package/dist/dashboard/assets/Tasks-cSx1PKyQ.js +0 -2
- package/dist/dashboard/assets/Traffic-DEAuM6jq.js +0 -1
- package/dist/dashboard/assets/Workspace-BQFjc1jH.js +0 -1
- package/dist/dashboard/assets/index-B8kld9xo.js +0 -104
- package/dist/dashboard/assets/index-BEmBgsbP.css +0 -2
- package/dist/dashboard/assets/task-errors-r-WYZ_S3.js +0 -1
- package/dist/goal-ETZWLVKO.js.map +0 -1
- /package/dist/{ChatApp-IF5D7XNH.js.map → ChatApp-RQZ2IZFQ.js.map} +0 -0
- /package/dist/{account-KBXJ5KR7.js.map → account-FY6THFLW.js.map} +0 -0
- /package/dist/{accounts-XUJNDCFH.js.map → accounts-ZV3KMLC5.js.map} +0 -0
- /package/dist/{budget-PHV4NRUS.js.map → budget-OUOEMMOM.js.map} +0 -0
- /package/dist/{chat-HGTX76DU.js.map → chat-OHVP4BS3.js.map} +0 -0
- /package/dist/{chunk-AFCQURO5.js.map → chunk-2MM5WT5J.js.map} +0 -0
- /package/dist/{chunk-EBV5A33R.js.map → chunk-2P5DJWIJ.js.map} +0 -0
- /package/dist/{chunk-FZJNOG2T.js.map → chunk-FFB2QA5I.js.map} +0 -0
- /package/dist/{chunk-FPSLYBLU.js.map → chunk-L7NEDOA3.js.map} +0 -0
- /package/dist/{chunk-DFQW3MRD.js.map → chunk-NKRI6SYR.js.map} +0 -0
- /package/dist/{chunk-ZVNX7CSO.js.map → chunk-U5CI774J.js.map} +0 -0
- /package/dist/{config-DG2E4VDW.js.map → config-ZLO3YOUX.js.map} +0 -0
- /package/dist/{dist-IPMBGFJD.js.map → dist-56V3DLJ4.js.map} +0 -0
- /package/dist/{doctor-5APOWJ5D.js.map → doctor-DUOKUSJL.js.map} +0 -0
- /package/dist/{status-QQPP67I4.js.map → goal-WG37XHUY.js.map} +0 -0
- /package/dist/{init-YDLNA3GD.js.map → init-WW4XPFMM.js.map} +0 -0
- /package/dist/{run-I6N7TLHV.js.map → run-QB4SGPEC.js.map} +0 -0
- /package/dist/{start-ECLELJGJ.js.map → start-U4RL377T.js.map} +0 -0
- /package/dist/{tasks-RNMC2XFN.js.map → tasks-VTBJ4B2G.js.map} +0 -0
|
@@ -123,6 +123,18 @@ import { z as z10 } from "zod";
|
|
|
123
123
|
import { readdir as readdir3 } from "fs/promises";
|
|
124
124
|
import { join as join7, relative as relative2 } from "path";
|
|
125
125
|
import { z as z11 } from "zod";
|
|
126
|
+
import { z as z12 } from "zod";
|
|
127
|
+
import { existsSync as existsSync4, mkdtempSync, realpathSync, rmSync as rmSync4, statSync as statSync3 } from "fs";
|
|
128
|
+
import { tmpdir } from "os";
|
|
129
|
+
import path8 from "path";
|
|
130
|
+
import { randomBytes as randomBytes9 } from "crypto";
|
|
131
|
+
import { z as z13 } from "zod";
|
|
132
|
+
import { createHash as createHash3 } from "crypto";
|
|
133
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
134
|
+
import { join as join8, resolve as resolve5 } from "path";
|
|
135
|
+
import { z as z14 } from "zod";
|
|
136
|
+
import { existsSync as existsSync5, realpathSync as realpathSync2, rmSync as rmSync5 } from "fs";
|
|
137
|
+
import path9 from "path";
|
|
126
138
|
var credentials = z.object({ clientId: z.string().min(1), clientSecret: z.string().min(1) });
|
|
127
139
|
var optionsSchema = z.object({
|
|
128
140
|
origin: z.string().url().refine((value) => {
|
|
@@ -343,10 +355,10 @@ var H2 = /^##[ \t]+(\S.*?)[ \t]*$/;
|
|
|
343
355
|
function stripCr(line) {
|
|
344
356
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
345
357
|
}
|
|
346
|
-
function listSections(
|
|
358
|
+
function listSections(lines2) {
|
|
347
359
|
const sections = [];
|
|
348
360
|
let fence;
|
|
349
|
-
|
|
361
|
+
lines2.forEach((raw, index) => {
|
|
350
362
|
const line = stripCr(raw);
|
|
351
363
|
const fenceMatch = FENCE.exec(line);
|
|
352
364
|
if (fenceMatch) {
|
|
@@ -363,7 +375,7 @@ function listSections(lines) {
|
|
|
363
375
|
sections.push({
|
|
364
376
|
heading: match[1].replace(/\s+#+$/, ""),
|
|
365
377
|
start: index,
|
|
366
|
-
end:
|
|
378
|
+
end: lines2.length
|
|
367
379
|
});
|
|
368
380
|
}
|
|
369
381
|
});
|
|
@@ -371,17 +383,17 @@ function listSections(lines) {
|
|
|
371
383
|
}
|
|
372
384
|
var sameHeading = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
373
385
|
function getSection(markdown, heading2) {
|
|
374
|
-
const
|
|
375
|
-
const section = listSections(
|
|
386
|
+
const lines2 = markdown.split("\n");
|
|
387
|
+
const section = listSections(lines2).find((s) => sameHeading(s.heading, heading2));
|
|
376
388
|
if (!section) return void 0;
|
|
377
|
-
return
|
|
389
|
+
return lines2.slice(section.start + 1, section.end).map(stripCr).join("\n").trim();
|
|
378
390
|
}
|
|
379
391
|
function upsertSection(markdown, heading2, body) {
|
|
380
392
|
const crlf = markdown.includes("\r\n");
|
|
381
393
|
const cr = crlf ? "\r" : "";
|
|
382
394
|
const block = [`## ${heading2.trim()}`, "", ...body.trim().split(/\r?\n/)].map((l) => l + cr);
|
|
383
|
-
const
|
|
384
|
-
const section = listSections(
|
|
395
|
+
const lines2 = markdown.split("\n");
|
|
396
|
+
const section = listSections(lines2).find((s) => sameHeading(s.heading, heading2));
|
|
385
397
|
if (!section) {
|
|
386
398
|
const base = markdown.replace(/\s+$/, "");
|
|
387
399
|
if (base === "") return `${block.join("\n")}
|
|
@@ -389,9 +401,9 @@ function upsertSection(markdown, heading2, body) {
|
|
|
389
401
|
return `${base}${crlf ? "\r\n\r\n" : "\n\n"}${block.join("\n")}
|
|
390
402
|
`;
|
|
391
403
|
}
|
|
392
|
-
const after =
|
|
404
|
+
const after = lines2.slice(section.end);
|
|
393
405
|
const replacement = after.length > 0 ? [...block, cr] : [...block, ""];
|
|
394
|
-
return [...
|
|
406
|
+
return [...lines2.slice(0, section.start), ...replacement, ...after].join("\n");
|
|
395
407
|
}
|
|
396
408
|
var LAST_UPDATED = /^_Last updated: \d{4}-\d{2}-\d{2}_[ \t]*(\r?)$/m;
|
|
397
409
|
function touchLastUpdated(markdown, date) {
|
|
@@ -472,28 +484,28 @@ var LOG_MARKER = "_[\u2026 older entries truncated to fit the context budget]_";
|
|
|
472
484
|
var heading = (title) => `===== ${title} =====`;
|
|
473
485
|
var EMPTY = "(empty)";
|
|
474
486
|
function describeTask(task, graph) {
|
|
475
|
-
const
|
|
476
|
-
if (task.attempts)
|
|
477
|
-
if (task.branch)
|
|
478
|
-
if (task.description)
|
|
487
|
+
const lines2 = [`id: ${task.id}`, `title: ${task.title}`, `status: ${task.status}`];
|
|
488
|
+
if (task.attempts) lines2.push(`attempts so far: ${task.attempts}`);
|
|
489
|
+
if (task.branch) lines2.push(`branch: ${task.branch}`);
|
|
490
|
+
if (task.description) lines2.push("", "Description:", task.description.trim());
|
|
479
491
|
if (task.acceptance?.length) {
|
|
480
|
-
|
|
492
|
+
lines2.push("", "Acceptance criteria:", ...task.acceptance.map((c) => `- ${c}`));
|
|
481
493
|
}
|
|
482
|
-
if (task.notes)
|
|
494
|
+
if (task.notes) lines2.push("", "Notes from previous attempts:", task.notes.trim());
|
|
483
495
|
if (task.dependsOn.length > 0) {
|
|
484
|
-
|
|
496
|
+
lines2.push("", "Dependencies:");
|
|
485
497
|
const byId = new Map(graph?.tasks.map((t) => [t.id, t]));
|
|
486
498
|
for (const id of task.dependsOn) {
|
|
487
499
|
const dep = byId.get(id);
|
|
488
500
|
if (!dep) {
|
|
489
|
-
|
|
501
|
+
lines2.push(`- ${id}: (not found in the task graph)`);
|
|
490
502
|
continue;
|
|
491
503
|
}
|
|
492
504
|
const note = dep.notes?.trim().split("\n")[0]?.slice(0, 200);
|
|
493
|
-
|
|
505
|
+
lines2.push(`- ${dep.id}: ${dep.title} [${dep.status}]${note ? ` (${note})` : ""}`);
|
|
494
506
|
}
|
|
495
507
|
}
|
|
496
|
-
return
|
|
508
|
+
return lines2.join("\n");
|
|
497
509
|
}
|
|
498
510
|
function cutEnd(text, excess, omittedLabel = OMITTED_MARKER) {
|
|
499
511
|
const keep = text.length - excess - CONTEXT_TRUNCATION_MARKER.length - 1;
|
|
@@ -504,15 +516,15 @@ ${CONTEXT_TRUNCATION_MARKER}`;
|
|
|
504
516
|
function trimActivityLog(state, excess) {
|
|
505
517
|
const body = getSection(state, "Activity log");
|
|
506
518
|
if (body === void 0) return state;
|
|
507
|
-
const
|
|
519
|
+
const lines2 = body.split("\n").filter((l) => l !== LOG_MARKER);
|
|
508
520
|
let removed = 0;
|
|
509
521
|
let drop = 0;
|
|
510
522
|
const needed = excess + LOG_MARKER.length + 1;
|
|
511
|
-
while (drop <
|
|
512
|
-
removed +=
|
|
523
|
+
while (drop < lines2.length && removed < needed) {
|
|
524
|
+
removed += lines2[drop].length + 1;
|
|
513
525
|
drop++;
|
|
514
526
|
}
|
|
515
|
-
const kept =
|
|
527
|
+
const kept = lines2.slice(drop);
|
|
516
528
|
return upsertSection(state, "Activity log", [LOG_MARKER, ...kept].join("\n"));
|
|
517
529
|
}
|
|
518
530
|
async function buildTaskContext(input) {
|
|
@@ -573,16 +585,16 @@ function projectPaths(root) {
|
|
|
573
585
|
taskGraph: join(dir, "TASK_GRAPH.json")
|
|
574
586
|
};
|
|
575
587
|
}
|
|
576
|
-
var sleep = (ms) => new Promise((
|
|
588
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
577
589
|
var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
578
|
-
async function atomicWriteFile(
|
|
579
|
-
await mkdir(dirname(
|
|
580
|
-
const tmp = `${
|
|
590
|
+
async function atomicWriteFile(path10, content) {
|
|
591
|
+
await mkdir(dirname(path10), { recursive: true });
|
|
592
|
+
const tmp = `${path10}.${process.pid}.${randomBytes2(4).toString("hex")}.tmp`;
|
|
581
593
|
try {
|
|
582
594
|
await writeFile(tmp, content, "utf8");
|
|
583
595
|
for (let attempt = 0; ; attempt++) {
|
|
584
596
|
try {
|
|
585
|
-
await rename(tmp,
|
|
597
|
+
await rename(tmp, path10);
|
|
586
598
|
return;
|
|
587
599
|
} catch (err) {
|
|
588
600
|
const code = err.code;
|
|
@@ -595,9 +607,9 @@ async function atomicWriteFile(path8, content) {
|
|
|
595
607
|
throw err;
|
|
596
608
|
}
|
|
597
609
|
}
|
|
598
|
-
async function readFileIfExists(
|
|
610
|
+
async function readFileIfExists(path10) {
|
|
599
611
|
try {
|
|
600
|
-
return await readFile(
|
|
612
|
+
return await readFile(path10, "utf8");
|
|
601
613
|
} catch (err) {
|
|
602
614
|
if (err.code === "ENOENT") return void 0;
|
|
603
615
|
throw err;
|
|
@@ -1028,10 +1040,10 @@ var LockTimeoutError = class extends Error {
|
|
|
1028
1040
|
lockPath;
|
|
1029
1041
|
};
|
|
1030
1042
|
var TRANSIENT_OPEN_ERRORS = ["EPERM", "EACCES", "EBUSY"];
|
|
1031
|
-
var sleep2 = (ms) => new Promise((
|
|
1032
|
-
async function readLockInfo(
|
|
1043
|
+
var sleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
1044
|
+
async function readLockInfo(path10) {
|
|
1033
1045
|
try {
|
|
1034
|
-
const parsed = JSON.parse(await readFile2(
|
|
1046
|
+
const parsed = JSON.parse(await readFile2(path10, "utf8"));
|
|
1035
1047
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.ts === "number" && typeof parsed.pid === "number") {
|
|
1036
1048
|
return parsed;
|
|
1037
1049
|
}
|
|
@@ -1039,11 +1051,11 @@ async function readLockInfo(path8) {
|
|
|
1039
1051
|
}
|
|
1040
1052
|
return void 0;
|
|
1041
1053
|
}
|
|
1042
|
-
async function lockAge(
|
|
1043
|
-
const info = await readLockInfo(
|
|
1054
|
+
async function lockAge(path10) {
|
|
1055
|
+
const info = await readLockInfo(path10);
|
|
1044
1056
|
if (info) return Date.now() - info.ts;
|
|
1045
1057
|
try {
|
|
1046
|
-
return Date.now() - (await stat2(
|
|
1058
|
+
return Date.now() - (await stat2(path10)).mtimeMs;
|
|
1047
1059
|
} catch {
|
|
1048
1060
|
return void 0;
|
|
1049
1061
|
}
|
|
@@ -1090,9 +1102,9 @@ async function withFileLock(lockPath, fn2, opts = {}) {
|
|
|
1090
1102
|
}
|
|
1091
1103
|
}
|
|
1092
1104
|
}
|
|
1093
|
-
async function exists(
|
|
1105
|
+
async function exists(path10) {
|
|
1094
1106
|
try {
|
|
1095
|
-
await access(
|
|
1107
|
+
await access(path10);
|
|
1096
1108
|
return true;
|
|
1097
1109
|
} catch {
|
|
1098
1110
|
return false;
|
|
@@ -1151,8 +1163,8 @@ var MissingBrainError = class extends Error {
|
|
|
1151
1163
|
/** HTTP status hint and machine-readable code for the gateway's error handler. */
|
|
1152
1164
|
statusCode = 409;
|
|
1153
1165
|
code = "no_brain";
|
|
1154
|
-
constructor(
|
|
1155
|
-
super(`Missing project brain file: ${
|
|
1166
|
+
constructor(path10) {
|
|
1167
|
+
super(`Missing project brain file: ${path10} (run ProjectBrain.init first)`);
|
|
1156
1168
|
this.name = "MissingBrainError";
|
|
1157
1169
|
}
|
|
1158
1170
|
};
|
|
@@ -1190,8 +1202,8 @@ var ProjectBrain = class _ProjectBrain {
|
|
|
1190
1202
|
[brain.paths.architecture, architectureTemplate(name)],
|
|
1191
1203
|
[brain.paths.taskGraph, serializeGraph(taskGraphTemplate())]
|
|
1192
1204
|
];
|
|
1193
|
-
for (const [
|
|
1194
|
-
if (!await exists(
|
|
1205
|
+
for (const [path10, content] of files) {
|
|
1206
|
+
if (!await exists(path10)) await atomicWriteFile(path10, content);
|
|
1195
1207
|
}
|
|
1196
1208
|
return brain;
|
|
1197
1209
|
}
|
|
@@ -1303,10 +1315,10 @@ ${line}` : line);
|
|
|
1303
1315
|
this.events?.emit({ type: "task.updated", task });
|
|
1304
1316
|
return task;
|
|
1305
1317
|
}
|
|
1306
|
-
async readText(
|
|
1307
|
-
const content = await readFileIfExists(
|
|
1318
|
+
async readText(path10) {
|
|
1319
|
+
const content = await readFileIfExists(path10);
|
|
1308
1320
|
if (content === void 0) {
|
|
1309
|
-
throw new MissingBrainError(
|
|
1321
|
+
throw new MissingBrainError(path10);
|
|
1310
1322
|
}
|
|
1311
1323
|
return content;
|
|
1312
1324
|
}
|
|
@@ -1520,14 +1532,14 @@ function filterProjectLayer(layer) {
|
|
|
1520
1532
|
const out = {};
|
|
1521
1533
|
for (const [key, v] of Object.entries(value)) {
|
|
1522
1534
|
const rule = Object.hasOwn(tree, key) ? tree[key] : void 0;
|
|
1523
|
-
const
|
|
1535
|
+
const path10 = prefix ? `${prefix}.${key}` : key;
|
|
1524
1536
|
if (rule === true) {
|
|
1525
1537
|
out[key] = v;
|
|
1526
1538
|
} else if (rule && isPlainObject(v)) {
|
|
1527
|
-
const inner = walk(v, rule,
|
|
1539
|
+
const inner = walk(v, rule, path10);
|
|
1528
1540
|
if (Object.keys(inner).length > 0) out[key] = inner;
|
|
1529
1541
|
} else {
|
|
1530
|
-
dropped.push(
|
|
1542
|
+
dropped.push(path10);
|
|
1531
1543
|
}
|
|
1532
1544
|
}
|
|
1533
1545
|
return out;
|
|
@@ -1552,21 +1564,21 @@ function deepMerge(base, override) {
|
|
|
1552
1564
|
}
|
|
1553
1565
|
return out;
|
|
1554
1566
|
}
|
|
1555
|
-
function readLayer(
|
|
1567
|
+
function readLayer(path10, project) {
|
|
1556
1568
|
let raw;
|
|
1557
1569
|
try {
|
|
1558
|
-
raw = readFileSync(
|
|
1570
|
+
raw = readFileSync(path10, "utf8");
|
|
1559
1571
|
} catch (err) {
|
|
1560
1572
|
if (err.code === "ENOENT") return void 0;
|
|
1561
|
-
throw new ConfigError(
|
|
1573
|
+
throw new ConfigError(path10, `cannot read file: ${err.message}`, { cause: err });
|
|
1562
1574
|
}
|
|
1563
1575
|
let parsed;
|
|
1564
1576
|
try {
|
|
1565
1577
|
parsed = JSON.parse(raw);
|
|
1566
1578
|
} catch (err) {
|
|
1567
|
-
throw new ConfigError(
|
|
1579
|
+
throw new ConfigError(path10, `malformed JSON: ${err.message}`, { cause: err });
|
|
1568
1580
|
}
|
|
1569
|
-
if (!isPlainObject(parsed)) throw new ConfigError(
|
|
1581
|
+
if (!isPlainObject(parsed)) throw new ConfigError(path10, "expected a JSON object");
|
|
1570
1582
|
let layer = parsed;
|
|
1571
1583
|
if (project) {
|
|
1572
1584
|
const { allowed, dropped } = filterProjectLayer(parsed);
|
|
@@ -1574,7 +1586,7 @@ function readLayer(path8, project) {
|
|
|
1574
1586
|
layer = allowed;
|
|
1575
1587
|
}
|
|
1576
1588
|
const result = configSchema.safeParse(layer);
|
|
1577
|
-
if (!result.success) throw new ConfigError(
|
|
1589
|
+
if (!result.success) throw new ConfigError(path10, z4.prettifyError(result.error));
|
|
1578
1590
|
return layer;
|
|
1579
1591
|
}
|
|
1580
1592
|
var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
|
|
@@ -1653,19 +1665,19 @@ function loadConfig(options = {}) {
|
|
|
1653
1665
|
}
|
|
1654
1666
|
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1655
1667
|
async function writeGlobalRouting(home, update, lock = {}) {
|
|
1656
|
-
const
|
|
1668
|
+
const path10 = globalPaths(home).config;
|
|
1657
1669
|
return withFileLock(
|
|
1658
|
-
`${
|
|
1670
|
+
`${path10}.lock`,
|
|
1659
1671
|
async () => {
|
|
1660
1672
|
let current = {};
|
|
1661
1673
|
try {
|
|
1662
|
-
const parsed = JSON.parse(await readFile3(
|
|
1663
|
-
if (!isPlainObject2(parsed)) throw new ConfigError(
|
|
1674
|
+
const parsed = JSON.parse(await readFile3(path10, "utf8"));
|
|
1675
|
+
if (!isPlainObject2(parsed)) throw new ConfigError(path10, "expected a JSON object");
|
|
1664
1676
|
current = parsed;
|
|
1665
1677
|
} catch (err) {
|
|
1666
1678
|
if (err instanceof ConfigError) throw err;
|
|
1667
1679
|
if (!isNodeError(err, "ENOENT")) {
|
|
1668
|
-
throw new ConfigError(
|
|
1680
|
+
throw new ConfigError(path10, `cannot update this file: ${err.message}`, {
|
|
1669
1681
|
cause: err
|
|
1670
1682
|
});
|
|
1671
1683
|
}
|
|
@@ -1680,11 +1692,11 @@ async function writeGlobalRouting(home, update, lock = {}) {
|
|
|
1680
1692
|
}
|
|
1681
1693
|
};
|
|
1682
1694
|
const checked = configSchema.safeParse(next);
|
|
1683
|
-
if (!checked.success) throw new ConfigError(
|
|
1684
|
-
await atomicWriteFile(
|
|
1695
|
+
if (!checked.success) throw new ConfigError(path10, z5.prettifyError(checked.error));
|
|
1696
|
+
await atomicWriteFile(path10, `${JSON.stringify(next, null, 2)}
|
|
1685
1697
|
`);
|
|
1686
1698
|
return {
|
|
1687
|
-
path:
|
|
1699
|
+
path: path10,
|
|
1688
1700
|
routes: checked.data.routing.routes,
|
|
1689
1701
|
defaultRoute: checked.data.routing.defaultRoute
|
|
1690
1702
|
};
|
|
@@ -1772,8 +1784,8 @@ function makeUsage(prompt, completion) {
|
|
|
1772
1784
|
function stripProviderPrefix(model, provider) {
|
|
1773
1785
|
return model.startsWith(`${provider}/`) ? model.slice(provider.length + 1) : model;
|
|
1774
1786
|
}
|
|
1775
|
-
function joinUrl(base,
|
|
1776
|
-
return `${base.replace(/\/+$/, "")}/${
|
|
1787
|
+
function joinUrl(base, path10) {
|
|
1788
|
+
return `${base.replace(/\/+$/, "")}/${path10.replace(/^\/+/, "")}`;
|
|
1777
1789
|
}
|
|
1778
1790
|
function configString(config, key) {
|
|
1779
1791
|
const value = config[key];
|
|
@@ -2590,8 +2602,8 @@ var SHELL_SAFE = /^[\w.:=@+\-/\\[\]]+$/;
|
|
|
2590
2602
|
function isShellSafeArg(arg) {
|
|
2591
2603
|
return SHELL_SAFE.test(arg);
|
|
2592
2604
|
}
|
|
2593
|
-
function quoteCommand(
|
|
2594
|
-
return isShellSafeArg(
|
|
2605
|
+
function quoteCommand(path10) {
|
|
2606
|
+
return isShellSafeArg(path10) ? path10 : `"${path10}"`;
|
|
2595
2607
|
}
|
|
2596
2608
|
function killTree(child) {
|
|
2597
2609
|
if (child.pid === void 0 || child.exitCode !== null) return;
|
|
@@ -2656,12 +2668,12 @@ function runCli(opts) {
|
|
|
2656
2668
|
killTree(child);
|
|
2657
2669
|
};
|
|
2658
2670
|
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2659
|
-
const closed = new Promise((
|
|
2671
|
+
const closed = new Promise((resolve6) => {
|
|
2660
2672
|
child.once("error", (err) => {
|
|
2661
2673
|
spawnError = err;
|
|
2662
|
-
|
|
2674
|
+
resolve6(null);
|
|
2663
2675
|
});
|
|
2664
|
-
child.once("close", (code) =>
|
|
2676
|
+
child.once("close", (code) => resolve6(code));
|
|
2665
2677
|
});
|
|
2666
2678
|
const exit = closed.then((code) => {
|
|
2667
2679
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
@@ -2978,7 +2990,7 @@ var ClaudeCliProvider = class {
|
|
|
2978
2990
|
accountId: ctx.account.id
|
|
2979
2991
|
});
|
|
2980
2992
|
};
|
|
2981
|
-
const
|
|
2993
|
+
const abortError2 = () => new ProviderError("claude-cli request aborted", {
|
|
2982
2994
|
kind: "timeout",
|
|
2983
2995
|
provider: this.kind,
|
|
2984
2996
|
accountId: ctx.account.id
|
|
@@ -3052,7 +3064,7 @@ var ClaudeCliProvider = class {
|
|
|
3052
3064
|
}
|
|
3053
3065
|
}
|
|
3054
3066
|
const exit = await run.exit;
|
|
3055
|
-
if (exit.aborted || ctx.signal?.aborted) throw
|
|
3067
|
+
if (exit.aborted || ctx.signal?.aborted) throw abortError2();
|
|
3056
3068
|
if (!finished) {
|
|
3057
3069
|
const detail = exit.stderr.trim() || `process exited with code ${exit.code ?? "unknown"}`;
|
|
3058
3070
|
throw fail(detail);
|
|
@@ -4035,7 +4047,7 @@ var SUBSCRIPTION_PROVIDER_KINDS = [
|
|
|
4035
4047
|
"antigravity-cli",
|
|
4036
4048
|
"gemini-web"
|
|
4037
4049
|
];
|
|
4038
|
-
var VERSION = "0.
|
|
4050
|
+
var VERSION = "0.6.0";
|
|
4039
4051
|
var QUOTA_WINDOWS = ["1m", "5h", "24h"];
|
|
4040
4052
|
var WINDOW_MS = {
|
|
4041
4053
|
"1m": 6e4,
|
|
@@ -4299,7 +4311,7 @@ var BudgetService = class {
|
|
|
4299
4311
|
const enabled = accounts.filter((a) => a.enabled);
|
|
4300
4312
|
if (enabled.length === 0) return "";
|
|
4301
4313
|
const available = enabled.filter((a) => a.available).length;
|
|
4302
|
-
const
|
|
4314
|
+
const lines2 = [
|
|
4303
4315
|
`ACCOUNT BUDGET (${isoTime(new Date(this.clock()).toISOString())}; ${available} of ${enabled.length} accounts available).`,
|
|
4304
4316
|
"The router already avoids exhausted accounts. When budget is scarce, keep prompts and outputs short and prefer fewer, larger steps."
|
|
4305
4317
|
];
|
|
@@ -4322,12 +4334,12 @@ var BudgetService = class {
|
|
|
4322
4334
|
const pct = Math.round(a.remaining * 100);
|
|
4323
4335
|
state = `${pct}% left${a.tightestWindow ? ` (${a.tightestWindow} window)` : " (provider-reported)"}`;
|
|
4324
4336
|
}
|
|
4325
|
-
|
|
4337
|
+
lines2.push(`${head}: ${state}`);
|
|
4326
4338
|
}
|
|
4327
4339
|
if (enabled.length > CONTEXT_ACCOUNTS) {
|
|
4328
|
-
|
|
4340
|
+
lines2.push(`- \u2026and ${enabled.length - CONTEXT_ACCOUNTS} more accounts`);
|
|
4329
4341
|
}
|
|
4330
|
-
return
|
|
4342
|
+
return lines2.join("\n");
|
|
4331
4343
|
}
|
|
4332
4344
|
};
|
|
4333
4345
|
var ExperimentalDisabledError = class extends Error {
|
|
@@ -4461,11 +4473,11 @@ function cliBinaryFor(account) {
|
|
|
4461
4473
|
return CLI_LOGINS[account.provider]?.binary;
|
|
4462
4474
|
}
|
|
4463
4475
|
var MAX_OUTPUT = 64 * 1024;
|
|
4464
|
-
var spawnProbe = (cmd) => new Promise((
|
|
4476
|
+
var spawnProbe = (cmd) => new Promise((resolve6) => {
|
|
4465
4477
|
const resolved = resolveOnPath(cmd.command, cmd.env);
|
|
4466
4478
|
const shell = needsShell(resolved);
|
|
4467
4479
|
if (shell && (cmd.args.some((a) => !isShellSafeArg(a)) || /["%!]/.test(resolved))) {
|
|
4468
|
-
|
|
4480
|
+
resolve6({ code: null, stdout: "" });
|
|
4469
4481
|
return;
|
|
4470
4482
|
}
|
|
4471
4483
|
const options = {
|
|
@@ -4487,7 +4499,7 @@ var spawnProbe = (cmd) => new Promise((resolve5) => {
|
|
|
4487
4499
|
}, cmd.timeoutMs);
|
|
4488
4500
|
child.once("error", (err) => {
|
|
4489
4501
|
clearTimeout(timer);
|
|
4490
|
-
|
|
4502
|
+
resolve6({
|
|
4491
4503
|
code: null,
|
|
4492
4504
|
stdout: "",
|
|
4493
4505
|
missing: err.code === "ENOENT"
|
|
@@ -4495,7 +4507,7 @@ var spawnProbe = (cmd) => new Promise((resolve5) => {
|
|
|
4495
4507
|
});
|
|
4496
4508
|
child.once("close", (code) => {
|
|
4497
4509
|
clearTimeout(timer);
|
|
4498
|
-
|
|
4510
|
+
resolve6(timedOut ? { code: null, stdout: "", timedOut } : { code, stdout });
|
|
4499
4511
|
});
|
|
4500
4512
|
});
|
|
4501
4513
|
var ConnectionChecker = class {
|
|
@@ -5683,9 +5695,9 @@ function migrate(db, migrations = MIGRATIONS) {
|
|
|
5683
5695
|
}
|
|
5684
5696
|
return version;
|
|
5685
5697
|
}
|
|
5686
|
-
function openDatabase(
|
|
5687
|
-
if (
|
|
5688
|
-
const db = new BetterSqlite3(
|
|
5698
|
+
function openDatabase(path10, options = {}) {
|
|
5699
|
+
if (path10 !== ":memory:") mkdirSync5(dirname5(path10), { recursive: true });
|
|
5700
|
+
const db = new BetterSqlite3(path10);
|
|
5689
5701
|
try {
|
|
5690
5702
|
db.pragma("journal_mode = WAL");
|
|
5691
5703
|
db.pragma("foreign_keys = ON");
|
|
@@ -6374,9 +6386,9 @@ var WorkspaceTools = class {
|
|
|
6374
6386
|
if (buffer.subarray(0, 8e3).includes(0)) throw new ToolInputError(`${rel} is a binary file`);
|
|
6375
6387
|
let text = buffer.toString("utf8");
|
|
6376
6388
|
if (args.offset !== void 0 || args.limit !== void 0) {
|
|
6377
|
-
const
|
|
6389
|
+
const lines2 = text.split("\n");
|
|
6378
6390
|
const start = (args.offset ?? 1) - 1;
|
|
6379
|
-
text =
|
|
6391
|
+
text = lines2.slice(start, args.limit ? start + args.limit : void 0).join("\n");
|
|
6380
6392
|
}
|
|
6381
6393
|
if (text.length > this.maxOutput) {
|
|
6382
6394
|
return `${text.slice(0, this.maxOutput)}
|
|
@@ -6448,9 +6460,9 @@ var WorkspaceTools = class {
|
|
|
6448
6460
|
if (info2.size > MAX_FILE_BYTES) return;
|
|
6449
6461
|
const buffer = await readFile4(file);
|
|
6450
6462
|
if (buffer.subarray(0, 8e3).includes(0)) return;
|
|
6451
|
-
const
|
|
6452
|
-
for (let i = 0; i <
|
|
6453
|
-
const line =
|
|
6463
|
+
const lines2 = buffer.toString("utf8").split("\n");
|
|
6464
|
+
for (let i = 0; i < lines2.length && results.length < args.max_results; i++) {
|
|
6465
|
+
const line = lines2[i].replace(/\r$/, "");
|
|
6454
6466
|
if (matcher(line)) results.push(`${rel}:${i + 1}: ${line.trim().slice(0, 300)}`);
|
|
6455
6467
|
}
|
|
6456
6468
|
};
|
|
@@ -6985,7 +6997,7 @@ var Git = class {
|
|
|
6985
6997
|
env;
|
|
6986
6998
|
/** Runs `git <args>` and returns stdout. Throws {@link GitError} on a non-zero exit. */
|
|
6987
6999
|
run(args) {
|
|
6988
|
-
return new Promise((
|
|
7000
|
+
return new Promise((resolve6, reject) => {
|
|
6989
7001
|
execFile(
|
|
6990
7002
|
this.binary,
|
|
6991
7003
|
args,
|
|
@@ -7004,7 +7016,7 @@ var Git = class {
|
|
|
7004
7016
|
reject(new GitError(`git ${args[0] ?? ""} failed: ${detail}`, args, code, stderr));
|
|
7005
7017
|
return;
|
|
7006
7018
|
}
|
|
7007
|
-
|
|
7019
|
+
resolve6(stdout);
|
|
7008
7020
|
}
|
|
7009
7021
|
);
|
|
7010
7022
|
});
|
|
@@ -7163,12 +7175,12 @@ var Git = class {
|
|
|
7163
7175
|
if (/\.(cmd|bat)$/i.test(gh)) {
|
|
7164
7176
|
return { outcome: "unavailable", reason: "gh is a shell shim; refusing to pass text args" };
|
|
7165
7177
|
}
|
|
7166
|
-
const runGh = (args) => new Promise((
|
|
7178
|
+
const runGh = (args) => new Promise((resolve6, reject) => {
|
|
7167
7179
|
execFile(
|
|
7168
7180
|
gh,
|
|
7169
7181
|
args,
|
|
7170
7182
|
{ cwd: this.cwd, env: this.env, timeout: this.timeoutMs, windowsHide: true },
|
|
7171
|
-
(err, stdout, stderr) => err ? reject(new Error((stderr || err.message).trim())) :
|
|
7183
|
+
(err, stdout, stderr) => err ? reject(new Error((stderr || err.message).trim())) : resolve6(stdout.trim())
|
|
7172
7184
|
);
|
|
7173
7185
|
});
|
|
7174
7186
|
try {
|
|
@@ -7430,17 +7442,17 @@ function statusLine(step) {
|
|
|
7430
7442
|
function summarizeValidation(steps) {
|
|
7431
7443
|
if (steps.length === 0) return "Validation PASSED: no validation commands are configured.";
|
|
7432
7444
|
const failed = steps.filter((s) => !s.ok);
|
|
7433
|
-
const
|
|
7445
|
+
const lines2 = [
|
|
7434
7446
|
failed.length === 0 ? "Validation PASSED." : `Validation FAILED (${failed.map((s) => s.name).join(", ")}).`,
|
|
7435
7447
|
...steps.map(statusLine)
|
|
7436
7448
|
];
|
|
7437
7449
|
for (const step of failed) {
|
|
7438
|
-
|
|
7439
|
-
if (step.stdout.trim())
|
|
7440
|
-
if (step.stderr.trim())
|
|
7441
|
-
if (!step.stdout.trim() && !step.stderr.trim())
|
|
7450
|
+
lines2.push("", `### ${step.name} output (\`${step.command}\`)`);
|
|
7451
|
+
if (step.stdout.trim()) lines2.push("stdout:", "```", step.stdout.trimEnd(), "```");
|
|
7452
|
+
if (step.stderr.trim()) lines2.push("stderr:", "```", step.stderr.trimEnd(), "```");
|
|
7453
|
+
if (!step.stdout.trim() && !step.stderr.trim()) lines2.push("(no output)");
|
|
7442
7454
|
}
|
|
7443
|
-
return
|
|
7455
|
+
return lines2.join("\n");
|
|
7444
7456
|
}
|
|
7445
7457
|
var Validator = class {
|
|
7446
7458
|
constructor(options) {
|
|
@@ -7620,8 +7632,8 @@ var AutonomousRunner = class {
|
|
|
7620
7632
|
this.stopRequested = false;
|
|
7621
7633
|
let started = () => void 0;
|
|
7622
7634
|
let refused = () => void 0;
|
|
7623
|
-
const ready = new Promise((
|
|
7624
|
-
started =
|
|
7635
|
+
const ready = new Promise((resolve6, reject) => {
|
|
7636
|
+
started = resolve6;
|
|
7625
7637
|
refused = reject;
|
|
7626
7638
|
});
|
|
7627
7639
|
this.active = (async () => {
|
|
@@ -7782,13 +7794,13 @@ var AutonomousRunner = class {
|
|
|
7782
7794
|
if (this.stopRequested) throw new StopSignal();
|
|
7783
7795
|
const controller = new AbortController();
|
|
7784
7796
|
this.idleSleep = controller;
|
|
7785
|
-
await new Promise((
|
|
7786
|
-
const timer = setTimeout(
|
|
7797
|
+
await new Promise((resolve6) => {
|
|
7798
|
+
const timer = setTimeout(resolve6, ms);
|
|
7787
7799
|
controller.signal.addEventListener(
|
|
7788
7800
|
"abort",
|
|
7789
7801
|
() => {
|
|
7790
7802
|
clearTimeout(timer);
|
|
7791
|
-
|
|
7803
|
+
resolve6();
|
|
7792
7804
|
},
|
|
7793
7805
|
{ once: true }
|
|
7794
7806
|
);
|
|
@@ -8079,7 +8091,7 @@ ${report}`, 4e3)
|
|
|
8079
8091
|
async checkpoint() {
|
|
8080
8092
|
if (this.stopRequested) throw new StopSignal();
|
|
8081
8093
|
while (this.pauseRequested) {
|
|
8082
|
-
await new Promise((
|
|
8094
|
+
await new Promise((resolve6) => this.pauseWaiters.push(resolve6));
|
|
8083
8095
|
if (this.stopRequested) throw new StopSignal();
|
|
8084
8096
|
}
|
|
8085
8097
|
}
|
|
@@ -8290,8 +8302,8 @@ var AccountClient = class {
|
|
|
8290
8302
|
return url.toString();
|
|
8291
8303
|
}
|
|
8292
8304
|
/** A page of the hosted service, such as `/login/done`. */
|
|
8293
|
-
pageUrl(
|
|
8294
|
-
return `${this.service.url}${
|
|
8305
|
+
pageUrl(path10) {
|
|
8306
|
+
return `${this.service.url}${path10}`;
|
|
8295
8307
|
}
|
|
8296
8308
|
/** Browser URL that starts Google or GitHub sign-in and returns to `redirectTo` with `?code=`. */
|
|
8297
8309
|
authorizeUrl(provider, redirectTo, challenge) {
|
|
@@ -8344,13 +8356,13 @@ var AccountClient = class {
|
|
|
8344
8356
|
signal: AbortSignal.timeout(1e4)
|
|
8345
8357
|
}).catch(() => void 0);
|
|
8346
8358
|
}
|
|
8347
|
-
async session(
|
|
8348
|
-
return toSession(await this.post(
|
|
8359
|
+
async session(path10, body) {
|
|
8360
|
+
return toSession(await this.post(path10, body), this.now());
|
|
8349
8361
|
}
|
|
8350
|
-
async post(
|
|
8362
|
+
async post(path10, body) {
|
|
8351
8363
|
let response;
|
|
8352
8364
|
try {
|
|
8353
|
-
response = await this.fetch(`${this.service.url}/auth/v1/${
|
|
8365
|
+
response = await this.fetch(`${this.service.url}/auth/v1/${path10}`, {
|
|
8354
8366
|
method: "POST",
|
|
8355
8367
|
headers: { apikey: this.service.publishableKey, "Content-Type": "application/json" },
|
|
8356
8368
|
body: JSON.stringify(body),
|
|
@@ -8385,8 +8397,8 @@ async function startAccountLogin(options) {
|
|
|
8385
8397
|
let fail = () => {
|
|
8386
8398
|
};
|
|
8387
8399
|
let settled = false;
|
|
8388
|
-
const session = new Promise((
|
|
8389
|
-
finish =
|
|
8400
|
+
const session = new Promise((resolve6, reject) => {
|
|
8401
|
+
finish = resolve6;
|
|
8390
8402
|
fail = reject;
|
|
8391
8403
|
});
|
|
8392
8404
|
session.catch(() => {
|
|
@@ -8422,14 +8434,14 @@ async function startAccountLogin(options) {
|
|
|
8422
8434
|
if (!res.headersSent) res.writeHead(500).end();
|
|
8423
8435
|
});
|
|
8424
8436
|
});
|
|
8425
|
-
await new Promise((
|
|
8437
|
+
await new Promise((resolve6, reject) => {
|
|
8426
8438
|
server.once("error", reject);
|
|
8427
|
-
server.listen(0, "127.0.0.1", () =>
|
|
8439
|
+
server.listen(0, "127.0.0.1", () => resolve6());
|
|
8428
8440
|
});
|
|
8429
8441
|
const port = server.address().port;
|
|
8430
|
-
const close = () => new Promise((
|
|
8442
|
+
const close = () => new Promise((resolve6) => {
|
|
8431
8443
|
server.closeAllConnections();
|
|
8432
|
-
server.close(() =>
|
|
8444
|
+
server.close(() => resolve6());
|
|
8433
8445
|
});
|
|
8434
8446
|
const timeout = setTimeout(
|
|
8435
8447
|
() => {
|
|
@@ -8570,16 +8582,16 @@ var AccountStore = class {
|
|
|
8570
8582
|
* refresh goes ahead without it (the service tolerates two refreshes racing).
|
|
8571
8583
|
*/
|
|
8572
8584
|
async lock() {
|
|
8573
|
-
const
|
|
8585
|
+
const path10 = `${this.path}.lock`;
|
|
8574
8586
|
const owner = `${process.pid}:${randomBytes8(8).toString("hex")}`;
|
|
8575
|
-
mkdirSync7(dirname6(
|
|
8587
|
+
mkdirSync7(dirname6(path10), { recursive: true });
|
|
8576
8588
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
8577
8589
|
for (; ; ) {
|
|
8578
8590
|
try {
|
|
8579
|
-
writeFileSync3(
|
|
8591
|
+
writeFileSync3(path10, owner, { flag: "wx", mode: 384 });
|
|
8580
8592
|
return () => {
|
|
8581
8593
|
try {
|
|
8582
|
-
if (readFileSync3(
|
|
8594
|
+
if (readFileSync3(path10, "utf8") === owner) rmSync3(path10, { force: true });
|
|
8583
8595
|
} catch {
|
|
8584
8596
|
}
|
|
8585
8597
|
};
|
|
@@ -8588,11 +8600,11 @@ var AccountStore = class {
|
|
|
8588
8600
|
return () => void 0;
|
|
8589
8601
|
}
|
|
8590
8602
|
try {
|
|
8591
|
-
if (Date.now() - statSync2(
|
|
8603
|
+
if (Date.now() - statSync2(path10).mtimeMs > LOCK_STALE_MS) rmSync3(path10, { force: true });
|
|
8592
8604
|
} catch {
|
|
8593
8605
|
}
|
|
8594
8606
|
if (Date.now() >= deadline) return () => void 0;
|
|
8595
|
-
await new Promise((
|
|
8607
|
+
await new Promise((resolve6) => setTimeout(resolve6, LOCK_POLL_MS));
|
|
8596
8608
|
}
|
|
8597
8609
|
}
|
|
8598
8610
|
};
|
|
@@ -9144,16 +9156,16 @@ ${goal.objective}`,
|
|
|
9144
9156
|
);
|
|
9145
9157
|
}
|
|
9146
9158
|
async review(goal, roles, signal) {
|
|
9147
|
-
const
|
|
9159
|
+
const lines2 = [];
|
|
9148
9160
|
for (const task of goal.tasks) {
|
|
9149
|
-
|
|
9161
|
+
lines2.push(
|
|
9150
9162
|
`- [${task.status}] ${task.id} (${task.role}, ${task.kind}, round ${task.round}, ${task.attempts} attempt(s)): ${task.title}`
|
|
9151
9163
|
);
|
|
9152
|
-
if (task.notes)
|
|
9164
|
+
if (task.notes) lines2.push(` notes: ${truncateHead(task.notes, 500).replace(/\n/g, " ")}`);
|
|
9153
9165
|
if (task.artifact) {
|
|
9154
9166
|
const text = await this.options.store.readArtifact(goal.id, task.id);
|
|
9155
9167
|
if (text)
|
|
9156
|
-
|
|
9168
|
+
lines2.push(` output excerpt:
|
|
9157
9169
|
${indent(truncateHead(text, MAX_REVIEW_EXCERPT))}`);
|
|
9158
9170
|
}
|
|
9159
9171
|
}
|
|
@@ -9162,7 +9174,7 @@ ${indent(truncateHead(text, MAX_REVIEW_EXCERPT))}`);
|
|
|
9162
9174
|
${goal.objective}`,
|
|
9163
9175
|
`Round ${goal.round} of ${goal.limits.maxRounds}. Room for ${Math.max(0, goal.limits.maxTasks - goal.tasks.length)} more task(s).`,
|
|
9164
9176
|
`TASKS:
|
|
9165
|
-
${
|
|
9177
|
+
${lines2.join("\n")}`,
|
|
9166
9178
|
await this.sharedContext()
|
|
9167
9179
|
].filter(Boolean).join("\n\n");
|
|
9168
9180
|
return this.askJson(
|
|
@@ -9649,10 +9661,10 @@ var GoalStore = class {
|
|
|
9649
9661
|
}
|
|
9650
9662
|
/** Writes a task's markdown artifact and returns its repository-relative path (`/`-separated). */
|
|
9651
9663
|
async writeArtifact(goalId, taskId, markdown) {
|
|
9652
|
-
const
|
|
9653
|
-
await atomicWriteFile(
|
|
9664
|
+
const path10 = join7(this.goalDir(goalId), `${taskId}.md`);
|
|
9665
|
+
await atomicWriteFile(path10, markdown.endsWith("\n") ? markdown : `${markdown}
|
|
9654
9666
|
`);
|
|
9655
|
-
return relative2(this.root,
|
|
9667
|
+
return relative2(this.root, path10).split("\\").join("/");
|
|
9656
9668
|
}
|
|
9657
9669
|
async readArtifact(goalId, taskId) {
|
|
9658
9670
|
if (!isGoalId(goalId) || !/^[-a-zA-Z0-9_]{1,64}$/.test(taskId)) return void 0;
|
|
@@ -9692,6 +9704,1531 @@ ${truncateHead(state.trim(), PROJECT_CONTEXT_CHARS)}`
|
|
|
9692
9704
|
});
|
|
9693
9705
|
return new GoalService({ orchestrator, store, lock });
|
|
9694
9706
|
}
|
|
9707
|
+
var ORCHESTRATOR_AGENTS = ["claude", "codex", "agy"];
|
|
9708
|
+
var ORCHESTRATOR_LIMITS = {
|
|
9709
|
+
objectiveMaxLength: 4e3,
|
|
9710
|
+
/** Most tasks a plan may contain. */
|
|
9711
|
+
maxTasks: 8,
|
|
9712
|
+
/** Default when the request does not say. */
|
|
9713
|
+
defaultMaxTasks: 5,
|
|
9714
|
+
/** Most tasks running at the same time. */
|
|
9715
|
+
maxParallel: 3,
|
|
9716
|
+
/** Hard timeout for one headless agent run. */
|
|
9717
|
+
taskTimeoutMs: 30 * 6e4,
|
|
9718
|
+
/** Hard timeout for the planning run. */
|
|
9719
|
+
planTimeoutMs: 5 * 6e4,
|
|
9720
|
+
/** Longest summary kept per task (characters, head kept). */
|
|
9721
|
+
summaryMaxLength: 4e3
|
|
9722
|
+
};
|
|
9723
|
+
var startRunSchema = z12.object({
|
|
9724
|
+
objective: z12.string().trim().min(1).max(ORCHESTRATOR_LIMITS.objectiveMaxLength),
|
|
9725
|
+
/** Defaults to every available agent. */
|
|
9726
|
+
agents: z12.array(z12.enum(ORCHESTRATOR_AGENTS)).min(1).max(3).optional(),
|
|
9727
|
+
maxTasks: z12.number().int().min(1).max(ORCHESTRATOR_LIMITS.maxTasks).optional(),
|
|
9728
|
+
/** The agent that splits the objective. It must be installed, but need not take tasks.
|
|
9729
|
+
* Defaults to the first allowed agent in the order claude, codex, agy. */
|
|
9730
|
+
planner: z12.enum(ORCHESTRATOR_AGENTS).optional()
|
|
9731
|
+
});
|
|
9732
|
+
var OrchestratorError = class extends Error {
|
|
9733
|
+
constructor(code, message) {
|
|
9734
|
+
super(message);
|
|
9735
|
+
this.code = code;
|
|
9736
|
+
this.name = "OrchestratorError";
|
|
9737
|
+
}
|
|
9738
|
+
code;
|
|
9739
|
+
};
|
|
9740
|
+
var MAX_TEXT_CHUNK = 2e3;
|
|
9741
|
+
var MAX_TOOL_LINE = 200;
|
|
9742
|
+
var MAX_ERROR = 300;
|
|
9743
|
+
var AGENTS = {
|
|
9744
|
+
claude: {
|
|
9745
|
+
label: "Claude Code",
|
|
9746
|
+
binary: "claude",
|
|
9747
|
+
provider: "claude-cli",
|
|
9748
|
+
signIn: "run `claude` in a terminal and sign in"
|
|
9749
|
+
},
|
|
9750
|
+
codex: {
|
|
9751
|
+
label: "Codex",
|
|
9752
|
+
binary: "codex",
|
|
9753
|
+
provider: "codex-cli",
|
|
9754
|
+
signIn: "run `codex login` in a terminal"
|
|
9755
|
+
},
|
|
9756
|
+
agy: {
|
|
9757
|
+
label: "Antigravity",
|
|
9758
|
+
binary: "agy",
|
|
9759
|
+
provider: "antigravity-cli",
|
|
9760
|
+
signIn: "run `agy` in a terminal and sign in"
|
|
9761
|
+
}
|
|
9762
|
+
};
|
|
9763
|
+
var AGY_PLAN_NOTE = "Note: you are running in an empty scratch directory, not inside the project, so you cannot see the repository's files. Answer from the information in this message only and do not create files.";
|
|
9764
|
+
function agentArgs(agent, mode) {
|
|
9765
|
+
switch (agent) {
|
|
9766
|
+
case "claude":
|
|
9767
|
+
return mode === "plan" ? [
|
|
9768
|
+
"-p",
|
|
9769
|
+
"--output-format",
|
|
9770
|
+
"stream-json",
|
|
9771
|
+
"--verbose",
|
|
9772
|
+
"--permission-mode",
|
|
9773
|
+
"plan",
|
|
9774
|
+
"--tools",
|
|
9775
|
+
"Read",
|
|
9776
|
+
"Glob",
|
|
9777
|
+
"Grep",
|
|
9778
|
+
"--strict-mcp-config"
|
|
9779
|
+
] : ["-p", "--output-format", "stream-json", "--verbose", "--permission-mode", "acceptEdits"];
|
|
9780
|
+
case "codex":
|
|
9781
|
+
return [
|
|
9782
|
+
"exec",
|
|
9783
|
+
"--json",
|
|
9784
|
+
"--skip-git-repo-check",
|
|
9785
|
+
"--sandbox",
|
|
9786
|
+
mode === "plan" ? "read-only" : "workspace-write",
|
|
9787
|
+
"-c",
|
|
9788
|
+
"approval_policy=never",
|
|
9789
|
+
"-"
|
|
9790
|
+
];
|
|
9791
|
+
case "agy":
|
|
9792
|
+
return [
|
|
9793
|
+
"--input-format",
|
|
9794
|
+
"stream-json",
|
|
9795
|
+
"--output-format",
|
|
9796
|
+
"stream-json",
|
|
9797
|
+
"--sandbox",
|
|
9798
|
+
"--disable-slash-commands",
|
|
9799
|
+
"--print="
|
|
9800
|
+
];
|
|
9801
|
+
}
|
|
9802
|
+
}
|
|
9803
|
+
function realPath(p) {
|
|
9804
|
+
try {
|
|
9805
|
+
return realpathSync.native(p);
|
|
9806
|
+
} catch {
|
|
9807
|
+
return path8.resolve(p);
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
function agentStdin(agent, mode, prompt) {
|
|
9811
|
+
if (agent === "agy") {
|
|
9812
|
+
const content = mode === "plan" ? `${AGY_PLAN_NOTE}
|
|
9813
|
+
|
|
9814
|
+
${prompt}` : prompt;
|
|
9815
|
+
return `${JSON.stringify({ event: "user", message: { content } })}
|
|
9816
|
+
`;
|
|
9817
|
+
}
|
|
9818
|
+
if (agent === "claude" && prompt.trimStart().startsWith("/")) return `Task:
|
|
9819
|
+
${prompt}`;
|
|
9820
|
+
return prompt;
|
|
9821
|
+
}
|
|
9822
|
+
function abortError() {
|
|
9823
|
+
const err = new Error("The agent run was aborted");
|
|
9824
|
+
err.name = "AbortError";
|
|
9825
|
+
return err;
|
|
9826
|
+
}
|
|
9827
|
+
var SECRET_PATTERNS = [
|
|
9828
|
+
[/\b(?:sk|rk|pk)-[A-Za-z0-9_-]{8,}/g, "[redacted]"],
|
|
9829
|
+
[/\b(?:ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{8,}/g, "[redacted]"],
|
|
9830
|
+
[/\bAIza[A-Za-z0-9_-]{20,}/g, "[redacted]"],
|
|
9831
|
+
[/\bya29\.[A-Za-z0-9._-]{10,}/g, "[redacted]"],
|
|
9832
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*/g, "[redacted]"],
|
|
9833
|
+
[/\b(bearer)\s+[^\s"']+/gi, "$1 [redacted]"],
|
|
9834
|
+
[
|
|
9835
|
+
/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)(["']?\s*[:=]\s*["']?)[^\s"',}]+/gi,
|
|
9836
|
+
"$1$2[redacted]"
|
|
9837
|
+
]
|
|
9838
|
+
];
|
|
9839
|
+
function scrubSecrets(text) {
|
|
9840
|
+
let out = redact(text);
|
|
9841
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) out = out.replace(pattern, replacement);
|
|
9842
|
+
return out.replace(/\s+/g, " ").trim();
|
|
9843
|
+
}
|
|
9844
|
+
function clip(text, max) {
|
|
9845
|
+
return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
9846
|
+
}
|
|
9847
|
+
function shortError(text) {
|
|
9848
|
+
return clip(scrubSecrets(text), MAX_ERROR);
|
|
9849
|
+
}
|
|
9850
|
+
function formatDuration(ms) {
|
|
9851
|
+
if (ms >= 6e4) return `${Math.round(ms / 6e4)} min`;
|
|
9852
|
+
return `${Math.max(1, Math.ceil(ms / 1e3))} s`;
|
|
9853
|
+
}
|
|
9854
|
+
function tail(text, max = 400) {
|
|
9855
|
+
const t = text.trim();
|
|
9856
|
+
return t.length <= max ? t : t.slice(t.length - max);
|
|
9857
|
+
}
|
|
9858
|
+
function toolLine(name, input, cwd) {
|
|
9859
|
+
let hint;
|
|
9860
|
+
if (isRecord(input)) {
|
|
9861
|
+
const lower = new Map(Object.entries(input).map(([k, v]) => [k.toLowerCase(), v]));
|
|
9862
|
+
for (const key of [
|
|
9863
|
+
"file_path",
|
|
9864
|
+
"filepath",
|
|
9865
|
+
"absolutepath",
|
|
9866
|
+
"path",
|
|
9867
|
+
"targetfile",
|
|
9868
|
+
"command",
|
|
9869
|
+
"commandline",
|
|
9870
|
+
"cmd",
|
|
9871
|
+
"pattern",
|
|
9872
|
+
"query",
|
|
9873
|
+
"url",
|
|
9874
|
+
"description"
|
|
9875
|
+
]) {
|
|
9876
|
+
const value = lower.get(key);
|
|
9877
|
+
const str2 = typeof value === "string" ? value : Array.isArray(value) ? value.join(" ") : "";
|
|
9878
|
+
if (str2.trim()) {
|
|
9879
|
+
hint = str2;
|
|
9880
|
+
break;
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
if (hint && path8.isAbsolute(hint)) {
|
|
9885
|
+
for (const base of /* @__PURE__ */ new Set([cwd, realPath(cwd)])) {
|
|
9886
|
+
const rel = path8.relative(base, hint);
|
|
9887
|
+
if (rel && !rel.startsWith("..") && !path8.isAbsolute(rel)) {
|
|
9888
|
+
hint = rel.split(path8.sep).join("/");
|
|
9889
|
+
break;
|
|
9890
|
+
}
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
return clip(scrubSecrets(hint ? `${name} ${hint}` : name), MAX_TOOL_LINE);
|
|
9894
|
+
}
|
|
9895
|
+
function claudeParser(emit, cwd) {
|
|
9896
|
+
const state = { text: "", completed: false };
|
|
9897
|
+
let lastAssistant = "";
|
|
9898
|
+
return {
|
|
9899
|
+
state,
|
|
9900
|
+
flush() {
|
|
9901
|
+
},
|
|
9902
|
+
handle(raw) {
|
|
9903
|
+
if (!isRecord(raw)) return;
|
|
9904
|
+
const type = asString(raw.type);
|
|
9905
|
+
if (type === "system" && raw.subtype === "init") {
|
|
9906
|
+
const model = asString(raw.model);
|
|
9907
|
+
emit({
|
|
9908
|
+
kind: "status",
|
|
9909
|
+
text: model ? `Claude Code started (${model})` : "Claude Code started"
|
|
9910
|
+
});
|
|
9911
|
+
return;
|
|
9912
|
+
}
|
|
9913
|
+
if (type === "assistant" && raw.parent_tool_use_id == null && isRecord(raw.message)) {
|
|
9914
|
+
const texts = [];
|
|
9915
|
+
for (const block of asArray(raw.message.content)) {
|
|
9916
|
+
if (!isRecord(block)) continue;
|
|
9917
|
+
if (block.type === "text") {
|
|
9918
|
+
const t = asString(block.text)?.trim();
|
|
9919
|
+
if (t) texts.push(t);
|
|
9920
|
+
} else if (block.type === "tool_use") {
|
|
9921
|
+
emit({
|
|
9922
|
+
kind: "tool",
|
|
9923
|
+
text: toolLine(asString(block.name) ?? "tool", block.input, cwd)
|
|
9924
|
+
});
|
|
9925
|
+
}
|
|
9926
|
+
}
|
|
9927
|
+
if (texts.length > 0) {
|
|
9928
|
+
lastAssistant = texts.join("\n");
|
|
9929
|
+
emit({ kind: "text", text: clip(lastAssistant, MAX_TEXT_CHUNK) });
|
|
9930
|
+
}
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9933
|
+
if (type === "result") {
|
|
9934
|
+
const result = asString(raw.result) ?? "";
|
|
9935
|
+
const subtype = asString(raw.subtype) ?? "success";
|
|
9936
|
+
const u = isRecord(raw.usage) ? raw.usage : void 0;
|
|
9937
|
+
if (u) {
|
|
9938
|
+
state.tokens = (asNumber(u.input_tokens) ?? 0) + (asNumber(u.cache_creation_input_tokens) ?? 0) + (asNumber(u.cache_read_input_tokens) ?? 0) + (asNumber(u.output_tokens) ?? 0);
|
|
9939
|
+
}
|
|
9940
|
+
if (raw.is_error === true || subtype !== "success") {
|
|
9941
|
+
state.failure = result || `Claude Code reported ${subtype}`;
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
state.completed = true;
|
|
9945
|
+
state.text = result || lastAssistant;
|
|
9946
|
+
}
|
|
9947
|
+
}
|
|
9948
|
+
};
|
|
9949
|
+
}
|
|
9950
|
+
function codexErrorText(value) {
|
|
9951
|
+
if (typeof value === "string") return value;
|
|
9952
|
+
if (isRecord(value)) return asString(value.message) ?? JSON.stringify(value);
|
|
9953
|
+
return void 0;
|
|
9954
|
+
}
|
|
9955
|
+
function codexItemLine(item, cwd) {
|
|
9956
|
+
const type = asString(item.type) ?? "item";
|
|
9957
|
+
switch (type) {
|
|
9958
|
+
case "reasoning":
|
|
9959
|
+
case "todo_list":
|
|
9960
|
+
return void 0;
|
|
9961
|
+
case "command_execution":
|
|
9962
|
+
return toolLine("Run", { command: asString(item.command) ?? "" }, cwd);
|
|
9963
|
+
case "file_change": {
|
|
9964
|
+
const files = asArray(item.changes).flatMap((c) => isRecord(c) && asString(c.path) ? [asString(c.path)] : []).map((f) => toolLine("", { path: f }, cwd).trim());
|
|
9965
|
+
return clip(`Edit ${files.join(", ")}`.trim(), MAX_TOOL_LINE);
|
|
9966
|
+
}
|
|
9967
|
+
case "mcp_tool_call": {
|
|
9968
|
+
const name = [asString(item.server), asString(item.tool)].filter(Boolean).join(".");
|
|
9969
|
+
return toolLine(name || "MCP tool", void 0, cwd);
|
|
9970
|
+
}
|
|
9971
|
+
case "web_search":
|
|
9972
|
+
return toolLine("Search", { query: asString(item.query) ?? "" }, cwd);
|
|
9973
|
+
default:
|
|
9974
|
+
return toolLine(type, item, cwd);
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
9977
|
+
function codexParser(emit, cwd) {
|
|
9978
|
+
const state = { text: "", completed: false };
|
|
9979
|
+
const announced = /* @__PURE__ */ new Set();
|
|
9980
|
+
let lastError;
|
|
9981
|
+
return {
|
|
9982
|
+
state,
|
|
9983
|
+
flush() {
|
|
9984
|
+
if (!state.completed && !state.failure && lastError) state.failure = lastError;
|
|
9985
|
+
},
|
|
9986
|
+
handle(raw) {
|
|
9987
|
+
if (!isRecord(raw)) return;
|
|
9988
|
+
const ev = isRecord(raw.msg) ? raw.msg : raw;
|
|
9989
|
+
const type = asString(ev.type) ?? "";
|
|
9990
|
+
if (type === "thread.started") {
|
|
9991
|
+
emit({ kind: "status", text: "Codex started" });
|
|
9992
|
+
return;
|
|
9993
|
+
}
|
|
9994
|
+
if ((type === "item.started" || type === "item.completed") && isRecord(ev.item)) {
|
|
9995
|
+
const item = ev.item;
|
|
9996
|
+
const itemType = asString(item.type);
|
|
9997
|
+
if (itemType === "agent_message" || itemType === "assistant_message") {
|
|
9998
|
+
if (type !== "item.completed") return;
|
|
9999
|
+
const text = (asString(item.text) ?? asString(item.message) ?? "").trim();
|
|
10000
|
+
if (text) {
|
|
10001
|
+
state.text = text;
|
|
10002
|
+
emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
|
|
10003
|
+
}
|
|
10004
|
+
return;
|
|
10005
|
+
}
|
|
10006
|
+
if (itemType === "error") {
|
|
10007
|
+
const message = asString(item.message);
|
|
10008
|
+
if (message && type === "item.completed")
|
|
10009
|
+
emit({ kind: "error", text: shortError(message) });
|
|
10010
|
+
return;
|
|
10011
|
+
}
|
|
10012
|
+
const id = asString(item.id) ?? `${itemType}-${announced.size}`;
|
|
10013
|
+
if (itemType === "file_change" && type !== "item.completed") return;
|
|
10014
|
+
if (announced.has(id)) return;
|
|
10015
|
+
announced.add(id);
|
|
10016
|
+
const line = codexItemLine(item, cwd);
|
|
10017
|
+
if (line) emit({ kind: "tool", text: line });
|
|
10018
|
+
return;
|
|
10019
|
+
}
|
|
10020
|
+
if (type === "turn.completed" || type === "task_complete") {
|
|
10021
|
+
state.completed = true;
|
|
10022
|
+
const last = asString(ev.last_agent_message);
|
|
10023
|
+
if (!state.text && last) state.text = last;
|
|
10024
|
+
const u = isRecord(ev.usage) ? ev.usage : void 0;
|
|
10025
|
+
if (u) state.tokens = (asNumber(u.input_tokens) ?? 0) + (asNumber(u.output_tokens) ?? 0);
|
|
10026
|
+
return;
|
|
10027
|
+
}
|
|
10028
|
+
if (type === "turn.failed") {
|
|
10029
|
+
state.failure = codexErrorText(ev.error) ?? lastError ?? "Codex reported an error";
|
|
10030
|
+
return;
|
|
10031
|
+
}
|
|
10032
|
+
if (type === "error") {
|
|
10033
|
+
lastError = codexErrorText(ev.message) ?? codexErrorText(ev.error) ?? lastError;
|
|
10034
|
+
if (lastError) emit({ kind: "error", text: shortError(lastError) });
|
|
10035
|
+
return;
|
|
10036
|
+
}
|
|
10037
|
+
if (type === "agent_message") {
|
|
10038
|
+
const text = (asString(ev.message) ?? "").trim();
|
|
10039
|
+
if (text) {
|
|
10040
|
+
state.text = text;
|
|
10041
|
+
emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
|
|
10042
|
+
}
|
|
10043
|
+
}
|
|
10044
|
+
}
|
|
10045
|
+
};
|
|
10046
|
+
}
|
|
10047
|
+
function agyParser(emit, cwd) {
|
|
10048
|
+
const state = { text: "", completed: false };
|
|
10049
|
+
const announced = /* @__PURE__ */ new Set();
|
|
10050
|
+
let streamed = "";
|
|
10051
|
+
let buffer = "";
|
|
10052
|
+
let bufferStep;
|
|
10053
|
+
const flushText = () => {
|
|
10054
|
+
const text = buffer.trim();
|
|
10055
|
+
buffer = "";
|
|
10056
|
+
bufferStep = void 0;
|
|
10057
|
+
if (text) emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
|
|
10058
|
+
};
|
|
10059
|
+
return {
|
|
10060
|
+
state,
|
|
10061
|
+
flush: flushText,
|
|
10062
|
+
handle(raw) {
|
|
10063
|
+
if (!isRecord(raw)) return;
|
|
10064
|
+
const event = asString(raw.event);
|
|
10065
|
+
if (event === "init") {
|
|
10066
|
+
emit({ kind: "status", text: "Antigravity started" });
|
|
10067
|
+
return;
|
|
10068
|
+
}
|
|
10069
|
+
if (event === "step_update" && isRecord(raw.step_update)) {
|
|
10070
|
+
const step = raw.step_update;
|
|
10071
|
+
const stepType = asString(step.step_type);
|
|
10072
|
+
const index = String(asNumber(step.step_index) ?? asString(step.step_index) ?? "");
|
|
10073
|
+
if (stepType === "agent_response") {
|
|
10074
|
+
if (bufferStep !== void 0 && bufferStep !== index) flushText();
|
|
10075
|
+
const delta = asString(step.text_delta);
|
|
10076
|
+
if (delta) {
|
|
10077
|
+
bufferStep = index;
|
|
10078
|
+
buffer += delta;
|
|
10079
|
+
streamed += delta;
|
|
10080
|
+
}
|
|
10081
|
+
if (asString(step.state) === "DONE") flushText();
|
|
10082
|
+
return;
|
|
10083
|
+
}
|
|
10084
|
+
if (stepType === "tool") {
|
|
10085
|
+
flushText();
|
|
10086
|
+
if (announced.has(index)) return;
|
|
10087
|
+
announced.add(index);
|
|
10088
|
+
const info = isRecord(step.tool_info) ? step.tool_info : {};
|
|
10089
|
+
const name = asString(step.tool_name) ?? asString(info.name) ?? "tool";
|
|
10090
|
+
emit({ kind: "tool", text: toolLine(name, info.parameters, cwd) });
|
|
10091
|
+
}
|
|
10092
|
+
return;
|
|
10093
|
+
}
|
|
10094
|
+
if (event === "result" && isRecord(raw.result)) {
|
|
10095
|
+
flushText();
|
|
10096
|
+
const result = raw.result;
|
|
10097
|
+
const u = isRecord(result.usage) ? result.usage : void 0;
|
|
10098
|
+
if (u) {
|
|
10099
|
+
state.tokens = (asNumber(u.input_tokens) ?? 0) + (asNumber(u.output_tokens) ?? 0) + (asNumber(u.thinking_tokens) ?? 0);
|
|
10100
|
+
}
|
|
10101
|
+
const status = asString(result.status);
|
|
10102
|
+
if (status !== "SUCCESS") {
|
|
10103
|
+
state.failure = asString(result.error) ?? `Antigravity ended with ${status ?? "an error"}`;
|
|
10104
|
+
return;
|
|
10105
|
+
}
|
|
10106
|
+
state.completed = true;
|
|
10107
|
+
state.text = (asString(result.response) ?? streamed).trim();
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
};
|
|
10111
|
+
}
|
|
10112
|
+
var PARSERS = {
|
|
10113
|
+
claude: claudeParser,
|
|
10114
|
+
codex: codexParser,
|
|
10115
|
+
agy: agyParser
|
|
10116
|
+
};
|
|
10117
|
+
function failureMessage(info, detail, evidence, code) {
|
|
10118
|
+
const { kind } = classifyCliText(evidence);
|
|
10119
|
+
if (kind === "auth") return `${info.label} is not signed in: ${info.signIn}`;
|
|
10120
|
+
const clean = scrubSecrets(detail);
|
|
10121
|
+
if (kind === "quota_exhausted") return shortError(`${info.label} usage limit reached: ${clean}`);
|
|
10122
|
+
if (kind === "rate_limit") return shortError(`${info.label} is rate limited: ${clean}`);
|
|
10123
|
+
const exit = code !== null && code !== 0 ? ` (exit ${code})` : "";
|
|
10124
|
+
return shortError(`${info.label} failed${exit}${clean ? `: ${clean}` : ""}`);
|
|
10125
|
+
}
|
|
10126
|
+
function createCliAgentRunner(options = {}) {
|
|
10127
|
+
const env = options.env ?? process.env;
|
|
10128
|
+
const childEnv = {};
|
|
10129
|
+
for (const [k, v] of Object.entries(env)) if (typeof v === "string") childEnv[k] = v;
|
|
10130
|
+
const resolveBinary = (agent) => {
|
|
10131
|
+
const override = options.binaries?.[agent];
|
|
10132
|
+
const command = override?.command ?? AGENTS[agent].binary;
|
|
10133
|
+
return { command: resolveOnPath(command, env), prefix: override?.args ?? [] };
|
|
10134
|
+
};
|
|
10135
|
+
const isAvailable = (agent) => {
|
|
10136
|
+
const { command } = resolveBinary(agent);
|
|
10137
|
+
if (!path8.isAbsolute(command)) return false;
|
|
10138
|
+
try {
|
|
10139
|
+
return existsSync4(command) && statSync3(command).isFile();
|
|
10140
|
+
} catch {
|
|
10141
|
+
return false;
|
|
10142
|
+
}
|
|
10143
|
+
};
|
|
10144
|
+
async function run(request) {
|
|
10145
|
+
if (request.signal.aborted) throw abortError();
|
|
10146
|
+
const info = AGENTS[request.agent];
|
|
10147
|
+
const { command, prefix } = resolveBinary(request.agent);
|
|
10148
|
+
const args = agentArgs(request.agent, request.mode);
|
|
10149
|
+
if (needsShell(command) && ![...prefix, ...args].every(isShellSafeArg)) {
|
|
10150
|
+
return { ok: false, text: "", error: `${info.label}: unsafe argument for the Windows shell` };
|
|
10151
|
+
}
|
|
10152
|
+
const emit = (chunk) => {
|
|
10153
|
+
try {
|
|
10154
|
+
request.onOutput(chunk);
|
|
10155
|
+
} catch {
|
|
10156
|
+
}
|
|
10157
|
+
};
|
|
10158
|
+
let scratch;
|
|
10159
|
+
let cwd = request.cwd;
|
|
10160
|
+
if (request.agent === "agy" && request.mode === "plan") {
|
|
10161
|
+
scratch = mkdtempSync(path8.join(options.scratchDir ?? tmpdir(), "davecode-agy-plan-"));
|
|
10162
|
+
cwd = scratch;
|
|
10163
|
+
}
|
|
10164
|
+
const timeout = AbortSignal.timeout(request.timeoutMs);
|
|
10165
|
+
const signal = AbortSignal.any([request.signal, timeout]);
|
|
10166
|
+
const timedOut = () => ({
|
|
10167
|
+
ok: false,
|
|
10168
|
+
text: "",
|
|
10169
|
+
error: `${info.label} timed out after ${formatDuration(request.timeoutMs)}`
|
|
10170
|
+
});
|
|
10171
|
+
try {
|
|
10172
|
+
let cli;
|
|
10173
|
+
try {
|
|
10174
|
+
cli = runCli({
|
|
10175
|
+
provider: info.provider,
|
|
10176
|
+
accountId: "orchestrator",
|
|
10177
|
+
settings: { command, prefixArgs: prefix },
|
|
10178
|
+
args,
|
|
10179
|
+
env: childEnv,
|
|
10180
|
+
cwd,
|
|
10181
|
+
stdin: agentStdin(request.agent, request.mode, request.prompt),
|
|
10182
|
+
signal
|
|
10183
|
+
});
|
|
10184
|
+
} catch (err) {
|
|
10185
|
+
if (request.signal.aborted) throw abortError();
|
|
10186
|
+
if (timeout.aborted) return timedOut();
|
|
10187
|
+
return { ok: false, text: "", error: shortError(errText(err)) };
|
|
10188
|
+
}
|
|
10189
|
+
const parser = PARSERS[request.agent](emit, cwd);
|
|
10190
|
+
let streamError;
|
|
10191
|
+
try {
|
|
10192
|
+
for await (const event of cli.events) parser.handle(event);
|
|
10193
|
+
} catch (err) {
|
|
10194
|
+
streamError = errText(err);
|
|
10195
|
+
}
|
|
10196
|
+
parser.flush();
|
|
10197
|
+
let exit;
|
|
10198
|
+
try {
|
|
10199
|
+
exit = await cli.exit;
|
|
10200
|
+
} catch {
|
|
10201
|
+
if (request.signal.aborted) throw abortError();
|
|
10202
|
+
return {
|
|
10203
|
+
ok: false,
|
|
10204
|
+
text: "",
|
|
10205
|
+
error: `${info.label} could not be started: is \`${info.binary}\` installed and on PATH?`
|
|
10206
|
+
};
|
|
10207
|
+
}
|
|
10208
|
+
if (request.signal.aborted) throw abortError();
|
|
10209
|
+
if (exit.aborted || timeout.aborted) return { ...timedOut(), text: parser.state.text };
|
|
10210
|
+
const { state } = parser;
|
|
10211
|
+
if (state.completed && !state.failure && exit.code === 0) {
|
|
10212
|
+
return {
|
|
10213
|
+
ok: true,
|
|
10214
|
+
text: state.text,
|
|
10215
|
+
...state.tokens !== void 0 ? { tokens: state.tokens } : {}
|
|
10216
|
+
};
|
|
10217
|
+
}
|
|
10218
|
+
const detail = state.failure ?? streamError ?? (tail(exit.stderr) || (exit.code === 0 ? "the CLI ended without a final result" : "no output"));
|
|
10219
|
+
return {
|
|
10220
|
+
ok: false,
|
|
10221
|
+
text: state.text,
|
|
10222
|
+
error: failureMessage(info, detail, `${detail}
|
|
10223
|
+
${tail(exit.stderr)}`, exit.code),
|
|
10224
|
+
...state.tokens !== void 0 ? { tokens: state.tokens } : {}
|
|
10225
|
+
};
|
|
10226
|
+
} finally {
|
|
10227
|
+
if (scratch) {
|
|
10228
|
+
try {
|
|
10229
|
+
rmSync4(scratch, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
10230
|
+
} catch {
|
|
10231
|
+
}
|
|
10232
|
+
}
|
|
10233
|
+
}
|
|
10234
|
+
}
|
|
10235
|
+
return {
|
|
10236
|
+
async availability() {
|
|
10237
|
+
return ORCHESTRATOR_AGENTS.map((agent) => ({ agent, available: isAvailable(agent) }));
|
|
10238
|
+
},
|
|
10239
|
+
run
|
|
10240
|
+
};
|
|
10241
|
+
}
|
|
10242
|
+
function errText(err) {
|
|
10243
|
+
return err instanceof Error ? err.message : String(err);
|
|
10244
|
+
}
|
|
10245
|
+
var AGENT_DESCRIPTIONS = {
|
|
10246
|
+
claude: "Claude Code (Anthropic): careful multi-file changes, refactors and reasoning about existing code.",
|
|
10247
|
+
codex: "Codex CLI (OpenAI): focused implementation work, scripts, tests and quick fixes.",
|
|
10248
|
+
agy: "Antigravity (Google): broad feature work, front-end and UI changes, exploratory tasks."
|
|
10249
|
+
};
|
|
10250
|
+
var TITLE_MAX_LENGTH = 120;
|
|
10251
|
+
var PROMPT_MAX_LENGTH = 8e3;
|
|
10252
|
+
var REPAIR_REPLY_MAX_LENGTH = 6e3;
|
|
10253
|
+
function pickPlanner(agents) {
|
|
10254
|
+
return ORCHESTRATOR_AGENTS.find((agent) => agents.includes(agent));
|
|
10255
|
+
}
|
|
10256
|
+
var REPLY_SHAPE = '{"tasks":[{"title":string,"agent":"claude"|"codex"|"agy","prompt":string,"dependsOn":[number]}]}';
|
|
10257
|
+
function buildPlanPrompt(input) {
|
|
10258
|
+
const agentLines = input.agents.map((agent) => `- ${agent}: ${AGENT_DESCRIPTIONS[agent]}`);
|
|
10259
|
+
return [
|
|
10260
|
+
"You are the planner of DaveCode, which splits one objective across several coding agents.",
|
|
10261
|
+
"Do not change any file. Read the repository as much as you need, then answer with a plan.",
|
|
10262
|
+
"",
|
|
10263
|
+
"Objective:",
|
|
10264
|
+
input.objective,
|
|
10265
|
+
"",
|
|
10266
|
+
"Agents you may assign tasks to:",
|
|
10267
|
+
...agentLines,
|
|
10268
|
+
"",
|
|
10269
|
+
"Rules:",
|
|
10270
|
+
`- Use at most ${input.maxTasks} task${input.maxTasks === 1 ? "" : "s"}. Fewer is better when the objective is small.`,
|
|
10271
|
+
"- Tasks run in parallel, each in its own separate copy of the repository, and their changes are merged afterwards. Give tasks different files to touch wherever possible so the merges do not conflict.",
|
|
10272
|
+
'- When a task needs the result of another task, list it in "dependsOn" (1-based positions in the "tasks" array). It then starts only after those tasks finished and were merged.',
|
|
10273
|
+
'- Each "prompt" must be self-contained: the agent sees only its own prompt and the repository, not this plan or the other prompts. Say exactly what to build or change, where, and how to know it is done.',
|
|
10274
|
+
'- Write every "title" and "prompt" in the language of the objective.',
|
|
10275
|
+
"- Spread the work over the agents according to their strengths.",
|
|
10276
|
+
"",
|
|
10277
|
+
"Reply with ONLY a JSON object, no prose and no code fence, of this shape:",
|
|
10278
|
+
REPLY_SHAPE
|
|
10279
|
+
].join("\n");
|
|
10280
|
+
}
|
|
10281
|
+
function buildRepairPrompt(input, previousReply, error) {
|
|
10282
|
+
const reply = previousReply.length > REPAIR_REPLY_MAX_LENGTH ? `${previousReply.slice(0, REPAIR_REPLY_MAX_LENGTH)}\u2026` : previousReply;
|
|
10283
|
+
return [
|
|
10284
|
+
"Your previous plan could not be used.",
|
|
10285
|
+
`Problem: ${error}`,
|
|
10286
|
+
"",
|
|
10287
|
+
"Your previous reply:",
|
|
10288
|
+
reply.trim() || "(empty)",
|
|
10289
|
+
"",
|
|
10290
|
+
`Objective: ${input.objective}`,
|
|
10291
|
+
`Allowed agents: ${input.agents.join(", ")}. At most ${input.maxTasks} tasks, at least one.`,
|
|
10292
|
+
"Dependencies are 1-based task positions and must not form a cycle.",
|
|
10293
|
+
"Do not change any file. Reply with ONLY a JSON object of this shape:",
|
|
10294
|
+
REPLY_SHAPE
|
|
10295
|
+
].join("\n");
|
|
10296
|
+
}
|
|
10297
|
+
function buildTaskPrompt(input) {
|
|
10298
|
+
const others = input.tasks.filter((t) => t.id !== input.task.id);
|
|
10299
|
+
const deps = input.tasks.filter((t) => input.task.dependsOn.includes(t.id));
|
|
10300
|
+
const lines2 = [
|
|
10301
|
+
"You are one of several coding agents working together on one objective, coordinated by DaveCode.",
|
|
10302
|
+
"",
|
|
10303
|
+
"Overall objective:",
|
|
10304
|
+
input.objective,
|
|
10305
|
+
"",
|
|
10306
|
+
`Your task: ${input.task.title}`,
|
|
10307
|
+
input.task.prompt,
|
|
10308
|
+
""
|
|
10309
|
+
];
|
|
10310
|
+
if (deps.length > 0) {
|
|
10311
|
+
lines2.push(
|
|
10312
|
+
"The results of these tasks are already in your copy of the repository:",
|
|
10313
|
+
...deps.map((t) => `- ${t.title}`),
|
|
10314
|
+
""
|
|
10315
|
+
);
|
|
10316
|
+
}
|
|
10317
|
+
if (others.length > 0) {
|
|
10318
|
+
lines2.push(
|
|
10319
|
+
"Other agents work on these parts in parallel, in their own copies of the repository:",
|
|
10320
|
+
...others.map((t) => `- ${t.title} (${t.agent})`),
|
|
10321
|
+
""
|
|
10322
|
+
);
|
|
10323
|
+
}
|
|
10324
|
+
lines2.push(
|
|
10325
|
+
"Rules:",
|
|
10326
|
+
"- Keep to the scope of your task; leave the other parts to the other agents.",
|
|
10327
|
+
"- Do not commit, push, create or switch branches. DaveCode commits and merges your changes.",
|
|
10328
|
+
"- Do not read or modify anything under `.davecode/`.",
|
|
10329
|
+
"- When you are done, finish with a short summary of what you changed and anything left open."
|
|
10330
|
+
);
|
|
10331
|
+
return lines2.join("\n");
|
|
10332
|
+
}
|
|
10333
|
+
var dependencySchema = z13.union([z13.number(), z13.string()]);
|
|
10334
|
+
var plannedTaskSchema2 = z13.object({
|
|
10335
|
+
title: z13.string().trim().min(1),
|
|
10336
|
+
agent: z13.string().optional(),
|
|
10337
|
+
prompt: z13.string().trim().min(1),
|
|
10338
|
+
dependsOn: z13.array(dependencySchema).nullish()
|
|
10339
|
+
});
|
|
10340
|
+
var planReplySchema = z13.object({
|
|
10341
|
+
tasks: z13.array(plannedTaskSchema2).min(1, "the plan has no tasks")
|
|
10342
|
+
});
|
|
10343
|
+
function dependencyIndex(value) {
|
|
10344
|
+
if (typeof value === "number") return Number.isInteger(value) ? value : void 0;
|
|
10345
|
+
const match = /^\s*t?(\d+)\s*$/i.exec(value);
|
|
10346
|
+
return match?.[1] ? Number(match[1]) : void 0;
|
|
10347
|
+
}
|
|
10348
|
+
function clip2(text, max) {
|
|
10349
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
10350
|
+
}
|
|
10351
|
+
function describeZodError(error) {
|
|
10352
|
+
const issue = error.issues[0];
|
|
10353
|
+
if (!issue) return "the reply does not match the expected shape";
|
|
10354
|
+
const path10 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
10355
|
+
return `${path10}${issue.message}`;
|
|
10356
|
+
}
|
|
10357
|
+
function findCycle(tasks) {
|
|
10358
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
10359
|
+
const done = /* @__PURE__ */ new Set();
|
|
10360
|
+
const stack = [];
|
|
10361
|
+
const onStack = /* @__PURE__ */ new Set();
|
|
10362
|
+
const visit = (id) => {
|
|
10363
|
+
stack.push(id);
|
|
10364
|
+
onStack.add(id);
|
|
10365
|
+
for (const dep of byId.get(id)?.dependsOn ?? []) {
|
|
10366
|
+
if (onStack.has(dep)) return [...stack.slice(stack.indexOf(dep)), dep];
|
|
10367
|
+
if (!done.has(dep) && byId.has(dep)) {
|
|
10368
|
+
const cycle = visit(dep);
|
|
10369
|
+
if (cycle) return cycle;
|
|
10370
|
+
}
|
|
10371
|
+
}
|
|
10372
|
+
stack.pop();
|
|
10373
|
+
onStack.delete(id);
|
|
10374
|
+
done.add(id);
|
|
10375
|
+
return void 0;
|
|
10376
|
+
};
|
|
10377
|
+
for (const task of tasks) {
|
|
10378
|
+
if (done.has(task.id)) continue;
|
|
10379
|
+
const cycle = visit(task.id);
|
|
10380
|
+
if (cycle) return cycle;
|
|
10381
|
+
}
|
|
10382
|
+
return void 0;
|
|
10383
|
+
}
|
|
10384
|
+
function parsePlan(text, options) {
|
|
10385
|
+
if (options.agents.length === 0) return { ok: false, error: "no agents are allowed" };
|
|
10386
|
+
let raw;
|
|
10387
|
+
try {
|
|
10388
|
+
raw = extractJsonObject(text);
|
|
10389
|
+
} catch (err) {
|
|
10390
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
10391
|
+
return { ok: false, error: `the reply is not a valid JSON object (${clip2(reason, 120)})` };
|
|
10392
|
+
}
|
|
10393
|
+
const parsed = planReplySchema.safeParse(raw);
|
|
10394
|
+
if (!parsed.success) return { ok: false, error: describeZodError(parsed.error) };
|
|
10395
|
+
const maxTasks = Math.max(1, Math.min(options.maxTasks, ORCHESTRATOR_LIMITS.maxTasks));
|
|
10396
|
+
const planned = parsed.data.tasks.slice(0, maxTasks);
|
|
10397
|
+
let roundRobin = 0;
|
|
10398
|
+
const tasks = planned.map((p, index) => {
|
|
10399
|
+
const allowed = options.agents.find((a) => a === p.agent?.trim().toLowerCase());
|
|
10400
|
+
const agent = allowed ?? options.agents[roundRobin++ % options.agents.length];
|
|
10401
|
+
const deps = /* @__PURE__ */ new Set();
|
|
10402
|
+
for (const value of p.dependsOn ?? []) {
|
|
10403
|
+
const dep = dependencyIndex(value);
|
|
10404
|
+
if (dep === void 0 || dep < 1 || dep > planned.length || dep === index + 1) continue;
|
|
10405
|
+
deps.add(`t${dep}`);
|
|
10406
|
+
}
|
|
10407
|
+
return {
|
|
10408
|
+
id: `t${index + 1}`,
|
|
10409
|
+
title: clip2(p.title.replace(/\s+/g, " "), TITLE_MAX_LENGTH),
|
|
10410
|
+
agent,
|
|
10411
|
+
prompt: clip2(p.prompt, PROMPT_MAX_LENGTH),
|
|
10412
|
+
dependsOn: [...deps],
|
|
10413
|
+
status: "pending"
|
|
10414
|
+
};
|
|
10415
|
+
});
|
|
10416
|
+
const cycle = findCycle(tasks);
|
|
10417
|
+
if (cycle) {
|
|
10418
|
+
return { ok: false, error: `the dependencies form a cycle (${cycle.join(" -> ")})` };
|
|
10419
|
+
}
|
|
10420
|
+
return { ok: true, tasks };
|
|
10421
|
+
}
|
|
10422
|
+
var ORCHESTRATOR_RUNS_KEPT = 20;
|
|
10423
|
+
var agentSchema = z14.enum(ORCHESTRATOR_AGENTS);
|
|
10424
|
+
var taskSchema = z14.object({
|
|
10425
|
+
id: z14.string().min(1),
|
|
10426
|
+
title: z14.string(),
|
|
10427
|
+
agent: agentSchema,
|
|
10428
|
+
prompt: z14.string(),
|
|
10429
|
+
dependsOn: z14.array(z14.string()),
|
|
10430
|
+
status: z14.enum(["pending", "running", "done", "failed", "conflict", "cancelled", "skipped"]),
|
|
10431
|
+
branch: z14.string().optional(),
|
|
10432
|
+
summary: z14.string().optional(),
|
|
10433
|
+
changedFiles: z14.array(z14.string()).optional(),
|
|
10434
|
+
error: z14.string().optional(),
|
|
10435
|
+
startedAt: z14.string().optional(),
|
|
10436
|
+
finishedAt: z14.string().optional()
|
|
10437
|
+
});
|
|
10438
|
+
var orchestratorRunSchema = z14.object({
|
|
10439
|
+
id: z14.string().min(1),
|
|
10440
|
+
objective: z14.string(),
|
|
10441
|
+
status: z14.enum(["planning", "running", "done", "failed", "cancelled"]),
|
|
10442
|
+
agents: z14.array(agentSchema),
|
|
10443
|
+
planner: agentSchema.optional(),
|
|
10444
|
+
baseBranch: z14.string().optional(),
|
|
10445
|
+
baseCommit: z14.string().optional(),
|
|
10446
|
+
integrationBranch: z14.string().optional(),
|
|
10447
|
+
tasks: z14.array(taskSchema),
|
|
10448
|
+
summary: z14.string().optional(),
|
|
10449
|
+
error: z14.string().optional(),
|
|
10450
|
+
appliedAt: z14.string().optional(),
|
|
10451
|
+
createdAt: z14.string(),
|
|
10452
|
+
updatedAt: z14.string()
|
|
10453
|
+
});
|
|
10454
|
+
var fileSchema2 = z14.object({
|
|
10455
|
+
version: z14.literal(1),
|
|
10456
|
+
runs: z14.array(z14.unknown())
|
|
10457
|
+
});
|
|
10458
|
+
function orchestratorRunsPath(home, projectDir) {
|
|
10459
|
+
let key = resolve5(projectDir);
|
|
10460
|
+
if (process.platform === "win32") key = key.toLowerCase();
|
|
10461
|
+
const hash = createHash3("sha256").update(key).digest("hex").slice(0, 16);
|
|
10462
|
+
return join8(home, "orchestrator", hash, "runs.json");
|
|
10463
|
+
}
|
|
10464
|
+
function newestFirst(runs) {
|
|
10465
|
+
return [...runs].sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
10466
|
+
}
|
|
10467
|
+
var OrchestratorStore = class {
|
|
10468
|
+
path;
|
|
10469
|
+
queued;
|
|
10470
|
+
writing;
|
|
10471
|
+
constructor(home, projectDir) {
|
|
10472
|
+
this.path = orchestratorRunsPath(home, projectDir);
|
|
10473
|
+
}
|
|
10474
|
+
/**
|
|
10475
|
+
* The stored runs, newest first. A missing or corrupt file yields no runs; a run that does not
|
|
10476
|
+
* validate is skipped.
|
|
10477
|
+
*/
|
|
10478
|
+
load() {
|
|
10479
|
+
let text;
|
|
10480
|
+
try {
|
|
10481
|
+
text = readFileSync4(this.path, "utf8");
|
|
10482
|
+
} catch {
|
|
10483
|
+
return [];
|
|
10484
|
+
}
|
|
10485
|
+
let file;
|
|
10486
|
+
try {
|
|
10487
|
+
file = fileSchema2.parse(JSON.parse(text));
|
|
10488
|
+
} catch {
|
|
10489
|
+
return [];
|
|
10490
|
+
}
|
|
10491
|
+
const runs = [];
|
|
10492
|
+
for (const entry of file.runs) {
|
|
10493
|
+
const parsed = orchestratorRunSchema.safeParse(entry);
|
|
10494
|
+
if (parsed.success) runs.push(parsed.data);
|
|
10495
|
+
}
|
|
10496
|
+
return newestFirst(runs).slice(0, ORCHESTRATOR_RUNS_KEPT);
|
|
10497
|
+
}
|
|
10498
|
+
/**
|
|
10499
|
+
* Snapshots `runs` now and writes them atomically in the background, keeping the newest
|
|
10500
|
+
* {@link ORCHESTRATOR_RUNS_KEPT}. Writes are serialized; a snapshot that is still queued is
|
|
10501
|
+
* replaced by the newer one. Resolves once that snapshot (or a newer one) is on disk; never
|
|
10502
|
+
* rejects (a failed write is retried by the next save).
|
|
10503
|
+
*/
|
|
10504
|
+
save(runs) {
|
|
10505
|
+
const kept = newestFirst(runs).slice(0, ORCHESTRATOR_RUNS_KEPT);
|
|
10506
|
+
this.queued = `${JSON.stringify({ version: 1, runs: kept }, null, 2)}
|
|
10507
|
+
`;
|
|
10508
|
+
if (!this.writing) {
|
|
10509
|
+
this.writing = this.drain().finally(() => {
|
|
10510
|
+
this.writing = void 0;
|
|
10511
|
+
});
|
|
10512
|
+
}
|
|
10513
|
+
return this.writing;
|
|
10514
|
+
}
|
|
10515
|
+
/** Waits for every queued write. */
|
|
10516
|
+
async flush() {
|
|
10517
|
+
while (this.writing) await this.writing;
|
|
10518
|
+
}
|
|
10519
|
+
async drain() {
|
|
10520
|
+
while (this.queued !== void 0) {
|
|
10521
|
+
const content = this.queued;
|
|
10522
|
+
this.queued = void 0;
|
|
10523
|
+
try {
|
|
10524
|
+
await atomicWriteFile(this.path, content);
|
|
10525
|
+
} catch {
|
|
10526
|
+
}
|
|
10527
|
+
}
|
|
10528
|
+
}
|
|
10529
|
+
};
|
|
10530
|
+
var ERROR_MAX_LENGTH = 300;
|
|
10531
|
+
var OUTPUT_MAX_LENGTH = 2e3;
|
|
10532
|
+
var RUNS_KEPT = 20;
|
|
10533
|
+
var INTERRUPTED_ERROR = "Interrupted: DaveCode closed while this run was active.";
|
|
10534
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["planning", "running"]);
|
|
10535
|
+
var RunFailure = class extends Error {
|
|
10536
|
+
};
|
|
10537
|
+
function clip3(text, max) {
|
|
10538
|
+
const trimmed = text.trim();
|
|
10539
|
+
return trimmed.length > max ? `${trimmed.slice(0, max - 1)}\u2026` : trimmed;
|
|
10540
|
+
}
|
|
10541
|
+
function errorText3(err) {
|
|
10542
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10543
|
+
return clip3(message || "unknown error", ERROR_MAX_LENGTH);
|
|
10544
|
+
}
|
|
10545
|
+
function isAbortError(err) {
|
|
10546
|
+
return typeof err === "object" && err !== null && "name" in err && (err.name === "AbortError" || err.name === "TimeoutError");
|
|
10547
|
+
}
|
|
10548
|
+
var Queue = class {
|
|
10549
|
+
tail = Promise.resolve();
|
|
10550
|
+
run(work) {
|
|
10551
|
+
const next = this.tail.then(work, work);
|
|
10552
|
+
this.tail = next.catch(() => void 0);
|
|
10553
|
+
return next;
|
|
10554
|
+
}
|
|
10555
|
+
};
|
|
10556
|
+
var isActive = (run) => ACTIVE_STATUSES.has(run.status);
|
|
10557
|
+
function createOrchestratorService(options) {
|
|
10558
|
+
return new OrchestratorEngine(options);
|
|
10559
|
+
}
|
|
10560
|
+
var OrchestratorEngine = class {
|
|
10561
|
+
constructor(options) {
|
|
10562
|
+
this.options = options;
|
|
10563
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
10564
|
+
this.newId = options.newId ?? (() => randomBytes9(6).toString("hex"));
|
|
10565
|
+
this.store = new OrchestratorStore(options.home, options.projectDir);
|
|
10566
|
+
this.runs = this.store.load();
|
|
10567
|
+
this.recoverInterrupted();
|
|
10568
|
+
}
|
|
10569
|
+
options;
|
|
10570
|
+
now;
|
|
10571
|
+
newId;
|
|
10572
|
+
store;
|
|
10573
|
+
/** Newest first. */
|
|
10574
|
+
runs;
|
|
10575
|
+
active = /* @__PURE__ */ new Map();
|
|
10576
|
+
applying = /* @__PURE__ */ new Set();
|
|
10577
|
+
/** A `start` that passed the busy check but has not registered its run yet. */
|
|
10578
|
+
starting = false;
|
|
10579
|
+
/** Every git mutation goes through here: worktree, commit and merge must not interleave. */
|
|
10580
|
+
git = new Queue();
|
|
10581
|
+
// --- Public API ------------------------------------------------------------------------------
|
|
10582
|
+
agents() {
|
|
10583
|
+
return this.options.runner.availability();
|
|
10584
|
+
}
|
|
10585
|
+
async start(rawInput) {
|
|
10586
|
+
const input = startRunSchema.parse(rawInput);
|
|
10587
|
+
this.assertIdle();
|
|
10588
|
+
this.starting = true;
|
|
10589
|
+
try {
|
|
10590
|
+
const { workspace, runner } = this.options;
|
|
10591
|
+
let head;
|
|
10592
|
+
try {
|
|
10593
|
+
const checked = await workspace.check();
|
|
10594
|
+
if (!checked.ok) throw new OrchestratorError("not_a_repository", clip3(checked.reason, 300));
|
|
10595
|
+
head = { branch: checked.branch, commit: checked.commit };
|
|
10596
|
+
} catch (err) {
|
|
10597
|
+
if (err instanceof OrchestratorError) throw err;
|
|
10598
|
+
throw new OrchestratorError("not_a_repository", errorText3(err));
|
|
10599
|
+
}
|
|
10600
|
+
const availability = await runner.availability();
|
|
10601
|
+
const available = new Set(availability.filter((a) => a.available).map((a) => a.agent));
|
|
10602
|
+
const wanted = new Set(input.agents ?? ORCHESTRATOR_AGENTS);
|
|
10603
|
+
const agents = ORCHESTRATOR_AGENTS.filter((a) => wanted.has(a) && available.has(a));
|
|
10604
|
+
if (agents.length === 0) {
|
|
10605
|
+
throw new OrchestratorError(
|
|
10606
|
+
"no_agents",
|
|
10607
|
+
input.agents ? `None of the selected agents (${input.agents.join(", ")}) is installed.` : "No agent CLI (claude, codex, agy) was found on PATH."
|
|
10608
|
+
);
|
|
10609
|
+
}
|
|
10610
|
+
if (input.planner && !available.has(input.planner)) {
|
|
10611
|
+
throw new OrchestratorError(
|
|
10612
|
+
"no_agents",
|
|
10613
|
+
`The chosen planner (${input.planner}) is not installed.`
|
|
10614
|
+
);
|
|
10615
|
+
}
|
|
10616
|
+
const planner = input.planner ?? pickPlanner(agents);
|
|
10617
|
+
const id = this.newId();
|
|
10618
|
+
let integrationBranch;
|
|
10619
|
+
try {
|
|
10620
|
+
integrationBranch = await this.git.run(() => workspace.createIntegration(id, head.commit));
|
|
10621
|
+
} catch (err) {
|
|
10622
|
+
throw new OrchestratorError("not_a_repository", errorText3(err));
|
|
10623
|
+
}
|
|
10624
|
+
const stamp = this.stamp();
|
|
10625
|
+
const run = {
|
|
10626
|
+
id,
|
|
10627
|
+
objective: input.objective,
|
|
10628
|
+
status: "planning",
|
|
10629
|
+
agents,
|
|
10630
|
+
planner,
|
|
10631
|
+
baseBranch: head.branch,
|
|
10632
|
+
baseCommit: head.commit,
|
|
10633
|
+
integrationBranch,
|
|
10634
|
+
tasks: [],
|
|
10635
|
+
createdAt: stamp,
|
|
10636
|
+
updatedAt: stamp
|
|
10637
|
+
};
|
|
10638
|
+
this.runs.unshift(run);
|
|
10639
|
+
this.trim();
|
|
10640
|
+
const active = {
|
|
10641
|
+
run,
|
|
10642
|
+
controller: new AbortController(),
|
|
10643
|
+
done: Promise.resolve()
|
|
10644
|
+
};
|
|
10645
|
+
this.active.set(id, active);
|
|
10646
|
+
this.changed(run);
|
|
10647
|
+
active.done = this.execute(active, input.maxTasks ?? ORCHESTRATOR_LIMITS.defaultMaxTasks).catch(() => void 0).finally(() => {
|
|
10648
|
+
this.active.delete(id);
|
|
10649
|
+
});
|
|
10650
|
+
return structuredClone(run);
|
|
10651
|
+
} finally {
|
|
10652
|
+
this.starting = false;
|
|
10653
|
+
}
|
|
10654
|
+
}
|
|
10655
|
+
get(id) {
|
|
10656
|
+
const run = this.find(id);
|
|
10657
|
+
return run ? structuredClone(run) : void 0;
|
|
10658
|
+
}
|
|
10659
|
+
list() {
|
|
10660
|
+
return this.runs.map((run) => structuredClone(run));
|
|
10661
|
+
}
|
|
10662
|
+
cancel(id) {
|
|
10663
|
+
const active = this.active.get(id);
|
|
10664
|
+
if (!active || !isActive(active.run)) return false;
|
|
10665
|
+
active.controller.abort();
|
|
10666
|
+
return true;
|
|
10667
|
+
}
|
|
10668
|
+
async apply(id) {
|
|
10669
|
+
const run = this.find(id);
|
|
10670
|
+
if (!run) throw new OrchestratorError("not_found", `No orchestrator run ${id}.`);
|
|
10671
|
+
if (run.status !== "done") {
|
|
10672
|
+
throw new OrchestratorError(
|
|
10673
|
+
"not_appliable",
|
|
10674
|
+
`The run is ${run.status}; only a finished run can be applied.`
|
|
10675
|
+
);
|
|
10676
|
+
}
|
|
10677
|
+
if (run.appliedAt) throw new OrchestratorError("not_appliable", "The run was already applied.");
|
|
10678
|
+
if (!run.integrationBranch || !run.baseBranch) {
|
|
10679
|
+
throw new OrchestratorError("not_appliable", "The run has no integration branch to apply.");
|
|
10680
|
+
}
|
|
10681
|
+
if (this.applying.has(id)) {
|
|
10682
|
+
throw new OrchestratorError("not_appliable", "The run is already being applied.");
|
|
10683
|
+
}
|
|
10684
|
+
const baseBranch = run.baseBranch;
|
|
10685
|
+
this.applying.add(id);
|
|
10686
|
+
try {
|
|
10687
|
+
let result;
|
|
10688
|
+
try {
|
|
10689
|
+
result = await this.git.run(() => this.options.workspace.apply(id, baseBranch));
|
|
10690
|
+
} catch (err) {
|
|
10691
|
+
result = { ok: false, reason: errorText3(err) };
|
|
10692
|
+
}
|
|
10693
|
+
if (!result.ok) throw new OrchestratorError("not_appliable", clip3(result.reason, 300));
|
|
10694
|
+
run.appliedAt = this.stamp();
|
|
10695
|
+
this.changed(run);
|
|
10696
|
+
return structuredClone(run);
|
|
10697
|
+
} finally {
|
|
10698
|
+
this.applying.delete(id);
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10701
|
+
async close() {
|
|
10702
|
+
const pending = [...this.active.values()];
|
|
10703
|
+
for (const active of pending) active.controller.abort();
|
|
10704
|
+
await Promise.all(pending.map((active) => active.done));
|
|
10705
|
+
await this.store.flush();
|
|
10706
|
+
}
|
|
10707
|
+
// --- Run lifecycle ---------------------------------------------------------------------------
|
|
10708
|
+
async execute(active, maxTasks) {
|
|
10709
|
+
const { run, controller } = active;
|
|
10710
|
+
try {
|
|
10711
|
+
run.tasks = await this.plan(active, maxTasks);
|
|
10712
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
10713
|
+
run.status = "running";
|
|
10714
|
+
this.changed(run);
|
|
10715
|
+
await this.runTasks(active);
|
|
10716
|
+
this.finish(run, controller.signal.aborted);
|
|
10717
|
+
} catch (err) {
|
|
10718
|
+
if (controller.signal.aborted || isAbortError(err)) {
|
|
10719
|
+
this.finish(run, true);
|
|
10720
|
+
} else {
|
|
10721
|
+
for (const task of run.tasks) this.endTask(task, "cancelled");
|
|
10722
|
+
run.status = "failed";
|
|
10723
|
+
run.error = errorText3(err);
|
|
10724
|
+
run.summary = `The run failed: ${run.error}`;
|
|
10725
|
+
this.changed(run);
|
|
10726
|
+
}
|
|
10727
|
+
} finally {
|
|
10728
|
+
try {
|
|
10729
|
+
await this.options.workspace.cleanup(run.id);
|
|
10730
|
+
} catch {
|
|
10731
|
+
}
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
async plan(active, maxTasks) {
|
|
10735
|
+
const { run } = active;
|
|
10736
|
+
const planner = run.planner;
|
|
10737
|
+
const promptInput = { objective: run.objective, agents: run.agents, maxTasks };
|
|
10738
|
+
const ask = async (prompt) => {
|
|
10739
|
+
const result = await this.options.runner.run({
|
|
10740
|
+
agent: planner,
|
|
10741
|
+
cwd: this.options.projectDir,
|
|
10742
|
+
prompt,
|
|
10743
|
+
mode: "plan",
|
|
10744
|
+
signal: active.controller.signal,
|
|
10745
|
+
timeoutMs: ORCHESTRATOR_LIMITS.planTimeoutMs,
|
|
10746
|
+
onOutput: (chunk) => this.output(run.id, "plan", chunk)
|
|
10747
|
+
});
|
|
10748
|
+
if (!result.ok) {
|
|
10749
|
+
throw new RunFailure(
|
|
10750
|
+
`The planner (${planner}) failed: ${result.error ?? "no reason given"}`
|
|
10751
|
+
);
|
|
10752
|
+
}
|
|
10753
|
+
return result;
|
|
10754
|
+
};
|
|
10755
|
+
this.output(run.id, "plan", { kind: "status", text: `Planning with ${planner}\u2026` });
|
|
10756
|
+
const first = await ask(buildPlanPrompt(promptInput));
|
|
10757
|
+
const parsed = parsePlan(first.text, { agents: run.agents, maxTasks });
|
|
10758
|
+
if (parsed.ok) return parsed.tasks;
|
|
10759
|
+
this.output(run.id, "plan", {
|
|
10760
|
+
kind: "status",
|
|
10761
|
+
text: `The plan was not usable (${parsed.error}); asking ${planner} to fix it.`
|
|
10762
|
+
});
|
|
10763
|
+
const second = await ask(buildRepairPrompt(promptInput, first.text, parsed.error));
|
|
10764
|
+
const repaired = parsePlan(second.text, { agents: run.agents, maxTasks });
|
|
10765
|
+
if (repaired.ok) return repaired.tasks;
|
|
10766
|
+
throw new RunFailure(`The planner did not return a usable plan: ${repaired.error}`);
|
|
10767
|
+
}
|
|
10768
|
+
async runTasks(active) {
|
|
10769
|
+
const { run, controller } = active;
|
|
10770
|
+
const running = /* @__PURE__ */ new Map();
|
|
10771
|
+
const busyAgents = /* @__PURE__ */ new Set();
|
|
10772
|
+
while (!controller.signal.aborted) {
|
|
10773
|
+
this.skipBlocked(run);
|
|
10774
|
+
for (const task of run.tasks) {
|
|
10775
|
+
if (running.size >= ORCHESTRATOR_LIMITS.maxParallel) break;
|
|
10776
|
+
if (task.status !== "pending" || busyAgents.has(task.agent)) continue;
|
|
10777
|
+
if (!task.dependsOn.every((dep) => this.taskById(run, dep)?.status === "done")) continue;
|
|
10778
|
+
busyAgents.add(task.agent);
|
|
10779
|
+
const work = this.runTask(active, task).finally(() => {
|
|
10780
|
+
running.delete(task.id);
|
|
10781
|
+
busyAgents.delete(task.agent);
|
|
10782
|
+
});
|
|
10783
|
+
running.set(task.id, work);
|
|
10784
|
+
}
|
|
10785
|
+
if (running.size === 0) break;
|
|
10786
|
+
await Promise.race(running.values());
|
|
10787
|
+
}
|
|
10788
|
+
await Promise.all(running.values());
|
|
10789
|
+
for (const task of run.tasks) {
|
|
10790
|
+
if (task.status !== "pending") continue;
|
|
10791
|
+
if (controller.signal.aborted) this.endTask(task, "cancelled");
|
|
10792
|
+
else this.endTask(task, "skipped", "Its dependencies never finished.");
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
/** Pending tasks that depend on a task that ended without `done` will never run. */
|
|
10796
|
+
skipBlocked(run) {
|
|
10797
|
+
let changed = true;
|
|
10798
|
+
while (changed) {
|
|
10799
|
+
changed = false;
|
|
10800
|
+
for (const task of run.tasks) {
|
|
10801
|
+
if (task.status !== "pending") continue;
|
|
10802
|
+
const blocker = task.dependsOn.map((dep) => this.taskById(run, dep)).find(
|
|
10803
|
+
(dep) => dep && dep.status !== "done" && dep.status !== "pending" && dep.status !== "running"
|
|
10804
|
+
);
|
|
10805
|
+
if (!blocker) continue;
|
|
10806
|
+
this.endTask(
|
|
10807
|
+
task,
|
|
10808
|
+
"skipped",
|
|
10809
|
+
`Depends on ${blocker.id} (${blocker.title}), which ended ${blocker.status}.`
|
|
10810
|
+
);
|
|
10811
|
+
this.changed(run);
|
|
10812
|
+
changed = true;
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
}
|
|
10816
|
+
/** Never rejects: every outcome ends up on the task. */
|
|
10817
|
+
async runTask(active, task) {
|
|
10818
|
+
const { run, controller } = active;
|
|
10819
|
+
const { workspace, runner } = this.options;
|
|
10820
|
+
const signal = controller.signal;
|
|
10821
|
+
task.status = "running";
|
|
10822
|
+
task.startedAt = this.stamp();
|
|
10823
|
+
this.changed(run);
|
|
10824
|
+
try {
|
|
10825
|
+
const ws = await this.git.run(
|
|
10826
|
+
() => workspace.createTask(run.id, task.id, run.integrationBranch)
|
|
10827
|
+
);
|
|
10828
|
+
task.branch = ws.branch;
|
|
10829
|
+
if (signal.aborted) throw signal.reason;
|
|
10830
|
+
this.changed(run);
|
|
10831
|
+
this.output(run.id, task.id, {
|
|
10832
|
+
kind: "status",
|
|
10833
|
+
text: `Starting ${task.agent} on ${ws.branch}`
|
|
10834
|
+
});
|
|
10835
|
+
const result = await runner.run({
|
|
10836
|
+
agent: task.agent,
|
|
10837
|
+
cwd: ws.path,
|
|
10838
|
+
prompt: buildTaskPrompt({ objective: run.objective, task, tasks: run.tasks }),
|
|
10839
|
+
mode: "edit",
|
|
10840
|
+
signal,
|
|
10841
|
+
timeoutMs: ORCHESTRATOR_LIMITS.taskTimeoutMs,
|
|
10842
|
+
onOutput: (chunk) => this.output(run.id, task.id, chunk)
|
|
10843
|
+
});
|
|
10844
|
+
if (signal.aborted) throw signal.reason;
|
|
10845
|
+
if (result.text.trim())
|
|
10846
|
+
task.summary = clip3(result.text, ORCHESTRATOR_LIMITS.summaryMaxLength);
|
|
10847
|
+
if (!result.ok) {
|
|
10848
|
+
this.endTask(task, "failed", result.error ?? `${task.agent} did not finish the task.`);
|
|
10849
|
+
return;
|
|
10850
|
+
}
|
|
10851
|
+
const files = await this.git.run(() => workspace.commitTask(ws, `davecode: ${task.title}`));
|
|
10852
|
+
task.changedFiles = files;
|
|
10853
|
+
if (files.length > 0) {
|
|
10854
|
+
const merged = await this.git.run(
|
|
10855
|
+
() => workspace.mergeIntoIntegration(
|
|
10856
|
+
run.id,
|
|
10857
|
+
ws.branch,
|
|
10858
|
+
`davecode: merge ${task.id} ${task.title}`
|
|
10859
|
+
)
|
|
10860
|
+
);
|
|
10861
|
+
if (!merged.ok) {
|
|
10862
|
+
this.endTask(task, "conflict", merged.reason);
|
|
10863
|
+
return;
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
this.endTask(task, "done");
|
|
10867
|
+
} catch (err) {
|
|
10868
|
+
if (signal.aborted || isAbortError(err)) this.endTask(task, "cancelled");
|
|
10869
|
+
else this.endTask(task, "failed", errorText3(err));
|
|
10870
|
+
} finally {
|
|
10871
|
+
this.changed(run);
|
|
10872
|
+
}
|
|
10873
|
+
}
|
|
10874
|
+
/** Final status and deterministic summary. */
|
|
10875
|
+
finish(run, cancelled) {
|
|
10876
|
+
for (const task of run.tasks) {
|
|
10877
|
+
if (task.status === "pending" || task.status === "running") this.endTask(task, "cancelled");
|
|
10878
|
+
}
|
|
10879
|
+
const total = run.tasks.length;
|
|
10880
|
+
const done = run.tasks.filter((t) => t.status === "done").length;
|
|
10881
|
+
const notes = run.tasks.filter((t) => t.status !== "done").map((t) => describeOutcome(t));
|
|
10882
|
+
const where = run.integrationBranch ?? "the integration branch";
|
|
10883
|
+
if (cancelled) {
|
|
10884
|
+
run.status = "cancelled";
|
|
10885
|
+
run.summary = total === 0 ? "Cancelled while planning." : `Cancelled: ${done} of ${total} tasks had finished on ${where}.`;
|
|
10886
|
+
} else if (done > 0) {
|
|
10887
|
+
run.status = "done";
|
|
10888
|
+
const tail2 = notes.length > 0 ? ` ${notes.join("; ")}.` : "";
|
|
10889
|
+
run.summary = `${done} of ${total} tasks finished on ${where}.${tail2} Apply it to merge into ${run.baseBranch ?? "the base branch"}.`;
|
|
10890
|
+
} else {
|
|
10891
|
+
run.status = "failed";
|
|
10892
|
+
run.error = "No task finished.";
|
|
10893
|
+
run.summary = `None of the ${total} tasks finished. ${notes.join("; ")}.`;
|
|
10894
|
+
}
|
|
10895
|
+
this.changed(run);
|
|
10896
|
+
}
|
|
10897
|
+
endTask(task, status, error) {
|
|
10898
|
+
if (task.status !== "pending" && task.status !== "running") return;
|
|
10899
|
+
task.status = status;
|
|
10900
|
+
if (error) task.error = clip3(error, ERROR_MAX_LENGTH);
|
|
10901
|
+
if (task.startedAt) task.finishedAt = this.stamp();
|
|
10902
|
+
}
|
|
10903
|
+
// --- Helpers ---------------------------------------------------------------------------------
|
|
10904
|
+
assertIdle() {
|
|
10905
|
+
const current = [...this.active.values()].find((a) => isActive(a.run));
|
|
10906
|
+
if (current || this.starting) {
|
|
10907
|
+
throw new OrchestratorError(
|
|
10908
|
+
"busy",
|
|
10909
|
+
current ? `Run ${current.run.id} is still ${current.run.status}; cancel it or wait for it to end.` : "Another run is starting."
|
|
10910
|
+
);
|
|
10911
|
+
}
|
|
10912
|
+
}
|
|
10913
|
+
recoverInterrupted() {
|
|
10914
|
+
const interrupted = this.runs.filter(isActive);
|
|
10915
|
+
if (interrupted.length === 0) return;
|
|
10916
|
+
const stamp = this.stamp();
|
|
10917
|
+
for (const run of interrupted) {
|
|
10918
|
+
for (const task of run.tasks) {
|
|
10919
|
+
if (task.status === "running") {
|
|
10920
|
+
task.status = "failed";
|
|
10921
|
+
task.error = INTERRUPTED_ERROR;
|
|
10922
|
+
task.finishedAt = stamp;
|
|
10923
|
+
} else if (task.status === "pending") {
|
|
10924
|
+
task.status = "cancelled";
|
|
10925
|
+
}
|
|
10926
|
+
}
|
|
10927
|
+
run.status = "failed";
|
|
10928
|
+
run.error = INTERRUPTED_ERROR;
|
|
10929
|
+
run.updatedAt = stamp;
|
|
10930
|
+
const { workspace } = this.options;
|
|
10931
|
+
Promise.resolve().then(() => workspace.cleanup(run.id)).catch(() => void 0);
|
|
10932
|
+
}
|
|
10933
|
+
void this.store.save(this.runs);
|
|
10934
|
+
}
|
|
10935
|
+
find(id) {
|
|
10936
|
+
return this.runs.find((run) => run.id === id);
|
|
10937
|
+
}
|
|
10938
|
+
taskById(run, id) {
|
|
10939
|
+
return run.tasks.find((task) => task.id === id);
|
|
10940
|
+
}
|
|
10941
|
+
stamp() {
|
|
10942
|
+
return this.now().toISOString();
|
|
10943
|
+
}
|
|
10944
|
+
/** Keeps the newest runs, never dropping an active one. */
|
|
10945
|
+
trim() {
|
|
10946
|
+
if (this.runs.length <= RUNS_KEPT) return;
|
|
10947
|
+
const keep = new Set(this.runs.slice(0, RUNS_KEPT));
|
|
10948
|
+
this.runs = this.runs.filter((run) => keep.has(run) || this.active.has(run.id));
|
|
10949
|
+
}
|
|
10950
|
+
/** Records a state change: bump `updatedAt`, persist, emit a snapshot. */
|
|
10951
|
+
changed(run) {
|
|
10952
|
+
run.updatedAt = this.stamp();
|
|
10953
|
+
void this.store.save(this.runs);
|
|
10954
|
+
this.options.events.emit({
|
|
10955
|
+
type: "orchestrator.run",
|
|
10956
|
+
run: structuredClone(run),
|
|
10957
|
+
ts: this.now().getTime()
|
|
10958
|
+
});
|
|
10959
|
+
}
|
|
10960
|
+
output(runId, taskId, chunk) {
|
|
10961
|
+
const text = chunk.text.length > OUTPUT_MAX_LENGTH ? chunk.text.slice(0, OUTPUT_MAX_LENGTH) : chunk.text;
|
|
10962
|
+
if (!text && chunk.kind !== "error") return;
|
|
10963
|
+
this.options.events.emit({
|
|
10964
|
+
type: "orchestrator.output",
|
|
10965
|
+
runId,
|
|
10966
|
+
taskId,
|
|
10967
|
+
output: { kind: chunk.kind, text },
|
|
10968
|
+
ts: this.now().getTime()
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10971
|
+
};
|
|
10972
|
+
function describeOutcome(task) {
|
|
10973
|
+
const reason = task.error ? `: ${clip3(task.error, 120)}` : "";
|
|
10974
|
+
switch (task.status) {
|
|
10975
|
+
case "conflict":
|
|
10976
|
+
return `${task.id} conflicts with the integration branch${reason}`;
|
|
10977
|
+
case "failed":
|
|
10978
|
+
return `${task.id} failed${reason}`;
|
|
10979
|
+
case "skipped":
|
|
10980
|
+
return `${task.id} was skipped${reason}`;
|
|
10981
|
+
case "cancelled":
|
|
10982
|
+
return `${task.id} was cancelled`;
|
|
10983
|
+
default:
|
|
10984
|
+
return `${task.id} is ${task.status}`;
|
|
10985
|
+
}
|
|
10986
|
+
}
|
|
10987
|
+
var SAFE_ID2 = /^[A-Za-z0-9_-]+$/;
|
|
10988
|
+
var INTEGRATION_DIR = "integration";
|
|
10989
|
+
var BRAIN_DIR2 = ".davecode";
|
|
10990
|
+
var FALLBACK_IDENTITY = ["-c", "user.name=DaveCode", "-c", "user.email=davecode@localhost"];
|
|
10991
|
+
function assertId(kind, value) {
|
|
10992
|
+
if (!SAFE_ID2.test(value)) {
|
|
10993
|
+
throw new GitError(`Invalid ${kind}: ${JSON.stringify(value)}`, [], null, "");
|
|
10994
|
+
}
|
|
10995
|
+
return value;
|
|
10996
|
+
}
|
|
10997
|
+
function lines(out) {
|
|
10998
|
+
return out.split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.trim().length > 0);
|
|
10999
|
+
}
|
|
11000
|
+
function nulList(out) {
|
|
11001
|
+
return out.split("\0").filter((f) => f.length > 0);
|
|
11002
|
+
}
|
|
11003
|
+
function errMessage(err) {
|
|
11004
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
11005
|
+
const text = raw.replace(/\s+/g, " ").trim();
|
|
11006
|
+
return text.length <= 300 ? text : `${text.slice(0, 299)}\u2026`;
|
|
11007
|
+
}
|
|
11008
|
+
function realPath2(p) {
|
|
11009
|
+
try {
|
|
11010
|
+
return realpathSync2.native(p);
|
|
11011
|
+
} catch {
|
|
11012
|
+
return path9.resolve(p);
|
|
11013
|
+
}
|
|
11014
|
+
}
|
|
11015
|
+
function samePath(a, b) {
|
|
11016
|
+
const na = realPath2(a);
|
|
11017
|
+
const nb = realPath2(b);
|
|
11018
|
+
return process.platform === "win32" ? na.toLowerCase() === nb.toLowerCase() : na === nb;
|
|
11019
|
+
}
|
|
11020
|
+
function isInside2(child, parent) {
|
|
11021
|
+
const rel = path9.relative(realPath2(parent), realPath2(child));
|
|
11022
|
+
return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
|
|
11023
|
+
}
|
|
11024
|
+
function createGitWorkspace(options) {
|
|
11025
|
+
const projectDir = path9.resolve(options.projectDir);
|
|
11026
|
+
const worktreesDir = path9.resolve(options.worktreesDir);
|
|
11027
|
+
const gitAt = (cwd) => new Git(cwd, { env: options.env });
|
|
11028
|
+
const repo = gitAt(projectDir);
|
|
11029
|
+
const runDir = (runId) => path9.join(worktreesDir, assertId("run id", runId));
|
|
11030
|
+
const integrationPath = (runId) => path9.join(runDir(runId), INTEGRATION_DIR);
|
|
11031
|
+
const integrationBranch = (runId) => `davecode/run-${assertId("run id", runId)}`;
|
|
11032
|
+
const taskBases = /* @__PURE__ */ new Map();
|
|
11033
|
+
let queue = Promise.resolve();
|
|
11034
|
+
const serial = (fn2) => {
|
|
11035
|
+
const next = queue.then(fn2, fn2);
|
|
11036
|
+
queue = next.catch(() => void 0);
|
|
11037
|
+
return next;
|
|
11038
|
+
};
|
|
11039
|
+
let hasIdentity = false;
|
|
11040
|
+
async function identityArgs(g) {
|
|
11041
|
+
if (hasIdentity) return [];
|
|
11042
|
+
try {
|
|
11043
|
+
await g.run(["var", "GIT_AUTHOR_IDENT"]);
|
|
11044
|
+
await g.run(["var", "GIT_COMMITTER_IDENT"]);
|
|
11045
|
+
hasIdentity = true;
|
|
11046
|
+
return [];
|
|
11047
|
+
} catch {
|
|
11048
|
+
return FALLBACK_IDENTITY;
|
|
11049
|
+
}
|
|
11050
|
+
}
|
|
11051
|
+
async function removeWorktree(target) {
|
|
11052
|
+
try {
|
|
11053
|
+
await repo.run(["worktree", "remove", "--force", target]);
|
|
11054
|
+
} catch {
|
|
11055
|
+
}
|
|
11056
|
+
if (existsSync5(target)) {
|
|
11057
|
+
rmSync5(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
11058
|
+
}
|
|
11059
|
+
try {
|
|
11060
|
+
await repo.run(["worktree", "prune"]);
|
|
11061
|
+
} catch {
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
async function addWorktree(target, branch, base) {
|
|
11065
|
+
const args = ["worktree", "add", "-B", assertSafeRef(branch), target, assertSafeRef(base)];
|
|
11066
|
+
try {
|
|
11067
|
+
await repo.run(args);
|
|
11068
|
+
} catch {
|
|
11069
|
+
await removeWorktree(target);
|
|
11070
|
+
await repo.run(args);
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
11073
|
+
async function merge(g, branch, message, noVerify) {
|
|
11074
|
+
const args = [...await identityArgs(g), "merge", "--no-ff", "--no-edit", "-m", message];
|
|
11075
|
+
if (noVerify) args.push("--no-verify");
|
|
11076
|
+
args.push(assertSafeRef(branch));
|
|
11077
|
+
try {
|
|
11078
|
+
await g.run(args);
|
|
11079
|
+
return { ok: true };
|
|
11080
|
+
} catch (err) {
|
|
11081
|
+
let conflicts = [];
|
|
11082
|
+
try {
|
|
11083
|
+
conflicts = nulList(await g.run(["diff", "--name-only", "-z", "--diff-filter=U"]));
|
|
11084
|
+
} catch {
|
|
11085
|
+
}
|
|
11086
|
+
await g.abortMerge();
|
|
11087
|
+
const reason = conflicts.length > 0 ? `Conflicts in ${conflicts.join(", ")}` : `Merge failed: ${errMessage(err)}`;
|
|
11088
|
+
return { ok: false, reason };
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
return {
|
|
11092
|
+
async check() {
|
|
11093
|
+
if (!await repo.isRepo()) {
|
|
11094
|
+
return {
|
|
11095
|
+
ok: false,
|
|
11096
|
+
reason: "This folder is not a git repository. Run `git init` and commit once to orchestrate agents."
|
|
11097
|
+
};
|
|
11098
|
+
}
|
|
11099
|
+
let commit;
|
|
11100
|
+
try {
|
|
11101
|
+
commit = (await repo.run(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])).trim();
|
|
11102
|
+
} catch {
|
|
11103
|
+
return {
|
|
11104
|
+
ok: false,
|
|
11105
|
+
reason: "This repository has no commits yet. Commit once to orchestrate agents."
|
|
11106
|
+
};
|
|
11107
|
+
}
|
|
11108
|
+
let branch;
|
|
11109
|
+
try {
|
|
11110
|
+
branch = (await repo.run(["symbolic-ref", "--quiet", "--short", "HEAD"])).trim();
|
|
11111
|
+
} catch {
|
|
11112
|
+
return {
|
|
11113
|
+
ok: false,
|
|
11114
|
+
reason: "HEAD is detached. Check out a branch to orchestrate agents."
|
|
11115
|
+
};
|
|
11116
|
+
}
|
|
11117
|
+
return { ok: true, branch, commit };
|
|
11118
|
+
},
|
|
11119
|
+
createIntegration(runId, baseCommit) {
|
|
11120
|
+
return serial(async () => {
|
|
11121
|
+
const branch = integrationBranch(runId);
|
|
11122
|
+
await addWorktree(integrationPath(runId), branch, baseCommit);
|
|
11123
|
+
return branch;
|
|
11124
|
+
});
|
|
11125
|
+
},
|
|
11126
|
+
createTask(runId, taskId, base) {
|
|
11127
|
+
return serial(async () => {
|
|
11128
|
+
assertId("task id", taskId);
|
|
11129
|
+
if (taskId === INTEGRATION_DIR) {
|
|
11130
|
+
throw new GitError(`Invalid task id: ${JSON.stringify(taskId)}`, [], null, "");
|
|
11131
|
+
}
|
|
11132
|
+
const branch = `${integrationBranch(runId)}-${taskId}`;
|
|
11133
|
+
const target = path9.join(runDir(runId), taskId);
|
|
11134
|
+
const baseCommit = (await repo.run(["rev-parse", "--verify", "--quiet", `${assertSafeRef(base)}^{commit}`])).trim();
|
|
11135
|
+
await addWorktree(target, branch, baseCommit);
|
|
11136
|
+
taskBases.set(path9.resolve(target), baseCommit);
|
|
11137
|
+
return { path: target, branch };
|
|
11138
|
+
});
|
|
11139
|
+
},
|
|
11140
|
+
commitTask(workspace, message) {
|
|
11141
|
+
return serial(async () => {
|
|
11142
|
+
const g = gitAt(workspace.path);
|
|
11143
|
+
await g.run(["add", "-A"]);
|
|
11144
|
+
await g.run(["reset", "-q", "--", BRAIN_DIR2]);
|
|
11145
|
+
const committed = await g.hasStagedChanges();
|
|
11146
|
+
if (committed) {
|
|
11147
|
+
await g.run([
|
|
11148
|
+
...await identityArgs(g),
|
|
11149
|
+
"commit",
|
|
11150
|
+
"--quiet",
|
|
11151
|
+
"--no-verify",
|
|
11152
|
+
"-m",
|
|
11153
|
+
message
|
|
11154
|
+
]);
|
|
11155
|
+
}
|
|
11156
|
+
const base = taskBases.get(path9.resolve(workspace.path));
|
|
11157
|
+
if (base) return nulList(await g.run(["diff", "--name-only", "-z", base, "HEAD"]));
|
|
11158
|
+
if (!committed) return [];
|
|
11159
|
+
return nulList(await g.run(["diff", "--name-only", "-z", "HEAD~1", "HEAD"]));
|
|
11160
|
+
});
|
|
11161
|
+
},
|
|
11162
|
+
mergeIntoIntegration(runId, branch, message) {
|
|
11163
|
+
return serial(async () => {
|
|
11164
|
+
const target = integrationPath(runId);
|
|
11165
|
+
if (!existsSync5(target)) {
|
|
11166
|
+
return { ok: false, reason: `The integration worktree for run ${runId} is missing` };
|
|
11167
|
+
}
|
|
11168
|
+
return merge(gitAt(target), branch, message, true);
|
|
11169
|
+
});
|
|
11170
|
+
},
|
|
11171
|
+
apply(runId, baseBranch) {
|
|
11172
|
+
return serial(async () => {
|
|
11173
|
+
const branch = integrationBranch(runId);
|
|
11174
|
+
if (!await repo.branchExists(branch)) {
|
|
11175
|
+
return { ok: false, reason: `Branch ${branch} does not exist` };
|
|
11176
|
+
}
|
|
11177
|
+
let current;
|
|
11178
|
+
try {
|
|
11179
|
+
current = (await repo.run(["symbolic-ref", "--quiet", "--short", "HEAD"])).trim();
|
|
11180
|
+
} catch {
|
|
11181
|
+
current = "a detached HEAD";
|
|
11182
|
+
}
|
|
11183
|
+
if (current !== baseBranch) {
|
|
11184
|
+
return {
|
|
11185
|
+
ok: false,
|
|
11186
|
+
reason: `Check out ${baseBranch} first: the run started there (you are on ${current}).`
|
|
11187
|
+
};
|
|
11188
|
+
}
|
|
11189
|
+
const dirty = lines(
|
|
11190
|
+
await repo.run(["status", "--porcelain", "--untracked-files=no"])
|
|
11191
|
+
).filter((l) => {
|
|
11192
|
+
const file = l.slice(3).replace(/^"|"$/g, "");
|
|
11193
|
+
return !(file === BRAIN_DIR2 || file.startsWith(`${BRAIN_DIR2}/`));
|
|
11194
|
+
});
|
|
11195
|
+
if (dirty.length > 0) {
|
|
11196
|
+
return { ok: false, reason: "Commit or stash your changes first." };
|
|
11197
|
+
}
|
|
11198
|
+
return merge(repo, branch, `davecode: apply run ${runId}`, false);
|
|
11199
|
+
});
|
|
11200
|
+
},
|
|
11201
|
+
cleanup(runId) {
|
|
11202
|
+
return serial(async () => {
|
|
11203
|
+
let dir;
|
|
11204
|
+
try {
|
|
11205
|
+
dir = runDir(runId);
|
|
11206
|
+
} catch {
|
|
11207
|
+
return;
|
|
11208
|
+
}
|
|
11209
|
+
try {
|
|
11210
|
+
const listed = lines(await repo.run(["worktree", "list", "--porcelain"])).filter((l) => l.startsWith("worktree ")).map((l) => l.slice("worktree ".length)).filter((p) => isInside2(p, dir) && !samePath(p, projectDir));
|
|
11211
|
+
for (const target of listed) {
|
|
11212
|
+
try {
|
|
11213
|
+
await repo.run(["worktree", "remove", "--force", target]);
|
|
11214
|
+
} catch {
|
|
11215
|
+
}
|
|
11216
|
+
taskBases.delete(path9.resolve(target));
|
|
11217
|
+
}
|
|
11218
|
+
} catch {
|
|
11219
|
+
}
|
|
11220
|
+
try {
|
|
11221
|
+
rmSync5(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
11222
|
+
} catch {
|
|
11223
|
+
}
|
|
11224
|
+
try {
|
|
11225
|
+
await repo.run(["worktree", "prune"]);
|
|
11226
|
+
} catch {
|
|
11227
|
+
}
|
|
11228
|
+
});
|
|
11229
|
+
}
|
|
11230
|
+
};
|
|
11231
|
+
}
|
|
9695
11232
|
|
|
9696
11233
|
export {
|
|
9697
11234
|
ProviderError,
|
|
@@ -9889,6 +11426,13 @@ export {
|
|
|
9889
11426
|
goalSchema,
|
|
9890
11427
|
isGoalId,
|
|
9891
11428
|
GoalStore,
|
|
9892
|
-
createGoalService
|
|
11429
|
+
createGoalService,
|
|
11430
|
+
ORCHESTRATOR_AGENTS,
|
|
11431
|
+
ORCHESTRATOR_LIMITS,
|
|
11432
|
+
startRunSchema,
|
|
11433
|
+
OrchestratorError,
|
|
11434
|
+
createCliAgentRunner,
|
|
11435
|
+
createOrchestratorService,
|
|
11436
|
+
createGitWorkspace
|
|
9893
11437
|
};
|
|
9894
|
-
//# sourceMappingURL=chunk-
|
|
11438
|
+
//# sourceMappingURL=chunk-2XGDILCW.js.map
|