@wrongstack/core 0.5.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/{agent-bridge-B07AYFBk.d.ts → agent-bridge-BKNiE1VH.d.ts} +1 -1
  2. package/dist/{compactor-BWhJXxsW.d.ts → compactor-RIPuTtWK.d.ts} +1 -1
  3. package/dist/{config-BgM0BIpz.d.ts → config-BGGuP_Ar.d.ts} +1 -1
  4. package/dist/{context-CLZXPPYk.d.ts → context-CDRyrkKQ.d.ts} +7 -0
  5. package/dist/coordination/index.d.ts +9 -9
  6. package/dist/coordination/index.js +339 -80
  7. package/dist/coordination/index.js.map +1 -1
  8. package/dist/defaults/index.d.ts +20 -19
  9. package/dist/defaults/index.js +731 -180
  10. package/dist/defaults/index.js.map +1 -1
  11. package/dist/{events-qnDZbrtb.d.ts → events-DPQKFX7W.d.ts} +1 -1
  12. package/dist/execution/index.d.ts +152 -12
  13. package/dist/execution/index.js +474 -5
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/extension/index.d.ts +6 -6
  16. package/dist/extension/index.js +2 -1
  17. package/dist/extension/index.js.map +1 -1
  18. package/dist/goal-store-BQ3YX1h1.d.ts +75 -0
  19. package/dist/{index-DPLJw_ZI.d.ts → index-B0qTujQW.d.ts} +52 -5
  20. package/dist/{index-BDnUCRvL.d.ts → index-DkdRz6yK.d.ts} +351 -15
  21. package/dist/index.d.ts +109 -28
  22. package/dist/index.js +1143 -234
  23. package/dist/index.js.map +1 -1
  24. package/dist/infrastructure/index.d.ts +6 -6
  25. package/dist/kernel/index.d.ts +9 -9
  26. package/dist/{mcp-servers-CSMfaBuL.d.ts → mcp-servers-DBdh3cee.d.ts} +3 -3
  27. package/dist/models/index.d.ts +2 -2
  28. package/dist/models/index.js +2 -3
  29. package/dist/models/index.js.map +1 -1
  30. package/dist/{multi-agent-Cv8wk47i.d.ts → multi-agent-Cpp7FXUl.d.ts} +11 -3
  31. package/dist/observability/index.d.ts +2 -2
  32. package/dist/{path-resolver-DiCUvEg6.d.ts → path-resolver--g_hKJ7V.d.ts} +2 -2
  33. package/dist/{plan-templates-DaxTCPfk.d.ts → plan-templates-CKJs_sYh.d.ts} +9 -8
  34. package/dist/{provider-runner-3SHqk9zB.d.ts → provider-runner-hl4Il3xS.d.ts} +3 -3
  35. package/dist/{retry-policy-LLUxJmYY.d.ts → retry-policy-LKS8MHsB.d.ts} +1 -1
  36. package/dist/sdd/index.d.ts +6 -4
  37. package/dist/sdd/index.js +102 -68
  38. package/dist/sdd/index.js.map +1 -1
  39. package/dist/{secret-scrubber-Z_VXuXQT.d.ts → secret-scrubber-BzQR5BiL.d.ts} +1 -1
  40. package/dist/{secret-scrubber-BhJTNr9v.d.ts → secret-scrubber-CfMdAJ_l.d.ts} +1 -1
  41. package/dist/security/index.d.ts +3 -3
  42. package/dist/security/index.js +82 -82
  43. package/dist/security/index.js.map +1 -1
  44. package/dist/{selector-DB2-byKH.d.ts → selector-C7HqnZJU.d.ts} +1 -1
  45. package/dist/{session-reader-4jxsYLZ8.d.ts → session-reader-CzfRA6Vk.d.ts} +1 -1
  46. package/dist/skills/index.js +102 -38
  47. package/dist/skills/index.js.map +1 -1
  48. package/dist/storage/index.d.ts +6 -5
  49. package/dist/storage/index.js +131 -22
  50. package/dist/storage/index.js.map +1 -1
  51. package/dist/{system-prompt-DI4Dtc1I.d.ts → system-prompt-Dl2QY1_B.d.ts} +1 -1
  52. package/dist/{tool-executor-DSvmOBe6.d.ts → tool-executor-B03CRwu-.d.ts} +4 -4
  53. package/dist/types/index.d.ts +15 -15
  54. package/dist/types/index.js +100 -102
  55. package/dist/types/index.js.map +1 -1
  56. package/dist/utils/index.d.ts +1 -1
  57. package/dist/utils/index.js +5 -0
  58. package/dist/utils/index.js.map +1 -1
  59. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,23 +1,109 @@
1
1
  import * as crypto2 from 'crypto';
2
2
  import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
3
- import * as fs from 'fs';
4
3
  import * as fsp2 from 'fs/promises';
5
- import { readFile, readdir, writeFile, stat, mkdir } from 'fs/promises';
4
+ import { readFile, readdir, stat, mkdir } from 'fs/promises';
6
5
  import * as path6 from 'path';
7
6
  import { join, extname, relative } from 'path';
7
+ import * as fs2 from 'fs';
8
8
  import * as os4 from 'os';
9
+ import { execFile, spawn } from 'child_process';
10
+ import { promisify } from 'util';
9
11
  import { EventEmitter } from 'events';
10
12
  import { createGunzip } from 'zlib';
11
13
  import { Readable } from 'stream';
12
14
  import { pipeline } from 'stream/promises';
13
- import { spawn } from 'child_process';
14
15
 
16
+ var __defProp = Object.defineProperty;
17
+ var __getOwnPropNames = Object.getOwnPropertyNames;
15
18
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
19
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
20
  }) : x)(function(x) {
18
21
  if (typeof require !== "undefined") return require.apply(this, arguments);
19
22
  throw Error('Dynamic require of "' + x + '" is not supported');
20
23
  });
24
+ var __esm = (fn, res) => function __init() {
25
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
26
+ };
27
+ var __export = (target, all) => {
28
+ for (var name in all)
29
+ __defProp(target, name, { get: all[name], enumerable: true });
30
+ };
31
+
32
+ // src/utils/atomic-write.ts
33
+ var atomic_write_exports = {};
34
+ __export(atomic_write_exports, {
35
+ atomicWrite: () => atomicWrite,
36
+ ensureDir: () => ensureDir
37
+ });
38
+ async function atomicWrite(targetPath, content, opts = {}) {
39
+ const dir = path6.dirname(targetPath);
40
+ await fsp2.mkdir(dir, { recursive: true });
41
+ const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
42
+ try {
43
+ if (typeof content === "string") {
44
+ await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
45
+ } else {
46
+ await fsp2.writeFile(tmp, content, { flag: "wx" });
47
+ }
48
+ try {
49
+ const fh = await fsp2.open(tmp, "r+");
50
+ try {
51
+ await fh.sync();
52
+ } finally {
53
+ await fh.close();
54
+ }
55
+ } catch {
56
+ }
57
+ let mode;
58
+ try {
59
+ const stat8 = await fsp2.stat(targetPath);
60
+ mode = stat8.mode & 511;
61
+ } catch {
62
+ mode = opts.mode;
63
+ }
64
+ if (mode !== void 0) {
65
+ await fsp2.chmod(tmp, mode);
66
+ }
67
+ await renameWithRetry(tmp, targetPath);
68
+ } catch (err) {
69
+ try {
70
+ await fsp2.unlink(tmp);
71
+ } catch {
72
+ }
73
+ throw err;
74
+ }
75
+ }
76
+ async function ensureDir(dir) {
77
+ await fsp2.mkdir(dir, { recursive: true });
78
+ }
79
+ async function renameWithRetry(from, to) {
80
+ if (process.platform !== "win32") {
81
+ await fsp2.rename(from, to);
82
+ return;
83
+ }
84
+ const delays = [10, 25, 60, 120, 250];
85
+ let lastErr;
86
+ for (let i = 0; i <= delays.length; i++) {
87
+ try {
88
+ await fsp2.rename(from, to);
89
+ return;
90
+ } catch (err) {
91
+ lastErr = err;
92
+ const code = err?.code;
93
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
94
+ throw err;
95
+ }
96
+ await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
97
+ }
98
+ }
99
+ throw lastErr;
100
+ }
101
+ var TRANSIENT_RENAME_CODES;
102
+ var init_atomic_write = __esm({
103
+ "src/utils/atomic-write.ts"() {
104
+ TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
105
+ }
106
+ });
21
107
 
22
108
  // src/types/errors.ts
23
109
  var WrongStackError = class extends Error {
@@ -823,6 +909,7 @@ function providerStatusToCode(status, type) {
823
909
  var ENCRYPTED_PREFIX = "enc:v1:";
824
910
 
825
911
  // src/security/secret-vault.ts
912
+ init_atomic_write();
826
913
  var KEY_BYTES = 32;
827
914
  var IV_BYTES = 12;
828
915
  var TAG_BYTES = 16;
@@ -867,7 +954,7 @@ var DefaultSecretVault = class {
867
954
  loadOrCreateKey() {
868
955
  if (this.key) return this.key;
869
956
  try {
870
- const buf = fs.readFileSync(this.keyFile);
957
+ const buf = fs2.readFileSync(this.keyFile);
871
958
  if (buf.length !== KEY_BYTES) {
872
959
  throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
873
960
  }
@@ -876,13 +963,13 @@ var DefaultSecretVault = class {
876
963
  } catch (err) {
877
964
  if (err.code !== "ENOENT") throw err;
878
965
  }
879
- fs.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
966
+ fs2.mkdirSync(path6.dirname(this.keyFile), { recursive: true });
880
967
  const key = randomBytes(KEY_BYTES);
881
968
  try {
882
- fs.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
969
+ fs2.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
883
970
  } catch (err) {
884
971
  if (err.code !== "EEXIST") throw err;
885
- const buf = fs.readFileSync(this.keyFile);
972
+ const buf = fs2.readFileSync(this.keyFile);
886
973
  if (buf.length !== KEY_BYTES) {
887
974
  throw new Error(`SecretVault: key file ${this.keyFile} has wrong size`);
888
975
  }
@@ -944,7 +1031,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
944
1031
  const merged = deepMerge(current, patch ?? {});
945
1032
  const encrypted = encryptConfigSecrets(merged, vault);
946
1033
  await fsp2.mkdir(path6.dirname(configPath), { recursive: true });
947
- await fsp2.writeFile(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
1034
+ await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
948
1035
  try {
949
1036
  await fsp2.chmod(configPath, 384);
950
1037
  } catch {
@@ -966,7 +1053,7 @@ async function migratePlaintextSecrets(configPath, vault) {
966
1053
  const counter = { n: 0 };
967
1054
  const migrated = walkCount(parsed, vault, counter);
968
1055
  if (counter.n === 0) return { migrated: 0, file: configPath };
969
- await fsp2.writeFile(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
1056
+ await atomicWrite(configPath, JSON.stringify(migrated, null, 2), { mode: 384 });
970
1057
  try {
971
1058
  await fsp2.chmod(configPath, 384);
972
1059
  } catch {
@@ -1076,7 +1163,7 @@ var DefaultLogger = class _DefaultLogger {
1076
1163
  this.pretty = opts.pretty ?? true;
1077
1164
  if (this.file) {
1078
1165
  try {
1079
- fs.mkdirSync(path6.dirname(this.file), { recursive: true });
1166
+ fs2.mkdirSync(path6.dirname(this.file), { recursive: true });
1080
1167
  } catch {
1081
1168
  }
1082
1169
  }
@@ -1115,7 +1202,7 @@ var DefaultLogger = class _DefaultLogger {
1115
1202
  }
1116
1203
  if (this.file) {
1117
1204
  try {
1118
- fs.appendFileSync(this.file, `${JSON.stringify(entry)}
1205
+ fs2.appendFileSync(this.file, `${JSON.stringify(entry)}
1119
1206
  `);
1120
1207
  } catch {
1121
1208
  }
@@ -1597,7 +1684,7 @@ var DefaultPathResolver = class {
1597
1684
  while (dir !== root) {
1598
1685
  for (const marker of PROJECT_MARKERS) {
1599
1686
  try {
1600
- fs.accessSync(path6.join(dir, marker));
1687
+ fs2.accessSync(path6.join(dir, marker));
1601
1688
  return dir;
1602
1689
  } catch {
1603
1690
  }
@@ -1612,7 +1699,7 @@ var DefaultPathResolver = class {
1612
1699
  const abs = path6.isAbsolute(input) ? input : path6.resolve(this.cwd, input);
1613
1700
  let real;
1614
1701
  try {
1615
- real = fs.realpathSync(abs);
1702
+ real = fs2.realpathSync(abs);
1616
1703
  } catch {
1617
1704
  real = path6.normalize(abs);
1618
1705
  }
@@ -1666,7 +1753,7 @@ function buildRecoveryStrategies(opts) {
1666
1753
  async attempt(err) {
1667
1754
  if (!(err instanceof ProviderError) || err.status !== 429) return null;
1668
1755
  const delayMs = err.body?.retryAfterMs ?? 5e3;
1669
- const delay = Math.max(1e3, Math.min(delayMs, 6e4));
1756
+ const delay = Math.min(6e4, Math.max(1e3, delayMs));
1670
1757
  await new Promise((r) => setTimeout(r, delay));
1671
1758
  return { action: "retry", reason: "rate_limit_backoff" };
1672
1759
  }
@@ -1819,15 +1906,15 @@ var PATTERNS = [
1819
1906
  { type: "redis_uri", regex: /redis:\/\/[^\s"'`]+/g },
1820
1907
  {
1821
1908
  type: "bearer_token",
1822
- // Bounded at 512 chars to prevent catastrophic backtracking on adversarial input.
1823
- // Negative lookahead is a simple single-char check no backtracking risk.
1824
- regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?![A-Za-z0-9_.~+/-])/g
1909
+ // Anchored with alternation instead of negative lookahead avoids V8
1910
+ // backtracking risk on adversarial input. Bounded at 512 chars.
1911
+ regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
1825
1912
  },
1826
1913
  {
1827
1914
  type: "high_entropy_env",
1828
- // Value bounded at 512 chars; negative lookahead simplified to avoid
1829
- // nested quantifier backtracking on adversarial input.
1830
- regex: /\b([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?/g
1915
+ // Anchored with alternation instead of lookbehind to avoid backtracking.
1916
+ // Value bounded at 512 chars.
1917
+ regex: /(?:^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?:\s|$)/g
1831
1918
  }
1832
1919
  ];
1833
1920
  var SCRUB_CHUNK_BYTES = 64 * 1024;
@@ -1879,72 +1966,9 @@ var DefaultSecretScrubber = class {
1879
1966
  return visit(obj);
1880
1967
  }
1881
1968
  };
1882
- async function atomicWrite(targetPath, content, opts = {}) {
1883
- const dir = path6.dirname(targetPath);
1884
- await fsp2.mkdir(dir, { recursive: true });
1885
- const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
1886
- try {
1887
- if (typeof content === "string") {
1888
- await fsp2.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
1889
- } else {
1890
- await fsp2.writeFile(tmp, content, { flag: "wx" });
1891
- }
1892
- try {
1893
- const fh = await fsp2.open(tmp, "r+");
1894
- try {
1895
- await fh.sync();
1896
- } finally {
1897
- await fh.close();
1898
- }
1899
- } catch {
1900
- }
1901
- let mode;
1902
- try {
1903
- const stat8 = await fsp2.stat(targetPath);
1904
- mode = stat8.mode & 511;
1905
- } catch {
1906
- mode = opts.mode;
1907
- }
1908
- if (mode !== void 0) {
1909
- await fsp2.chmod(tmp, mode);
1910
- }
1911
- await renameWithRetry(tmp, targetPath);
1912
- } catch (err) {
1913
- try {
1914
- await fsp2.unlink(tmp);
1915
- } catch {
1916
- }
1917
- throw err;
1918
- }
1919
- }
1920
- async function ensureDir(dir) {
1921
- await fsp2.mkdir(dir, { recursive: true });
1922
- }
1923
- var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
1924
- async function renameWithRetry(from, to) {
1925
- if (process.platform !== "win32") {
1926
- await fsp2.rename(from, to);
1927
- return;
1928
- }
1929
- const delays = [10, 25, 60, 120, 250];
1930
- let lastErr;
1931
- for (let i = 0; i <= delays.length; i++) {
1932
- try {
1933
- await fsp2.rename(from, to);
1934
- return;
1935
- } catch (err) {
1936
- lastErr = err;
1937
- const code = err?.code;
1938
- if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
1939
- throw err;
1940
- }
1941
- await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
1942
- }
1943
- }
1944
- throw lastErr;
1945
- }
1946
1969
 
1947
1970
  // src/models/models-registry.ts
1971
+ init_atomic_write();
1948
1972
  var DEFAULT_URL = "https://models.dev/api.json";
1949
1973
  var DEFAULT_TTL_SECONDS = 24 * 3600;
1950
1974
  var FAMILY_BY_NPM = {
@@ -3297,6 +3321,9 @@ function renderPlainText(meta, events) {
3297
3321
  return lines.join("\n");
3298
3322
  }
3299
3323
 
3324
+ // src/utils/index.ts
3325
+ init_atomic_write();
3326
+
3300
3327
  // src/utils/safe-json.ts
3301
3328
  function safeParse(input, maxBytes = 5e6) {
3302
3329
  if (input.length > maxBytes) {
@@ -3725,6 +3752,11 @@ function looksSecret(name) {
3725
3752
  function buildChildEnv(optsOrSessionId) {
3726
3753
  const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
3727
3754
  const passthrough = process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
3755
+ if (passthrough && !process.env["CI"]) {
3756
+ console.warn(
3757
+ "[WrongStack] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
3758
+ );
3759
+ }
3728
3760
  const out = {};
3729
3761
  for (const [k, v] of Object.entries(process.env)) {
3730
3762
  if (v === void 0) continue;
@@ -3755,11 +3787,11 @@ function validateAgainstSchema(value, schema) {
3755
3787
  walk2(value, schema, "", errors);
3756
3788
  return { ok: errors.length === 0, errors };
3757
3789
  }
3758
- function walk2(value, schema, path24, errors) {
3790
+ function walk2(value, schema, path26, errors) {
3759
3791
  if (schema.enum !== void 0) {
3760
3792
  if (!schema.enum.some((e) => deepEqual(e, value))) {
3761
3793
  errors.push({
3762
- path: path24 || "<root>",
3794
+ path: path26 || "<root>",
3763
3795
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
3764
3796
  });
3765
3797
  return;
@@ -3768,7 +3800,7 @@ function walk2(value, schema, path24, errors) {
3768
3800
  if (typeof schema.type === "string") {
3769
3801
  if (!checkType(value, schema.type)) {
3770
3802
  errors.push({
3771
- path: path24 || "<root>",
3803
+ path: path26 || "<root>",
3772
3804
  message: `expected ${schema.type}, got ${describeType(value)}`
3773
3805
  });
3774
3806
  return;
@@ -3778,19 +3810,19 @@ function walk2(value, schema, path24, errors) {
3778
3810
  const obj = value;
3779
3811
  for (const req of schema.required ?? []) {
3780
3812
  if (!(req in obj)) {
3781
- errors.push({ path: joinPath(path24, req), message: "required property missing" });
3813
+ errors.push({ path: joinPath(path26, req), message: "required property missing" });
3782
3814
  }
3783
3815
  }
3784
3816
  if (schema.properties) {
3785
3817
  for (const [key, subSchema] of Object.entries(schema.properties)) {
3786
3818
  if (key in obj) {
3787
- walk2(obj[key], subSchema, joinPath(path24, key), errors);
3819
+ walk2(obj[key], subSchema, joinPath(path26, key), errors);
3788
3820
  }
3789
3821
  }
3790
3822
  }
3791
3823
  }
3792
3824
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
3793
- value.forEach((item, i) => walk2(item, schema.items, `${path24}[${i}]`, errors));
3825
+ value.forEach((item, i) => walk2(item, schema.items, `${path26}[${i}]`, errors));
3794
3826
  }
3795
3827
  }
3796
3828
  function checkType(value, type) {
@@ -3842,6 +3874,9 @@ function deepEqual(a, b) {
3842
3874
  }
3843
3875
  return false;
3844
3876
  }
3877
+
3878
+ // src/storage/session-store.ts
3879
+ init_atomic_write();
3845
3880
  var DefaultSessionStore = class {
3846
3881
  dir;
3847
3882
  events;
@@ -3885,19 +3920,25 @@ var DefaultSessionStore = class {
3885
3920
  { cause: err }
3886
3921
  );
3887
3922
  }
3888
- const writer = new FileSessionWriter(
3889
- id,
3890
- handle,
3891
- (/* @__PURE__ */ new Date()).toISOString(),
3892
- {
3923
+ try {
3924
+ const writer = new FileSessionWriter(
3893
3925
  id,
3894
- model: data.metadata.model,
3895
- provider: data.metadata.provider
3896
- },
3897
- this.events,
3898
- { resumed: true, dir: this.dir, filePath: file }
3899
- );
3900
- return { writer, data };
3926
+ handle,
3927
+ (/* @__PURE__ */ new Date()).toISOString(),
3928
+ {
3929
+ id,
3930
+ model: data.metadata.model,
3931
+ provider: data.metadata.provider
3932
+ },
3933
+ this.events,
3934
+ { resumed: true, dir: this.dir, filePath: file }
3935
+ );
3936
+ return { writer, data };
3937
+ } catch (err) {
3938
+ await handle.close().catch(() => {
3939
+ });
3940
+ throw err;
3941
+ }
3901
3942
  }
3902
3943
  async load(id) {
3903
3944
  const file = path6.join(this.dir, `${id}.jsonl`);
@@ -3943,7 +3984,7 @@ var DefaultSessionStore = class {
3943
3984
  const full = path6.join(this.dir, `${id}.jsonl`);
3944
3985
  const stat8 = await fsp2.stat(full);
3945
3986
  const summary = await this.summarize(id, stat8.mtime.toISOString());
3946
- await fsp2.writeFile(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
3987
+ await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
3947
3988
  console.warn(
3948
3989
  `[session-store] Failed to write manifest for "${id}":`,
3949
3990
  err instanceof Error ? err.message : String(err)
@@ -4171,7 +4212,7 @@ var FileSessionWriter = class {
4171
4212
  this.closed = true;
4172
4213
  if (this.manifestFile) {
4173
4214
  try {
4174
- await fsp2.writeFile(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
4215
+ await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
4175
4216
  } catch {
4176
4217
  }
4177
4218
  }
@@ -4267,6 +4308,9 @@ function userInputTitle(content) {
4267
4308
  const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
4268
4309
  return (text || "(non-text input)").slice(0, 60);
4269
4310
  }
4311
+
4312
+ // src/storage/queue-store.ts
4313
+ init_atomic_write();
4270
4314
  var QueueStore = class {
4271
4315
  file;
4272
4316
  constructor(opts) {
@@ -4317,6 +4361,9 @@ function isPersistedQueueItem(v) {
4317
4361
  const o = v;
4318
4362
  return typeof o["displayText"] === "string" && Array.isArray(o["blocks"]);
4319
4363
  }
4364
+
4365
+ // src/storage/attachment-store.ts
4366
+ init_atomic_write();
4320
4367
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
4321
4368
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
4322
4369
  var DefaultAttachmentStore = class {
@@ -4338,7 +4385,9 @@ var DefaultAttachmentStore = class {
4338
4385
  if (this.spoolDir && bytes >= this.spoolThreshold) {
4339
4386
  await fsp2.mkdir(this.spoolDir, { recursive: true });
4340
4387
  spooledPath = path6.join(this.spoolDir, `${id}.bin`);
4341
- await fsp2.writeFile(spooledPath, input.data, input.kind === "image" ? "base64" : "utf8");
4388
+ await atomicWrite(spooledPath, input.data, {
4389
+ encoding: input.kind === "image" ? "base64" : "utf8"
4390
+ });
4342
4391
  data = void 0;
4343
4392
  }
4344
4393
  const att = {
@@ -4437,6 +4486,9 @@ function mergeAdjacentText(blocks) {
4437
4486
  }
4438
4487
  return out;
4439
4488
  }
4489
+
4490
+ // src/storage/memory-store.ts
4491
+ init_atomic_write();
4440
4492
  var MAX_BYTES_TOTAL = 32e3;
4441
4493
  var DefaultMemoryStore = class {
4442
4494
  files;
@@ -4449,12 +4501,13 @@ var DefaultMemoryStore = class {
4449
4501
  * issue order. Different scopes still proceed in parallel.
4450
4502
  *
4451
4503
  * The chain tracks only the last pending write. If a write fails, its
4452
- * error is caught and swallowed (line 43) so the chain stays alive for
4453
- * subsequent calls. A crash between atomicWrite() and backup copy leaves
4454
- * the file at its new content with no backup — acceptable for an optional
4455
- * backup whose worst case is losing a memory consolidation pass.
4504
+ * error is caught and swallowed so the chain stays alive for subsequent
4505
+ * calls. The error is stored in `writeErrors` so callers can learn about
4506
+ * it on the next read operation.
4456
4507
  */
4457
4508
  writeChain = /* @__PURE__ */ new Map();
4509
+ /** Last write error per scope — surfaced as warnings on the next readAll(). */
4510
+ writeErrors = /* @__PURE__ */ new Map();
4458
4511
  constructor(opts) {
4459
4512
  this.files = {
4460
4513
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -4464,10 +4517,16 @@ var DefaultMemoryStore = class {
4464
4517
  }
4465
4518
  async runSerialized(scope, work) {
4466
4519
  const prior = this.writeChain.get(scope) ?? Promise.resolve();
4520
+ prior.catch((err) => {
4521
+ this.writeErrors.set(scope, err);
4522
+ });
4467
4523
  const next = prior.catch(() => void 0).then(work);
4468
4524
  this.writeChain.set(scope, next);
4469
4525
  try {
4470
4526
  return await next;
4527
+ } catch (err) {
4528
+ this.writeErrors.set(scope, err);
4529
+ throw err;
4471
4530
  } finally {
4472
4531
  if (this.writeChain.get(scope) === next) {
4473
4532
  this.writeChain.delete(scope);
@@ -4477,6 +4536,10 @@ var DefaultMemoryStore = class {
4477
4536
  async readAll() {
4478
4537
  const parts = [];
4479
4538
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
4539
+ const writeErr = this.writeErrors.get(scope);
4540
+ if (writeErr) {
4541
+ parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
4542
+ }
4480
4543
  const body = await this.read(scope);
4481
4544
  if (body.trim()) parts.push(`## ${labelOf(scope)}
4482
4545
 
@@ -4969,6 +5032,9 @@ function runConfigMigrations(input, targetVersion, migrations) {
4969
5032
  return { config: current, applied, shouldPersist };
4970
5033
  }
4971
5034
  var DEFAULT_CONFIG_MIGRATIONS = [];
5035
+
5036
+ // src/storage/recovery-lock.ts
5037
+ init_atomic_write();
4972
5038
  var LOCK_FILE = "active.json";
4973
5039
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
4974
5040
  var RecoveryLock = class {
@@ -5189,6 +5255,9 @@ var SessionAnalyzer = class {
5189
5255
  return last - first;
5190
5256
  }
5191
5257
  };
5258
+
5259
+ // src/storage/todos-checkpoint.ts
5260
+ init_atomic_write();
5192
5261
  async function loadTodosCheckpoint(filePath) {
5193
5262
  let raw;
5194
5263
  try {
@@ -5257,6 +5326,9 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
5257
5326
  }
5258
5327
  };
5259
5328
  }
5329
+
5330
+ // src/storage/plan-store.ts
5331
+ init_atomic_write();
5260
5332
  async function loadPlan(filePath) {
5261
5333
  let raw;
5262
5334
  try {
@@ -5491,6 +5563,9 @@ ${cat}:`);
5491
5563
  }
5492
5564
  return lines.join("\n");
5493
5565
  }
5566
+
5567
+ // src/storage/director-state.ts
5568
+ init_atomic_write();
5494
5569
  async function loadDirectorState(filePath) {
5495
5570
  let raw;
5496
5571
  try {
@@ -5669,6 +5744,9 @@ var DirectorStateCheckpoint = class {
5669
5744
  }
5670
5745
  }
5671
5746
  };
5747
+
5748
+ // src/security/permission-policy.ts
5749
+ init_atomic_write();
5672
5750
  var DefaultPermissionPolicy = class {
5673
5751
  policy = {};
5674
5752
  loaded = false;
@@ -7070,6 +7148,14 @@ var DoneConditionChecker = class {
7070
7148
  };
7071
7149
  }
7072
7150
  break;
7151
+ case "directive":
7152
+ if (this.condition.maxIterations && state.iterations >= this.condition.maxIterations) {
7153
+ return { done: true, reason: `max iterations (${this.condition.maxIterations}) reached`, ...state };
7154
+ }
7155
+ if (this.condition.maxToolCalls && state.toolCalls >= this.condition.maxToolCalls) {
7156
+ return { done: true, reason: `max tool calls (${this.condition.maxToolCalls}) reached`, ...state };
7157
+ }
7158
+ break;
7073
7159
  }
7074
7160
  return { done: false, iterations: state.iterations, toolCalls: state.toolCalls };
7075
7161
  }
@@ -7089,10 +7175,16 @@ var AutonomousRunner = class {
7089
7175
  const offToolExecuted = this.opts.agent.events?.on?.("tool.executed", () => {
7090
7176
  this.toolCalls++;
7091
7177
  });
7178
+ const offIterationCompleted = this.opts.agent.events?.on?.("iteration.completed", () => {
7179
+ if (this.opts.enableAutonomousContinue && this.opts.doneCondition.type === "directive") {
7180
+ this.iterations++;
7181
+ }
7182
+ });
7092
7183
  try {
7093
7184
  return await this.runLoop();
7094
7185
  } finally {
7095
7186
  offToolExecuted?.();
7187
+ offIterationCompleted?.();
7096
7188
  }
7097
7189
  }
7098
7190
  async runLoop() {
@@ -7116,12 +7208,14 @@ var AutonomousRunner = class {
7116
7208
  const ctrl = new AbortController();
7117
7209
  const timeout = setTimeout(() => ctrl.abort(), this.opts.iterationTimeoutMs ?? 3e4);
7118
7210
  try {
7211
+ const isDirectiveMode = this.opts.doneCondition.type === "directive" && this.opts.enableAutonomousContinue;
7119
7212
  const result = await this.opts.agent.run("", {
7120
7213
  signal: ctrl.signal,
7121
- maxIterations: 1,
7122
- executionStrategy: "sequential"
7214
+ maxIterations: isDirectiveMode ? this.opts.doneCondition.maxIterations ?? 100 : 1,
7215
+ executionStrategy: "sequential",
7216
+ autonomousContinue: isDirectiveMode ? true : void 0
7123
7217
  });
7124
- this.iterations++;
7218
+ if (!isDirectiveMode) this.iterations++;
7125
7219
  if (result.status === "done") {
7126
7220
  this.lastOutput = result.finalText;
7127
7221
  }
@@ -7156,23 +7250,469 @@ var AutonomousRunner = class {
7156
7250
  toolCalls: this.toolCalls,
7157
7251
  reason: e instanceof Error ? e.message : String(e)
7158
7252
  };
7159
- this.opts.onDone?.(failedResult);
7160
- return failedResult;
7161
- } finally {
7162
- clearTimeout(timeout);
7253
+ this.opts.onDone?.(failedResult);
7254
+ return failedResult;
7255
+ } finally {
7256
+ clearTimeout(timeout);
7257
+ }
7258
+ }
7259
+ return {
7260
+ status: "aborted",
7261
+ iterations: this.iterations,
7262
+ toolCalls: this.toolCalls,
7263
+ reason: "stopped externally"
7264
+ };
7265
+ }
7266
+ stop() {
7267
+ this.stopped = true;
7268
+ }
7269
+ };
7270
+
7271
+ // src/storage/goal-store.ts
7272
+ init_atomic_write();
7273
+ var MAX_JOURNAL_ENTRIES = 500;
7274
+ function goalFilePath(projectRoot) {
7275
+ return path6.join(projectRoot, ".wrongstack", "goal.json");
7276
+ }
7277
+ async function loadGoal(filePath) {
7278
+ let raw;
7279
+ try {
7280
+ raw = await fsp2.readFile(filePath, "utf8");
7281
+ } catch {
7282
+ return null;
7283
+ }
7284
+ try {
7285
+ const parsed = JSON.parse(raw);
7286
+ if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
7287
+ return null;
7288
+ }
7289
+ return parsed;
7290
+ } catch {
7291
+ return null;
7292
+ }
7293
+ }
7294
+ async function saveGoal(filePath, goal) {
7295
+ await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
7296
+ }
7297
+ function emptyGoal(goal) {
7298
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7299
+ return {
7300
+ version: 1,
7301
+ goal,
7302
+ setAt: now,
7303
+ lastActivityAt: now,
7304
+ iterations: 0,
7305
+ engineState: "idle",
7306
+ journal: []
7307
+ };
7308
+ }
7309
+ function appendJournal(goal, entry) {
7310
+ const iteration = goal.iterations + 1;
7311
+ const at = (/* @__PURE__ */ new Date()).toISOString();
7312
+ const full = { ...entry, iteration, at };
7313
+ const journal = [...goal.journal, full];
7314
+ const trimmed = journal.length > MAX_JOURNAL_ENTRIES ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES) : journal;
7315
+ return {
7316
+ ...goal,
7317
+ iterations: iteration,
7318
+ lastActivityAt: at,
7319
+ journal: trimmed
7320
+ };
7321
+ }
7322
+ function summarizeUsage(goal) {
7323
+ let totalCostUsd = 0;
7324
+ let totalInputTokens = 0;
7325
+ let totalOutputTokens = 0;
7326
+ let iterationsWithUsage = 0;
7327
+ for (const e of goal.journal) {
7328
+ if (typeof e.costUsd === "number") totalCostUsd += e.costUsd;
7329
+ if (e.tokens) {
7330
+ totalInputTokens += e.tokens.input;
7331
+ totalOutputTokens += e.tokens.output;
7332
+ }
7333
+ if (typeof e.costUsd === "number" || e.tokens) iterationsWithUsage++;
7334
+ }
7335
+ return { totalCostUsd, totalInputTokens, totalOutputTokens, iterationsWithUsage };
7336
+ }
7337
+ function formatGoal(goal, journalLimit = 10) {
7338
+ const lines = [];
7339
+ lines.push(`Goal: ${goal.goal}`);
7340
+ lines.push(`Set: ${goal.setAt}`);
7341
+ lines.push(`Last activity: ${goal.lastActivityAt}`);
7342
+ lines.push(`Iterations: ${goal.iterations}`);
7343
+ lines.push(`Engine: ${goal.engineState}`);
7344
+ const usage = summarizeUsage(goal);
7345
+ if (usage.iterationsWithUsage > 0) {
7346
+ lines.push(
7347
+ `Spent: $${usage.totalCostUsd.toFixed(4)} (in ${usage.totalInputTokens} / out ${usage.totalOutputTokens} tokens across ${usage.iterationsWithUsage} iterations)`
7348
+ );
7349
+ }
7350
+ if (goal.journal.length > 0) {
7351
+ lines.push("");
7352
+ lines.push(`Recent journal (last ${Math.min(journalLimit, goal.journal.length)}):`);
7353
+ const tail = goal.journal.slice(-journalLimit);
7354
+ for (const e of tail) {
7355
+ const mark = e.status === "success" ? "\u2713" : e.status === "failure" ? "\u2717" : e.status === "aborted" ? "\u2298" : "\xB7";
7356
+ const note = e.note ? ` \u2014 ${e.note}` : "";
7357
+ const cost = typeof e.costUsd === "number" ? ` ($${e.costUsd.toFixed(4)})` : "";
7358
+ lines.push(` #${e.iteration} ${mark} [${e.source}] ${e.task}${cost}${note}`);
7359
+ }
7360
+ }
7361
+ return lines.join("\n");
7362
+ }
7363
+
7364
+ // src/execution/eternal-autonomy.ts
7365
+ var execFileP = promisify(execFile);
7366
+ var EternalAutonomyEngine = class {
7367
+ constructor(opts) {
7368
+ this.opts = opts;
7369
+ this.goalPath = goalFilePath(opts.projectRoot);
7370
+ }
7371
+ opts;
7372
+ state = "idle";
7373
+ stopRequested = false;
7374
+ consecutiveFailures = 0;
7375
+ currentCtrl = null;
7376
+ iterationsSinceCompact = 0;
7377
+ goalPath;
7378
+ /** Current engine state — readable for UIs. */
7379
+ get currentState() {
7380
+ return this.state;
7381
+ }
7382
+ /** Synchronously request stop. Resolves once the running iteration aborts. */
7383
+ stop() {
7384
+ this.stopRequested = true;
7385
+ this.currentCtrl?.abort();
7386
+ void this.persistEngineState("stopped").catch(() => {
7387
+ });
7388
+ this.state = "stopped";
7389
+ }
7390
+ /**
7391
+ * Mark the engine as 'running' on disk + reset stop state so a new
7392
+ * batch of `runOneIteration()` calls can proceed. Called by the REPL
7393
+ * when the user invokes `/autonomy eternal`. Idempotent.
7394
+ */
7395
+ async prime() {
7396
+ this.stopRequested = false;
7397
+ this.state = "running";
7398
+ await this.persistEngineState("running").catch(() => {
7399
+ });
7400
+ }
7401
+ /**
7402
+ * Main loop. Returns when stop() is called or the goal file is removed.
7403
+ * Does NOT throw — every iteration is wrapped to keep the loop alive.
7404
+ */
7405
+ async run() {
7406
+ this.state = "running";
7407
+ await this.persistEngineState("running");
7408
+ try {
7409
+ while (!this.stopRequested) {
7410
+ let iterationOk = false;
7411
+ try {
7412
+ iterationOk = await this.runOneIteration();
7413
+ } catch (err) {
7414
+ this.consecutiveFailures++;
7415
+ this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
7416
+ await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
7417
+ }
7418
+ if (iterationOk) {
7419
+ this.consecutiveFailures = 0;
7420
+ }
7421
+ if (this.stopRequested) break;
7422
+ await sleep(this.opts.cycleGapMs ?? 1e3);
7423
+ }
7424
+ } finally {
7425
+ this.state = "stopped";
7426
+ await this.persistEngineState("stopped").catch(() => {
7427
+ });
7428
+ }
7429
+ }
7430
+ /**
7431
+ * Execute a single sense-decide-execute-reflect cycle.
7432
+ * Returns true on success, false on handled failure / no-op.
7433
+ *
7434
+ * Exposed publicly so the REPL can pace iterations from its main loop
7435
+ * — running the engine and the REPL as a single sequential consumer of
7436
+ * `agent.run()` avoids race conditions on the shared Context.
7437
+ */
7438
+ async runOneIteration() {
7439
+ const goal = await loadGoal(this.goalPath);
7440
+ if (!goal) {
7441
+ this.stopRequested = true;
7442
+ return false;
7443
+ }
7444
+ const action = await this.decide(goal);
7445
+ if (!action) {
7446
+ await sleep(5e3);
7447
+ return false;
7448
+ }
7449
+ const ctrl = new AbortController();
7450
+ this.currentCtrl = ctrl;
7451
+ const timer = setTimeout(
7452
+ () => ctrl.abort(),
7453
+ this.opts.iterationTimeoutMs ?? 5 * 6e4
7454
+ );
7455
+ let status = "success";
7456
+ let note;
7457
+ const tc = this.opts.agent.ctx?.tokenCounter;
7458
+ const beforeUsage = tc?.total?.();
7459
+ const beforeCost = tc?.estimateCost?.().total;
7460
+ try {
7461
+ const result = await this.opts.agent.run(
7462
+ [{ type: "text", text: action.directive }],
7463
+ { signal: ctrl.signal }
7464
+ );
7465
+ if (result.status === "aborted") {
7466
+ status = "aborted";
7467
+ note = "stopped by user";
7468
+ } else if (result.status === "failed") {
7469
+ status = "failure";
7470
+ note = result.error?.describe?.() ?? "agent run failed";
7471
+ } else if (result.status === "max_iterations") {
7472
+ status = "failure";
7473
+ note = `max iterations (${result.iterations})`;
7474
+ } else {
7475
+ status = "success";
7476
+ const tail = (result.finalText ?? "").slice(0, 240).replace(/\s+/g, " ").trim();
7477
+ if (tail) note = tail;
7478
+ }
7479
+ } catch (err) {
7480
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
7481
+ status = isAbort ? "aborted" : "failure";
7482
+ note = err instanceof Error ? err.message : String(err);
7483
+ } finally {
7484
+ clearTimeout(timer);
7485
+ this.currentCtrl = null;
7486
+ }
7487
+ const afterUsage = tc?.total?.();
7488
+ const afterCost = tc?.estimateCost?.().total;
7489
+ const tokens = beforeUsage && afterUsage ? {
7490
+ input: Math.max(0, afterUsage.input - beforeUsage.input),
7491
+ output: Math.max(0, afterUsage.output - beforeUsage.output)
7492
+ } : void 0;
7493
+ const costUsd = typeof beforeCost === "number" && typeof afterCost === "number" ? Math.max(0, afterCost - beforeCost) : void 0;
7494
+ await this.appendIterationEntry({
7495
+ source: action.source,
7496
+ task: action.task,
7497
+ status,
7498
+ note,
7499
+ tokens,
7500
+ costUsd
7501
+ });
7502
+ let iterationIndex = 0;
7503
+ try {
7504
+ const reloaded = await loadGoal(this.goalPath);
7505
+ iterationIndex = reloaded?.iterations ?? 0;
7506
+ } catch {
7507
+ }
7508
+ this.opts.onIteration?.({
7509
+ at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
7510
+ iteration: iterationIndex,
7511
+ source: action.source,
7512
+ task: action.task,
7513
+ status,
7514
+ note,
7515
+ tokens,
7516
+ costUsd
7517
+ });
7518
+ if (status === "failure") {
7519
+ this.consecutiveFailures++;
7520
+ return false;
7521
+ }
7522
+ if (status === "aborted") {
7523
+ if (this.stopRequested) return false;
7524
+ this.consecutiveFailures++;
7525
+ return false;
7526
+ }
7527
+ this.iterationsSinceCompact++;
7528
+ await this.maybeCompact().catch((err) => {
7529
+ this.opts.onError?.(
7530
+ err instanceof Error ? err : new Error(String(err)),
7531
+ this.consecutiveFailures
7532
+ );
7533
+ });
7534
+ return true;
7535
+ }
7536
+ /**
7537
+ * Run compaction when either trigger fires:
7538
+ * - We've done >= compactEveryNIterations since the last compact.
7539
+ * - Current request tokens exceed aggressiveCompactRatio * maxContext.
7540
+ *
7541
+ * The second check uses *aggressive* mode to free more headroom; the
7542
+ * cadence check uses non-aggressive (cheaper).
7543
+ */
7544
+ async maybeCompact() {
7545
+ const compactor = this.opts.compactor;
7546
+ if (!compactor) return;
7547
+ const ctx = this.opts.agent.ctx;
7548
+ if (!ctx) return;
7549
+ const cadence = this.opts.compactEveryNIterations ?? 25;
7550
+ const threshold = this.opts.aggressiveCompactRatio ?? 0.85;
7551
+ const maxCtx = this.opts.maxContextTokens;
7552
+ let aggressive = false;
7553
+ let shouldRun = false;
7554
+ if (this.iterationsSinceCompact >= cadence) {
7555
+ shouldRun = true;
7556
+ }
7557
+ if (maxCtx && maxCtx > 0) {
7558
+ const used = ctx.tokenCounter?.currentRequestTokens?.();
7559
+ if (used) {
7560
+ const total = used.input + used.cacheRead;
7561
+ if (total / maxCtx >= threshold) {
7562
+ shouldRun = true;
7563
+ aggressive = true;
7564
+ }
7565
+ }
7566
+ }
7567
+ if (!shouldRun) return;
7568
+ const report = await compactor.compact(ctx, { aggressive });
7569
+ this.iterationsSinceCompact = 0;
7570
+ const saved = report.before - report.after;
7571
+ await this.appendIterationEntry({
7572
+ source: "manual",
7573
+ task: `compaction (${aggressive ? "aggressive" : "cadence"})`,
7574
+ status: "success",
7575
+ note: `saved ~${saved} tokens (${report.before}\u2192${report.after})`
7576
+ });
7577
+ }
7578
+ /**
7579
+ * Hybrid idea source.
7580
+ * 1. Pending todos on the agent's context.
7581
+ * 2. Dirty git working tree → propose a "review and finish this" task.
7582
+ * 3. Otherwise: brainstorm via the LLM against the goal.
7583
+ *
7584
+ * After failureBudget consecutive failures, force brainstorm so the
7585
+ * engine doesn't loop on the same broken todo or stuck git state.
7586
+ */
7587
+ async decide(goal) {
7588
+ const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
7589
+ if (!forceBrainstorm) {
7590
+ const todo = this.pickPendingTodo();
7591
+ if (todo) {
7592
+ return {
7593
+ source: "todo",
7594
+ task: todo.content,
7595
+ directive: this.buildDirective(goal, "todo", todo.content)
7596
+ };
7597
+ }
7598
+ const gitTask = await this.pickGitTask();
7599
+ if (gitTask) {
7600
+ return {
7601
+ source: "git",
7602
+ task: gitTask,
7603
+ directive: this.buildDirective(goal, "git", gitTask)
7604
+ };
7163
7605
  }
7164
7606
  }
7607
+ const brainstormed = await this.brainstormTask(goal);
7608
+ if (!brainstormed) return null;
7165
7609
  return {
7166
- status: "aborted",
7167
- iterations: this.iterations,
7168
- toolCalls: this.toolCalls,
7169
- reason: "stopped externally"
7610
+ source: "brainstorm",
7611
+ task: brainstormed,
7612
+ directive: this.buildDirective(goal, "brainstorm", brainstormed)
7170
7613
  };
7171
7614
  }
7172
- stop() {
7173
- this.stopped = true;
7615
+ pickPendingTodo() {
7616
+ const todos = this.opts.agent.ctx.todos;
7617
+ if (!Array.isArray(todos)) return null;
7618
+ return todos.find((t2) => t2.status === "pending") ?? null;
7619
+ }
7620
+ async pickGitTask() {
7621
+ let out;
7622
+ try {
7623
+ out = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
7624
+ } catch {
7625
+ return null;
7626
+ }
7627
+ const dirty = out.trim();
7628
+ if (!dirty) return null;
7629
+ const lines = dirty.split("\n").slice(0, 8);
7630
+ const preview = lines.join(", ");
7631
+ return `Inspect the dirty working tree and either finish the in-progress work or revert it. Files: ${preview}`;
7632
+ }
7633
+ async readGitStatus() {
7634
+ const { stdout } = await execFileP("git", ["status", "--porcelain"], {
7635
+ cwd: this.opts.projectRoot,
7636
+ timeout: 5e3
7637
+ });
7638
+ return stdout;
7639
+ }
7640
+ async brainstormTask(goal) {
7641
+ const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
7642
+ const directive = [
7643
+ "You are deciding the next action in an autonomous loop pursuing a long-running goal.",
7644
+ "",
7645
+ `Goal: ${goal.goal}`,
7646
+ "",
7647
+ lastFew ? `Recent iterations:
7648
+ ${lastFew}` : "No prior iterations yet.",
7649
+ "",
7650
+ "Output ONE concrete, immediately-actionable task that advances the goal.",
7651
+ "Constraints:",
7652
+ "- One sentence, imperative form, under 200 chars.",
7653
+ "- No preamble, no explanation, no markdown \u2014 just the task line.",
7654
+ "- If recent iterations show repeated failures on the same target, pivot.",
7655
+ "- If the goal appears fully accomplished, output exactly: DONE"
7656
+ ].join("\n");
7657
+ try {
7658
+ const ctrl = new AbortController();
7659
+ const timer = setTimeout(() => ctrl.abort(), 6e4);
7660
+ try {
7661
+ const result = await this.opts.agent.run(
7662
+ [{ type: "text", text: directive }],
7663
+ { signal: ctrl.signal, maxIterations: 1 }
7664
+ );
7665
+ if (result.status !== "done") return null;
7666
+ const text = (result.finalText ?? "").trim();
7667
+ if (!text || text === "DONE") return null;
7668
+ const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
7669
+ if (!firstLine) return null;
7670
+ return firstLine.slice(0, 240);
7671
+ } finally {
7672
+ clearTimeout(timer);
7673
+ }
7674
+ } catch {
7675
+ return null;
7676
+ }
7677
+ }
7678
+ buildDirective(goal, source, task) {
7679
+ return [
7680
+ "[ETERNAL AUTONOMY \u2014 iteration directive]",
7681
+ "",
7682
+ `Goal: ${goal.goal}`,
7683
+ `Source: ${source}`,
7684
+ `Task: ${task}`,
7685
+ "",
7686
+ "Execute this task end-to-end using the tools available to you. Make the",
7687
+ "changes, run tests if relevant, and commit / push as appropriate. Do not",
7688
+ "ask for confirmation \u2014 YOLO mode is active. When the task is done, stop;",
7689
+ "the loop will pick the next action."
7690
+ ].join("\n");
7691
+ }
7692
+ async appendIterationEntry(entry) {
7693
+ const current = await loadGoal(this.goalPath);
7694
+ if (!current) {
7695
+ return;
7696
+ }
7697
+ const updated = appendJournal(current, entry);
7698
+ await saveGoal(this.goalPath, updated);
7699
+ }
7700
+ async appendFailure(task, note) {
7701
+ await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
7702
+ }
7703
+ async persistEngineState(state) {
7704
+ const current = await loadGoal(this.goalPath);
7705
+ if (!current) return;
7706
+ if (current.engineState === state) return;
7707
+ await saveGoal(this.goalPath, { ...current, engineState: state });
7174
7708
  }
7175
7709
  };
7710
+ function sleep(ms) {
7711
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
7712
+ }
7713
+
7714
+ // src/coordination/director.ts
7715
+ init_atomic_write();
7176
7716
 
7177
7717
  // src/coordination/director-prompts.ts
7178
7718
  var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
@@ -8318,10 +8858,10 @@ function makeSpawnTool(director, roster) {
8318
8858
  const subagentId = await director.spawn(cfg);
8319
8859
  return { subagentId, provider: cfg.provider, model: cfg.model, name: cfg.name };
8320
8860
  } catch (err) {
8321
- if (err instanceof DirectorBudgetError) {
8861
+ if (err instanceof FleetSpawnBudgetError) {
8322
8862
  return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
8323
8863
  }
8324
- if (err instanceof DirectorCostCapError) {
8864
+ if (err instanceof FleetCostCapError) {
8325
8865
  return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
8326
8866
  }
8327
8867
  return { error: err instanceof Error ? err.message : String(err) };
@@ -8522,7 +9062,7 @@ function makeFleetHealthTool(director) {
8522
9062
  }
8523
9063
 
8524
9064
  // src/coordination/director.ts
8525
- var DirectorBudgetError = class extends Error {
9065
+ var FleetSpawnBudgetError = class extends Error {
8526
9066
  kind;
8527
9067
  limit;
8528
9068
  observed;
@@ -8530,13 +9070,13 @@ var DirectorBudgetError = class extends Error {
8530
9070
  super(
8531
9071
  kind === "max_spawns" ? `Director spawn budget exceeded: tried to spawn #${observed} but maxSpawns is ${limit}` : `Director spawn depth budget exceeded: this director is at depth ${observed} and maxSpawnDepth is ${limit}`
8532
9072
  );
8533
- this.name = "DirectorBudgetError";
9073
+ this.name = "FleetSpawnBudgetError";
8534
9074
  this.kind = kind;
8535
9075
  this.limit = limit;
8536
9076
  this.observed = observed;
8537
9077
  }
8538
9078
  };
8539
- var DirectorCostCapError = class extends Error {
9079
+ var FleetCostCapError = class extends Error {
8540
9080
  kind;
8541
9081
  limit;
8542
9082
  observed;
@@ -8544,16 +9084,34 @@ var DirectorCostCapError = class extends Error {
8544
9084
  super(
8545
9085
  `Director cost cap exceeded: total fleet spend ${observed.toFixed(4)} exceeds maxCostUsd ${limit.toFixed(4)}`
8546
9086
  );
8547
- this.name = "DirectorCostCapError";
9087
+ this.name = "FleetCostCapError";
8548
9088
  this.kind = "max_cost_usd";
8549
9089
  this.limit = limit;
8550
9090
  this.observed = observed;
8551
9091
  }
8552
9092
  };
8553
9093
  var Director = class {
9094
+ /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
9095
+ get coordinatorId() {
9096
+ return this.id;
9097
+ }
8554
9098
  id;
9099
+ /**
9100
+ * The fleet event bus. Backed by `fleetManager?.fleet` when a FleetManager
9101
+ * is injected; otherwise own FleetBus instance (preserves existing behavior).
9102
+ */
8555
9103
  fleet;
9104
+ /**
9105
+ * Usage rollup. Backed by `fleetManager?.usage` when a FleetManager is
9106
+ * injected; otherwise own FleetUsageAggregator.
9107
+ */
8556
9108
  usage;
9109
+ /**
9110
+ * Optional fleet-level policy container. When provided the Director
9111
+ * delegates spawn budgeting, manifest entries, and checkpointing to it
9112
+ * instead of managing those internally. All other behavior is unchanged.
9113
+ */
9114
+ fleetManager;
8557
9115
  /**
8558
9116
  * Director-side bridge endpoint. Subagents are wired to the same
8559
9117
  * in-memory transport so the director can `ask()` them synchronously
@@ -8604,8 +9162,8 @@ var Director = class {
8604
9162
  /** Debounce timer for periodic manifest writes. */
8605
9163
  manifestTimer = null;
8606
9164
  manifestDebounceMs;
8607
- /** Fleet-wide cost cap. Infinity means no cap. */
8608
- maxCostUsd;
9165
+ /** Fleet-wide cost cap (entire fleet total, distinct from SubagentBudget limits). Infinity means no cap. */
9166
+ maxFleetCostUsd;
8609
9167
  /** Max auto-extensions per subagent per budget kind before denying. */
8610
9168
  maxBudgetExtensions;
8611
9169
  /** Sessions root for direct subagent JSONL reads (fleet_session tool). */
@@ -8638,7 +9196,7 @@ var Director = class {
8638
9196
  this.spawnDepth = opts.spawnDepth ?? 0;
8639
9197
  this.sessionWriter = opts.sessionWriter ?? null;
8640
9198
  this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
8641
- this.maxCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
9199
+ this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
8642
9200
  this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 2;
8643
9201
  this.sessionsRoot = opts.sessionsRoot;
8644
9202
  this.directorRunId = opts.directorRunId ?? this.id;
@@ -8649,20 +9207,28 @@ var Director = class {
8649
9207
  maxSpawnDepth: this.maxSpawnDepth,
8650
9208
  directorBudget: opts.directorBudget
8651
9209
  }, opts.checkpointDebounceMs ?? 250) : null;
9210
+ this.fleetManager = opts.fleetManager;
8652
9211
  if (this.sharedScratchpadPath) {
8653
- void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
9212
+ void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
9213
+ (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
9214
+ );
8654
9215
  }
8655
9216
  this.transport = new InMemoryBridgeTransport();
8656
9217
  this.bridge = new InMemoryAgentBridge(
8657
9218
  { agentId: this.id, coordinatorId: this.id },
8658
9219
  this.transport
8659
9220
  );
8660
- this.fleet = new FleetBus();
8661
- this.usage = new FleetUsageAggregator(
8662
- this.fleet,
8663
- (id) => this.priceLookups.get(id),
8664
- (id) => this.subagentMeta.get(id)
8665
- );
9221
+ if (this.fleetManager) {
9222
+ this.fleet = this.fleetManager.fleet;
9223
+ this.usage = this.fleetManager.usage;
9224
+ } else {
9225
+ this.fleet = new FleetBus();
9226
+ this.usage = new FleetUsageAggregator(
9227
+ this.fleet,
9228
+ (id) => this.priceLookups.get(id),
9229
+ (id) => this.subagentMeta.get(id)
9230
+ );
9231
+ }
8666
9232
  this.coordinator = new DefaultMultiAgentCoordinator(
8667
9233
  { ...opts.config, coordinatorId: this.id },
8668
9234
  { runner: opts.runner }
@@ -8751,7 +9317,9 @@ var Director = class {
8751
9317
  if (this.manifestTimer) return;
8752
9318
  this.manifestTimer = setTimeout(() => {
8753
9319
  this.manifestTimer = null;
8754
- void this.writeManifest().catch(() => void 0);
9320
+ void this.writeManifest().catch(
9321
+ (err) => this.logShutdownError("manifest_write_debounced", err)
9322
+ );
8755
9323
  }, this.manifestDebounceMs);
8756
9324
  }
8757
9325
  /**
@@ -8764,58 +9332,73 @@ var Director = class {
8764
9332
  * it the `cost` column in `usage.snapshot()` stays at 0.
8765
9333
  */
8766
9334
  async spawn(config, priceLookup) {
8767
- if (this.spawnDepth >= this.maxSpawnDepth) {
8768
- throw new DirectorBudgetError("max_spawn_depth", this.maxSpawnDepth, this.spawnDepth);
8769
- }
8770
- if (this.spawnCount >= this.maxSpawns) {
8771
- throw new DirectorBudgetError("max_spawns", this.maxSpawns, this.spawnCount + 1);
8772
- }
8773
- if (this.maxCostUsd < Number.POSITIVE_INFINITY) {
8774
- const totalCost = this.usage.snapshot().total?.cost ?? 0;
8775
- if (totalCost >= this.maxCostUsd) {
8776
- throw new DirectorCostCapError(this.maxCostUsd, totalCost);
9335
+ if (this.fleetManager) {
9336
+ const rejection = this.fleetManager.canSpawn(config);
9337
+ if (rejection) {
9338
+ if (rejection.kind === "max_spawn_depth") throw new FleetSpawnBudgetError("max_spawn_depth", rejection.limit, rejection.observed);
9339
+ if (rejection.kind === "max_spawns") throw new FleetSpawnBudgetError("max_spawns", rejection.limit, rejection.observed);
9340
+ if (rejection.kind === "max_cost_usd") throw new FleetCostCapError(rejection.limit, rejection.observed);
9341
+ }
9342
+ } else {
9343
+ if (this.spawnDepth >= this.maxSpawnDepth) {
9344
+ throw new FleetSpawnBudgetError("max_spawn_depth", this.maxSpawnDepth, this.spawnDepth);
9345
+ }
9346
+ if (this.spawnCount >= this.maxSpawns) {
9347
+ throw new FleetSpawnBudgetError("max_spawns", this.maxSpawns, this.spawnCount + 1);
9348
+ }
9349
+ if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
9350
+ const totalCost = this.usage.snapshot().total?.cost ?? 0;
9351
+ if (totalCost >= this.maxFleetCostUsd) {
9352
+ throw new FleetCostCapError(this.maxFleetCostUsd, totalCost);
9353
+ }
8777
9354
  }
8778
9355
  }
8779
9356
  const result = await this.coordinator.spawn(config);
8780
- this.spawnCount += 1;
8781
- this.subagentMeta.set(result.subagentId, {
8782
- provider: config.provider,
8783
- model: config.model
8784
- });
8785
- if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
9357
+ if (this.fleetManager) {
9358
+ this.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
9359
+ } else {
9360
+ this.spawnCount += 1;
9361
+ this.subagentMeta.set(result.subagentId, {
9362
+ provider: config.provider,
9363
+ model: config.model
9364
+ });
9365
+ if (priceLookup) this.priceLookups.set(result.subagentId, priceLookup);
9366
+ }
8786
9367
  const subagentBridge = new InMemoryAgentBridge(
8787
9368
  { agentId: result.subagentId, coordinatorId: this.id },
8788
9369
  this.transport
8789
9370
  );
8790
9371
  this.coordinator.setSubagentBridge(result.subagentId, subagentBridge);
8791
9372
  this.subagentBridges.set(result.subagentId, subagentBridge);
8792
- this.manifestEntries.set(result.subagentId, {
8793
- subagentId: result.subagentId,
8794
- name: config.name,
8795
- role: config.role,
8796
- provider: config.provider,
8797
- model: config.model,
8798
- taskIds: []
8799
- });
8800
- const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
8801
- this.stateCheckpoint?.recordSpawn(
8802
- {
8803
- id: result.subagentId,
9373
+ if (!this.fleetManager) {
9374
+ this.manifestEntries.set(result.subagentId, {
9375
+ subagentId: result.subagentId,
8804
9376
  name: config.name,
8805
9377
  role: config.role,
8806
9378
  provider: config.provider,
8807
9379
  model: config.model,
8808
- spawnedAt
8809
- },
8810
- this.spawnCount
8811
- );
8812
- void this.appendSessionEvent({
8813
- type: "agent_spawned",
8814
- ts: spawnedAt,
8815
- agentId: result.subagentId,
8816
- role: config.role ?? config.name
8817
- });
8818
- this.scheduleManifest();
9380
+ taskIds: []
9381
+ });
9382
+ const spawnedAt = (/* @__PURE__ */ new Date()).toISOString();
9383
+ this.stateCheckpoint?.recordSpawn(
9384
+ {
9385
+ id: result.subagentId,
9386
+ name: config.name,
9387
+ role: config.role,
9388
+ provider: config.provider,
9389
+ model: config.model,
9390
+ spawnedAt
9391
+ },
9392
+ this.spawnCount
9393
+ );
9394
+ void this.appendSessionEvent({
9395
+ type: "agent_spawned",
9396
+ ts: spawnedAt,
9397
+ agentId: result.subagentId,
9398
+ role: config.role ?? config.name
9399
+ });
9400
+ this.scheduleManifest();
9401
+ }
8819
9402
  return result.subagentId;
8820
9403
  }
8821
9404
  /**
@@ -8927,7 +9510,7 @@ var Director = class {
8927
9510
  usage: this.usage.snapshot()
8928
9511
  };
8929
9512
  await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
8930
- await fsp2.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
9513
+ await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
8931
9514
  return this.manifestPath;
8932
9515
  }
8933
9516
  /**
@@ -8982,8 +9565,12 @@ var Director = class {
8982
9565
  async assign(task) {
8983
9566
  const taskWithId = task.id ? task : { ...task, id: randomUUID() };
8984
9567
  if (task.subagentId) {
8985
- const entry = this.manifestEntries.get(task.subagentId);
8986
- if (entry) entry.taskIds.push(taskWithId.id);
9568
+ if (this.fleetManager) {
9569
+ this.fleetManager.addTaskToSubagent(task.subagentId, taskWithId.id);
9570
+ } else {
9571
+ const entry = this.manifestEntries.get(task.subagentId);
9572
+ if (entry) entry.taskIds.push(taskWithId.id);
9573
+ }
8987
9574
  }
8988
9575
  await this.coordinator.assign(taskWithId);
8989
9576
  this.taskDescriptions.set(taskWithId.id, taskWithId.description);
@@ -9181,8 +9768,9 @@ var Director = class {
9181
9768
  * still permission-checked normally.
9182
9769
  */
9183
9770
  tools(roster) {
9771
+ const effectiveRoster = roster ?? this.roster;
9184
9772
  const t2 = [
9185
- makeSpawnTool(this, roster),
9773
+ makeSpawnTool(this, effectiveRoster),
9186
9774
  makeAssignTool(this),
9187
9775
  makeAwaitTasksTool(this),
9188
9776
  makeAskTool(this),
@@ -9451,9 +10039,11 @@ async function readSubagentPartial(opts, subagentId) {
9451
10039
  candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
9452
10040
  } else {
9453
10041
  try {
9454
- const runDirs = await fsp2.readdir(opts.sessionsRoot);
9455
- for (const r of runDirs) {
9456
- candidates.push(path6.join(opts.sessionsRoot, r, `${subagentId}.jsonl`));
10042
+ const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
10043
+ for (const entry of entries) {
10044
+ if (entry.isDirectory()) {
10045
+ candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
10046
+ }
9457
10047
  }
9458
10048
  } catch {
9459
10049
  return void 0;
@@ -9648,6 +10238,12 @@ function makeDirectorSessionFactory(opts) {
9648
10238
  }
9649
10239
  };
9650
10240
  }
10241
+
10242
+ // src/coordination/null-fleet-bus.ts
10243
+ var NULL_FLEET_BUS = new FleetBus();
10244
+
10245
+ // src/models/mode-store.ts
10246
+ init_atomic_write();
9651
10247
  var DefaultModeStore = class {
9652
10248
  activeModeId = null;
9653
10249
  modes;
@@ -9706,10 +10302,9 @@ var DefaultModeStore = class {
9706
10302
  try {
9707
10303
  await fsp2.mkdir(this.configDir, { recursive: true });
9708
10304
  const configPath = path6.join(this.configDir, "mode.json");
9709
- await fsp2.writeFile(
10305
+ await atomicWrite(
9710
10306
  configPath,
9711
- JSON.stringify({ activeMode: this.activeModeId }, null, 2),
9712
- "utf8"
10307
+ JSON.stringify({ activeMode: this.activeModeId }, null, 2)
9713
10308
  );
9714
10309
  } catch {
9715
10310
  }
@@ -10563,6 +11158,9 @@ var SpecDrivenDev = class {
10563
11158
  }));
10564
11159
  }
10565
11160
  };
11161
+
11162
+ // src/sdd/spec-store.ts
11163
+ init_atomic_write();
10566
11164
  var SpecStore = class {
10567
11165
  baseDir;
10568
11166
  indexPath;
@@ -10672,6 +11270,9 @@ var SpecStore = class {
10672
11270
  await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
10673
11271
  }
10674
11272
  };
11273
+
11274
+ // src/sdd/task-graph-store.ts
11275
+ init_atomic_write();
10675
11276
  function graphToJSON(graph) {
10676
11277
  const serialisable = {
10677
11278
  ...graph,
@@ -10973,10 +11574,11 @@ var AISpecBuilder = class {
10973
11574
  async saveSession() {
10974
11575
  if (!this.sessionPath) return;
10975
11576
  try {
10976
- const fsp14 = await import('fs/promises');
10977
- const path24 = await import('path');
10978
- await fsp14.mkdir(path24.dirname(this.sessionPath), { recursive: true });
10979
- await fsp14.writeFile(this.sessionPath, JSON.stringify(this.session, null, 2), "utf8");
11577
+ const fsp16 = await import('fs/promises');
11578
+ const path26 = await import('path');
11579
+ const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
11580
+ await fsp16.mkdir(path26.dirname(this.sessionPath), { recursive: true });
11581
+ await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
10980
11582
  } catch {
10981
11583
  }
10982
11584
  }
@@ -10984,8 +11586,8 @@ var AISpecBuilder = class {
10984
11586
  async loadSession() {
10985
11587
  if (!this.sessionPath) return false;
10986
11588
  try {
10987
- const fsp14 = await import('fs/promises');
10988
- const raw = await fsp14.readFile(this.sessionPath, "utf8");
11589
+ const fsp16 = await import('fs/promises');
11590
+ const raw = await fsp16.readFile(this.sessionPath, "utf8");
10989
11591
  const loaded = JSON.parse(raw);
10990
11592
  if (loaded?.id && loaded?.phase && loaded?.title) {
10991
11593
  this.session = loaded;
@@ -10999,14 +11601,21 @@ var AISpecBuilder = class {
10999
11601
  async deleteSession() {
11000
11602
  if (!this.sessionPath) return;
11001
11603
  try {
11002
- const fsp14 = await import('fs/promises');
11003
- await fsp14.unlink(this.sessionPath);
11604
+ const fsp16 = await import('fs/promises');
11605
+ await fsp16.unlink(this.sessionPath);
11004
11606
  } catch {
11005
11607
  }
11006
11608
  }
11007
- /** Auto-save helper — calls saveSession() but never throws. */
11609
+ /** Auto-save helper — calls saveSession() but never throws.
11610
+ * Failures are surfaced via process.emitWarning so a persistent
11611
+ * ENOSPC / EACCES doesn't silently strand session edits in memory. */
11008
11612
  autoSave() {
11009
- this.saveSession().catch(() => {
11613
+ this.saveSession().catch((err) => {
11614
+ const detail = err instanceof Error ? err.message : String(err);
11615
+ process.emitWarning(
11616
+ `SpecBuilder autoSave failed: ${detail}`,
11617
+ "SpecBuilderWarning"
11618
+ );
11010
11619
  });
11011
11620
  }
11012
11621
  // ── Session Lifecycle ─────────────────────────────────────────────────────
@@ -11678,15 +12287,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
11678
12287
  maxId = id;
11679
12288
  }
11680
12289
  }
11681
- const path24 = [];
12290
+ const path26 = [];
11682
12291
  let current = maxId;
11683
12292
  const visited = /* @__PURE__ */ new Set();
11684
12293
  while (current && !visited.has(current)) {
11685
12294
  visited.add(current);
11686
- path24.unshift(current);
12295
+ path26.unshift(current);
11687
12296
  current = prev.get(current) ?? null;
11688
12297
  }
11689
- return path24;
12298
+ return path26;
11690
12299
  }
11691
12300
  function computeParallelGroups(graph, blockedByMap) {
11692
12301
  const groups = [];
@@ -12396,7 +13005,7 @@ async function startMetricsServer(opts) {
12396
13005
  const tls = opts.tls;
12397
13006
  const useHttps = !!(tls?.cert && tls?.key);
12398
13007
  const host = opts.host ?? "127.0.0.1";
12399
- const path24 = opts.path ?? "/metrics";
13008
+ const path26 = opts.path ?? "/metrics";
12400
13009
  const healthPath = opts.healthPath ?? "/healthz";
12401
13010
  const healthRegistry = opts.healthRegistry;
12402
13011
  const listener = (req, res) => {
@@ -12406,7 +13015,7 @@ async function startMetricsServer(opts) {
12406
13015
  return;
12407
13016
  }
12408
13017
  const url = req.url.split("?")[0];
12409
- if (url === path24) {
13018
+ if (url === path26) {
12410
13019
  let body;
12411
13020
  try {
12412
13021
  body = renderPrometheus(opts.sink.snapshot());
@@ -12470,7 +13079,7 @@ async function startMetricsServer(opts) {
12470
13079
  const protocol = useHttps ? "https" : "http";
12471
13080
  return {
12472
13081
  port: boundPort,
12473
- url: `${protocol}://${host}:${boundPort}${path24}`,
13082
+ url: `${protocol}://${host}:${boundPort}${path26}`,
12474
13083
  close: () => new Promise((resolve5, reject) => {
12475
13084
  server.close((err) => err ? reject(err) : resolve5());
12476
13085
  })
@@ -13197,6 +13806,9 @@ function stripTopDir(p) {
13197
13806
  if (idx === -1) return "";
13198
13807
  return p.slice(idx + 1);
13199
13808
  }
13809
+
13810
+ // src/skills/manifest-store.ts
13811
+ init_atomic_write();
13200
13812
  var SkillManifestStore = class {
13201
13813
  manifestPath;
13202
13814
  cache;
@@ -13221,7 +13833,7 @@ var SkillManifestStore = class {
13221
13833
  async write(data) {
13222
13834
  const dir = path6.dirname(this.manifestPath);
13223
13835
  await fsp2.mkdir(dir, { recursive: true });
13224
- await fsp2.writeFile(this.manifestPath, JSON.stringify(data, null, 2) + "\n", "utf8");
13836
+ await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
13225
13837
  this.cache = data;
13226
13838
  }
13227
13839
  async addEntry(entry) {
@@ -13552,6 +14164,9 @@ async function collectFiles(dir, baseDir) {
13552
14164
  }
13553
14165
  return results;
13554
14166
  }
14167
+
14168
+ // src/storage/session-rewinder.ts
14169
+ init_atomic_write();
13555
14170
  var DefaultSessionRewinder = class {
13556
14171
  constructor(sessionsDir) {
13557
14172
  this.sessionsDir = sessionsDir;
@@ -13685,7 +14300,7 @@ async function revertSnapshots(snapshots) {
13685
14300
  try {
13686
14301
  if (file.action === "deleted") {
13687
14302
  if (file.before !== null) {
13688
- await fsp2.writeFile(file.path, file.before, { mode: 420 });
14303
+ await atomicWrite(file.path, file.before, { mode: 420 });
13689
14304
  revertedFiles.push(file.path);
13690
14305
  }
13691
14306
  } else if (file.action === "created") {
@@ -13693,7 +14308,7 @@ async function revertSnapshots(snapshots) {
13693
14308
  revertedFiles.push(file.path);
13694
14309
  } else if (file.action === "modified") {
13695
14310
  if (file.before !== null) {
13696
- await fsp2.writeFile(file.path, file.before, { mode: 420 });
14311
+ await atomicWrite(file.path, file.before, { mode: 420 });
13697
14312
  revertedFiles.push(file.path);
13698
14313
  }
13699
14314
  }
@@ -14479,10 +15094,10 @@ var SecurityScanner = class {
14479
15094
  for (const pattern of patterns) {
14480
15095
  if (!this.matchesCategory(pattern)) continue;
14481
15096
  for (const regex of pattern.patterns) {
14482
- regex.lastIndex = 0;
14483
15097
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
14484
15098
  const line = lines[lineNum];
14485
15099
  if (!line) continue;
15100
+ regex.lastIndex = 0;
14486
15101
  if (regex.test(line)) {
14487
15102
  if (this.isFalsePositive(line, pattern.falsePositiveMarkers)) {
14488
15103
  continue;
@@ -14544,6 +15159,9 @@ var SecurityScanner = class {
14544
15159
  }
14545
15160
  };
14546
15161
  var defaultSecurityScanner = new SecurityScanner();
15162
+
15163
+ // src/security-scanner/report-generator.ts
15164
+ init_atomic_write();
14547
15165
  var DEFAULT_REPORT_OPTIONS = {
14548
15166
  outputDir: "security-reports",
14549
15167
  format: "markdown",
@@ -14571,15 +15189,15 @@ var ReportGenerator = class {
14571
15189
  default:
14572
15190
  content = this.generateMarkdown(scanResult);
14573
15191
  }
14574
- await writeFile(filepath, content, "utf-8");
15192
+ await atomicWrite(filepath, content);
14575
15193
  return filepath;
14576
15194
  }
14577
15195
  async ensureOutputDir() {
14578
15196
  try {
14579
15197
  await stat(this.options.outputDir);
14580
15198
  } catch {
14581
- const { mkdir: mkdir10 } = await import('fs/promises');
14582
- await mkdir10(this.options.outputDir, { recursive: true });
15199
+ const { mkdir: mkdir11 } = await import('fs/promises');
15200
+ await mkdir11(this.options.outputDir, { recursive: true });
14583
15201
  }
14584
15202
  }
14585
15203
  generateMarkdown(result) {
@@ -14763,6 +15381,9 @@ var ReportGenerator = class {
14763
15381
  }
14764
15382
  };
14765
15383
  var defaultReportGenerator = new ReportGenerator();
15384
+
15385
+ // src/security-scanner/gitignore-updater.ts
15386
+ init_atomic_write();
14766
15387
  var DEFAULT_OPTIONS2 = {
14767
15388
  gitignorePath: ".gitignore",
14768
15389
  entries: ["security-reports/", "security-reports/*"]
@@ -14789,12 +15410,12 @@ var GitignoreUpdater = class {
14789
15410
  }
14790
15411
  if (added.length > 0) {
14791
15412
  const newContent = [...lines].filter(Boolean).join("\n") + "\n";
14792
- await writeFile(this.options.gitignorePath, newContent, "utf-8");
15413
+ await atomicWrite(this.options.gitignorePath, newContent);
14793
15414
  }
14794
15415
  } catch (err) {
14795
15416
  if (err.code === "ENOENT") {
14796
15417
  const content = this.options.entries.join("\n") + "\n";
14797
- await writeFile(this.options.gitignorePath, content, "utf-8");
15418
+ await atomicWrite(this.options.gitignorePath, content);
14798
15419
  added.push(...this.options.entries);
14799
15420
  } else {
14800
15421
  errors.push(`Failed to update .gitignore: ${err}`);
@@ -14813,6 +15434,9 @@ var GitignoreUpdater = class {
14813
15434
  }
14814
15435
  };
14815
15436
  var defaultGitignoreUpdater = new GitignoreUpdater();
15437
+
15438
+ // src/security-scanner/orchestrator.ts
15439
+ init_atomic_write();
14816
15440
  var NETWORK_ERR_RE2 = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
14817
15441
  var SecurityScannerOrchestrator = class {
14818
15442
  constructor(retryPolicy, errorHandler) {
@@ -14858,7 +15482,7 @@ var SecurityScannerOrchestrator = class {
14858
15482
  */
14859
15483
  async run(ctx, options) {
14860
15484
  const { projectRoot, reportOptions, skipGitignore, model: explicitModel } = options;
14861
- const provider = "provider" in ctx ? ctx.provider : ctx;
15485
+ const provider = "provider" in ctx && ctx.provider ? ctx.provider : ctx;
14862
15486
  const model = explicitModel ?? ("model" in ctx ? ctx.model : void 0);
14863
15487
  const detectionResult = await this.detector.detect(projectRoot);
14864
15488
  if (detectionResult.detectedStacks.length === 0) {
@@ -15197,7 +15821,7 @@ Be specific about the vulnerabilities found and how to fix them.`;
15197
15821
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
15198
15822
  const filename = `security-report-${timestamp}.${format}`;
15199
15823
  const filepath = join(outputDir, filename);
15200
- await writeFile(filepath, content, "utf-8");
15824
+ await atomicWrite(filepath, content);
15201
15825
  return filepath;
15202
15826
  }
15203
15827
  /**
@@ -15371,11 +15995,21 @@ Show a previous security report.
15371
15995
  }
15372
15996
  };
15373
15997
  }
15998
+ function getProviderFromContext(ctx) {
15999
+ if (ctx.provider && typeof ctx.provider.complete === "function") {
16000
+ return { provider: ctx.provider, model: ctx.model };
16001
+ }
16002
+ return null;
16003
+ }
15374
16004
  async function handleScan(args, ctx) {
15375
16005
  const options = parseArgs(args);
15376
16006
  const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
15377
16007
  try {
15378
- const result = await defaultOrchestrator.run(ctx, {
16008
+ const providerInfo = getProviderFromContext(ctx);
16009
+ if (!providerInfo) {
16010
+ return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
16011
+ }
16012
+ const result = await defaultOrchestrator.run(providerInfo, {
15379
16013
  projectRoot,
15380
16014
  scanOptions: {
15381
16015
  depth: options.depth || "standard",
@@ -15424,7 +16058,11 @@ async function handleScan(args, ctx) {
15424
16058
  async function handleAudit(ctx) {
15425
16059
  const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
15426
16060
  try {
15427
- const result = await defaultOrchestrator.run(ctx, {
16061
+ const providerInfo = getProviderFromContext(ctx);
16062
+ if (!providerInfo) {
16063
+ return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
16064
+ }
16065
+ const result = await defaultOrchestrator.run(providerInfo, {
15428
16066
  projectRoot,
15429
16067
  reportOptions: { format: "markdown" }
15430
16068
  });
@@ -15480,16 +16118,16 @@ Use \`/security report <number>\` to view a specific report.` };
15480
16118
  }
15481
16119
  const index = parseInt(reportId, 10) - 1;
15482
16120
  if (!isNaN(index) && reports[index]) {
15483
- const { readFile: readFile28 } = await import('fs/promises');
15484
- const content = await readFile28(join(reportsDir, reports[index]), "utf-8");
16121
+ const { readFile: readFile29 } = await import('fs/promises');
16122
+ const content = await readFile29(join(reportsDir, reports[index]), "utf-8");
15485
16123
  return { message: `# Security Report
15486
16124
 
15487
16125
  ${content}` };
15488
16126
  }
15489
16127
  const match = reports.find((r) => r.includes(reportId));
15490
16128
  if (match) {
15491
- const { readFile: readFile28 } = await import('fs/promises');
15492
- const content = await readFile28(join(reportsDir, match), "utf-8");
16129
+ const { readFile: readFile29 } = await import('fs/promises');
16130
+ const content = await readFile29(join(reportsDir, match), "utf-8");
15493
16131
  return { message: `# Security Report
15494
16132
 
15495
16133
  ${content}` };
@@ -15540,6 +16178,211 @@ function getHelpMessage() {
15540
16178
  }
15541
16179
  var securitySlashCommand = createSecuritySlashCommand();
15542
16180
 
16181
+ // src/coordination/fleet-manager.ts
16182
+ init_atomic_write();
16183
+ var FleetManager = class {
16184
+ /** The fleet-wide event bus. */
16185
+ fleet;
16186
+ /** Usage rollup across all subagents. */
16187
+ usage;
16188
+ manifestPath;
16189
+ sessionsRoot;
16190
+ directorRunId;
16191
+ /** Spawn cap (lifetime total). Infinity means unlimited. */
16192
+ maxSpawns;
16193
+ /** Nesting cap. */
16194
+ maxSpawnDepth;
16195
+ /** This director's depth in a director chain. Root = 0. */
16196
+ spawnDepth;
16197
+ /** Live spawn counter. */
16198
+ spawnCount = 0;
16199
+ stateCheckpoint;
16200
+ sessionWriter;
16201
+ manifestTimer = null;
16202
+ manifestDebounceMs;
16203
+ /** Fleet-wide cost cap. Infinity = no cap. Distinct from SubagentBudget limits,
16204
+ * which track per-subagent spend — this field caps the entire fleet total. */
16205
+ maxFleetCostUsd;
16206
+ manifestEntries = /* @__PURE__ */ new Map();
16207
+ /** Pending tasks with their descriptions — populated by `addPendingTask`
16208
+ * and cleared by `removePendingTask`. Replaces the host-side `pending`
16209
+ * Map so task descriptions live in one place (FleetManager). */
16210
+ pendingTasks = /* @__PURE__ */ new Map();
16211
+ subagentMeta = /* @__PURE__ */ new Map();
16212
+ priceLookups = /* @__PURE__ */ new Map();
16213
+ constructor(opts = {}) {
16214
+ this.manifestPath = opts.manifestPath;
16215
+ this.sessionsRoot = opts.sessionsRoot;
16216
+ this.directorRunId = opts.directorRunId ?? randomUUID();
16217
+ this.maxSpawns = opts.maxSpawns ?? Number.POSITIVE_INFINITY;
16218
+ this.maxSpawnDepth = opts.maxSpawnDepth ?? 2;
16219
+ this.spawnDepth = opts.spawnDepth ?? 0;
16220
+ this.sessionWriter = opts.sessionWriter ?? null;
16221
+ this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
16222
+ this.maxFleetCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
16223
+ this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(
16224
+ opts.stateCheckpointPath,
16225
+ {
16226
+ directorRunId: this.directorRunId,
16227
+ maxSpawns: opts.maxSpawns,
16228
+ spawnDepth: this.spawnDepth,
16229
+ maxSpawnDepth: this.maxSpawnDepth,
16230
+ directorBudget: opts.directorBudget
16231
+ },
16232
+ opts.checkpointDebounceMs ?? 250
16233
+ ) : null;
16234
+ this.fleet = new FleetBus();
16235
+ this.usage = new FleetUsageAggregator(
16236
+ this.fleet,
16237
+ (id) => this.priceLookups.get(id),
16238
+ (id) => this.subagentMeta.get(id)
16239
+ );
16240
+ }
16241
+ // -----------------------------------------------------------------------
16242
+ // IFleetManager surface
16243
+ // -----------------------------------------------------------------------
16244
+ get fleetBus() {
16245
+ return this.fleet;
16246
+ }
16247
+ snapshot() {
16248
+ return this.usage.snapshot();
16249
+ }
16250
+ getSubagentMeta(id) {
16251
+ return this.subagentMeta.get(id);
16252
+ }
16253
+ /**
16254
+ * Returns null if the spawn is allowed, or an object describing
16255
+ * which cap was exceeded. Does NOT throw — the caller decides
16256
+ * how to surface the rejection.
16257
+ */
16258
+ canSpawn(config) {
16259
+ if (this.spawnDepth >= this.maxSpawnDepth) {
16260
+ return { kind: "max_spawn_depth", limit: this.maxSpawnDepth, observed: this.spawnDepth };
16261
+ }
16262
+ if (this.spawnCount >= this.maxSpawns) {
16263
+ return { kind: "max_spawns", limit: this.maxSpawns, observed: this.spawnCount + 1 };
16264
+ }
16265
+ if (this.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
16266
+ const totalCost = this.usage.snapshot().total?.cost ?? 0;
16267
+ if (totalCost >= this.maxFleetCostUsd) {
16268
+ return { kind: "max_cost_usd", limit: this.maxFleetCostUsd, observed: totalCost };
16269
+ }
16270
+ }
16271
+ return null;
16272
+ }
16273
+ /**
16274
+ * Records a spawn: increments counter, stores metadata, updates state checkpoint,
16275
+ * and schedules a debounced manifest write. Call AFTER the coordinator
16276
+ * has successfully spawned the subagent.
16277
+ *
16278
+ * @param subagentId The subagent's id (from coordinator.spawn result)
16279
+ * @param config The SubagentConfig that was used
16280
+ * @param priceLookup Optional per-subagent pricing data
16281
+ */
16282
+ recordSpawn(subagentId, config, priceLookup) {
16283
+ this.spawnCount += 1;
16284
+ this.subagentMeta.set(subagentId, {
16285
+ provider: config.provider,
16286
+ model: config.model
16287
+ });
16288
+ if (priceLookup) this.priceLookups.set(subagentId, priceLookup);
16289
+ this.manifestEntries.set(subagentId, {
16290
+ subagentId,
16291
+ name: config.name,
16292
+ role: config.role,
16293
+ provider: config.provider,
16294
+ model: config.model,
16295
+ taskIds: []
16296
+ });
16297
+ this.stateCheckpoint?.recordSpawn({
16298
+ id: subagentId,
16299
+ name: config.name,
16300
+ role: config.role,
16301
+ provider: config.provider,
16302
+ model: config.model,
16303
+ spawnedAt: (/* @__PURE__ */ new Date()).toISOString()
16304
+ }, this.spawnCount);
16305
+ void this.appendSessionEvent({
16306
+ type: "agent_spawned",
16307
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
16308
+ agentId: subagentId,
16309
+ role: config.role ?? config.name
16310
+ });
16311
+ this.scheduleManifest();
16312
+ }
16313
+ async writeManifest() {
16314
+ if (!this.manifestPath) return null;
16315
+ const manifest = {
16316
+ version: 1,
16317
+ directorRunId: this.directorRunId,
16318
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
16319
+ children: Array.from(this.manifestEntries.values()).map((entry) => ({
16320
+ id: entry.subagentId,
16321
+ name: entry.name,
16322
+ role: entry.role,
16323
+ provider: entry.provider,
16324
+ model: entry.model,
16325
+ taskIds: entry.taskIds
16326
+ })),
16327
+ usage: this.usage.snapshot()
16328
+ };
16329
+ await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
16330
+ await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
16331
+ return this.manifestPath;
16332
+ }
16333
+ /**
16334
+ * Attach task ids to an already-spawned subagent. Called by
16335
+ * `Director.assign()` after the coordinator assigns a task.
16336
+ */
16337
+ addTaskToSubagent(subagentId, taskId) {
16338
+ const entry = this.manifestEntries.get(subagentId);
16339
+ if (entry) entry.taskIds.push(taskId);
16340
+ }
16341
+ /**
16342
+ * Debounced manifest write. Call after any state mutation
16343
+ * (spawn, assign, complete) so a burst collapses into one write.
16344
+ */
16345
+ scheduleManifest() {
16346
+ if (!this.manifestPath || this.manifestDebounceMs <= 0) return;
16347
+ if (this.manifestTimer) return;
16348
+ this.manifestTimer = setTimeout(() => {
16349
+ this.manifestTimer = null;
16350
+ void this.writeManifest().catch((err) => {
16351
+ const detail = err instanceof Error ? err.message : String(err);
16352
+ process.emitWarning(
16353
+ `FleetManager manifest write failed: ${detail}`,
16354
+ "FleetManagerWarning"
16355
+ );
16356
+ });
16357
+ }, this.manifestDebounceMs);
16358
+ }
16359
+ /** Best-effort session event writer. Swallows failures. */
16360
+ async appendSessionEvent(event) {
16361
+ if (!this.sessionWriter) return;
16362
+ try {
16363
+ await this.sessionWriter.append(event);
16364
+ } catch {
16365
+ }
16366
+ }
16367
+ // -----------------------------------------------------------------------
16368
+ // Pending task management — eliminates host-side state duplication
16369
+ // -----------------------------------------------------------------------
16370
+ addPendingTask(taskId, subagentId, description) {
16371
+ this.pendingTasks.set(taskId, { subagentId, description });
16372
+ }
16373
+ removePendingTask(taskId) {
16374
+ this.pendingTasks.delete(taskId);
16375
+ }
16376
+ getFleetStatus() {
16377
+ const pending = Array.from(this.pendingTasks.entries()).map(([taskId, v]) => ({
16378
+ taskId,
16379
+ description: v.description,
16380
+ subagentId: v.subagentId
16381
+ }));
16382
+ return { pending, live: [] };
16383
+ }
16384
+ };
16385
+
15543
16386
  // src/extension/registry.ts
15544
16387
  var ExtensionRegistry = class {
15545
16388
  extensions = [];
@@ -15568,7 +16411,8 @@ var ExtensionRegistry = class {
15568
16411
  */
15569
16412
  async buildSystemPromptContributions(ctx) {
15570
16413
  const blocks = [];
15571
- for (const c of this.promptContributors) {
16414
+ const snapshot = [...this.promptContributors];
16415
+ for (const c of snapshot) {
15572
16416
  try {
15573
16417
  const contributed = await c(ctx);
15574
16418
  blocks.push(...contributed);
@@ -15748,6 +16592,50 @@ var ExtensionRegistry = class {
15748
16592
  }
15749
16593
  };
15750
16594
 
16595
+ // src/core/continue-to-next-iteration.ts
16596
+ function parseContinueDirective(text) {
16597
+ const LINE_MARKERS = /^\s*\[(continue|next step|proceed|done)\]\s*$/gim;
16598
+ let match;
16599
+ let lastDirective = "none";
16600
+ while ((match = LINE_MARKERS.exec(text)) !== null) {
16601
+ const value = (match[1] ?? "").toLowerCase();
16602
+ if (value === "continue" || value === "next step" || value === "proceed") {
16603
+ lastDirective = "continue";
16604
+ } else if (value === "done") {
16605
+ lastDirective = "stop";
16606
+ }
16607
+ }
16608
+ return lastDirective;
16609
+ }
16610
+ var META_KEY = "_autonomousContinue";
16611
+ function setAutonomousContinue(ctx) {
16612
+ ctx.meta[META_KEY] = true;
16613
+ }
16614
+ function consumeAutonomousContinue(ctx) {
16615
+ const val = ctx.meta[META_KEY] === true;
16616
+ delete ctx.meta[META_KEY];
16617
+ return val;
16618
+ }
16619
+ function makeContinueToNextIterationTool() {
16620
+ const inputSchema = {
16621
+ type: "object",
16622
+ properties: {},
16623
+ required: [],
16624
+ description: "Signal that the agent should continue to the next iteration immediately, without waiting for user input. Use this when you have completed a step and want to proceed automatically to the next step in your plan."
16625
+ };
16626
+ return {
16627
+ name: "continue_to_next_iteration",
16628
+ description: "Continue to the next iteration without returning to the user. Call this when you have finished a step and want to keep working autonomously.",
16629
+ permission: "auto",
16630
+ mutating: false,
16631
+ inputSchema,
16632
+ async execute(_input, ctx) {
16633
+ setAutonomousContinue(ctx);
16634
+ return { continue: true };
16635
+ }
16636
+ };
16637
+ }
16638
+
15751
16639
  // src/core/iteration-limit.ts
15752
16640
  function requestLimitExtension(opts) {
15753
16641
  const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
@@ -15826,6 +16714,8 @@ var Agent = class {
15826
16714
  plugins = [];
15827
16715
  toolExecutor;
15828
16716
  autoExtendLimit;
16717
+ /** Enables autonomous continue: model can signal `[continue]` or call continue_to_next_iteration() to re-run. */
16718
+ autonomousContinue;
15829
16719
  tracer;
15830
16720
  extensions;
15831
16721
  constructor(init) {
@@ -15840,19 +16730,11 @@ var Agent = class {
15840
16730
  this.executionStrategy = init.executionStrategy ?? "smart";
15841
16731
  this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
15842
16732
  this.autoExtendLimit = init.autoExtendLimit ?? true;
16733
+ this.autonomousContinue = init.autonomousContinue ?? false;
15843
16734
  this.tracer = init.tracer;
15844
16735
  this.extensions = init.extensions ?? new ExtensionRegistry();
15845
16736
  this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
15846
- this.toolExecutor = new ToolExecutor(this.tools, {
15847
- permissionPolicy: init.permissionPolicy ?? this.permission,
15848
- secretScrubber: this.scrubber,
15849
- renderer: this.renderer,
15850
- events: this.events,
15851
- confirmAwaiter: init.confirmAwaiter,
15852
- iterationTimeoutMs: this.iterationTimeoutMs,
15853
- perIterationOutputCapBytes: this.perIterationOutputCapBytes,
15854
- tracer: this.tracer
15855
- });
16737
+ this.toolExecutor = init.toolExecutor;
15856
16738
  }
15857
16739
  get logger() {
15858
16740
  return this.container.resolve(TOKENS.Logger);
@@ -15988,6 +16870,9 @@ var Agent = class {
15988
16870
  if (controller.signal.aborted) {
15989
16871
  return { status: "aborted", iterations };
15990
16872
  }
16873
+ if (this.autonomousContinue) {
16874
+ consumeAutonomousContinue(this.ctx);
16875
+ }
15991
16876
  const limitCheck = await this.checkIterationLimit(
15992
16877
  i,
15993
16878
  effectiveLimit,
@@ -16064,12 +16949,32 @@ var Agent = class {
16064
16949
  const toolUses = res.content.filter(isToolUseBlock);
16065
16950
  if (toolUses.length === 0) {
16066
16951
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16952
+ if (this.autonomousContinue && responseResult.directive === "continue") {
16953
+ await this.compactContextIfNeeded();
16954
+ await this.extensions.runAfterIteration(this.ctx, i);
16955
+ continue;
16956
+ }
16957
+ if (this.autonomousContinue && responseResult.directive === "stop") {
16958
+ return { status: "done", iterations, finalText };
16959
+ }
16067
16960
  return { status: "done", iterations, finalText };
16068
16961
  }
16069
16962
  await this.executeTools(toolUses);
16963
+ if (this.autonomousContinue && consumeAutonomousContinue(this.ctx)) {
16964
+ this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16965
+ await this.compactContextIfNeeded();
16966
+ await this.extensions.runAfterIteration(this.ctx, i);
16967
+ continue;
16968
+ }
16070
16969
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16071
16970
  await this.compactContextIfNeeded();
16072
16971
  await this.extensions.runAfterIteration(this.ctx, i);
16972
+ if (this.autonomousContinue && responseResult.directive === "continue") {
16973
+ continue;
16974
+ }
16975
+ if (this.autonomousContinue && responseResult.directive === "stop") {
16976
+ return { status: "done", iterations, finalText };
16977
+ }
16073
16978
  }
16074
16979
  }
16075
16980
  /**
@@ -16156,7 +17061,11 @@ var Agent = class {
16156
17061
  if (!streamed) this.renderer?.write(rendered);
16157
17062
  }
16158
17063
  }
16159
- return { finalText, aborted: false, done: false };
17064
+ let directive = "none";
17065
+ if (this.autonomousContinue && finalText) {
17066
+ directive = parseContinueDirective(finalText);
17067
+ }
17068
+ return { finalText, aborted: false, done: false, directive };
16160
17069
  }
16161
17070
  /**
16162
17071
  * Execute tools and append tool results to context.
@@ -17749,6 +18658,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
17749
18658
  });
17750
18659
  }
17751
18660
 
17752
- export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
18661
+ export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeContinueToNextIterationTool, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
17753
18662
  //# sourceMappingURL=index.js.map
17754
18663
  //# sourceMappingURL=index.js.map