@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.
Files changed (84) hide show
  1. package/README.md +1 -0
  2. package/dist/{ChatApp-IF5D7XNH.js → ChatApp-RQZ2IZFQ.js} +6 -6
  3. package/dist/{account-KBXJ5KR7.js → account-FY6THFLW.js} +3 -3
  4. package/dist/{accounts-XUJNDCFH.js → accounts-ZV3KMLC5.js} +5 -5
  5. package/dist/{budget-PHV4NRUS.js → budget-OUOEMMOM.js} +4 -4
  6. package/dist/{chat-HGTX76DU.js → chat-OHVP4BS3.js} +8 -8
  7. package/dist/{chunk-AFCQURO5.js → chunk-2MM5WT5J.js} +3 -3
  8. package/dist/{chunk-EBV5A33R.js → chunk-2P5DJWIJ.js} +4 -4
  9. package/dist/{chunk-ING5HCIO.js → chunk-2XGDILCW.js} +1677 -133
  10. package/dist/chunk-2XGDILCW.js.map +1 -0
  11. package/dist/{chunk-FZJNOG2T.js → chunk-FFB2QA5I.js} +2 -2
  12. package/dist/{goal-ETZWLVKO.js → chunk-HL4RIXPM.js} +8 -8
  13. package/dist/chunk-HL4RIXPM.js.map +1 -0
  14. package/dist/{chunk-FPSLYBLU.js → chunk-L7NEDOA3.js} +2 -2
  15. package/dist/{chunk-DFQW3MRD.js → chunk-NKRI6SYR.js} +2 -2
  16. package/dist/{chunk-YSD6W27K.js → chunk-TP3ORSHY.js} +134 -4
  17. package/dist/chunk-TP3ORSHY.js.map +1 -0
  18. package/dist/{chunk-ZVNX7CSO.js → chunk-U5CI774J.js} +3 -3
  19. package/dist/{config-DG2E4VDW.js → config-ZLO3YOUX.js} +3 -3
  20. package/dist/dashboard/assets/Accounts-CIhOt9FH.js +1 -0
  21. package/dist/dashboard/assets/{Callout-1E8Wsl9W.js → Callout-CqdcH6dC.js} +1 -1
  22. package/dist/dashboard/assets/Goals-CITP2GqG.js +1 -0
  23. package/dist/dashboard/assets/{Library-DZxBVUo_.js → Library-DnCIb76j.js} +1 -1
  24. package/dist/dashboard/assets/{Panes-C-TJekjs.js → Panes-DBqKYuaE.js} +3 -3
  25. package/dist/dashboard/assets/RoutesPage-BNVmKhyU.js +4 -0
  26. package/dist/dashboard/assets/Runner-KR35ZWUE.js +1 -0
  27. package/dist/dashboard/assets/Settings-B5qE-a5O.js +1 -0
  28. package/dist/dashboard/assets/Tasks-bJLfNJmS.js +2 -0
  29. package/dist/dashboard/assets/Traffic-CGfilrRx.js +1 -0
  30. package/dist/dashboard/assets/Workspace-Dc3cvct_.js +1 -0
  31. package/dist/dashboard/assets/format-328qIBHX.js +111 -0
  32. package/dist/dashboard/assets/index-C-bwzlsl.js +50 -0
  33. package/dist/dashboard/assets/index-D9FCfPmM.css +2 -0
  34. package/dist/dashboard/assets/task-errors-DK4-900l.js +1 -0
  35. package/dist/dashboard/index.html +3 -3
  36. package/dist/{dist-IPMBGFJD.js → dist-56V3DLJ4.js} +16 -2
  37. package/dist/{doctor-5APOWJ5D.js → doctor-DUOKUSJL.js} +3 -3
  38. package/dist/goal-WG37XHUY.js +26 -0
  39. package/dist/index.js +64 -31
  40. package/dist/index.js.map +1 -1
  41. package/dist/{init-YDLNA3GD.js → init-WW4XPFMM.js} +3 -3
  42. package/dist/orchestrate-7YQDBS3E.js +382 -0
  43. package/dist/orchestrate-7YQDBS3E.js.map +1 -0
  44. package/dist/{run-I6N7TLHV.js → run-QB4SGPEC.js} +3 -3
  45. package/dist/runtime.js +6 -2
  46. package/dist/{start-ECLELJGJ.js → start-U4RL377T.js} +3 -3
  47. package/dist/{status-QQPP67I4.js → status-GHVMGZI5.js} +4 -4
  48. package/dist/status-GHVMGZI5.js.map +1 -0
  49. package/dist/{tasks-RNMC2XFN.js → tasks-VTBJ4B2G.js} +4 -4
  50. package/package.json +4 -4
  51. package/dist/chunk-ING5HCIO.js.map +0 -1
  52. package/dist/chunk-YSD6W27K.js.map +0 -1
  53. package/dist/dashboard/assets/Accounts-DXiGsutB.js +0 -1
  54. package/dist/dashboard/assets/Badge-DUGKlx3Z.js +0 -55
  55. package/dist/dashboard/assets/Goals-kL4VAxw3.js +0 -1
  56. package/dist/dashboard/assets/RoutesPage-DWYm9tW8.js +0 -4
  57. package/dist/dashboard/assets/Runner-CmHBjECR.js +0 -1
  58. package/dist/dashboard/assets/Settings-BANphxva.js +0 -1
  59. package/dist/dashboard/assets/Tasks-cSx1PKyQ.js +0 -2
  60. package/dist/dashboard/assets/Traffic-DEAuM6jq.js +0 -1
  61. package/dist/dashboard/assets/Workspace-BQFjc1jH.js +0 -1
  62. package/dist/dashboard/assets/index-B8kld9xo.js +0 -104
  63. package/dist/dashboard/assets/index-BEmBgsbP.css +0 -2
  64. package/dist/dashboard/assets/task-errors-r-WYZ_S3.js +0 -1
  65. package/dist/goal-ETZWLVKO.js.map +0 -1
  66. /package/dist/{ChatApp-IF5D7XNH.js.map → ChatApp-RQZ2IZFQ.js.map} +0 -0
  67. /package/dist/{account-KBXJ5KR7.js.map → account-FY6THFLW.js.map} +0 -0
  68. /package/dist/{accounts-XUJNDCFH.js.map → accounts-ZV3KMLC5.js.map} +0 -0
  69. /package/dist/{budget-PHV4NRUS.js.map → budget-OUOEMMOM.js.map} +0 -0
  70. /package/dist/{chat-HGTX76DU.js.map → chat-OHVP4BS3.js.map} +0 -0
  71. /package/dist/{chunk-AFCQURO5.js.map → chunk-2MM5WT5J.js.map} +0 -0
  72. /package/dist/{chunk-EBV5A33R.js.map → chunk-2P5DJWIJ.js.map} +0 -0
  73. /package/dist/{chunk-FZJNOG2T.js.map → chunk-FFB2QA5I.js.map} +0 -0
  74. /package/dist/{chunk-FPSLYBLU.js.map → chunk-L7NEDOA3.js.map} +0 -0
  75. /package/dist/{chunk-DFQW3MRD.js.map → chunk-NKRI6SYR.js.map} +0 -0
  76. /package/dist/{chunk-ZVNX7CSO.js.map → chunk-U5CI774J.js.map} +0 -0
  77. /package/dist/{config-DG2E4VDW.js.map → config-ZLO3YOUX.js.map} +0 -0
  78. /package/dist/{dist-IPMBGFJD.js.map → dist-56V3DLJ4.js.map} +0 -0
  79. /package/dist/{doctor-5APOWJ5D.js.map → doctor-DUOKUSJL.js.map} +0 -0
  80. /package/dist/{status-QQPP67I4.js.map → goal-WG37XHUY.js.map} +0 -0
  81. /package/dist/{init-YDLNA3GD.js.map → init-WW4XPFMM.js.map} +0 -0
  82. /package/dist/{run-I6N7TLHV.js.map → run-QB4SGPEC.js.map} +0 -0
  83. /package/dist/{start-ECLELJGJ.js.map → start-U4RL377T.js.map} +0 -0
  84. /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(lines) {
358
+ function listSections(lines2) {
347
359
  const sections = [];
348
360
  let fence;
349
- lines.forEach((raw, index) => {
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: lines.length
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 lines = markdown.split("\n");
375
- const section = listSections(lines).find((s) => sameHeading(s.heading, heading2));
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 lines.slice(section.start + 1, section.end).map(stripCr).join("\n").trim();
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 lines = markdown.split("\n");
384
- const section = listSections(lines).find((s) => sameHeading(s.heading, heading2));
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 = lines.slice(section.end);
404
+ const after = lines2.slice(section.end);
393
405
  const replacement = after.length > 0 ? [...block, cr] : [...block, ""];
394
- return [...lines.slice(0, section.start), ...replacement, ...after].join("\n");
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 lines = [`id: ${task.id}`, `title: ${task.title}`, `status: ${task.status}`];
476
- if (task.attempts) lines.push(`attempts so far: ${task.attempts}`);
477
- if (task.branch) lines.push(`branch: ${task.branch}`);
478
- if (task.description) lines.push("", "Description:", task.description.trim());
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
- lines.push("", "Acceptance criteria:", ...task.acceptance.map((c) => `- ${c}`));
492
+ lines2.push("", "Acceptance criteria:", ...task.acceptance.map((c) => `- ${c}`));
481
493
  }
482
- if (task.notes) lines.push("", "Notes from previous attempts:", task.notes.trim());
494
+ if (task.notes) lines2.push("", "Notes from previous attempts:", task.notes.trim());
483
495
  if (task.dependsOn.length > 0) {
484
- lines.push("", "Dependencies:");
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
- lines.push(`- ${id}: (not found in the task graph)`);
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
- lines.push(`- ${dep.id}: ${dep.title} [${dep.status}]${note ? ` (${note})` : ""}`);
505
+ lines2.push(`- ${dep.id}: ${dep.title} [${dep.status}]${note ? ` (${note})` : ""}`);
494
506
  }
495
507
  }
496
- return lines.join("\n");
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 lines = body.split("\n").filter((l) => l !== LOG_MARKER);
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 < lines.length && removed < needed) {
512
- removed += lines[drop].length + 1;
523
+ while (drop < lines2.length && removed < needed) {
524
+ removed += lines2[drop].length + 1;
513
525
  drop++;
514
526
  }
515
- const kept = lines.slice(drop);
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((resolve5) => setTimeout(resolve5, ms));
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(path8, content) {
579
- await mkdir(dirname(path8), { recursive: true });
580
- const tmp = `${path8}.${process.pid}.${randomBytes2(4).toString("hex")}.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, path8);
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(path8) {
610
+ async function readFileIfExists(path10) {
599
611
  try {
600
- return await readFile(path8, "utf8");
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((resolve5) => setTimeout(resolve5, ms));
1032
- async function readLockInfo(path8) {
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(path8, "utf8"));
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(path8) {
1043
- const info = await readLockInfo(path8);
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(path8)).mtimeMs;
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(path8) {
1105
+ async function exists(path10) {
1094
1106
  try {
1095
- await access(path8);
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(path8) {
1155
- super(`Missing project brain file: ${path8} (run ProjectBrain.init first)`);
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 [path8, content] of files) {
1194
- if (!await exists(path8)) await atomicWriteFile(path8, content);
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(path8) {
1307
- const content = await readFileIfExists(path8);
1318
+ async readText(path10) {
1319
+ const content = await readFileIfExists(path10);
1308
1320
  if (content === void 0) {
1309
- throw new MissingBrainError(path8);
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 path8 = prefix ? `${prefix}.${key}` : key;
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, path8);
1539
+ const inner = walk(v, rule, path10);
1528
1540
  if (Object.keys(inner).length > 0) out[key] = inner;
1529
1541
  } else {
1530
- dropped.push(path8);
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(path8, project) {
1567
+ function readLayer(path10, project) {
1556
1568
  let raw;
1557
1569
  try {
1558
- raw = readFileSync(path8, "utf8");
1570
+ raw = readFileSync(path10, "utf8");
1559
1571
  } catch (err) {
1560
1572
  if (err.code === "ENOENT") return void 0;
1561
- throw new ConfigError(path8, `cannot read file: ${err.message}`, { cause: err });
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(path8, `malformed JSON: ${err.message}`, { cause: err });
1579
+ throw new ConfigError(path10, `malformed JSON: ${err.message}`, { cause: err });
1568
1580
  }
1569
- if (!isPlainObject(parsed)) throw new ConfigError(path8, "expected a JSON object");
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(path8, z4.prettifyError(result.error));
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 path8 = globalPaths(home).config;
1668
+ const path10 = globalPaths(home).config;
1657
1669
  return withFileLock(
1658
- `${path8}.lock`,
1670
+ `${path10}.lock`,
1659
1671
  async () => {
1660
1672
  let current = {};
1661
1673
  try {
1662
- const parsed = JSON.parse(await readFile3(path8, "utf8"));
1663
- if (!isPlainObject2(parsed)) throw new ConfigError(path8, "expected a JSON object");
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(path8, `cannot update this file: ${err.message}`, {
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(path8, z5.prettifyError(checked.error));
1684
- await atomicWriteFile(path8, `${JSON.stringify(next, null, 2)}
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: path8,
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, path8) {
1776
- return `${base.replace(/\/+$/, "")}/${path8.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(path8) {
2594
- return isShellSafeArg(path8) ? path8 : `"${path8}"`;
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((resolve5) => {
2671
+ const closed = new Promise((resolve6) => {
2660
2672
  child.once("error", (err) => {
2661
2673
  spawnError = err;
2662
- resolve5(null);
2674
+ resolve6(null);
2663
2675
  });
2664
- child.once("close", (code) => resolve5(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 abortError = () => new ProviderError("claude-cli request aborted", {
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 abortError();
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.5.3";
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 lines = [
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
- lines.push(`${head}: ${state}`);
4337
+ lines2.push(`${head}: ${state}`);
4326
4338
  }
4327
4339
  if (enabled.length > CONTEXT_ACCOUNTS) {
4328
- lines.push(`- \u2026and ${enabled.length - CONTEXT_ACCOUNTS} more accounts`);
4340
+ lines2.push(`- \u2026and ${enabled.length - CONTEXT_ACCOUNTS} more accounts`);
4329
4341
  }
4330
- return lines.join("\n");
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((resolve5) => {
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
- resolve5({ code: null, stdout: "" });
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
- resolve5({
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
- resolve5(timedOut ? { code: null, stdout: "", timedOut } : { code, stdout });
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(path8, options = {}) {
5687
- if (path8 !== ":memory:") mkdirSync5(dirname5(path8), { recursive: true });
5688
- const db = new BetterSqlite3(path8);
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 lines = text.split("\n");
6389
+ const lines2 = text.split("\n");
6378
6390
  const start = (args.offset ?? 1) - 1;
6379
- text = lines.slice(start, args.limit ? start + args.limit : void 0).join("\n");
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 lines = buffer.toString("utf8").split("\n");
6452
- for (let i = 0; i < lines.length && results.length < args.max_results; i++) {
6453
- const line = lines[i].replace(/\r$/, "");
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((resolve5, reject) => {
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
- resolve5(stdout);
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((resolve5, reject) => {
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())) : resolve5(stdout.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 lines = [
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
- lines.push("", `### ${step.name} output (\`${step.command}\`)`);
7439
- if (step.stdout.trim()) lines.push("stdout:", "```", step.stdout.trimEnd(), "```");
7440
- if (step.stderr.trim()) lines.push("stderr:", "```", step.stderr.trimEnd(), "```");
7441
- if (!step.stdout.trim() && !step.stderr.trim()) lines.push("(no output)");
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 lines.join("\n");
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((resolve5, reject) => {
7624
- started = resolve5;
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((resolve5) => {
7786
- const timer = setTimeout(resolve5, ms);
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
- resolve5();
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((resolve5) => this.pauseWaiters.push(resolve5));
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(path8) {
8294
- return `${this.service.url}${path8}`;
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(path8, body) {
8348
- return toSession(await this.post(path8, body), this.now());
8359
+ async session(path10, body) {
8360
+ return toSession(await this.post(path10, body), this.now());
8349
8361
  }
8350
- async post(path8, body) {
8362
+ async post(path10, body) {
8351
8363
  let response;
8352
8364
  try {
8353
- response = await this.fetch(`${this.service.url}/auth/v1/${path8}`, {
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((resolve5, reject) => {
8389
- finish = resolve5;
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((resolve5, reject) => {
8437
+ await new Promise((resolve6, reject) => {
8426
8438
  server.once("error", reject);
8427
- server.listen(0, "127.0.0.1", () => resolve5());
8439
+ server.listen(0, "127.0.0.1", () => resolve6());
8428
8440
  });
8429
8441
  const port = server.address().port;
8430
- const close = () => new Promise((resolve5) => {
8442
+ const close = () => new Promise((resolve6) => {
8431
8443
  server.closeAllConnections();
8432
- server.close(() => resolve5());
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 path8 = `${this.path}.lock`;
8585
+ const path10 = `${this.path}.lock`;
8574
8586
  const owner = `${process.pid}:${randomBytes8(8).toString("hex")}`;
8575
- mkdirSync7(dirname6(path8), { recursive: true });
8587
+ mkdirSync7(dirname6(path10), { recursive: true });
8576
8588
  const deadline = Date.now() + LOCK_WAIT_MS;
8577
8589
  for (; ; ) {
8578
8590
  try {
8579
- writeFileSync3(path8, owner, { flag: "wx", mode: 384 });
8591
+ writeFileSync3(path10, owner, { flag: "wx", mode: 384 });
8580
8592
  return () => {
8581
8593
  try {
8582
- if (readFileSync3(path8, "utf8") === owner) rmSync3(path8, { force: true });
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(path8).mtimeMs > LOCK_STALE_MS) rmSync3(path8, { force: true });
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((resolve5) => setTimeout(resolve5, LOCK_POLL_MS));
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 lines = [];
9159
+ const lines2 = [];
9148
9160
  for (const task of goal.tasks) {
9149
- lines.push(
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) lines.push(` notes: ${truncateHead(task.notes, 500).replace(/\n/g, " ")}`);
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
- lines.push(` output excerpt:
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
- ${lines.join("\n")}`,
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 path8 = join7(this.goalDir(goalId), `${taskId}.md`);
9653
- await atomicWriteFile(path8, markdown.endsWith("\n") ? markdown : `${markdown}
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, path8).split("\\").join("/");
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-ING5HCIO.js.map
11438
+ //# sourceMappingURL=chunk-2XGDILCW.js.map