@wrongstack/core 0.87.0 → 0.89.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 (49) hide show
  1. package/dist/{agent-bridge-C9P_HPez.d.ts → agent-bridge-B8F2ORwg.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-2Aq0jOSj.d.ts → agent-subagent-runner-CQaLFNnA.d.ts} +3 -2
  3. package/dist/coordination/index.d.ts +7 -7
  4. package/dist/coordination/index.js +183 -56
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/{default-config-DEXI4jsl.d.ts → default-config-CXsDvOmP.d.ts} +3 -1
  7. package/dist/defaults/index.d.ts +10 -10
  8. package/dist/defaults/index.js +268 -68
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{events-DnRqXaZ3.d.ts → events-DzyLtja6.d.ts} +6 -0
  11. package/dist/execution/index.d.ts +5 -5
  12. package/dist/execution/index.js +56 -19
  13. package/dist/execution/index.js.map +1 -1
  14. package/dist/extension/index.d.ts +2 -2
  15. package/dist/{index-BNOLadHw.d.ts → index-Ca_lOmKl.d.ts} +2 -2
  16. package/dist/{index-N0_c4bHQ.d.ts → index-Dx-CnrDN.d.ts} +3 -1
  17. package/dist/index.d.ts +24 -21
  18. package/dist/index.js +448 -179
  19. package/dist/index.js.map +1 -1
  20. package/dist/infrastructure/index.d.ts +2 -2
  21. package/dist/infrastructure/index.js +12 -3
  22. package/dist/infrastructure/index.js.map +1 -1
  23. package/dist/kernel/index.d.ts +3 -3
  24. package/dist/kernel/index.js +17 -0
  25. package/dist/kernel/index.js.map +1 -1
  26. package/dist/models/index.js.map +1 -1
  27. package/dist/{multi-agent-coordinator-DllpCVkF.d.ts → multi-agent-coordinator-Da9nd8Ic.d.ts} +1 -1
  28. package/dist/{null-fleet-bus-BY0AN-sr.d.ts → null-fleet-bus-CHJOXv3t.d.ts} +11 -5
  29. package/dist/observability/index.d.ts +1 -1
  30. package/dist/{parallel-eternal-engine-D402RASp.d.ts → parallel-eternal-engine-DyMCj2Pw.d.ts} +4 -3
  31. package/dist/{path-resolver-UPFTsDyD.d.ts → path-resolver-TjBlRKyH.d.ts} +1 -1
  32. package/dist/{plan-templates-DRvPgkfZ.d.ts → plan-templates-D9UhwoGh.d.ts} +1 -1
  33. package/dist/{provider-runner-COAJM8tC.d.ts → provider-runner-BEPWwkxH.d.ts} +1 -1
  34. package/dist/sdd/index.d.ts +3 -3
  35. package/dist/sdd/index.js +89 -19
  36. package/dist/sdd/index.js.map +1 -1
  37. package/dist/security/index.js +18 -2
  38. package/dist/security/index.js.map +1 -1
  39. package/dist/skills/index.js.map +1 -1
  40. package/dist/storage/index.d.ts +4 -4
  41. package/dist/storage/index.js +149 -90
  42. package/dist/storage/index.js.map +1 -1
  43. package/dist/types/index.d.ts +10 -10
  44. package/dist/types/index.js +44 -7
  45. package/dist/types/index.js.map +1 -1
  46. package/dist/utils/index.d.ts +6 -1
  47. package/dist/utils/index.js +51 -3
  48. package/dist/utils/index.js.map +1 -1
  49. package/package.json +1 -1
@@ -29,7 +29,8 @@ var __export = (target, all) => {
29
29
  var atomic_write_exports = {};
30
30
  __export(atomic_write_exports, {
31
31
  atomicWrite: () => atomicWrite,
32
- ensureDir: () => ensureDir
32
+ ensureDir: () => ensureDir,
33
+ withFileLock: () => withFileLock
33
34
  });
34
35
  async function atomicWrite(targetPath, content, opts = {}) {
35
36
  const dir = path3.dirname(targetPath);
@@ -52,8 +53,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
52
53
  }
53
54
  let mode;
54
55
  try {
55
- const stat5 = await fsp.stat(targetPath);
56
- mode = stat5.mode & 511;
56
+ const stat6 = await fsp.stat(targetPath);
57
+ mode = stat6.mode & 511;
57
58
  } catch {
58
59
  mode = opts.mode;
59
60
  }
@@ -72,6 +73,49 @@ async function atomicWrite(targetPath, content, opts = {}) {
72
73
  async function ensureDir(dir) {
73
74
  await fsp.mkdir(dir, { recursive: true });
74
75
  }
76
+ async function withFileLock(targetPath, fn, opts = {}) {
77
+ const dir = path3.dirname(targetPath);
78
+ await fsp.mkdir(dir, { recursive: true });
79
+ const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
80
+ const timeoutMs = opts.timeoutMs ?? 5e3;
81
+ const staleMs = opts.staleMs ?? 3e4;
82
+ const started = Date.now();
83
+ let handle;
84
+ for (; ; ) {
85
+ try {
86
+ handle = await fsp.open(lockPath, "wx");
87
+ await handle.writeFile(`${process.pid}:${Date.now()}`);
88
+ break;
89
+ } catch (err) {
90
+ if (err.code !== "EEXIST") throw err;
91
+ try {
92
+ const stat6 = await fsp.stat(lockPath);
93
+ if (Date.now() - stat6.mtimeMs > staleMs) {
94
+ await fsp.unlink(lockPath);
95
+ continue;
96
+ }
97
+ } catch {
98
+ continue;
99
+ }
100
+ if (Date.now() - started >= timeoutMs) {
101
+ throw new Error(`Timed out waiting for file lock: ${targetPath}`);
102
+ }
103
+ await new Promise((resolve5) => setTimeout(resolve5, 25));
104
+ }
105
+ }
106
+ try {
107
+ return await fn();
108
+ } finally {
109
+ try {
110
+ await handle?.close();
111
+ } catch {
112
+ }
113
+ try {
114
+ await fsp.unlink(lockPath);
115
+ } catch {
116
+ }
117
+ }
118
+ }
75
119
  async function renameWithRetry(from, to) {
76
120
  if (process.platform !== "win32") {
77
121
  await fsp.rename(from, to);
@@ -119,10 +163,15 @@ function writeErr(s, stream = process.stderr) {
119
163
 
120
164
  // src/utils/color.ts
121
165
  var isColorTty = () => {
122
- if (process.env.NO_COLOR) return false;
123
- if (process.env.FORCE_COLOR) return true;
166
+ if (envFlag(process.env.NO_COLOR)) return false;
167
+ if (envFlag(process.env.FORCE_COLOR)) return true;
124
168
  return isStdoutTTY();
125
169
  };
170
+ function envFlag(value) {
171
+ if (value === void 0) return false;
172
+ if (value.trim() === "") return false;
173
+ return !/^(0|false|no|off)$/i.test(value.trim());
174
+ }
126
175
  var COLOR = isColorTty();
127
176
  var wrap = (open3, close) => (s) => COLOR ? `\x1B[${open3}m${s}\x1B[${close}m` : s;
128
177
  var color = {
@@ -159,6 +208,7 @@ var COLORS = {
159
208
  debug: color.gray,
160
209
  trace: color.dim
161
210
  };
211
+ var LOG_LEVELS = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
162
212
  var DefaultLogger = class _DefaultLogger {
163
213
  level;
164
214
  file;
@@ -166,7 +216,7 @@ var DefaultLogger = class _DefaultLogger {
166
216
  pretty;
167
217
  stderr;
168
218
  constructor(opts = {}) {
169
- this.level = opts.level ?? process.env.WRONGSTACK_LOG_LEVEL ?? "info";
219
+ this.level = opts.level ?? parseLogLevel(process.env.WRONGSTACK_LOG_LEVEL);
170
220
  this.file = opts.file;
171
221
  this.bindings = opts.bindings ?? {};
172
222
  this.pretty = opts.pretty ?? true;
@@ -231,6 +281,9 @@ var DefaultLogger = class _DefaultLogger {
231
281
  }
232
282
  }
233
283
  };
284
+ function parseLogLevel(raw) {
285
+ return raw && LOG_LEVELS.has(raw) ? raw : "info";
286
+ }
234
287
  function formatCtx(ctx) {
235
288
  if (ctx instanceof Error) return color.dim(ctx.message);
236
289
  if (typeof ctx === "string") return color.dim(ctx);
@@ -614,8 +667,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
614
667
  return JSON.parse(raw);
615
668
  } catch {
616
669
  const full = this.sessionPath(id, ".jsonl");
617
- const stat5 = await fsp.stat(full);
618
- const summary = await this.summarize(id, stat5.mtime.toISOString());
670
+ const stat6 = await fsp.stat(full);
671
+ const summary = await this.summarize(id, stat6.mtime.toISOString());
619
672
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
620
673
  console.warn(
621
674
  `[session-store] Failed to write manifest for "${id}":`,
@@ -663,8 +716,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
663
716
  if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
664
717
  const jsonlPath = path3.join(dateDir, file.name);
665
718
  try {
666
- const stat5 = await fsp.stat(jsonlPath);
667
- if (stat5.mtimeMs >= cutoff) continue;
719
+ const stat6 = await fsp.stat(jsonlPath);
720
+ if (stat6.mtimeMs >= cutoff) continue;
668
721
  } catch {
669
722
  continue;
670
723
  }
@@ -1774,6 +1827,13 @@ var BEHAVIOR_DEFAULTS = {
1774
1827
  function envBool(v) {
1775
1828
  return !/^(0|false|no|off)$/i.test(v.trim());
1776
1829
  }
1830
+ function envBoolOptional(v) {
1831
+ return v !== void 0 && envBool(v);
1832
+ }
1833
+ var LOG_LEVELS2 = /* @__PURE__ */ new Set(["error", "warn", "info", "debug", "trace"]);
1834
+ function envLogLevel(v) {
1835
+ return LOG_LEVELS2.has(v) ? v : "info";
1836
+ }
1777
1837
  var ENV_MAP = {
1778
1838
  WRONGSTACK_PROVIDER: (c, v) => {
1779
1839
  c.provider = v;
@@ -1797,7 +1857,7 @@ var ENV_MAP = {
1797
1857
  },
1798
1858
  WRONGSTACK_LOG_LEVEL: (c, v) => {
1799
1859
  if (!c.log) c.log = { level: "info" };
1800
- c.log.level = v;
1860
+ c.log.level = envLogLevel(v);
1801
1861
  },
1802
1862
  WRONGSTACK_INDEX_ON_START: (c, v) => {
1803
1863
  c.indexing = { ...defaultIndexing, ...c.indexing, onSessionStart: envBool(v) };
@@ -1839,7 +1899,7 @@ function deepMerge(base, patch) {
1839
1899
  out[k] = [.../* @__PURE__ */ new Set([...existing, ...v])];
1840
1900
  } else {
1841
1901
  out[k] = v;
1842
- if (process.env.WRONGSTACK_DEBUG_CONFIG) {
1902
+ if (envBoolOptional(process.env.WRONGSTACK_DEBUG_CONFIG)) {
1843
1903
  console.warn(
1844
1904
  `[config] Non-primitive array for "${k}" replaced (global + local config merge). Global entries: ${existing?.length ?? 0}, local entries: ${v.length}.`
1845
1905
  );
@@ -3467,9 +3527,18 @@ async function restrictFilePermissions(filePath, opts) {
3467
3527
  const { execFile: execFile2 } = await import('child_process');
3468
3528
  const { promisify: promisify2 } = await import('util');
3469
3529
  const execFileAsync = promisify2(execFile2);
3470
- await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${process.env.USERNAME}:(F)`]);
3530
+ const user = windowsAccountName();
3531
+ if (!user) {
3532
+ warn(
3533
+ `[secret-vault] Could not determine the current Windows user for ${filePath}; skipping icacls hardening.`
3534
+ );
3535
+ return;
3536
+ }
3537
+ await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${user}:(F)`]);
3471
3538
  } catch {
3472
- warn(`[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`);
3539
+ warn(
3540
+ `[secret-vault] Could not restrict permissions on ${filePath} \u2014 config file may be readable by other users on this system.`
3541
+ );
3473
3542
  }
3474
3543
  } else {
3475
3544
  try {
@@ -3478,6 +3547,13 @@ async function restrictFilePermissions(filePath, opts) {
3478
3547
  }
3479
3548
  }
3480
3549
  }
3550
+ function windowsAccountName() {
3551
+ const username = process.env.USERNAME || process.env.USER;
3552
+ if (!username || username.includes("\0")) return void 0;
3553
+ const domain = process.env.USERDOMAIN;
3554
+ if (domain && !domain.includes("\0")) return `${domain}\\${username}`;
3555
+ return username;
3556
+ }
3481
3557
  function walkCount(node, vault, counter) {
3482
3558
  if (node === null || node === void 0) return node;
3483
3559
  if (typeof node !== "object") return node;
@@ -4542,6 +4618,7 @@ function tryParse(s) {
4542
4618
  }
4543
4619
 
4544
4620
  // src/core/streaming-response-builder.ts
4621
+ var STREAM_DRAIN_TIMEOUT_MS = 500;
4545
4622
  function buildResponse(state) {
4546
4623
  const content = [];
4547
4624
  for (const b of state.blockOrder) {
@@ -4754,7 +4831,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
4754
4831
  await Promise.race([
4755
4832
  Promise.resolve(iter.return?.()),
4756
4833
  new Promise((resolve5) => {
4757
- drainTimer = setTimeout(resolve5, 500);
4834
+ drainTimer = setTimeout(resolve5, STREAM_DRAIN_TIMEOUT_MS);
4758
4835
  })
4759
4836
  ]);
4760
4837
  } finally {
@@ -6086,6 +6163,7 @@ var ToolExecutor = class {
6086
6163
  this.registry = registry;
6087
6164
  this.opts = opts;
6088
6165
  this.iterationTimeoutMs = opts.iterationTimeoutMs ?? 3e5;
6166
+ this.maxToolTimeoutMs = opts.maxToolTimeoutMs ?? 3e5;
6089
6167
  this.serializer = createToolOutputSerializer({
6090
6168
  perIterationOutputCapBytes: opts.perIterationOutputCapBytes ?? 1e5
6091
6169
  });
@@ -6094,6 +6172,7 @@ var ToolExecutor = class {
6094
6172
  opts;
6095
6173
  serializer;
6096
6174
  iterationTimeoutMs;
6175
+ maxToolTimeoutMs;
6097
6176
  /**
6098
6177
  * Clear the interactive confirm awaiter so the executor returns
6099
6178
  * `ToolConfirmPendingResult` instead of blocking on stdin. Used by
@@ -6331,7 +6410,10 @@ ${post.additionalContext}` };
6331
6410
  if (parentSignal.reason instanceof Error) throw parentSignal.reason;
6332
6411
  throw new Error(typeof parentSignal.reason === "string" ? parentSignal.reason : "aborted");
6333
6412
  }
6334
- const timeoutMs = tool.timeoutMs ?? this.iterationTimeoutMs;
6413
+ const timeoutMs = clampTimeoutMs(
6414
+ tool.timeoutMs ?? this.iterationTimeoutMs,
6415
+ this.maxToolTimeoutMs
6416
+ );
6335
6417
  const ctrl = new AbortController();
6336
6418
  const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
6337
6419
  const combined = AbortSignal.any([parentSignal, ctrl.signal]);
@@ -6466,6 +6548,12 @@ ${excerpt}`;
6466
6548
  return void 0;
6467
6549
  }
6468
6550
  };
6551
+ function clampTimeoutMs(timeoutMs, maxTimeoutMs) {
6552
+ const fallback = 3e5;
6553
+ const finiteTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : fallback;
6554
+ const finiteMax = Number.isFinite(maxTimeoutMs) && maxTimeoutMs > 0 ? maxTimeoutMs : fallback;
6555
+ return Math.max(1, Math.min(finiteTimeout, finiteMax));
6556
+ }
6469
6557
  var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
6470
6558
  function hasMalformedArguments(input) {
6471
6559
  if (!input || typeof input !== "object" || Array.isArray(input)) return false;
@@ -7278,6 +7366,7 @@ ${recentJournal}` : "No prior iterations.",
7278
7366
  };
7279
7367
 
7280
7368
  // src/coordination/subagent-budget.ts
7369
+ var TIMEOUT_PREEMPT_FRACTION = 0.85;
7281
7370
  var BudgetExceededError = class extends Error {
7282
7371
  kind;
7283
7372
  limit;
@@ -10079,16 +10168,17 @@ Do not add prose, markdown, or code fences.`;
10079
10168
  const raw = (await complete(prompt)).trim();
10080
10169
  const match = raw.match(/\{[\s\S]*\}/);
10081
10170
  if (!match) return null;
10082
- try {
10083
- const parsed = JSON.parse(match[0]);
10084
- if (typeof parsed.role !== "string") return null;
10085
- const role = parsed.role.trim();
10086
- const valid = candidates.some((c) => c.role === role);
10087
- if (!valid) return null;
10088
- return { role, reason: typeof parsed.reason === "string" ? parsed.reason : void 0 };
10089
- } catch {
10090
- return null;
10091
- }
10171
+ const parsed = safeParse(
10172
+ match[0]
10173
+ );
10174
+ if (!parsed.ok || !parsed.value || typeof parsed.value.role !== "string") return null;
10175
+ const role = parsed.value.role.trim();
10176
+ const valid = candidates.some((c) => c.role === role);
10177
+ if (!valid) return null;
10178
+ return {
10179
+ role,
10180
+ reason: typeof parsed.value.reason === "string" ? parsed.value.reason : void 0
10181
+ };
10092
10182
  };
10093
10183
  }
10094
10184
 
@@ -11043,15 +11133,38 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11043
11133
  }
11044
11134
  const start = Date.now();
11045
11135
  let timer = null;
11136
+ let preemptedForLimit = null;
11046
11137
  const timeoutPromise = new Promise((_, reject) => {
11047
11138
  const armFor = (ms) => {
11048
11139
  if (timer) clearTimeout(timer);
11049
11140
  timer = setTimeout(onTick, Math.max(0, ms));
11050
11141
  };
11051
11142
  const scheduleNext = () => {
11052
- const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.timeoutMs ?? initialTimeoutMs) - (Date.now() - start);
11143
+ const wallLimit = budget.limits.timeoutMs ?? initialTimeoutMs;
11144
+ const wallRemaining = initialTimeoutMs === void 0 ? Number.POSITIVE_INFINITY : wallLimit - (Date.now() - start);
11053
11145
  const idleRemaining = idleLimitMs === void 0 ? Number.POSITIVE_INFINITY : (budget.limits.idleTimeoutMs ?? idleLimitMs) - budget.idleMs();
11054
- armFor(Math.max(25, Math.min(wallRemaining, idleRemaining)));
11146
+ const preemptRemaining = initialTimeoutMs === void 0 || preemptedForLimit === wallLimit ? Number.POSITIVE_INFINITY : wallLimit * TIMEOUT_PREEMPT_FRACTION - (Date.now() - start);
11147
+ armFor(Math.max(25, Math.min(wallRemaining, idleRemaining, preemptRemaining)));
11148
+ };
11149
+ const negotiateTimeout = async (used, limit) => {
11150
+ const handler = budget.onThreshold;
11151
+ if (!handler) return "stop";
11152
+ const result = handler({
11153
+ kind: "timeout",
11154
+ used,
11155
+ limit,
11156
+ requestDecision: () => new Promise((resolveDecision) => {
11157
+ budget._events?.emit("budget.threshold_reached", {
11158
+ kind: "timeout",
11159
+ used,
11160
+ limit,
11161
+ timeoutMs: 6e4,
11162
+ extend: (extra) => resolveDecision({ extend: extra }),
11163
+ deny: () => resolveDecision("stop")
11164
+ });
11165
+ })
11166
+ });
11167
+ return typeof result === "string" ? result : await result;
11055
11168
  };
11056
11169
  const onTick = async () => {
11057
11170
  const elapsed = Date.now() - start;
@@ -11064,6 +11177,21 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11064
11177
  reject(new BudgetExceededError("timeout", idleLimit ?? 0, budget.idleMs()));
11065
11178
  return;
11066
11179
  }
11180
+ if (wallLimit !== void 0 && !wallExceeded && budget.onThreshold && preemptedForLimit !== wallLimit && elapsed >= wallLimit * TIMEOUT_PREEMPT_FRACTION) {
11181
+ try {
11182
+ const decision = await negotiateTimeout(elapsed, wallLimit);
11183
+ if (typeof decision !== "string" && decision.extend.timeoutMs !== void 0) {
11184
+ budget.limits.timeoutMs = decision.extend.timeoutMs;
11185
+ preemptedForLimit = null;
11186
+ } else {
11187
+ preemptedForLimit = wallLimit;
11188
+ }
11189
+ } catch {
11190
+ preemptedForLimit = wallLimit;
11191
+ }
11192
+ scheduleNext();
11193
+ return;
11194
+ }
11067
11195
  if (!wallExceeded) {
11068
11196
  scheduleNext();
11069
11197
  return;
@@ -11075,28 +11203,15 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11075
11203
  return;
11076
11204
  }
11077
11205
  try {
11078
- const result = budget.onThreshold({
11079
- kind: "timeout",
11080
- used: elapsed,
11081
- limit,
11082
- requestDecision: () => new Promise((resolveDecision) => {
11083
- budget._events?.emit("budget.threshold_reached", {
11084
- kind: "timeout",
11085
- used: elapsed,
11086
- limit,
11087
- timeoutMs: 6e4,
11088
- extend: (extra) => resolveDecision({ extend: extra }),
11089
- deny: () => resolveDecision("stop")
11090
- });
11091
- })
11092
- });
11093
- const decision = typeof result === "string" ? result : await result;
11206
+ const decision = await negotiateTimeout(elapsed, limit);
11094
11207
  if (decision === "continue" || decision === "throw" || decision === "stop") {
11208
+ preemptedForLimit = null;
11095
11209
  armFor(Math.max(1e3, limit));
11096
11210
  return;
11097
11211
  }
11098
11212
  if (decision.extend.timeoutMs !== void 0) {
11099
11213
  budget.limits.timeoutMs = decision.extend.timeoutMs;
11214
+ preemptedForLimit = null;
11100
11215
  scheduleNext();
11101
11216
  return;
11102
11217
  }
@@ -12004,8 +12119,8 @@ async function expandGlob(pattern) {
12004
12119
  for (const e of entries) {
12005
12120
  const full = `${dir}${SEP}${e}`;
12006
12121
  try {
12007
- const stat5 = await fsp.stat(full);
12008
- if (stat5.isDirectory()) await walk4(full, rest);
12122
+ const stat6 = await fsp.stat(full);
12123
+ if (stat6.isDirectory()) await walk4(full, rest);
12009
12124
  } catch {
12010
12125
  }
12011
12126
  }
@@ -12022,8 +12137,8 @@ async function expandGlob(pattern) {
12022
12137
  if (entries.includes(seg)) {
12023
12138
  const full = `${dir}${SEP}${seg}`;
12024
12139
  try {
12025
- const stat5 = await fsp.stat(full);
12026
- if (stat5.isDirectory()) await walk4(full, rest);
12140
+ const stat6 = await fsp.stat(full);
12141
+ if (stat6.isDirectory()) await walk4(full, rest);
12027
12142
  } catch {
12028
12143
  }
12029
12144
  }
@@ -12051,6 +12166,7 @@ var CollabSession = class extends EventEmitter {
12051
12166
  timeoutMs;
12052
12167
  cancelled = false;
12053
12168
  alerts = [];
12169
+ snapshotWarnings = [];
12054
12170
  /** Tracks tool call counts per subagent for progress-based timeout decisions. */
12055
12171
  progressBySubagent = /* @__PURE__ */ new Map();
12056
12172
  /** Last tool call count when a timeout warning was handled. */
@@ -12120,10 +12236,19 @@ var CollabSession = class extends EventEmitter {
12120
12236
  }
12121
12237
  for (const filePath of allFiles) {
12122
12238
  try {
12123
- const content = await fsp.readFile(filePath, "utf8");
12239
+ const [content, stat6] = await Promise.all([
12240
+ fsp.readFile(filePath, "utf8"),
12241
+ fsp.stat(filePath)
12242
+ ]);
12124
12243
  const ext = filePath.split(".").pop() ?? "";
12125
12244
  const language = ext === "ts" || ext === "tsx" ? "typescript" : ext === "js" || ext === "jsx" ? "javascript" : ext === "md" ? "markdown" : ext === "json" ? "json" : void 0;
12126
- this.snapshot.files.push({ path: filePath, content, language });
12245
+ this.snapshot.files.push({
12246
+ path: filePath,
12247
+ content,
12248
+ language,
12249
+ snapshotMtimeMs: stat6.mtimeMs,
12250
+ snapshotSizeBytes: stat6.size
12251
+ });
12127
12252
  } catch {
12128
12253
  this.snapshot.files.push({ path: filePath, content: "", language: void 0 });
12129
12254
  }
@@ -12146,7 +12271,11 @@ var CollabSession = class extends EventEmitter {
12146
12271
  subagentId: this.director.id,
12147
12272
  ts: Date.now(),
12148
12273
  type: "director.cancel_collab",
12149
- payload: { sessionId: this.sessionId, reason, cancelledAt: (/* @__PURE__ */ new Date()).toISOString() }
12274
+ payload: {
12275
+ sessionId: this.sessionId,
12276
+ reason,
12277
+ cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
12278
+ }
12150
12279
  });
12151
12280
  this.fleetBus.emit({
12152
12281
  subagentId: this.director.id,
@@ -12197,6 +12326,7 @@ var CollabSession = class extends EventEmitter {
12197
12326
  for (const result of results?.flat() ?? []) {
12198
12327
  await this.parseAndEmit(result);
12199
12328
  }
12329
+ this.snapshotWarnings = await this.checkSnapshotFreshness();
12200
12330
  const report = this.assembleReport();
12201
12331
  this.cleanup();
12202
12332
  this.emit("session.done", report);
@@ -12260,7 +12390,7 @@ var CollabSession = class extends EventEmitter {
12260
12390
  const defaults = {
12261
12391
  "bug-hunter": { maxIterations: 2e3, maxToolCalls: 5e3, timeoutMs: 10 * 60 * 1e3 },
12262
12392
  "refactor-planner": { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 },
12263
- "critic": { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
12393
+ critic: { maxIterations: 1e3, maxToolCalls: 3e3, timeoutMs: 6 * 60 * 1e3 }
12264
12394
  };
12265
12395
  return defaults[role] ?? { maxIterations: 1500, maxToolCalls: 4e3, timeoutMs: 8 * 60 * 1e3 };
12266
12396
  }
@@ -12340,7 +12470,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12340
12470
  }
12341
12471
  wireFleetBus() {
12342
12472
  const dTool = this.fleetBus.filter("tool.executed", (e) => {
12343
- this.progressBySubagent.set(e.subagentId, (this.progressBySubagent.get(e.subagentId) ?? 0) + 1);
12473
+ this.progressBySubagent.set(
12474
+ e.subagentId,
12475
+ (this.progressBySubagent.get(e.subagentId) ?? 0) + 1
12476
+ );
12344
12477
  });
12345
12478
  this.disposers.push(dTool);
12346
12479
  const dBudget = this.fleetBus.filter("budget.threshold_reached", (e) => {
@@ -12379,7 +12512,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12379
12512
  return;
12380
12513
  }
12381
12514
  this.lastTimeoutProgress.set(e.subagentId, progress);
12382
- const newLimit = Math.min(Math.ceil((payload.timeoutMs ?? payload.limit) * 2), 24 * 60 * 6e4);
12515
+ const newLimit = Math.min(
12516
+ Math.ceil((payload.timeoutMs ?? payload.limit) * 2),
12517
+ 24 * 60 * 6e4
12518
+ );
12383
12519
  setImmediate(() => {
12384
12520
  payload.extend({ timeoutMs: newLimit });
12385
12521
  });
@@ -12489,15 +12625,18 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12489
12625
  needs_revision: 1,
12490
12626
  reject: 2
12491
12627
  };
12492
- const overallVerdict = evalList.reduce(
12493
- (worst, eval_) => {
12494
- const w = verdictOrder[worst];
12495
- const c = verdictOrder[eval_.verdict];
12496
- return c > w ? eval_.verdict : worst;
12497
- },
12498
- "approve"
12628
+ const overallVerdict = evalList.reduce((worst, eval_) => {
12629
+ const w = verdictOrder[worst];
12630
+ const c = verdictOrder[eval_.verdict];
12631
+ return c > w ? eval_.verdict : worst;
12632
+ }, "approve");
12633
+ const summary = this.buildMarkdownSummary(
12634
+ bugList,
12635
+ planList,
12636
+ evalList,
12637
+ overallVerdict,
12638
+ disposition
12499
12639
  );
12500
- const summary = this.buildMarkdownSummary(bugList, planList, evalList, overallVerdict, disposition);
12501
12640
  return {
12502
12641
  sessionId: this.sessionId,
12503
12642
  startedAt: this.snapshot.createdAt,
@@ -12508,10 +12647,28 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12508
12647
  refactorPlans: planList,
12509
12648
  evaluations: evalList,
12510
12649
  alerts: [...this.alerts],
12650
+ ...this.snapshotWarnings.length > 0 ? { snapshotWarnings: this.snapshotWarnings } : {},
12511
12651
  overallVerdict,
12512
12652
  summary
12513
12653
  };
12514
12654
  }
12655
+ async checkSnapshotFreshness() {
12656
+ const warnings = [];
12657
+ for (const file of this.snapshot.files) {
12658
+ if (file.snapshotMtimeMs === void 0 && file.snapshotSizeBytes === void 0) continue;
12659
+ try {
12660
+ const stat6 = await fsp.stat(file.path);
12661
+ const mtimeChanged = file.snapshotMtimeMs !== void 0 && stat6.mtimeMs > file.snapshotMtimeMs + 1;
12662
+ const sizeChanged = file.snapshotSizeBytes !== void 0 && stat6.size !== file.snapshotSizeBytes;
12663
+ if (mtimeChanged || sizeChanged) {
12664
+ warnings.push(`${file.path} changed after the collab snapshot was captured.`);
12665
+ }
12666
+ } catch {
12667
+ warnings.push(`${file.path} could not be checked after the collab snapshot was captured.`);
12668
+ }
12669
+ }
12670
+ return warnings;
12671
+ }
12515
12672
  buildMarkdownSummary(bugs, plans, evals, overallVerdict, disposition) {
12516
12673
  const lines = [
12517
12674
  `## Collaborative Debugging Report \u2014 ${this.sessionId}`,
@@ -12528,10 +12685,19 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12528
12685
  }
12529
12686
  lines.push("");
12530
12687
  }
12688
+ if (this.snapshotWarnings.length > 0) {
12689
+ lines.push("### Snapshot Warnings", "");
12690
+ for (const warning of this.snapshotWarnings) {
12691
+ lines.push(`- ${warning}`);
12692
+ }
12693
+ lines.push("");
12694
+ }
12531
12695
  if (bugs.length > 0) {
12532
12696
  lines.push("### Bugs Found", "");
12533
12697
  for (const b of bugs) {
12534
- lines.push(`- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`);
12698
+ lines.push(
12699
+ `- **[${b.severity.toUpperCase()}]** \`${b.location.file}:${b.location.line}\` \u2014 ${b.description}`
12700
+ );
12535
12701
  }
12536
12702
  lines.push("");
12537
12703
  }
@@ -13134,6 +13300,7 @@ var FleetBus = class {
13134
13300
  set.add(handler);
13135
13301
  return () => {
13136
13302
  set.delete(handler);
13303
+ if (set.size === 0) this.byId.delete(subagentId);
13137
13304
  };
13138
13305
  }
13139
13306
  /** Subscribe to one event type across all subagents. */
@@ -13146,6 +13313,7 @@ var FleetBus = class {
13146
13313
  set.add(handler);
13147
13314
  return () => {
13148
13315
  set.delete(handler);
13316
+ if (set.size === 0) this.byType.delete(type);
13149
13317
  };
13150
13318
  }
13151
13319
  /** Subscribe to literally everything. The fleet roll-up uses this. */
@@ -13179,6 +13347,11 @@ var FleetBus = class {
13179
13347
  }
13180
13348
  }
13181
13349
  }
13350
+ clear() {
13351
+ this.byId.clear();
13352
+ this.byType.clear();
13353
+ this.any.clear();
13354
+ }
13182
13355
  };
13183
13356
  var FleetUsageAggregator = class {
13184
13357
  constructor(bus, priceLookup, metaLookup) {
@@ -14411,7 +14584,9 @@ var Director = class _Director {
14411
14584
  let toolUses = 0;
14412
14585
  for (const line of targetLines) {
14413
14586
  try {
14414
- const ev = JSON.parse(line);
14587
+ const parsed = safeParse(line);
14588
+ if (!parsed.ok || !parsed.value) continue;
14589
+ const ev = parsed.value;
14415
14590
  if (ev.type === "assistant" && typeof ev.text === "string") {
14416
14591
  lastAssistantText = ev.text;
14417
14592
  } else if (ev.type === "stop" && ev.stopReason) {
@@ -14725,7 +14900,7 @@ function createDelegateTool(opts) {
14725
14900
  const offIter = dir.fleet.filter("iteration.started", bump);
14726
14901
  const offProgress = dir.fleet.filter("tool.progress", bump);
14727
14902
  arm();
14728
- dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __timeout: true })).catch(() => finish({ __timeout: true }));
14903
+ dir.awaitTasks([taskId]).then((r) => finish(r[0] ?? { __emptyResult: true })).catch(() => finish({ __timeout: true }));
14729
14904
  });
14730
14905
  if ("__timeout" in result) {
14731
14906
  const partial2 = await readSubagentPartial(opts, subagentId);
@@ -14750,6 +14925,29 @@ function createDelegateTool(opts) {
14750
14925
  partial: partial2
14751
14926
  };
14752
14927
  }
14928
+ if ("__emptyResult" in result) {
14929
+ const partial2 = await readSubagentPartial(opts, subagentId);
14930
+ opts.events?.emit("delegate.completed", {
14931
+ target,
14932
+ task: i.task,
14933
+ ok: false,
14934
+ status: "empty_result",
14935
+ summary: `[${target}] completed without a task result`,
14936
+ durationMs: 0,
14937
+ iterations: partial2?.events ?? 0,
14938
+ toolCalls: partial2?.toolUsesObserved ?? 0,
14939
+ subagentId
14940
+ });
14941
+ return {
14942
+ ok: false,
14943
+ stopReason: "error",
14944
+ error: "Director returned no task result for the delegated task.",
14945
+ hint: "Check fleet state with /fleet status, then retry or reassign the task.",
14946
+ subagentId,
14947
+ taskId,
14948
+ partial: partial2
14949
+ };
14950
+ }
14753
14951
  const baseStopReason = result.status === "success" ? "end_turn" : result.status === "timeout" ? "subagent_timeout" : result.status === "stopped" ? "aborted" : "budget_exhausted";
14754
14952
  const partial = result.status === "success" ? void 0 : await readSubagentPartial(opts, subagentId);
14755
14953
  const errorKind = result.error?.kind;
@@ -14922,7 +15120,9 @@ async function readSubagentPartial(opts, subagentId) {
14922
15120
  let toolUses = 0;
14923
15121
  for (const line of lines) {
14924
15122
  try {
14925
- const ev = JSON.parse(line);
15123
+ const parsed = safeParse(line);
15124
+ if (!parsed.ok || !parsed.value) continue;
15125
+ const ev = parsed.value;
14926
15126
  if (ev.type === "tool_use") toolUses += 1;
14927
15127
  if (ev.type === "llm_response") {
14928
15128
  if (typeof ev.stopReason === "string") lastStopReason = ev.stopReason;
@@ -15694,8 +15894,8 @@ async function loadProjectModes(modesDir) {
15694
15894
  for (const entry of entries) {
15695
15895
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
15696
15896
  const filePath = path3.join(modesDir, entry);
15697
- const stat5 = await fsp.stat(filePath);
15698
- if (!stat5.isFile()) continue;
15897
+ const stat6 = await fsp.stat(filePath);
15898
+ if (!stat6.isFile()) continue;
15699
15899
  const content = await fsp.readFile(filePath, "utf8");
15700
15900
  const id = path3.basename(entry, path3.extname(entry));
15701
15901
  modes.push({