@wrongstack/core 0.5.7 → 0.6.1

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 (47) hide show
  1. package/dist/{agent-bridge-BKNiE1VH.d.ts → agent-bridge-DaCvA_uK.d.ts} +1 -1
  2. package/dist/coordination/index.d.ts +5 -5
  3. package/dist/coordination/index.js +218 -78
  4. package/dist/coordination/index.js.map +1 -1
  5. package/dist/defaults/index.d.ts +10 -9
  6. package/dist/defaults/index.js +1127 -180
  7. package/dist/defaults/index.js.map +1 -1
  8. package/dist/{events-DPQKFX7W.d.ts → events-CzkeaVVl.d.ts} +8 -2
  9. package/dist/execution/index.d.ts +281 -5
  10. package/dist/execution/index.js +785 -3
  11. package/dist/execution/index.js.map +1 -1
  12. package/dist/extension/index.d.ts +2 -2
  13. package/dist/extension/index.js +2 -1
  14. package/dist/extension/index.js.map +1 -1
  15. package/dist/goal-store-DVCfj7Ff.d.ts +95 -0
  16. package/dist/{index-j2WyAyML.d.ts → index-BkKLQjea.d.ts} +34 -2
  17. package/dist/{index-Bf9Bpkdc.d.ts → index-i9rPR53g.d.ts} +11 -4
  18. package/dist/index.d.ts +51 -16
  19. package/dist/index.js +1506 -247
  20. package/dist/index.js.map +1 -1
  21. package/dist/infrastructure/index.d.ts +2 -2
  22. package/dist/kernel/index.d.ts +2 -2
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/models/index.js +2 -3
  25. package/dist/models/index.js.map +1 -1
  26. package/dist/{multi-agent-Cpp7FXUl.d.ts → multi-agent-MfI6dmv-.d.ts} +30 -5
  27. package/dist/observability/index.d.ts +1 -1
  28. package/dist/{path-resolver--g_hKJ7V.d.ts → path-resolver-CWINz5XR.d.ts} +1 -1
  29. package/dist/{plan-templates-Bveo2W8n.d.ts → plan-templates-Bne1pB4d.d.ts} +6 -5
  30. package/dist/{provider-runner-hl4Il3xS.d.ts → provider-runner-CZYIzeBp.d.ts} +1 -1
  31. package/dist/sdd/index.d.ts +5 -3
  32. package/dist/sdd/index.js +102 -68
  33. package/dist/sdd/index.js.map +1 -1
  34. package/dist/security/index.js +82 -82
  35. package/dist/security/index.js.map +1 -1
  36. package/dist/skills/index.js +102 -38
  37. package/dist/skills/index.js.map +1 -1
  38. package/dist/storage/index.d.ts +3 -2
  39. package/dist/storage/index.js +134 -22
  40. package/dist/storage/index.js.map +1 -1
  41. package/dist/{tool-executor-DbAFkHdP.d.ts → tool-executor-40Q6shR-.d.ts} +1 -1
  42. package/dist/types/index.d.ts +7 -7
  43. package/dist/types/index.js +116 -103
  44. package/dist/types/index.js.map +1 -1
  45. package/dist/utils/index.js +5 -0
  46. package/dist/utils/index.js.map +1 -1
  47. package/package.json +5 -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 = {
@@ -2910,13 +2934,17 @@ var ToolExecutor = class {
2910
2934
  const ctrl = new AbortController();
2911
2935
  const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
2912
2936
  const combined = AbortSignal.any([parentSignal, ctrl.signal]);
2937
+ let cleanupCalled = false;
2938
+ let caught = false;
2913
2939
  try {
2914
2940
  if (typeof tool.executeStream === "function") {
2915
2941
  return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
2916
2942
  }
2917
2943
  return await tool.execute(input, ctx, { signal: combined });
2918
2944
  } catch (err) {
2945
+ caught = true;
2919
2946
  if (combined.aborted && typeof tool.cleanup === "function") {
2947
+ cleanupCalled = true;
2920
2948
  try {
2921
2949
  await tool.cleanup(input, ctx);
2922
2950
  } catch {
@@ -2925,6 +2953,16 @@ var ToolExecutor = class {
2925
2953
  throw err;
2926
2954
  } finally {
2927
2955
  clearTimeout(timer);
2956
+ if (combined.aborted && !caught) {
2957
+ if (!cleanupCalled && typeof tool.cleanup === "function") {
2958
+ try {
2959
+ await tool.cleanup(input, ctx);
2960
+ } catch {
2961
+ }
2962
+ }
2963
+ const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
2964
+ throw reason;
2965
+ }
2928
2966
  }
2929
2967
  }
2930
2968
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
@@ -2982,7 +3020,8 @@ var ToolExecutor = class {
2982
3020
  if (subjectKey) {
2983
3021
  const v = obj[subjectKey];
2984
3022
  if (typeof v === "string") {
2985
- return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
3023
+ const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
3024
+ return isPathKey ? normalizePath(v) : escapeGlob(v);
2986
3025
  }
2987
3026
  }
2988
3027
  if (toolName === "bash" && typeof obj.command === "string") {
@@ -3297,6 +3336,9 @@ function renderPlainText(meta, events) {
3297
3336
  return lines.join("\n");
3298
3337
  }
3299
3338
 
3339
+ // src/utils/index.ts
3340
+ init_atomic_write();
3341
+
3300
3342
  // src/utils/safe-json.ts
3301
3343
  function safeParse(input, maxBytes = 5e6) {
3302
3344
  if (input.length > maxBytes) {
@@ -3725,6 +3767,11 @@ function looksSecret(name) {
3725
3767
  function buildChildEnv(optsOrSessionId) {
3726
3768
  const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
3727
3769
  const passthrough = process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
3770
+ if (passthrough && !process.env["CI"]) {
3771
+ console.warn(
3772
+ "[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."
3773
+ );
3774
+ }
3728
3775
  const out = {};
3729
3776
  for (const [k, v] of Object.entries(process.env)) {
3730
3777
  if (v === void 0) continue;
@@ -3755,11 +3802,11 @@ function validateAgainstSchema(value, schema) {
3755
3802
  walk2(value, schema, "", errors);
3756
3803
  return { ok: errors.length === 0, errors };
3757
3804
  }
3758
- function walk2(value, schema, path25, errors) {
3805
+ function walk2(value, schema, path26, errors) {
3759
3806
  if (schema.enum !== void 0) {
3760
3807
  if (!schema.enum.some((e) => deepEqual(e, value))) {
3761
3808
  errors.push({
3762
- path: path25 || "<root>",
3809
+ path: path26 || "<root>",
3763
3810
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
3764
3811
  });
3765
3812
  return;
@@ -3768,7 +3815,7 @@ function walk2(value, schema, path25, errors) {
3768
3815
  if (typeof schema.type === "string") {
3769
3816
  if (!checkType(value, schema.type)) {
3770
3817
  errors.push({
3771
- path: path25 || "<root>",
3818
+ path: path26 || "<root>",
3772
3819
  message: `expected ${schema.type}, got ${describeType(value)}`
3773
3820
  });
3774
3821
  return;
@@ -3778,19 +3825,19 @@ function walk2(value, schema, path25, errors) {
3778
3825
  const obj = value;
3779
3826
  for (const req of schema.required ?? []) {
3780
3827
  if (!(req in obj)) {
3781
- errors.push({ path: joinPath(path25, req), message: "required property missing" });
3828
+ errors.push({ path: joinPath(path26, req), message: "required property missing" });
3782
3829
  }
3783
3830
  }
3784
3831
  if (schema.properties) {
3785
3832
  for (const [key, subSchema] of Object.entries(schema.properties)) {
3786
3833
  if (key in obj) {
3787
- walk2(obj[key], subSchema, joinPath(path25, key), errors);
3834
+ walk2(obj[key], subSchema, joinPath(path26, key), errors);
3788
3835
  }
3789
3836
  }
3790
3837
  }
3791
3838
  }
3792
3839
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
3793
- value.forEach((item, i) => walk2(item, schema.items, `${path25}[${i}]`, errors));
3840
+ value.forEach((item, i) => walk2(item, schema.items, `${path26}[${i}]`, errors));
3794
3841
  }
3795
3842
  }
3796
3843
  function checkType(value, type) {
@@ -3842,6 +3889,9 @@ function deepEqual(a, b) {
3842
3889
  }
3843
3890
  return false;
3844
3891
  }
3892
+
3893
+ // src/storage/session-store.ts
3894
+ init_atomic_write();
3845
3895
  var DefaultSessionStore = class {
3846
3896
  dir;
3847
3897
  events;
@@ -3885,19 +3935,25 @@ var DefaultSessionStore = class {
3885
3935
  { cause: err }
3886
3936
  );
3887
3937
  }
3888
- const writer = new FileSessionWriter(
3889
- id,
3890
- handle,
3891
- (/* @__PURE__ */ new Date()).toISOString(),
3892
- {
3938
+ try {
3939
+ const writer = new FileSessionWriter(
3893
3940
  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 };
3941
+ handle,
3942
+ (/* @__PURE__ */ new Date()).toISOString(),
3943
+ {
3944
+ id,
3945
+ model: data.metadata.model,
3946
+ provider: data.metadata.provider
3947
+ },
3948
+ this.events,
3949
+ { resumed: true, dir: this.dir, filePath: file }
3950
+ );
3951
+ return { writer, data };
3952
+ } catch (err) {
3953
+ await handle.close().catch(() => {
3954
+ });
3955
+ throw err;
3956
+ }
3901
3957
  }
3902
3958
  async load(id) {
3903
3959
  const file = path6.join(this.dir, `${id}.jsonl`);
@@ -3943,7 +3999,7 @@ var DefaultSessionStore = class {
3943
3999
  const full = path6.join(this.dir, `${id}.jsonl`);
3944
4000
  const stat8 = await fsp2.stat(full);
3945
4001
  const summary = await this.summarize(id, stat8.mtime.toISOString());
3946
- await fsp2.writeFile(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
4002
+ await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
3947
4003
  console.warn(
3948
4004
  `[session-store] Failed to write manifest for "${id}":`,
3949
4005
  err instanceof Error ? err.message : String(err)
@@ -4171,7 +4227,7 @@ var FileSessionWriter = class {
4171
4227
  this.closed = true;
4172
4228
  if (this.manifestFile) {
4173
4229
  try {
4174
- await fsp2.writeFile(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
4230
+ await atomicWrite(this.manifestFile, JSON.stringify(this.summary), { mode: 384 });
4175
4231
  } catch {
4176
4232
  }
4177
4233
  }
@@ -4267,6 +4323,9 @@ function userInputTitle(content) {
4267
4323
  const text = content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
4268
4324
  return (text || "(non-text input)").slice(0, 60);
4269
4325
  }
4326
+
4327
+ // src/storage/queue-store.ts
4328
+ init_atomic_write();
4270
4329
  var QueueStore = class {
4271
4330
  file;
4272
4331
  constructor(opts) {
@@ -4317,6 +4376,9 @@ function isPersistedQueueItem(v) {
4317
4376
  const o = v;
4318
4377
  return typeof o["displayText"] === "string" && Array.isArray(o["blocks"]);
4319
4378
  }
4379
+
4380
+ // src/storage/attachment-store.ts
4381
+ init_atomic_write();
4320
4382
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
4321
4383
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)\]/g;
4322
4384
  var DefaultAttachmentStore = class {
@@ -4338,7 +4400,9 @@ var DefaultAttachmentStore = class {
4338
4400
  if (this.spoolDir && bytes >= this.spoolThreshold) {
4339
4401
  await fsp2.mkdir(this.spoolDir, { recursive: true });
4340
4402
  spooledPath = path6.join(this.spoolDir, `${id}.bin`);
4341
- await fsp2.writeFile(spooledPath, input.data, input.kind === "image" ? "base64" : "utf8");
4403
+ await atomicWrite(spooledPath, input.data, {
4404
+ encoding: input.kind === "image" ? "base64" : "utf8"
4405
+ });
4342
4406
  data = void 0;
4343
4407
  }
4344
4408
  const att = {
@@ -4437,6 +4501,9 @@ function mergeAdjacentText(blocks) {
4437
4501
  }
4438
4502
  return out;
4439
4503
  }
4504
+
4505
+ // src/storage/memory-store.ts
4506
+ init_atomic_write();
4440
4507
  var MAX_BYTES_TOTAL = 32e3;
4441
4508
  var DefaultMemoryStore = class {
4442
4509
  files;
@@ -4449,12 +4516,13 @@ var DefaultMemoryStore = class {
4449
4516
  * issue order. Different scopes still proceed in parallel.
4450
4517
  *
4451
4518
  * 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.
4519
+ * error is caught and swallowed so the chain stays alive for subsequent
4520
+ * calls. The error is stored in `writeErrors` so callers can learn about
4521
+ * it on the next read operation.
4456
4522
  */
4457
4523
  writeChain = /* @__PURE__ */ new Map();
4524
+ /** Last write error per scope — surfaced as warnings on the next readAll(). */
4525
+ writeErrors = /* @__PURE__ */ new Map();
4458
4526
  constructor(opts) {
4459
4527
  this.files = {
4460
4528
  "project-agents": opts.paths.inProjectAgentsFile,
@@ -4464,10 +4532,16 @@ var DefaultMemoryStore = class {
4464
4532
  }
4465
4533
  async runSerialized(scope, work) {
4466
4534
  const prior = this.writeChain.get(scope) ?? Promise.resolve();
4535
+ prior.catch((err) => {
4536
+ this.writeErrors.set(scope, err);
4537
+ });
4467
4538
  const next = prior.catch(() => void 0).then(work);
4468
4539
  this.writeChain.set(scope, next);
4469
4540
  try {
4470
4541
  return await next;
4542
+ } catch (err) {
4543
+ this.writeErrors.set(scope, err);
4544
+ throw err;
4471
4545
  } finally {
4472
4546
  if (this.writeChain.get(scope) === next) {
4473
4547
  this.writeChain.delete(scope);
@@ -4477,6 +4551,10 @@ var DefaultMemoryStore = class {
4477
4551
  async readAll() {
4478
4552
  const parts = [];
4479
4553
  for (const scope of ["project-agents", "project-memory", "user-memory"]) {
4554
+ const writeErr = this.writeErrors.get(scope);
4555
+ if (writeErr) {
4556
+ parts.push(`> \u26A0\uFE0F Memory write error (${labelOf(scope)}): ${writeErr.message}`);
4557
+ }
4480
4558
  const body = await this.read(scope);
4481
4559
  if (body.trim()) parts.push(`## ${labelOf(scope)}
4482
4560
 
@@ -4969,6 +5047,9 @@ function runConfigMigrations(input, targetVersion, migrations) {
4969
5047
  return { config: current, applied, shouldPersist };
4970
5048
  }
4971
5049
  var DEFAULT_CONFIG_MIGRATIONS = [];
5050
+
5051
+ // src/storage/recovery-lock.ts
5052
+ init_atomic_write();
4972
5053
  var LOCK_FILE = "active.json";
4973
5054
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
4974
5055
  var RecoveryLock = class {
@@ -5189,6 +5270,9 @@ var SessionAnalyzer = class {
5189
5270
  return last - first;
5190
5271
  }
5191
5272
  };
5273
+
5274
+ // src/storage/todos-checkpoint.ts
5275
+ init_atomic_write();
5192
5276
  async function loadTodosCheckpoint(filePath) {
5193
5277
  let raw;
5194
5278
  try {
@@ -5257,6 +5341,9 @@ function attachTodosCheckpoint(state, filePath, sessionId) {
5257
5341
  }
5258
5342
  };
5259
5343
  }
5344
+
5345
+ // src/storage/plan-store.ts
5346
+ init_atomic_write();
5260
5347
  async function loadPlan(filePath) {
5261
5348
  let raw;
5262
5349
  try {
@@ -5491,6 +5578,9 @@ ${cat}:`);
5491
5578
  }
5492
5579
  return lines.join("\n");
5493
5580
  }
5581
+
5582
+ // src/storage/director-state.ts
5583
+ init_atomic_write();
5494
5584
  async function loadDirectorState(filePath) {
5495
5585
  let raw;
5496
5586
  try {
@@ -5669,6 +5759,9 @@ var DirectorStateCheckpoint = class {
5669
5759
  }
5670
5760
  }
5671
5761
  };
5762
+
5763
+ // src/security/permission-policy.ts
5764
+ init_atomic_write();
5672
5765
  var DefaultPermissionPolicy = class {
5673
5766
  policy = {};
5674
5767
  loaded = false;
@@ -6199,17 +6292,23 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
6199
6292
  handleTextDelta(state, ev.text);
6200
6293
  events.emit("provider.text_delta", { ctx, text: ev.text });
6201
6294
  break;
6202
- case "tool_use_start":
6203
- handleToolUseStart(state, ev);
6204
- events.emit("provider.tool_use_start", { ctx, id: ev.id, name: ev.name });
6295
+ case "tool_use_start": {
6296
+ const idVal = ev.id;
6297
+ const nameVal = ev.name;
6298
+ handleToolUseStart(state, { id: idVal, name: nameVal });
6299
+ const emittedPayload = { ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
6300
+ events.emit("provider.tool_use_start", emittedPayload);
6205
6301
  break;
6302
+ }
6206
6303
  case "tool_use_input_delta":
6207
6304
  handleToolUseInputDelta(state, ev);
6208
6305
  break;
6209
- case "tool_use_stop":
6306
+ case "tool_use_stop": {
6307
+ const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
6210
6308
  handleToolUseStop(state, ev);
6211
- events.emit("provider.tool_use_stop", { ctx, id: ev.id });
6309
+ events.emit("provider.tool_use_stop", { ctx, id: ev.id, name: stoppedName });
6212
6310
  break;
6311
+ }
6213
6312
  case "thinking_start":
6214
6313
  handleThinkingStart(state, ev);
6215
6314
  break;
@@ -7164,31 +7263,794 @@ var AutonomousRunner = class {
7164
7263
  this.opts.onDone?.(timeoutResult);
7165
7264
  return timeoutResult;
7166
7265
  }
7167
- this.stopped = true;
7168
- const failedResult = {
7169
- status: "failed",
7170
- error: toWrongStackError(e),
7171
- iterations: this.iterations,
7172
- toolCalls: this.toolCalls,
7173
- reason: e instanceof Error ? e.message : String(e)
7266
+ this.stopped = true;
7267
+ const failedResult = {
7268
+ status: "failed",
7269
+ error: toWrongStackError(e),
7270
+ iterations: this.iterations,
7271
+ toolCalls: this.toolCalls,
7272
+ reason: e instanceof Error ? e.message : String(e)
7273
+ };
7274
+ this.opts.onDone?.(failedResult);
7275
+ return failedResult;
7276
+ } finally {
7277
+ clearTimeout(timeout);
7278
+ }
7279
+ }
7280
+ return {
7281
+ status: "aborted",
7282
+ iterations: this.iterations,
7283
+ toolCalls: this.toolCalls,
7284
+ reason: "stopped externally"
7285
+ };
7286
+ }
7287
+ stop() {
7288
+ this.stopped = true;
7289
+ }
7290
+ };
7291
+
7292
+ // src/storage/goal-store.ts
7293
+ init_atomic_write();
7294
+ var MAX_JOURNAL_ENTRIES = 500;
7295
+ function goalFilePath(projectRoot) {
7296
+ return path6.join(projectRoot, ".wrongstack", "goal.json");
7297
+ }
7298
+ async function loadGoal(filePath) {
7299
+ let raw;
7300
+ try {
7301
+ raw = await fsp2.readFile(filePath, "utf8");
7302
+ } catch {
7303
+ return null;
7304
+ }
7305
+ try {
7306
+ const parsed = JSON.parse(raw);
7307
+ if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
7308
+ return null;
7309
+ }
7310
+ return parsed;
7311
+ } catch {
7312
+ return null;
7313
+ }
7314
+ }
7315
+ async function saveGoal(filePath, goal) {
7316
+ await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
7317
+ }
7318
+ function emptyGoal(goal) {
7319
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7320
+ return {
7321
+ version: 1,
7322
+ goal,
7323
+ setAt: now,
7324
+ lastActivityAt: now,
7325
+ iterations: 0,
7326
+ engineState: "idle",
7327
+ goalState: "active",
7328
+ todoAttempts: {},
7329
+ journal: []
7330
+ };
7331
+ }
7332
+ function appendJournal(goal, entry) {
7333
+ const iteration = goal.iterations + 1;
7334
+ const at = (/* @__PURE__ */ new Date()).toISOString();
7335
+ const full = { ...entry, iteration, at };
7336
+ const journal = [...goal.journal, full];
7337
+ const trimmed = journal.length > MAX_JOURNAL_ENTRIES ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES) : journal;
7338
+ return {
7339
+ ...goal,
7340
+ iterations: iteration,
7341
+ lastActivityAt: at,
7342
+ journal: trimmed
7343
+ };
7344
+ }
7345
+ function summarizeUsage(goal) {
7346
+ let totalCostUsd = 0;
7347
+ let totalInputTokens = 0;
7348
+ let totalOutputTokens = 0;
7349
+ let iterationsWithUsage = 0;
7350
+ for (const e of goal.journal) {
7351
+ if (typeof e.costUsd === "number") totalCostUsd += e.costUsd;
7352
+ if (e.tokens) {
7353
+ totalInputTokens += e.tokens.input;
7354
+ totalOutputTokens += e.tokens.output;
7355
+ }
7356
+ if (typeof e.costUsd === "number" || e.tokens) iterationsWithUsage++;
7357
+ }
7358
+ return { totalCostUsd, totalInputTokens, totalOutputTokens, iterationsWithUsage };
7359
+ }
7360
+ function formatGoal(goal, journalLimit = 10) {
7361
+ const lines = [];
7362
+ lines.push(`Goal: ${goal.goal}`);
7363
+ lines.push(`Set: ${goal.setAt}`);
7364
+ lines.push(`Last activity: ${goal.lastActivityAt}`);
7365
+ lines.push(`Iterations: ${goal.iterations}`);
7366
+ lines.push(`Mission: ${goal.goalState ?? "active"}`);
7367
+ lines.push(`Engine: ${goal.engineState}`);
7368
+ const usage = summarizeUsage(goal);
7369
+ if (usage.iterationsWithUsage > 0) {
7370
+ lines.push(
7371
+ `Spent: $${usage.totalCostUsd.toFixed(4)} (in ${usage.totalInputTokens} / out ${usage.totalOutputTokens} tokens across ${usage.iterationsWithUsage} iterations)`
7372
+ );
7373
+ }
7374
+ if (goal.journal.length > 0) {
7375
+ lines.push("");
7376
+ lines.push(`Recent journal (last ${Math.min(journalLimit, goal.journal.length)}):`);
7377
+ const tail = goal.journal.slice(-journalLimit);
7378
+ for (const e of tail) {
7379
+ const mark = e.status === "success" ? "\u2713" : e.status === "failure" ? "\u2717" : e.status === "aborted" ? "\u2298" : "\xB7";
7380
+ const note = e.note ? ` \u2014 ${e.note}` : "";
7381
+ const cost = typeof e.costUsd === "number" ? ` ($${e.costUsd.toFixed(4)})` : "";
7382
+ lines.push(` #${e.iteration} ${mark} [${e.source}] ${e.task}${cost}${note}`);
7383
+ }
7384
+ }
7385
+ return lines.join("\n");
7386
+ }
7387
+
7388
+ // src/execution/eternal-autonomy.ts
7389
+ var execFileP = promisify(execFile);
7390
+ var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
7391
+ var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
7392
+ var EternalAutonomyEngine = class {
7393
+ constructor(opts) {
7394
+ this.opts = opts;
7395
+ this.goalPath = goalFilePath(opts.projectRoot);
7396
+ }
7397
+ opts;
7398
+ state = "idle";
7399
+ stopRequested = false;
7400
+ consecutiveFailures = 0;
7401
+ consecutiveBrainstormDone = 0;
7402
+ /**
7403
+ * Count of consecutive transient (recoverable) provider failures. Drives
7404
+ * the exponential backoff between iterations. Reset on the first
7405
+ * successful iteration so a single bad afternoon doesn't permanently
7406
+ * slow the loop down.
7407
+ */
7408
+ consecutiveTransientRetries = 0;
7409
+ currentCtrl = null;
7410
+ iterationsSinceCompact = 0;
7411
+ goalPath;
7412
+ /** Current engine state — readable for UIs. */
7413
+ get currentState() {
7414
+ return this.state;
7415
+ }
7416
+ /** Synchronously request stop. Resolves once the running iteration aborts. */
7417
+ stop() {
7418
+ this.stopRequested = true;
7419
+ this.currentCtrl?.abort();
7420
+ void this.persistEngineState("stopped").catch(() => {
7421
+ });
7422
+ this.state = "stopped";
7423
+ }
7424
+ /**
7425
+ * Mark the engine as 'running' on disk + reset stop state so a new
7426
+ * batch of `runOneIteration()` calls can proceed. Called by the REPL
7427
+ * when the user invokes `/autonomy eternal`. Idempotent.
7428
+ */
7429
+ async prime() {
7430
+ this.stopRequested = false;
7431
+ this.state = "running";
7432
+ await this.persistEngineState("running").catch(() => {
7433
+ });
7434
+ }
7435
+ /**
7436
+ * Main loop. Returns when stop() is called or the goal file is removed.
7437
+ * Does NOT throw — every iteration is wrapped to keep the loop alive.
7438
+ */
7439
+ async run() {
7440
+ this.state = "running";
7441
+ await this.persistEngineState("running");
7442
+ try {
7443
+ while (!this.stopRequested) {
7444
+ let iterationOk = false;
7445
+ try {
7446
+ iterationOk = await this.runOneIteration();
7447
+ } catch (err) {
7448
+ this.consecutiveFailures++;
7449
+ this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
7450
+ await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
7451
+ }
7452
+ if (iterationOk) {
7453
+ this.consecutiveFailures = 0;
7454
+ }
7455
+ if (this.stopRequested) break;
7456
+ await sleep(this.opts.cycleGapMs ?? 1e3);
7457
+ }
7458
+ } finally {
7459
+ this.state = "stopped";
7460
+ await this.persistEngineState("stopped").catch(() => {
7461
+ });
7462
+ }
7463
+ }
7464
+ /**
7465
+ * Execute a single sense-decide-execute-reflect cycle.
7466
+ * Returns true on success, false on handled failure / no-op.
7467
+ *
7468
+ * Exposed publicly so the REPL can pace iterations from its main loop
7469
+ * — running the engine and the REPL as a single sequential consumer of
7470
+ * `agent.run()` avoids race conditions on the shared Context.
7471
+ */
7472
+ async runOneIteration() {
7473
+ const goal = await loadGoal(this.goalPath);
7474
+ if (!goal) {
7475
+ this.stopRequested = true;
7476
+ return false;
7477
+ }
7478
+ const missionState = goal.goalState ?? "active";
7479
+ if (missionState !== "active") {
7480
+ this.stopRequested = true;
7481
+ return false;
7482
+ }
7483
+ const action = await this.decide(goal);
7484
+ if (!action) {
7485
+ if (!this.stopRequested) {
7486
+ await sleep(5e3);
7487
+ }
7488
+ return false;
7489
+ }
7490
+ const ctrl = new AbortController();
7491
+ this.currentCtrl = ctrl;
7492
+ const timer = setTimeout(
7493
+ () => ctrl.abort(),
7494
+ this.opts.iterationTimeoutMs ?? 5 * 6e4
7495
+ );
7496
+ let status = "success";
7497
+ let note;
7498
+ let finalText = "";
7499
+ let isTransientFailure = false;
7500
+ const tc = this.opts.agent.ctx?.tokenCounter;
7501
+ const beforeUsage = tc?.total?.();
7502
+ const beforeCost = tc?.estimateCost?.().total;
7503
+ try {
7504
+ const result = await this.opts.agent.run(
7505
+ [{ type: "text", text: action.directive }],
7506
+ {
7507
+ signal: ctrl.signal,
7508
+ // Enable per-call autonomous continuation so the agent can chain
7509
+ // multiple internal tool/response cycles end-to-end on one
7510
+ // directive instead of returning to the engine after a single
7511
+ // round-trip. The model uses `[continue]` / `[done]` markers
7512
+ // (or the `continue_to_next_iteration` tool) to control the
7513
+ // inner loop. Without this flag the engine produced shallow
7514
+ // iterations and almost never let a real task finish.
7515
+ autonomousContinue: true,
7516
+ // Cap the inner loop so a runaway agent.run can't burn through
7517
+ // the iteration timeout — the engine's own outer loop is the
7518
+ // long-running thing, each tick should be bounded.
7519
+ maxIterations: this.opts.iterationMaxAgentSteps ?? 50
7520
+ }
7521
+ );
7522
+ if (result.status === "aborted") {
7523
+ status = "aborted";
7524
+ note = "stopped by user";
7525
+ } else if (result.status === "failed") {
7526
+ status = "failure";
7527
+ note = result.error?.describe?.() ?? "agent run failed";
7528
+ isTransientFailure = result.error?.recoverable === true;
7529
+ } else if (result.status === "max_iterations") {
7530
+ status = "failure";
7531
+ note = `max iterations (${result.iterations})`;
7532
+ } else {
7533
+ status = "success";
7534
+ finalText = result.finalText ?? "";
7535
+ const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
7536
+ if (tail) note = tail;
7537
+ }
7538
+ } catch (err) {
7539
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
7540
+ status = isAbort ? "aborted" : "failure";
7541
+ note = err instanceof Error ? err.message : String(err);
7542
+ if (!isAbort && typeof err?.recoverable === "boolean") {
7543
+ isTransientFailure = err.recoverable;
7544
+ }
7545
+ } finally {
7546
+ clearTimeout(timer);
7547
+ this.currentCtrl = null;
7548
+ }
7549
+ if (action.source === "todo" && action.todoId && status !== "success") {
7550
+ await this.bumpTodoAttempt(action.todoId);
7551
+ }
7552
+ const afterUsage = tc?.total?.();
7553
+ const afterCost = tc?.estimateCost?.().total;
7554
+ const tokens = beforeUsage && afterUsage ? {
7555
+ input: Math.max(0, afterUsage.input - beforeUsage.input),
7556
+ output: Math.max(0, afterUsage.output - beforeUsage.output)
7557
+ } : void 0;
7558
+ const costUsd = typeof beforeCost === "number" && typeof afterCost === "number" ? Math.max(0, afterCost - beforeCost) : void 0;
7559
+ await this.appendIterationEntry({
7560
+ source: action.source,
7561
+ task: action.task,
7562
+ status,
7563
+ note,
7564
+ tokens,
7565
+ costUsd
7566
+ });
7567
+ let iterationIndex = 0;
7568
+ try {
7569
+ const reloaded = await loadGoal(this.goalPath);
7570
+ iterationIndex = reloaded?.iterations ?? 0;
7571
+ } catch {
7572
+ }
7573
+ this.opts.onIteration?.({
7574
+ at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
7575
+ iteration: iterationIndex,
7576
+ source: action.source,
7577
+ task: action.task,
7578
+ status,
7579
+ note,
7580
+ tokens,
7581
+ costUsd
7582
+ });
7583
+ if (status === "failure") {
7584
+ if (isTransientFailure) {
7585
+ this.consecutiveTransientRetries++;
7586
+ const delay = this.computeTransientBackoffMs();
7587
+ if (delay > 0) {
7588
+ await this.sleepInterruptible(delay);
7589
+ }
7590
+ return false;
7591
+ }
7592
+ this.consecutiveFailures++;
7593
+ return false;
7594
+ }
7595
+ if (status === "aborted") {
7596
+ if (this.stopRequested) return false;
7597
+ this.consecutiveFailures++;
7598
+ return false;
7599
+ }
7600
+ this.consecutiveTransientRetries = 0;
7601
+ if (GOAL_COMPLETE_MARKER.test(finalText)) {
7602
+ await this.markGoalCompleted(action, finalText);
7603
+ this.stopRequested = true;
7604
+ return true;
7605
+ }
7606
+ this.iterationsSinceCompact++;
7607
+ await this.maybeCompact().catch((err) => {
7608
+ this.opts.onError?.(
7609
+ err instanceof Error ? err : new Error(String(err)),
7610
+ this.consecutiveFailures
7611
+ );
7612
+ });
7613
+ return true;
7614
+ }
7615
+ /**
7616
+ * Run compaction when either trigger fires:
7617
+ * - We've done >= compactEveryNIterations since the last compact.
7618
+ * - Current request tokens exceed aggressiveCompactRatio * maxContext.
7619
+ *
7620
+ * The second check uses *aggressive* mode to free more headroom; the
7621
+ * cadence check uses non-aggressive (cheaper).
7622
+ */
7623
+ async maybeCompact() {
7624
+ const compactor = this.opts.compactor;
7625
+ if (!compactor) return;
7626
+ const ctx = this.opts.agent.ctx;
7627
+ if (!ctx) return;
7628
+ const cadence = this.opts.compactEveryNIterations ?? 25;
7629
+ const threshold = this.opts.aggressiveCompactRatio ?? 0.85;
7630
+ const maxCtx = this.opts.maxContextTokens;
7631
+ let aggressive = false;
7632
+ let shouldRun = false;
7633
+ if (this.iterationsSinceCompact >= cadence) {
7634
+ shouldRun = true;
7635
+ }
7636
+ if (maxCtx && maxCtx > 0) {
7637
+ const used = ctx.tokenCounter?.currentRequestTokens?.();
7638
+ if (used) {
7639
+ const total = used.input + used.cacheRead;
7640
+ if (total / maxCtx >= threshold) {
7641
+ shouldRun = true;
7642
+ aggressive = true;
7643
+ }
7644
+ }
7645
+ }
7646
+ if (!shouldRun) return;
7647
+ const report = await compactor.compact(ctx, { aggressive });
7648
+ this.iterationsSinceCompact = 0;
7649
+ const saved = report.before - report.after;
7650
+ await this.appendIterationEntry({
7651
+ source: "manual",
7652
+ task: `compaction (${aggressive ? "aggressive" : "cadence"})`,
7653
+ status: "success",
7654
+ note: `saved ~${saved} tokens (${report.before}\u2192${report.after})`
7655
+ });
7656
+ }
7657
+ /**
7658
+ * Hybrid idea source.
7659
+ * 1. Pending todos on the agent's context.
7660
+ * 2. Dirty git working tree → propose a "review and finish this" task.
7661
+ * 3. Otherwise: brainstorm via the LLM against the goal.
7662
+ *
7663
+ * After failureBudget consecutive failures, force brainstorm so the
7664
+ * engine doesn't loop on the same broken todo or stuck git state.
7665
+ */
7666
+ async decide(goal) {
7667
+ const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
7668
+ if (!forceBrainstorm) {
7669
+ const todo = this.pickPendingTodo(goal);
7670
+ if (todo) {
7671
+ return {
7672
+ source: "todo",
7673
+ task: todo.content,
7674
+ todoId: todo.id,
7675
+ directive: this.buildDirective(goal, "todo", todo.content)
7174
7676
  };
7175
- this.opts.onDone?.(failedResult);
7176
- return failedResult;
7177
- } finally {
7178
- clearTimeout(timeout);
7179
7677
  }
7678
+ const gitTask = await this.pickGitTask();
7679
+ if (gitTask) {
7680
+ return {
7681
+ source: "git",
7682
+ task: gitTask,
7683
+ directive: this.buildDirective(goal, "git", gitTask)
7684
+ };
7685
+ }
7686
+ }
7687
+ const brainstormed = await this.brainstormTask(goal);
7688
+ if (brainstormed === BRAINSTORM_DONE) {
7689
+ this.consecutiveBrainstormDone++;
7690
+ const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
7691
+ if (this.consecutiveBrainstormDone >= threshold) {
7692
+ await this.markGoalCompleted(
7693
+ { source: "brainstorm", task: "no further work", directive: "" },
7694
+ `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
7695
+ );
7696
+ this.stopRequested = true;
7697
+ }
7698
+ return null;
7180
7699
  }
7700
+ if (!brainstormed) return null;
7701
+ this.consecutiveBrainstormDone = 0;
7181
7702
  return {
7182
- status: "aborted",
7183
- iterations: this.iterations,
7184
- toolCalls: this.toolCalls,
7185
- reason: "stopped externally"
7703
+ source: "brainstorm",
7704
+ task: brainstormed,
7705
+ directive: this.buildDirective(goal, "brainstorm", brainstormed)
7186
7706
  };
7187
7707
  }
7188
- stop() {
7189
- this.stopped = true;
7708
+ pickPendingTodo(goal) {
7709
+ const todos = this.opts.agent.ctx.todos;
7710
+ if (!Array.isArray(todos)) return null;
7711
+ const attempts = goal.todoAttempts ?? {};
7712
+ const ceiling = this.opts.todoMaxAttempts ?? 3;
7713
+ for (const t2 of todos) {
7714
+ if (t2.status !== "pending") continue;
7715
+ const used = attempts[t2.id] ?? 0;
7716
+ if (used >= ceiling) continue;
7717
+ return t2;
7718
+ }
7719
+ return null;
7720
+ }
7721
+ async pickGitTask() {
7722
+ let out;
7723
+ try {
7724
+ out = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
7725
+ } catch {
7726
+ return null;
7727
+ }
7728
+ const dirty = out.trim();
7729
+ if (!dirty) return null;
7730
+ const lines = dirty.split("\n").slice(0, 8);
7731
+ const preview = lines.join(", ");
7732
+ return `Inspect the dirty working tree and either finish the in-progress work or revert it. Files: ${preview}`;
7733
+ }
7734
+ async readGitStatus() {
7735
+ const { stdout } = await execFileP("git", ["status", "--porcelain"], {
7736
+ cwd: this.opts.projectRoot,
7737
+ timeout: 5e3
7738
+ });
7739
+ return stdout;
7740
+ }
7741
+ async brainstormTask(goal) {
7742
+ const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
7743
+ const directive = [
7744
+ "You are deciding the next action in an autonomous loop pursuing a long-running goal.",
7745
+ "",
7746
+ `Goal: ${goal.goal}`,
7747
+ "",
7748
+ lastFew ? `Recent iterations:
7749
+ ${lastFew}` : "No prior iterations yet.",
7750
+ "",
7751
+ "Output ONE concrete, immediately-actionable task that advances the goal.",
7752
+ "Constraints:",
7753
+ "- One sentence, imperative form, under 200 chars.",
7754
+ "- No preamble, no explanation, no markdown \u2014 just the task line.",
7755
+ "- If recent iterations show repeated failures on the same target, pivot.",
7756
+ "- If the goal appears fully accomplished AND you can name a concrete",
7757
+ " artifact / test / output that proves it, output exactly: DONE",
7758
+ "- Be conservative with DONE: if the recent journal contains failures",
7759
+ " or aborted entries, the goal is almost certainly NOT done."
7760
+ ].join("\n");
7761
+ try {
7762
+ const ctrl = new AbortController();
7763
+ const timer = setTimeout(() => ctrl.abort(), 6e4);
7764
+ try {
7765
+ const result = await this.opts.agent.run(
7766
+ [{ type: "text", text: directive }],
7767
+ { signal: ctrl.signal, maxIterations: 1 }
7768
+ );
7769
+ if (result.status !== "done") return null;
7770
+ const text = (result.finalText ?? "").trim();
7771
+ if (!text) return null;
7772
+ if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
7773
+ const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
7774
+ if (!firstLine) return null;
7775
+ if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
7776
+ return firstLine.slice(0, 240);
7777
+ } finally {
7778
+ clearTimeout(timer);
7779
+ }
7780
+ } catch {
7781
+ return null;
7782
+ }
7783
+ }
7784
+ buildDirective(goal, source, task) {
7785
+ const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
7786
+ return [
7787
+ "\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
7788
+ "",
7789
+ `Mission: ${goal.goal}`,
7790
+ `Iteration: #${goal.iterations + 1}`,
7791
+ `Source: ${source}`,
7792
+ `Task: ${task}`,
7793
+ "",
7794
+ recentJournal ? `Recent journal (last 5):
7795
+ ${recentJournal}` : "No prior iterations.",
7796
+ "",
7797
+ "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
7798
+ "You are inside a long-running autonomous loop. Each iteration you",
7799
+ "execute ONE concrete task that advances the Mission. No user is",
7800
+ "available to clarify \u2014 make defensible decisions and move forward.",
7801
+ "",
7802
+ "1. EXECUTE END-TO-END",
7803
+ " \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
7804
+ " to chain to the next internal step without returning.",
7805
+ " \u2022 When this iteration's Task is finished (real artifact / passing",
7806
+ " test / applied diff / clean output), emit `[done]` on its own line.",
7807
+ " \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
7808
+ " approaches before giving up. YOLO is active; no confirmations.",
7809
+ "",
7810
+ "2. UPDATE TODO STATE (when Source is `todo`)",
7811
+ " \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
7812
+ " \u2022 Mark it `completed` on success, with a one-line outcome note.",
7813
+ " \u2022 If you cannot make progress after 2 distinct attempts, mark it",
7814
+ " `cancelled` with the obstacle. The loop will skip it next time.",
7815
+ "",
7816
+ "3. MISSION-COMPLETE PROTOCOL",
7817
+ " \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
7818
+ " verifiably accomplished, emit on its own line:",
7819
+ " [GOAL_COMPLETE]",
7820
+ " followed by a one-paragraph verification recipe (artifact path,",
7821
+ " test command, or 10-second reproduction). This halts the loop.",
7822
+ " \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
7823
+ ' "looks fine". Required: a concrete artifact that proves it AND',
7824
+ " no recent journal failures contradicting completion.",
7825
+ " \u2022 If unsure, emit `[done]` instead and let the next iteration",
7826
+ " decide. The loop is patient; false completion is not.",
7827
+ "",
7828
+ "4. NO INTERACTIVITY",
7829
+ " \u2022 Do not ask questions, do not request confirmation, do not propose",
7830
+ " options. Pick the best path and execute. The user is asleep."
7831
+ ].join("\n");
7832
+ }
7833
+ /**
7834
+ * Exponential backoff for transient provider errors. `2^N * base`
7835
+ * capped at `transientBackoffMaxMs`. Zero base disables backoff.
7836
+ * Public-private to keep `runOneIteration` readable; the value is
7837
+ * recomputed each call from the current retry count, so callers
7838
+ * don't have to track state.
7839
+ */
7840
+ computeTransientBackoffMs() {
7841
+ const base = this.opts.transientBackoffBaseMs ?? 2e3;
7842
+ const cap = this.opts.transientBackoffMaxMs ?? 6e4;
7843
+ if (base <= 0) return 0;
7844
+ const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
7845
+ return Math.min(cap, base * Math.pow(2, exponent));
7846
+ }
7847
+ /**
7848
+ * Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
7849
+ * so SIGINT / `/autonomy stop` can land in the middle of a long
7850
+ * backoff instead of waiting up to a minute for the timer.
7851
+ */
7852
+ async sleepInterruptible(totalMs) {
7853
+ const step = 250;
7854
+ let remaining = totalMs;
7855
+ while (remaining > 0 && !this.stopRequested) {
7856
+ const chunk = Math.min(step, remaining);
7857
+ await sleep(chunk);
7858
+ remaining -= chunk;
7859
+ }
7860
+ }
7861
+ async appendIterationEntry(entry) {
7862
+ const current = await loadGoal(this.goalPath);
7863
+ if (!current) {
7864
+ return;
7865
+ }
7866
+ const updated = appendJournal(current, entry);
7867
+ await saveGoal(this.goalPath, updated);
7868
+ }
7869
+ /**
7870
+ * Persistent per-todo failure counter. Skipped silently when the goal
7871
+ * file has been removed (graceful clear). Each non-success iteration
7872
+ * against a todo source bumps the counter by 1; `pickPendingTodo` reads
7873
+ * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
7874
+ */
7875
+ async bumpTodoAttempt(todoId) {
7876
+ const current = await loadGoal(this.goalPath);
7877
+ if (!current) return;
7878
+ const attempts = { ...current.todoAttempts ?? {} };
7879
+ attempts[todoId] = (attempts[todoId] ?? 0) + 1;
7880
+ await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
7881
+ }
7882
+ /**
7883
+ * Flip the mission to `completed` and journal it. Called from two
7884
+ * paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
7885
+ * finalText, (b) `brainstorm` returning DONE consecutively past the
7886
+ * configured threshold. Idempotent — re-entry is a no-op once the
7887
+ * goal is already `completed`.
7888
+ */
7889
+ async markGoalCompleted(action, note) {
7890
+ const current = await loadGoal(this.goalPath);
7891
+ if (!current) return;
7892
+ if (current.goalState === "completed") return;
7893
+ const withFlag = { ...current, goalState: "completed" };
7894
+ const withEntry = appendJournal(withFlag, {
7895
+ source: action.source,
7896
+ task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
7897
+ status: "success",
7898
+ note: note.slice(0, 240)
7899
+ });
7900
+ await saveGoal(this.goalPath, withEntry);
7901
+ }
7902
+ async appendFailure(task, note) {
7903
+ await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
7904
+ }
7905
+ async persistEngineState(state) {
7906
+ const current = await loadGoal(this.goalPath);
7907
+ if (!current) return;
7908
+ if (current.engineState === state) return;
7909
+ await saveGoal(this.goalPath, { ...current, engineState: state });
7190
7910
  }
7191
7911
  };
7912
+ function sleep(ms) {
7913
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
7914
+ }
7915
+
7916
+ // src/execution/autonomy-prompt-contributor.ts
7917
+ function makeAutonomyPromptContributor(opts) {
7918
+ return async (ctx) => {
7919
+ if (ctx.subagent) return [];
7920
+ if (!opts.enabled()) return [];
7921
+ let goal;
7922
+ try {
7923
+ goal = await loadGoal(opts.goalPath);
7924
+ } catch {
7925
+ return [];
7926
+ }
7927
+ if (!goal) return [];
7928
+ const missionState = goal.goalState ?? "active";
7929
+ if (missionState !== "active") return [];
7930
+ const tailSize = opts.journalTailSize ?? 5;
7931
+ const journalTail = goal.journal.slice(-tailSize).map((e) => {
7932
+ const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
7933
+ return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
7934
+ });
7935
+ const text = [
7936
+ "## ETERNAL AUTONOMY \u2014 active mission",
7937
+ "",
7938
+ "You are inside a long-running autonomous loop. The user is asleep",
7939
+ "and is not available to confirm decisions. Each turn you receive a",
7940
+ "directive describing one concrete sub-task that advances the mission.",
7941
+ "",
7942
+ `Mission: ${goal.goal}`,
7943
+ `Iteration: #${goal.iterations}`,
7944
+ journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
7945
+ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
7946
+ "",
7947
+ "### Loop control markers",
7948
+ "Emit these on their own line in your final text \u2014 case-insensitive,",
7949
+ "whitespace-tolerant, but they must occupy the entire line:",
7950
+ "- `[continue]` \u2014 chain to the next internal step without returning.",
7951
+ "- `[done]` \u2014 the current sub-task is finished; return to the engine.",
7952
+ "- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
7953
+ " verifiably done. Must be followed by a one-paragraph verification",
7954
+ " recipe (artifact path, test command, or 10-second reproduction).",
7955
+ " The engine halts on this marker \u2014 false positives waste real",
7956
+ " human time. If unsure, emit `[done]` and let the next iteration",
7957
+ " decide.",
7958
+ "",
7959
+ "### Operating principles",
7960
+ "- YOLO is active. Do NOT ask for confirmation, do NOT propose",
7961
+ " options. Pick the best path and execute it.",
7962
+ "- Use tools freely; multiple calls per turn are normal and expected.",
7963
+ "- When working on a todo, mark it `in_progress` via the todos tool",
7964
+ " before tool work and `completed` (or `cancelled` with a reason)",
7965
+ " when done. The loop reads todo state between iterations.",
7966
+ "- If an approach fails twice in a row, pivot. Don't grind on the",
7967
+ " same wall \u2014 try a different angle, file a cancel on the todo, or",
7968
+ " surface the obstacle via `[done]` and let the next iteration",
7969
+ " re-plan."
7970
+ ].join("\n");
7971
+ return [
7972
+ {
7973
+ type: "text",
7974
+ text,
7975
+ cache_control: { type: "ephemeral" }
7976
+ }
7977
+ ];
7978
+ };
7979
+ }
7980
+
7981
+ // src/execution/goal-preamble.ts
7982
+ function buildGoalPreamble(goal) {
7983
+ return [
7984
+ "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
7985
+ "The user granted you full autonomy. Read these constraints once, then act.",
7986
+ "",
7987
+ "YOUR GOAL:",
7988
+ "---",
7989
+ goal,
7990
+ "---",
7991
+ "",
7992
+ "AUTHORITY YOU HAVE:",
7993
+ "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
7994
+ " Parallel + recursive fan-out are both fine. There is no spawn budget.",
7995
+ "- Use any provider/model per subagent \u2014 pick the right tool for each",
7996
+ " piece of work. Heavy reasoning model for planning, fast model for",
7997
+ " batch work, specialist model for domain code.",
7998
+ "- Run unlimited tool calls and iterations. There is NO hidden budget.",
7999
+ " The Agent loop auto-extends every 100 iterations forever.",
8000
+ "- Retry failed tools with different inputs, alternative paths, fresh",
8001
+ " subagents. Switch providers mid-run if one is rate-limited.",
8002
+ "- Re-plan freely when an approach hits a dead end. You are not obliged",
8003
+ " to stick with the first plan you proposed.",
8004
+ "",
8005
+ 'WHAT "DONE" MEANS \u2014 non-negotiable:',
8006
+ "- You can name a concrete artifact (a passing test, a written file at",
8007
+ " a specific path, a fixed bug verified by re-running the failing case,",
8008
+ " a clean grep that previously had matches).",
8009
+ "- You can tell the user HOW to verify it themselves in 10 seconds.",
8010
+ '- You have NOT hedged. None of: "looks like it should work", "I',
8011
+ ' believe this fixes it", "the changes appear correct".',
8012
+ "",
8013
+ "WHAT IS NOT DONE \u2014 never report any of these as completion:",
8014
+ "- An error message you didn't recover from.",
8015
+ '- An empty result, a 0-line file, a "no matches found" you accepted',
8016
+ " without questioning the search.",
8017
+ '- "Should I continue?" / "Want me to also...?" / "Let me know if you',
8018
+ ' want X." Those are hedges. The user already told you to finish the',
8019
+ " goal \u2014 just do it.",
8020
+ "- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
8021
+ " done, not done.",
8022
+ "- A subagent's failed/timeout/stopped TaskResult that you didn't",
8023
+ " respond to with a fresh attempt (different role, different model,",
8024
+ " tighter prompt).",
8025
+ "",
8026
+ "PERSISTENCE PROTOCOL:",
8027
+ "- If blocked, try at least 3 different angles before reporting the",
8028
+ " problem to the user. Different tool inputs, different subagent",
8029
+ " roles, different providers, different decomposition of the task.",
8030
+ "- If a tool fails, read its error, alter the input, try again. Do",
8031
+ " not just report the failure back.",
8032
+ "- If a subagent returns useless output, respawn with a tighter prompt",
8033
+ ' or a different role. Do not accept "I could not determine\u2026" as the',
8034
+ " final answer.",
8035
+ "- Use `ask_subagent` for one-shot questions when you don't need a",
8036
+ " full delegated task.",
8037
+ "",
8038
+ "REPORTING:",
8039
+ "- Stream short progress notes between major actions so the user can",
8040
+ " monitor. Do not go silent for 50 tool calls then dump a wall of",
8041
+ " text \u2014 but also do not narrate every tool call.",
8042
+ "- Use the shared scratchpad (if available) to leave breadcrumbs",
8043
+ " subagents can read.",
8044
+ "- Final response must include: (a) what was accomplished, (b) how",
8045
+ " to verify, (c) any caveats (residual TODOs, things the user",
8046
+ " should know about).",
8047
+ "",
8048
+ "BEGIN.]"
8049
+ ].join("\n");
8050
+ }
8051
+
8052
+ // src/coordination/director.ts
8053
+ init_atomic_write();
7192
8054
 
7193
8055
  // src/coordination/director-prompts.ts
7194
8056
  var DEFAULT_DIRECTOR_PREAMBLE = `You are the Director of a multi-agent fleet. You orchestrate worker
@@ -7497,7 +8359,7 @@ var BudgetThresholdSignal = class extends Error {
7497
8359
  this.decision = decision;
7498
8360
  }
7499
8361
  };
7500
- var SubagentBudget = class {
8362
+ var SubagentBudget = class _SubagentBudget {
7501
8363
  limits;
7502
8364
  iterations = 0;
7503
8365
  toolCalls = 0;
@@ -7506,6 +8368,26 @@ var SubagentBudget = class {
7506
8368
  costUsd = 0;
7507
8369
  startTime = null;
7508
8370
  _onThreshold;
8371
+ /**
8372
+ * Tracks which budget kinds currently have an extension request
8373
+ * in flight. While a kind is here, further `checkLimit` calls for the
8374
+ * same kind are no-ops — without this dedup, every `recordIteration`
8375
+ * after the limit is reached spawns a fresh decision Promise (until
8376
+ * the first one lands and patches limits), flooding the FleetBus
8377
+ * with redundant threshold events. Cleared in `checkLimitAsync`'s
8378
+ * `finally`.
8379
+ */
8380
+ pendingExtensions = /* @__PURE__ */ new Set();
8381
+ /**
8382
+ * Hard cap on how long `checkLimitAsync` waits for the coordinator to
8383
+ * respond before defaulting to 'stop'. Without this fallback an absent
8384
+ * or hung listener (Director not built / event filter detached mid-run)
8385
+ * leaves the budget over-limit and never enforces anything, since
8386
+ * `checkLimit` returns synchronously via `void this.checkLimitAsync`.
8387
+ * Hardcoded for now — most fleets set their own per-task timeout that
8388
+ * dwarfs this anyway. 30 s matches the existing event-emit timeoutMs.
8389
+ */
8390
+ static DECISION_TIMEOUT_MS = 3e4;
7509
8391
  /**
7510
8392
  * Injected by the runner when wiring the budget to its EventBus.
7511
8393
  * Used by `checkLimitAsync` to emit `budget.threshold_reached` events.
@@ -7548,56 +8430,92 @@ var SubagentBudget = class {
7548
8430
  if (kind === "timeout" || !this._onThreshold) {
7549
8431
  throw new BudgetExceededError(kind, limit, used);
7550
8432
  }
7551
- void this.checkLimitAsync(kind, used, limit);
8433
+ const bus = this._events;
8434
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
8435
+ throw new BudgetExceededError(kind, limit, used);
8436
+ }
8437
+ if (this.pendingExtensions.has(kind)) return;
8438
+ this.pendingExtensions.add(kind);
8439
+ const decision = this.negotiateExtension(kind, used, limit);
8440
+ throw new BudgetThresholdSignal(kind, limit, used, decision);
7552
8441
  }
7553
8442
  /**
7554
- * Async threshold negotiation with the coordinator. Fire-and-forget
7555
- * any error thrown here becomes an unhandled rejection in the test environment
7556
- * because the runner's catch only handles the synchronous throw from `checkLimit`.
8443
+ * Drive the threshold handler to a decision. Resolves with `'stop'`
8444
+ * (signal the runner to abort) or `{ extend: ... }` (limits already
8445
+ * patched in-place; the runner should not abort). Always releases the
8446
+ * `pendingExtensions` slot in `finally`.
8447
+ *
8448
+ * The 'continue' return from a sync handler is treated as
8449
+ * `{ extend: {} }` — keep going without patching, next overrun will
8450
+ * fire a fresh signal.
7557
8451
  */
7558
- async checkLimitAsync(kind, used, limit) {
7559
- const result = this._onThreshold({
7560
- kind,
7561
- used,
7562
- limit,
7563
- // Inject a requestDecision helper the handler can call to emit the
7564
- // budget.threshold_reached event and wait for the coordinator's verdict.
7565
- // The runner wires this by injecting its EventBus into ctx.budget._events.
7566
- requestDecision: () => {
7567
- return new Promise((resolve5) => {
7568
- this._events?.emit("budget.threshold_reached", {
7569
- kind,
7570
- used,
7571
- limit,
7572
- timeoutMs: 3e4,
7573
- extend: (extra) => resolve5({ extend: extra }),
7574
- deny: () => resolve5("stop")
8452
+ async negotiateExtension(kind, used, limit) {
8453
+ try {
8454
+ const result = this._onThreshold({
8455
+ kind,
8456
+ used,
8457
+ limit,
8458
+ // Inject a requestDecision helper the handler can call to emit the
8459
+ // budget.threshold_reached event and wait for the coordinator's verdict.
8460
+ // A hard fallback timer guarantees the promise eventually resolves
8461
+ // even if no listener responds — without it, an absent/detached
8462
+ // Director would leave the budget permanently in "asking" state.
8463
+ requestDecision: () => {
8464
+ const bus = this._events;
8465
+ if (!bus || bus.listenerCount("budget.threshold_reached") === 0) {
8466
+ return Promise.resolve("stop");
8467
+ }
8468
+ return new Promise((resolve5) => {
8469
+ let resolved = false;
8470
+ const respond = (d) => {
8471
+ if (resolved) return;
8472
+ resolved = true;
8473
+ resolve5(d);
8474
+ };
8475
+ const fallback = setTimeout(
8476
+ () => respond("stop"),
8477
+ _SubagentBudget.DECISION_TIMEOUT_MS
8478
+ );
8479
+ bus.emit("budget.threshold_reached", {
8480
+ kind,
8481
+ used,
8482
+ limit,
8483
+ timeoutMs: _SubagentBudget.DECISION_TIMEOUT_MS,
8484
+ extend: (extra) => {
8485
+ clearTimeout(fallback);
8486
+ respond({ extend: extra });
8487
+ },
8488
+ deny: () => {
8489
+ clearTimeout(fallback);
8490
+ respond("stop");
8491
+ }
8492
+ });
7575
8493
  });
7576
- });
8494
+ }
8495
+ });
8496
+ if (result === "throw") return "stop";
8497
+ if (result === "continue") return { extend: {} };
8498
+ const decision = await result;
8499
+ if (decision === "stop") return "stop";
8500
+ const ext = decision.extend;
8501
+ if (ext.maxIterations !== void 0) {
8502
+ this.limits.maxIterations = ext.maxIterations;
7577
8503
  }
7578
- });
7579
- if (result === "throw") {
7580
- throw new BudgetExceededError(kind, limit, used);
7581
- }
7582
- if (result === "continue") {
7583
- return;
7584
- }
7585
- const decision = await result;
7586
- if (decision === "stop") {
7587
- throw new BudgetExceededError(kind, limit, used);
7588
- }
7589
- const ext = decision.extend;
7590
- if (ext.maxIterations !== void 0) {
7591
- this.limits.maxIterations = ext.maxIterations;
7592
- }
7593
- if (ext.maxToolCalls !== void 0) {
7594
- this.limits.maxToolCalls = ext.maxToolCalls;
7595
- }
7596
- if (ext.maxTokens !== void 0) {
7597
- this.limits.maxTokens = ext.maxTokens;
7598
- }
7599
- if (ext.maxCostUsd !== void 0) {
7600
- this.limits.maxCostUsd = ext.maxCostUsd;
8504
+ if (ext.maxToolCalls !== void 0) {
8505
+ this.limits.maxToolCalls = ext.maxToolCalls;
8506
+ }
8507
+ if (ext.maxTokens !== void 0) {
8508
+ this.limits.maxTokens = ext.maxTokens;
8509
+ }
8510
+ if (ext.maxCostUsd !== void 0) {
8511
+ this.limits.maxCostUsd = ext.maxCostUsd;
8512
+ }
8513
+ if (ext.timeoutMs !== void 0) {
8514
+ this.limits.timeoutMs = ext.timeoutMs;
8515
+ }
8516
+ return decision;
8517
+ } finally {
8518
+ this.pendingExtensions.delete(kind);
7601
8519
  }
7602
8520
  }
7603
8521
  recordIteration() {
@@ -7880,6 +8798,19 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
7880
8798
  setRunner(runner) {
7881
8799
  this.runner = runner;
7882
8800
  }
8801
+ /**
8802
+ * Change the in-flight dispatch ceiling at runtime. Lowering does NOT
8803
+ * preempt running tasks — already-dispatched subagents finish their
8804
+ * current task; only future dispatches respect the new cap. Raising
8805
+ * immediately tries to fill the freed slots from the pending queue.
8806
+ */
8807
+ setMaxConcurrent(n) {
8808
+ if (!Number.isFinite(n) || n < 1) {
8809
+ throw new Error(`maxConcurrent must be a finite integer >= 1, got ${n}`);
8810
+ }
8811
+ this.config.maxConcurrent = Math.floor(n);
8812
+ this.tryDispatchNext();
8813
+ }
7883
8814
  async spawn(subagent) {
7884
8815
  const id = subagent.id || randomUUID();
7885
8816
  if (this.subagents.has(id)) {
@@ -8139,14 +9070,65 @@ var DefaultMultiAgentCoordinator = class extends EventEmitter {
8139
9070
  this.recordCompletion(result);
8140
9071
  }
8141
9072
  async executeWithTimeout(runner, task, ctx, budget) {
8142
- const timeoutMs = budget.limits.timeoutMs;
8143
- if (timeoutMs === void 0) return runner(task, ctx);
9073
+ const initialTimeoutMs = budget.limits.timeoutMs;
9074
+ if (initialTimeoutMs === void 0) return runner(task, ctx);
9075
+ const start = Date.now();
8144
9076
  let timer = null;
8145
9077
  const timeoutPromise = new Promise((_, reject) => {
8146
- timer = setTimeout(() => {
8147
- this.subagents.get(ctx.subagentId)?.abortController.abort();
8148
- reject(new BudgetExceededError("timeout", timeoutMs, Date.now()));
8149
- }, timeoutMs);
9078
+ const armFor = (ms) => {
9079
+ if (timer) clearTimeout(timer);
9080
+ timer = setTimeout(async () => {
9081
+ const elapsed = Date.now() - start;
9082
+ const limit = budget.limits.timeoutMs ?? initialTimeoutMs;
9083
+ if (!budget.onThreshold) {
9084
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
9085
+ reject(new BudgetExceededError("timeout", limit, elapsed));
9086
+ return;
9087
+ }
9088
+ try {
9089
+ const result = budget.onThreshold({
9090
+ kind: "timeout",
9091
+ used: elapsed,
9092
+ limit,
9093
+ requestDecision: () => new Promise((resolveDecision) => {
9094
+ budget._events?.emit("budget.threshold_reached", {
9095
+ kind: "timeout",
9096
+ used: elapsed,
9097
+ limit,
9098
+ timeoutMs: 3e4,
9099
+ extend: (extra) => resolveDecision({ extend: extra }),
9100
+ deny: () => resolveDecision("stop")
9101
+ });
9102
+ })
9103
+ });
9104
+ const decision = typeof result === "string" ? result : await result;
9105
+ if (decision === "continue") {
9106
+ armFor(Math.max(1e3, limit));
9107
+ return;
9108
+ }
9109
+ if (decision === "throw" || decision === "stop") {
9110
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
9111
+ reject(new BudgetExceededError("timeout", limit, elapsed));
9112
+ return;
9113
+ }
9114
+ if (decision.extend.timeoutMs !== void 0) {
9115
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
9116
+ const newLimit = decision.extend.timeoutMs;
9117
+ const remaining = Math.max(1e3, newLimit - elapsed);
9118
+ armFor(remaining);
9119
+ return;
9120
+ }
9121
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
9122
+ reject(new BudgetExceededError("timeout", limit, elapsed));
9123
+ } catch (err) {
9124
+ this.subagents.get(ctx.subagentId)?.abortController.abort();
9125
+ reject(
9126
+ err instanceof BudgetExceededError ? err : new BudgetExceededError("timeout", limit, elapsed)
9127
+ );
9128
+ }
9129
+ }, ms);
9130
+ };
9131
+ armFor(initialTimeoutMs);
8150
9132
  });
8151
9133
  try {
8152
9134
  return await Promise.race([runner(task, ctx), timeoutPromise]);
@@ -8685,7 +9667,9 @@ var Director = class {
8685
9667
  }, opts.checkpointDebounceMs ?? 250) : null;
8686
9668
  this.fleetManager = opts.fleetManager;
8687
9669
  if (this.sharedScratchpadPath) {
8688
- void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
9670
+ void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(
9671
+ (err) => this.logShutdownError("shared_scratchpad_mkdir", err)
9672
+ );
8689
9673
  }
8690
9674
  this.transport = new InMemoryBridgeTransport();
8691
9675
  this.bridge = new InMemoryAgentBridge(
@@ -8755,7 +9739,7 @@ var Director = class {
8755
9739
  return;
8756
9740
  }
8757
9741
  extendCounts.set(guardKey, prior + 1);
8758
- setTimeout(() => {
9742
+ setImmediate(() => {
8759
9743
  const extra = {};
8760
9744
  switch (payload.kind) {
8761
9745
  case "iterations":
@@ -8770,9 +9754,12 @@ var Director = class {
8770
9754
  case "cost":
8771
9755
  extra.maxCostUsd = Math.min(payload.limit * 1.5, 10);
8772
9756
  break;
9757
+ case "timeout":
9758
+ extra.timeoutMs = Math.min(Math.ceil(payload.limit * 1.5), 60 * 6e4);
9759
+ break;
8773
9760
  }
8774
9761
  payload.extend(extra);
8775
- }, Math.min(payload.timeoutMs, 3e4));
9762
+ });
8776
9763
  });
8777
9764
  }
8778
9765
  /** Best-effort session-writer append. Swallows failures — the director
@@ -8791,7 +9778,9 @@ var Director = class {
8791
9778
  if (this.manifestTimer) return;
8792
9779
  this.manifestTimer = setTimeout(() => {
8793
9780
  this.manifestTimer = null;
8794
- void this.writeManifest().catch(() => void 0);
9781
+ void this.writeManifest().catch(
9782
+ (err) => this.logShutdownError("manifest_write_debounced", err)
9783
+ );
8795
9784
  }, this.manifestDebounceMs);
8796
9785
  }
8797
9786
  /**
@@ -8982,7 +9971,7 @@ var Director = class {
8982
9971
  usage: this.usage.snapshot()
8983
9972
  };
8984
9973
  await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
8985
- await fsp2.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
9974
+ await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
8986
9975
  return this.manifestPath;
8987
9976
  }
8988
9977
  /**
@@ -9511,9 +10500,11 @@ async function readSubagentPartial(opts, subagentId) {
9511
10500
  candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
9512
10501
  } else {
9513
10502
  try {
9514
- const runDirs = await fsp2.readdir(opts.sessionsRoot);
9515
- for (const r of runDirs) {
9516
- candidates.push(path6.join(opts.sessionsRoot, r, `${subagentId}.jsonl`));
10503
+ const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
10504
+ for (const entry of entries) {
10505
+ if (entry.isDirectory()) {
10506
+ candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
10507
+ }
9517
10508
  }
9518
10509
  } catch {
9519
10510
  return void 0;
@@ -9563,6 +10554,7 @@ function makeAgentSubagentRunner(opts) {
9563
10554
  const detachFleet = opts.fleetBus?.attach(ctx.subagentId, events, task.id);
9564
10555
  const aborter = new AbortController();
9565
10556
  ctx.budget._events = events;
10557
+ ctx.budget.onThreshold = ({ requestDecision }) => requestDecision();
9566
10558
  let budgetError = null;
9567
10559
  const onBudgetError = (err) => {
9568
10560
  if (err instanceof BudgetThresholdSignal) {
@@ -9711,6 +10703,9 @@ function makeDirectorSessionFactory(opts) {
9711
10703
 
9712
10704
  // src/coordination/null-fleet-bus.ts
9713
10705
  var NULL_FLEET_BUS = new FleetBus();
10706
+
10707
+ // src/models/mode-store.ts
10708
+ init_atomic_write();
9714
10709
  var DefaultModeStore = class {
9715
10710
  activeModeId = null;
9716
10711
  modes;
@@ -9769,10 +10764,9 @@ var DefaultModeStore = class {
9769
10764
  try {
9770
10765
  await fsp2.mkdir(this.configDir, { recursive: true });
9771
10766
  const configPath = path6.join(this.configDir, "mode.json");
9772
- await fsp2.writeFile(
10767
+ await atomicWrite(
9773
10768
  configPath,
9774
- JSON.stringify({ activeMode: this.activeModeId }, null, 2),
9775
- "utf8"
10769
+ JSON.stringify({ activeMode: this.activeModeId }, null, 2)
9776
10770
  );
9777
10771
  } catch {
9778
10772
  }
@@ -10626,6 +11620,9 @@ var SpecDrivenDev = class {
10626
11620
  }));
10627
11621
  }
10628
11622
  };
11623
+
11624
+ // src/sdd/spec-store.ts
11625
+ init_atomic_write();
10629
11626
  var SpecStore = class {
10630
11627
  baseDir;
10631
11628
  indexPath;
@@ -10735,6 +11732,9 @@ var SpecStore = class {
10735
11732
  await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
10736
11733
  }
10737
11734
  };
11735
+
11736
+ // src/sdd/task-graph-store.ts
11737
+ init_atomic_write();
10738
11738
  function graphToJSON(graph) {
10739
11739
  const serialisable = {
10740
11740
  ...graph,
@@ -11036,10 +12036,11 @@ var AISpecBuilder = class {
11036
12036
  async saveSession() {
11037
12037
  if (!this.sessionPath) return;
11038
12038
  try {
11039
- const fsp15 = await import('fs/promises');
11040
- const path25 = await import('path');
11041
- await fsp15.mkdir(path25.dirname(this.sessionPath), { recursive: true });
11042
- await fsp15.writeFile(this.sessionPath, JSON.stringify(this.session, null, 2), "utf8");
12039
+ const fsp16 = await import('fs/promises');
12040
+ const path26 = await import('path');
12041
+ const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
12042
+ await fsp16.mkdir(path26.dirname(this.sessionPath), { recursive: true });
12043
+ await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
11043
12044
  } catch {
11044
12045
  }
11045
12046
  }
@@ -11047,8 +12048,8 @@ var AISpecBuilder = class {
11047
12048
  async loadSession() {
11048
12049
  if (!this.sessionPath) return false;
11049
12050
  try {
11050
- const fsp15 = await import('fs/promises');
11051
- const raw = await fsp15.readFile(this.sessionPath, "utf8");
12051
+ const fsp16 = await import('fs/promises');
12052
+ const raw = await fsp16.readFile(this.sessionPath, "utf8");
11052
12053
  const loaded = JSON.parse(raw);
11053
12054
  if (loaded?.id && loaded?.phase && loaded?.title) {
11054
12055
  this.session = loaded;
@@ -11062,14 +12063,21 @@ var AISpecBuilder = class {
11062
12063
  async deleteSession() {
11063
12064
  if (!this.sessionPath) return;
11064
12065
  try {
11065
- const fsp15 = await import('fs/promises');
11066
- await fsp15.unlink(this.sessionPath);
12066
+ const fsp16 = await import('fs/promises');
12067
+ await fsp16.unlink(this.sessionPath);
11067
12068
  } catch {
11068
12069
  }
11069
12070
  }
11070
- /** Auto-save helper — calls saveSession() but never throws. */
12071
+ /** Auto-save helper — calls saveSession() but never throws.
12072
+ * Failures are surfaced via process.emitWarning so a persistent
12073
+ * ENOSPC / EACCES doesn't silently strand session edits in memory. */
11071
12074
  autoSave() {
11072
- this.saveSession().catch(() => {
12075
+ this.saveSession().catch((err) => {
12076
+ const detail = err instanceof Error ? err.message : String(err);
12077
+ process.emitWarning(
12078
+ `SpecBuilder autoSave failed: ${detail}`,
12079
+ "SpecBuilderWarning"
12080
+ );
11073
12081
  });
11074
12082
  }
11075
12083
  // ── Session Lifecycle ─────────────────────────────────────────────────────
@@ -11741,15 +12749,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
11741
12749
  maxId = id;
11742
12750
  }
11743
12751
  }
11744
- const path25 = [];
12752
+ const path26 = [];
11745
12753
  let current = maxId;
11746
12754
  const visited = /* @__PURE__ */ new Set();
11747
12755
  while (current && !visited.has(current)) {
11748
12756
  visited.add(current);
11749
- path25.unshift(current);
12757
+ path26.unshift(current);
11750
12758
  current = prev.get(current) ?? null;
11751
12759
  }
11752
- return path25;
12760
+ return path26;
11753
12761
  }
11754
12762
  function computeParallelGroups(graph, blockedByMap) {
11755
12763
  const groups = [];
@@ -12459,7 +13467,7 @@ async function startMetricsServer(opts) {
12459
13467
  const tls = opts.tls;
12460
13468
  const useHttps = !!(tls?.cert && tls?.key);
12461
13469
  const host = opts.host ?? "127.0.0.1";
12462
- const path25 = opts.path ?? "/metrics";
13470
+ const path26 = opts.path ?? "/metrics";
12463
13471
  const healthPath = opts.healthPath ?? "/healthz";
12464
13472
  const healthRegistry = opts.healthRegistry;
12465
13473
  const listener = (req, res) => {
@@ -12469,7 +13477,7 @@ async function startMetricsServer(opts) {
12469
13477
  return;
12470
13478
  }
12471
13479
  const url = req.url.split("?")[0];
12472
- if (url === path25) {
13480
+ if (url === path26) {
12473
13481
  let body;
12474
13482
  try {
12475
13483
  body = renderPrometheus(opts.sink.snapshot());
@@ -12533,7 +13541,7 @@ async function startMetricsServer(opts) {
12533
13541
  const protocol = useHttps ? "https" : "http";
12534
13542
  return {
12535
13543
  port: boundPort,
12536
- url: `${protocol}://${host}:${boundPort}${path25}`,
13544
+ url: `${protocol}://${host}:${boundPort}${path26}`,
12537
13545
  close: () => new Promise((resolve5, reject) => {
12538
13546
  server.close((err) => err ? reject(err) : resolve5());
12539
13547
  })
@@ -13260,6 +14268,9 @@ function stripTopDir(p) {
13260
14268
  if (idx === -1) return "";
13261
14269
  return p.slice(idx + 1);
13262
14270
  }
14271
+
14272
+ // src/skills/manifest-store.ts
14273
+ init_atomic_write();
13263
14274
  var SkillManifestStore = class {
13264
14275
  manifestPath;
13265
14276
  cache;
@@ -13284,7 +14295,7 @@ var SkillManifestStore = class {
13284
14295
  async write(data) {
13285
14296
  const dir = path6.dirname(this.manifestPath);
13286
14297
  await fsp2.mkdir(dir, { recursive: true });
13287
- await fsp2.writeFile(this.manifestPath, JSON.stringify(data, null, 2) + "\n", "utf8");
14298
+ await atomicWrite(this.manifestPath, JSON.stringify(data, null, 2) + "\n");
13288
14299
  this.cache = data;
13289
14300
  }
13290
14301
  async addEntry(entry) {
@@ -13615,6 +14626,9 @@ async function collectFiles(dir, baseDir) {
13615
14626
  }
13616
14627
  return results;
13617
14628
  }
14629
+
14630
+ // src/storage/session-rewinder.ts
14631
+ init_atomic_write();
13618
14632
  var DefaultSessionRewinder = class {
13619
14633
  constructor(sessionsDir) {
13620
14634
  this.sessionsDir = sessionsDir;
@@ -13748,7 +14762,7 @@ async function revertSnapshots(snapshots) {
13748
14762
  try {
13749
14763
  if (file.action === "deleted") {
13750
14764
  if (file.before !== null) {
13751
- await fsp2.writeFile(file.path, file.before, { mode: 420 });
14765
+ await atomicWrite(file.path, file.before, { mode: 420 });
13752
14766
  revertedFiles.push(file.path);
13753
14767
  }
13754
14768
  } else if (file.action === "created") {
@@ -13756,7 +14770,7 @@ async function revertSnapshots(snapshots) {
13756
14770
  revertedFiles.push(file.path);
13757
14771
  } else if (file.action === "modified") {
13758
14772
  if (file.before !== null) {
13759
- await fsp2.writeFile(file.path, file.before, { mode: 420 });
14773
+ await atomicWrite(file.path, file.before, { mode: 420 });
13760
14774
  revertedFiles.push(file.path);
13761
14775
  }
13762
14776
  }
@@ -14542,10 +15556,10 @@ var SecurityScanner = class {
14542
15556
  for (const pattern of patterns) {
14543
15557
  if (!this.matchesCategory(pattern)) continue;
14544
15558
  for (const regex of pattern.patterns) {
14545
- regex.lastIndex = 0;
14546
15559
  for (let lineNum = 0; lineNum < lines.length; lineNum++) {
14547
15560
  const line = lines[lineNum];
14548
15561
  if (!line) continue;
15562
+ regex.lastIndex = 0;
14549
15563
  if (regex.test(line)) {
14550
15564
  if (this.isFalsePositive(line, pattern.falsePositiveMarkers)) {
14551
15565
  continue;
@@ -14607,6 +15621,9 @@ var SecurityScanner = class {
14607
15621
  }
14608
15622
  };
14609
15623
  var defaultSecurityScanner = new SecurityScanner();
15624
+
15625
+ // src/security-scanner/report-generator.ts
15626
+ init_atomic_write();
14610
15627
  var DEFAULT_REPORT_OPTIONS = {
14611
15628
  outputDir: "security-reports",
14612
15629
  format: "markdown",
@@ -14634,7 +15651,7 @@ var ReportGenerator = class {
14634
15651
  default:
14635
15652
  content = this.generateMarkdown(scanResult);
14636
15653
  }
14637
- await writeFile(filepath, content, "utf-8");
15654
+ await atomicWrite(filepath, content);
14638
15655
  return filepath;
14639
15656
  }
14640
15657
  async ensureOutputDir() {
@@ -14826,6 +15843,9 @@ var ReportGenerator = class {
14826
15843
  }
14827
15844
  };
14828
15845
  var defaultReportGenerator = new ReportGenerator();
15846
+
15847
+ // src/security-scanner/gitignore-updater.ts
15848
+ init_atomic_write();
14829
15849
  var DEFAULT_OPTIONS2 = {
14830
15850
  gitignorePath: ".gitignore",
14831
15851
  entries: ["security-reports/", "security-reports/*"]
@@ -14852,12 +15872,12 @@ var GitignoreUpdater = class {
14852
15872
  }
14853
15873
  if (added.length > 0) {
14854
15874
  const newContent = [...lines].filter(Boolean).join("\n") + "\n";
14855
- await writeFile(this.options.gitignorePath, newContent, "utf-8");
15875
+ await atomicWrite(this.options.gitignorePath, newContent);
14856
15876
  }
14857
15877
  } catch (err) {
14858
15878
  if (err.code === "ENOENT") {
14859
15879
  const content = this.options.entries.join("\n") + "\n";
14860
- await writeFile(this.options.gitignorePath, content, "utf-8");
15880
+ await atomicWrite(this.options.gitignorePath, content);
14861
15881
  added.push(...this.options.entries);
14862
15882
  } else {
14863
15883
  errors.push(`Failed to update .gitignore: ${err}`);
@@ -14876,6 +15896,9 @@ var GitignoreUpdater = class {
14876
15896
  }
14877
15897
  };
14878
15898
  var defaultGitignoreUpdater = new GitignoreUpdater();
15899
+
15900
+ // src/security-scanner/orchestrator.ts
15901
+ init_atomic_write();
14879
15902
  var NETWORK_ERR_RE2 = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
14880
15903
  var SecurityScannerOrchestrator = class {
14881
15904
  constructor(retryPolicy, errorHandler) {
@@ -14921,7 +15944,7 @@ var SecurityScannerOrchestrator = class {
14921
15944
  */
14922
15945
  async run(ctx, options) {
14923
15946
  const { projectRoot, reportOptions, skipGitignore, model: explicitModel } = options;
14924
- const provider = "provider" in ctx ? ctx.provider : ctx;
15947
+ const provider = "provider" in ctx && ctx.provider ? ctx.provider : ctx;
14925
15948
  const model = explicitModel ?? ("model" in ctx ? ctx.model : void 0);
14926
15949
  const detectionResult = await this.detector.detect(projectRoot);
14927
15950
  if (detectionResult.detectedStacks.length === 0) {
@@ -15260,7 +16283,7 @@ Be specific about the vulnerabilities found and how to fix them.`;
15260
16283
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
15261
16284
  const filename = `security-report-${timestamp}.${format}`;
15262
16285
  const filepath = join(outputDir, filename);
15263
- await writeFile(filepath, content, "utf-8");
16286
+ await atomicWrite(filepath, content);
15264
16287
  return filepath;
15265
16288
  }
15266
16289
  /**
@@ -15434,11 +16457,21 @@ Show a previous security report.
15434
16457
  }
15435
16458
  };
15436
16459
  }
16460
+ function getProviderFromContext(ctx) {
16461
+ if (ctx.provider && typeof ctx.provider.complete === "function") {
16462
+ return { provider: ctx.provider, model: ctx.model };
16463
+ }
16464
+ return null;
16465
+ }
15437
16466
  async function handleScan(args, ctx) {
15438
16467
  const options = parseArgs(args);
15439
16468
  const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
15440
16469
  try {
15441
- const result = await defaultOrchestrator.run(ctx, {
16470
+ const providerInfo = getProviderFromContext(ctx);
16471
+ if (!providerInfo) {
16472
+ return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
16473
+ }
16474
+ const result = await defaultOrchestrator.run(providerInfo, {
15442
16475
  projectRoot,
15443
16476
  scanOptions: {
15444
16477
  depth: options.depth || "standard",
@@ -15487,7 +16520,11 @@ async function handleScan(args, ctx) {
15487
16520
  async function handleAudit(ctx) {
15488
16521
  const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
15489
16522
  try {
15490
- const result = await defaultOrchestrator.run(ctx, {
16523
+ const providerInfo = getProviderFromContext(ctx);
16524
+ if (!providerInfo) {
16525
+ return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
16526
+ }
16527
+ const result = await defaultOrchestrator.run(providerInfo, {
15491
16528
  projectRoot,
15492
16529
  reportOptions: { format: "markdown" }
15493
16530
  });
@@ -15543,16 +16580,16 @@ Use \`/security report <number>\` to view a specific report.` };
15543
16580
  }
15544
16581
  const index = parseInt(reportId, 10) - 1;
15545
16582
  if (!isNaN(index) && reports[index]) {
15546
- const { readFile: readFile28 } = await import('fs/promises');
15547
- const content = await readFile28(join(reportsDir, reports[index]), "utf-8");
16583
+ const { readFile: readFile30 } = await import('fs/promises');
16584
+ const content = await readFile30(join(reportsDir, reports[index]), "utf-8");
15548
16585
  return { message: `# Security Report
15549
16586
 
15550
16587
  ${content}` };
15551
16588
  }
15552
16589
  const match = reports.find((r) => r.includes(reportId));
15553
16590
  if (match) {
15554
- const { readFile: readFile28 } = await import('fs/promises');
15555
- const content = await readFile28(join(reportsDir, match), "utf-8");
16591
+ const { readFile: readFile30 } = await import('fs/promises');
16592
+ const content = await readFile30(join(reportsDir, match), "utf-8");
15556
16593
  return { message: `# Security Report
15557
16594
 
15558
16595
  ${content}` };
@@ -15602,6 +16639,9 @@ function getHelpMessage() {
15602
16639
  `;
15603
16640
  }
15604
16641
  var securitySlashCommand = createSecuritySlashCommand();
16642
+
16643
+ // src/coordination/fleet-manager.ts
16644
+ init_atomic_write();
15605
16645
  var FleetManager = class {
15606
16646
  /** The fleet-wide event bus. */
15607
16647
  fleet;
@@ -15749,7 +16789,7 @@ var FleetManager = class {
15749
16789
  usage: this.usage.snapshot()
15750
16790
  };
15751
16791
  await fsp2.mkdir(path6.dirname(this.manifestPath), { recursive: true });
15752
- await fsp2.writeFile(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
16792
+ await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
15753
16793
  return this.manifestPath;
15754
16794
  }
15755
16795
  /**
@@ -15769,7 +16809,13 @@ var FleetManager = class {
15769
16809
  if (this.manifestTimer) return;
15770
16810
  this.manifestTimer = setTimeout(() => {
15771
16811
  this.manifestTimer = null;
15772
- void this.writeManifest().catch(() => void 0);
16812
+ void this.writeManifest().catch((err) => {
16813
+ const detail = err instanceof Error ? err.message : String(err);
16814
+ process.emitWarning(
16815
+ `FleetManager manifest write failed: ${detail}`,
16816
+ "FleetManagerWarning"
16817
+ );
16818
+ });
15773
16819
  }, this.manifestDebounceMs);
15774
16820
  }
15775
16821
  /** Best-effort session event writer. Swallows failures. */
@@ -15798,6 +16844,226 @@ var FleetManager = class {
15798
16844
  return { pending, live: [] };
15799
16845
  }
15800
16846
  };
16847
+ function createMcpControlTool(opts) {
16848
+ const { getConfig, configPath, registry } = opts;
16849
+ const inputSchema = {
16850
+ type: "object",
16851
+ properties: {
16852
+ action: {
16853
+ type: "string",
16854
+ enum: ["list", "search", "enable", "disable", "restart"],
16855
+ description: "The management action to perform."
16856
+ },
16857
+ /** Filter for `search`. Matches server name or description case-insensitively. */
16858
+ query: {
16859
+ type: "string",
16860
+ description: "Search term for `search` action. Matches server name or description."
16861
+ },
16862
+ /** Target server name for `enable`, `disable`, `restart`. */
16863
+ server: {
16864
+ type: "string",
16865
+ description: 'Server name (e.g. "github", "filesystem", "brave-search").'
16866
+ }
16867
+ },
16868
+ required: ["action"]
16869
+ };
16870
+ return {
16871
+ name: "mcp_control",
16872
+ description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers.",
16873
+ category: "mcp",
16874
+ permission: "auto",
16875
+ mutating: false,
16876
+ riskTier: "standard",
16877
+ inputSchema,
16878
+ async execute(raw, ctx) {
16879
+ const input = raw;
16880
+ return mcpControlDispatch(input, { getConfig, configPath, registry });
16881
+ }
16882
+ };
16883
+ }
16884
+ async function mcpControlDispatch(input, deps) {
16885
+ const { action, query, server } = input;
16886
+ switch (action) {
16887
+ case "list":
16888
+ return renderList(deps);
16889
+ case "search":
16890
+ return renderSearch(query ?? "", deps);
16891
+ case "enable":
16892
+ return runEnable(server, deps);
16893
+ case "disable":
16894
+ return runDisable(server, deps);
16895
+ case "restart":
16896
+ return runRestart(server, deps);
16897
+ default:
16898
+ return `Unknown action "${action}". Use one of: list, search, enable, disable, restart.`;
16899
+ }
16900
+ }
16901
+ function renderList(deps) {
16902
+ const configured = deps.getConfig().mcpServers ?? {};
16903
+ const live = deps.registry.describe();
16904
+ if (Object.keys(configured).length === 0) {
16905
+ return [
16906
+ "No MCP servers configured.",
16907
+ ' Use `mcp_control({ action: "search" })` to see available presets,',
16908
+ ' then `mcp_control({ action: "enable", server: "<name>" })` to add one.'
16909
+ ].join("\n");
16910
+ }
16911
+ const lines = [];
16912
+ const liveMap = new Map(live.map((s) => [s.name, s]));
16913
+ for (const [name, cfg] of Object.entries(configured)) {
16914
+ const liveInfo = liveMap.get(name);
16915
+ const toolCount = liveInfo ? ` (${liveInfo.toolCount} tools)` : "";
16916
+ const stateStr = liveInfo ? badge(liveInfo.state) : dim("\u25CB not loaded");
16917
+ const enabled = cfg.enabled === false ? `${dim("disabled")} ` : `${green("\u25CF enabled")} `;
16918
+ lines.push(` ${bold(name)} ${enabled}${stateStr}${toolCount}`);
16919
+ if (cfg.description) lines.push(` ${dim(cfg.description)}`);
16920
+ }
16921
+ lines.push("");
16922
+ lines.push(dim(' Use `mcp_control({ action: "search", query: "<keyword>" })` to find servers.'));
16923
+ lines.push(dim(' Use `mcp_control({ action: "enable", server: "<name>" })` to start a server.'));
16924
+ return lines.join("\n");
16925
+ }
16926
+ function renderSearch(query, deps) {
16927
+ const configured = deps.getConfig().mcpServers ?? {};
16928
+ const all = allServers();
16929
+ const q = query.toLowerCase();
16930
+ const configuredNames = new Set(Object.keys(configured));
16931
+ const configuredEntries = Object.entries(configured).filter(
16932
+ ([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
16933
+ );
16934
+ const unconfiguredEntries = Object.entries(all).filter(([name]) => !configuredNames.has(name)).filter(
16935
+ ([name, cfg]) => name.toLowerCase().includes(q) || (cfg.description ?? "").toLowerCase().includes(q)
16936
+ );
16937
+ const lines = [];
16938
+ if (configuredEntries.length > 0) {
16939
+ lines.push(bold('Configured servers matching "') + query + '":');
16940
+ for (const [name, cfg] of configuredEntries) {
16941
+ lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}`);
16942
+ }
16943
+ lines.push("");
16944
+ }
16945
+ if (unconfiguredEntries.length > 0) {
16946
+ lines.push(bold('Available presets matching "') + query + '":');
16947
+ for (const [name, cfg] of unconfiguredEntries) {
16948
+ const warn = cfg.permission === "deny" ? red(" \u26A0 confirm required") : "";
16949
+ lines.push(` ${bold(name)} ${cfg.description ?? cfg.transport}${warn}`);
16950
+ }
16951
+ lines.push("");
16952
+ }
16953
+ if (configuredEntries.length === 0 && unconfiguredEntries.length === 0) {
16954
+ return `No servers match "${query}". Try a shorter keyword or \`mcp_control({ action: "list" })\`.`;
16955
+ }
16956
+ const total = configuredEntries.length + unconfiguredEntries.length;
16957
+ lines.push(dim(` ${total} server${total !== 1 ? "s" : ""} shown. Run \`enable\` on one to activate it.`));
16958
+ return lines.join("\n");
16959
+ }
16960
+ async function runEnable(name, deps) {
16961
+ if (!name) return '`server` is required for enable. Example: { action: "enable", server: "github" }';
16962
+ const all = allServers();
16963
+ const configured = deps.getConfig().mcpServers ?? {};
16964
+ const cfg = configured[name] ?? all[name];
16965
+ if (!cfg) {
16966
+ const known = Object.keys(all).join(", ");
16967
+ return `Unknown server "${name}". Available presets: ${known}`;
16968
+ }
16969
+ const full = await readConfig(deps.configPath);
16970
+ const mcpServers = {
16971
+ ...full.mcpServers ?? {}
16972
+ };
16973
+ mcpServers[name] = { ...cfg, enabled: true };
16974
+ full.mcpServers = mcpServers;
16975
+ await writeConfig(deps.configPath, full);
16976
+ try {
16977
+ const live = deps.registry.describe().find((s) => s.name === name);
16978
+ if (live && live.state === "connected") {
16979
+ return `${green("\u25CF")} Server "${name}" is already running (${live.toolCount} tools registered).`;
16980
+ }
16981
+ await deps.registry.start({ ...cfg, enabled: true });
16982
+ const updated = deps.registry.describe().find((s) => s.name === name);
16983
+ return `${green("\u2713 Enabled and started")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
16984
+ } catch (err) {
16985
+ return `${red("\u2717 Failed to start")} "${name}": ${err instanceof Error ? err.message : String(err)}`;
16986
+ }
16987
+ }
16988
+ async function runDisable(name, deps) {
16989
+ if (!name) return '`server` is required for disable. Example: { action: "disable", server: "github" }';
16990
+ const configured = deps.getConfig().mcpServers ?? {};
16991
+ if (!configured[name]) {
16992
+ return `Server "${name}" is not in config. Add it with \`mcp_control({ action: "enable", server: "${name}" })\`.`;
16993
+ }
16994
+ const full = await readConfig(deps.configPath);
16995
+ const mcpServers = {
16996
+ ...full.mcpServers ?? {}
16997
+ };
16998
+ const existing = mcpServers[name];
16999
+ mcpServers[name] = { ...existing, enabled: false };
17000
+ full.mcpServers = mcpServers;
17001
+ await writeConfig(deps.configPath, full);
17002
+ try {
17003
+ await deps.registry.stop(name);
17004
+ return `${yellow("\u25CB Disabled")} "${name}". It will not be started on next boot.`;
17005
+ } catch {
17006
+ return `${yellow("\u25CB Disabled")} "${name}" (it was not running). Config updated.`;
17007
+ }
17008
+ }
17009
+ async function runRestart(name, deps) {
17010
+ if (!name) return '`server` is required for restart. Example: { action: "restart", server: "github" }';
17011
+ const configured = deps.getConfig().mcpServers ?? {};
17012
+ if (!configured[name]) {
17013
+ return `Server "${name}" is not configured. Use \`mcp_control({ action: "enable", server: "${name}" })\` first.`;
17014
+ }
17015
+ try {
17016
+ await deps.registry.restart(name);
17017
+ const updated = deps.registry.describe().find((s) => s.name === name);
17018
+ return `${green("\u2713 Restarted")} "${name}"${updated ? ` (${updated.toolCount} tools registered).` : "."}`;
17019
+ } catch (err) {
17020
+ return `${red("\u2717 Restart failed")} for "${name}": ${err instanceof Error ? err.message : String(err)}`;
17021
+ }
17022
+ }
17023
+ async function readConfig(p) {
17024
+ try {
17025
+ return JSON.parse(await fsp2.readFile(p, "utf8"));
17026
+ } catch {
17027
+ return {};
17028
+ }
17029
+ }
17030
+ async function writeConfig(p, cfg) {
17031
+ const raw = JSON.stringify(cfg, null, 2);
17032
+ const tmp = p + ".tmp";
17033
+ await fsp2.writeFile(tmp, raw, "utf8");
17034
+ await fsp2.rename(tmp, p);
17035
+ }
17036
+ function bold(s) {
17037
+ return `\x1B[1m${s}\x1B[0m`;
17038
+ }
17039
+ function dim(s) {
17040
+ return `\x1B[2m${s}\x1B[0m`;
17041
+ }
17042
+ function green(s) {
17043
+ return `\x1B[32m${s}\x1B[0m`;
17044
+ }
17045
+ function yellow(s) {
17046
+ return `\x1B[33m${s}\x1B[0m`;
17047
+ }
17048
+ function red(s) {
17049
+ return `\x1B[31m${s}\x1B[0m`;
17050
+ }
17051
+ function badge(state) {
17052
+ switch (state) {
17053
+ case "connected":
17054
+ return green("\u25CF connected");
17055
+ case "connecting":
17056
+ return `\x1B[36m\u25D0 connecting\x1B[0m`;
17057
+ case "reconnecting":
17058
+ return `\x1B[36m\u25D1 reconnecting\x1B[0m`;
17059
+ case "disconnected":
17060
+ return dim("\u25CB disconnected");
17061
+ case "failed":
17062
+ return red("\u2717 failed");
17063
+ default:
17064
+ return dim(state);
17065
+ }
17066
+ }
15801
17067
 
15802
17068
  // src/extension/registry.ts
15803
17069
  var ExtensionRegistry = class {
@@ -15827,7 +17093,8 @@ var ExtensionRegistry = class {
15827
17093
  */
15828
17094
  async buildSystemPromptContributions(ctx) {
15829
17095
  const blocks = [];
15830
- for (const c of this.promptContributors) {
17096
+ const snapshot = [...this.promptContributors];
17097
+ for (const c of snapshot) {
15831
17098
  try {
15832
17099
  const contributed = await c(ctx);
15833
17100
  blocks.push(...contributed);
@@ -16149,16 +17416,7 @@ var Agent = class {
16149
17416
  this.tracer = init.tracer;
16150
17417
  this.extensions = init.extensions ?? new ExtensionRegistry();
16151
17418
  this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
16152
- this.toolExecutor = new ToolExecutor(this.tools, {
16153
- permissionPolicy: init.permissionPolicy ?? this.permission,
16154
- secretScrubber: this.scrubber,
16155
- renderer: this.renderer,
16156
- events: this.events,
16157
- confirmAwaiter: init.confirmAwaiter,
16158
- iterationTimeoutMs: this.iterationTimeoutMs,
16159
- perIterationOutputCapBytes: this.perIterationOutputCapBytes,
16160
- tracer: this.tracer
16161
- });
17419
+ this.toolExecutor = init.toolExecutor;
16162
17420
  }
16163
17421
  get logger() {
16164
17422
  return this.container.resolve(TOKENS.Logger);
@@ -16268,6 +17526,7 @@ var Agent = class {
16268
17526
  let effectiveLimit = opts.maxIterations ?? this.maxIterations;
16269
17527
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
16270
17528
  let recoveryRetries = 0;
17529
+ const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
16271
17530
  const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
16272
17531
  const baseRunner = diRunner ? (ctx, req) => diRunner.run({
16273
17532
  provider: ctx.provider,
@@ -16294,7 +17553,7 @@ var Agent = class {
16294
17553
  if (controller.signal.aborted) {
16295
17554
  return { status: "aborted", iterations };
16296
17555
  }
16297
- if (this.autonomousContinue) {
17556
+ if (autonomousContinue) {
16298
17557
  consumeAutonomousContinue(this.ctx);
16299
17558
  }
16300
17559
  const limitCheck = await this.checkIterationLimit(
@@ -16373,18 +17632,18 @@ var Agent = class {
16373
17632
  const toolUses = res.content.filter(isToolUseBlock);
16374
17633
  if (toolUses.length === 0) {
16375
17634
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16376
- if (this.autonomousContinue && responseResult.directive === "continue") {
17635
+ if (autonomousContinue && responseResult.directive === "continue") {
16377
17636
  await this.compactContextIfNeeded();
16378
17637
  await this.extensions.runAfterIteration(this.ctx, i);
16379
17638
  continue;
16380
17639
  }
16381
- if (this.autonomousContinue && responseResult.directive === "stop") {
17640
+ if (autonomousContinue && responseResult.directive === "stop") {
16382
17641
  return { status: "done", iterations, finalText };
16383
17642
  }
16384
17643
  return { status: "done", iterations, finalText };
16385
17644
  }
16386
17645
  await this.executeTools(toolUses);
16387
- if (this.autonomousContinue && consumeAutonomousContinue(this.ctx)) {
17646
+ if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
16388
17647
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16389
17648
  await this.compactContextIfNeeded();
16390
17649
  await this.extensions.runAfterIteration(this.ctx, i);
@@ -16393,10 +17652,10 @@ var Agent = class {
16393
17652
  this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
16394
17653
  await this.compactContextIfNeeded();
16395
17654
  await this.extensions.runAfterIteration(this.ctx, i);
16396
- if (this.autonomousContinue && responseResult.directive === "continue") {
17655
+ if (autonomousContinue && responseResult.directive === "continue") {
16397
17656
  continue;
16398
17657
  }
16399
- if (this.autonomousContinue && responseResult.directive === "stop") {
17658
+ if (autonomousContinue && responseResult.directive === "stop") {
16400
17659
  return { status: "done", iterations, finalText };
16401
17660
  }
16402
17661
  }
@@ -16486,7 +17745,7 @@ var Agent = class {
16486
17745
  }
16487
17746
  }
16488
17747
  let directive = "none";
16489
- if (this.autonomousContinue && finalText) {
17748
+ if (finalText) {
16490
17749
  directive = parseContinueDirective(finalText);
16491
17750
  }
16492
17751
  return { finalText, aborted: false, done: false, directive };
@@ -18082,6 +19341,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
18082
19341
  });
18083
19342
  }
18084
19343
 
18085
- 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, 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, 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, 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, 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, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
19344
+ 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, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMcpControlTool, 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, makeAutonomyPromptContributor, 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 };
18086
19345
  //# sourceMappingURL=index.js.map
18087
19346
  //# sourceMappingURL=index.js.map