@dmrlzz/davecode 0.6.0 → 0.7.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 (83) hide show
  1. package/dist/{ChatApp-RQZ2IZFQ.js → ChatApp-C23NVTME.js} +6 -6
  2. package/dist/{account-FY6THFLW.js → account-GG7UFSNY.js} +3 -3
  3. package/dist/{accounts-ZV3KMLC5.js → accounts-IPOGUMHF.js} +5 -5
  4. package/dist/{budget-OUOEMMOM.js → budget-7QJGPYPF.js} +4 -4
  5. package/dist/{chat-OHVP4BS3.js → chat-3K2KXS3E.js} +8 -8
  6. package/dist/{chunk-FFB2QA5I.js → chunk-3BTE76UD.js} +2 -2
  7. package/dist/{chunk-L7NEDOA3.js → chunk-4FE2YV5U.js} +2 -2
  8. package/dist/{chunk-TP3ORSHY.js → chunk-FKGZERYB.js} +157 -9
  9. package/dist/chunk-FKGZERYB.js.map +1 -0
  10. package/dist/{chunk-2MM5WT5J.js → chunk-JBX6J74G.js} +3 -3
  11. package/dist/{chunk-2P5DJWIJ.js → chunk-KEURMO75.js} +4 -4
  12. package/dist/{chunk-NKRI6SYR.js → chunk-LK2K73DC.js} +2 -2
  13. package/dist/{chunk-2XGDILCW.js → chunk-NBQIMB43.js} +2422 -1599
  14. package/dist/chunk-NBQIMB43.js.map +1 -0
  15. package/dist/{chunk-HL4RIXPM.js → chunk-PGZPSJS7.js} +3 -3
  16. package/dist/{chunk-U5CI774J.js → chunk-U42YONUN.js} +3 -3
  17. package/dist/{config-ZLO3YOUX.js → config-BFEQFGAR.js} +3 -3
  18. package/dist/dashboard/assets/Accounts-CUMFzGls.js +1 -0
  19. package/dist/dashboard/assets/{Callout-CqdcH6dC.js → Callout-DDtTG_BA.js} +1 -1
  20. package/dist/dashboard/assets/Goals-CBGiTkcP.js +1 -0
  21. package/dist/dashboard/assets/{Library-DnCIb76j.js → Library-CKWa3h0E.js} +1 -1
  22. package/dist/dashboard/assets/{Panes-DBqKYuaE.js → Panes-ziAz-CZa.js} +4 -4
  23. package/dist/dashboard/assets/RoutesPage-D2iW-KhM.js +4 -0
  24. package/dist/dashboard/assets/Runner-4e79FB1J.js +1 -0
  25. package/dist/dashboard/assets/Settings-CSae84S0.js +1 -0
  26. package/dist/dashboard/assets/Tasks-CnbZ5PvX.js +2 -0
  27. package/dist/dashboard/assets/Traffic-B12xKOUm.js +1 -0
  28. package/dist/dashboard/assets/Workspace-CMPHlw4f.js +1 -0
  29. package/dist/dashboard/assets/chevron-down-DT349Fth.js +1 -0
  30. package/dist/dashboard/assets/external-link-DCIUScea.js +1 -0
  31. package/dist/dashboard/assets/format-C7Q-B9fI.js +112 -0
  32. package/dist/dashboard/assets/index-4RIBvhr9.css +2 -0
  33. package/dist/dashboard/assets/index-z8TlFwRe.js +50 -0
  34. package/dist/dashboard/assets/{task-errors-DK4-900l.js → task-errors-3F3vbQ7k.js} +1 -1
  35. package/dist/dashboard/assets/x-UBQsC1rW.js +1 -0
  36. package/dist/dashboard/index.html +4 -3
  37. package/dist/{dist-56V3DLJ4.js → dist-VTC5E7Z5.js} +34 -2
  38. package/dist/{doctor-DUOKUSJL.js → doctor-JLFYQ3EP.js} +3 -3
  39. package/dist/{goal-WG37XHUY.js → goal-JM7QYTL2.js} +4 -4
  40. package/dist/index.js +32 -32
  41. package/dist/{init-WW4XPFMM.js → init-6K4EGMTV.js} +3 -3
  42. package/dist/{orchestrate-7YQDBS3E.js → orchestrate-QC5P6IUZ.js} +4 -4
  43. package/dist/{run-QB4SGPEC.js → run-ABQNQ722.js} +3 -3
  44. package/dist/runtime.js +2 -2
  45. package/dist/{start-U4RL377T.js → start-MSO7TGBX.js} +3 -3
  46. package/dist/{status-GHVMGZI5.js → status-U6C4LUCM.js} +4 -4
  47. package/dist/{tasks-VTBJ4B2G.js → tasks-HEVT67YM.js} +4 -4
  48. package/package.json +4 -4
  49. package/dist/chunk-2XGDILCW.js.map +0 -1
  50. package/dist/chunk-TP3ORSHY.js.map +0 -1
  51. package/dist/dashboard/assets/Accounts-CIhOt9FH.js +0 -1
  52. package/dist/dashboard/assets/Goals-CITP2GqG.js +0 -1
  53. package/dist/dashboard/assets/RoutesPage-BNVmKhyU.js +0 -4
  54. package/dist/dashboard/assets/Runner-KR35ZWUE.js +0 -1
  55. package/dist/dashboard/assets/Settings-B5qE-a5O.js +0 -1
  56. package/dist/dashboard/assets/Tasks-bJLfNJmS.js +0 -2
  57. package/dist/dashboard/assets/Traffic-CGfilrRx.js +0 -1
  58. package/dist/dashboard/assets/Workspace-Dc3cvct_.js +0 -1
  59. package/dist/dashboard/assets/format-328qIBHX.js +0 -111
  60. package/dist/dashboard/assets/index-C-bwzlsl.js +0 -50
  61. package/dist/dashboard/assets/index-D9FCfPmM.css +0 -2
  62. /package/dist/{ChatApp-RQZ2IZFQ.js.map → ChatApp-C23NVTME.js.map} +0 -0
  63. /package/dist/{account-FY6THFLW.js.map → account-GG7UFSNY.js.map} +0 -0
  64. /package/dist/{accounts-ZV3KMLC5.js.map → accounts-IPOGUMHF.js.map} +0 -0
  65. /package/dist/{budget-OUOEMMOM.js.map → budget-7QJGPYPF.js.map} +0 -0
  66. /package/dist/{chat-OHVP4BS3.js.map → chat-3K2KXS3E.js.map} +0 -0
  67. /package/dist/{chunk-FFB2QA5I.js.map → chunk-3BTE76UD.js.map} +0 -0
  68. /package/dist/{chunk-L7NEDOA3.js.map → chunk-4FE2YV5U.js.map} +0 -0
  69. /package/dist/{chunk-2MM5WT5J.js.map → chunk-JBX6J74G.js.map} +0 -0
  70. /package/dist/{chunk-2P5DJWIJ.js.map → chunk-KEURMO75.js.map} +0 -0
  71. /package/dist/{chunk-NKRI6SYR.js.map → chunk-LK2K73DC.js.map} +0 -0
  72. /package/dist/{chunk-HL4RIXPM.js.map → chunk-PGZPSJS7.js.map} +0 -0
  73. /package/dist/{chunk-U5CI774J.js.map → chunk-U42YONUN.js.map} +0 -0
  74. /package/dist/{config-ZLO3YOUX.js.map → config-BFEQFGAR.js.map} +0 -0
  75. /package/dist/{dist-56V3DLJ4.js.map → dist-VTC5E7Z5.js.map} +0 -0
  76. /package/dist/{doctor-DUOKUSJL.js.map → doctor-JLFYQ3EP.js.map} +0 -0
  77. /package/dist/{goal-WG37XHUY.js.map → goal-JM7QYTL2.js.map} +0 -0
  78. /package/dist/{init-WW4XPFMM.js.map → init-6K4EGMTV.js.map} +0 -0
  79. /package/dist/{orchestrate-7YQDBS3E.js.map → orchestrate-QC5P6IUZ.js.map} +0 -0
  80. /package/dist/{run-QB4SGPEC.js.map → run-ABQNQ722.js.map} +0 -0
  81. /package/dist/{start-U4RL377T.js.map → start-MSO7TGBX.js.map} +0 -0
  82. /package/dist/{status-GHVMGZI5.js.map → status-U6C4LUCM.js.map} +0 -0
  83. /package/dist/{tasks-VTBJ4B2G.js.map → tasks-HEVT67YM.js.map} +0 -0
@@ -120,21 +120,37 @@ import { mkdirSync as mkdirSync7, readFileSync as readFileSync3, rmSync as rmSyn
120
120
  import { dirname as dirname6, join as join6 } from "path";
121
121
  import { z as z9 } from "zod";
122
122
  import { z as z10 } from "zod";
123
- import { readdir as readdir3 } from "fs/promises";
124
- import { join as join7, relative as relative2 } from "path";
125
- import { z as z11 } from "zod";
126
- import { z as z12 } from "zod";
123
+ import { execFile as execFile2 } from "child_process";
124
+ import { createHash as createHash3, randomBytes as randomBytes9 } from "crypto";
125
+ import {
126
+ existsSync as existsSync5,
127
+ mkdirSync as mkdirSync8,
128
+ readdirSync,
129
+ readFileSync as readFileSync4,
130
+ realpathSync as realpathSync2,
131
+ rmSync as rmSync5,
132
+ statSync as statSync4,
133
+ writeFileSync as writeFileSync4
134
+ } from "fs";
135
+ import path9 from "path";
136
+ import { z as z13 } from "zod";
127
137
  import { existsSync as existsSync4, mkdtempSync, realpathSync, rmSync as rmSync4, statSync as statSync3 } from "fs";
128
138
  import { tmpdir } from "os";
129
139
  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";
140
+ import { z as z11 } from "zod";
141
+ import { z as z12 } from "zod";
135
142
  import { z as z14 } from "zod";
136
- import { existsSync as existsSync5, realpathSync as realpathSync2, rmSync as rmSync5 } from "fs";
137
- import path9 from "path";
143
+ import { readdir as readdir3 } from "fs/promises";
144
+ import { join as join7, relative as relative2 } from "path";
145
+ import { z as z15 } from "zod";
146
+ import { randomBytes as randomBytes10 } from "crypto";
147
+ import { z as z16 } from "zod";
148
+ import { createHash as createHash4 } from "crypto";
149
+ import { readFileSync as readFileSync5 } from "fs";
150
+ import { join as join8, resolve as resolve5 } from "path";
151
+ import { z as z17 } from "zod";
152
+ import { existsSync as existsSync6, realpathSync as realpathSync3, rmSync as rmSync6 } from "fs";
153
+ import path10 from "path";
138
154
  var credentials = z.object({ clientId: z.string().min(1), clientSecret: z.string().min(1) });
139
155
  var optionsSchema = z.object({
140
156
  origin: z.string().url().refine((value) => {
@@ -587,14 +603,14 @@ function projectPaths(root) {
587
603
  }
588
604
  var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
589
605
  var RETRYABLE_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES"]);
590
- async function atomicWriteFile(path10, content) {
591
- await mkdir(dirname(path10), { recursive: true });
592
- const tmp = `${path10}.${process.pid}.${randomBytes2(4).toString("hex")}.tmp`;
606
+ async function atomicWriteFile(path11, content) {
607
+ await mkdir(dirname(path11), { recursive: true });
608
+ const tmp = `${path11}.${process.pid}.${randomBytes2(4).toString("hex")}.tmp`;
593
609
  try {
594
610
  await writeFile(tmp, content, "utf8");
595
611
  for (let attempt = 0; ; attempt++) {
596
612
  try {
597
- await rename(tmp, path10);
613
+ await rename(tmp, path11);
598
614
  return;
599
615
  } catch (err) {
600
616
  const code = err.code;
@@ -607,9 +623,9 @@ async function atomicWriteFile(path10, content) {
607
623
  throw err;
608
624
  }
609
625
  }
610
- async function readFileIfExists(path10) {
626
+ async function readFileIfExists(path11) {
611
627
  try {
612
- return await readFile(path10, "utf8");
628
+ return await readFile(path11, "utf8");
613
629
  } catch (err) {
614
630
  if (err.code === "ENOENT") return void 0;
615
631
  throw err;
@@ -1041,9 +1057,9 @@ var LockTimeoutError = class extends Error {
1041
1057
  };
1042
1058
  var TRANSIENT_OPEN_ERRORS = ["EPERM", "EACCES", "EBUSY"];
1043
1059
  var sleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
1044
- async function readLockInfo(path10) {
1060
+ async function readLockInfo(path11) {
1045
1061
  try {
1046
- const parsed = JSON.parse(await readFile2(path10, "utf8"));
1062
+ const parsed = JSON.parse(await readFile2(path11, "utf8"));
1047
1063
  if (typeof parsed === "object" && parsed !== null && typeof parsed.ts === "number" && typeof parsed.pid === "number") {
1048
1064
  return parsed;
1049
1065
  }
@@ -1051,11 +1067,11 @@ async function readLockInfo(path10) {
1051
1067
  }
1052
1068
  return void 0;
1053
1069
  }
1054
- async function lockAge(path10) {
1055
- const info = await readLockInfo(path10);
1070
+ async function lockAge(path11) {
1071
+ const info = await readLockInfo(path11);
1056
1072
  if (info) return Date.now() - info.ts;
1057
1073
  try {
1058
- return Date.now() - (await stat2(path10)).mtimeMs;
1074
+ return Date.now() - (await stat2(path11)).mtimeMs;
1059
1075
  } catch {
1060
1076
  return void 0;
1061
1077
  }
@@ -1102,9 +1118,9 @@ async function withFileLock(lockPath, fn2, opts = {}) {
1102
1118
  }
1103
1119
  }
1104
1120
  }
1105
- async function exists(path10) {
1121
+ async function exists(path11) {
1106
1122
  try {
1107
- await access(path10);
1123
+ await access(path11);
1108
1124
  return true;
1109
1125
  } catch {
1110
1126
  return false;
@@ -1163,8 +1179,8 @@ var MissingBrainError = class extends Error {
1163
1179
  /** HTTP status hint and machine-readable code for the gateway's error handler. */
1164
1180
  statusCode = 409;
1165
1181
  code = "no_brain";
1166
- constructor(path10) {
1167
- super(`Missing project brain file: ${path10} (run ProjectBrain.init first)`);
1182
+ constructor(path11) {
1183
+ super(`Missing project brain file: ${path11} (run ProjectBrain.init first)`);
1168
1184
  this.name = "MissingBrainError";
1169
1185
  }
1170
1186
  };
@@ -1202,8 +1218,8 @@ var ProjectBrain = class _ProjectBrain {
1202
1218
  [brain.paths.architecture, architectureTemplate(name)],
1203
1219
  [brain.paths.taskGraph, serializeGraph(taskGraphTemplate())]
1204
1220
  ];
1205
- for (const [path10, content] of files) {
1206
- if (!await exists(path10)) await atomicWriteFile(path10, content);
1221
+ for (const [path11, content] of files) {
1222
+ if (!await exists(path11)) await atomicWriteFile(path11, content);
1207
1223
  }
1208
1224
  return brain;
1209
1225
  }
@@ -1315,10 +1331,10 @@ ${line}` : line);
1315
1331
  this.events?.emit({ type: "task.updated", task });
1316
1332
  return task;
1317
1333
  }
1318
- async readText(path10) {
1319
- const content = await readFileIfExists(path10);
1334
+ async readText(path11) {
1335
+ const content = await readFileIfExists(path11);
1320
1336
  if (content === void 0) {
1321
- throw new MissingBrainError(path10);
1337
+ throw new MissingBrainError(path11);
1322
1338
  }
1323
1339
  return content;
1324
1340
  }
@@ -1532,14 +1548,14 @@ function filterProjectLayer(layer) {
1532
1548
  const out = {};
1533
1549
  for (const [key, v] of Object.entries(value)) {
1534
1550
  const rule = Object.hasOwn(tree, key) ? tree[key] : void 0;
1535
- const path10 = prefix ? `${prefix}.${key}` : key;
1551
+ const path11 = prefix ? `${prefix}.${key}` : key;
1536
1552
  if (rule === true) {
1537
1553
  out[key] = v;
1538
1554
  } else if (rule && isPlainObject(v)) {
1539
- const inner = walk(v, rule, path10);
1555
+ const inner = walk(v, rule, path11);
1540
1556
  if (Object.keys(inner).length > 0) out[key] = inner;
1541
1557
  } else {
1542
- dropped.push(path10);
1558
+ dropped.push(path11);
1543
1559
  }
1544
1560
  }
1545
1561
  return out;
@@ -1564,21 +1580,21 @@ function deepMerge(base, override) {
1564
1580
  }
1565
1581
  return out;
1566
1582
  }
1567
- function readLayer(path10, project) {
1583
+ function readLayer(path11, project) {
1568
1584
  let raw;
1569
1585
  try {
1570
- raw = readFileSync(path10, "utf8");
1586
+ raw = readFileSync(path11, "utf8");
1571
1587
  } catch (err) {
1572
1588
  if (err.code === "ENOENT") return void 0;
1573
- throw new ConfigError(path10, `cannot read file: ${err.message}`, { cause: err });
1589
+ throw new ConfigError(path11, `cannot read file: ${err.message}`, { cause: err });
1574
1590
  }
1575
1591
  let parsed;
1576
1592
  try {
1577
1593
  parsed = JSON.parse(raw);
1578
1594
  } catch (err) {
1579
- throw new ConfigError(path10, `malformed JSON: ${err.message}`, { cause: err });
1595
+ throw new ConfigError(path11, `malformed JSON: ${err.message}`, { cause: err });
1580
1596
  }
1581
- if (!isPlainObject(parsed)) throw new ConfigError(path10, "expected a JSON object");
1597
+ if (!isPlainObject(parsed)) throw new ConfigError(path11, "expected a JSON object");
1582
1598
  let layer = parsed;
1583
1599
  if (project) {
1584
1600
  const { allowed, dropped } = filterProjectLayer(parsed);
@@ -1586,7 +1602,7 @@ function readLayer(path10, project) {
1586
1602
  layer = allowed;
1587
1603
  }
1588
1604
  const result = configSchema.safeParse(layer);
1589
- if (!result.success) throw new ConfigError(path10, z4.prettifyError(result.error));
1605
+ if (!result.success) throw new ConfigError(path11, z4.prettifyError(result.error));
1590
1606
  return layer;
1591
1607
  }
1592
1608
  var TRUE_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
@@ -1665,19 +1681,19 @@ function loadConfig(options = {}) {
1665
1681
  }
1666
1682
  var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1667
1683
  async function writeGlobalRouting(home, update, lock = {}) {
1668
- const path10 = globalPaths(home).config;
1684
+ const path11 = globalPaths(home).config;
1669
1685
  return withFileLock(
1670
- `${path10}.lock`,
1686
+ `${path11}.lock`,
1671
1687
  async () => {
1672
1688
  let current = {};
1673
1689
  try {
1674
- const parsed = JSON.parse(await readFile3(path10, "utf8"));
1675
- if (!isPlainObject2(parsed)) throw new ConfigError(path10, "expected a JSON object");
1690
+ const parsed = JSON.parse(await readFile3(path11, "utf8"));
1691
+ if (!isPlainObject2(parsed)) throw new ConfigError(path11, "expected a JSON object");
1676
1692
  current = parsed;
1677
1693
  } catch (err) {
1678
1694
  if (err instanceof ConfigError) throw err;
1679
1695
  if (!isNodeError(err, "ENOENT")) {
1680
- throw new ConfigError(path10, `cannot update this file: ${err.message}`, {
1696
+ throw new ConfigError(path11, `cannot update this file: ${err.message}`, {
1681
1697
  cause: err
1682
1698
  });
1683
1699
  }
@@ -1692,11 +1708,11 @@ async function writeGlobalRouting(home, update, lock = {}) {
1692
1708
  }
1693
1709
  };
1694
1710
  const checked = configSchema.safeParse(next);
1695
- if (!checked.success) throw new ConfigError(path10, z5.prettifyError(checked.error));
1696
- await atomicWriteFile(path10, `${JSON.stringify(next, null, 2)}
1711
+ if (!checked.success) throw new ConfigError(path11, z5.prettifyError(checked.error));
1712
+ await atomicWriteFile(path11, `${JSON.stringify(next, null, 2)}
1697
1713
  `);
1698
1714
  return {
1699
- path: path10,
1715
+ path: path11,
1700
1716
  routes: checked.data.routing.routes,
1701
1717
  defaultRoute: checked.data.routing.defaultRoute
1702
1718
  };
@@ -1784,8 +1800,8 @@ function makeUsage(prompt, completion) {
1784
1800
  function stripProviderPrefix(model, provider) {
1785
1801
  return model.startsWith(`${provider}/`) ? model.slice(provider.length + 1) : model;
1786
1802
  }
1787
- function joinUrl(base, path10) {
1788
- return `${base.replace(/\/+$/, "")}/${path10.replace(/^\/+/, "")}`;
1803
+ function joinUrl(base, path11) {
1804
+ return `${base.replace(/\/+$/, "")}/${path11.replace(/^\/+/, "")}`;
1789
1805
  }
1790
1806
  function configString(config, key) {
1791
1807
  const value = config[key];
@@ -2002,7 +2018,7 @@ function fraction(d) {
2002
2018
  }
2003
2019
  function limitsFromHeaders(headers, now = Date.now()) {
2004
2020
  const pick = (a, b) => a.remaining !== void 0 ? a : b;
2005
- const tokens = pick(
2021
+ const tokens2 = pick(
2006
2022
  pick(
2007
2023
  dimension(
2008
2024
  headers,
@@ -2049,16 +2065,16 @@ function limitsFromHeaders(headers, now = Date.now()) {
2049
2065
  now
2050
2066
  )
2051
2067
  );
2052
- if (tokens.remaining === void 0 && requests.remaining === void 0) return void 0;
2068
+ if (tokens2.remaining === void 0 && requests.remaining === void 0) return void 0;
2053
2069
  const out = {};
2054
- if (tokens.remaining !== void 0) out.tokensRemaining = tokens.remaining;
2055
- if (tokens.limit !== void 0) out.tokensLimit = tokens.limit;
2070
+ if (tokens2.remaining !== void 0) out.tokensRemaining = tokens2.remaining;
2071
+ if (tokens2.limit !== void 0) out.tokensLimit = tokens2.limit;
2056
2072
  if (requests.remaining !== void 0) out.requestsRemaining = requests.remaining;
2057
2073
  if (requests.limit !== void 0) out.requestsLimit = requests.limit;
2058
- const tighter = (fraction(tokens) ?? 1) <= (fraction(requests) ?? 1) ? tokens : requests;
2059
- const resetAt = tighter.resetAt ?? tokens.resetAt ?? requests.resetAt;
2074
+ const tighter = (fraction(tokens2) ?? 1) <= (fraction(requests) ?? 1) ? tokens2 : requests;
2075
+ const resetAt = tighter.resetAt ?? tokens2.resetAt ?? requests.resetAt;
2060
2076
  if (resetAt !== void 0) out.resetAt = resetAt;
2061
- if (tokens.remaining === 0 || requests.remaining === 0) out.exhausted = true;
2077
+ if (tokens2.remaining === 0 || requests.remaining === 0) out.exhausted = true;
2062
2078
  return out;
2063
2079
  }
2064
2080
  function reportedFraction(limits) {
@@ -2602,8 +2618,8 @@ var SHELL_SAFE = /^[\w.:=@+\-/\\[\]]+$/;
2602
2618
  function isShellSafeArg(arg) {
2603
2619
  return SHELL_SAFE.test(arg);
2604
2620
  }
2605
- function quoteCommand(path10) {
2606
- return isShellSafeArg(path10) ? path10 : `"${path10}"`;
2621
+ function quoteCommand(path11) {
2622
+ return isShellSafeArg(path11) ? path11 : `"${path11}"`;
2607
2623
  }
2608
2624
  function killTree(child) {
2609
2625
  if (child.pid === void 0 || child.exitCode !== null) return;
@@ -4047,7 +4063,7 @@ var SUBSCRIPTION_PROVIDER_KINDS = [
4047
4063
  "antigravity-cli",
4048
4064
  "gemini-web"
4049
4065
  ];
4050
- var VERSION = "0.6.0";
4066
+ var VERSION = "0.7.0";
4051
4067
  var QUOTA_WINDOWS = ["1m", "5h", "24h"];
4052
4068
  var WINDOW_MS = {
4053
4069
  "1m": 6e4,
@@ -4059,19 +4075,19 @@ var WINDOW_COUNT = QUOTA_WINDOWS.length;
4059
4075
  function limitsFor(window, limits) {
4060
4076
  const pick = (v) => v !== void 0 && v > 0 ? v : void 0;
4061
4077
  const out = {};
4062
- let tokens;
4078
+ let tokens2;
4063
4079
  let requests;
4064
4080
  if (window === "1m") {
4065
- tokens = pick(limits.tpm);
4081
+ tokens2 = pick(limits.tpm);
4066
4082
  requests = pick(limits.rpm);
4067
4083
  } else if (window === "5h") {
4068
- tokens = pick(limits.tokens5h);
4084
+ tokens2 = pick(limits.tokens5h);
4069
4085
  requests = pick(limits.requests5h);
4070
4086
  } else {
4071
- tokens = pick(limits.tokensDaily);
4087
+ tokens2 = pick(limits.tokensDaily);
4072
4088
  requests = pick(limits.requestsDaily);
4073
4089
  }
4074
- if (tokens !== void 0) out.tokenLimit = tokens;
4090
+ if (tokens2 !== void 0) out.tokenLimit = tokens2;
4075
4091
  if (requests !== void 0) out.requestLimit = requests;
4076
4092
  return out;
4077
4093
  }
@@ -4083,26 +4099,26 @@ var AccountSeries = class {
4083
4099
  heads = new Array(WINDOW_COUNT).fill(0);
4084
4100
  tokenSums = new Array(WINDOW_COUNT).fill(0);
4085
4101
  requestSums = new Array(WINDOW_COUNT).fill(0);
4086
- add(ts, tokens, requests) {
4102
+ add(ts, tokens2, requests) {
4087
4103
  const last = this.slotTs.length - 1;
4088
4104
  let slot = Math.floor(ts / SLOT_MS) * SLOT_MS;
4089
4105
  if (last >= 0 && slot < this.slotTs[last]) slot = this.slotTs[last];
4090
4106
  if (last >= 0 && this.slotTs[last] === slot) {
4091
- this.slotTokens[last] += tokens;
4107
+ this.slotTokens[last] += tokens2;
4092
4108
  this.slotRequests[last] += requests;
4093
4109
  for (let w = 0; w < WINDOW_COUNT; w++) {
4094
4110
  if (this.heads[w] <= last) {
4095
- this.tokenSums[w] += tokens;
4111
+ this.tokenSums[w] += tokens2;
4096
4112
  this.requestSums[w] += requests;
4097
4113
  }
4098
4114
  }
4099
4115
  return;
4100
4116
  }
4101
4117
  this.slotTs.push(slot);
4102
- this.slotTokens.push(tokens);
4118
+ this.slotTokens.push(tokens2);
4103
4119
  this.slotRequests.push(requests);
4104
4120
  for (let w = 0; w < WINDOW_COUNT; w++) {
4105
- this.tokenSums[w] += tokens;
4121
+ this.tokenSums[w] += tokens2;
4106
4122
  this.requestSums[w] += requests;
4107
4123
  }
4108
4124
  }
@@ -4152,13 +4168,13 @@ var QuotaEngine = class {
4152
4168
  constructor(options = {}) {
4153
4169
  this.clock = options.clock ?? Date.now;
4154
4170
  }
4155
- record(accountId, tokens, options = {}) {
4171
+ record(accountId, tokens2, options = {}) {
4156
4172
  let series = this.series.get(accountId);
4157
4173
  if (!series) {
4158
4174
  series = new AccountSeries();
4159
4175
  this.series.set(accountId, series);
4160
4176
  }
4161
- series.add(options.ts ?? this.clock(), Math.max(0, tokens), Math.max(0, options.requests ?? 1));
4177
+ series.add(options.ts ?? this.clock(), Math.max(0, tokens2), Math.max(0, options.requests ?? 1));
4162
4178
  series.advance(this.clock());
4163
4179
  }
4164
4180
  totals(accountId) {
@@ -4173,11 +4189,11 @@ var QuotaEngine = class {
4173
4189
  const totals = this.totals(account.id);
4174
4190
  const windows = {};
4175
4191
  QUOTA_WINDOWS.forEach((window, i) => {
4176
- const { tokens, requests } = totals[i];
4192
+ const { tokens: tokens2, requests } = totals[i];
4177
4193
  const limits = limitsFor(window, account.limits);
4178
- const usage = { window, tokens, requests, utilization: 0, ...limits };
4194
+ const usage = { window, tokens: tokens2, requests, utilization: 0, ...limits };
4179
4195
  usage.utilization = Math.max(
4180
- limits.tokenLimit ? tokens / limits.tokenLimit : 0,
4196
+ limits.tokenLimit ? tokens2 / limits.tokenLimit : 0,
4181
4197
  limits.requestLimit ? requests / limits.requestLimit : 0
4182
4198
  );
4183
4199
  windows[window] = usage;
@@ -4193,9 +4209,9 @@ var QuotaEngine = class {
4193
4209
  wouldExceed(account, estimatedTokens) {
4194
4210
  const totals = this.totals(account.id);
4195
4211
  return QUOTA_WINDOWS.some((window, i) => {
4196
- const { tokens, requests } = totals[i];
4212
+ const { tokens: tokens2, requests } = totals[i];
4197
4213
  const { tokenLimit, requestLimit } = limitsFor(window, account.limits);
4198
- if (tokenLimit !== void 0 && tokens + estimatedTokens > tokenLimit) return true;
4214
+ if (tokenLimit !== void 0 && tokens2 + estimatedTokens > tokenLimit) return true;
4199
4215
  if (requestLimit !== void 0 && requests + 1 > requestLimit) return true;
4200
4216
  return false;
4201
4217
  });
@@ -5193,19 +5209,19 @@ var Router = class {
5193
5209
  });
5194
5210
  }
5195
5211
  /** The caller aborted: record the attempt as cancelled without touching cooldowns. */
5196
- onCancelled(candidate, requestId, startedAt, tokens = { prompt: 0, completion: 0 }) {
5212
+ onCancelled(candidate, requestId, startedAt, tokens2 = { prompt: 0, completion: 0 }) {
5197
5213
  const latencyMs = Math.max(0, this.clock() - startedAt);
5198
5214
  this.breaker.onCancel(candidate.account.id);
5199
5215
  this.recordUsage(
5200
5216
  candidate,
5201
5217
  requestId,
5202
5218
  "cancelled",
5203
- tokens.prompt,
5204
- tokens.completion,
5219
+ tokens2.prompt,
5220
+ tokens2.completion,
5205
5221
  latencyMs
5206
5222
  );
5207
5223
  }
5208
- onFailure(candidate, requestId, error, startedAt, tokens = { prompt: 0, completion: 0 }) {
5224
+ onFailure(candidate, requestId, error, startedAt, tokens2 = { prompt: 0, completion: 0 }) {
5209
5225
  const { account } = candidate;
5210
5226
  const latencyMs = Math.max(0, this.clock() - startedAt);
5211
5227
  const rateLimited = error.kind === "rate_limit" || error.kind === "quota_exhausted";
@@ -5213,8 +5229,8 @@ var Router = class {
5213
5229
  candidate,
5214
5230
  requestId,
5215
5231
  rateLimited ? "rate_limited" : "error",
5216
- tokens.prompt,
5217
- tokens.completion,
5232
+ tokens2.prompt,
5233
+ tokens2.completion,
5218
5234
  latencyMs,
5219
5235
  error.kind
5220
5236
  );
@@ -5383,7 +5399,7 @@ var Router = class {
5383
5399
  if (chunk.usage) upstreamUsage = chunk.usage;
5384
5400
  for (const choice of chunk.choices) text += messageText(choice.delta);
5385
5401
  };
5386
- const tokens = () => {
5402
+ const tokens2 = () => {
5387
5403
  const has = upstreamUsage && (upstreamUsage.prompt_tokens > 0 || upstreamUsage.completion_tokens > 0);
5388
5404
  return has && upstreamUsage ? { prompt: upstreamUsage.prompt_tokens, completion: upstreamUsage.completion_tokens } : { prompt: estimate, completion: countTextTokens(text) };
5389
5405
  };
@@ -5405,9 +5421,9 @@ var Router = class {
5405
5421
  failed = true;
5406
5422
  const error = toProviderError2(err, candidate.account);
5407
5423
  if (signal?.aborted) {
5408
- this.onCancelled(candidate, meta.requestId, startedAt, tokens());
5424
+ this.onCancelled(candidate, meta.requestId, startedAt, tokens2());
5409
5425
  } else {
5410
- this.onFailure(candidate, meta.requestId, error, startedAt, tokens());
5426
+ this.onFailure(candidate, meta.requestId, error, startedAt, tokens2());
5411
5427
  }
5412
5428
  throw error;
5413
5429
  } finally {
@@ -5418,7 +5434,7 @@ var Router = class {
5418
5434
  }
5419
5435
  }
5420
5436
  if (!failed) {
5421
- const { prompt, completion } = tokens();
5437
+ const { prompt, completion } = tokens2();
5422
5438
  this.onSuccess(candidate, meta.requestId, prompt, completion, startedAt);
5423
5439
  }
5424
5440
  }
@@ -5695,9 +5711,9 @@ function migrate(db, migrations = MIGRATIONS) {
5695
5711
  }
5696
5712
  return version;
5697
5713
  }
5698
- function openDatabase(path10, options = {}) {
5699
- if (path10 !== ":memory:") mkdirSync5(dirname5(path10), { recursive: true });
5700
- const db = new BetterSqlite3(path10);
5714
+ function openDatabase(path11, options = {}) {
5715
+ if (path11 !== ":memory:") mkdirSync5(dirname5(path11), { recursive: true });
5716
+ const db = new BetterSqlite3(path11);
5701
5717
  try {
5702
5718
  db.pragma("journal_mode = WAL");
5703
5719
  db.pragma("foreign_keys = ON");
@@ -6899,7 +6915,7 @@ ${repairPrompt(runOpts.cycle, runOpts.failureReport)}`;
6899
6915
  }
6900
6916
  let resultText;
6901
6917
  let isError = false;
6902
- let tokens = 0;
6918
+ let tokens2 = 0;
6903
6919
  let turns = 0;
6904
6920
  try {
6905
6921
  for await (const raw of cli.events) {
@@ -6919,7 +6935,7 @@ ${repairPrompt(runOpts.cycle, runOpts.failureReport)}`;
6919
6935
  resultText = asString(raw.result) ?? "";
6920
6936
  isError = raw.is_error === true || (asString(raw.subtype) ?? "success") !== "success";
6921
6937
  const usage = isRecord(raw.usage) ? raw.usage : {};
6922
- tokens = (asNumber(usage.input_tokens) ?? 0) + (asNumber(usage.output_tokens) ?? 0) + (asNumber(usage.cache_creation_input_tokens) ?? 0);
6938
+ tokens2 = (asNumber(usage.input_tokens) ?? 0) + (asNumber(usage.output_tokens) ?? 0) + (asNumber(usage.cache_creation_input_tokens) ?? 0);
6923
6939
  }
6924
6940
  }
6925
6941
  } catch (err) {
@@ -6938,7 +6954,7 @@ ${repairPrompt(runOpts.cycle, runOpts.failureReport)}`;
6938
6954
  `Claude Code CLI timed out after ${opts.timeoutMs ?? 18e5} ms`
6939
6955
  );
6940
6956
  }
6941
- totalTokens += tokens;
6957
+ totalTokens += tokens2;
6942
6958
  if (exit.code !== 0 || isError || resultText === void 0) {
6943
6959
  const detail = redact(
6944
6960
  (isError && resultText ? resultText : exit.stderr || resultText || "").trim()
@@ -6951,7 +6967,7 @@ ${repairPrompt(runOpts.cycle, runOpts.failureReport)}`;
6951
6967
  summary: resultText.trim() || "Implemented by Claude Code.",
6952
6968
  stopReason: "completed",
6953
6969
  iterations: Math.max(1, turns),
6954
- tokens,
6970
+ tokens: tokens2,
6955
6971
  totalTokens,
6956
6972
  changedFiles: []
6957
6973
  };
@@ -8302,8 +8318,8 @@ var AccountClient = class {
8302
8318
  return url.toString();
8303
8319
  }
8304
8320
  /** A page of the hosted service, such as `/login/done`. */
8305
- pageUrl(path10) {
8306
- return `${this.service.url}${path10}`;
8321
+ pageUrl(path11) {
8322
+ return `${this.service.url}${path11}`;
8307
8323
  }
8308
8324
  /** Browser URL that starts Google or GitHub sign-in and returns to `redirectTo` with `?code=`. */
8309
8325
  authorizeUrl(provider, redirectTo, challenge) {
@@ -8356,13 +8372,13 @@ var AccountClient = class {
8356
8372
  signal: AbortSignal.timeout(1e4)
8357
8373
  }).catch(() => void 0);
8358
8374
  }
8359
- async session(path10, body) {
8360
- return toSession(await this.post(path10, body), this.now());
8375
+ async session(path11, body) {
8376
+ return toSession(await this.post(path11, body), this.now());
8361
8377
  }
8362
- async post(path10, body) {
8378
+ async post(path11, body) {
8363
8379
  let response;
8364
8380
  try {
8365
- response = await this.fetch(`${this.service.url}/auth/v1/${path10}`, {
8381
+ response = await this.fetch(`${this.service.url}/auth/v1/${path11}`, {
8366
8382
  method: "POST",
8367
8383
  headers: { apikey: this.service.publishableKey, "Content-Type": "application/json" },
8368
8384
  body: JSON.stringify(body),
@@ -8582,16 +8598,16 @@ var AccountStore = class {
8582
8598
  * refresh goes ahead without it (the service tolerates two refreshes racing).
8583
8599
  */
8584
8600
  async lock() {
8585
- const path10 = `${this.path}.lock`;
8601
+ const path11 = `${this.path}.lock`;
8586
8602
  const owner = `${process.pid}:${randomBytes8(8).toString("hex")}`;
8587
- mkdirSync7(dirname6(path10), { recursive: true });
8603
+ mkdirSync7(dirname6(path11), { recursive: true });
8588
8604
  const deadline = Date.now() + LOCK_WAIT_MS;
8589
8605
  for (; ; ) {
8590
8606
  try {
8591
- writeFileSync3(path10, owner, { flag: "wx", mode: 384 });
8607
+ writeFileSync3(path11, owner, { flag: "wx", mode: 384 });
8592
8608
  return () => {
8593
8609
  try {
8594
- if (readFileSync3(path10, "utf8") === owner) rmSync3(path10, { force: true });
8610
+ if (readFileSync3(path11, "utf8") === owner) rmSync3(path11, { force: true });
8595
8611
  } catch {
8596
8612
  }
8597
8613
  };
@@ -8600,7 +8616,7 @@ var AccountStore = class {
8600
8616
  return () => void 0;
8601
8617
  }
8602
8618
  try {
8603
- if (Date.now() - statSync2(path10).mtimeMs > LOCK_STALE_MS) rmSync3(path10, { force: true });
8619
+ if (Date.now() - statSync2(path11).mtimeMs > LOCK_STALE_MS) rmSync3(path11, { force: true });
8604
8620
  } catch {
8605
8621
  }
8606
8622
  if (Date.now() >= deadline) return () => void 0;
@@ -8699,1548 +8715,2339 @@ function brandLogo(unicode = true) {
8699
8715
  (line) => line.replaceAll("\u2588", "#").replaceAll("\u2580", '"').replaceAll("\u2584", ".")
8700
8716
  );
8701
8717
  }
8702
- var DEFAULT_ROLES = [
8703
- {
8704
- id: "planner",
8705
- description: "Breaks the objective into a plan, defines milestones and success criteria.",
8706
- kind: "artifact"
8707
- },
8708
- {
8709
- id: "researcher",
8710
- description: "Analyses markets, users, competitors and options from its own knowledge and the workspace; cannot browse the web.",
8711
- kind: "artifact"
8712
- },
8713
- {
8714
- id: "implementer",
8715
- description: "Writes and changes code in the repository (handed to the autonomous runner).",
8716
- kind: "code"
8718
+ var CHANGES_LIMITS = {
8719
+ /** Most isolated workspaces per project. */
8720
+ maxWorkspaces: 12,
8721
+ labelMaxLength: 60,
8722
+ messageMaxLength: 5e3,
8723
+ titleMaxLength: 200,
8724
+ bodyMaxLength: 2e4,
8725
+ /** Largest patch returned by `diff` (characters); longer patches are cut and flagged. */
8726
+ diffMaxLength: 1e6,
8727
+ /** Largest untracked file rendered as a patch (bytes); bigger ones are listed as binary. */
8728
+ untrackedMaxBytes: 512 * 1024,
8729
+ /** Largest patch handed to an agent to write the commit message (characters). */
8730
+ suggestDiffMaxLength: 4e4,
8731
+ /** Hard timeout for an agent writing the commit message. */
8732
+ suggestTimeoutMs: 3 * 6e4
8733
+ };
8734
+ var MAIN_WORKSPACE_ID = "main";
8735
+ var WORKSPACE_ID_PATTERN = /^[a-z0-9]{4,24}$/;
8736
+ var PANE_BRANCH_PREFIX = "davecode/pane-";
8737
+ var workspaceIdSchema = z10.string().refine((id) => id === MAIN_WORKSPACE_ID || WORKSPACE_ID_PATTERN.test(id), "invalid workspace");
8738
+ var safeRef = z10.string().regex(/^(?!.*\.\.)(?!.*\/\/)(?!\/)(?!.*\/$)(?!.*\.lock$)(?!-)[A-Za-z0-9._/-]{1,200}$/);
8739
+ var createWorkspaceSchema = z10.object({
8740
+ label: z10.string().trim().min(1).max(CHANGES_LIMITS.labelMaxLength).optional()
8741
+ });
8742
+ var suggestSchema = z10.object({
8743
+ /** Agent that writes the text; without one, DaveCode derives it from the file list. */
8744
+ agent: z10.enum(["claude", "codex", "agy"]).optional()
8745
+ });
8746
+ var shipSchema = z10.object({
8747
+ message: z10.string().trim().min(1).max(CHANGES_LIMITS.messageMaxLength).optional(),
8748
+ push: z10.boolean().default(false),
8749
+ pr: z10.object({
8750
+ title: z10.string().trim().min(1).max(CHANGES_LIMITS.titleMaxLength),
8751
+ body: z10.string().max(CHANGES_LIMITS.bodyMaxLength).default(""),
8752
+ draft: z10.boolean().default(false),
8753
+ /** Default: `baseBranch` for isolated workspaces, the remote's default branch otherwise. */
8754
+ base: safeRef.optional()
8755
+ }).optional()
8756
+ });
8757
+ var mergeSchema = z10.object({
8758
+ /** Commits uncommitted work in the isolated workspace with this message before merging. */
8759
+ message: z10.string().trim().min(1).max(CHANGES_LIMITS.messageMaxLength).optional()
8760
+ });
8761
+ var removeSchema = z10.object({
8762
+ /** Also delete the workspace's branch. By default it stays, so no work is lost. */
8763
+ deleteBranch: z10.boolean().default(false)
8764
+ });
8765
+ var ChangesError = class extends Error {
8766
+ constructor(code, message) {
8767
+ super(message);
8768
+ this.code = code;
8769
+ this.name = "ChangesError";
8770
+ }
8771
+ code;
8772
+ };
8773
+ var ORCHESTRATOR_AGENTS = ["claude", "codex", "agy"];
8774
+ var ORCHESTRATOR_LIMITS = {
8775
+ objectiveMaxLength: 4e3,
8776
+ /** Most tasks a plan may contain. */
8777
+ maxTasks: 8,
8778
+ /** Default when the request does not say. */
8779
+ defaultMaxTasks: 5,
8780
+ /** Most tasks running at the same time. */
8781
+ maxParallel: 3,
8782
+ /** Hard timeout for one headless agent run. */
8783
+ taskTimeoutMs: 30 * 6e4,
8784
+ /** Hard timeout for the planning run. */
8785
+ planTimeoutMs: 5 * 6e4,
8786
+ /** Longest summary kept per task (characters, head kept). */
8787
+ summaryMaxLength: 4e3
8788
+ };
8789
+ var startRunSchema = z11.object({
8790
+ objective: z11.string().trim().min(1).max(ORCHESTRATOR_LIMITS.objectiveMaxLength),
8791
+ /** Defaults to every available agent. */
8792
+ agents: z11.array(z11.enum(ORCHESTRATOR_AGENTS)).min(1).max(3).optional(),
8793
+ maxTasks: z11.number().int().min(1).max(ORCHESTRATOR_LIMITS.maxTasks).optional(),
8794
+ /** The agent that splits the objective. It must be installed, but need not take tasks.
8795
+ * Defaults to the first allowed agent in the order claude, codex, agy. */
8796
+ planner: z11.enum(ORCHESTRATOR_AGENTS).optional()
8797
+ });
8798
+ var OrchestratorError = class extends Error {
8799
+ constructor(code, message) {
8800
+ super(message);
8801
+ this.code = code;
8802
+ this.name = "OrchestratorError";
8803
+ }
8804
+ code;
8805
+ };
8806
+ var MAX_TEXT_CHUNK = 2e3;
8807
+ var MAX_TOOL_LINE = 200;
8808
+ var MAX_ERROR = 300;
8809
+ var AGENTS = {
8810
+ claude: {
8811
+ label: "Claude Code",
8812
+ binary: "claude",
8813
+ provider: "claude-cli",
8814
+ signIn: "run `claude` in a terminal and sign in"
8717
8815
  },
8718
- {
8719
- id: "reviewer",
8720
- description: "Critically reviews other outputs for gaps, risks and quality.",
8721
- kind: "artifact"
8816
+ codex: {
8817
+ label: "Codex",
8818
+ binary: "codex",
8819
+ provider: "codex-cli",
8820
+ signIn: "run `codex login` in a terminal"
8722
8821
  },
8723
- {
8724
- id: "writer",
8725
- description: "Writes positioning, marketing copy, landing-page text, documentation and drafts of messages.",
8726
- kind: "artifact"
8822
+ agy: {
8823
+ label: "Antigravity",
8824
+ binary: "agy",
8825
+ provider: "antigravity-cli",
8826
+ signIn: "run `agy` in a terminal and sign in"
8727
8827
  }
8728
- ];
8729
- function resolveRoles(config) {
8730
- const roles = new Map(DEFAULT_ROLES.map((r) => [r.id, { ...r }]));
8731
- for (const [id, override] of Object.entries(config.roles)) {
8732
- const base = roles.get(id);
8733
- const role = {
8734
- id,
8735
- description: override.description ?? base?.description ?? id,
8736
- kind: override.kind ?? base?.kind ?? "artifact"
8737
- };
8738
- if (override.route) role.route = override.route;
8739
- const instructions = override.instructions ?? base?.instructions;
8740
- if (instructions) role.instructions = instructions;
8741
- roles.set(id, role);
8828
+ };
8829
+ 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.";
8830
+ function agentArgs(agent, mode) {
8831
+ switch (agent) {
8832
+ case "claude":
8833
+ return mode === "plan" ? [
8834
+ "-p",
8835
+ "--output-format",
8836
+ "stream-json",
8837
+ "--verbose",
8838
+ "--permission-mode",
8839
+ "plan",
8840
+ "--tools",
8841
+ "Read",
8842
+ "Glob",
8843
+ "Grep",
8844
+ "--strict-mcp-config"
8845
+ ] : ["-p", "--output-format", "stream-json", "--verbose", "--permission-mode", "acceptEdits"];
8846
+ case "codex":
8847
+ return [
8848
+ "exec",
8849
+ "--json",
8850
+ "--skip-git-repo-check",
8851
+ "--sandbox",
8852
+ mode === "plan" ? "read-only" : "workspace-write",
8853
+ "-c",
8854
+ "approval_policy=never",
8855
+ "-"
8856
+ ];
8857
+ case "agy":
8858
+ return [
8859
+ "--input-format",
8860
+ "stream-json",
8861
+ "--output-format",
8862
+ "stream-json",
8863
+ "--sandbox",
8864
+ "--disable-slash-commands",
8865
+ "--print="
8866
+ ];
8742
8867
  }
8743
- return [...roles.values()];
8744
8868
  }
8745
- function publicRoles(roles) {
8746
- return roles.map(
8747
- ({ id, description, route }) => route ? { id, description, route } : { id, description }
8748
- );
8869
+ function realPath(p) {
8870
+ try {
8871
+ return realpathSync.native(p);
8872
+ } catch {
8873
+ return path8.resolve(p);
8874
+ }
8749
8875
  }
8750
- var OUTBOUND = [
8751
- /\b(send|sending|email|e-mail|emailing|mail out)\b/i,
8752
- /\b(tweet|dm|direct message|share on)\b/i,
8753
- /\b(post|publish)\b[^.\n]{0,60}\b(on|to)\b/i,
8754
- /\b(pay|purchase|buy|checkout|subscribe to|sign up|signup|register (for|on|at))\b/i,
8755
- /\b(outreach|cold[- ]?(call|email)|contact (the|our|potential|prospects?|customers?|leads?))\b/i,
8756
- /\b(launch (the )?ads?|run (the )?ads?|ad campaign|deploy to production|go live)\b/i
8757
- ];
8758
- var OPERATIONAL = [
8759
- /\b(deploy|release|publish)\b[^.\n]*\b(production|prod|npm|app store|play store|live)\b/i,
8760
- /\b(go live|pay|purchase|buy|checkout)\b/i,
8761
- /\b(send|email|e-mail|message)\b[^.\n]*\b(customers?|users?|leads?|prospects?|subscribers?|mailing list|newsletter)\b/i,
8762
- /\b(post|tweet|publish)\b[^.\n]*\b(twitter|x\.com|linkedin|facebook|instagram|reddit|hacker news|product hunt)\b/i
8763
- ];
8764
- function firstMatch(patterns, text) {
8765
- for (const pattern of patterns) {
8766
- const match = pattern.exec(text);
8767
- if (match) return match[0];
8876
+ function agentStdin(agent, mode, prompt) {
8877
+ if (agent === "agy") {
8878
+ const content = mode === "plan" ? `${AGY_PLAN_NOTE}
8879
+
8880
+ ${prompt}` : prompt;
8881
+ return `${JSON.stringify({ event: "user", message: { content } })}
8882
+ `;
8768
8883
  }
8769
- return void 0;
8884
+ if (agent === "claude" && prompt.trimStart().startsWith("/")) return `Task:
8885
+ ${prompt}`;
8886
+ return prompt;
8770
8887
  }
8771
- function outboundAction(text) {
8772
- return firstMatch(OUTBOUND, text);
8888
+ function abortError() {
8889
+ const err = new Error("The agent run was aborted");
8890
+ err.name = "AbortError";
8891
+ return err;
8773
8892
  }
8774
- var DRAFT_NOTICE = "DaveCode never performs outbound actions (emails, posts, payments, sign-ups, deployments). Produce a ready-to-use DRAFT for a human to review and execute.";
8775
- function applySafetyGuard(task) {
8776
- const text = `${task.title}
8777
- ${task.description ?? ""}`;
8778
- const action = task.kind === "code" ? firstMatch(OPERATIONAL, text) : firstMatch(OUTBOUND, text);
8779
- if (!action) return task;
8780
- const title = /^draft\b/i.test(task.title) ? task.title : `Draft: ${task.title}`;
8781
- const description = [task.description?.trim(), DRAFT_NOTICE].filter(Boolean).join("\n\n");
8782
- return { ...task, kind: "artifact", title: title.slice(0, 300), description };
8893
+ var SECRET_PATTERNS = [
8894
+ [/\b(?:sk|rk|pk)-[A-Za-z0-9_-]{8,}/g, "[redacted]"],
8895
+ [/\b(?:ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{8,}/g, "[redacted]"],
8896
+ [/\bAIza[A-Za-z0-9_-]{20,}/g, "[redacted]"],
8897
+ [/\bya29\.[A-Za-z0-9._-]{10,}/g, "[redacted]"],
8898
+ [/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]*/g, "[redacted]"],
8899
+ [/\b(bearer)\s+[^\s"']+/gi, "$1 [redacted]"],
8900
+ [
8901
+ /\b(api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password|authorization)(["']?\s*[:=]\s*["']?)[^\s"',}]+/gi,
8902
+ "$1$2[redacted]"
8903
+ ]
8904
+ ];
8905
+ function scrubSecrets(text) {
8906
+ let out = redact(text);
8907
+ for (const [pattern, replacement] of SECRET_PATTERNS) out = out.replace(pattern, replacement);
8908
+ return out.replace(/\s+/g, " ").trim();
8783
8909
  }
8784
- var plannedTaskSchema = z10.object({
8785
- id: z10.string().min(1).max(80),
8786
- title: z10.string().trim().min(1).max(300),
8787
- description: z10.string().max(8e3).optional(),
8788
- role: z10.string().min(1).max(40),
8789
- kind: z10.enum(["code", "artifact"]).optional(),
8790
- dependsOn: z10.array(z10.string().min(1)).max(50).default([]),
8791
- acceptance: z10.array(z10.string().min(1).max(1e3)).max(20).optional()
8792
- });
8793
- var planSchema = z10.object({
8794
- summary: z10.string().max(4e3).optional(),
8795
- tasks: z10.array(plannedTaskSchema).min(1).max(100)
8796
- });
8797
- var reviewSchema = z10.object({
8798
- /** The objective is achieved as far as DaveCode can take it. */
8799
- done: z10.boolean(),
8800
- summary: z10.string().max(8e3),
8801
- /** Ids of FAILED tasks worth another attempt. */
8802
- retry: z10.array(z10.string()).max(50).default([]),
8803
- /** New tasks for the next round. */
8804
- tasks: z10.array(plannedTaskSchema).max(50).default([])
8805
- });
8806
- var TASK_ID = /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/;
8807
- function sanitizeTaskId(raw) {
8808
- const id = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
8809
- return TASK_ID.test(id) ? id : "task";
8910
+ function clip(text, max) {
8911
+ return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
8810
8912
  }
8811
- function normalizeTasks(planned, opts) {
8812
- const roleById = new Map(opts.roles.map((r) => [r.id, r]));
8813
- const taken = new Set(opts.existing.map((t) => t.id));
8814
- const idMap = /* @__PURE__ */ new Map();
8815
- const out = [];
8816
- for (const p of planned.slice(0, Math.max(0, opts.room))) {
8817
- let id = sanitizeTaskId(p.id);
8818
- for (let n = 2; taken.has(id); n++) id = `${sanitizeTaskId(p.id)}-${n}`;
8819
- taken.add(id);
8820
- idMap.set(p.id, id);
8821
- let role = roleById.get(p.role.toLowerCase());
8822
- const requested = p.kind ?? role?.kind ?? "artifact";
8823
- if (!role)
8824
- role = roleById.get(requested === "code" ? "implementer" : "writer") ?? opts.roles[0];
8825
- const kind = requested === "code" && opts.codeTasks ? "code" : "artifact";
8826
- const task = {
8827
- id,
8828
- title: p.title,
8829
- role: role?.id ?? "writer",
8830
- kind,
8831
- status: "PENDING",
8832
- dependsOn: p.dependsOn,
8833
- round: opts.round,
8834
- attempts: 0
8835
- };
8836
- const description = requested === "code" && kind === "artifact" ? [
8837
- p.description,
8838
- "Code tasks cannot run here: write a precise implementation plan instead."
8839
- ].filter(Boolean).join("\n\n") : p.description;
8840
- if (description) task.description = description;
8841
- if (p.acceptance?.length) task.acceptance = p.acceptance;
8842
- out.push(applySafetyGuard(task));
8843
- }
8844
- const known = /* @__PURE__ */ new Set([...opts.existing.map((t) => t.id), ...out.map((t) => t.id)]);
8845
- for (const task of out) {
8846
- task.dependsOn = [
8847
- ...new Set(task.dependsOn.map((d) => idMap.get(d) ?? sanitizeTaskId(d)))
8848
- ].filter((d) => d !== task.id && known.has(d));
8849
- }
8850
- for (; ; ) {
8851
- const graph = {
8852
- version: 1,
8853
- tasks: [...opts.existing, ...out].map((t) => ({
8854
- id: t.id,
8855
- title: t.title,
8856
- status: t.status,
8857
- dependsOn: t.dependsOn
8858
- }))
8859
- };
8860
- const cycle = findCycles(graph)[0];
8861
- if (!cycle) break;
8862
- const from = cycle[cycle.length - 2];
8863
- const to = cycle[cycle.length - 1];
8864
- const task = out.find((t) => t.id === from);
8865
- if (!task) break;
8866
- task.dependsOn = task.dependsOn.filter((d) => d !== to);
8867
- }
8868
- return out;
8869
- }
8870
- var SAFETY_RULES = `Hard rules (non-negotiable):
8871
- - You run inside DaveCode with no internet access and no ability to contact anyone.
8872
- - Never send emails or messages, post or publish anything, make payments or purchases, sign up for services, or deploy. When such a step is useful, produce a DRAFT for a human to review and execute.
8873
- - Never invent credentials, personal data or facts presented as verified; label assumptions and estimates clearly.
8874
- - Stay within the objective; prefer concrete, actionable outputs.`;
8875
- function roleList(roles) {
8876
- return roles.map((r) => `- ${r.id} (${r.kind}): ${r.description}`).join("\n");
8913
+ function shortError(text) {
8914
+ return clip(scrubSecrets(text), MAX_ERROR);
8877
8915
  }
8878
- function plannerPrompt(roles, codeTasks) {
8879
- return `You are the planner of a small team of AI agents working toward one objective. Decompose the objective into a dependency graph of concrete tasks and assign each task to one role.
8880
-
8881
- Roles:
8882
- ${roleList(roles)}
8883
-
8884
- Task kinds:
8885
- - "artifact": the role writes one markdown document (analysis, plan, copy, checklist, draft).
8886
- - "code": the implementer changes the repository; an autonomous runner branches, runs the quality gates and merges.${codeTasks ? "" : "\n Code tasks are NOT available in this workspace: plan artifact tasks only."}
8887
-
8888
- ${SAFETY_RULES}
8889
-
8890
- Plan at most the number of tasks you are allowed, smallest useful set first. Ids are short lowercase kebab-case. Use dependsOn so later tasks build on earlier outputs.
8891
-
8892
- Respond with ONLY a JSON object, no prose and no code fences:
8893
- {"summary": "one paragraph plan", "tasks": [{"id": "market-research", "title": "...", "description": "...", "role": "researcher", "kind": "artifact", "dependsOn": [], "acceptance": ["..."]}]}`;
8916
+ function formatDuration(ms) {
8917
+ if (ms >= 6e4) return `${Math.round(ms / 6e4)} min`;
8918
+ return `${Math.max(1, Math.ceil(ms / 1e3))} s`;
8894
8919
  }
8895
- var REVIEWER_PROMPT = `You are the reviewer of a team of AI agents working toward one objective. Judge the results so far against the objective.
8896
-
8897
- ${SAFETY_RULES}
8898
-
8899
- Decide:
8900
- - "done": true when the objective is achieved as far as an offline team can take it (remaining steps are outbound actions a human must do from the drafts).
8901
- - otherwise propose the next round: "retry" lists FAILED task ids worth another attempt, "tasks" lists new tasks (same shape as the planner's, dependsOn may reference existing ids).
8902
-
8903
- Respond with ONLY a JSON object, no prose and no code fences:
8904
- {"done": false, "summary": "progress, gaps and what a human must do next", "retry": [], "tasks": []}`;
8905
- function roleSystemPrompt(role) {
8906
- return [
8907
- `You are the ${role.id} of a small team of AI agents. ${role.description}`,
8908
- SAFETY_RULES,
8909
- "Write your deliverable as a single, well-structured markdown document. Do not wrap it in code fences. Start with a level-1 heading.",
8910
- role.instructions?.trim()
8911
- ].filter(Boolean).join("\n\n");
8920
+ function tail(text, max = 400) {
8921
+ const t = text.trim();
8922
+ return t.length <= max ? t : t.slice(t.length - max);
8912
8923
  }
8913
- var MeteredExecutor = class {
8914
- constructor(inner) {
8915
- this.inner = inner;
8916
- }
8917
- inner;
8918
- tokens = 0;
8919
- get name() {
8920
- return this.inner.name;
8921
- }
8922
- createSession(task) {
8923
- const session = this.inner.createSession(task);
8924
- return {
8925
- run: async (opts) => {
8926
- const result = await session.run(opts);
8927
- this.tokens += Math.max(0, result.tokens);
8928
- return result;
8924
+ function toolLine(name, input, cwd) {
8925
+ let hint;
8926
+ if (isRecord(input)) {
8927
+ const lower = new Map(Object.entries(input).map(([k, v]) => [k.toLowerCase(), v]));
8928
+ for (const key of [
8929
+ "file_path",
8930
+ "filepath",
8931
+ "absolutepath",
8932
+ "path",
8933
+ "targetfile",
8934
+ "command",
8935
+ "commandline",
8936
+ "cmd",
8937
+ "pattern",
8938
+ "query",
8939
+ "url",
8940
+ "description"
8941
+ ]) {
8942
+ const value = lower.get(key);
8943
+ const str2 = typeof value === "string" ? value : Array.isArray(value) ? value.join(" ") : "";
8944
+ if (str2.trim()) {
8945
+ hint = str2;
8946
+ break;
8929
8947
  }
8930
- };
8948
+ }
8931
8949
  }
8932
- };
8933
- var MIN_TASK_TOKENS = 1e4;
8934
- var RunnerCodeTasks = class {
8935
- constructor(options) {
8936
- this.options = options;
8937
- this.owner = options.owner ?? "goal";
8950
+ if (hint && path8.isAbsolute(hint)) {
8951
+ for (const base of /* @__PURE__ */ new Set([cwd, realPath(cwd)])) {
8952
+ const rel = path8.relative(base, hint);
8953
+ if (rel && !rel.startsWith("..") && !path8.isAbsolute(rel)) {
8954
+ hint = rel.split(path8.sep).join("/");
8955
+ break;
8956
+ }
8957
+ }
8938
8958
  }
8939
- options;
8940
- owner;
8941
- async available() {
8942
- const { brain, engine } = this.options;
8943
- const git = new Git(brain.root);
8944
- if (!await git.isRepo())
8945
- return { ok: false, reason: `${brain.root} is not a git repository` };
8946
- if (!await brain.isInitialised()) {
8947
- return { ok: false, reason: "the project brain is missing (run davecode init)" };
8959
+ return clip(scrubSecrets(hint ? `${name} ${hint}` : name), MAX_TOOL_LINE);
8960
+ }
8961
+ function claudeParser(emit, cwd) {
8962
+ const state = { text: "", completed: false };
8963
+ let lastAssistant = "";
8964
+ return {
8965
+ state,
8966
+ flush() {
8967
+ },
8968
+ handle(raw) {
8969
+ if (!isRecord(raw)) return;
8970
+ const type = asString(raw.type);
8971
+ if (type === "system" && raw.subtype === "init") {
8972
+ const model = asString(raw.model);
8973
+ emit({
8974
+ kind: "status",
8975
+ text: model ? `Claude Code started (${model})` : "Claude Code started"
8976
+ });
8977
+ return;
8978
+ }
8979
+ if (type === "assistant" && raw.parent_tool_use_id == null && isRecord(raw.message)) {
8980
+ const texts = [];
8981
+ for (const block of asArray(raw.message.content)) {
8982
+ if (!isRecord(block)) continue;
8983
+ if (block.type === "text") {
8984
+ const t = asString(block.text)?.trim();
8985
+ if (t) texts.push(t);
8986
+ } else if (block.type === "tool_use") {
8987
+ emit({
8988
+ kind: "tool",
8989
+ text: toolLine(asString(block.name) ?? "tool", block.input, cwd)
8990
+ });
8991
+ }
8992
+ }
8993
+ if (texts.length > 0) {
8994
+ lastAssistant = texts.join("\n");
8995
+ emit({ kind: "text", text: clip(lastAssistant, MAX_TEXT_CHUNK) });
8996
+ }
8997
+ return;
8998
+ }
8999
+ if (type === "result") {
9000
+ const result = asString(raw.result) ?? "";
9001
+ const subtype = asString(raw.subtype) ?? "success";
9002
+ const u = isRecord(raw.usage) ? raw.usage : void 0;
9003
+ if (u) {
9004
+ 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);
9005
+ }
9006
+ if (raw.is_error === true || subtype !== "success") {
9007
+ state.failure = result || `Claude Code reported ${subtype}`;
9008
+ return;
9009
+ }
9010
+ state.completed = true;
9011
+ state.text = result || lastAssistant;
9012
+ }
8948
9013
  }
8949
- const base = engine.config.runner.baseBranch;
8950
- if (!await git.branchExists(base)) {
8951
- return { ok: false, reason: `base branch "${base}" does not exist` };
9014
+ };
9015
+ }
9016
+ function codexErrorText(value) {
9017
+ if (typeof value === "string") return value;
9018
+ if (isRecord(value)) return asString(value.message) ?? JSON.stringify(value);
9019
+ return void 0;
9020
+ }
9021
+ function codexItemLine(item, cwd) {
9022
+ const type = asString(item.type) ?? "item";
9023
+ switch (type) {
9024
+ case "reasoning":
9025
+ case "todo_list":
9026
+ return void 0;
9027
+ case "command_execution":
9028
+ return toolLine("Run", { command: asString(item.command) ?? "" }, cwd);
9029
+ case "file_change": {
9030
+ const files = asArray(item.changes).flatMap((c) => isRecord(c) && asString(c.path) ? [asString(c.path)] : []).map((f) => toolLine("", { path: f }, cwd).trim());
9031
+ return clip(`Edit ${files.join(", ")}`.trim(), MAX_TOOL_LINE);
8952
9032
  }
8953
- if ((await git.status({ ignoreBrain: true })).length > 0) {
8954
- return { ok: false, reason: "the working tree has uncommitted changes" };
9033
+ case "mcp_tool_call": {
9034
+ const name = [asString(item.server), asString(item.tool)].filter(Boolean).join(".");
9035
+ return toolLine(name || "MCP tool", void 0, cwd);
8955
9036
  }
8956
- return { ok: true };
9037
+ case "web_search":
9038
+ return toolLine("Search", { query: asString(item.query) ?? "" }, cwd);
9039
+ default:
9040
+ return toolLine(type, item, cwd);
8957
9041
  }
8958
- async run(task, opts) {
8959
- const { brain, engine } = this.options;
8960
- const runnerTaskId = sanitizeTaskId(`goal-${opts.goal.id.slice(-6)}-${task.id}`);
8961
- const graph = await brain.readGraph();
8962
- const existing = graph.tasks.find((t) => t.id === runnerTaskId);
8963
- if (existing?.status === "SUCCESS") {
8964
- return { ok: true, summary: existing.notes ?? "already done", tokens: 0, runnerTaskId };
8965
- }
8966
- if (!existing) {
8967
- await brain.createTask({
8968
- id: runnerTaskId,
8969
- title: task.title,
8970
- description: [
8971
- task.description,
8972
- `Part of DaveCode goal ${opts.goal.id}: ${opts.goal.objective}`
8973
- ].filter(Boolean).join("\n\n"),
8974
- ...task.acceptance?.length ? { acceptance: task.acceptance } : {}
8975
- });
8976
- } else if (existing.status !== "PENDING") {
8977
- await brain.setTaskStatus(runnerTaskId, "PENDING", { notes: "retried by goal mode" });
8978
- }
8979
- const base = engine.config.runner;
8980
- const config = {
8981
- ...base,
8982
- maxTaskTokens: Math.max(MIN_TASK_TOKENS, Math.min(base.maxTaskTokens, opts.maxTokens))
8983
- };
8984
- const metered = new MeteredExecutor(this.options.executor ?? createExecutor(engine, config));
8985
- const runner = this.options.makeRunner?.(config, metered) ?? createRunner(engine, {
8986
- brain,
8987
- config,
8988
- executor: metered,
8989
- lock: { lock: this.options.lock, owner: this.owner },
8990
- ...this.options.contextExtras ? { contextExtras: this.options.contextExtras } : {}
8991
- });
8992
- const onAbort = () => void runner.stop();
8993
- opts.signal.addEventListener("abort", onAbort, { once: true });
8994
- try {
8995
- const result = await runner.runOnce({ taskId: runnerTaskId });
8996
- return {
8997
- ok: result.outcome === "success",
8998
- summary: result.summary ?? result.error ?? result.outcome,
8999
- tokens: metered.tokens,
9000
- runnerTaskId,
9001
- ...result.outcome === "stopped" ? { stopped: true } : {}
9002
- };
9003
- } catch (err) {
9004
- if (!(err instanceof RunnerError)) throw err;
9005
- return { ok: false, summary: err.message, tokens: metered.tokens, runnerTaskId };
9006
- } finally {
9007
- opts.signal.removeEventListener("abort", onAbort);
9008
- }
9009
- }
9010
- };
9011
- var TERMINAL = /* @__PURE__ */ new Set([
9012
- "planned",
9013
- "succeeded",
9014
- "failed",
9015
- "cancelled",
9016
- "budget_exhausted"
9017
- ]);
9018
- function isTerminalGoal(goal) {
9019
- return TERMINAL.has(goal.status);
9020
9042
  }
9021
- var Cancelled = class extends Error {
9022
- };
9023
- var OutOfBudget = class extends Error {
9024
- };
9025
- var MAX_DEP_CHARS = 6e3;
9026
- var MAX_DEPS_TOTAL = 2e4;
9027
- var MAX_REVIEW_EXCERPT = 1500;
9028
- var MAX_TOOL_ROUNDS = 6;
9029
- var GoalOrchestrator = class {
9030
- constructor(options) {
9031
- this.options = options;
9032
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
9033
- this.tools = new WorkspaceTools({ root: options.store.root, readOnly: true });
9034
- }
9035
- options;
9036
- now;
9037
- tools;
9038
- /** A fresh goal (not yet persisted) with limits clamped to the config. */
9039
- create(input) {
9040
- const cfg = this.options.config;
9041
- const stamp = this.now().toISOString();
9042
- const goal = {
9043
- id: newId2("goal", 6),
9044
- objective: input.objective.trim(),
9045
- status: "planning",
9046
- round: 0,
9047
- limits: {
9048
- maxRounds: Math.min(input.maxRounds ?? cfg.maxRounds, cfg.maxRounds),
9049
- maxTasks: Math.min(input.maxTasks ?? cfg.maxTasks, cfg.maxTasks),
9050
- maxTokens: Math.min(input.maxTokens ?? cfg.maxTokens, cfg.maxTokens),
9051
- tokensUsed: 0
9052
- },
9053
- roles: publicRoles(resolveRoles(cfg)),
9054
- tasks: [],
9055
- createdAt: stamp,
9056
- updatedAt: stamp
9057
- };
9058
- if (input.dryRun) goal.dryRun = true;
9059
- return goal;
9060
- }
9061
- /** Runs `goal` to a terminal status. Never throws: failures end up in `status`/`lastError`. */
9062
- async run(goal, signal) {
9063
- const cfg = this.options.config;
9064
- const roles = resolveRoles(cfg);
9065
- try {
9066
- const code = await this.codeAvailability();
9067
- if (!code.ok) this.log(goal, "info", `code tasks disabled: ${code.reason}`);
9068
- if (goal.round === 0 || goal.tasks.length === 0) {
9069
- goal.round = 1;
9070
- await this.update(goal, { status: "planning" });
9071
- this.log(goal, "info", `planning: ${goal.objective}`, "planner");
9072
- const plan = await this.plan(goal, roles, code.ok, signal);
9073
- goal.tasks = normalizeTasks(plan.tasks, {
9074
- existing: [],
9075
- roles,
9076
- round: 1,
9077
- room: goal.limits.maxTasks,
9078
- codeTasks: code.ok
9079
- });
9080
- if (plan.summary) goal.summary = plan.summary;
9081
- this.log(goal, "info", `planned ${goal.tasks.length} task(s)`, "planner");
9082
- if (goal.dryRun) return await this.finish(goal, "planned");
9083
- } else {
9084
- this.log(goal, "info", `executing planned goal (${goal.tasks.length} task(s))`, "planner");
9043
+ function codexParser(emit, cwd) {
9044
+ const state = { text: "", completed: false };
9045
+ const announced = /* @__PURE__ */ new Set();
9046
+ let lastError;
9047
+ return {
9048
+ state,
9049
+ flush() {
9050
+ if (!state.completed && !state.failure && lastError) state.failure = lastError;
9051
+ },
9052
+ handle(raw) {
9053
+ if (!isRecord(raw)) return;
9054
+ const ev = isRecord(raw.msg) ? raw.msg : raw;
9055
+ const type = asString(ev.type) ?? "";
9056
+ if (type === "thread.started") {
9057
+ emit({ kind: "status", text: "Codex started" });
9058
+ return;
9085
9059
  }
9086
- for (; ; ) {
9087
- await this.update(goal, { status: "running" });
9088
- await this.executeReady(goal, roles, signal);
9089
- await this.update(goal, { status: "reviewing" });
9090
- this.log(goal, "info", `reviewing round ${goal.round}`, "reviewer");
9091
- const review = await this.review(goal, roles, signal);
9092
- goal.summary = review.summary;
9093
- if (review.done) return await this.finish(goal, "succeeded");
9094
- if (goal.round >= goal.limits.maxRounds) {
9095
- goal.lastError = `reached ${goal.limits.maxRounds} round(s) without completing the objective`;
9096
- return await this.finish(goal, "failed");
9060
+ if ((type === "item.started" || type === "item.completed") && isRecord(ev.item)) {
9061
+ const item = ev.item;
9062
+ const itemType = asString(item.type);
9063
+ if (itemType === "agent_message" || itemType === "assistant_message") {
9064
+ if (type !== "item.completed") return;
9065
+ const text = (asString(item.text) ?? asString(item.message) ?? "").trim();
9066
+ if (text) {
9067
+ state.text = text;
9068
+ emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
9069
+ }
9070
+ return;
9097
9071
  }
9098
- const retried = this.applyRetries(goal, review.retry);
9099
- const added = normalizeTasks(review.tasks, {
9100
- existing: goal.tasks,
9101
- roles,
9102
- round: goal.round + 1,
9103
- room: goal.limits.maxTasks - goal.tasks.length,
9104
- codeTasks: code.ok
9105
- });
9106
- if (retried === 0 && added.length === 0) {
9107
- goal.lastError = goal.tasks.length >= goal.limits.maxTasks ? `reached the cap of ${goal.limits.maxTasks} task(s) without completing the objective` : "the reviewer proposed no further work but the objective is not done";
9108
- return await this.finish(goal, "failed");
9072
+ if (itemType === "error") {
9073
+ const message = asString(item.message);
9074
+ if (message && type === "item.completed")
9075
+ emit({ kind: "error", text: shortError(message) });
9076
+ return;
9109
9077
  }
9110
- goal.tasks.push(...added);
9111
- goal.round++;
9112
- this.log(
9113
- goal,
9114
- "info",
9115
- `round ${goal.round}: ${added.length} new task(s), ${retried} retried`,
9116
- "reviewer"
9117
- );
9078
+ const id = asString(item.id) ?? `${itemType}-${announced.size}`;
9079
+ if (itemType === "file_change" && type !== "item.completed") return;
9080
+ if (announced.has(id)) return;
9081
+ announced.add(id);
9082
+ const line = codexItemLine(item, cwd);
9083
+ if (line) emit({ kind: "tool", text: line });
9084
+ return;
9118
9085
  }
9119
- } catch (err) {
9120
- for (const task of goal.tasks) if (task.status === "IN_PROGRESS") task.status = "PENDING";
9121
- delete goal.activeTaskId;
9122
- if (err instanceof Cancelled || signal.aborted) return await this.finish(goal, "cancelled");
9123
- if (err instanceof OutOfBudget) {
9124
- goal.lastError = err.message;
9125
- return await this.finish(goal, "budget_exhausted");
9086
+ if (type === "turn.completed" || type === "task_complete") {
9087
+ state.completed = true;
9088
+ const last = asString(ev.last_agent_message);
9089
+ if (!state.text && last) state.text = last;
9090
+ const u = isRecord(ev.usage) ? ev.usage : void 0;
9091
+ if (u) state.tokens = (asNumber(u.input_tokens) ?? 0) + (asNumber(u.output_tokens) ?? 0);
9092
+ return;
9126
9093
  }
9127
- goal.lastError = err instanceof Error ? err.message : String(err);
9128
- this.log(goal, "error", goal.lastError);
9129
- return await this.finish(goal, "failed");
9130
- }
9131
- }
9132
- // -- phases -----------------------------------------------------------------
9133
- async codeAvailability() {
9134
- if (!this.options.config.codeTasks) return { ok: false, reason: "goal.codeTasks is off" };
9135
- if (!this.options.codeRunner) return { ok: false, reason: "no project runner available" };
9136
- try {
9137
- return await this.options.codeRunner.available();
9138
- } catch (err) {
9139
- return { ok: false, reason: err instanceof Error ? err.message : String(err) };
9140
- }
9141
- }
9142
- async plan(goal, roles, codeTasks, signal) {
9143
- const user = [
9144
- `OBJECTIVE:
9145
- ${goal.objective}`,
9146
- `You may plan at most ${goal.limits.maxTasks} task(s) in total; the team has ${goal.limits.maxRounds} round(s) of plan \u2192 execute \u2192 review.`,
9147
- await this.sharedContext()
9148
- ].filter(Boolean).join("\n\n");
9149
- return this.askJson(
9150
- planSchema,
9151
- goal,
9152
- this.roleFor(roles, "planner"),
9153
- plannerPrompt(roles, codeTasks),
9154
- user,
9155
- signal
9156
- );
9157
- }
9158
- async review(goal, roles, signal) {
9159
- const lines2 = [];
9160
- for (const task of goal.tasks) {
9161
- lines2.push(
9162
- `- [${task.status}] ${task.id} (${task.role}, ${task.kind}, round ${task.round}, ${task.attempts} attempt(s)): ${task.title}`
9163
- );
9164
- if (task.notes) lines2.push(` notes: ${truncateHead(task.notes, 500).replace(/\n/g, " ")}`);
9165
- if (task.artifact) {
9166
- const text = await this.options.store.readArtifact(goal.id, task.id);
9167
- if (text)
9168
- lines2.push(` output excerpt:
9169
- ${indent(truncateHead(text, MAX_REVIEW_EXCERPT))}`);
9094
+ if (type === "turn.failed") {
9095
+ state.failure = codexErrorText(ev.error) ?? lastError ?? "Codex reported an error";
9096
+ return;
9097
+ }
9098
+ if (type === "error") {
9099
+ lastError = codexErrorText(ev.message) ?? codexErrorText(ev.error) ?? lastError;
9100
+ if (lastError) emit({ kind: "error", text: shortError(lastError) });
9101
+ return;
9102
+ }
9103
+ if (type === "agent_message") {
9104
+ const text = (asString(ev.message) ?? "").trim();
9105
+ if (text) {
9106
+ state.text = text;
9107
+ emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
9108
+ }
9170
9109
  }
9171
9110
  }
9172
- const user = [
9173
- `OBJECTIVE:
9174
- ${goal.objective}`,
9175
- `Round ${goal.round} of ${goal.limits.maxRounds}. Room for ${Math.max(0, goal.limits.maxTasks - goal.tasks.length)} more task(s).`,
9176
- `TASKS:
9177
- ${lines2.join("\n")}`,
9178
- await this.sharedContext()
9179
- ].filter(Boolean).join("\n\n");
9180
- return this.askJson(
9181
- reviewSchema,
9182
- goal,
9183
- this.roleFor(roles, "reviewer"),
9184
- REVIEWER_PROMPT,
9185
- user,
9186
- signal
9187
- );
9188
- }
9189
- /** Runs ready tasks until none is left; failed tasks are retried up to `maxTaskAttempts`. */
9190
- async executeReady(goal, roles, signal) {
9191
- for (; ; ) {
9192
- const task = readyGoalTasks(goal)[0];
9193
- if (!task) return;
9194
- this.checkpoint(goal, signal);
9195
- task.attempts++;
9196
- task.status = "IN_PROGRESS";
9197
- await this.update(goal, { activeTaskId: task.id });
9198
- this.log(goal, "info", `${task.title} (attempt ${task.attempts})`, task.role, task.id);
9199
- try {
9200
- if (task.kind === "code") await this.runCodeTask(goal, task, signal);
9201
- else await this.runArtifactTask(goal, task, this.roleFor(roles, task.role), signal);
9202
- const outcome = task.status;
9203
- this.log(
9204
- goal,
9205
- outcome === "SUCCESS" ? "info" : "warn",
9206
- `${task.id}: ${outcome}`,
9207
- task.role,
9208
- task.id
9209
- );
9210
- } catch (err) {
9211
- if (err instanceof Cancelled || err instanceof OutOfBudget || signal.aborted) throw err;
9212
- if (err instanceof RouterError && (err.code === "no_capacity" || err.code === "rate_limited")) {
9213
- throw new OutOfBudget(`no account has capacity left (${err.message})`);
9111
+ };
9112
+ }
9113
+ function agyParser(emit, cwd) {
9114
+ const state = { text: "", completed: false };
9115
+ const announced = /* @__PURE__ */ new Set();
9116
+ let streamed = "";
9117
+ let buffer = "";
9118
+ let bufferStep;
9119
+ const flushText = () => {
9120
+ const text = buffer.trim();
9121
+ buffer = "";
9122
+ bufferStep = void 0;
9123
+ if (text) emit({ kind: "text", text: clip(text, MAX_TEXT_CHUNK) });
9124
+ };
9125
+ return {
9126
+ state,
9127
+ flush: flushText,
9128
+ handle(raw) {
9129
+ if (!isRecord(raw)) return;
9130
+ const event = asString(raw.event);
9131
+ if (event === "init") {
9132
+ emit({ kind: "status", text: "Antigravity started" });
9133
+ return;
9134
+ }
9135
+ if (event === "step_update" && isRecord(raw.step_update)) {
9136
+ const step = raw.step_update;
9137
+ const stepType = asString(step.step_type);
9138
+ const index = String(asNumber(step.step_index) ?? asString(step.step_index) ?? "");
9139
+ if (stepType === "agent_response") {
9140
+ if (bufferStep !== void 0 && bufferStep !== index) flushText();
9141
+ const delta = asString(step.text_delta);
9142
+ if (delta) {
9143
+ bufferStep = index;
9144
+ buffer += delta;
9145
+ streamed += delta;
9146
+ }
9147
+ if (asString(step.state) === "DONE") flushText();
9148
+ return;
9214
9149
  }
9215
- task.status = "FAILED";
9216
- task.notes = truncateHead(err instanceof Error ? err.message : String(err), 2e3);
9217
- this.log(goal, "warn", `${task.id} failed: ${task.notes}`, task.role, task.id);
9150
+ if (stepType === "tool") {
9151
+ flushText();
9152
+ if (announced.has(index)) return;
9153
+ announced.add(index);
9154
+ const info = isRecord(step.tool_info) ? step.tool_info : {};
9155
+ const name = asString(step.tool_name) ?? asString(info.name) ?? "tool";
9156
+ emit({ kind: "tool", text: toolLine(name, info.parameters, cwd) });
9157
+ }
9158
+ return;
9218
9159
  }
9219
- if (task.status === "FAILED" && task.attempts < this.options.config.maxTaskAttempts) {
9220
- task.status = "PENDING";
9160
+ if (event === "result" && isRecord(raw.result)) {
9161
+ flushText();
9162
+ const result = raw.result;
9163
+ const u = isRecord(result.usage) ? result.usage : void 0;
9164
+ if (u) {
9165
+ state.tokens = (asNumber(u.input_tokens) ?? 0) + (asNumber(u.output_tokens) ?? 0) + (asNumber(u.thinking_tokens) ?? 0);
9166
+ }
9167
+ const status = asString(result.status);
9168
+ if (status !== "SUCCESS") {
9169
+ state.failure = asString(result.error) ?? `Antigravity ended with ${status ?? "an error"}`;
9170
+ return;
9171
+ }
9172
+ state.completed = true;
9173
+ state.text = (asString(result.response) ?? streamed).trim();
9221
9174
  }
9222
- delete goal.activeTaskId;
9223
- await this.update(goal, {});
9224
9175
  }
9225
- }
9226
- async runArtifactTask(goal, task, role, signal) {
9227
- const parts = [`OBJECTIVE OF THE TEAM:
9228
- ${goal.objective}`, `YOUR TASK: ${task.title}`];
9229
- if (task.description) parts.push(task.description);
9230
- if (task.acceptance?.length) {
9231
- parts.push(`Acceptance criteria:
9232
- ${task.acceptance.map((c) => `- ${c}`).join("\n")}`);
9176
+ };
9177
+ }
9178
+ var PARSERS = {
9179
+ claude: claudeParser,
9180
+ codex: codexParser,
9181
+ agy: agyParser
9182
+ };
9183
+ function failureMessage(info, detail, evidence, code) {
9184
+ const { kind } = classifyCliText(evidence);
9185
+ if (kind === "auth") return `${info.label} is not signed in: ${info.signIn}`;
9186
+ const clean = scrubSecrets(detail);
9187
+ if (kind === "quota_exhausted") return shortError(`${info.label} usage limit reached: ${clean}`);
9188
+ if (kind === "rate_limit") return shortError(`${info.label} is rate limited: ${clean}`);
9189
+ const exit = code !== null && code !== 0 ? ` (exit ${code})` : "";
9190
+ return shortError(`${info.label} failed${exit}${clean ? `: ${clean}` : ""}`);
9191
+ }
9192
+ function createCliAgentRunner(options = {}) {
9193
+ const env = options.env ?? process.env;
9194
+ const childEnv = {};
9195
+ for (const [k, v] of Object.entries(env)) if (typeof v === "string") childEnv[k] = v;
9196
+ const resolveBinary = (agent) => {
9197
+ const override = options.binaries?.[agent];
9198
+ const command = override?.command ?? AGENTS[agent].binary;
9199
+ return { command: resolveOnPath(command, env), prefix: override?.args ?? [] };
9200
+ };
9201
+ const isAvailable = (agent) => {
9202
+ const { command } = resolveBinary(agent);
9203
+ if (!path8.isAbsolute(command)) return false;
9204
+ try {
9205
+ return existsSync4(command) && statSync3(command).isFile();
9206
+ } catch {
9207
+ return false;
9233
9208
  }
9234
- let budget = MAX_DEPS_TOTAL;
9235
- for (const depId of task.dependsOn) {
9236
- const dep = goal.tasks.find((t) => t.id === depId);
9237
- if (!dep || budget <= 0) continue;
9238
- const text = dep.artifact ? await this.options.store.readArtifact(goal.id, dep.id) : dep.notes;
9239
- if (!text) continue;
9240
- const excerpt = truncateHead(text, Math.min(MAX_DEP_CHARS, budget));
9241
- budget -= excerpt.length;
9242
- parts.push(`OUTPUT OF ${dep.id} (${dep.role}): ${dep.title}
9243
- ${excerpt}`);
9209
+ };
9210
+ async function run(request) {
9211
+ if (request.signal.aborted) throw abortError();
9212
+ const info = AGENTS[request.agent];
9213
+ const { command, prefix } = resolveBinary(request.agent);
9214
+ const args = agentArgs(request.agent, request.mode);
9215
+ if (needsShell(command) && ![...prefix, ...args].every(isShellSafeArg)) {
9216
+ return { ok: false, text: "", error: `${info.label}: unsafe argument for the Windows shell` };
9244
9217
  }
9245
- const shared = await this.sharedContext(false);
9246
- if (shared) parts.push(shared);
9247
- const reply = (await this.ask(goal, role, roleSystemPrompt(role), parts.join("\n\n"), signal)).trim();
9248
- if (!reply) throw new Error("the agent returned an empty reply");
9249
- task.artifact = await this.options.store.writeArtifact(goal.id, task.id, stripFence(reply));
9250
- task.status = "SUCCESS";
9251
- task.notes = `Wrote ${task.artifact}`;
9252
- }
9253
- async runCodeTask(goal, task, signal) {
9254
- const runner = this.options.codeRunner;
9255
- if (!runner) throw new Error("no project runner available for code tasks");
9256
- const result = await runner.run(task, {
9257
- goal,
9258
- maxTokens: goal.limits.maxTokens - goal.limits.tokensUsed,
9259
- signal
9260
- });
9261
- this.addTokens(goal, result.tokens);
9262
- if (result.runnerTaskId) task.runnerTaskId = result.runnerTaskId;
9263
- if (result.stopped || signal.aborted) throw new Cancelled();
9264
- task.status = result.ok ? "SUCCESS" : "FAILED";
9265
- task.notes = truncateHead(result.summary || (result.ok ? "done" : "failed"), 2e3);
9266
- }
9267
- applyRetries(goal, ids) {
9268
- let count = 0;
9269
- for (const id of new Set(ids)) {
9270
- const task = goal.tasks.find((t) => t.id === id);
9271
- if (task?.status !== "FAILED") continue;
9272
- task.status = "PENDING";
9273
- task.attempts = 0;
9274
- count++;
9218
+ const emit = (chunk) => {
9219
+ try {
9220
+ request.onOutput(chunk);
9221
+ } catch {
9222
+ }
9223
+ };
9224
+ let scratch;
9225
+ let cwd = request.cwd;
9226
+ if (request.agent === "agy" && request.mode === "plan") {
9227
+ scratch = mkdtempSync(path8.join(options.scratchDir ?? tmpdir(), "davecode-agy-plan-"));
9228
+ cwd = scratch;
9275
9229
  }
9276
- return count;
9277
- }
9278
- // -- model calls ------------------------------------------------------------
9279
- roleFor(roles, id) {
9280
- return roles.find((r) => r.id === id) ?? { id, description: id, kind: "artifact" };
9281
- }
9282
- async ask(goal, role, system, user, signal) {
9283
- const model = routeModel(role.route ?? this.options.config.route);
9284
- const messages = [
9285
- { role: "system", content: system },
9286
- { role: "user", content: user }
9287
- ];
9288
- let attachTools = this.options.readTools === true;
9289
- for (let round = 0; round <= MAX_TOOL_ROUNDS; round++) {
9290
- this.checkpoint(goal, signal);
9291
- const allowTools = attachTools && round < MAX_TOOL_ROUNDS;
9292
- let completion;
9230
+ const timeout = AbortSignal.timeout(request.timeoutMs);
9231
+ const signal = AbortSignal.any([request.signal, timeout]);
9232
+ const timedOut = () => ({
9233
+ ok: false,
9234
+ text: "",
9235
+ error: `${info.label} timed out after ${formatDuration(request.timeoutMs)}`
9236
+ });
9237
+ try {
9238
+ let cli;
9293
9239
  try {
9294
- ({ completion } = await this.options.router.complete(
9295
- {
9296
- model,
9297
- messages,
9298
- ...allowTools ? { tools: [...READ_ONLY_TOOL_DEFINITIONS] } : {}
9299
- },
9300
- { signal }
9301
- ));
9240
+ cli = runCli({
9241
+ provider: info.provider,
9242
+ accountId: "orchestrator",
9243
+ settings: { command, prefixArgs: prefix },
9244
+ args,
9245
+ env: childEnv,
9246
+ cwd,
9247
+ stdin: agentStdin(request.agent, request.mode, request.prompt),
9248
+ signal
9249
+ });
9302
9250
  } catch (err) {
9303
- if (allowTools && err instanceof RouterError && err.code === "no_available_account") {
9304
- attachTools = false;
9305
- ({ completion } = await this.options.router.complete({ model, messages }, { signal }));
9306
- } else {
9307
- throw err;
9308
- }
9251
+ if (request.signal.aborted) throw abortError();
9252
+ if (timeout.aborted) return timedOut();
9253
+ return { ok: false, text: "", error: shortError(errText(err)) };
9309
9254
  }
9310
- const message = completion.choices[0]?.message;
9311
- const content = message?.content;
9312
- const text = typeof content === "string" ? content : "";
9313
- const usage = completion.usage;
9314
- this.addTokens(
9315
- goal,
9316
- usage && usage.prompt_tokens + usage.completion_tokens > 0 ? usage.prompt_tokens + usage.completion_tokens : countTextTokens(system) + countTextTokens(user) + countTextTokens(text)
9317
- );
9318
- const calls = message?.tool_calls ?? [];
9319
- if (calls.length === 0 || round === MAX_TOOL_ROUNDS) return text;
9320
- messages.push({ role: "assistant", content: content ?? null, tool_calls: calls });
9321
- for (const call of calls) {
9322
- this.log(goal, "debug", `tool ${call.function.name}`, role.id, goal.activeTaskId);
9323
- const outcome = await this.tools.call(call.function.name, call.function.arguments, signal);
9324
- messages.push({
9325
- role: "tool",
9326
- tool_call_id: call.id,
9327
- name: call.function.name,
9328
- content: outcome.output
9329
- });
9330
- }
9331
- }
9332
- return "";
9333
- }
9334
- /** Asks for a JSON reply matching `schema`; one corrective retry on malformed output. */
9335
- async askJson(schema, goal, role, system, user, signal) {
9336
- let problem = "";
9337
- for (let attempt = 0; attempt < 2; attempt++) {
9338
- const prompt = attempt === 0 ? user : `${user}
9339
-
9340
- Your previous reply was rejected (${problem}). Respond with ONLY the JSON object in the required shape.`;
9341
- const reply = await this.ask(goal, role, system, prompt, signal);
9255
+ const parser = PARSERS[request.agent](emit, cwd);
9256
+ let streamError;
9342
9257
  try {
9343
- const parsed = schema.safeParse(extractJsonObject(reply));
9344
- if (parsed.success) return parsed.data;
9345
- problem = parsed.error.issues.slice(0, 3).map((i) => `${i.path.join(".") || "reply"}: ${i.message}`).join("; ");
9258
+ for await (const event of cli.events) parser.handle(event);
9346
9259
  } catch (err) {
9347
- problem = err instanceof Error ? err.message : String(err);
9260
+ streamError = errText(err);
9261
+ }
9262
+ parser.flush();
9263
+ let exit;
9264
+ try {
9265
+ exit = await cli.exit;
9266
+ } catch {
9267
+ if (request.signal.aborted) throw abortError();
9268
+ return {
9269
+ ok: false,
9270
+ text: "",
9271
+ error: `${info.label} could not be started: is \`${info.binary}\` installed and on PATH?`
9272
+ };
9273
+ }
9274
+ if (request.signal.aborted) throw abortError();
9275
+ if (exit.aborted || timeout.aborted) return { ...timedOut(), text: parser.state.text };
9276
+ const { state } = parser;
9277
+ if (state.completed && !state.failure && exit.code === 0) {
9278
+ return {
9279
+ ok: true,
9280
+ text: state.text,
9281
+ ...state.tokens !== void 0 ? { tokens: state.tokens } : {}
9282
+ };
9283
+ }
9284
+ const detail = state.failure ?? streamError ?? (tail(exit.stderr) || (exit.code === 0 ? "the CLI ended without a final result" : "no output"));
9285
+ return {
9286
+ ok: false,
9287
+ text: state.text,
9288
+ error: failureMessage(info, detail, `${detail}
9289
+ ${tail(exit.stderr)}`, exit.code),
9290
+ ...state.tokens !== void 0 ? { tokens: state.tokens } : {}
9291
+ };
9292
+ } finally {
9293
+ if (scratch) {
9294
+ try {
9295
+ rmSync4(scratch, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
9296
+ } catch {
9297
+ }
9348
9298
  }
9349
- this.log(goal, "warn", `invalid JSON from the ${role.id}: ${problem}`, role.id);
9350
- }
9351
- throw new Error(`the ${role.id} did not return valid JSON (${problem})`);
9352
- }
9353
- async sharedContext(includeProject = true) {
9354
- const parts = [];
9355
- if (includeProject && this.options.projectContext) {
9356
- const project = (await this.options.projectContext()).trim();
9357
- if (project) parts.push(`PROJECT CONTEXT:
9358
- ${project}`);
9359
- }
9360
- if (this.options.budgetContext) {
9361
- const budget = (await this.options.budgetContext()).trim();
9362
- if (budget) parts.push(budget);
9363
9299
  }
9364
- return parts.join("\n\n");
9365
9300
  }
9366
- // -- bookkeeping ------------------------------------------------------------
9367
- checkpoint(goal, signal) {
9368
- if (signal.aborted) throw new Cancelled();
9369
- if (goal.limits.tokensUsed >= goal.limits.maxTokens) {
9370
- throw new OutOfBudget(
9371
- `token budget exhausted (${goal.limits.tokensUsed}/${goal.limits.maxTokens})`
9301
+ return {
9302
+ async availability() {
9303
+ return ORCHESTRATOR_AGENTS.map((agent) => ({ agent, available: isAvailable(agent) }));
9304
+ },
9305
+ run
9306
+ };
9307
+ }
9308
+ function errText(err) {
9309
+ return err instanceof Error ? err.message : String(err);
9310
+ }
9311
+ function tokens(out) {
9312
+ const parts = out.split("\0");
9313
+ if (parts.at(-1) === "") parts.pop();
9314
+ return parts;
9315
+ }
9316
+ function statusOf(letter) {
9317
+ switch (letter[0]) {
9318
+ case "A":
9319
+ return "added";
9320
+ case "D":
9321
+ return "deleted";
9322
+ case "R":
9323
+ return "renamed";
9324
+ case "C":
9325
+ return "added";
9326
+ default:
9327
+ return "modified";
9328
+ }
9329
+ }
9330
+ function parseNameStatus(out) {
9331
+ const parts = tokens(out);
9332
+ const files = [];
9333
+ for (let i = 0; i < parts.length; ) {
9334
+ const code = parts[i++] ?? "";
9335
+ if (!code) continue;
9336
+ if (code[0] === "R" || code[0] === "C") {
9337
+ const oldPath = parts[i++] ?? "";
9338
+ const path11 = parts[i++] ?? "";
9339
+ files.push(
9340
+ code[0] === "R" ? { path: path11, oldPath, status: "renamed" } : { path: path11, status: "added" }
9372
9341
  );
9342
+ } else {
9343
+ files.push({ path: parts[i++] ?? "", status: statusOf(code) });
9373
9344
  }
9374
9345
  }
9375
- addTokens(goal, tokens) {
9376
- if (Number.isFinite(tokens) && tokens > 0) goal.limits.tokensUsed += Math.round(tokens);
9377
- }
9378
- async update(goal, patch) {
9379
- Object.assign(goal, patch);
9380
- goal.updatedAt = this.now().toISOString();
9381
- await this.options.store.save(goal);
9382
- this.options.events.emit({ type: "goal.updated", goal: structuredClone(goal) });
9383
- }
9384
- async finish(goal, status) {
9385
- delete goal.activeTaskId;
9386
- goal.finishedAt = this.now().toISOString();
9387
- await this.update(goal, { status });
9388
- this.log(
9389
- goal,
9390
- status === "succeeded" || status === "planned" ? "info" : "warn",
9391
- `goal ${status}${goal.lastError ? `: ${goal.lastError}` : ""} (${goal.limits.tokensUsed} tokens)`
9392
- );
9393
- return goal;
9394
- }
9395
- log(goal, level, message, role, taskId) {
9396
- this.options.events.emit({
9397
- type: "goal.log",
9398
- goalId: goal.id,
9399
- level,
9400
- message,
9401
- ...role ? { role } : {},
9402
- ...taskId ? { taskId } : {}
9346
+ return files.filter((f) => f.path.length > 0);
9347
+ }
9348
+ function parseNumStat(out) {
9349
+ const parts = tokens(out);
9350
+ const stats = /* @__PURE__ */ new Map();
9351
+ for (let i = 0; i < parts.length; ) {
9352
+ const head = parts[i++] ?? "";
9353
+ const match = /^(-|\d+)\t(-|\d+)\t(.*)$/s.exec(head);
9354
+ if (!match) continue;
9355
+ const [, added = "0", deleted = "0", inline = ""] = match;
9356
+ let path11 = inline;
9357
+ if (inline === "") {
9358
+ i++;
9359
+ path11 = parts[i++] ?? "";
9360
+ }
9361
+ const binary = added === "-" || deleted === "-";
9362
+ stats.set(path11, {
9363
+ additions: binary ? 0 : Number(added),
9364
+ deletions: binary ? 0 : Number(deleted),
9365
+ binary
9403
9366
  });
9404
9367
  }
9405
- };
9406
- function readyGoalTasks(goal) {
9407
- const status = new Map(goal.tasks.map((t) => [t.id, t.status]));
9408
- return goal.tasks.filter(
9409
- (t) => t.status === "PENDING" && t.dependsOn.every((d) => status.get(d) === "SUCCESS")
9410
- );
9368
+ return stats;
9411
9369
  }
9412
- function indent(text) {
9413
- return text.split("\n").map((l) => ` ${l}`).join("\n");
9370
+ function mergeFileLists(tracked, stats, untracked) {
9371
+ const files = tracked.map((file) => {
9372
+ const stat4 = stats.get(file.path) ?? { additions: 0, deletions: 0, binary: false };
9373
+ return { ...file, ...stat4 };
9374
+ });
9375
+ const seen = new Set(files.map((f) => f.path));
9376
+ for (const file of untracked) if (!seen.has(file.path)) files.push(file);
9377
+ return files.sort((a, b) => a.path.localeCompare(b.path));
9414
9378
  }
9415
- function stripFence(text) {
9416
- const match = /^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i.exec(text);
9417
- return match?.[1] ?? text;
9379
+ function looksBinary(content) {
9380
+ return content.subarray(0, 8e3).includes(0);
9418
9381
  }
9419
- var GoalError = class extends Error {
9420
- constructor(code, message) {
9421
- super(message);
9422
- this.code = code;
9423
- this.name = "GoalError";
9424
- this.statusCode = code === "not_found" ? 404 : code === "invalid" ? 400 : 409;
9382
+ function countLines(text) {
9383
+ if (text.length === 0) return 0;
9384
+ const breaks = text.split("\n").length - 1;
9385
+ return text.endsWith("\n") ? breaks : breaks + 1;
9386
+ }
9387
+ function newFilePatch(path11, content) {
9388
+ const header = `diff --git a/${path11} b/${path11}
9389
+ new file mode 100644
9390
+ `;
9391
+ if (!content || looksBinary(content)) {
9392
+ return `${header}Binary files /dev/null and b/${path11} differ
9393
+ `;
9425
9394
  }
9426
- code;
9427
- /** HTTP status hint for the gateway. */
9428
- statusCode;
9429
- };
9430
- var GoalService = class {
9431
- constructor(options) {
9432
- this.options = options;
9433
- this.owner = options.owner ?? "goal";
9434
- this.now = options.now ?? (() => /* @__PURE__ */ new Date());
9395
+ const text = content.toString("utf8");
9396
+ if (text.length === 0) return header;
9397
+ const lines2 = text.split("\n");
9398
+ const trailingNewline = text.endsWith("\n");
9399
+ if (trailingNewline) lines2.pop();
9400
+ const body = lines2.map((line) => `+${line}`).join("\n");
9401
+ const hunk = `@@ -0,0 +1,${lines2.length} @@
9402
+ ${body}
9403
+ `;
9404
+ return `${header}--- /dev/null
9405
+ +++ b/${path11}
9406
+ ${hunk}${trailingNewline ? "" : "\\n"}`;
9407
+ }
9408
+ function slugify(text, max = 40) {
9409
+ const slug = text.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/^[a-z]+(\([^)]*\))?!?:\s*/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/g, "");
9410
+ return slug || "changes";
9411
+ }
9412
+ function scrubUrls(text) {
9413
+ return text.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/gi, "$1***@");
9414
+ }
9415
+ var DOC = /(^|\/)docs?\/|\.(md|mdx|txt|rst)$/i;
9416
+ var TEST = /(^|\/)(__tests__|tests?)\/|\.(test|spec)\.[a-z0-9]+$/i;
9417
+ var PACKAGE = /^(?:packages|apps|libs|services)\/([^/]+)\//;
9418
+ var MAX_LISTED = 30;
9419
+ function basename3(path11) {
9420
+ return path11.split("/").at(-1) ?? path11;
9421
+ }
9422
+ function commitType(files) {
9423
+ if (files.every((f) => DOC.test(f.path))) return "docs";
9424
+ if (files.every((f) => TEST.test(f.path))) return "test";
9425
+ return "chore";
9426
+ }
9427
+ function commitScope(files) {
9428
+ const scopes = new Set(files.map((f) => PACKAGE.exec(f.path)?.[1]));
9429
+ if (scopes.size !== 1) return void 0;
9430
+ const [scope] = scopes;
9431
+ return scope;
9432
+ }
9433
+ function subject(files) {
9434
+ if (files.length === 1) {
9435
+ const [file] = files;
9436
+ const verb = file.status === "added" || file.status === "untracked" ? "add" : file.status === "deleted" ? "remove" : file.status === "renamed" ? "rename" : "update";
9437
+ return `${verb} ${basename3(file.path)}`;
9438
+ }
9439
+ return `update ${files.length} files`;
9440
+ }
9441
+ function fileLine(file) {
9442
+ const counts = file.binary ? "binary" : `+${file.additions} \u2212${file.deletions}`;
9443
+ const from = file.oldPath ? ` (from \`${file.oldPath}\`)` : "";
9444
+ return `- \`${file.path}\`${from} \u2014 ${counts}`;
9445
+ }
9446
+ function fallbackSuggestion(files) {
9447
+ if (files.length === 0) {
9448
+ return { message: "chore: update", title: "chore: update", body: "", by: "davecode" };
9449
+ }
9450
+ const scope = commitScope(files);
9451
+ const message = `${commitType(files)}${scope ? `(${scope})` : ""}: ${subject(files)}`;
9452
+ const listed = files.slice(0, MAX_LISTED).map(fileLine);
9453
+ if (files.length > MAX_LISTED) listed.push(`- \u2026and ${files.length - MAX_LISTED} more`);
9454
+ const body = `## Changes
9455
+
9456
+ ${listed.join("\n")}
9457
+ `;
9458
+ return { message, title: message, body, by: "davecode" };
9459
+ }
9460
+ function agentPrompt(input) {
9461
+ const max = CHANGES_LIMITS.suggestDiffMaxLength;
9462
+ const patch = input.patch.length > max ? `${input.patch.slice(0, max)}
9463
+ [patch truncated]` : input.patch;
9464
+ const files = input.files.map(
9465
+ (f) => `${f.status} ${f.path}${f.binary ? " (binary)" : ` (+${f.additions} -${f.deletions})`}`
9466
+ ).join("\n");
9467
+ return [
9468
+ "Write the git commit message and pull request text for the changes below.",
9469
+ "Do not modify any file and do not run any command: only read the patch.",
9470
+ "Reply with a single JSON object and nothing else (no code fence):",
9471
+ '{"message": "<Conventional Commits subject, imperative mood, at most 72 characters>",',
9472
+ ' "title": "<pull request title, at most 72 characters>",',
9473
+ ' "body": "<pull request description in Markdown: one short paragraph, then a bullet list of the main changes>"}',
9474
+ "Write in the same language as the code comments; default to English.",
9475
+ "",
9476
+ `Branch: ${input.branch ?? "(detached)"}`,
9477
+ "Changed files:",
9478
+ files,
9479
+ "",
9480
+ "Patch:",
9481
+ patch
9482
+ ].join("\n");
9483
+ }
9484
+ var agentReplySchema = z12.object({
9485
+ message: z12.string().trim().min(1).max(CHANGES_LIMITS.messageMaxLength),
9486
+ title: z12.string().trim().min(1).max(CHANGES_LIMITS.titleMaxLength),
9487
+ body: z12.string().max(CHANGES_LIMITS.bodyMaxLength).default("")
9488
+ });
9489
+ function parseAgentSuggestion(text) {
9490
+ const start = text.indexOf("{");
9491
+ const end = text.lastIndexOf("}");
9492
+ if (start < 0 || end <= start) return void 0;
9493
+ let json;
9494
+ try {
9495
+ json = JSON.parse(text.slice(start, end + 1));
9496
+ } catch {
9497
+ return void 0;
9435
9498
  }
9436
- options;
9437
- active;
9438
- owner;
9439
- now;
9440
- get activeGoalId() {
9441
- return this.active?.goal.id;
9499
+ const parsed = agentReplySchema.safeParse(json);
9500
+ return parsed.success ? parsed.data : void 0;
9501
+ }
9502
+ var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
9503
+ var FALLBACK_IDENTITY = ["-c", "user.name=DaveCode", "-c", "user.email=davecode@localhost"];
9504
+ var SLOW_MS = 10 * 6e4;
9505
+ function paneWorktreesDir(home, projectDir) {
9506
+ const key = createHash3("sha256").update(path9.resolve(projectDir)).digest("hex").slice(0, 16);
9507
+ return path9.join(home, "panes", key);
9508
+ }
9509
+ function paneWorkspacePath(home, projectDir, id) {
9510
+ if (!WORKSPACE_ID_PATTERN.test(id)) return void 0;
9511
+ return path9.join(paneWorktreesDir(home, projectDir), id);
9512
+ }
9513
+ var metaSchema = z13.object({
9514
+ id: z13.string().regex(WORKSPACE_ID_PATTERN),
9515
+ label: z13.string(),
9516
+ baseBranch: z13.string().optional(),
9517
+ baseCommit: z13.string(),
9518
+ createdAt: z13.string()
9519
+ });
9520
+ function short(err) {
9521
+ const raw = err instanceof GitError ? err.stderr || err.message : err instanceof Error ? err.message : String(err);
9522
+ const text = scrubSecrets(scrubUrls(raw));
9523
+ return text.length <= 300 ? text : `${text.slice(0, 299)}\u2026`;
9524
+ }
9525
+ function nul(out) {
9526
+ return out.split("\0").filter((f) => f.length > 0);
9527
+ }
9528
+ function samePath(a, b) {
9529
+ const real = (p) => {
9530
+ try {
9531
+ return realpathSync2.native(p);
9532
+ } catch {
9533
+ return path9.resolve(p);
9534
+ }
9535
+ };
9536
+ const [x, y] = [real(a), real(b)];
9537
+ return process.platform === "win32" ? x.toLowerCase() === y.toLowerCase() : x === y;
9538
+ }
9539
+ function createChangesService(options) {
9540
+ const projectDir = path9.resolve(options.projectDir);
9541
+ const panesDir = paneWorktreesDir(options.home, projectDir);
9542
+ const env = options.env ?? process.env;
9543
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
9544
+ const gitAt = (cwd, slow = false) => new Git(cwd, { env, ...slow ? { timeoutMs: SLOW_MS } : {} });
9545
+ const repo = gitAt(projectDir);
9546
+ let queue = Promise.resolve();
9547
+ const serial = (fn2) => {
9548
+ const next = queue.then(fn2, fn2);
9549
+ queue = next.catch(() => void 0);
9550
+ return next;
9551
+ };
9552
+ const identity = /* @__PURE__ */ new Map();
9553
+ async function identityArgs(g) {
9554
+ if (identity.get(g.cwd)) return [];
9555
+ try {
9556
+ await g.run(["var", "GIT_AUTHOR_IDENT"]);
9557
+ await g.run(["var", "GIT_COMMITTER_IDENT"]);
9558
+ identity.set(g.cwd, true);
9559
+ return [];
9560
+ } catch {
9561
+ return FALLBACK_IDENTITY;
9562
+ }
9442
9563
  }
9443
- /** Validates, persists and starts a goal in the background. Resolves with its initial state. */
9444
- async start(input) {
9445
- if (!input.objective.trim()) throw new GoalError("invalid", "the objective is empty");
9446
- if (this.active) {
9447
- throw new GoalError("busy", `goal ${this.active.goal.id} is still running; cancel it first`);
9564
+ async function assertRepo() {
9565
+ if (!await repo.isRepo()) {
9566
+ throw new ChangesError("not_a_repository", "This folder is not a git repository.");
9448
9567
  }
9449
- const lock = input.dryRun ? void 0 : this.options.lock;
9450
- if (lock && !lock.acquire(this.owner)) {
9451
- throw new GoalError(
9452
- "busy",
9453
- `the working tree is busy (${lock.holder ?? "another task"} is running)`
9454
- );
9568
+ try {
9569
+ return (await repo.run(["rev-parse", "--verify", "--quiet", "HEAD^{commit}"])).trim();
9570
+ } catch {
9571
+ throw new ChangesError("not_a_repository", "This repository has no commits yet.");
9455
9572
  }
9573
+ }
9574
+ const metaFile = (id) => path9.join(panesDir, `${id}.json`);
9575
+ function readMeta(id) {
9456
9576
  try {
9457
- const goal = this.options.orchestrator.create(input);
9458
- await this.options.store.save(goal);
9459
- this.launch(goal, lock);
9460
- return structuredClone(goal);
9461
- } catch (err) {
9462
- lock?.release(this.owner);
9463
- throw err;
9577
+ const parsed = metaSchema.safeParse(JSON.parse(readFileSync4(metaFile(id), "utf8")));
9578
+ return parsed.success && parsed.data.id === id ? parsed.data : void 0;
9579
+ } catch {
9580
+ return void 0;
9464
9581
  }
9465
9582
  }
9466
- /**
9467
- * Executes a `planned` (dry-run) goal in the background without re-planning round 1.
9468
- * Resolves with the updated goal (`status: 'running'`).
9469
- */
9470
- async execute(id) {
9471
- if (this.active) {
9472
- throw new GoalError("busy", `goal ${this.active.goal.id} is still running; cancel it first`);
9583
+ function isolatedIds() {
9584
+ if (!existsSync5(panesDir)) return [];
9585
+ return readdirSync(panesDir).filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -5)).filter((id) => WORKSPACE_ID_PATTERN.test(id) && existsSync5(path9.join(panesDir, id)));
9586
+ }
9587
+ function target(id) {
9588
+ if (id === MAIN_WORKSPACE_ID) {
9589
+ return { id, label: path9.basename(projectDir), path: projectDir, isolated: false };
9473
9590
  }
9474
- const goal = await this.options.store.load(id);
9475
- if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
9476
- if (goal.status !== "planned") {
9477
- throw new GoalError(
9478
- "invalid",
9479
- `goal "${id}" is ${goal.status}; only a planned goal can be executed`
9480
- );
9481
- }
9482
- const lock = this.options.lock;
9483
- if (lock && !lock.acquire(this.owner)) {
9484
- throw new GoalError(
9485
- "busy",
9486
- `the working tree is busy (${lock.holder ?? "another task"} is running)`
9487
- );
9488
- }
9489
- try {
9490
- delete goal.dryRun;
9491
- delete goal.finishedAt;
9492
- delete goal.lastError;
9493
- goal.status = "running";
9494
- goal.updatedAt = this.now().toISOString();
9495
- await this.options.store.save(goal);
9496
- this.launch(goal, lock);
9497
- return structuredClone(goal);
9498
- } catch (err) {
9499
- lock?.release(this.owner);
9500
- throw err;
9591
+ const meta = WORKSPACE_ID_PATTERN.test(id) ? readMeta(id) : void 0;
9592
+ const dir = path9.join(panesDir, id);
9593
+ if (!meta || !existsSync5(dir)) throw new ChangesError("not_found", `unknown workspace "${id}"`);
9594
+ return { id, label: meta.label, path: dir, isolated: true, meta };
9595
+ }
9596
+ async function optional(g, args) {
9597
+ try {
9598
+ const out = (await g.run(args)).trim();
9599
+ return out || void 0;
9600
+ } catch {
9601
+ return void 0;
9501
9602
  }
9502
9603
  }
9503
- /** Reads the markdown artifact produced by `taskId` in goal `id`. */
9504
- async readArtifact(id, taskId) {
9505
- const goal = await this.get(id);
9506
- if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
9507
- const task = goal.tasks.find((t) => t.id === taskId);
9508
- if (!task?.artifact) {
9509
- throw new GoalError("not_found", `task "${taskId}" has no artifact in goal "${id}"`);
9510
- }
9511
- const content = await this.options.store.readArtifact(id, taskId);
9512
- if (content === void 0) {
9513
- throw new GoalError("not_found", `artifact file for task "${taskId}" was not found`);
9604
+ async function count(g, range) {
9605
+ const out = await optional(g, ["rev-list", "--count", ...range]);
9606
+ return out ? Number(out) || 0 : 0;
9607
+ }
9608
+ async function diffBase(g, t) {
9609
+ const head = await optional(g, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]);
9610
+ if (!head) return EMPTY_TREE;
9611
+ if (!t.isolated || !t.meta) return head;
9612
+ if (t.meta.baseBranch) {
9613
+ const base = await optional(g, ["merge-base", `refs/heads/${t.meta.baseBranch}`, "HEAD"]);
9614
+ if (base) return base;
9514
9615
  }
9515
- return { taskId: task.id, path: task.artifact, content };
9616
+ return await optional(g, ["merge-base", t.meta.baseCommit, "HEAD"]) ?? t.meta.baseCommit;
9516
9617
  }
9517
- launch(goal, lock) {
9518
- const controller = new AbortController();
9519
- const done = this.options.orchestrator.run(goal, controller.signal).catch((err) => {
9520
- goal.status = "failed";
9521
- goal.lastError = err instanceof Error ? err.message : String(err);
9522
- return goal;
9523
- }).finally(() => {
9524
- if (this.active?.goal === goal) this.active = void 0;
9525
- lock?.release(this.owner);
9618
+ async function untrackedFiles(g, root) {
9619
+ const paths = nul(await g.run(["ls-files", "--others", "--exclude-standard", "-z"]));
9620
+ return paths.map((rel) => {
9621
+ const content = readUntracked(root, rel);
9622
+ const binary = !content || looksBinary(content);
9623
+ return {
9624
+ path: rel,
9625
+ status: "untracked",
9626
+ additions: binary ? 0 : countLines(content.toString("utf8")),
9627
+ deletions: 0,
9628
+ binary
9629
+ };
9526
9630
  });
9527
- this.active = { goal, controller, done };
9528
9631
  }
9529
- async get(id) {
9530
- if (this.active?.goal.id === id) return structuredClone(this.active.goal);
9531
- return this.options.store.load(id);
9632
+ function readUntracked(root, rel) {
9633
+ const file = path9.join(root, rel);
9634
+ try {
9635
+ if (statSync4(file).size > CHANGES_LIMITS.untrackedMaxBytes) return void 0;
9636
+ return readFileSync4(file);
9637
+ } catch {
9638
+ return void 0;
9639
+ }
9532
9640
  }
9533
- /** Every goal of the workspace, newest first. */
9534
- async list() {
9535
- const stored = await this.options.store.list();
9536
- const active = this.active?.goal;
9537
- if (!active) return stored;
9538
- const rest = stored.filter((g) => g.id !== active.id);
9539
- return [structuredClone(active), ...rest];
9641
+ async function changedFiles(g, t) {
9642
+ const base = await diffBase(g, t);
9643
+ const common = ["-c", "core.quotepath=false", "diff", "--no-ext-diff", "-M", "-z"];
9644
+ const [names, stats, untracked] = await Promise.all([
9645
+ g.run([...common, "--name-status", base]),
9646
+ g.run([...common, "--numstat", base]),
9647
+ untrackedFiles(g, t.path)
9648
+ ]);
9649
+ return { base, files: mergeFileLists(parseNameStatus(names), parseNumStat(stats), untracked) };
9650
+ }
9651
+ async function describe(t) {
9652
+ const g = gitAt(t.path);
9653
+ const [branch, { files }, porcelain, hasRemote] = await Promise.all([
9654
+ optional(g, ["symbolic-ref", "--quiet", "--short", "HEAD"]),
9655
+ changedFiles(g, t),
9656
+ g.run(["status", "--porcelain", "-z", "--untracked-files=all"]),
9657
+ g.hasRemote()
9658
+ ]);
9659
+ const upstream = branch ? await optional(g, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]) : void 0;
9660
+ let ahead = 0;
9661
+ let unpushed = 0;
9662
+ if (t.isolated && t.meta) {
9663
+ const base = t.meta.baseBranch ? `refs/heads/${t.meta.baseBranch}` : t.meta.baseCommit;
9664
+ ahead = await count(g, [`${base}..HEAD`]);
9665
+ }
9666
+ if (upstream) unpushed = await count(g, ["@{u}..HEAD"]);
9667
+ else if (t.isolated) unpushed = ahead;
9668
+ else if (hasRemote) unpushed = await count(g, ["HEAD", "--not", "--remotes"]);
9669
+ if (!t.isolated) ahead = unpushed;
9670
+ return {
9671
+ id: t.id,
9672
+ label: t.label,
9673
+ isolated: t.isolated,
9674
+ path: t.path,
9675
+ ...branch ? { branch } : {},
9676
+ ...t.meta?.baseBranch ? { baseBranch: t.meta.baseBranch } : {},
9677
+ files,
9678
+ additions: files.reduce((sum, f) => sum + f.additions, 0),
9679
+ deletions: files.reduce((sum, f) => sum + f.deletions, 0),
9680
+ dirty: porcelain.length > 0,
9681
+ ahead,
9682
+ ...upstream ? { upstream } : {},
9683
+ unpushed,
9684
+ hasRemote,
9685
+ ...t.meta ? { createdAt: t.meta.createdAt } : {}
9686
+ };
9540
9687
  }
9541
- /**
9542
- * Cancels a running goal and resolves once it has stopped. A goal left unfinished by an earlier
9543
- * process is marked cancelled; a finished goal is returned unchanged.
9544
- */
9545
- async cancel(id) {
9546
- if (this.active?.goal.id === id) {
9547
- this.active.controller.abort();
9548
- return structuredClone(await this.active.done);
9688
+ async function uniqueBranch(g, wanted) {
9689
+ for (let n = 1; n < 100; n++) {
9690
+ const name = n === 1 ? wanted : `${wanted}-${n}`;
9691
+ if (!await g.branchExists(name)) return name;
9549
9692
  }
9550
- const goal = await this.options.store.load(id);
9551
- if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
9552
- if (isTerminalGoal(goal)) return goal;
9553
- const stamp = this.now().toISOString();
9554
- goal.status = "cancelled";
9555
- goal.updatedAt = stamp;
9556
- goal.finishedAt = stamp;
9557
- delete goal.activeTaskId;
9558
- await this.options.store.save(goal);
9559
- return goal;
9693
+ return `${wanted}-${randomBytes9(3).toString("hex")}`;
9560
9694
  }
9561
- /** Resolves when the goal is finished (immediately for stored goals). */
9562
- async wait(id) {
9563
- if (this.active?.goal.id === id) return structuredClone(await this.active.done);
9564
- return this.options.store.load(id);
9565
- }
9566
- };
9567
- var GOAL_ID = /^goal_[a-z0-9]{6,32}$/;
9568
- var goalTaskSchema = z11.object({
9569
- id: z11.string().min(1),
9570
- title: z11.string(),
9571
- description: z11.string().optional(),
9572
- role: z11.string(),
9573
- kind: z11.enum(["code", "artifact"]),
9574
- status: taskStatusSchema,
9575
- dependsOn: z11.array(z11.string()),
9576
- acceptance: z11.array(z11.string()).optional(),
9577
- round: z11.number().int().min(1),
9578
- attempts: z11.number().int().min(0),
9579
- artifact: z11.string().optional(),
9580
- runnerTaskId: z11.string().optional(),
9581
- notes: z11.string().optional()
9582
- });
9583
- var goalSchema = z11.object({
9584
- id: z11.string().regex(GOAL_ID),
9585
- objective: z11.string().min(1),
9586
- status: z11.enum([
9587
- "planning",
9588
- "running",
9589
- "reviewing",
9590
- "planned",
9591
- "succeeded",
9592
- "failed",
9593
- "cancelled",
9594
- "budget_exhausted"
9595
- ]),
9596
- round: z11.number().int().min(0),
9597
- limits: z11.object({
9598
- maxRounds: z11.number().int().min(1),
9599
- maxTasks: z11.number().int().min(1),
9600
- maxTokens: z11.number().int().min(1),
9601
- tokensUsed: z11.number().int().min(0)
9602
- }),
9603
- roles: z11.array(
9604
- z11.object({ id: z11.string(), description: z11.string(), route: z11.string().optional() })
9605
- ),
9606
- tasks: z11.array(goalTaskSchema),
9607
- activeTaskId: z11.string().optional(),
9608
- summary: z11.string().optional(),
9609
- lastError: z11.string().optional(),
9610
- dryRun: z11.boolean().optional(),
9611
- createdAt: z11.string(),
9612
- updatedAt: z11.string(),
9613
- finishedAt: z11.string().optional()
9614
- });
9615
- function isGoalId(id) {
9616
- return GOAL_ID.test(id);
9617
- }
9618
- var GoalStore = class {
9619
- constructor(root) {
9620
- this.root = root;
9621
- this.dir = join7(projectPaths(root).dir, "goals");
9695
+ async function defaultBranch(g) {
9696
+ const head = await optional(g, [
9697
+ "symbolic-ref",
9698
+ "--quiet",
9699
+ "--short",
9700
+ "refs/remotes/origin/HEAD"
9701
+ ]);
9702
+ if (head?.startsWith("origin/")) return head.slice("origin/".length);
9703
+ for (const name of ["main", "master"]) if (await g.branchExists(name)) return name;
9704
+ return void 0;
9622
9705
  }
9623
- root;
9624
- dir;
9625
- goalDir(id) {
9626
- if (!isGoalId(id)) throw new Error(`invalid goal id: ${id}`);
9627
- return join7(this.dir, id);
9706
+ function runGh(cwd, args) {
9707
+ if (options.runGh) return options.runGh(cwd, args);
9708
+ const gh = resolveOnPath(options.gh ?? "gh", env);
9709
+ if (/\.(cmd|bat)$/i.test(gh)) {
9710
+ return Promise.reject(new Error("gh is a shell shim; DaveCode will not pass text to it"));
9711
+ }
9712
+ return new Promise((resolve6, reject) => {
9713
+ execFile2(
9714
+ gh,
9715
+ args,
9716
+ {
9717
+ cwd,
9718
+ env: { ...env, GH_PROMPT_DISABLED: "1" },
9719
+ timeout: SLOW_MS,
9720
+ windowsHide: true,
9721
+ encoding: "utf8"
9722
+ },
9723
+ (err, stdout, stderr) => {
9724
+ if (err) {
9725
+ const missing = err.code === "ENOENT";
9726
+ reject(
9727
+ new Error(
9728
+ missing ? "GitHub CLI (gh) is not installed. Install it and run `gh auth login`." : (stderr || err.message).trim()
9729
+ )
9730
+ );
9731
+ } else resolve6(stdout.trim());
9732
+ }
9733
+ );
9734
+ });
9628
9735
  }
9629
- async save(goal) {
9630
- await atomicWriteFile(
9631
- join7(this.goalDir(goal.id), "goal.json"),
9632
- `${JSON.stringify(goal, null, 2)}
9633
- `
9634
- );
9736
+ async function commitAll(g, message) {
9737
+ await g.run(["add", "-A"]);
9738
+ if (!await g.hasStagedChanges()) return void 0;
9739
+ await g.run([...await identityArgs(g), "commit", "--quiet", "-m", message]);
9740
+ return (await g.run(["rev-parse", "--short", "HEAD"])).trim();
9635
9741
  }
9636
- /** The goal, or `undefined` when unknown. Throws when `goal.json` is corrupt. */
9637
- async load(id) {
9638
- if (!isGoalId(id)) return void 0;
9639
- const text = await readFileIfExists(join7(this.goalDir(id), "goal.json"));
9640
- if (text === void 0) return void 0;
9641
- return goalSchema.parse(JSON.parse(text));
9742
+ function writeMeta(meta) {
9743
+ mkdirSync8(panesDir, { recursive: true });
9744
+ writeFileSync4(metaFile(meta.id), `${JSON.stringify(meta, null, 2)}
9745
+ `);
9642
9746
  }
9643
- /** Every readable goal, newest first. Corrupt entries are skipped. */
9644
- async list() {
9645
- let names;
9747
+ async function removeWorktree(dir) {
9646
9748
  try {
9647
- names = await readdir3(this.dir);
9648
- } catch (err) {
9649
- if (isNodeError(err, "ENOENT")) return [];
9650
- throw err;
9749
+ await repo.run(["worktree", "remove", "--force", dir]);
9750
+ } catch {
9651
9751
  }
9652
- const goals = [];
9653
- for (const name of names.filter(isGoalId)) {
9654
- try {
9655
- const goal = await this.load(name);
9656
- if (goal) goals.push(goal);
9657
- } catch {
9658
- }
9752
+ if (existsSync5(dir))
9753
+ rmSync5(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
9754
+ try {
9755
+ await repo.run(["worktree", "prune"]);
9756
+ } catch {
9659
9757
  }
9660
- return goals.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
9661
- }
9662
- /** Writes a task's markdown artifact and returns its repository-relative path (`/`-separated). */
9663
- async writeArtifact(goalId, taskId, markdown) {
9664
- const path10 = join7(this.goalDir(goalId), `${taskId}.md`);
9665
- await atomicWriteFile(path10, markdown.endsWith("\n") ? markdown : `${markdown}
9666
- `);
9667
- return relative2(this.root, path10).split("\\").join("/");
9668
- }
9669
- async readArtifact(goalId, taskId) {
9670
- if (!isGoalId(goalId) || !/^[-a-zA-Z0-9_]{1,64}$/.test(taskId)) return void 0;
9671
- return readFileIfExists(join7(this.goalDir(goalId), `${taskId}.md`));
9672
9758
  }
9673
- };
9674
- var PROJECT_CONTEXT_CHARS = 3e3;
9675
- function createGoalService(engine, opts) {
9676
- const { brain } = opts;
9677
- const lock = opts.lock ?? new WorkspaceLock();
9678
- const store = new GoalStore(brain.root);
9679
- const codeRunner = new RunnerCodeTasks({
9680
- engine,
9681
- brain,
9682
- lock,
9683
- ...opts.budgetContext ? { contextExtras: opts.budgetContext } : {},
9684
- ...opts.executor ? { executor: opts.executor } : {}
9685
- });
9686
- const projectContext = async () => {
9687
- if (!await brain.isInitialised()) return "";
9688
- const [architecture, state] = await Promise.all([brain.readArchitecture(), brain.readState()]);
9689
- return [
9690
- `ARCHITECTURE.md:
9691
- ${truncateHead(architecture.trim(), PROJECT_CONTEXT_CHARS)}`,
9692
- `STATE.md:
9693
- ${truncateHead(state.trim(), PROJECT_CONTEXT_CHARS)}`
9694
- ].join("\n\n");
9759
+ const service = {
9760
+ async list() {
9761
+ await assertRepo();
9762
+ const isolated = isolatedIds().map((id) => target(id)).sort((a, b) => (a.meta?.createdAt ?? "").localeCompare(b.meta?.createdAt ?? ""));
9763
+ return Promise.all([target(MAIN_WORKSPACE_ID), ...isolated].map(describe));
9764
+ },
9765
+ async get(id) {
9766
+ await assertRepo();
9767
+ return describe(target(id));
9768
+ },
9769
+ create(input = {}) {
9770
+ return serial(async () => {
9771
+ const head = await assertRepo();
9772
+ if (isolatedIds().length >= CHANGES_LIMITS.maxWorkspaces) {
9773
+ throw new ChangesError(
9774
+ "limit",
9775
+ `At most ${CHANGES_LIMITS.maxWorkspaces} isolated workspaces per project. Remove one first.`
9776
+ );
9777
+ }
9778
+ const id = randomBytes9(4).toString("hex").slice(0, 6);
9779
+ const dir = path9.join(panesDir, id);
9780
+ const baseBranch = await optional(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
9781
+ const branch = await uniqueBranch(repo, `${PANE_BRANCH_PREFIX}${id}`);
9782
+ mkdirSync8(panesDir, { recursive: true });
9783
+ await repo.run(["worktree", "add", "-b", assertSafeRef(branch), dir, head]);
9784
+ writeMeta({
9785
+ id,
9786
+ label: input.label ?? "Isolated",
9787
+ ...baseBranch ? { baseBranch } : {},
9788
+ baseCommit: head,
9789
+ createdAt: now().toISOString()
9790
+ });
9791
+ return describe(target(id));
9792
+ });
9793
+ },
9794
+ async diff(id, file) {
9795
+ await assertRepo();
9796
+ const t = target(id);
9797
+ const g = gitAt(t.path);
9798
+ const { base, files } = await changedFiles(g, t);
9799
+ let selected = files;
9800
+ if (file !== void 0) {
9801
+ selected = files.filter((f) => f.path === file || f.oldPath === file);
9802
+ if (selected.length === 0) throw new ChangesError("not_found", `"${file}" has no changes`);
9803
+ }
9804
+ const tracked = selected.filter((f) => f.status !== "untracked");
9805
+ let patch = "";
9806
+ if (tracked.length > 0) {
9807
+ const paths = tracked.flatMap((f) => f.oldPath ? [f.oldPath, f.path] : [f.path]);
9808
+ patch = await g.run([
9809
+ "-c",
9810
+ "core.quotepath=false",
9811
+ "diff",
9812
+ "--no-ext-diff",
9813
+ "--no-color",
9814
+ "-M",
9815
+ base,
9816
+ "--",
9817
+ ...file === void 0 ? [] : paths
9818
+ ]);
9819
+ }
9820
+ for (const f of selected) {
9821
+ if (f.status === "untracked") patch += newFilePatch(f.path, readUntracked(t.path, f.path));
9822
+ }
9823
+ const truncated = patch.length > CHANGES_LIMITS.diffMaxLength;
9824
+ return { patch: truncated ? patch.slice(0, CHANGES_LIMITS.diffMaxLength) : patch, truncated };
9825
+ },
9826
+ async suggest(id, input = {}) {
9827
+ const { agent } = suggestSchema.parse(input);
9828
+ await assertRepo();
9829
+ const t = target(id);
9830
+ const workspace = await describe(t);
9831
+ if (workspace.files.length === 0) {
9832
+ throw new ChangesError("nothing_to_commit", "There are no changes to describe.");
9833
+ }
9834
+ const fallback = fallbackSuggestion(workspace.files);
9835
+ if (!agent) return fallback;
9836
+ if (!options.writer) {
9837
+ throw new ChangesError("unavailable", "No agent can write the message on this gateway.");
9838
+ }
9839
+ const { patch } = await service.diff(id);
9840
+ const controller = new AbortController();
9841
+ const result = await options.writer.run({
9842
+ agent,
9843
+ cwd: t.path,
9844
+ prompt: agentPrompt({
9845
+ ...workspace.branch ? { branch: workspace.branch } : {},
9846
+ files: workspace.files,
9847
+ patch
9848
+ }),
9849
+ mode: "plan",
9850
+ signal: controller.signal,
9851
+ timeoutMs: CHANGES_LIMITS.suggestTimeoutMs,
9852
+ onOutput: () => {
9853
+ }
9854
+ });
9855
+ const parsed = result.ok ? parseAgentSuggestion(result.text) : void 0;
9856
+ if (!parsed) {
9857
+ const why = result.ok ? "it did not reply with the expected JSON" : result.error ?? "it failed";
9858
+ throw new ChangesError("unavailable", `${agent} could not write the message: ${why}`);
9859
+ }
9860
+ return { ...parsed, by: agent };
9861
+ },
9862
+ ship(id, input) {
9863
+ const request = shipSchema.parse(input);
9864
+ return serial(async () => {
9865
+ await assertRepo();
9866
+ const t = target(id);
9867
+ const g = gitAt(t.path, true);
9868
+ const steps = [];
9869
+ let branch = await optional(g, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
9870
+ let commit;
9871
+ let prUrl;
9872
+ const wantPush = request.push || request.pr !== void 0;
9873
+ if (!branch && (wantPush || request.pr)) {
9874
+ throw new ChangesError("invalid", "HEAD is detached. Check out a branch first.");
9875
+ }
9876
+ const finish = async (ok) => ({
9877
+ ok,
9878
+ steps,
9879
+ ...commit ? { commit } : {},
9880
+ ...branch ? { branch } : {},
9881
+ ...prUrl ? { prUrl } : {},
9882
+ workspace: await describe(t)
9883
+ });
9884
+ const step = async (phase, work) => {
9885
+ try {
9886
+ steps.push({ phase, ok: true, detail: await work() });
9887
+ return true;
9888
+ } catch (err) {
9889
+ steps.push({ phase, ok: false, detail: short(err) });
9890
+ return false;
9891
+ }
9892
+ };
9893
+ const before = await describe(t);
9894
+ if (request.message && !before.dirty && !wantPush) {
9895
+ throw new ChangesError("nothing_to_commit", "There is nothing to commit.");
9896
+ }
9897
+ if (!request.message && !wantPush) {
9898
+ throw new ChangesError("invalid", "Give a commit message, push or a pull request.");
9899
+ }
9900
+ const base = request.pr?.base ?? (t.isolated ? t.meta?.baseBranch : void 0) ?? (request.pr ? await defaultBranch(g) : void 0);
9901
+ if (request.pr && branch) {
9902
+ const current = branch;
9903
+ const slug = slugify(request.pr.title);
9904
+ if (!t.isolated && current === base) {
9905
+ const ok = await step("branch", async () => {
9906
+ const name = await uniqueBranch(g, `davecode/${slug}`);
9907
+ await g.run(["switch", "--quiet", "-c", assertSafeRef(name)]);
9908
+ branch = name;
9909
+ return `Created ${name} from ${current}`;
9910
+ });
9911
+ if (!ok) return finish(false);
9912
+ } else if (t.isolated && current.startsWith(PANE_BRANCH_PREFIX) && !before.upstream) {
9913
+ const ok = await step("branch", async () => {
9914
+ const name = await uniqueBranch(g, `davecode/${slug}`);
9915
+ await g.run(["branch", "-m", assertSafeRef(current), assertSafeRef(name)]);
9916
+ branch = name;
9917
+ return `Renamed ${current} to ${name}`;
9918
+ });
9919
+ if (!ok) return finish(false);
9920
+ }
9921
+ }
9922
+ if (request.message && before.dirty) {
9923
+ const message = request.message;
9924
+ const ok = await step("commit", async () => {
9925
+ commit = await commitAll(g, message);
9926
+ if (!commit) throw new Error("Nothing was staged.");
9927
+ return `Committed ${commit}`;
9928
+ });
9929
+ if (!ok) return finish(false);
9930
+ }
9931
+ if (wantPush && branch) {
9932
+ const name = branch;
9933
+ const ok = await step("push", async () => {
9934
+ if (!await g.hasRemote()) throw new Error('This repository has no "origin" remote.');
9935
+ await g.run(["push", "--quiet", "--set-upstream", "origin", assertSafeRef(name)]);
9936
+ return `Pushed ${name} to origin`;
9937
+ });
9938
+ if (!ok) return finish(false);
9939
+ }
9940
+ if (request.pr && branch) {
9941
+ const pr = request.pr;
9942
+ const head = branch;
9943
+ const ok = await step("pr", async () => {
9944
+ if (!base) throw new Error("Could not tell which branch the pull request targets.");
9945
+ if (base === head) throw new Error(`The pull request would merge ${head} into itself.`);
9946
+ const args = [
9947
+ "pr",
9948
+ "create",
9949
+ "--base",
9950
+ assertSafeRef(base),
9951
+ "--head",
9952
+ assertSafeRef(head)
9953
+ ];
9954
+ args.push("--title", pr.title, "--body", pr.body || pr.title);
9955
+ if (pr.draft) args.push("--draft");
9956
+ try {
9957
+ const out = await runGh(t.path, args);
9958
+ prUrl = out.split("\n").at(-1)?.trim() || out;
9959
+ } catch (err) {
9960
+ const text = err instanceof Error ? err.message : String(err);
9961
+ if (!/already exists/i.test(text)) throw err;
9962
+ prUrl = await runGh(t.path, ["pr", "view", head, "--json", "url", "--jq", ".url"]);
9963
+ }
9964
+ return prUrl;
9965
+ });
9966
+ if (!ok) return finish(false);
9967
+ }
9968
+ return finish(true);
9969
+ });
9970
+ },
9971
+ merge(id, input = {}) {
9972
+ const { message } = mergeSchema.parse(input);
9973
+ return serial(async () => {
9974
+ await assertRepo();
9975
+ const t = target(id);
9976
+ if (!t.isolated)
9977
+ throw new ChangesError("invalid", "Only an isolated workspace can be merged.");
9978
+ const g = gitAt(t.path, true);
9979
+ const branch = await optional(g, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
9980
+ if (!branch) throw new ChangesError("invalid", "The workspace has no branch checked out.");
9981
+ let state = await describe(t);
9982
+ if (state.dirty) {
9983
+ if (!message) {
9984
+ throw new ChangesError(
9985
+ "dirty",
9986
+ "The workspace has uncommitted changes. Commit them first."
9987
+ );
9988
+ }
9989
+ await commitAll(g, message);
9990
+ state = await describe(t);
9991
+ }
9992
+ if (state.ahead === 0)
9993
+ throw new ChangesError("nothing_to_commit", "There is nothing to merge.");
9994
+ const into = await optional(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
9995
+ if (!into) throw new ChangesError("invalid", "The project checkout is on a detached HEAD.");
9996
+ if (samePath(t.path, projectDir))
9997
+ throw new ChangesError("invalid", "Cannot merge into itself.");
9998
+ const main = gitAt(projectDir, true);
9999
+ try {
10000
+ await main.run([
10001
+ ...await identityArgs(main),
10002
+ "merge",
10003
+ "--no-ff",
10004
+ "--no-edit",
10005
+ "-m",
10006
+ `Merge ${t.label} (${branch}) into ${into}`,
10007
+ assertSafeRef(branch)
10008
+ ]);
10009
+ } catch (err) {
10010
+ const conflicts = nul(
10011
+ await optional(main, ["diff", "--name-only", "-z", "--diff-filter=U"]) ?? ""
10012
+ );
10013
+ await main.abortMerge();
10014
+ throw new ChangesError(
10015
+ "conflict",
10016
+ conflicts.length > 0 ? `The merge conflicts in ${conflicts.join(", ")}. Nothing was changed.` : `The merge failed: ${short(err)}`
10017
+ );
10018
+ }
10019
+ return describe(target(MAIN_WORKSPACE_ID));
10020
+ });
10021
+ },
10022
+ remove(id, input = {}) {
10023
+ const { deleteBranch } = removeSchema.parse(input);
10024
+ return serial(async () => {
10025
+ const t = target(id);
10026
+ if (!t.isolated)
10027
+ throw new ChangesError("invalid", "The project checkout cannot be removed.");
10028
+ const branch = await optional(gitAt(t.path), [
10029
+ "symbolic-ref",
10030
+ "--quiet",
10031
+ "--short",
10032
+ "HEAD"
10033
+ ]);
10034
+ await removeWorktree(t.path);
10035
+ rmSync5(metaFile(id), { force: true });
10036
+ if (deleteBranch && branch) {
10037
+ try {
10038
+ await repo.run(["branch", "-D", assertSafeRef(branch)]);
10039
+ } catch {
10040
+ }
10041
+ }
10042
+ });
10043
+ }
9695
10044
  };
9696
- const orchestrator = new GoalOrchestrator({
9697
- router: engine.router,
9698
- store,
9699
- events: engine.events,
9700
- config: engine.config.goal,
9701
- codeRunner,
9702
- projectContext,
9703
- ...opts.budgetContext ? { budgetContext: opts.budgetContext } : {}
9704
- });
9705
- return new GoalService({ orchestrator, store, lock });
10045
+ return service;
9706
10046
  }
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"
10047
+ var DEFAULT_ROLES = [
10048
+ {
10049
+ id: "planner",
10050
+ description: "Breaks the objective into a plan, defines milestones and success criteria.",
10051
+ kind: "artifact"
9749
10052
  },
9750
- codex: {
9751
- label: "Codex",
9752
- binary: "codex",
9753
- provider: "codex-cli",
9754
- signIn: "run `codex login` in a terminal"
10053
+ {
10054
+ id: "researcher",
10055
+ description: "Analyses markets, users, competitors and options from its own knowledge and the workspace; cannot browse the web.",
10056
+ kind: "artifact"
9755
10057
  },
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);
10058
+ {
10059
+ id: "implementer",
10060
+ description: "Writes and changes code in the repository (handed to the autonomous runner).",
10061
+ kind: "code"
10062
+ },
10063
+ {
10064
+ id: "reviewer",
10065
+ description: "Critically reviews other outputs for gaps, risks and quality.",
10066
+ kind: "artifact"
10067
+ },
10068
+ {
10069
+ id: "writer",
10070
+ description: "Writes positioning, marketing copy, landing-page text, documentation and drafts of messages.",
10071
+ kind: "artifact"
9808
10072
  }
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
- `;
10073
+ ];
10074
+ function resolveRoles(config) {
10075
+ const roles = new Map(DEFAULT_ROLES.map((r) => [r.id, { ...r }]));
10076
+ for (const [id, override] of Object.entries(config.roles)) {
10077
+ const base = roles.get(id);
10078
+ const role = {
10079
+ id,
10080
+ description: override.description ?? base?.description ?? id,
10081
+ kind: override.kind ?? base?.kind ?? "artifact"
10082
+ };
10083
+ if (override.route) role.route = override.route;
10084
+ const instructions = override.instructions ?? base?.instructions;
10085
+ if (instructions) role.instructions = instructions;
10086
+ roles.set(id, role);
9817
10087
  }
9818
- if (agent === "claude" && prompt.trimStart().startsWith("/")) return `Task:
9819
- ${prompt}`;
9820
- return prompt;
10088
+ return [...roles.values()];
9821
10089
  }
9822
- function abortError() {
9823
- const err = new Error("The agent run was aborted");
9824
- err.name = "AbortError";
9825
- return err;
10090
+ function publicRoles(roles) {
10091
+ return roles.map(
10092
+ ({ id, description, route }) => route ? { id, description, route } : { id, description }
10093
+ );
9826
10094
  }
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
- ]
10095
+ var OUTBOUND = [
10096
+ /\b(send|sending|email|e-mail|emailing|mail out)\b/i,
10097
+ /\b(tweet|dm|direct message|share on)\b/i,
10098
+ /\b(post|publish)\b[^.\n]{0,60}\b(on|to)\b/i,
10099
+ /\b(pay|purchase|buy|checkout|subscribe to|sign up|signup|register (for|on|at))\b/i,
10100
+ /\b(outreach|cold[- ]?(call|email)|contact (the|our|potential|prospects?|customers?|leads?))\b/i,
10101
+ /\b(launch (the )?ads?|run (the )?ads?|ad campaign|deploy to production|go live)\b/i
9838
10102
  ];
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`;
10103
+ var OPERATIONAL = [
10104
+ /\b(deploy|release|publish)\b[^.\n]*\b(production|prod|npm|app store|play store|live)\b/i,
10105
+ /\b(go live|pay|purchase|buy|checkout)\b/i,
10106
+ /\b(send|email|e-mail|message)\b[^.\n]*\b(customers?|users?|leads?|prospects?|subscribers?|mailing list|newsletter)\b/i,
10107
+ /\b(post|tweet|publish)\b[^.\n]*\b(twitter|x\.com|linkedin|facebook|instagram|reddit|hacker news|product hunt)\b/i
10108
+ ];
10109
+ function firstMatch(patterns, text) {
10110
+ for (const pattern of patterns) {
10111
+ const match = pattern.exec(text);
10112
+ if (match) return match[0];
10113
+ }
10114
+ return void 0;
9846
10115
  }
9847
- function shortError(text) {
9848
- return clip(scrubSecrets(text), MAX_ERROR);
10116
+ function outboundAction(text) {
10117
+ return firstMatch(OUTBOUND, text);
9849
10118
  }
9850
- function formatDuration(ms) {
9851
- if (ms >= 6e4) return `${Math.round(ms / 6e4)} min`;
9852
- return `${Math.max(1, Math.ceil(ms / 1e3))} s`;
10119
+ var DRAFT_NOTICE = "DaveCode never performs outbound actions (emails, posts, payments, sign-ups, deployments). Produce a ready-to-use DRAFT for a human to review and execute.";
10120
+ function applySafetyGuard(task) {
10121
+ const text = `${task.title}
10122
+ ${task.description ?? ""}`;
10123
+ const action = task.kind === "code" ? firstMatch(OPERATIONAL, text) : firstMatch(OUTBOUND, text);
10124
+ if (!action) return task;
10125
+ const title = /^draft\b/i.test(task.title) ? task.title : `Draft: ${task.title}`;
10126
+ const description = [task.description?.trim(), DRAFT_NOTICE].filter(Boolean).join("\n\n");
10127
+ return { ...task, kind: "artifact", title: title.slice(0, 300), description };
9853
10128
  }
9854
- function tail(text, max = 400) {
9855
- const t = text.trim();
9856
- return t.length <= max ? t : t.slice(t.length - max);
10129
+ var plannedTaskSchema = z14.object({
10130
+ id: z14.string().min(1).max(80),
10131
+ title: z14.string().trim().min(1).max(300),
10132
+ description: z14.string().max(8e3).optional(),
10133
+ role: z14.string().min(1).max(40),
10134
+ kind: z14.enum(["code", "artifact"]).optional(),
10135
+ dependsOn: z14.array(z14.string().min(1)).max(50).default([]),
10136
+ acceptance: z14.array(z14.string().min(1).max(1e3)).max(20).optional()
10137
+ });
10138
+ var planSchema = z14.object({
10139
+ summary: z14.string().max(4e3).optional(),
10140
+ tasks: z14.array(plannedTaskSchema).min(1).max(100)
10141
+ });
10142
+ var reviewSchema = z14.object({
10143
+ /** The objective is achieved as far as DaveCode can take it. */
10144
+ done: z14.boolean(),
10145
+ summary: z14.string().max(8e3),
10146
+ /** Ids of FAILED tasks worth another attempt. */
10147
+ retry: z14.array(z14.string()).max(50).default([]),
10148
+ /** New tasks for the next round. */
10149
+ tasks: z14.array(plannedTaskSchema).max(50).default([])
10150
+ });
10151
+ var TASK_ID = /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/;
10152
+ function sanitizeTaskId(raw) {
10153
+ const id = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
10154
+ return TASK_ID.test(id) ? id : "task";
9857
10155
  }
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
- }
10156
+ function normalizeTasks(planned, opts) {
10157
+ const roleById = new Map(opts.roles.map((r) => [r.id, r]));
10158
+ const taken = new Set(opts.existing.map((t) => t.id));
10159
+ const idMap = /* @__PURE__ */ new Map();
10160
+ const out = [];
10161
+ for (const p of planned.slice(0, Math.max(0, opts.room))) {
10162
+ let id = sanitizeTaskId(p.id);
10163
+ for (let n = 2; taken.has(id); n++) id = `${sanitizeTaskId(p.id)}-${n}`;
10164
+ taken.add(id);
10165
+ idMap.set(p.id, id);
10166
+ let role = roleById.get(p.role.toLowerCase());
10167
+ const requested = p.kind ?? role?.kind ?? "artifact";
10168
+ if (!role)
10169
+ role = roleById.get(requested === "code" ? "implementer" : "writer") ?? opts.roles[0];
10170
+ const kind = requested === "code" && opts.codeTasks ? "code" : "artifact";
10171
+ const task = {
10172
+ id,
10173
+ title: p.title,
10174
+ role: role?.id ?? "writer",
10175
+ kind,
10176
+ status: "PENDING",
10177
+ dependsOn: p.dependsOn,
10178
+ round: opts.round,
10179
+ attempts: 0
10180
+ };
10181
+ const description = requested === "code" && kind === "artifact" ? [
10182
+ p.description,
10183
+ "Code tasks cannot run here: write a precise implementation plan instead."
10184
+ ].filter(Boolean).join("\n\n") : p.description;
10185
+ if (description) task.description = description;
10186
+ if (p.acceptance?.length) task.acceptance = p.acceptance;
10187
+ out.push(applySafetyGuard(task));
9892
10188
  }
9893
- return clip(scrubSecrets(hint ? `${name} ${hint}` : name), MAX_TOOL_LINE);
10189
+ const known = /* @__PURE__ */ new Set([...opts.existing.map((t) => t.id), ...out.map((t) => t.id)]);
10190
+ for (const task of out) {
10191
+ task.dependsOn = [
10192
+ ...new Set(task.dependsOn.map((d) => idMap.get(d) ?? sanitizeTaskId(d)))
10193
+ ].filter((d) => d !== task.id && known.has(d));
10194
+ }
10195
+ for (; ; ) {
10196
+ const graph = {
10197
+ version: 1,
10198
+ tasks: [...opts.existing, ...out].map((t) => ({
10199
+ id: t.id,
10200
+ title: t.title,
10201
+ status: t.status,
10202
+ dependsOn: t.dependsOn
10203
+ }))
10204
+ };
10205
+ const cycle = findCycles(graph)[0];
10206
+ if (!cycle) break;
10207
+ const from = cycle[cycle.length - 2];
10208
+ const to = cycle[cycle.length - 1];
10209
+ const task = out.find((t) => t.id === from);
10210
+ if (!task) break;
10211
+ task.dependsOn = task.dependsOn.filter((d) => d !== to);
10212
+ }
10213
+ return out;
9894
10214
  }
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
- };
10215
+ var SAFETY_RULES = `Hard rules (non-negotiable):
10216
+ - You run inside DaveCode with no internet access and no ability to contact anyone.
10217
+ - Never send emails or messages, post or publish anything, make payments or purchases, sign up for services, or deploy. When such a step is useful, produce a DRAFT for a human to review and execute.
10218
+ - Never invent credentials, personal data or facts presented as verified; label assumptions and estimates clearly.
10219
+ - Stay within the objective; prefer concrete, actionable outputs.`;
10220
+ function roleList(roles) {
10221
+ return roles.map((r) => `- ${r.id} (${r.kind}): ${r.description}`).join("\n");
9949
10222
  }
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;
10223
+ function plannerPrompt(roles, codeTasks) {
10224
+ return `You are the planner of a small team of AI agents working toward one objective. Decompose the objective into a dependency graph of concrete tasks and assign each task to one role.
10225
+
10226
+ Roles:
10227
+ ${roleList(roles)}
10228
+
10229
+ Task kinds:
10230
+ - "artifact": the role writes one markdown document (analysis, plan, copy, checklist, draft).
10231
+ - "code": the implementer changes the repository; an autonomous runner branches, runs the quality gates and merges.${codeTasks ? "" : "\n Code tasks are NOT available in this workspace: plan artifact tasks only."}
10232
+
10233
+ ${SAFETY_RULES}
10234
+
10235
+ Plan at most the number of tasks you are allowed, smallest useful set first. Ids are short lowercase kebab-case. Use dependsOn so later tasks build on earlier outputs.
10236
+
10237
+ Respond with ONLY a JSON object, no prose and no code fences:
10238
+ {"summary": "one paragraph plan", "tasks": [{"id": "market-research", "title": "...", "description": "...", "role": "researcher", "kind": "artifact", "dependsOn": [], "acceptance": ["..."]}]}`;
9954
10239
  }
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);
10240
+ var REVIEWER_PROMPT = `You are the reviewer of a team of AI agents working toward one objective. Judge the results so far against the objective.
10241
+
10242
+ ${SAFETY_RULES}
10243
+
10244
+ Decide:
10245
+ - "done": true when the objective is achieved as far as an offline team can take it (remaining steps are outbound actions a human must do from the drafts).
10246
+ - otherwise propose the next round: "retry" lists FAILED task ids worth another attempt, "tasks" lists new tasks (same shape as the planner's, dependsOn may reference existing ids).
10247
+
10248
+ Respond with ONLY a JSON object, no prose and no code fences:
10249
+ {"done": false, "summary": "progress, gaps and what a human must do next", "retry": [], "tasks": []}`;
10250
+ function roleSystemPrompt(role) {
10251
+ return [
10252
+ `You are the ${role.id} of a small team of AI agents. ${role.description}`,
10253
+ SAFETY_RULES,
10254
+ "Write your deliverable as a single, well-structured markdown document. Do not wrap it in code fences. Start with a level-1 heading.",
10255
+ role.instructions?.trim()
10256
+ ].filter(Boolean).join("\n\n");
10257
+ }
10258
+ var MeteredExecutor = class {
10259
+ constructor(inner) {
10260
+ this.inner = inner;
10261
+ }
10262
+ inner;
10263
+ tokens = 0;
10264
+ get name() {
10265
+ return this.inner.name;
10266
+ }
10267
+ createSession(task) {
10268
+ const session = this.inner.createSession(task);
10269
+ return {
10270
+ run: async (opts) => {
10271
+ const result = await session.run(opts);
10272
+ this.tokens += Math.max(0, result.tokens);
10273
+ return result;
10274
+ }
10275
+ };
10276
+ }
10277
+ };
10278
+ var MIN_TASK_TOKENS = 1e4;
10279
+ var RunnerCodeTasks = class {
10280
+ constructor(options) {
10281
+ this.options = options;
10282
+ this.owner = options.owner ?? "goal";
10283
+ }
10284
+ options;
10285
+ owner;
10286
+ async available() {
10287
+ const { brain, engine } = this.options;
10288
+ const git = new Git(brain.root);
10289
+ if (!await git.isRepo())
10290
+ return { ok: false, reason: `${brain.root} is not a git repository` };
10291
+ if (!await brain.isInitialised()) {
10292
+ return { ok: false, reason: "the project brain is missing (run davecode init)" };
9966
10293
  }
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);
10294
+ const base = engine.config.runner.baseBranch;
10295
+ if (!await git.branchExists(base)) {
10296
+ return { ok: false, reason: `base branch "${base}" does not exist` };
9970
10297
  }
9971
- case "web_search":
9972
- return toolLine("Search", { query: asString(item.query) ?? "" }, cwd);
9973
- default:
9974
- return toolLine(type, item, cwd);
10298
+ if ((await git.status({ ignoreBrain: true })).length > 0) {
10299
+ return { ok: false, reason: "the working tree has uncommitted changes" };
10300
+ }
10301
+ return { ok: true };
9975
10302
  }
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
- }
10303
+ async run(task, opts) {
10304
+ const { brain, engine } = this.options;
10305
+ const runnerTaskId = sanitizeTaskId(`goal-${opts.goal.id.slice(-6)}-${task.id}`);
10306
+ const graph = await brain.readGraph();
10307
+ const existing = graph.tasks.find((t) => t.id === runnerTaskId);
10308
+ if (existing?.status === "SUCCESS") {
10309
+ return { ok: true, summary: existing.notes ?? "already done", tokens: 0, runnerTaskId };
10044
10310
  }
10045
- };
10311
+ if (!existing) {
10312
+ await brain.createTask({
10313
+ id: runnerTaskId,
10314
+ title: task.title,
10315
+ description: [
10316
+ task.description,
10317
+ `Part of DaveCode goal ${opts.goal.id}: ${opts.goal.objective}`
10318
+ ].filter(Boolean).join("\n\n"),
10319
+ ...task.acceptance?.length ? { acceptance: task.acceptance } : {}
10320
+ });
10321
+ } else if (existing.status !== "PENDING") {
10322
+ await brain.setTaskStatus(runnerTaskId, "PENDING", { notes: "retried by goal mode" });
10323
+ }
10324
+ const base = engine.config.runner;
10325
+ const config = {
10326
+ ...base,
10327
+ maxTaskTokens: Math.max(MIN_TASK_TOKENS, Math.min(base.maxTaskTokens, opts.maxTokens))
10328
+ };
10329
+ const metered = new MeteredExecutor(this.options.executor ?? createExecutor(engine, config));
10330
+ const runner = this.options.makeRunner?.(config, metered) ?? createRunner(engine, {
10331
+ brain,
10332
+ config,
10333
+ executor: metered,
10334
+ lock: { lock: this.options.lock, owner: this.owner },
10335
+ ...this.options.contextExtras ? { contextExtras: this.options.contextExtras } : {}
10336
+ });
10337
+ const onAbort = () => void runner.stop();
10338
+ opts.signal.addEventListener("abort", onAbort, { once: true });
10339
+ try {
10340
+ const result = await runner.runOnce({ taskId: runnerTaskId });
10341
+ return {
10342
+ ok: result.outcome === "success",
10343
+ summary: result.summary ?? result.error ?? result.outcome,
10344
+ tokens: metered.tokens,
10345
+ runnerTaskId,
10346
+ ...result.outcome === "stopped" ? { stopped: true } : {}
10347
+ };
10348
+ } catch (err) {
10349
+ if (!(err instanceof RunnerError)) throw err;
10350
+ return { ok: false, summary: err.message, tokens: metered.tokens, runnerTaskId };
10351
+ } finally {
10352
+ opts.signal.removeEventListener("abort", onAbort);
10353
+ }
10354
+ }
10355
+ };
10356
+ var TERMINAL = /* @__PURE__ */ new Set([
10357
+ "planned",
10358
+ "succeeded",
10359
+ "failed",
10360
+ "cancelled",
10361
+ "budget_exhausted"
10362
+ ]);
10363
+ function isTerminalGoal(goal) {
10364
+ return TERMINAL.has(goal.status);
10046
10365
  }
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;
10366
+ var Cancelled = class extends Error {
10367
+ };
10368
+ var OutOfBudget = class extends Error {
10369
+ };
10370
+ var MAX_DEP_CHARS = 6e3;
10371
+ var MAX_DEPS_TOTAL = 2e4;
10372
+ var MAX_REVIEW_EXCERPT = 1500;
10373
+ var MAX_TOOL_ROUNDS = 6;
10374
+ var GoalOrchestrator = class {
10375
+ constructor(options) {
10376
+ this.options = options;
10377
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
10378
+ this.tools = new WorkspaceTools({ root: options.store.root, readOnly: true });
10379
+ }
10380
+ options;
10381
+ now;
10382
+ tools;
10383
+ /** A fresh goal (not yet persisted) with limits clamped to the config. */
10384
+ create(input) {
10385
+ const cfg = this.options.config;
10386
+ const stamp = this.now().toISOString();
10387
+ const goal = {
10388
+ id: newId2("goal", 6),
10389
+ objective: input.objective.trim(),
10390
+ status: "planning",
10391
+ round: 0,
10392
+ limits: {
10393
+ maxRounds: Math.min(input.maxRounds ?? cfg.maxRounds, cfg.maxRounds),
10394
+ maxTasks: Math.min(input.maxTasks ?? cfg.maxTasks, cfg.maxTasks),
10395
+ maxTokens: Math.min(input.maxTokens ?? cfg.maxTokens, cfg.maxTokens),
10396
+ tokensUsed: 0
10397
+ },
10398
+ roles: publicRoles(resolveRoles(cfg)),
10399
+ tasks: [],
10400
+ createdAt: stamp,
10401
+ updatedAt: stamp
10402
+ };
10403
+ if (input.dryRun) goal.dryRun = true;
10404
+ return goal;
10405
+ }
10406
+ /** Runs `goal` to a terminal status. Never throws: failures end up in `status`/`lastError`. */
10407
+ async run(goal, signal) {
10408
+ const cfg = this.options.config;
10409
+ const roles = resolveRoles(cfg);
10410
+ try {
10411
+ const code = await this.codeAvailability();
10412
+ if (!code.ok) this.log(goal, "info", `code tasks disabled: ${code.reason}`);
10413
+ if (goal.round === 0 || goal.tasks.length === 0) {
10414
+ goal.round = 1;
10415
+ await this.update(goal, { status: "planning" });
10416
+ this.log(goal, "info", `planning: ${goal.objective}`, "planner");
10417
+ const plan = await this.plan(goal, roles, code.ok, signal);
10418
+ goal.tasks = normalizeTasks(plan.tasks, {
10419
+ existing: [],
10420
+ roles,
10421
+ round: 1,
10422
+ room: goal.limits.maxTasks,
10423
+ codeTasks: code.ok
10424
+ });
10425
+ if (plan.summary) goal.summary = plan.summary;
10426
+ this.log(goal, "info", `planned ${goal.tasks.length} task(s)`, "planner");
10427
+ if (goal.dryRun) return await this.finish(goal, "planned");
10428
+ } else {
10429
+ this.log(goal, "info", `executing planned goal (${goal.tasks.length} task(s))`, "planner");
10068
10430
  }
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;
10431
+ for (; ; ) {
10432
+ await this.update(goal, { status: "running" });
10433
+ await this.executeReady(goal, roles, signal);
10434
+ await this.update(goal, { status: "reviewing" });
10435
+ this.log(goal, "info", `reviewing round ${goal.round}`, "reviewer");
10436
+ const review = await this.review(goal, roles, signal);
10437
+ goal.summary = review.summary;
10438
+ if (review.done) return await this.finish(goal, "succeeded");
10439
+ if (goal.round >= goal.limits.maxRounds) {
10440
+ goal.lastError = `reached ${goal.limits.maxRounds} round(s) without completing the objective`;
10441
+ return await this.finish(goal, "failed");
10083
10442
  }
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) });
10443
+ const retried = this.applyRetries(goal, review.retry);
10444
+ const added = normalizeTasks(review.tasks, {
10445
+ existing: goal.tasks,
10446
+ roles,
10447
+ round: goal.round + 1,
10448
+ room: goal.limits.maxTasks - goal.tasks.length,
10449
+ codeTasks: code.ok
10450
+ });
10451
+ if (retried === 0 && added.length === 0) {
10452
+ goal.lastError = goal.tasks.length >= goal.limits.maxTasks ? `reached the cap of ${goal.limits.maxTasks} task(s) without completing the objective` : "the reviewer proposed no further work but the objective is not done";
10453
+ return await this.finish(goal, "failed");
10091
10454
  }
10092
- return;
10455
+ goal.tasks.push(...added);
10456
+ goal.round++;
10457
+ this.log(
10458
+ goal,
10459
+ "info",
10460
+ `round ${goal.round}: ${added.length} new task(s), ${retried} retried`,
10461
+ "reviewer"
10462
+ );
10093
10463
  }
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();
10464
+ } catch (err) {
10465
+ for (const task of goal.tasks) if (task.status === "IN_PROGRESS") task.status = "PENDING";
10466
+ delete goal.activeTaskId;
10467
+ if (err instanceof Cancelled || signal.aborted) return await this.finish(goal, "cancelled");
10468
+ if (err instanceof OutOfBudget) {
10469
+ goal.lastError = err.message;
10470
+ return await this.finish(goal, "budget_exhausted");
10108
10471
  }
10472
+ goal.lastError = err instanceof Error ? err.message : String(err);
10473
+ this.log(goal, "error", goal.lastError);
10474
+ return await this.finish(goal, "failed");
10475
+ }
10476
+ }
10477
+ // -- phases -----------------------------------------------------------------
10478
+ async codeAvailability() {
10479
+ if (!this.options.config.codeTasks) return { ok: false, reason: "goal.codeTasks is off" };
10480
+ if (!this.options.codeRunner) return { ok: false, reason: "no project runner available" };
10481
+ try {
10482
+ return await this.options.codeRunner.available();
10483
+ } catch (err) {
10484
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
10485
+ }
10486
+ }
10487
+ async plan(goal, roles, codeTasks, signal) {
10488
+ const user = [
10489
+ `OBJECTIVE:
10490
+ ${goal.objective}`,
10491
+ `You may plan at most ${goal.limits.maxTasks} task(s) in total; the team has ${goal.limits.maxRounds} round(s) of plan \u2192 execute \u2192 review.`,
10492
+ await this.sharedContext()
10493
+ ].filter(Boolean).join("\n\n");
10494
+ return this.askJson(
10495
+ planSchema,
10496
+ goal,
10497
+ this.roleFor(roles, "planner"),
10498
+ plannerPrompt(roles, codeTasks),
10499
+ user,
10500
+ signal
10501
+ );
10502
+ }
10503
+ async review(goal, roles, signal) {
10504
+ const lines2 = [];
10505
+ for (const task of goal.tasks) {
10506
+ lines2.push(
10507
+ `- [${task.status}] ${task.id} (${task.role}, ${task.kind}, round ${task.round}, ${task.attempts} attempt(s)): ${task.title}`
10508
+ );
10509
+ if (task.notes) lines2.push(` notes: ${truncateHead(task.notes, 500).replace(/\n/g, " ")}`);
10510
+ if (task.artifact) {
10511
+ const text = await this.options.store.readArtifact(goal.id, task.id);
10512
+ if (text)
10513
+ lines2.push(` output excerpt:
10514
+ ${indent(truncateHead(text, MAX_REVIEW_EXCERPT))}`);
10515
+ }
10516
+ }
10517
+ const user = [
10518
+ `OBJECTIVE:
10519
+ ${goal.objective}`,
10520
+ `Round ${goal.round} of ${goal.limits.maxRounds}. Room for ${Math.max(0, goal.limits.maxTasks - goal.tasks.length)} more task(s).`,
10521
+ `TASKS:
10522
+ ${lines2.join("\n")}`,
10523
+ await this.sharedContext()
10524
+ ].filter(Boolean).join("\n\n");
10525
+ return this.askJson(
10526
+ reviewSchema,
10527
+ goal,
10528
+ this.roleFor(roles, "reviewer"),
10529
+ REVIEWER_PROMPT,
10530
+ user,
10531
+ signal
10532
+ );
10533
+ }
10534
+ /** Runs ready tasks until none is left; failed tasks are retried up to `maxTaskAttempts`. */
10535
+ async executeReady(goal, roles, signal) {
10536
+ for (; ; ) {
10537
+ const task = readyGoalTasks(goal)[0];
10538
+ if (!task) return;
10539
+ this.checkpoint(goal, signal);
10540
+ task.attempts++;
10541
+ task.status = "IN_PROGRESS";
10542
+ await this.update(goal, { activeTaskId: task.id });
10543
+ this.log(goal, "info", `${task.title} (attempt ${task.attempts})`, task.role, task.id);
10544
+ try {
10545
+ if (task.kind === "code") await this.runCodeTask(goal, task, signal);
10546
+ else await this.runArtifactTask(goal, task, this.roleFor(roles, task.role), signal);
10547
+ const outcome = task.status;
10548
+ this.log(
10549
+ goal,
10550
+ outcome === "SUCCESS" ? "info" : "warn",
10551
+ `${task.id}: ${outcome}`,
10552
+ task.role,
10553
+ task.id
10554
+ );
10555
+ } catch (err) {
10556
+ if (err instanceof Cancelled || err instanceof OutOfBudget || signal.aborted) throw err;
10557
+ if (err instanceof RouterError && (err.code === "no_capacity" || err.code === "rate_limited")) {
10558
+ throw new OutOfBudget(`no account has capacity left (${err.message})`);
10559
+ }
10560
+ task.status = "FAILED";
10561
+ task.notes = truncateHead(err instanceof Error ? err.message : String(err), 2e3);
10562
+ this.log(goal, "warn", `${task.id} failed: ${task.notes}`, task.role, task.id);
10563
+ }
10564
+ if (task.status === "FAILED" && task.attempts < this.options.config.maxTaskAttempts) {
10565
+ task.status = "PENDING";
10566
+ }
10567
+ delete goal.activeTaskId;
10568
+ await this.update(goal, {});
10569
+ }
10570
+ }
10571
+ async runArtifactTask(goal, task, role, signal) {
10572
+ const parts = [`OBJECTIVE OF THE TEAM:
10573
+ ${goal.objective}`, `YOUR TASK: ${task.title}`];
10574
+ if (task.description) parts.push(task.description);
10575
+ if (task.acceptance?.length) {
10576
+ parts.push(`Acceptance criteria:
10577
+ ${task.acceptance.map((c) => `- ${c}`).join("\n")}`);
10578
+ }
10579
+ let budget = MAX_DEPS_TOTAL;
10580
+ for (const depId of task.dependsOn) {
10581
+ const dep = goal.tasks.find((t) => t.id === depId);
10582
+ if (!dep || budget <= 0) continue;
10583
+ const text = dep.artifact ? await this.options.store.readArtifact(goal.id, dep.id) : dep.notes;
10584
+ if (!text) continue;
10585
+ const excerpt = truncateHead(text, Math.min(MAX_DEP_CHARS, budget));
10586
+ budget -= excerpt.length;
10587
+ parts.push(`OUTPUT OF ${dep.id} (${dep.role}): ${dep.title}
10588
+ ${excerpt}`);
10589
+ }
10590
+ const shared = await this.sharedContext(false);
10591
+ if (shared) parts.push(shared);
10592
+ const reply = (await this.ask(goal, role, roleSystemPrompt(role), parts.join("\n\n"), signal)).trim();
10593
+ if (!reply) throw new Error("the agent returned an empty reply");
10594
+ task.artifact = await this.options.store.writeArtifact(goal.id, task.id, stripFence(reply));
10595
+ task.status = "SUCCESS";
10596
+ task.notes = `Wrote ${task.artifact}`;
10597
+ }
10598
+ async runCodeTask(goal, task, signal) {
10599
+ const runner = this.options.codeRunner;
10600
+ if (!runner) throw new Error("no project runner available for code tasks");
10601
+ const result = await runner.run(task, {
10602
+ goal,
10603
+ maxTokens: goal.limits.maxTokens - goal.limits.tokensUsed,
10604
+ signal
10605
+ });
10606
+ this.addTokens(goal, result.tokens);
10607
+ if (result.runnerTaskId) task.runnerTaskId = result.runnerTaskId;
10608
+ if (result.stopped || signal.aborted) throw new Cancelled();
10609
+ task.status = result.ok ? "SUCCESS" : "FAILED";
10610
+ task.notes = truncateHead(result.summary || (result.ok ? "done" : "failed"), 2e3);
10611
+ }
10612
+ applyRetries(goal, ids) {
10613
+ let count = 0;
10614
+ for (const id of new Set(ids)) {
10615
+ const task = goal.tasks.find((t) => t.id === id);
10616
+ if (task?.status !== "FAILED") continue;
10617
+ task.status = "PENDING";
10618
+ task.attempts = 0;
10619
+ count++;
10620
+ }
10621
+ return count;
10622
+ }
10623
+ // -- model calls ------------------------------------------------------------
10624
+ roleFor(roles, id) {
10625
+ return roles.find((r) => r.id === id) ?? { id, description: id, kind: "artifact" };
10626
+ }
10627
+ async ask(goal, role, system, user, signal) {
10628
+ const model = routeModel(role.route ?? this.options.config.route);
10629
+ const messages = [
10630
+ { role: "system", content: system },
10631
+ { role: "user", content: user }
10632
+ ];
10633
+ let attachTools = this.options.readTools === true;
10634
+ for (let round = 0; round <= MAX_TOOL_ROUNDS; round++) {
10635
+ this.checkpoint(goal, signal);
10636
+ const allowTools = attachTools && round < MAX_TOOL_ROUNDS;
10637
+ let completion;
10638
+ try {
10639
+ ({ completion } = await this.options.router.complete(
10640
+ {
10641
+ model,
10642
+ messages,
10643
+ ...allowTools ? { tools: [...READ_ONLY_TOOL_DEFINITIONS] } : {}
10644
+ },
10645
+ { signal }
10646
+ ));
10647
+ } catch (err) {
10648
+ if (allowTools && err instanceof RouterError && err.code === "no_available_account") {
10649
+ attachTools = false;
10650
+ ({ completion } = await this.options.router.complete({ model, messages }, { signal }));
10651
+ } else {
10652
+ throw err;
10653
+ }
10654
+ }
10655
+ const message = completion.choices[0]?.message;
10656
+ const content = message?.content;
10657
+ const text = typeof content === "string" ? content : "";
10658
+ const usage = completion.usage;
10659
+ this.addTokens(
10660
+ goal,
10661
+ usage && usage.prompt_tokens + usage.completion_tokens > 0 ? usage.prompt_tokens + usage.completion_tokens : countTextTokens(system) + countTextTokens(user) + countTextTokens(text)
10662
+ );
10663
+ const calls = message?.tool_calls ?? [];
10664
+ if (calls.length === 0 || round === MAX_TOOL_ROUNDS) return text;
10665
+ messages.push({ role: "assistant", content: content ?? null, tool_calls: calls });
10666
+ for (const call of calls) {
10667
+ this.log(goal, "debug", `tool ${call.function.name}`, role.id, goal.activeTaskId);
10668
+ const outcome = await this.tools.call(call.function.name, call.function.arguments, signal);
10669
+ messages.push({
10670
+ role: "tool",
10671
+ tool_call_id: call.id,
10672
+ name: call.function.name,
10673
+ content: outcome.output
10674
+ });
10675
+ }
10676
+ }
10677
+ return "";
10678
+ }
10679
+ /** Asks for a JSON reply matching `schema`; one corrective retry on malformed output. */
10680
+ async askJson(schema, goal, role, system, user, signal) {
10681
+ let problem = "";
10682
+ for (let attempt = 0; attempt < 2; attempt++) {
10683
+ const prompt = attempt === 0 ? user : `${user}
10684
+
10685
+ Your previous reply was rejected (${problem}). Respond with ONLY the JSON object in the required shape.`;
10686
+ const reply = await this.ask(goal, role, system, prompt, signal);
10687
+ try {
10688
+ const parsed = schema.safeParse(extractJsonObject(reply));
10689
+ if (parsed.success) return parsed.data;
10690
+ problem = parsed.error.issues.slice(0, 3).map((i) => `${i.path.join(".") || "reply"}: ${i.message}`).join("; ");
10691
+ } catch (err) {
10692
+ problem = err instanceof Error ? err.message : String(err);
10693
+ }
10694
+ this.log(goal, "warn", `invalid JSON from the ${role.id}: ${problem}`, role.id);
10695
+ }
10696
+ throw new Error(`the ${role.id} did not return valid JSON (${problem})`);
10697
+ }
10698
+ async sharedContext(includeProject = true) {
10699
+ const parts = [];
10700
+ if (includeProject && this.options.projectContext) {
10701
+ const project = (await this.options.projectContext()).trim();
10702
+ if (project) parts.push(`PROJECT CONTEXT:
10703
+ ${project}`);
10704
+ }
10705
+ if (this.options.budgetContext) {
10706
+ const budget = (await this.options.budgetContext()).trim();
10707
+ if (budget) parts.push(budget);
10708
+ }
10709
+ return parts.join("\n\n");
10710
+ }
10711
+ // -- bookkeeping ------------------------------------------------------------
10712
+ checkpoint(goal, signal) {
10713
+ if (signal.aborted) throw new Cancelled();
10714
+ if (goal.limits.tokensUsed >= goal.limits.maxTokens) {
10715
+ throw new OutOfBudget(
10716
+ `token budget exhausted (${goal.limits.tokensUsed}/${goal.limits.maxTokens})`
10717
+ );
10718
+ }
10719
+ }
10720
+ addTokens(goal, tokens2) {
10721
+ if (Number.isFinite(tokens2) && tokens2 > 0) goal.limits.tokensUsed += Math.round(tokens2);
10722
+ }
10723
+ async update(goal, patch) {
10724
+ Object.assign(goal, patch);
10725
+ goal.updatedAt = this.now().toISOString();
10726
+ await this.options.store.save(goal);
10727
+ this.options.events.emit({ type: "goal.updated", goal: structuredClone(goal) });
10728
+ }
10729
+ async finish(goal, status) {
10730
+ delete goal.activeTaskId;
10731
+ goal.finishedAt = this.now().toISOString();
10732
+ await this.update(goal, { status });
10733
+ this.log(
10734
+ goal,
10735
+ status === "succeeded" || status === "planned" ? "info" : "warn",
10736
+ `goal ${status}${goal.lastError ? `: ${goal.lastError}` : ""} (${goal.limits.tokensUsed} tokens)`
10737
+ );
10738
+ return goal;
10739
+ }
10740
+ log(goal, level, message, role, taskId) {
10741
+ this.options.events.emit({
10742
+ type: "goal.log",
10743
+ goalId: goal.id,
10744
+ level,
10745
+ message,
10746
+ ...role ? { role } : {},
10747
+ ...taskId ? { taskId } : {}
10748
+ });
10749
+ }
10750
+ };
10751
+ function readyGoalTasks(goal) {
10752
+ const status = new Map(goal.tasks.map((t) => [t.id, t.status]));
10753
+ return goal.tasks.filter(
10754
+ (t) => t.status === "PENDING" && t.dependsOn.every((d) => status.get(d) === "SUCCESS")
10755
+ );
10756
+ }
10757
+ function indent(text) {
10758
+ return text.split("\n").map((l) => ` ${l}`).join("\n");
10759
+ }
10760
+ function stripFence(text) {
10761
+ const match = /^```(?:markdown|md)?\s*\n([\s\S]*?)\n```\s*$/i.exec(text);
10762
+ return match?.[1] ?? text;
10763
+ }
10764
+ var GoalError = class extends Error {
10765
+ constructor(code, message) {
10766
+ super(message);
10767
+ this.code = code;
10768
+ this.name = "GoalError";
10769
+ this.statusCode = code === "not_found" ? 404 : code === "invalid" ? 400 : 409;
10770
+ }
10771
+ code;
10772
+ /** HTTP status hint for the gateway. */
10773
+ statusCode;
10774
+ };
10775
+ var GoalService = class {
10776
+ constructor(options) {
10777
+ this.options = options;
10778
+ this.owner = options.owner ?? "goal";
10779
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
10780
+ }
10781
+ options;
10782
+ active;
10783
+ owner;
10784
+ now;
10785
+ get activeGoalId() {
10786
+ return this.active?.goal.id;
10787
+ }
10788
+ /** Validates, persists and starts a goal in the background. Resolves with its initial state. */
10789
+ async start(input) {
10790
+ if (!input.objective.trim()) throw new GoalError("invalid", "the objective is empty");
10791
+ if (this.active) {
10792
+ throw new GoalError("busy", `goal ${this.active.goal.id} is still running; cancel it first`);
10793
+ }
10794
+ const lock = input.dryRun ? void 0 : this.options.lock;
10795
+ if (lock && !lock.acquire(this.owner)) {
10796
+ throw new GoalError(
10797
+ "busy",
10798
+ `the working tree is busy (${lock.holder ?? "another task"} is running)`
10799
+ );
10800
+ }
10801
+ try {
10802
+ const goal = this.options.orchestrator.create(input);
10803
+ await this.options.store.save(goal);
10804
+ this.launch(goal, lock);
10805
+ return structuredClone(goal);
10806
+ } catch (err) {
10807
+ lock?.release(this.owner);
10808
+ throw err;
10809
+ }
10810
+ }
10811
+ /**
10812
+ * Executes a `planned` (dry-run) goal in the background without re-planning round 1.
10813
+ * Resolves with the updated goal (`status: 'running'`).
10814
+ */
10815
+ async execute(id) {
10816
+ if (this.active) {
10817
+ throw new GoalError("busy", `goal ${this.active.goal.id} is still running; cancel it first`);
10818
+ }
10819
+ const goal = await this.options.store.load(id);
10820
+ if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
10821
+ if (goal.status !== "planned") {
10822
+ throw new GoalError(
10823
+ "invalid",
10824
+ `goal "${id}" is ${goal.status}; only a planned goal can be executed`
10825
+ );
10826
+ }
10827
+ const lock = this.options.lock;
10828
+ if (lock && !lock.acquire(this.owner)) {
10829
+ throw new GoalError(
10830
+ "busy",
10831
+ `the working tree is busy (${lock.holder ?? "another task"} is running)`
10832
+ );
10833
+ }
10834
+ try {
10835
+ delete goal.dryRun;
10836
+ delete goal.finishedAt;
10837
+ delete goal.lastError;
10838
+ goal.status = "running";
10839
+ goal.updatedAt = this.now().toISOString();
10840
+ await this.options.store.save(goal);
10841
+ this.launch(goal, lock);
10842
+ return structuredClone(goal);
10843
+ } catch (err) {
10844
+ lock?.release(this.owner);
10845
+ throw err;
10846
+ }
10847
+ }
10848
+ /** Reads the markdown artifact produced by `taskId` in goal `id`. */
10849
+ async readArtifact(id, taskId) {
10850
+ const goal = await this.get(id);
10851
+ if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
10852
+ const task = goal.tasks.find((t) => t.id === taskId);
10853
+ if (!task?.artifact) {
10854
+ throw new GoalError("not_found", `task "${taskId}" has no artifact in goal "${id}"`);
10855
+ }
10856
+ const content = await this.options.store.readArtifact(id, taskId);
10857
+ if (content === void 0) {
10858
+ throw new GoalError("not_found", `artifact file for task "${taskId}" was not found`);
10859
+ }
10860
+ return { taskId: task.id, path: task.artifact, content };
10861
+ }
10862
+ launch(goal, lock) {
10863
+ const controller = new AbortController();
10864
+ const done = this.options.orchestrator.run(goal, controller.signal).catch((err) => {
10865
+ goal.status = "failed";
10866
+ goal.lastError = err instanceof Error ? err.message : String(err);
10867
+ return goal;
10868
+ }).finally(() => {
10869
+ if (this.active?.goal === goal) this.active = void 0;
10870
+ lock?.release(this.owner);
10871
+ });
10872
+ this.active = { goal, controller, done };
10873
+ }
10874
+ async get(id) {
10875
+ if (this.active?.goal.id === id) return structuredClone(this.active.goal);
10876
+ return this.options.store.load(id);
10877
+ }
10878
+ /** Every goal of the workspace, newest first. */
10879
+ async list() {
10880
+ const stored = await this.options.store.list();
10881
+ const active = this.active?.goal;
10882
+ if (!active) return stored;
10883
+ const rest = stored.filter((g) => g.id !== active.id);
10884
+ return [structuredClone(active), ...rest];
10885
+ }
10886
+ /**
10887
+ * Cancels a running goal and resolves once it has stopped. A goal left unfinished by an earlier
10888
+ * process is marked cancelled; a finished goal is returned unchanged.
10889
+ */
10890
+ async cancel(id) {
10891
+ if (this.active?.goal.id === id) {
10892
+ this.active.controller.abort();
10893
+ return structuredClone(await this.active.done);
10109
10894
  }
10110
- };
10111
- }
10112
- var PARSERS = {
10113
- claude: claudeParser,
10114
- codex: codexParser,
10115
- agy: agyParser
10895
+ const goal = await this.options.store.load(id);
10896
+ if (!goal) throw new GoalError("not_found", `unknown goal "${id}"`);
10897
+ if (isTerminalGoal(goal)) return goal;
10898
+ const stamp = this.now().toISOString();
10899
+ goal.status = "cancelled";
10900
+ goal.updatedAt = stamp;
10901
+ goal.finishedAt = stamp;
10902
+ delete goal.activeTaskId;
10903
+ await this.options.store.save(goal);
10904
+ return goal;
10905
+ }
10906
+ /** Resolves when the goal is finished (immediately for stored goals). */
10907
+ async wait(id) {
10908
+ if (this.active?.goal.id === id) return structuredClone(await this.active.done);
10909
+ return this.options.store.load(id);
10910
+ }
10116
10911
  };
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}` : ""}`);
10912
+ var GOAL_ID = /^goal_[a-z0-9]{6,32}$/;
10913
+ var goalTaskSchema = z15.object({
10914
+ id: z15.string().min(1),
10915
+ title: z15.string(),
10916
+ description: z15.string().optional(),
10917
+ role: z15.string(),
10918
+ kind: z15.enum(["code", "artifact"]),
10919
+ status: taskStatusSchema,
10920
+ dependsOn: z15.array(z15.string()),
10921
+ acceptance: z15.array(z15.string()).optional(),
10922
+ round: z15.number().int().min(1),
10923
+ attempts: z15.number().int().min(0),
10924
+ artifact: z15.string().optional(),
10925
+ runnerTaskId: z15.string().optional(),
10926
+ notes: z15.string().optional()
10927
+ });
10928
+ var goalSchema = z15.object({
10929
+ id: z15.string().regex(GOAL_ID),
10930
+ objective: z15.string().min(1),
10931
+ status: z15.enum([
10932
+ "planning",
10933
+ "running",
10934
+ "reviewing",
10935
+ "planned",
10936
+ "succeeded",
10937
+ "failed",
10938
+ "cancelled",
10939
+ "budget_exhausted"
10940
+ ]),
10941
+ round: z15.number().int().min(0),
10942
+ limits: z15.object({
10943
+ maxRounds: z15.number().int().min(1),
10944
+ maxTasks: z15.number().int().min(1),
10945
+ maxTokens: z15.number().int().min(1),
10946
+ tokensUsed: z15.number().int().min(0)
10947
+ }),
10948
+ roles: z15.array(
10949
+ z15.object({ id: z15.string(), description: z15.string(), route: z15.string().optional() })
10950
+ ),
10951
+ tasks: z15.array(goalTaskSchema),
10952
+ activeTaskId: z15.string().optional(),
10953
+ summary: z15.string().optional(),
10954
+ lastError: z15.string().optional(),
10955
+ dryRun: z15.boolean().optional(),
10956
+ createdAt: z15.string(),
10957
+ updatedAt: z15.string(),
10958
+ finishedAt: z15.string().optional()
10959
+ });
10960
+ function isGoalId(id) {
10961
+ return GOAL_ID.test(id);
10125
10962
  }
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;
10963
+ var GoalStore = class {
10964
+ constructor(root) {
10965
+ this.root = root;
10966
+ this.dir = join7(projectPaths(root).dir, "goals");
10967
+ }
10968
+ root;
10969
+ dir;
10970
+ goalDir(id) {
10971
+ if (!isGoalId(id)) throw new Error(`invalid goal id: ${id}`);
10972
+ return join7(this.dir, id);
10973
+ }
10974
+ async save(goal) {
10975
+ await atomicWriteFile(
10976
+ join7(this.goalDir(goal.id), "goal.json"),
10977
+ `${JSON.stringify(goal, null, 2)}
10978
+ `
10979
+ );
10980
+ }
10981
+ /** The goal, or `undefined` when unknown. Throws when `goal.json` is corrupt. */
10982
+ async load(id) {
10983
+ if (!isGoalId(id)) return void 0;
10984
+ const text = await readFileIfExists(join7(this.goalDir(id), "goal.json"));
10985
+ if (text === void 0) return void 0;
10986
+ return goalSchema.parse(JSON.parse(text));
10987
+ }
10988
+ /** Every readable goal, newest first. Corrupt entries are skipped. */
10989
+ async list() {
10990
+ let names;
10138
10991
  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;
10992
+ names = await readdir3(this.dir);
10993
+ } catch (err) {
10994
+ if (isNodeError(err, "ENOENT")) return [];
10995
+ throw err;
10163
10996
  }
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;
10997
+ const goals = [];
10998
+ for (const name of names.filter(isGoalId)) {
10198
10999
  try {
10199
- exit = await cli.exit;
11000
+ const goal = await this.load(name);
11001
+ if (goal) goals.push(goal);
10200
11002
  } 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
11003
  }
10233
11004
  }
11005
+ return goals.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
10234
11006
  }
10235
- return {
10236
- async availability() {
10237
- return ORCHESTRATOR_AGENTS.map((agent) => ({ agent, available: isAvailable(agent) }));
10238
- },
10239
- run
11007
+ /** Writes a task's markdown artifact and returns its repository-relative path (`/`-separated). */
11008
+ async writeArtifact(goalId, taskId, markdown) {
11009
+ const path11 = join7(this.goalDir(goalId), `${taskId}.md`);
11010
+ await atomicWriteFile(path11, markdown.endsWith("\n") ? markdown : `${markdown}
11011
+ `);
11012
+ return relative2(this.root, path11).split("\\").join("/");
11013
+ }
11014
+ async readArtifact(goalId, taskId) {
11015
+ if (!isGoalId(goalId) || !/^[-a-zA-Z0-9_]{1,64}$/.test(taskId)) return void 0;
11016
+ return readFileIfExists(join7(this.goalDir(goalId), `${taskId}.md`));
11017
+ }
11018
+ };
11019
+ var PROJECT_CONTEXT_CHARS = 3e3;
11020
+ function createGoalService(engine, opts) {
11021
+ const { brain } = opts;
11022
+ const lock = opts.lock ?? new WorkspaceLock();
11023
+ const store = new GoalStore(brain.root);
11024
+ const codeRunner = new RunnerCodeTasks({
11025
+ engine,
11026
+ brain,
11027
+ lock,
11028
+ ...opts.budgetContext ? { contextExtras: opts.budgetContext } : {},
11029
+ ...opts.executor ? { executor: opts.executor } : {}
11030
+ });
11031
+ const projectContext = async () => {
11032
+ if (!await brain.isInitialised()) return "";
11033
+ const [architecture, state] = await Promise.all([brain.readArchitecture(), brain.readState()]);
11034
+ return [
11035
+ `ARCHITECTURE.md:
11036
+ ${truncateHead(architecture.trim(), PROJECT_CONTEXT_CHARS)}`,
11037
+ `STATE.md:
11038
+ ${truncateHead(state.trim(), PROJECT_CONTEXT_CHARS)}`
11039
+ ].join("\n\n");
10240
11040
  };
10241
- }
10242
- function errText(err) {
10243
- return err instanceof Error ? err.message : String(err);
11041
+ const orchestrator = new GoalOrchestrator({
11042
+ router: engine.router,
11043
+ store,
11044
+ events: engine.events,
11045
+ config: engine.config.goal,
11046
+ codeRunner,
11047
+ projectContext,
11048
+ ...opts.budgetContext ? { budgetContext: opts.budgetContext } : {}
11049
+ });
11050
+ return new GoalService({ orchestrator, store, lock });
10244
11051
  }
10245
11052
  var AGENT_DESCRIPTIONS = {
10246
11053
  claude: "Claude Code (Anthropic): careful multi-file changes, refactors and reasoning about existing code.",
@@ -10330,15 +11137,15 @@ function buildTaskPrompt(input) {
10330
11137
  );
10331
11138
  return lines2.join("\n");
10332
11139
  }
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()
11140
+ var dependencySchema = z16.union([z16.number(), z16.string()]);
11141
+ var plannedTaskSchema2 = z16.object({
11142
+ title: z16.string().trim().min(1),
11143
+ agent: z16.string().optional(),
11144
+ prompt: z16.string().trim().min(1),
11145
+ dependsOn: z16.array(dependencySchema).nullish()
10339
11146
  });
10340
- var planReplySchema = z13.object({
10341
- tasks: z13.array(plannedTaskSchema2).min(1, "the plan has no tasks")
11147
+ var planReplySchema = z16.object({
11148
+ tasks: z16.array(plannedTaskSchema2).min(1, "the plan has no tasks")
10342
11149
  });
10343
11150
  function dependencyIndex(value) {
10344
11151
  if (typeof value === "number") return Number.isInteger(value) ? value : void 0;
@@ -10351,8 +11158,8 @@ function clip2(text, max) {
10351
11158
  function describeZodError(error) {
10352
11159
  const issue = error.issues[0];
10353
11160
  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}`;
11161
+ const path11 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
11162
+ return `${path11}${issue.message}`;
10356
11163
  }
10357
11164
  function findCycle(tasks) {
10358
11165
  const byId = new Map(tasks.map((t) => [t.id, t]));
@@ -10420,45 +11227,45 @@ function parsePlan(text, options) {
10420
11227
  return { ok: true, tasks };
10421
11228
  }
10422
11229
  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(),
11230
+ var agentSchema = z17.enum(ORCHESTRATOR_AGENTS);
11231
+ var taskSchema = z17.object({
11232
+ id: z17.string().min(1),
11233
+ title: z17.string(),
10427
11234
  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()
11235
+ prompt: z17.string(),
11236
+ dependsOn: z17.array(z17.string()),
11237
+ status: z17.enum(["pending", "running", "done", "failed", "conflict", "cancelled", "skipped"]),
11238
+ branch: z17.string().optional(),
11239
+ summary: z17.string().optional(),
11240
+ changedFiles: z17.array(z17.string()).optional(),
11241
+ error: z17.string().optional(),
11242
+ startedAt: z17.string().optional(),
11243
+ finishedAt: z17.string().optional()
10437
11244
  });
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),
11245
+ var orchestratorRunSchema = z17.object({
11246
+ id: z17.string().min(1),
11247
+ objective: z17.string(),
11248
+ status: z17.enum(["planning", "running", "done", "failed", "cancelled"]),
11249
+ agents: z17.array(agentSchema),
10443
11250
  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()
11251
+ baseBranch: z17.string().optional(),
11252
+ baseCommit: z17.string().optional(),
11253
+ integrationBranch: z17.string().optional(),
11254
+ tasks: z17.array(taskSchema),
11255
+ summary: z17.string().optional(),
11256
+ error: z17.string().optional(),
11257
+ appliedAt: z17.string().optional(),
11258
+ createdAt: z17.string(),
11259
+ updatedAt: z17.string()
10453
11260
  });
10454
- var fileSchema2 = z14.object({
10455
- version: z14.literal(1),
10456
- runs: z14.array(z14.unknown())
11261
+ var fileSchema2 = z17.object({
11262
+ version: z17.literal(1),
11263
+ runs: z17.array(z17.unknown())
10457
11264
  });
10458
11265
  function orchestratorRunsPath(home, projectDir) {
10459
11266
  let key = resolve5(projectDir);
10460
11267
  if (process.platform === "win32") key = key.toLowerCase();
10461
- const hash = createHash3("sha256").update(key).digest("hex").slice(0, 16);
11268
+ const hash = createHash4("sha256").update(key).digest("hex").slice(0, 16);
10462
11269
  return join8(home, "orchestrator", hash, "runs.json");
10463
11270
  }
10464
11271
  function newestFirst(runs) {
@@ -10478,7 +11285,7 @@ var OrchestratorStore = class {
10478
11285
  load() {
10479
11286
  let text;
10480
11287
  try {
10481
- text = readFileSync4(this.path, "utf8");
11288
+ text = readFileSync5(this.path, "utf8");
10482
11289
  } catch {
10483
11290
  return [];
10484
11291
  }
@@ -10561,7 +11368,7 @@ var OrchestratorEngine = class {
10561
11368
  constructor(options) {
10562
11369
  this.options = options;
10563
11370
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
10564
- this.newId = options.newId ?? (() => randomBytes9(6).toString("hex"));
11371
+ this.newId = options.newId ?? (() => randomBytes10(6).toString("hex"));
10565
11372
  this.store = new OrchestratorStore(options.home, options.projectDir);
10566
11373
  this.runs = this.store.load();
10567
11374
  this.recoverInterrupted();
@@ -10987,7 +11794,7 @@ function describeOutcome(task) {
10987
11794
  var SAFE_ID2 = /^[A-Za-z0-9_-]+$/;
10988
11795
  var INTEGRATION_DIR = "integration";
10989
11796
  var BRAIN_DIR2 = ".davecode";
10990
- var FALLBACK_IDENTITY = ["-c", "user.name=DaveCode", "-c", "user.email=davecode@localhost"];
11797
+ var FALLBACK_IDENTITY2 = ["-c", "user.name=DaveCode", "-c", "user.email=davecode@localhost"];
10991
11798
  function assertId(kind, value) {
10992
11799
  if (!SAFE_ID2.test(value)) {
10993
11800
  throw new GitError(`Invalid ${kind}: ${JSON.stringify(value)}`, [], null, "");
@@ -11007,27 +11814,27 @@ function errMessage(err) {
11007
11814
  }
11008
11815
  function realPath2(p) {
11009
11816
  try {
11010
- return realpathSync2.native(p);
11817
+ return realpathSync3.native(p);
11011
11818
  } catch {
11012
- return path9.resolve(p);
11819
+ return path10.resolve(p);
11013
11820
  }
11014
11821
  }
11015
- function samePath(a, b) {
11822
+ function samePath2(a, b) {
11016
11823
  const na = realPath2(a);
11017
11824
  const nb = realPath2(b);
11018
11825
  return process.platform === "win32" ? na.toLowerCase() === nb.toLowerCase() : na === nb;
11019
11826
  }
11020
11827
  function isInside2(child, parent) {
11021
- const rel = path9.relative(realPath2(parent), realPath2(child));
11022
- return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
11828
+ const rel = path10.relative(realPath2(parent), realPath2(child));
11829
+ return rel === "" || !rel.startsWith("..") && !path10.isAbsolute(rel);
11023
11830
  }
11024
11831
  function createGitWorkspace(options) {
11025
- const projectDir = path9.resolve(options.projectDir);
11026
- const worktreesDir = path9.resolve(options.worktreesDir);
11832
+ const projectDir = path10.resolve(options.projectDir);
11833
+ const worktreesDir = path10.resolve(options.worktreesDir);
11027
11834
  const gitAt = (cwd) => new Git(cwd, { env: options.env });
11028
11835
  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);
11836
+ const runDir = (runId) => path10.join(worktreesDir, assertId("run id", runId));
11837
+ const integrationPath = (runId) => path10.join(runDir(runId), INTEGRATION_DIR);
11031
11838
  const integrationBranch = (runId) => `davecode/run-${assertId("run id", runId)}`;
11032
11839
  const taskBases = /* @__PURE__ */ new Map();
11033
11840
  let queue = Promise.resolve();
@@ -11045,7 +11852,7 @@ function createGitWorkspace(options) {
11045
11852
  hasIdentity = true;
11046
11853
  return [];
11047
11854
  } catch {
11048
- return FALLBACK_IDENTITY;
11855
+ return FALLBACK_IDENTITY2;
11049
11856
  }
11050
11857
  }
11051
11858
  async function removeWorktree(target) {
@@ -11053,8 +11860,8 @@ function createGitWorkspace(options) {
11053
11860
  await repo.run(["worktree", "remove", "--force", target]);
11054
11861
  } catch {
11055
11862
  }
11056
- if (existsSync5(target)) {
11057
- rmSync5(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
11863
+ if (existsSync6(target)) {
11864
+ rmSync6(target, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
11058
11865
  }
11059
11866
  try {
11060
11867
  await repo.run(["worktree", "prune"]);
@@ -11130,10 +11937,10 @@ function createGitWorkspace(options) {
11130
11937
  throw new GitError(`Invalid task id: ${JSON.stringify(taskId)}`, [], null, "");
11131
11938
  }
11132
11939
  const branch = `${integrationBranch(runId)}-${taskId}`;
11133
- const target = path9.join(runDir(runId), taskId);
11940
+ const target = path10.join(runDir(runId), taskId);
11134
11941
  const baseCommit = (await repo.run(["rev-parse", "--verify", "--quiet", `${assertSafeRef(base)}^{commit}`])).trim();
11135
11942
  await addWorktree(target, branch, baseCommit);
11136
- taskBases.set(path9.resolve(target), baseCommit);
11943
+ taskBases.set(path10.resolve(target), baseCommit);
11137
11944
  return { path: target, branch };
11138
11945
  });
11139
11946
  },
@@ -11153,7 +11960,7 @@ function createGitWorkspace(options) {
11153
11960
  message
11154
11961
  ]);
11155
11962
  }
11156
- const base = taskBases.get(path9.resolve(workspace.path));
11963
+ const base = taskBases.get(path10.resolve(workspace.path));
11157
11964
  if (base) return nulList(await g.run(["diff", "--name-only", "-z", base, "HEAD"]));
11158
11965
  if (!committed) return [];
11159
11966
  return nulList(await g.run(["diff", "--name-only", "-z", "HEAD~1", "HEAD"]));
@@ -11162,7 +11969,7 @@ function createGitWorkspace(options) {
11162
11969
  mergeIntoIntegration(runId, branch, message) {
11163
11970
  return serial(async () => {
11164
11971
  const target = integrationPath(runId);
11165
- if (!existsSync5(target)) {
11972
+ if (!existsSync6(target)) {
11166
11973
  return { ok: false, reason: `The integration worktree for run ${runId} is missing` };
11167
11974
  }
11168
11975
  return merge(gitAt(target), branch, message, true);
@@ -11207,18 +12014,18 @@ function createGitWorkspace(options) {
11207
12014
  return;
11208
12015
  }
11209
12016
  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));
12017
+ 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) && !samePath2(p, projectDir));
11211
12018
  for (const target of listed) {
11212
12019
  try {
11213
12020
  await repo.run(["worktree", "remove", "--force", target]);
11214
12021
  } catch {
11215
12022
  }
11216
- taskBases.delete(path9.resolve(target));
12023
+ taskBases.delete(path10.resolve(target));
11217
12024
  }
11218
12025
  } catch {
11219
12026
  }
11220
12027
  try {
11221
- rmSync5(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
12028
+ rmSync6(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
11222
12029
  } catch {
11223
12030
  }
11224
12031
  try {
@@ -11412,6 +12219,27 @@ export {
11412
12219
  createAccountControl,
11413
12220
  BRAND_MARK_SVG,
11414
12221
  brandLogo,
12222
+ CHANGES_LIMITS,
12223
+ MAIN_WORKSPACE_ID,
12224
+ WORKSPACE_ID_PATTERN,
12225
+ PANE_BRANCH_PREFIX,
12226
+ workspaceIdSchema,
12227
+ createWorkspaceSchema,
12228
+ suggestSchema,
12229
+ shipSchema,
12230
+ mergeSchema,
12231
+ removeSchema,
12232
+ ChangesError,
12233
+ ORCHESTRATOR_AGENTS,
12234
+ ORCHESTRATOR_LIMITS,
12235
+ startRunSchema,
12236
+ OrchestratorError,
12237
+ createCliAgentRunner,
12238
+ fallbackSuggestion,
12239
+ parseAgentSuggestion,
12240
+ paneWorktreesDir,
12241
+ paneWorkspacePath,
12242
+ createChangesService,
11415
12243
  DEFAULT_ROLES,
11416
12244
  resolveRoles,
11417
12245
  outboundAction,
@@ -11427,12 +12255,7 @@ export {
11427
12255
  isGoalId,
11428
12256
  GoalStore,
11429
12257
  createGoalService,
11430
- ORCHESTRATOR_AGENTS,
11431
- ORCHESTRATOR_LIMITS,
11432
- startRunSchema,
11433
- OrchestratorError,
11434
- createCliAgentRunner,
11435
12258
  createOrchestratorService,
11436
12259
  createGitWorkspace
11437
12260
  };
11438
- //# sourceMappingURL=chunk-2XGDILCW.js.map
12261
+ //# sourceMappingURL=chunk-NBQIMB43.js.map