@nathapp/nax 0.83.0-canary.1 → 0.83.0-canary.2

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 (2) hide show
  1. package/dist/nax.js +978 -365
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -2668,7 +2668,7 @@ var package_default;
2668
2668
  var init_package = __esm(() => {
2669
2669
  package_default = {
2670
2670
  name: "@nathapp/nax",
2671
- version: "0.83.0-canary.1",
2671
+ version: "0.83.0-canary.2",
2672
2672
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
2673
2673
  type: "module",
2674
2674
  bin: {
@@ -17962,7 +17962,8 @@ var init_schemas_sandbox = __esm(() => {
17962
17962
  backend: exports_external.enum(["srt"]).default("srt"),
17963
17963
  filesystem: exports_external.object({
17964
17964
  allowWrite: exports_external.array(literalPath).default([]),
17965
- denyRead: exports_external.array(literalPath).default([])
17965
+ denyRead: exports_external.array(literalPath).default([]),
17966
+ allowSharedTmp: exports_external.boolean().default(false)
17966
17967
  }).prefault({}),
17967
17968
  network: exports_external.object({
17968
17969
  allowedDomains: exports_external.array(exports_external.string()).optional()
@@ -18278,8 +18279,8 @@ function describeNetwork(network) {
18278
18279
  return "network access is disabled";
18279
18280
  return `network access is limited to ${network.join(", ")}`;
18280
18281
  }
18281
- function sandboxSentence(network) {
18282
- return "inside an OS sandbox: writes are allowed only under the repository root, the system temp directories and " + "package-manager caches -- and nothing under .nax/ except .nax/scratchpad/, since .nax/ is nax's own state " + "(change a feature's acceptance test with the Edit tool); credential files (~/.ssh, ~/.aws, ~/.npmrc, nax credentials and similar) are unreadable; " + `${describeNetwork(network)}. A write anywhere else fails with "Operation not permitted" or "Read-only file system" ` + "-- that is the sandbox, not a bug in your command.";
18282
+ function sandboxSentence(network, sharedTmp = true) {
18283
+ return `inside an OS sandbox: writes are allowed only under the repository root, ${tempRootsPhrase(sharedTmp)} and ` + "package-manager caches -- and nothing under .nax/ except .nax/scratchpad/, since .nax/ is nax's own state " + "(change a feature's acceptance test with the Edit tool); credential files (~/.ssh, ~/.aws, ~/.npmrc, nax credentials and similar) are unreadable; " + `${describeNetwork(network)}. A write anywhere else fails with "Operation not permitted" or "Read-only file system" ` + "-- that is the sandbox, not a bug in your command.";
18283
18284
  }
18284
18285
  function unsandboxedSentence(reason) {
18285
18286
  return `Commands are NOT sandboxed on this machine (${reason}).`;
@@ -18287,10 +18288,11 @@ function unsandboxedSentence(reason) {
18287
18288
  function rawBashRefusalReason(reason) {
18288
18289
  return `sandbox unavailable (${reason}): raw bash requires the sandbox when execution.sandbox.enabled is true -- ` + "set this stage's bashApproval to gated or escalate, or disable the sandbox.";
18289
18290
  }
18290
- function denialHintLine(writeRoots) {
18291
- return `note: this command ran in the nax sandbox; that failure may be a sandbox denial -- writable roots: ${writeRoots.join(", ")}.`;
18291
+ function denialHintLine(writeRoots, sharedTmp = false) {
18292
+ const tempClause = sharedTmp ? "" : " For temporary files use $TMPDIR or .nax/scratchpad/, not /tmp.";
18293
+ return `note: this command ran in the nax sandbox; that failure may be a sandbox denial -- writable roots: ${writeRoots.join(", ")}.` + tempClause;
18292
18294
  }
18293
- var LIKELY_SANDBOX_DENIAL;
18295
+ var LIKELY_SANDBOX_DENIAL, tempRootsPhrase = (sharedTmp) => sharedTmp ? "the system temp directories" : "this run's temp directory ($TMPDIR)";
18294
18296
  var init_messages = __esm(() => {
18295
18297
  LIKELY_SANDBOX_DENIAL = /Operation not permitted|Read-only file system/;
18296
18298
  });
@@ -18325,22 +18327,22 @@ async function ensureTmpDir(tmpDir) {
18325
18327
  return false;
18326
18328
  }
18327
18329
  }
18328
- async function runUnwrapped(req, sandbox, tmpDir) {
18330
+ async function runUnwrapped(req, sandbox, env2) {
18329
18331
  const argv = logicalArgv(req);
18330
- const env2 = withTmpEnv(req, tmpDir);
18332
+ const overlay = env2.tmpDir !== undefined ? withTmpEnv(req, env2.tmpDir) : req.env;
18331
18333
  const result = await _launcherDeps.runArgv({
18332
18334
  argv,
18333
18335
  cwd: req.cwd,
18334
18336
  timeoutMs: req.timeoutMs,
18335
18337
  stripEnvVars: [...req.stripEnvVars],
18336
- ...env2 !== undefined ? { env: env2 } : {},
18338
+ ...overlay !== undefined ? { env: overlay } : {},
18337
18339
  ...req.signal !== undefined ? { signal: req.signal } : {}
18338
18340
  });
18339
18341
  return { ...result, executed: argv, sandbox };
18340
18342
  }
18341
- async function runWrapped(req, backend, policy, tmpDir) {
18343
+ async function runWrapped(req, backend, policy, env2) {
18342
18344
  const rawCommand = req.spec.kind === "shell" ? req.spec.command : quoteArgvForShell(req.spec.argv);
18343
- const command = tmpDir !== undefined ? `${tmpEnvPrefix(tmpDir)}${rawCommand}` : rawCommand;
18345
+ const command = env2.tmpDir !== undefined ? `${tmpEnvPrefix(env2.tmpDir)}${rawCommand}` : rawCommand;
18344
18346
  const shell = req.spec.kind === "shell" ? req.spec.shell : "/bin/sh";
18345
18347
  const commandId = _launcherDeps.newCommandId();
18346
18348
  let argv;
@@ -18365,7 +18367,10 @@ async function runWrapped(req, backend, policy, tmpDir) {
18365
18367
  });
18366
18368
  const denied = result.exitCode !== 0 && LIKELY_SANDBOX_DENIAL.test(result.stderr);
18367
18369
  const violations = backend.annotate(commandId, result.stderr);
18368
- const extra = [...denied ? [denialHintLine(policy.writeRoots)] : [], ...violations !== "" ? [violations] : []];
18370
+ const extra = [
18371
+ ...denied ? [denialHintLine(policy.writeRoots, env2.sharedTmp)] : [],
18372
+ ...violations !== "" ? [violations] : []
18373
+ ];
18369
18374
  return {
18370
18375
  ...result,
18371
18376
  stderr: extra.length > 0 ? `${result.stderr}
@@ -18385,14 +18390,18 @@ ${extra.join(`
18385
18390
  }
18386
18391
  function createCommandLauncher(opts) {
18387
18392
  const { state } = opts;
18393
+ const env2 = {
18394
+ tmpDir: opts.tmpDir,
18395
+ sharedTmp: state.kind !== "available" || state.sharedTmp !== false
18396
+ };
18388
18397
  return {
18389
18398
  state,
18390
18399
  async run(req) {
18391
- const tmpDir = opts.tmpDir !== undefined && await ensureTmpDir(opts.tmpDir) ? opts.tmpDir : undefined;
18400
+ const tmpDir = env2.tmpDir !== undefined && await ensureTmpDir(env2.tmpDir) ? env2.tmpDir : undefined;
18392
18401
  if (state.kind === "disabled")
18393
- return runUnwrapped(req, { backend: "none", wrapped: false }, tmpDir);
18402
+ return runUnwrapped(req, { backend: "none", wrapped: false }, { ...env2, tmpDir });
18394
18403
  if (state.kind === "unavailable") {
18395
- return runUnwrapped(req, { backend: state.backend, wrapped: false, reason: state.reason }, tmpDir);
18404
+ return runUnwrapped(req, { backend: state.backend, wrapped: false, reason: state.reason }, { ...env2, tmpDir });
18396
18405
  }
18397
18406
  if (opts.backend === undefined || opts.policyFor === undefined) {
18398
18407
  throw new NaxError("[sandbox] an available launcher needs a backend and a policy", "SANDBOX_NOT_CONFIGURED", {
@@ -18400,7 +18409,7 @@ function createCommandLauncher(opts) {
18400
18409
  });
18401
18410
  }
18402
18411
  try {
18403
- return await runWrapped(req, opts.backend, await opts.policyFor(req.root), tmpDir);
18412
+ return await runWrapped(req, opts.backend, await opts.policyFor(req.root), { ...env2, tmpDir });
18404
18413
  } finally {
18405
18414
  await opts.afterWrapped?.();
18406
18415
  }
@@ -19239,7 +19248,11 @@ async function listCredentialFiles() {
19239
19248
  function defaultTempRoots() {
19240
19249
  return [_policyInputDeps.tmpdir(), "/tmp"];
19241
19250
  }
19242
- var GIT_LAYOUT_TIMEOUT_MS = 1e4, _policyInputDeps;
19251
+ function runTempRoots(opts) {
19252
+ const shared = SHARED_TMP_ROOTS.some((root) => opts.tmpdir === root || opts.tmpdir.startsWith(`${root}/`));
19253
+ return shared ? [opts.runTmpRoot] : [opts.tmpdir, opts.runTmpRoot];
19254
+ }
19255
+ var GIT_LAYOUT_TIMEOUT_MS = 1e4, _policyInputDeps, SHARED_TMP_ROOTS;
19243
19256
  var init_policy_inputs = __esm(() => {
19244
19257
  init_paths();
19245
19258
  init_schemas_sandbox();
@@ -19252,6 +19265,7 @@ var init_policy_inputs = __esm(() => {
19252
19265
  tmpdir,
19253
19266
  platform: () => process.platform
19254
19267
  };
19268
+ SHARED_TMP_ROOTS = ["/tmp", "/private/tmp"];
19255
19269
  });
19256
19270
 
19257
19271
  // src/sandbox/probe.ts
@@ -19442,16 +19456,45 @@ var init_registry = __esm(() => {
19442
19456
  });
19443
19457
 
19444
19458
  // src/sandbox/session-tmp.ts
19445
- function sanitizePart(part) {
19446
- return part.replace(/[^A-Za-z0-9_-]/g, "_");
19459
+ import { accessSync, constants, lstatSync } from "fs";
19460
+ function isUsableSharedParent() {
19461
+ try {
19462
+ const stats = _sessionTmpDeps.lstat(SHARED_TMP_PARENT);
19463
+ if (!stats.isDirectory() || stats.isSymbolicLink())
19464
+ return false;
19465
+ } catch (err) {
19466
+ return err.code === "ENOENT";
19467
+ }
19468
+ try {
19469
+ _sessionTmpDeps.access(SHARED_TMP_PARENT, constants.W_OK | constants.X_OK);
19470
+ return true;
19471
+ } catch {
19472
+ return false;
19473
+ }
19447
19474
  }
19448
- function runTmpRoot(runId) {
19449
- return `${RUN_TMP_PARENT}/nax-${sanitizePart(runId)}`;
19475
+ function tmpParent() {
19476
+ return isUsableSharedParent() ? SHARED_TMP_PARENT : `${TMP_ROOT}/nax-${_sessionTmpDeps.uid()}`;
19450
19477
  }
19451
- function sessionTmpDir(runId, sessionName) {
19452
- return `${runTmpRoot(runId)}/${sanitizePart(sessionName)}`;
19478
+ function sanitizePart(part) {
19479
+ return part.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/^\.+$/, "_");
19453
19480
  }
19454
- var RUN_TMP_PARENT = "/tmp";
19481
+ function runTmpRoot(runId) {
19482
+ return `${tmpParent()}/${sanitizePart(runId)}`;
19483
+ }
19484
+ function sessionTmpDirUnder(root, sessionName) {
19485
+ return `${root}/${sanitizePart(sessionName)}`;
19486
+ }
19487
+ var TMP_ROOT = "/tmp", SHARED_TMP_PARENT, _sessionTmpDeps;
19488
+ var init_session_tmp = __esm(() => {
19489
+ SHARED_TMP_PARENT = `${TMP_ROOT}/nax`;
19490
+ _sessionTmpDeps = {
19491
+ lstat: (path) => lstatSync(path),
19492
+ access: (path, mode) => {
19493
+ accessSync(path, mode);
19494
+ },
19495
+ uid: () => process.getuid?.() ?? 0
19496
+ };
19497
+ });
19455
19498
 
19456
19499
  // src/sandbox/index.ts
19457
19500
  var init_sandbox = __esm(() => {
@@ -19462,6 +19505,7 @@ var init_sandbox = __esm(() => {
19462
19505
  init_policy_inputs();
19463
19506
  init_probe();
19464
19507
  init_registry();
19508
+ init_session_tmp();
19465
19509
  init_srt_backend();
19466
19510
  });
19467
19511
 
@@ -19645,14 +19689,14 @@ function bashToolDescription(shell, opts) {
19645
19689
  const state = opts.launcher?.state ?? { kind: "disabled" };
19646
19690
  if (opts.bashApproval === "raw") {
19647
19691
  if (state.kind === "available")
19648
- return rawDescription(shell, `The command runs from the repository root ${sandboxSentence(state.network)} `);
19692
+ return rawDescription(shell, `The command runs from the repository root ${sandboxSentence(state.network, state.sharedTmp !== false)} `);
19649
19693
  if (state.kind === "unavailable")
19650
19694
  return rawUnavailableDescription(shell, state.reason);
19651
19695
  return rawDescription(shell);
19652
19696
  }
19653
19697
  const policyDescription = opts.bashApproval === "escalate" && opts.humanApproval === true ? escalateDescription(shell, opts.patterns) : gatedDescription(shell, opts.patterns);
19654
19698
  if (state.kind === "available") {
19655
- return `${policyDescription} Commands that pass run ${sandboxSentence(state.network)}`;
19699
+ return `${policyDescription} Commands that pass run ${sandboxSentence(state.network, state.sharedTmp !== false)}`;
19656
19700
  }
19657
19701
  if (state.kind === "unavailable")
19658
19702
  return `${policyDescription} ${unsandboxedSentence(state.reason)}`;
@@ -23681,7 +23725,7 @@ function otherCommandsDeclaring(declared, key, currentCommand) {
23681
23725
  function execSandboxNote(launcher) {
23682
23726
  const state = launcher?.state;
23683
23727
  if (state?.kind === "available")
23684
- return ` An "argv" call runs ${sandboxSentence(state.network)}`;
23728
+ return ` An "argv" call runs ${sandboxSentence(state.network, state.sharedTmp !== false)}`;
23685
23729
  if (state?.kind === "unavailable")
23686
23730
  return ` ${unsandboxedSentence(state.reason)}`;
23687
23731
  return "";
@@ -24009,11 +24053,14 @@ function resolveTarget(target, frame) {
24009
24053
  return posix2.normalize(target.text);
24010
24054
  return frame === undefined ? undefined : posix2.normalize(posix2.join(frame, target.text));
24011
24055
  }
24056
+ function isNaxTempTree(path, root) {
24057
+ return path.startsWith(`${root}/${NAX_PARENT_SEGMENT}/`) || path.startsWith(`${root}/${NAX_FALLBACK_SEGMENT}`);
24058
+ }
24012
24059
  function isTmpPath(path) {
24013
24060
  for (const root of TEMP_ROOTS) {
24014
24061
  if (path !== root && !path.startsWith(`${root}/`))
24015
24062
  continue;
24016
- return !path.startsWith(`${root}/${NAX_PREFIX}`);
24063
+ return !isNaxTempTree(path, root);
24017
24064
  }
24018
24065
  return false;
24019
24066
  }
@@ -24037,7 +24084,7 @@ function detectTmpWrite(command, cwd) {
24037
24084
  }
24038
24085
  return false;
24039
24086
  }
24040
- var TEMP_ROOTS, NAX_PREFIX = "nax-", ARG_TARGET_COMMANDS, LAST_TARGET_COMMANDS;
24087
+ var TEMP_ROOTS, NAX_PARENT_SEGMENT = "nax", NAX_FALLBACK_SEGMENT = "nax-", ARG_TARGET_COMMANDS, LAST_TARGET_COMMANDS;
24041
24088
  var init_tmp_write = __esm(() => {
24042
24089
  init_permissions();
24043
24090
  TEMP_ROOTS = ["/tmp", "/private/tmp"];
@@ -26494,7 +26541,7 @@ var init_package_config_cache = __esm(() => {
26494
26541
  });
26495
26542
 
26496
26543
  // src/config/path-security.ts
26497
- import { existsSync as existsSync4, lstatSync, realpathSync as realpathSync2 } from "fs";
26544
+ import { existsSync as existsSync4, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
26498
26545
  import { basename as basename3, isAbsolute as isAbsolute8, normalize, relative as relative9, resolve as resolve15 } from "path";
26499
26546
  function validateDirectory(dirPath, baseDir) {
26500
26547
  const resolved = resolve15(dirPath);
@@ -26516,7 +26563,7 @@ function validateDirectory(dirPath, baseDir) {
26516
26563
  }
26517
26564
  let isDirectory;
26518
26565
  try {
26519
- isDirectory = lstatSync(realPath).isDirectory();
26566
+ isDirectory = lstatSync2(realPath).isDirectory();
26520
26567
  } catch (error48) {
26521
26568
  throw new NaxError(`Failed to stat path: ${dirPath} (${errorMessage(error48)})`, "PATH_DIRECTORY_STAT_FAILED", {
26522
26569
  stage: "config",
@@ -30479,6 +30526,14 @@ function buildToolResult(args) {
30479
30526
  };
30480
30527
  }
30481
30528
 
30529
+ // src/utils/thenable.ts
30530
+ function isThenable(value) {
30531
+ if (value === null)
30532
+ return false;
30533
+ const type = typeof value;
30534
+ return (type === "object" || type === "function") && typeof value.then === "function";
30535
+ }
30536
+
30482
30537
  // src/agents/native/session/loop-events/payload-guard.ts
30483
30538
  function snapshotArrays(payload) {
30484
30539
  return Object.entries(payload).flatMap(([field, value]) => Array.isArray(value) ? [{ field, array: value, saved: [...value] }] : []);
@@ -30513,18 +30568,43 @@ function pickPatchFields(returned, fields) {
30513
30568
  }
30514
30569
  return picked;
30515
30570
  }
30571
+ function isBeforeToolOutcome(value) {
30572
+ if (typeof value !== "object" || value === null)
30573
+ return false;
30574
+ const kind = value.kind;
30575
+ return typeof kind === "string" && BEFORE_TOOL_KINDS.includes(kind);
30576
+ }
30577
+ function isCompleteBeforeToolOutcome(value) {
30578
+ if (!isBeforeToolOutcome(value))
30579
+ return false;
30580
+ const outcome = value;
30581
+ if (value.kind === "block" || value.kind === "terminate")
30582
+ return typeof outcome.content === "string";
30583
+ if (value.kind === "nudge")
30584
+ return typeof outcome.text === "string";
30585
+ return true;
30586
+ }
30587
+ function asOutcome(returned, tool) {
30588
+ if (isBeforeToolOutcome(returned))
30589
+ return returned;
30590
+ getSafeLogger()?.warn("native-loop-events", "before_tool handler answered no valid outcome; treating it as allow", {
30591
+ tool
30592
+ });
30593
+ return { kind: "allow" };
30594
+ }
30516
30595
  async function dispatchBeforeTool(handlers, payload) {
30517
30596
  const { call, tools } = payload;
30518
30597
  let input;
30519
30598
  let nudgeText2;
30520
30599
  for (const [index, handler] of handlers.entries()) {
30521
- let outcome;
30600
+ let returned;
30522
30601
  const snapshot = snapshotArrays({ tools });
30523
30602
  try {
30524
- outcome = await handler({
30603
+ const candidate = handler({
30525
30604
  call: { ...call, input: input ?? call.input },
30526
30605
  tools
30527
30606
  });
30607
+ returned = isThenable(candidate) ? await candidate : candidate;
30528
30608
  } catch (err) {
30529
30609
  getSafeLogger()?.warn("native-loop-events", "before_tool handler threw; skipping it", {
30530
30610
  tool: call.name,
@@ -30534,6 +30614,7 @@ async function dispatchBeforeTool(handlers, payload) {
30534
30614
  } finally {
30535
30615
  restoreMutated(snapshot, "before_tool", index);
30536
30616
  }
30617
+ const outcome = asOutcome(returned, call.name);
30537
30618
  if (outcome.kind === "block" || outcome.kind === "terminate")
30538
30619
  return outcome;
30539
30620
  if (outcome.kind === "nudge")
@@ -30599,7 +30680,7 @@ function createLoopEventRegistry() {
30599
30680
  }
30600
30681
  };
30601
30682
  }
30602
- var PATCHABLE_FIELDS;
30683
+ var PATCHABLE_FIELDS, LOOP_EVENTS, BEFORE_TOOL_KINDS;
30603
30684
  var init_registry3 = __esm(() => {
30604
30685
  init_logger2();
30605
30686
  init_payload_guard();
@@ -30613,10 +30694,123 @@ var init_registry3 = __esm(() => {
30613
30694
  before_compaction: ["decline", "summary"],
30614
30695
  before_turn_end: ["followUp"]
30615
30696
  };
30697
+ LOOP_EVENTS = Object.freeze(Object.keys(PATCHABLE_FIELDS));
30698
+ BEFORE_TOOL_KINDS = ["allow", "nudge", "block", "terminate"];
30699
+ });
30700
+
30701
+ // src/agents/native/session/loop-events/external-handler.ts
30702
+ function raceSettlement(entry, payload, deps) {
30703
+ const timeoutMs = _externalHandlerDeps.timeoutMs;
30704
+ return new Promise((resolve17) => {
30705
+ let settled = false;
30706
+ let timer;
30707
+ const finish = (result) => {
30708
+ if (settled)
30709
+ return;
30710
+ settled = true;
30711
+ if (timer !== undefined)
30712
+ clearTimeout(timer);
30713
+ deps.signal.removeEventListener("abort", onAbort);
30714
+ resolve17(result);
30715
+ };
30716
+ const onAbort = () => finish({ ok: false, reason: "aborted before it settled" });
30717
+ timer = setTimeout(() => finish({ ok: false, reason: `did not settle within ${timeoutMs}ms` }), timeoutMs);
30718
+ deps.signal.addEventListener("abort", onAbort, { once: true });
30719
+ let ctx;
30720
+ try {
30721
+ ctx = deps.getCtx();
30722
+ } catch (err) {
30723
+ finish({ ok: false, reason: `reading the handler context failed: ${errorMessage(err)}` });
30724
+ return;
30725
+ }
30726
+ let returned;
30727
+ try {
30728
+ returned = entry.handler(payload, ctx);
30729
+ } catch (err) {
30730
+ finish({ ok: false, reason: errorMessage(err) });
30731
+ return;
30732
+ }
30733
+ Promise.resolve(returned).then((value) => finish({ ok: true, value }), (err) => finish({ ok: false, reason: errorMessage(err) }));
30734
+ });
30735
+ }
30736
+ function failure(entry, payload, reason) {
30737
+ const logger = getSafeLogger();
30738
+ if (entry.event === "before_tool") {
30739
+ const tool = payload?.call?.name;
30740
+ logger?.warn("native-loop-events", `plugin '${entry.plugin}' failed on before_tool; blocking the call`, {
30741
+ plugin: entry.plugin,
30742
+ event: entry.event,
30743
+ ...tool !== undefined ? { tool } : {},
30744
+ error: reason
30745
+ });
30746
+ return {
30747
+ kind: "block",
30748
+ isError: true,
30749
+ content: `Blocked: loop handler from plugin '${entry.plugin}' failed (${reason}).`
30750
+ };
30751
+ }
30752
+ logger?.warn("native-loop-events", `plugin '${entry.plugin}' failed on ${entry.event}`, {
30753
+ plugin: entry.plugin,
30754
+ event: entry.event,
30755
+ error: reason
30756
+ });
30757
+ return {};
30758
+ }
30759
+ function tracePatch(entry, value) {
30760
+ if (typeof value !== "object" || value === null)
30761
+ return;
30762
+ const patch = value;
30763
+ const fields = TRACED_PATCH_FIELDS.filter((field) => patch[field] !== undefined);
30764
+ if (fields.length === 0)
30765
+ return;
30766
+ getSafeLogger()?.debug("native-loop-events", `plugin '${entry.plugin}' patched ${entry.event}`, {
30767
+ plugin: entry.plugin,
30768
+ event: entry.event,
30769
+ fields
30770
+ });
30771
+ }
30772
+ function describeValue(value) {
30773
+ if (value === null)
30774
+ return "null";
30775
+ if (Array.isArray(value))
30776
+ return "an array";
30777
+ if (typeof value === "object")
30778
+ return "an object whose kind is not a before_tool outcome";
30779
+ return `a ${typeof value} value`;
30780
+ }
30781
+ function settled(entry, payload, value) {
30782
+ if (entry.event === "before_tool") {
30783
+ if (value === undefined)
30784
+ return { kind: "allow" };
30785
+ if (!isCompleteBeforeToolOutcome(value))
30786
+ return failure(entry, payload, `returned ${describeValue(value)}`);
30787
+ return value;
30788
+ }
30789
+ if (value === undefined)
30790
+ return {};
30791
+ tracePatch(entry, value);
30792
+ return value;
30793
+ }
30794
+ function wrapExternalHandler(entry, getCtx, signal) {
30795
+ return async (payload) => {
30796
+ const settlement = await raceSettlement(entry, payload, { getCtx, signal });
30797
+ const answer = settlement.ok ? settled(entry, payload, settlement.value) : failure(entry, payload, settlement.reason);
30798
+ return answer;
30799
+ };
30800
+ }
30801
+ var LOOP_HANDLER_TIMEOUT_MS = 1e4, _externalHandlerDeps, TRACED_PATCH_FIELDS;
30802
+ var init_external_handler = __esm(() => {
30803
+ init_logger2();
30804
+ init_registry3();
30805
+ _externalHandlerDeps = {
30806
+ timeoutMs: LOOP_HANDLER_TIMEOUT_MS
30807
+ };
30808
+ TRACED_PATCH_FIELDS = ["seed", "history", "messages", "summary"];
30616
30809
  });
30617
30810
 
30618
30811
  // src/agents/native/session/loop-events/index.ts
30619
30812
  var init_loop_events = __esm(() => {
30813
+ init_external_handler();
30620
30814
  init_registry3();
30621
30815
  });
30622
30816
 
@@ -30696,6 +30890,16 @@ var init_truncation_handler = __esm(() => {
30696
30890
  });
30697
30891
 
30698
30892
  // src/agents/native/session/loop-handlers.ts
30893
+ function contextFor(state) {
30894
+ return state.current.loopHandlerContext ?? { sessionName: state.current.sessionName };
30895
+ }
30896
+ function turnSignalView(state) {
30897
+ const current = () => state.current.signal ?? NEVER_ABORTED;
30898
+ return {
30899
+ addEventListener: (...args) => current().addEventListener(...args),
30900
+ removeEventListener: (...args) => current().removeEventListener(...args)
30901
+ };
30902
+ }
30699
30903
  function registerBuiltinLoopHandlers(registry3, deps) {
30700
30904
  const installed = turnStateByRegistry.get(registry3);
30701
30905
  if (installed !== undefined) {
@@ -30706,6 +30910,9 @@ function registerBuiltinLoopHandlers(registry3, deps) {
30706
30910
  turnStateByRegistry.set(registry3, state);
30707
30911
  registry3.register("before_tool", ({ call, tools }) => repairInvalidCall(state, call, tools));
30708
30912
  registry3.register("before_tool", ({ call }) => observeSpin(state, call));
30913
+ for (const entry of deps.loopHandlers ?? []) {
30914
+ registry3.register(entry.event, wrapExternalHandler(entry, () => contextFor(state), turnSignalView(state)));
30915
+ }
30709
30916
  registry3.register("after_tool", (payload) => createTruncationHandler(state.current.sessionName)(payload));
30710
30917
  }
30711
30918
  function repairInvalidCall(state, call, tools) {
@@ -30732,11 +30939,13 @@ function observeSpin(state, call) {
30732
30939
  return { kind: "nudge", text: verdict.text };
30733
30940
  return { kind: "allow" };
30734
30941
  }
30735
- var turnStateByRegistry;
30942
+ var NEVER_ABORTED, turnStateByRegistry;
30736
30943
  var init_loop_handlers = __esm(() => {
30737
30944
  init_spin_breaker();
30738
30945
  init_handle_invalid_tool_call();
30946
+ init_loop_events();
30739
30947
  init_truncation_handler();
30948
+ NEVER_ABORTED = new AbortController().signal;
30740
30949
  turnStateByRegistry = new WeakMap;
30741
30950
  });
30742
30951
 
@@ -31704,6 +31913,9 @@ async function runNativeTurn(handle, prompt, opts, deps) {
31704
31913
  sessionName: handle.id,
31705
31914
  budget: invalidCallBudget,
31706
31915
  ...deps.spinBreaker !== undefined ? { spinBreaker: deps.spinBreaker } : {},
31916
+ ...deps.loopHandlers !== undefined ? { loopHandlers: deps.loopHandlers } : {},
31917
+ ...deps.loopHandlerContext !== undefined ? { loopHandlerContext: deps.loopHandlerContext } : {},
31918
+ ...deps.signal !== undefined ? { signal: deps.signal } : {},
31707
31919
  onSpinStop: () => {
31708
31920
  spinFlags.warned = true;
31709
31921
  }
@@ -31854,6 +32066,12 @@ An earlier summary of still-older history follows. Merge it into your summary `
31854
32066
 
31855
32067
  ${previousSummary}`;
31856
32068
  }
32069
+ function loopHandlerDeps(opts) {
32070
+ return {
32071
+ ...opts.loopHandlers !== undefined ? { loopHandlers: opts.loopHandlers } : {},
32072
+ ...opts.loopHandlerContext !== undefined ? { loopHandlerContext: opts.loopHandlerContext } : {}
32073
+ };
32074
+ }
31857
32075
 
31858
32076
  class NativeAgentAdapter {
31859
32077
  catalogOverrides;
@@ -31983,6 +32201,7 @@ class NativeAgentAdapter {
31983
32201
  ...nativeSessionTransportRetry.get(handle.id) !== undefined ? { transportRetry: nativeSessionTransportRetry.get(handle.id) } : {},
31984
32202
  ...nativeSessionSpinBreaker.get(handle.id) !== undefined ? { spinBreaker: nativeSessionSpinBreaker.get(handle.id) } : {},
31985
32203
  ...opts.loopEvents !== undefined ? { loopEvents: opts.loopEvents } : {},
32204
+ ...loopHandlerDeps(opts),
31986
32205
  signal: turnSignal,
31987
32206
  onWaiting: () => {},
31988
32207
  pricingSource,
@@ -35439,7 +35658,7 @@ function killProcessTree(pid, graceMs, exited) {
35439
35658
  killProcessGroup(pid, "SIGTERM");
35440
35659
  getSafeLogger()?.debug("acp-adapter", `Sent SIGTERM to process group ${pid}`);
35441
35660
  } catch {}
35442
- let settled = false;
35661
+ let settled2 = false;
35443
35662
  const timer = { id: undefined };
35444
35663
  const gracePromise = new Promise((resolve17) => {
35445
35664
  timer.id = setTimeout(() => resolve17("grace"), graceMs);
@@ -35450,9 +35669,9 @@ function killProcessTree(pid, graceMs, exited) {
35450
35669
  const raced = exitedPromise !== undefined ? Promise.race([gracePromise, exitedPromise]) : gracePromise;
35451
35670
  raced.then((outcome) => {
35452
35671
  clearTimeout(timer.id);
35453
- if (settled)
35672
+ if (settled2)
35454
35673
  return;
35455
- settled = true;
35674
+ settled2 = true;
35456
35675
  activeKillTimers.delete(pid);
35457
35676
  if (outcome === "exited")
35458
35677
  return;
@@ -35464,7 +35683,7 @@ function killProcessTree(pid, graceMs, exited) {
35464
35683
  });
35465
35684
  const handle = {
35466
35685
  cancel: () => {
35467
- settled = true;
35686
+ settled2 = true;
35468
35687
  clearTimeout(timer.id);
35469
35688
  activeKillTimers.delete(pid);
35470
35689
  }
@@ -37422,13 +37641,13 @@ var init_interaction_handler = __esm(() => {
37422
37641
  function failurePolicyFor(outcome) {
37423
37642
  return POLICIES[outcome];
37424
37643
  }
37425
- function resolveCooldownExpiry(failure, now) {
37426
- const { cooldown } = failurePolicyFor(failure.outcome);
37644
+ function resolveCooldownExpiry(failure2, now) {
37645
+ const { cooldown } = failurePolicyFor(failure2.outcome);
37427
37646
  if (cooldown === "none")
37428
37647
  return null;
37429
37648
  if (cooldown === "run")
37430
37649
  return "run";
37431
- const retryAfter = failure.retryAfterSeconds;
37650
+ const retryAfter = failure2.retryAfterSeconds;
37432
37651
  const fromProvider = retryAfter !== undefined && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter * 1000 : undefined;
37433
37652
  return now + (fromProvider ?? cooldown.ms);
37434
37653
  }
@@ -37530,10 +37749,10 @@ var init_failure_policy = __esm(() => {
37530
37749
  });
37531
37750
 
37532
37751
  // src/agents/swap-decision.ts
37533
- function decideSwap(failure, hopsSoFar, fallback) {
37534
- if (!failure)
37752
+ function decideSwap(failure2, hopsSoFar, fallback) {
37753
+ if (!failure2)
37535
37754
  return { swap: false, reason: "no-failure" };
37536
- const policy = failurePolicyFor(failure.outcome);
37755
+ const policy = failurePolicyFor(failure2.outcome);
37537
37756
  if (policy.swap === "never")
37538
37757
  return { swap: false, reason: "outcome-refused" };
37539
37758
  if (!fallback?.enabled)
@@ -37617,12 +37836,12 @@ var MAX_RETRIES = 3, defaultRetryStrategy;
37617
37836
  var init_default_strategy = __esm(() => {
37618
37837
  init_failure_policy();
37619
37838
  defaultRetryStrategy = {
37620
- shouldRetry(failure, attempt, _ctx) {
37839
+ shouldRetry(failure2, attempt, _ctx) {
37621
37840
  if (attempt >= MAX_RETRIES)
37622
37841
  return { retry: false };
37623
- if (failure instanceof Error)
37842
+ if (failure2 instanceof Error)
37624
37843
  return { retry: false };
37625
- const af = failure;
37844
+ const af = failure2;
37626
37845
  if (af.retriable === false)
37627
37846
  return { retry: false };
37628
37847
  if (!failurePolicyFor(af.outcome).terminalBackoff)
@@ -38569,6 +38788,9 @@ function createTestFileClassifier(resolved) {
38569
38788
  }
38570
38789
 
38571
38790
  // src/test-runners/compile-crash.ts
38791
+ function isCommandNotRunnable(exitCode) {
38792
+ return exitCode === 126 || exitCode === 127;
38793
+ }
38572
38794
  function isGoMissingSymbol(message) {
38573
38795
  const msg = message.trim();
38574
38796
  if (msg.startsWith("undefined: "))
@@ -38737,11 +38959,11 @@ async function listFilesInDir(workdir, dir) {
38737
38959
  const stdoutPromise = new Response(proc.stdout).text().catch(() => "");
38738
38960
  const stderrPromise = new Response(proc.stderr).text().catch(() => "");
38739
38961
  const exitCode = await new Promise((resolve17) => {
38740
- let settled = false;
38962
+ let settled2 = false;
38741
38963
  const finish = (code) => {
38742
- if (settled)
38964
+ if (settled2)
38743
38965
  return;
38744
- settled = true;
38966
+ settled2 = true;
38745
38967
  clearTimeout(timer);
38746
38968
  resolve17(code);
38747
38969
  };
@@ -38853,11 +39075,11 @@ async function gitLsFiles(workdir) {
38853
39075
  const stdoutPromise = new Response(proc.stdout).text().catch(() => "");
38854
39076
  const stderrPromise = new Response(proc.stderr).text().catch(() => "");
38855
39077
  const exitCode = await new Promise((resolve17) => {
38856
- let settled = false;
39078
+ let settled2 = false;
38857
39079
  const finish = (code) => {
38858
- if (settled)
39080
+ if (settled2)
38859
39081
  return;
38860
- settled = true;
39082
+ settled2 = true;
38861
39083
  clearTimeout(timer);
38862
39084
  resolve17(code);
38863
39085
  };
@@ -40114,11 +40336,11 @@ function parsePytestOutput(output) {
40114
40336
  }
40115
40337
  failures.push(...errorByFile.values());
40116
40338
  const verboseStacks = parsePytestVerboseStacks(output);
40117
- for (const failure of failures) {
40118
- const leafName = failure.testName.split(" > ").pop() ?? failure.testName;
40119
- const stack = verboseStacks.get(leafName) ?? verboseStacks.get(failure.testName) ?? verboseStacks.get(failure.testName.replace(/ > /g, "."));
40339
+ for (const failure2 of failures) {
40340
+ const leafName = failure2.testName.split(" > ").pop() ?? failure2.testName;
40341
+ const stack = verboseStacks.get(leafName) ?? verboseStacks.get(failure2.testName) ?? verboseStacks.get(failure2.testName.replace(/ > /g, "."));
40120
40342
  if (stack && stack.length > 0) {
40121
- failure.stackTrace = stack;
40343
+ failure2.stackTrace = stack;
40122
40344
  }
40123
40345
  }
40124
40346
  return {
@@ -40229,11 +40451,11 @@ function formatFailureSummary(failures, maxChars = 2000) {
40229
40451
  const lines = [];
40230
40452
  let totalChars = 0;
40231
40453
  for (let i = 0;i < failures.length; i++) {
40232
- const failure = failures[i];
40454
+ const failure2 = failures[i];
40233
40455
  const num = i + 1;
40234
- const header = `${num}. ${failure.file} > ${failure.testName}`;
40235
- const errorLine = ` Error: ${failure.error}`;
40236
- const stackTrace = failure.stackTrace ?? [];
40456
+ const header = `${num}. ${failure2.file} > ${failure2.testName}`;
40457
+ const errorLine = ` Error: ${failure2.error}`;
40458
+ const stackTrace = failure2.stackTrace ?? [];
40237
40459
  const stackLine = stackTrace.length > 0 ? ` ${stackTrace[0]}` : "";
40238
40460
  const blockLines = [header, errorLine];
40239
40461
  if (stackLine)
@@ -40971,6 +41193,7 @@ __export(exports_test_runners, {
40971
41193
  formatFailureSummary: () => formatFailureSummary,
40972
41194
  globsToPathspec: () => globsToPathspec,
40973
41195
  globsToTestRegex: () => globsToTestRegex,
41196
+ isCommandNotRunnable: () => isCommandNotRunnable,
40974
41197
  isMonorepoOrchestratorCommand: () => isMonorepoOrchestratorCommand,
40975
41198
  isTestFile: () => isTestFile,
40976
41199
  isTestFileByPatterns: () => isTestFileByPatterns,
@@ -41835,17 +42058,17 @@ var init_lint = __esm(() => {
41835
42058
  init_path_utils();
41836
42059
  });
41837
42060
  // src/findings/adapters/test-failure.ts
41838
- function testFailureToFinding(failure) {
41839
- const frames = (failure.stackTrace ?? []).slice(0, MAX_FRAMES_IN_MESSAGE);
41840
- const message = frames.length > 0 ? `${failure.error}
42061
+ function testFailureToFinding(failure2) {
42062
+ const frames = (failure2.stackTrace ?? []).slice(0, MAX_FRAMES_IN_MESSAGE);
42063
+ const message = frames.length > 0 ? `${failure2.error}
41841
42064
  ${frames.join(`
41842
- `)}` : failure.error;
42065
+ `)}` : failure2.error;
41843
42066
  return {
41844
42067
  source: "test-runner",
41845
42068
  severity: "error",
41846
42069
  category: "failed-test",
41847
- rule: failure.testName,
41848
- file: failure.file,
42070
+ rule: failure2.testName,
42071
+ file: failure2.file,
41849
42072
  message
41850
42073
  };
41851
42074
  }
@@ -42747,11 +42970,15 @@ async function validateRecordAndDecide(frame, state, it) {
42747
42970
  break;
42748
42971
  } catch (err) {
42749
42972
  if (validatorAttempt >= cycle.config.validatorRetries) {
42973
+ const iterationCostUsd2 = fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
42974
+ state.totalCostUsd += iterationCostUsd2;
42750
42975
  logger?.error("findings.cycle", "cycle exited \u2014 validator error", {
42751
42976
  storyId: logCtx.storyId,
42752
42977
  packageDir: logCtx.packageDir,
42753
42978
  cycleName: logCtx.cycleName,
42754
42979
  reason: "validator-error",
42980
+ strategiesRun: group.map((s) => s.name),
42981
+ iterationCostUsd: iterationCostUsd2,
42755
42982
  error: errorMessage(err)
42756
42983
  });
42757
42984
  return {
@@ -43662,8 +43889,12 @@ To try a snippet that imports project code or dependencies, write it under \`${d
43662
43889
  project's package names or path aliases where it has them. A script written outside the repository,
43663
43890
  such as in \`/tmp\`, cannot resolve the project's modules, and the file tools cannot write there.
43664
43891
 
43665
- Shell commands run with \`$TMPDIR\` set to a temp directory for this session, which nax deletes when
43666
- the run ends. Put temporary files there (\`$TMPDIR\` or \`mktemp\`), not in \`/tmp\` directly.
43892
+ Use the scratchpad as your temp folder. Files you make while working \u2014 command output you want to
43893
+ re-read, generated inputs, one-off scripts \u2014 go under \`${dir}\`: the shell, the file tools
43894
+ and the scratchpad tools can all reach it, and it is cleared after the run. For throwaway files a
43895
+ command creates on its own, shell commands run with \`$TMPDIR\` set to this run's temp directory; use
43896
+ \`$TMPDIR\` or \`mktemp\` for those. Never write to \`/tmp\` directly: in the sandbox, \`/tmp\` outside
43897
+ \`$TMPDIR\` is not writable and the write fails.
43667
43898
 
43668
43899
  \`${dir}\` is the one directory under \`.nax/\` you may write to. Every other path under \`.nax/\`
43669
43900
  must still never be moved, renamed, or deleted.`;
@@ -47167,18 +47398,18 @@ function formatPriorFailures(failures) {
47167
47398
  return "";
47168
47399
  }
47169
47400
  const parts = [];
47170
- for (const failure of failures) {
47171
- parts.push(`### Attempt ${failure.attempt} \u2014 ${failure.modelTier}`);
47172
- if (failure.agent) {
47173
- const profilePart = failure.agentProfileId ? ` (profile: ${failure.agentProfileId})` : "";
47174
- parts.push(`**Agent:** ${failure.agent}${profilePart}`);
47175
- }
47176
- parts.push(`**Stage:** ${failure.stage}`);
47177
- parts.push(`**Summary:** ${failure.summary}`);
47178
- if (failure.testFailures && failure.testFailures.length > 0) {
47401
+ for (const failure2 of failures) {
47402
+ parts.push(`### Attempt ${failure2.attempt} \u2014 ${failure2.modelTier}`);
47403
+ if (failure2.agent) {
47404
+ const profilePart = failure2.agentProfileId ? ` (profile: ${failure2.agentProfileId})` : "";
47405
+ parts.push(`**Agent:** ${failure2.agent}${profilePart}`);
47406
+ }
47407
+ parts.push(`**Stage:** ${failure2.stage}`);
47408
+ parts.push(`**Summary:** ${failure2.summary}`);
47409
+ if (failure2.testFailures && failure2.testFailures.length > 0) {
47179
47410
  parts.push(`
47180
47411
  **Test Failures:**`);
47181
- for (const testFailure of failure.testFailures) {
47412
+ for (const testFailure of failure2.testFailures) {
47182
47413
  parts.push(`
47183
47414
  - **File:** \`${testFailure.file}\``);
47184
47415
  parts.push(` **Test:** ${testFailure.testName}`);
@@ -47188,10 +47419,10 @@ function formatPriorFailures(failures) {
47188
47419
  }
47189
47420
  }
47190
47421
  }
47191
- if (failure.reviewFindings && failure.reviewFindings.length > 0) {
47422
+ if (failure2.reviewFindings && failure2.reviewFindings.length > 0) {
47192
47423
  parts.push(`
47193
47424
  **Review Findings (fix these issues):**`);
47194
- for (const finding of failure.reviewFindings) {
47425
+ for (const finding of failure2.reviewFindings) {
47195
47426
  const source = finding.source ? ` (${finding.source})` : "";
47196
47427
  const loc = finding.file ? `${finding.file}:${finding.line ?? 0}` : "global";
47197
47428
  parts.push(`
@@ -50179,6 +50410,37 @@ function decideReuse(live, descriptor, requested) {
50179
50410
  return sameEndpoint(live.modelDef, requested.modelDef) ? "reuse" : "close-then-reopen";
50180
50411
  }
50181
50412
 
50413
+ // src/session/loop-handler-forwarding.ts
50414
+ function buildLoopHandlerTurnOpts(input) {
50415
+ if (input.set.length === 0 || input.handle.agentName !== NATIVE_AGENT_NAME) {
50416
+ return {};
50417
+ }
50418
+ return {
50419
+ loopHandlers: input.set,
50420
+ loopHandlerContext: buildLoopHandlerContext(input.handle, input.descriptor)
50421
+ };
50422
+ }
50423
+ function buildLoopHandlerContext(handle, descriptor) {
50424
+ const role = descriptor ? descriptor.role : handle.role;
50425
+ const modelDef = handle.modelDef;
50426
+ return Object.freeze({
50427
+ sessionName: handle.id,
50428
+ ...role !== undefined ? { role } : {},
50429
+ ...descriptor?.storyId !== undefined ? { storyId: descriptor.storyId } : {},
50430
+ ...descriptor?.featureName !== undefined ? { feature: descriptor.featureName } : {},
50431
+ ...descriptor?.workdir !== undefined ? { workdir: descriptor.workdir } : {},
50432
+ ...modelDef?.model !== undefined ? { model: modelDef.model } : {},
50433
+ ...modelDef?.provider !== undefined ? { provider: modelDef.provider } : {}
50434
+ });
50435
+ }
50436
+ function shouldLogNativeOnlyScope(set2, handle, alreadyLogged) {
50437
+ return !alreadyLogged && set2.length > 0 && handle.agentName !== NATIVE_AGENT_NAME;
50438
+ }
50439
+ var LOOP_HANDLERS_NATIVE_ONLY_MESSAGE = "loop handlers apply to the native agent only";
50440
+ var init_loop_handler_forwarding = __esm(() => {
50441
+ init_config();
50442
+ });
50443
+
50182
50444
  // src/session/manager-deps.ts
50183
50445
  import { randomUUID as randomUUID8 } from "crypto";
50184
50446
  import { mkdir as mkdir12 } from "fs/promises";
@@ -50202,9 +50464,23 @@ function deriveNativeTranscriptDir(opts) {
50202
50464
  return;
50203
50465
  return join34(opts.transcriptRoot, "features", opts.featureName, "sessions");
50204
50466
  }
50467
+ function persistDescriptor(descriptor) {
50468
+ if (!descriptor.scratchDir)
50469
+ return;
50470
+ const projectDir = resolveProjectDirFromScratchDir(descriptor.scratchDir);
50471
+ _sessionManagerDeps.writeDescriptor(descriptor.scratchDir, descriptor, projectDir).catch((err) => {
50472
+ getLogger().warn("session", "Failed to re-persist session descriptor", {
50473
+ storyId: descriptor.storyId,
50474
+ sessionId: descriptor.id,
50475
+ scratchDir: descriptor.scratchDir,
50476
+ error: err instanceof Error ? err.message : String(err)
50477
+ });
50478
+ });
50479
+ }
50205
50480
  var _sessionManagerDeps;
50206
50481
  var init_manager_deps = __esm(() => {
50207
50482
  init_config();
50483
+ init_logger2();
50208
50484
  _sessionManagerDeps = {
50209
50485
  now: () => new Date().toISOString(),
50210
50486
  nowMs: () => Date.now(),
@@ -50368,6 +50644,29 @@ var init_types6 = __esm(() => {
50368
50644
  };
50369
50645
  });
50370
50646
 
50647
+ // src/session/watchdog-cancel-tracker.ts
50648
+ class WatchdogCancelTracker {
50649
+ _cancelledCallsBySession = new Map;
50650
+ buildOnActiveCall(sessionName, registry3) {
50651
+ if (!registry3)
50652
+ return;
50653
+ return (callId, cancel) => {
50654
+ registry3.set(callId, async () => {
50655
+ const cancelledCalls = this._cancelledCallsBySession.get(sessionName) ?? new Set;
50656
+ cancelledCalls.add(callId);
50657
+ this._cancelledCallsBySession.set(sessionName, cancelledCalls);
50658
+ await cancel();
50659
+ });
50660
+ };
50661
+ }
50662
+ hasCancelled(sessionName) {
50663
+ return (this._cancelledCallsBySession.get(sessionName)?.size ?? 0) > 0;
50664
+ }
50665
+ clear(sessionName) {
50666
+ this._cancelledCallsBySession.delete(sessionName);
50667
+ }
50668
+ }
50669
+
50371
50670
  // src/session/watchdog-turn-classification.ts
50372
50671
  function isWatchdogCancelledTurn(input) {
50373
50672
  const { watchdogFired, err, signalAborted } = input;
@@ -50395,7 +50694,9 @@ class SessionManager {
50395
50694
  _watchdogControllerRegistry;
50396
50695
  _transcriptRoot;
50397
50696
  _onStreamActivity;
50398
- _watchdogCancelledCallsBySession = new Map;
50697
+ _loopHandlers = NO_LOOP_HANDLERS;
50698
+ _loopHandlerScopeLogged = false;
50699
+ _watchdogCancels = new WatchdogCancelTracker;
50399
50700
  _agentStreamUnsubscribe;
50400
50701
  constructor(opts) {
50401
50702
  this._getAdapter = opts?.getAdapter ?? (() => {
@@ -50425,34 +50726,8 @@ class SessionManager {
50425
50726
  });
50426
50727
  }
50427
50728
  }
50428
- _buildOnActiveCall(sessionName) {
50429
- const registry3 = this._watchdogControllerRegistry;
50430
- if (!registry3)
50431
- return;
50432
- return (callId, cancel) => {
50433
- registry3.set(callId, async () => {
50434
- const cancelledCalls = this._watchdogCancelledCallsBySession.get(sessionName) ?? new Set;
50435
- cancelledCalls.add(callId);
50436
- this._watchdogCancelledCallsBySession.set(sessionName, cancelledCalls);
50437
- await cancel();
50438
- });
50439
- };
50440
- }
50441
- _clearWatchdogCancelledCalls(sessionName) {
50442
- this._watchdogCancelledCallsBySession.delete(sessionName);
50443
- }
50444
- _persistDescriptor(descriptor) {
50445
- if (!descriptor.scratchDir)
50446
- return;
50447
- const projectDir = resolveProjectDirFromScratchDir(descriptor.scratchDir);
50448
- _sessionManagerDeps.writeDescriptor(descriptor.scratchDir, descriptor, projectDir).catch((err) => {
50449
- getLogger().warn("session", "Failed to re-persist session descriptor", {
50450
- storyId: descriptor.storyId,
50451
- sessionId: descriptor.id,
50452
- scratchDir: descriptor.scratchDir,
50453
- error: err instanceof Error ? err.message : String(err)
50454
- });
50455
- });
50729
+ configureLoopHandlers(set2) {
50730
+ this._loopHandlers = set2;
50456
50731
  }
50457
50732
  create(options) {
50458
50733
  const now = _sessionManagerDeps.now();
@@ -50523,7 +50798,7 @@ class SessionManager {
50523
50798
  updated.completedStages = [...session.completedStages, options.completedStage];
50524
50799
  }
50525
50800
  this._sessions.set(id, updated);
50526
- this._persistDescriptor(updated);
50801
+ persistDescriptor(updated);
50527
50802
  getLogger().debug("session", "Session transitioned", {
50528
50803
  storyId: session.storyId,
50529
50804
  sessionId: id,
@@ -50547,7 +50822,7 @@ class SessionManager {
50547
50822
  lastActivityAt: _sessionManagerDeps.now()
50548
50823
  };
50549
50824
  this._sessions.set(id, updated);
50550
- this._persistDescriptor(updated);
50825
+ persistDescriptor(updated);
50551
50826
  getLogger().debug("session", "Session handle bound", {
50552
50827
  storyId: session.storyId,
50553
50828
  sessionId: id,
@@ -50569,7 +50844,7 @@ class SessionManager {
50569
50844
  lastActivityAt: _sessionManagerDeps.now()
50570
50845
  };
50571
50846
  this._sessions.set(id, updated);
50572
- this._persistDescriptor(updated);
50847
+ persistDescriptor(updated);
50573
50848
  getLogger().info("session", "Session handed off to fallback agent", {
50574
50849
  storyId: session.storyId,
50575
50850
  sessionId: id,
@@ -50604,7 +50879,7 @@ class SessionManager {
50604
50879
  if (terminal.includes(session.state))
50605
50880
  continue;
50606
50881
  const updated = { ...session, state: "COMPLETED", lastActivityAt: now };
50607
- this._persistDescriptor(updated);
50882
+ persistDescriptor(updated);
50608
50883
  this._sessions.delete(id);
50609
50884
  if (updated.handle)
50610
50885
  this._liveHandles.delete(updated.handle);
@@ -50688,7 +50963,7 @@ class SessionManager {
50688
50963
  onSessionEstablished: opts.onSessionEstablished,
50689
50964
  signal: opts.signal,
50690
50965
  resume,
50691
- onActiveCall: this._buildOnActiveCall(name),
50966
+ onActiveCall: this._watchdogCancels.buildOnActiveCall(name, this._watchdogControllerRegistry),
50692
50967
  onStreamActivity: this._onStreamActivity,
50693
50968
  transcriptDir: opts.transcriptDir ?? deriveNativeTranscriptDir({ featureName: opts.featureName, transcriptRoot: this._transcriptRoot }),
50694
50969
  ...opts.transcriptOwner !== undefined ? { transcriptOwner: opts.transcriptOwner } : {},
@@ -50717,7 +50992,7 @@ class SessionManager {
50717
50992
  lastActivityAt: _sessionManagerDeps.now()
50718
50993
  };
50719
50994
  this._sessions.set(existingDescriptor.id, updated);
50720
- this._persistDescriptor(updated);
50995
+ persistDescriptor(updated);
50721
50996
  } else {
50722
50997
  getLogger().warn("session", "openSession called on already-RUNNING session", {
50723
50998
  storyId: opts.storyId,
@@ -50752,7 +51027,7 @@ class SessionManager {
50752
51027
  }
50753
51028
  this._busySessions.delete(handle.id);
50754
51029
  this._cancelledSessions.delete(handle.id);
50755
- this._clearWatchdogCancelledCalls(handle.id);
51030
+ this._watchdogCancels.clear(handle.id);
50756
51031
  }
50757
51032
  async sendPrompt(handle, prompt, opts) {
50758
51033
  if (this._cancelledSessions.has(handle.id)) {
@@ -50769,16 +51044,22 @@ class SessionManager {
50769
51044
  if (!adapter) {
50770
51045
  throw new NaxError(`SessionManager.sendPrompt: no adapter found for agent "${handle.agentName}"`, "ADAPTER_NOT_FOUND", { stage: "session", agentName: handle.agentName });
50771
51046
  }
51047
+ const loopHandlerOpts = buildLoopHandlerTurnOpts({ handle, descriptor: terminalDesc, set: this._loopHandlers });
51048
+ if (shouldLogNativeOnlyScope(this._loopHandlers, handle, this._loopHandlerScopeLogged)) {
51049
+ this._loopHandlerScopeLogged = true;
51050
+ getLogger().info("plugins", LOOP_HANDLERS_NATIVE_ONLY_MESSAGE, { sessionName: handle.id });
51051
+ }
50772
51052
  this._busySessions.add(handle.id);
50773
51053
  try {
50774
51054
  const result = await adapter.sendTurn(handle, prompt, {
50775
51055
  ...opts,
50776
- interactionHandler: opts?.interactionHandler ?? NO_OP_INTERACTION_HANDLER
51056
+ interactionHandler: opts?.interactionHandler ?? NO_OP_INTERACTION_HANDLER,
51057
+ ...loopHandlerOpts
50777
51058
  });
50778
51059
  return { ...result, protocolIds: result.protocolIds ?? handle.protocolIds };
50779
51060
  } catch (err) {
50780
51061
  if (isWatchdogCancelledTurn({
50781
- watchdogFired: (this._watchdogCancelledCallsBySession.get(handle.id)?.size ?? 0) > 0,
51062
+ watchdogFired: this._watchdogCancels.hasCancelled(handle.id),
50782
51063
  err,
50783
51064
  signalAborted: opts?.signal?.aborted === true
50784
51065
  })) {
@@ -50799,7 +51080,7 @@ class SessionManager {
50799
51080
  }
50800
51081
  throw err;
50801
51082
  } finally {
50802
- this._clearWatchdogCancelledCalls(handle.id);
51083
+ this._watchdogCancels.clear(handle.id);
50803
51084
  this._busySessions.delete(handle.id);
50804
51085
  }
50805
51086
  }
@@ -50822,7 +51103,7 @@ class SessionManager {
50822
51103
  this._agentStreamUnsubscribe = undefined;
50823
51104
  }
50824
51105
  }
50825
- var NULL_PROTOCOL_IDS;
51106
+ var NULL_PROTOCOL_IDS, NO_LOOP_HANDLERS;
50826
51107
  var init_manager = __esm(() => {
50827
51108
  init_types2();
50828
51109
  init_config();
@@ -50830,6 +51111,7 @@ var init_manager = __esm(() => {
50830
51111
  init_errors();
50831
51112
  init_logger2();
50832
51113
  init_no_op_interaction_handler();
51114
+ init_loop_handler_forwarding();
50833
51115
  init_manager_deps();
50834
51116
  init_manager_sweep();
50835
51117
  init_naming();
@@ -50838,6 +51120,7 @@ var init_manager = __esm(() => {
50838
51120
  init_watchdog_turn_classification();
50839
51121
  init_manager_deps();
50840
51122
  NULL_PROTOCOL_IDS = { recordId: null, sessionId: null };
51123
+ NO_LOOP_HANDLERS = Object.freeze([]);
50841
51124
  });
50842
51125
 
50843
51126
  // src/session/reopen-handoff.ts
@@ -52782,12 +53065,12 @@ function toContextChunk(packed) {
52782
53065
  ...packed.staleCandidate && { staleCandidate: true }
52783
53066
  };
52784
53067
  }
52785
- function buildFailureNoteChunk(priorAgentId, newAgentId, failure) {
53068
+ function buildFailureNoteChunk(priorAgentId, newAgentId, failure2) {
52786
53069
  const lines = [
52787
53070
  "## Agent swap (availability fallback)",
52788
53071
  "",
52789
53072
  `Prior agent: ${priorAgentId} became unavailable.`,
52790
- `Reason: ${failure.outcome} \u2014 ${failure.message}`,
53073
+ `Reason: ${failure2.outcome} \u2014 ${failure2.message}`,
52791
53074
  "",
52792
53075
  `Continuing as: ${newAgentId}`,
52793
53076
  "",
@@ -52798,7 +53081,7 @@ function buildFailureNoteChunk(priorAgentId, newAgentId, failure) {
52798
53081
  `);
52799
53082
  const tokens = Math.ceil(content.length / 4);
52800
53083
  return {
52801
- id: `failure-note:${priorAgentId}:${newAgentId}:${failure.outcome}`,
53084
+ id: `failure-note:${priorAgentId}:${newAgentId}:${failure2.outcome}`,
52802
53085
  providerId: "orchestrator",
52803
53086
  kind: "session",
52804
53087
  scope: "session",
@@ -52977,7 +53260,7 @@ function toPackedChunks(prior, newAgentId, targetAgentId) {
52977
53260
  });
52978
53261
  }
52979
53262
  function rebuild(prior, options = {}, deps = DEFAULT_REBUILD_DEPS) {
52980
- const { newAgentId, failure, priorStageDigest, storyId } = options;
53263
+ const { newAgentId, failure: failure2, priorStageDigest, storyId } = options;
52981
53264
  const targetAgentId = newAgentId ?? prior.agentId ?? DEFAULT_REBUILD_AGENT_ID;
52982
53265
  const logger = deps.getLogger();
52983
53266
  if (newAgentId && !AGENT_PROFILES[newAgentId]) {
@@ -52993,8 +53276,8 @@ function rebuild(prior, options = {}, deps = DEFAULT_REBUILD_DEPS) {
52993
53276
  const rebuiltPullTools = targetProfile.caps.supportsToolCalls ? prior.pullTools : [];
52994
53277
  const effectiveBudget = Math.min(prior.manifest.effectiveBudget ?? Number.POSITIVE_INFINITY, targetProfile.caps.preferredPromptTokens);
52995
53278
  let failureNoteChunk;
52996
- if (failure && newAgentId && !prior.manifest.rebuildInfo) {
52997
- failureNoteChunk = buildFailureNoteChunk(prior.agentId || "unknown", newAgentId, failure);
53279
+ if (failure2 && newAgentId && !prior.manifest.rebuildInfo) {
53280
+ failureNoteChunk = buildFailureNoteChunk(prior.agentId || "unknown", newAgentId, failure2);
52998
53281
  packedChunks.push(failureNoteChunk);
52999
53282
  }
53000
53283
  let packResult = packChunks(packedChunks, effectiveBudget);
@@ -53021,11 +53304,11 @@ function rebuild(prior, options = {}, deps = DEFAULT_REBUILD_DEPS) {
53021
53304
  const newDigestContent = priorStageDigest?.trim();
53022
53305
  const digestContribution = newDigestContent ? Math.ceil(newDigestContent.length / 4) : 0;
53023
53306
  const usedTokens = packResult.usedTokens + digestContribution;
53024
- const rebuildInfo = failure && newAgentId ? {
53307
+ const rebuildInfo = failure2 && newAgentId ? {
53025
53308
  priorAgentId: prior.agentId ?? "unknown",
53026
53309
  newAgentId: targetAgentId,
53027
- failureCategory: failure.category,
53028
- failureOutcome: failure.outcome,
53310
+ failureCategory: failure2.category,
53311
+ failureOutcome: failure2.outcome,
53029
53312
  priorChunkIds,
53030
53313
  newChunkIds: orderedChunks.map((c) => c.id),
53031
53314
  chunkIdMap: [
@@ -55116,8 +55399,8 @@ var init_version = __esm(() => {
55116
55399
  NAX_AI_VERSION = CATALOG_VERSION;
55117
55400
  NAX_COMMIT = (() => {
55118
55401
  try {
55119
- if (/^[0-9a-f]{6,10}$/.test("bff0ca3b"))
55120
- return "bff0ca3b";
55402
+ if (/^[0-9a-f]{6,10}$/.test("1c7aa52e"))
55403
+ return "1c7aa52e";
55121
55404
  } catch {}
55122
55405
  try {
55123
55406
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -56519,16 +56802,16 @@ var init_factory = __esm(() => {
56519
56802
  import { existsSync as existsSync8 } from "fs";
56520
56803
  function boundedProcRead(proc, timeoutMs) {
56521
56804
  return new Promise((resolve25) => {
56522
- let settled = false;
56805
+ let settled2 = false;
56523
56806
  const finish = (exitCode, stdout) => {
56524
- if (settled)
56807
+ if (settled2)
56525
56808
  return;
56526
- settled = true;
56809
+ settled2 = true;
56527
56810
  clearTimeout(timer);
56528
56811
  resolve25({ exitCode, stdout });
56529
56812
  };
56530
56813
  const timer = setTimeout(() => {
56531
- if (settled)
56814
+ if (settled2)
56532
56815
  return;
56533
56816
  try {
56534
56817
  proc.kill("SIGKILL");
@@ -56540,26 +56823,26 @@ function boundedProcRead(proc, timeoutMs) {
56540
56823
  }
56541
56824
  function awaitProcExit(proc, timeoutMs) {
56542
56825
  return new Promise((resolve25) => {
56543
- let settled = false;
56826
+ let settled2 = false;
56544
56827
  const timer = setTimeout(() => {
56545
- if (settled)
56828
+ if (settled2)
56546
56829
  return;
56547
- settled = true;
56830
+ settled2 = true;
56548
56831
  try {
56549
56832
  proc.kill("SIGKILL");
56550
56833
  } catch {}
56551
56834
  resolve25(-1);
56552
56835
  }, timeoutMs);
56553
56836
  proc.exited.then((code) => {
56554
- if (settled)
56837
+ if (settled2)
56555
56838
  return;
56556
- settled = true;
56839
+ settled2 = true;
56557
56840
  clearTimeout(timer);
56558
56841
  resolve25(code);
56559
56842
  }, () => {
56560
- if (settled)
56843
+ if (settled2)
56561
56844
  return;
56562
- settled = true;
56845
+ settled2 = true;
56563
56846
  clearTimeout(timer);
56564
56847
  resolve25(-1);
56565
56848
  });
@@ -59302,11 +59585,11 @@ var require_codegen = __commonJS(function(exports) {
59302
59585
  const rhs = this.rhs === undefined ? "" : ` = ${this.rhs}`;
59303
59586
  return `${varKind} ${this.name}${rhs};` + _n;
59304
59587
  }
59305
- optimizeNames(names, constants) {
59588
+ optimizeNames(names, constants2) {
59306
59589
  if (!names[this.name.str])
59307
59590
  return;
59308
59591
  if (this.rhs)
59309
- this.rhs = optimizeExpr(this.rhs, names, constants);
59592
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
59310
59593
  return this;
59311
59594
  }
59312
59595
  get names() {
@@ -59324,10 +59607,10 @@ var require_codegen = __commonJS(function(exports) {
59324
59607
  render({ _n }) {
59325
59608
  return `${this.lhs} = ${this.rhs};` + _n;
59326
59609
  }
59327
- optimizeNames(names, constants) {
59610
+ optimizeNames(names, constants2) {
59328
59611
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
59329
59612
  return;
59330
- this.rhs = optimizeExpr(this.rhs, names, constants);
59613
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
59331
59614
  return this;
59332
59615
  }
59333
59616
  get names() {
@@ -59393,8 +59676,8 @@ var require_codegen = __commonJS(function(exports) {
59393
59676
  optimizeNodes() {
59394
59677
  return `${this.code}` ? this : undefined;
59395
59678
  }
59396
- optimizeNames(names, constants) {
59397
- this.code = optimizeExpr(this.code, names, constants);
59679
+ optimizeNames(names, constants2) {
59680
+ this.code = optimizeExpr(this.code, names, constants2);
59398
59681
  return this;
59399
59682
  }
59400
59683
  get names() {
@@ -59424,12 +59707,12 @@ var require_codegen = __commonJS(function(exports) {
59424
59707
  }
59425
59708
  return nodes.length > 0 ? this : undefined;
59426
59709
  }
59427
- optimizeNames(names, constants) {
59710
+ optimizeNames(names, constants2) {
59428
59711
  const { nodes } = this;
59429
59712
  let i = nodes.length;
59430
59713
  while (i--) {
59431
59714
  const n = nodes[i];
59432
- if (n.optimizeNames(names, constants))
59715
+ if (n.optimizeNames(names, constants2))
59433
59716
  continue;
59434
59717
  subtractNames(names, n.names);
59435
59718
  nodes.splice(i, 1);
@@ -59486,12 +59769,12 @@ var require_codegen = __commonJS(function(exports) {
59486
59769
  return;
59487
59770
  return this;
59488
59771
  }
59489
- optimizeNames(names, constants) {
59772
+ optimizeNames(names, constants2) {
59490
59773
  var _a2;
59491
- this.else = (_a2 = this.else) === null || _a2 === undefined ? undefined : _a2.optimizeNames(names, constants);
59492
- if (!(super.optimizeNames(names, constants) || this.else))
59774
+ this.else = (_a2 = this.else) === null || _a2 === undefined ? undefined : _a2.optimizeNames(names, constants2);
59775
+ if (!(super.optimizeNames(names, constants2) || this.else))
59493
59776
  return;
59494
- this.condition = optimizeExpr(this.condition, names, constants);
59777
+ this.condition = optimizeExpr(this.condition, names, constants2);
59495
59778
  return this;
59496
59779
  }
59497
59780
  get names() {
@@ -59516,10 +59799,10 @@ var require_codegen = __commonJS(function(exports) {
59516
59799
  render(opts) {
59517
59800
  return `for(${this.iteration})` + super.render(opts);
59518
59801
  }
59519
- optimizeNames(names, constants) {
59520
- if (!super.optimizeNames(names, constants))
59802
+ optimizeNames(names, constants2) {
59803
+ if (!super.optimizeNames(names, constants2))
59521
59804
  return;
59522
- this.iteration = optimizeExpr(this.iteration, names, constants);
59805
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
59523
59806
  return this;
59524
59807
  }
59525
59808
  get names() {
@@ -59557,10 +59840,10 @@ var require_codegen = __commonJS(function(exports) {
59557
59840
  render(opts) {
59558
59841
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
59559
59842
  }
59560
- optimizeNames(names, constants) {
59561
- if (!super.optimizeNames(names, constants))
59843
+ optimizeNames(names, constants2) {
59844
+ if (!super.optimizeNames(names, constants2))
59562
59845
  return;
59563
- this.iterable = optimizeExpr(this.iterable, names, constants);
59846
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
59564
59847
  return this;
59565
59848
  }
59566
59849
  get names() {
@@ -59605,11 +59888,11 @@ var require_codegen = __commonJS(function(exports) {
59605
59888
  (_b = this.finally) === null || _b === undefined || _b.optimizeNodes();
59606
59889
  return this;
59607
59890
  }
59608
- optimizeNames(names, constants) {
59891
+ optimizeNames(names, constants2) {
59609
59892
  var _a2, _b;
59610
- super.optimizeNames(names, constants);
59611
- (_a2 = this.catch) === null || _a2 === undefined || _a2.optimizeNames(names, constants);
59612
- (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants);
59893
+ super.optimizeNames(names, constants2);
59894
+ (_a2 = this.catch) === null || _a2 === undefined || _a2.optimizeNames(names, constants2);
59895
+ (_b = this.finally) === null || _b === undefined || _b.optimizeNames(names, constants2);
59613
59896
  return this;
59614
59897
  }
59615
59898
  get names() {
@@ -59883,7 +60166,7 @@ var require_codegen = __commonJS(function(exports) {
59883
60166
  function addExprNames(names, from) {
59884
60167
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
59885
60168
  }
59886
- function optimizeExpr(expr, names, constants) {
60169
+ function optimizeExpr(expr, names, constants2) {
59887
60170
  if (expr instanceof code_1.Name)
59888
60171
  return replaceName(expr);
59889
60172
  if (!canOptimize(expr))
@@ -59898,14 +60181,14 @@ var require_codegen = __commonJS(function(exports) {
59898
60181
  return items;
59899
60182
  }, []));
59900
60183
  function replaceName(n) {
59901
- const c = constants[n.str];
60184
+ const c = constants2[n.str];
59902
60185
  if (c === undefined || names[n.str] !== 1)
59903
60186
  return n;
59904
60187
  delete names[n.str];
59905
60188
  return c;
59906
60189
  }
59907
60190
  function canOptimize(e) {
59908
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== undefined);
60191
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== undefined);
59909
60192
  }
59910
60193
  }
59911
60194
  function subtractNames(names, from) {
@@ -67337,8 +67620,8 @@ function createMcpPool(opts) {
67337
67620
  closed = true;
67338
67621
  const pending = [...entries.values()];
67339
67622
  entries.clear();
67340
- const settled = await Promise.allSettled(pending);
67341
- await Promise.allSettled(settled.map(async (result) => {
67623
+ const settled2 = await Promise.allSettled(pending);
67624
+ await Promise.allSettled(settled2.map(async (result) => {
67342
67625
  if (result.status !== "fulfilled" || result.value === undefined)
67343
67626
  return;
67344
67627
  const { connection } = result.value;
@@ -67478,9 +67761,9 @@ function probeRanNoTests(output) {
67478
67761
  function escapeRegex2(input) {
67479
67762
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
67480
67763
  }
67481
- function buildIsolationCommand(baseCommand, failure, framework) {
67482
- const file2 = failure.file;
67483
- const name = failure.testName;
67764
+ function buildIsolationCommand(baseCommand, failure2, framework) {
67765
+ const file2 = failure2.file;
67766
+ const name = failure2.testName;
67484
67767
  switch (framework) {
67485
67768
  case "pytest":
67486
67769
  return `${baseCommand} ${shellQuoteArg(`${file2}::${name}`)}`;
@@ -67498,15 +67781,15 @@ function buildIsolationCommand(baseCommand, failure, framework) {
67498
67781
  }
67499
67782
  }
67500
67783
  async function runFlakeProbe(input) {
67501
- const { framework, baseCommand, failure, cwd, probeRuns, probeTimeoutSeconds } = input;
67784
+ const { framework, baseCommand, failure: failure2, cwd, probeRuns, probeTimeoutSeconds } = input;
67502
67785
  const logger = getSafeLogger();
67503
- if (failure.file === "unknown" || framework === "unknown") {
67786
+ if (failure2.file === "unknown" || framework === "unknown") {
67504
67787
  return {
67505
67788
  verdict: "unprobeable",
67506
- reason: failure.file === "unknown" && framework === "unknown" ? "unknown file and framework" : failure.file === "unknown" ? "unknown file" : "unknown framework"
67789
+ reason: failure2.file === "unknown" && framework === "unknown" ? "unknown file and framework" : failure2.file === "unknown" ? "unknown file" : "unknown framework"
67507
67790
  };
67508
67791
  }
67509
- const command = buildIsolationCommand(baseCommand, failure, framework);
67792
+ const command = buildIsolationCommand(baseCommand, failure2, framework);
67510
67793
  let probePasses = 0;
67511
67794
  let attributableRuns = 0;
67512
67795
  for (let i = 0;i < probeRuns; i += 1) {
@@ -67534,8 +67817,8 @@ async function runFlakeProbe(input) {
67534
67817
  if (attributableRuns === 0) {
67535
67818
  const reason = `all ${probeRuns} probe run(s) were environmental (executor crash, timeout, or non-attributable) \u2014 no attributable pass or fail`;
67536
67819
  logger?.warn("flake-probe", `Unprobeable \u2014 ${reason}`, {
67537
- file: failure.file,
67538
- testName: failure.testName,
67820
+ file: failure2.file,
67821
+ testName: failure2.testName,
67539
67822
  probeRuns
67540
67823
  });
67541
67824
  return { verdict: "unprobeable", reason };
@@ -67589,8 +67872,8 @@ var init_flake_triage_telemetry = __esm(() => {
67589
67872
  });
67590
67873
 
67591
67874
  // src/verification/flake-triage.ts
67592
- function flakeMemoKey(failure) {
67593
- return `${failure.file ?? ""}::${failure.rule ?? ""}`;
67875
+ function flakeMemoKey(failure2) {
67876
+ return `${failure2.file ?? ""}::${failure2.rule ?? ""}`;
67594
67877
  }
67595
67878
  function createQuarantineMemo() {
67596
67879
  const keys = new Set;
@@ -67651,7 +67934,7 @@ async function triageFlakyFindings(input) {
67651
67934
  result.push(copy);
67652
67935
  continue;
67653
67936
  }
67654
- const failure = {
67937
+ const failure2 = {
67655
67938
  file: copy.file ?? "unknown",
67656
67939
  testName: copy.rule ?? "",
67657
67940
  error: copy.message,
@@ -67660,14 +67943,14 @@ async function triageFlakyFindings(input) {
67660
67943
  const probeInput = {
67661
67944
  framework,
67662
67945
  baseCommand,
67663
- failure,
67946
+ failure: failure2,
67664
67947
  cwd,
67665
67948
  probeRuns: flakeDetection.probeRuns,
67666
67949
  probeTimeoutSeconds: flakeDetection.probeTimeoutSeconds
67667
67950
  };
67668
67951
  let verdict;
67669
67952
  try {
67670
- verdict = await _flakeTriageDeps.runFlakeProbe({ failure, config: flakeDetection, probeInput });
67953
+ verdict = await _flakeTriageDeps.runFlakeProbe({ failure: failure2, config: flakeDetection, probeInput });
67671
67954
  } catch (err) {
67672
67955
  logger?.warn("flake-triage", `Probe dependency threw for ${key} \u2014 keeping finding blocking`, {
67673
67956
  file: copy.file,
@@ -67780,7 +68063,28 @@ var init_coding_tool_extras = __esm(() => {
67780
68063
  });
67781
68064
 
67782
68065
  // src/agents/coding-tool-sandbox.ts
67783
- import { homedir as homedir5 } from "os";
68066
+ import { mkdir as mkdir21 } from "fs/promises";
68067
+ import { homedir as homedir5, tmpdir as tmpdir4 } from "os";
68068
+ async function sessionTempRoots(args) {
68069
+ const { runTmpRoot: runTmpRoot2, tmpDir } = args;
68070
+ const shared = () => ({ tempRoots: _sessionSandboxDeps.tempRoots(), confined: false });
68071
+ if (args.config.filesystem.allowSharedTmp || runTmpRoot2 === undefined || tmpDir === undefined)
68072
+ return shared();
68073
+ try {
68074
+ await _sessionSandboxDeps.mkdir(tmpDir);
68075
+ } catch (err) {
68076
+ getSafeLogger()?.warn("sandbox", "could not create the session temp dir \u2014 granting the shared temp roots", {
68077
+ storyId: args.storyId ?? "_dispatch",
68078
+ tmpDir,
68079
+ error: errorMessage(err)
68080
+ });
68081
+ return shared();
68082
+ }
68083
+ return {
68084
+ tempRoots: _sessionSandboxDeps.runTempRoots({ runTmpRoot: runTmpRoot2, tmpdir: _sessionSandboxDeps.tmpdir() }),
68085
+ confined: true
68086
+ };
68087
+ }
67784
68088
  async function resolveSessionSandbox(args) {
67785
68089
  const config2 = args.config;
67786
68090
  if (config2 === undefined || !config2.enabled || !args.needsLauncher) {
@@ -67801,6 +68105,12 @@ async function resolveSessionSandbox(args) {
67801
68105
  const git = await _sessionSandboxDeps.gitLayout(args.root);
67802
68106
  const credentialFiles = await _sessionSandboxDeps.credentialFiles();
67803
68107
  const approvalsFile = args.outputDir !== undefined ? approvalsPath(args.outputDir) : undefined;
68108
+ const { tempRoots, confined } = await sessionTempRoots({
68109
+ config: config2,
68110
+ storyId: args.storyId,
68111
+ runTmpRoot: args.runTmpRoot,
68112
+ tmpDir: args.tmpDir
68113
+ });
67804
68114
  const policyFor = async (root) => buildSandboxPolicy({
67805
68115
  root,
67806
68116
  git,
@@ -67809,7 +68119,7 @@ async function resolveSessionSandbox(args) {
67809
68119
  credentialFiles,
67810
68120
  ...approvalsFile !== undefined ? { approvalsFile } : {},
67811
68121
  home: _sessionSandboxDeps.homedir(),
67812
- tempRoots: _sessionSandboxDeps.tempRoots(),
68122
+ tempRoots,
67813
68123
  platform: _sessionSandboxDeps.platform(),
67814
68124
  config: config2
67815
68125
  });
@@ -67821,7 +68131,7 @@ async function resolveSessionSandbox(args) {
67821
68131
  const afterWrapped = await _sessionSandboxDeps.commonDirTripwire(git, args.storyId);
67822
68132
  const network = config2.network.allowedDomains ?? "open";
67823
68133
  return createCommandLauncher({
67824
- state: { kind: "available", backend: backend2.name, network },
68134
+ state: { kind: "available", backend: backend2.name, network, ...confined ? { sharedTmp: false } : {} },
67825
68135
  backend: backend2,
67826
68136
  policyFor,
67827
68137
  ...afterWrapped !== undefined ? { afterWrapped } : {},
@@ -67851,6 +68161,7 @@ async function literalPolicyError(policyFor, root) {
67851
68161
  var _sessionSandboxDeps;
67852
68162
  var init_coding_tool_sandbox = __esm(() => {
67853
68163
  init_errors();
68164
+ init_logger2();
67854
68165
  init_permissions();
67855
68166
  init_sandbox();
67856
68167
  _sessionSandboxDeps = {
@@ -67863,7 +68174,10 @@ var init_coding_tool_sandbox = __esm(() => {
67863
68174
  credentialFiles: listCredentialFiles,
67864
68175
  tempRoots: defaultTempRoots,
67865
68176
  homedir: homedir5,
67866
- platform: () => process.platform
68177
+ platform: () => process.platform,
68178
+ mkdir: (path7) => mkdir21(path7, { recursive: true }),
68179
+ tmpdir: () => tmpdir4(),
68180
+ runTempRoots
67867
68181
  };
67868
68182
  });
67869
68183
 
@@ -67990,23 +68304,26 @@ function unionDeclaredTools(declared, providerResult) {
67990
68304
  ];
67991
68305
  }
67992
68306
  function warnDroppedProviders(providerResult, storyId) {
67993
- for (const failure of providerResult.failures) {
68307
+ for (const failure2 of providerResult.failures) {
67994
68308
  getSafeLogger()?.warn("tools", "[provider] dropped", {
67995
68309
  storyId,
67996
- providerId: failure.providerId,
67997
- reason: failure.reason
68310
+ providerId: failure2.providerId,
68311
+ reason: failure2.reason
67998
68312
  });
67999
68313
  }
68000
68314
  }
68001
68315
  async function resolveDispatchLauncher(options, declared, sessionName) {
68002
- return options.codingToolRoot !== undefined && options.codingToolRoot.trim() !== "" ? await resolveSessionSandbox({
68316
+ if (options.codingToolRoot === undefined || options.codingToolRoot.trim() === "")
68317
+ return;
68318
+ const runRoot = options.runId !== undefined ? runTmpRoot(options.runId) : undefined;
68319
+ return await resolveSessionSandbox({
68003
68320
  config: options.config?.execution?.sandbox,
68004
68321
  root: options.codingToolRoot,
68005
68322
  ...options.outputDir !== undefined ? { outputDir: options.outputDir } : {},
68006
68323
  needsLauncher: declared.includes(BASH_TOOL_NAME) || declared.includes(EXEC_TOOL_NAME),
68007
68324
  ...options.storyId !== undefined ? { storyId: options.storyId } : {},
68008
- ...options.runId !== undefined ? { tmpDir: sessionTmpDir(options.runId, sessionName) } : {}
68009
- }) : undefined;
68325
+ ...runRoot !== undefined ? { tmpDir: sessionTmpDirUnder(runRoot, sessionName), runTmpRoot: runRoot } : {}
68326
+ });
68010
68327
  }
68011
68328
  function optionalDispatchArgs(options) {
68012
68329
  return {
@@ -69312,8 +69629,8 @@ async function mayHaveJournal(candidateRoots) {
69312
69629
  return false;
69313
69630
  }
69314
69631
  async function recordInFlight(repoRoot, entry) {
69315
- const { mkdir: mkdir21 } = await import("fs/promises");
69316
- await mkdir21(journalDir(repoRoot), { recursive: true });
69632
+ const { mkdir: mkdir22 } = await import("fs/promises");
69633
+ await mkdir22(journalDir(repoRoot), { recursive: true });
69317
69634
  await Bun.write(journalPathFor(repoRoot, entry.storyId), JSON.stringify(entry));
69318
69635
  }
69319
69636
  async function clearInFlight(repoRoot, storyId) {
@@ -69762,7 +70079,7 @@ var init_runners = __esm(() => {
69762
70079
 
69763
70080
  // src/verification/test-baseline.ts
69764
70081
  import { existsSync as existsSync10 } from "fs";
69765
- import { mkdir as mkdir21, readdir as readdir6, rm as rm5 } from "fs/promises";
70082
+ import { mkdir as mkdir22, readdir as readdir6, rm as rm5 } from "fs/promises";
69766
70083
  import { join as join57 } from "path";
69767
70084
  function runBaselinePath(root, featureId) {
69768
70085
  return join57(featureDir(root, featureId), RUN_BASELINE_FILENAME);
@@ -69779,7 +70096,7 @@ async function readRunBaseline(root, featureId) {
69779
70096
  }
69780
70097
  async function writeStoryBaseline(root, featureId, storyId, baseline) {
69781
70098
  const path7 = storyBaselinePath(root, featureId, storyId);
69782
- await mkdir21(join57(featureDir(root, featureId), "stories", storyId), { recursive: true });
70099
+ await mkdir22(join57(featureDir(root, featureId), "stories", storyId), { recursive: true });
69783
70100
  await saveJsonFile(path7, baseline, "test-baseline");
69784
70101
  }
69785
70102
  async function readStoryBaseline(root, featureId, storyId) {
@@ -70143,13 +70460,13 @@ function formatDuration2(ms) {
70143
70460
  return `${minutes}m ${seconds}s`;
70144
70461
  }
70145
70462
  function timeoutRetry(input) {
70146
- const { prompt, changedFiles, elapsedMs, attempt, failure } = input;
70147
- if (failure?.outcome === "fail-invalid-tool-call")
70148
- return invalidToolCallRetry(input, failure);
70149
- if (failure?.outcome === "fail-spin") {
70463
+ const { prompt, changedFiles, elapsedMs, attempt, failure: failure2 } = input;
70464
+ if (failure2?.outcome === "fail-invalid-tool-call")
70465
+ return invalidToolCallRetry(input, failure2);
70466
+ if (failure2?.outcome === "fail-spin") {
70150
70467
  return nonTimeoutRetry(input, "The previous attempt was stopped because it kept repeating the same tool calls without making progress.", "Do not repeat a call whose result you already have; if a check keeps returning the same result, change the code or the approach before running it again.");
70151
70468
  }
70152
- if (failure?.outcome === "fail-incomplete") {
70469
+ if (failure2?.outcome === "fail-incomplete") {
70153
70470
  return nonTimeoutRetry(input, "The previous attempt ended its turn before finishing the story.", "Keep working until every acceptance criterion is met, then finish with your final answer.");
70154
70471
  }
70155
70472
  const duration3 = formatDuration2(elapsedMs);
@@ -70193,8 +70510,8 @@ ${state}
70193
70510
 
70194
70511
  ${prompt}`;
70195
70512
  }
70196
- function invalidToolCallRetry(input, failure) {
70197
- const detail = failure.invalidToolCall;
70513
+ function invalidToolCallRetry(input, failure2) {
70514
+ const detail = failure2.invalidToolCall;
70198
70515
  const rejected = detail === undefined ? "The previous attempt was stopped because it kept repeating the same invalid tool call. The tool rejected that input every time and never ran it." : `The previous attempt was stopped because it kept repeating the same invalid \`${detail.tool}\` call: property \`${detail.property}\` expected ${detail.expected}, got ${detail.actual}. The tool rejected that input every time and never ran it.`;
70199
70516
  const fix = detail === undefined ? "Before calling a tool, check its input schema. If an optional property has no value, leave it out entirely." : `Before calling \`${detail.tool}\` again, make \`${detail.property}\` match its schema (${detail.expected}). If it is optional and you have no value for it, leave it out entirely.`;
70200
70517
  return nonTimeoutRetry(input, rejected, fix);
@@ -70557,26 +70874,35 @@ function resolvePytestBin(packageDir) {
70557
70874
  return "pytest";
70558
70875
  }
70559
70876
  function substituteAcceptanceTestPath(command, testPath) {
70560
- return command.replace(/\{\{files\}\}/g, testPath).replace(/\{\{file\}\}/g, testPath).replace(/\{\{FILE\}\}/g, testPath);
70877
+ return command.replace(/\{\{files\}\}/g, () => testPath).replace(/\{\{file\}\}/g, () => testPath).replace(/\{\{FILE\}\}/g, () => testPath);
70561
70878
  }
70562
70879
  function buildAcceptanceRunCommand(testPath, testFramework, commandOverride, packageDir) {
70563
- if (commandOverride) {
70564
- return commandOverride.trim().split(/\s+/).map((part) => substituteAcceptanceTestPath(part, testPath));
70880
+ const trimmedOverride = commandOverride?.trim();
70881
+ if (trimmedOverride) {
70882
+ return substituteAcceptanceTestPath(trimmedOverride, shellQuoteArg(testPath));
70565
70883
  }
70884
+ let defaultArgv;
70566
70885
  switch (testFramework?.toLowerCase()) {
70567
70886
  case "vitest":
70568
- return ["npx", "vitest", "run", testPath];
70887
+ defaultArgv = ["npx", "vitest", "run", testPath];
70888
+ break;
70569
70889
  case "jest":
70570
- return ["npx", "jest", testPath];
70890
+ defaultArgv = ["npx", "jest", testPath];
70891
+ break;
70571
70892
  case "pytest":
70572
- return [resolvePytestBin(packageDir), testPath];
70893
+ defaultArgv = [resolvePytestBin(packageDir), testPath];
70894
+ break;
70573
70895
  case "go-test":
70574
- return ["go", "test", testPath];
70896
+ defaultArgv = ["go", "test", testPath];
70897
+ break;
70575
70898
  case "cargo-test":
70576
- return ["cargo", "test", "--test", "acceptance"];
70899
+ defaultArgv = ["cargo", "test", "--test", "acceptance"];
70900
+ break;
70577
70901
  default:
70578
- return ["bun", "test", testPath, "--timeout=60000"];
70902
+ defaultArgv = ["bun", "test", testPath, "--timeout=60000"];
70903
+ break;
70579
70904
  }
70905
+ return defaultArgv.map(shellQuoteArg).join(" ");
70580
70906
  }
70581
70907
  function parseAcceptanceCriteria(specContent) {
70582
70908
  const criteria = [];
@@ -72005,7 +72331,7 @@ var init_build_hop_callback = __esm(() => {
72005
72331
  init_build_hop_callback_hop();
72006
72332
  init_hop_endpoint();
72007
72333
  _buildHopCallbackDeps = {
72008
- rebuildForAgent: (prior, newAgentId, failure, storyId) => new ContextOrchestrator([]).rebuildForAgent(prior, { newAgentId, failure, storyId }),
72334
+ rebuildForAgent: (prior, newAgentId, failure2, storyId) => new ContextOrchestrator([]).rebuildForAgent(prior, { newAgentId, failure: failure2, storyId }),
72009
72335
  writeRebuildManifest,
72010
72336
  createContextToolRuntime,
72011
72337
  captureGitRef,
@@ -72161,8 +72487,8 @@ function decideCompleteRetry(args) {
72161
72487
  const { ctx, op, err, attempt, retryStrategy, dispatchAgent, abortSignal } = args;
72162
72488
  if (!retryStrategy)
72163
72489
  return;
72164
- const failure = err;
72165
- const decision = retryStrategy.shouldRetry(failure, attempt, {
72490
+ const failure2 = err;
72491
+ const decision = retryStrategy.shouldRetry(failure2, attempt, {
72166
72492
  site: "complete",
72167
72493
  agentName: dispatchAgent,
72168
72494
  stage: op.stage,
@@ -72182,8 +72508,8 @@ function decideCompleteRetry(args) {
72182
72508
  attempt,
72183
72509
  delayMs: decision.delayMs,
72184
72510
  promptTransformed: decision.nextPrompt !== undefined,
72185
- failureKind: failure instanceof Error ? "error" : failure.outcome,
72186
- failureMessage: errorMessage(failure)
72511
+ failureKind: failure2 instanceof Error ? "error" : failure2.outcome,
72512
+ failureMessage: errorMessage(failure2)
72187
72513
  });
72188
72514
  return { delayMs: decision.delayMs };
72189
72515
  }
@@ -74591,7 +74917,7 @@ var init_scope = __esm(() => {
74591
74917
 
74592
74918
  // src/review/fix-review/tree-snapshot/index.ts
74593
74919
  import { mkdtemp, rm as rm6 } from "fs/promises";
74594
- import { tmpdir as tmpdir4 } from "os";
74920
+ import { tmpdir as tmpdir5 } from "os";
74595
74921
  import { join as join61 } from "path";
74596
74922
  async function runGit(args, cwd, indexOverlay, timeoutMs = _treeSnapshotDeps.gitTimeoutMs) {
74597
74923
  const proc = _treeSnapshotDeps.spawn(hardenedGitArgv(["git", ...args]), {
@@ -74678,7 +75004,7 @@ var init_tree_snapshot = __esm(() => {
74678
75004
  gitTimeoutMs: TREE_SNAPSHOT_GIT_TIMEOUT_MS,
74679
75005
  mkdtemp,
74680
75006
  rm: rm6,
74681
- tmpdir: tmpdir4
75007
+ tmpdir: tmpdir5
74682
75008
  };
74683
75009
  });
74684
75010
 
@@ -79220,8 +79546,8 @@ function makeParseRetryStrategy(opts) {
79220
79546
  const checkTruncated = opts.looksTruncated ?? looksLikeTruncatedJson;
79221
79547
  const maxAttempts = opts.maxAttempts ?? 2;
79222
79548
  return {
79223
- shouldRetry(failure, attempt, ctx) {
79224
- if (!(failure instanceof ParseValidationError)) {
79549
+ shouldRetry(failure2, attempt, ctx) {
79550
+ if (!(failure2 instanceof ParseValidationError)) {
79225
79551
  return { retry: false };
79226
79552
  }
79227
79553
  if (ctx.lastOutput && ctx.lastTurnResult?.adapterFailure) {
@@ -79282,13 +79608,13 @@ var init_parse_retry = __esm(() => {
79282
79608
  // src/agents/retry/presets.ts
79283
79609
  function resolveRetryPreset(preset) {
79284
79610
  return {
79285
- shouldRetry(failure, attempt, _ctx) {
79611
+ shouldRetry(failure2, attempt, _ctx) {
79286
79612
  if (attempt >= preset.maxAttempts - 1)
79287
79613
  return { retry: false };
79288
79614
  if (preset.preset === "transient-network") {
79289
- if (failure instanceof Error)
79615
+ if (failure2 instanceof Error)
79290
79616
  return { retry: true, delayMs: preset.baseDelayMs };
79291
- const af = failure;
79617
+ const af = failure2;
79292
79618
  if (af.retriable)
79293
79619
  return { retry: true, delayMs: preset.baseDelayMs };
79294
79620
  return { retry: false };
@@ -79382,11 +79708,11 @@ function recordAgentFallbacks(ctx, fallbacks) {
79382
79708
  }
79383
79709
  store.set(ctx.storyId, [...fallbacks]);
79384
79710
  }
79385
- function recordAdapterFailure(ctx, failure) {
79711
+ function recordAdapterFailure(ctx, failure2) {
79386
79712
  if (!ctx.storyId)
79387
79713
  return;
79388
- if (failure) {
79389
- ctx.runtime.lastAdapterFailure.set(ctx.storyId, failure);
79714
+ if (failure2) {
79715
+ ctx.runtime.lastAdapterFailure.set(ctx.storyId, failure2);
79390
79716
  return;
79391
79717
  }
79392
79718
  ctx.runtime.lastAdapterFailure.delete(ctx.storyId);
@@ -79428,12 +79754,12 @@ class CooldownStore {
79428
79754
  constructor(_now) {
79429
79755
  this._now = _now;
79430
79756
  }
79431
- mark(agent, failure, tier, modelId) {
79432
- const expiresAt = resolveCooldownExpiry(failure, this._now());
79757
+ mark(agent, failure2, tier, modelId) {
79758
+ const expiresAt = resolveCooldownExpiry(failure2, this._now());
79433
79759
  if (expiresAt === null)
79434
79760
  return;
79435
- const key = failurePolicyFor(failure.outcome).cooldownScope === "agent" ? agent : identityKey(agent, tier, modelId);
79436
- this._entries.set(key, { failure, expiresAt });
79761
+ const key = failurePolicyFor(failure2.outcome).cooldownScope === "agent" ? agent : identityKey(agent, tier, modelId);
79762
+ this._entries.set(key, { failure: failure2, expiresAt });
79437
79763
  }
79438
79764
  isCooling(agent, tier, modelId) {
79439
79765
  return this._live(agent, tier, modelId) !== undefined;
@@ -79731,11 +80057,11 @@ var init_manager_dispatch = __esm(() => {
79731
80057
 
79732
80058
  // src/agents/retry/resolve-exhaustion.ts
79733
80059
  async function resolveExhaustion(input) {
79734
- const { failure, retryStrategy, retryCtx, signal, sleep: sleep2, onExhausted } = input;
80060
+ const { failure: failure2, retryStrategy, retryCtx, signal, sleep: sleep2, onExhausted } = input;
79735
80061
  if (signal?.aborted)
79736
80062
  return "cancelled";
79737
- if (failure && failurePolicyFor(failure.outcome).terminalBackoff) {
79738
- const decision = retryStrategy.shouldRetry(failure, input.attempt, retryCtx);
80063
+ if (failure2 && failurePolicyFor(failure2.outcome).terminalBackoff) {
80064
+ const decision = retryStrategy.shouldRetry(failure2, input.attempt, retryCtx);
79739
80065
  if (decision.retry) {
79740
80066
  await sleep2(decision.delayMs, signal);
79741
80067
  return signal?.aborted ? "cancelled" : "retry";
@@ -79965,13 +80291,13 @@ async function runWithFallback(input) {
79965
80291
  dispatchesCompleted
79966
80292
  };
79967
80293
  }
79968
- const failure = result.adapterFailure ?? unknownFailure();
80294
+ const failure2 = result.adapterFailure ?? unknownFailure();
79969
80295
  const currentTier = currentHopKind.tier;
79970
- input.markUnavailable(currentAgent, failure, currentTier, dispatchedModel);
80296
+ input.markUnavailable(currentAgent, failure2, currentTier, dispatchedModel);
79971
80297
  const next = input.nextCandidate(primaryAgent, hopsSoFar, currentAgent, currentTier, dispatchedModel);
79972
80298
  if (!next) {
79973
80299
  const outcome = await input.resolveExhaustion({
79974
- failure,
80300
+ failure: failure2,
79975
80301
  hopsSoFar,
79976
80302
  attempt: rateLimitRetry,
79977
80303
  swapWasPossible: true,
@@ -80004,7 +80330,7 @@ async function runWithFallback(input) {
80004
80330
  currentBundle = updatedBundle;
80005
80331
  currentHopKind = {
80006
80332
  kind: "swap",
80007
- failure,
80333
+ failure: failure2,
80008
80334
  ...next.tier ? { tier: next.tier } : {},
80009
80335
  ...next.model ? { model: next.model } : {}
80010
80336
  };
@@ -80013,7 +80339,7 @@ async function runWithFallback(input) {
80013
80339
  priorAgent: currentAgent,
80014
80340
  newAgent: next.agent,
80015
80341
  hop: hopsSoFar,
80016
- failure,
80342
+ failure: failure2,
80017
80343
  costUsd: result.estimatedCostUsd ?? 0
80018
80344
  });
80019
80345
  fallbacks.push(fallback);
@@ -80265,8 +80591,8 @@ class AgentManager {
80265
80591
  resolveFallbackChain(agent, _failure) {
80266
80592
  return this._fallbackIdentity.chain(agent, this._isExcluded);
80267
80593
  }
80268
- shouldSwap(failure, hopsSoFar) {
80269
- return decideSwap(failure, hopsSoFar, this._config.agent?.fallback).swap;
80594
+ shouldSwap(failure2, hopsSoFar) {
80595
+ return decideSwap(failure2, hopsSoFar, this._config.agent?.fallback).swap;
80270
80596
  }
80271
80597
  nextCandidate(cur, hops, exclude, tier, model) {
80272
80598
  return this._fallbackIdentity.nextCandidate({ cur, hops, exclude, tier, model }, this._isExcluded);
@@ -80282,7 +80608,7 @@ class AgentManager {
80282
80608
  logger: this._loggerOverride ?? getSafeLogger(),
80283
80609
  getDefault: () => this.getDefault(),
80284
80610
  isUnavailable: (agent, tier, model) => this.isUnavailable(agent, tier, model),
80285
- markUnavailable: (agent, failure, tier, model) => this.markUnavailable(agent, failure, tier, model),
80611
+ markUnavailable: (agent, failure2, tier, model) => this.markUnavailable(agent, failure2, tier, model),
80286
80612
  nextCandidate: (cur, hops, exclude, tier, model) => this.nextCandidate(cur, hops, exclude, tier, model),
80287
80613
  resolveExhaustion: (options) => this._resolveExhaustion(options),
80288
80614
  emitSwapAttempt: (fallback) => this._emitter.emit("onSwapAttempt", fallback),
@@ -80934,11 +81260,11 @@ async function promptForConfirmation(question) {
80934
81260
  stdin.setRawMode(true);
80935
81261
  stdin.resume();
80936
81262
  stdin.setEncoding("utf8");
80937
- let settled = false;
81263
+ let settled2 = false;
80938
81264
  const finish = (answer) => {
80939
- if (settled)
81265
+ if (settled2)
80940
81266
  return;
80941
- settled = true;
81267
+ settled2 = true;
80942
81268
  stdin.setRawMode(false);
80943
81269
  stdin.pause();
80944
81270
  stdin.removeListener("data", onData);
@@ -81197,11 +81523,11 @@ function read(message, echo, onEmptySubmit) {
81197
81523
  _authPromptDeps.write(`${source_default.cyan("?")} ${message} `);
81198
81524
  return new Promise((resolve28, reject) => {
81199
81525
  let buffer = "";
81200
- let settled = false;
81526
+ let settled2 = false;
81201
81527
  const cleanup = () => {
81202
- if (settled)
81528
+ if (settled2)
81203
81529
  return;
81204
- settled = true;
81530
+ settled2 = true;
81205
81531
  stdin.removeListener("data", onData);
81206
81532
  stdin.removeListener("end", onEnd);
81207
81533
  stdin.removeListener("error", onEnd);
@@ -81265,7 +81591,7 @@ function promptForSelect(message, choices) {
81265
81591
  `);
81266
81592
  return new Promise((resolve28, reject) => {
81267
81593
  let index = 0;
81268
- let settled = false;
81594
+ let settled2 = false;
81269
81595
  let drawn = false;
81270
81596
  const render3 = () => {
81271
81597
  if (drawn)
@@ -81280,9 +81606,9 @@ function promptForSelect(message, choices) {
81280
81606
  }
81281
81607
  };
81282
81608
  const cleanup = () => {
81283
- if (settled)
81609
+ if (settled2)
81284
81610
  return;
81285
- settled = true;
81611
+ settled2 = true;
81286
81612
  stdin.removeListener("data", onData);
81287
81613
  stdin.removeListener("end", onEnd);
81288
81614
  stdin.removeListener("error", onEnd);
@@ -81620,6 +81946,7 @@ var init_config_descriptions = __esm(() => {
81620
81946
  "execution.sandbox.filesystem": "Filesystem policy additions for the sandbox",
81621
81947
  "execution.sandbox.filesystem.allowWrite": "Extra literal paths the sandbox may write (no glob characters)",
81622
81948
  "execution.sandbox.filesystem.denyRead": "Extra literal paths the sandbox may not read (no glob characters)",
81949
+ "execution.sandbox.filesystem.allowSharedTmp": "Also grant the shared temp directories (os.tmpdir(), /tmp) to sandboxed commands. Off by default: temp writes stay inside this run's own temp root",
81623
81950
  "execution.sandbox.network": "Network policy for sandboxed commands",
81624
81951
  "execution.sandbox.network.allowedDomains": "Domains sandboxed commands may reach; absent means open",
81625
81952
  "execution.commandSafety": "Shadow command classifier; observes every command, decides nothing. Root-only (ADR-031)",
@@ -83022,7 +83349,12 @@ async function processPackageGroup(ctx, packageDir, groupStories, language, resu
83022
83349
  }
83023
83350
  await _hardeningDeps.writeFile(suggestedTestPath, testCode);
83024
83351
  const testCmd = buildAcceptanceRunCommand(suggestedTestPath, config2.project?.testFramework, config2.acceptance?.command, packageDir);
83025
- const proc = _hardeningDeps.spawn(testCmd, { cwd: packageDir, stdout: "pipe", stderr: "pipe", detached: true });
83352
+ const proc = _hardeningDeps.spawn(["/bin/sh", "-c", testCmd], {
83353
+ cwd: packageDir,
83354
+ stdout: "pipe",
83355
+ stderr: "pipe",
83356
+ detached: true
83357
+ });
83026
83358
  let exitedBeforeSigkill = false;
83027
83359
  let sigkillTimer;
83028
83360
  proc.exited.then(() => {
@@ -84233,13 +84565,13 @@ var init_generate = __esm(() => {
84233
84565
  });
84234
84566
 
84235
84567
  // src/cli/init-context.ts
84236
- import { mkdir as mkdir22, readdir as readdir7 } from "fs/promises";
84568
+ import { mkdir as mkdir23, readdir as readdir7 } from "fs/promises";
84237
84569
  import { basename as basename11, join as join76, relative as relative25, sep as sep13 } from "path";
84238
84570
  async function bunFileExists(path12) {
84239
84571
  return Bun.file(path12).exists();
84240
84572
  }
84241
84573
  async function mkdirp(path12) {
84242
- await mkdir22(path12, { recursive: true });
84574
+ await mkdir23(path12, { recursive: true });
84243
84575
  }
84244
84576
  async function findFiles(dir, maxFiles = 200) {
84245
84577
  const files = [];
@@ -84726,7 +85058,7 @@ __export(exports_init, {
84726
85058
  validateProjectName: () => validateProjectName
84727
85059
  });
84728
85060
  import { existsSync as existsSync22 } from "fs";
84729
- import { mkdir as mkdir23 } from "fs/promises";
85061
+ import { mkdir as mkdir24 } from "fs/promises";
84730
85062
  import { join as join78 } from "path";
84731
85063
  function validateProjectName(name) {
84732
85064
  if (!name)
@@ -84854,7 +85186,7 @@ async function initGlobal() {
84854
85186
  const logger = getLogger();
84855
85187
  const globalDir = globalConfigDir();
84856
85188
  if (!existsSync22(globalDir)) {
84857
- await mkdir23(globalDir, { recursive: true });
85189
+ await mkdir24(globalDir, { recursive: true });
84858
85190
  logger.info("init", "Created global config directory", { path: globalDir });
84859
85191
  }
84860
85192
  const configPath = join78(globalDir, "config.json");
@@ -84874,7 +85206,7 @@ async function initGlobal() {
84874
85206
  }
84875
85207
  const hooksDir = join78(globalDir, "hooks");
84876
85208
  if (!existsSync22(hooksDir)) {
84877
- await mkdir23(hooksDir, { recursive: true });
85209
+ await mkdir24(hooksDir, { recursive: true });
84878
85210
  logger.info("init", "Created global hooks directory", { path: hooksDir });
84879
85211
  } else {
84880
85212
  logger.info("init", "Global hooks directory already exists", { path: hooksDir });
@@ -84919,7 +85251,7 @@ async function initProject(projectRoot, options) {
84919
85251
  }
84920
85252
  }
84921
85253
  if (!existsSync22(projectDir)) {
84922
- await mkdir23(projectDir, { recursive: true });
85254
+ await mkdir24(projectDir, { recursive: true });
84923
85255
  logger.info("init", "Created project config directory", { path: projectDir });
84924
85256
  }
84925
85257
  const stack = detectStack(projectRoot);
@@ -84954,7 +85286,7 @@ async function initProject(projectRoot, options) {
84954
85286
  ["features", featuresDir(projectRoot)]
84955
85287
  ]) {
84956
85288
  if (!existsSync22(dir)) {
84957
- await mkdir23(dir, { recursive: true });
85289
+ await mkdir24(dir, { recursive: true });
84958
85290
  logger.info("init", `Created project ${label} directory`, { path: dir });
84959
85291
  } else {
84960
85292
  logger.info("init", `Project ${label} directory already exists`, { path: dir });
@@ -87696,11 +88028,11 @@ async function runGitBounded2(args, workdir) {
87696
88028
  let timedOut = false;
87697
88029
  let exitCode = -1;
87698
88030
  await new Promise((resolve29) => {
87699
- let settled = false;
88031
+ let settled2 = false;
87700
88032
  const finish = (code) => {
87701
- if (settled)
88033
+ if (settled2)
87702
88034
  return;
87703
- settled = true;
88035
+ settled2 = true;
87704
88036
  clearTimeout(timer);
87705
88037
  exitCode = code;
87706
88038
  resolve29();
@@ -88441,7 +88773,35 @@ function findingFile(file2) {
88441
88773
  return file2;
88442
88774
  }
88443
88775
  async function runFixReview(ctx, req, deps = {}) {
88444
- const d = { ...DEFAULT_DEPS2, ...deps };
88776
+ const verdict = await evaluateFixReview(ctx, req, { ...DEFAULT_DEPS2, ...deps });
88777
+ logVerdict(ctx.runtime?.logger, req.story.id, verdict);
88778
+ return verdict;
88779
+ }
88780
+ function logVerdict(logger, storyId, verdict) {
88781
+ if (verdict.kind === "pass") {
88782
+ if (verdict.reviewed) {
88783
+ logger?.info("review", "Fix review passed", { storyId, reason: verdict.reason });
88784
+ } else {
88785
+ logger?.info("review", `Fix review skipped \u2014 ${verdict.reason}`, { storyId });
88786
+ }
88787
+ return;
88788
+ }
88789
+ if (verdict.kind === "error") {
88790
+ logger?.warn("review", "Fix review errored", { storyId, reason: verdict.reason });
88791
+ return;
88792
+ }
88793
+ if (verdict.cause === "scope") {
88794
+ logger?.info("review", "Fix review failed (scope)", { storyId, files: verdict.files, reason: verdict.reason });
88795
+ return;
88796
+ }
88797
+ logger?.info("review", "Fix review failed (contradiction)", {
88798
+ storyId,
88799
+ reason: verdict.reason,
88800
+ acIndex: verdict.acIndex,
88801
+ file: verdict.file
88802
+ });
88803
+ }
88804
+ async function evaluateFixReview(ctx, req, d) {
88445
88805
  const logger = ctx.runtime?.logger;
88446
88806
  if (req.config.fixReview?.enabled === false) {
88447
88807
  return { kind: "pass", reviewed: false, reason: "fixReview is disabled" };
@@ -88517,6 +88877,11 @@ async function runFixReview(ctx, req, deps = {}) {
88517
88877
  return { kind: "error", reason: `diff failed: ${err instanceof Error ? err.message : String(err)}` };
88518
88878
  }
88519
88879
  const diff = truncateDiff(rawDiff);
88880
+ logger?.info("review", "Running fix review", {
88881
+ storyId: req.story.id,
88882
+ fixFiles: fixFiles.length,
88883
+ storyFiles: storyFiles?.length
88884
+ });
88520
88885
  let output;
88521
88886
  try {
88522
88887
  output = await d.callOp(ctx, fixReviewOp, { story: req.story, diff, findings: req.findings });
@@ -88720,6 +89085,8 @@ function logUnifiedReviewPhaseResult(storyId, opName, output) {
88720
89085
  const payload = toReviewDecisionPayload(opName, output);
88721
89086
  if (!payload)
88722
89087
  return;
89088
+ if (payload.reviewer === "fix")
89089
+ return;
88723
89090
  if (!payload.parsed) {
88724
89091
  if (payload.noDispatch) {
88725
89092
  logger?.warn("review", `${payload.reviewer} review had no dispatch \u2014 failed closed`, { storyId });
@@ -88731,7 +89098,7 @@ function logUnifiedReviewPhaseResult(storyId, opName, output) {
88731
89098
  return;
88732
89099
  }
88733
89100
  const findingsCount = payload.result.findings.length;
88734
- const title = payload.reviewer === "semantic" ? "Semantic review" : payload.reviewer === "adversarial" ? "Adversarial review" : "Fix review";
89101
+ const title = payload.reviewer === "semantic" ? "Semantic review" : "Adversarial review";
88735
89102
  if (payload.passed) {
88736
89103
  logger?.info("review", `${title} passed`, { storyId });
88737
89104
  return;
@@ -91782,8 +92149,8 @@ var init_ensure_package_dirs = __esm(() => {
91782
92149
  }
91783
92150
  },
91784
92151
  mkdirp: async (p) => {
91785
- const { mkdir: mkdir24 } = await import("fs/promises");
91786
- await mkdir24(p, { recursive: true });
92152
+ const { mkdir: mkdir25 } = await import("fs/promises");
92153
+ await mkdir25(p, { recursive: true });
91787
92154
  }
91788
92155
  };
91789
92156
  });
@@ -91940,7 +92307,7 @@ var init_quote_integrity = __esm(() => {
91940
92307
  });
91941
92308
 
91942
92309
  // src/execution/progress.ts
91943
- import { appendFile as appendFile5, mkdir as mkdir24 } from "fs/promises";
92310
+ import { appendFile as appendFile5, mkdir as mkdir25 } from "fs/promises";
91944
92311
  import { join as join86 } from "path";
91945
92312
  function getSafeLogger2() {
91946
92313
  try {
@@ -91951,7 +92318,7 @@ function getSafeLogger2() {
91951
92318
  }
91952
92319
  async function appendProgress(featureDir2, storyId, status, message) {
91953
92320
  try {
91954
- await mkdir24(featureDir2, { recursive: true });
92321
+ await mkdir25(featureDir2, { recursive: true });
91955
92322
  const progressPath = join86(featureDir2, "progress.txt");
91956
92323
  const timestamp = new Date().toISOString();
91957
92324
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
@@ -92527,7 +92894,7 @@ var init_lock2 = __esm(() => {
92527
92894
  });
92528
92895
 
92529
92896
  // src/execution/feature-lock.ts
92530
- import { mkdir as mkdir25, rename as rename6, unlink as unlink6 } from "fs/promises";
92897
+ import { mkdir as mkdir26, rename as rename6, unlink as unlink6 } from "fs/promises";
92531
92898
  import { hostname as hostname4 } from "os";
92532
92899
  import path14 from "path";
92533
92900
  function validateFeatureId2(featureId) {
@@ -92621,7 +92988,7 @@ function emptyHolder(feature) {
92621
92988
  async function acquireFeatureLock(args) {
92622
92989
  const lockPath = _featureLockDeps.featureLockPath(args.outputDir, args.feature);
92623
92990
  const featureDir2 = path14.dirname(lockPath);
92624
- await mkdir25(featureDir2, { recursive: true });
92991
+ await mkdir26(featureDir2, { recursive: true });
92625
92992
  const lockFile = Bun.file(lockPath);
92626
92993
  if (await lockFile.exists()) {
92627
92994
  const existingContent = await lockFile.text();
@@ -93183,7 +93550,7 @@ var init_worktree_id = __esm(() => {
93183
93550
 
93184
93551
  // src/worktree/manager.ts
93185
93552
  import { existsSync as existsSync23, symlinkSync } from "fs";
93186
- import { mkdir as mkdir26, stat as stat7 } from "fs/promises";
93553
+ import { mkdir as mkdir27, stat as stat7 } from "fs/promises";
93187
93554
  import { join as join89, resolve as resolve30 } from "path";
93188
93555
  async function resolveGitInfoDir(projectRoot) {
93189
93556
  const dotGit = join89(projectRoot, ".git");
@@ -93218,7 +93585,7 @@ class WorktreeManager {
93218
93585
  try {
93219
93586
  const infoDir = await resolveGitInfoDir(projectRoot);
93220
93587
  const excludePath = join89(infoDir, "exclude");
93221
- await mkdir26(infoDir, { recursive: true });
93588
+ await mkdir27(infoDir, { recursive: true });
93222
93589
  const { withPathFileLock: withPathFileLock2 } = await Promise.resolve().then(() => (init_path_file_lock(), exports_path_file_lock));
93223
93590
  await withPathFileLock2(excludePath, async () => {
93224
93591
  let existing2 = "";
@@ -93832,6 +94199,54 @@ function areAllStoriesComplete(ctx) {
93832
94199
  const totalComplete = counts.passed + counts.failed + counts.skipped;
93833
94200
  return totalComplete === counts.total;
93834
94201
  }
94202
+ function recordPackageCrash(params) {
94203
+ const {
94204
+ logger,
94205
+ storyId,
94206
+ exitCode,
94207
+ packageDir,
94208
+ testPath,
94209
+ testFramework,
94210
+ commandOverride,
94211
+ testCmd,
94212
+ output,
94213
+ allFailedACs,
94214
+ allFindings,
94215
+ failedPackages,
94216
+ errorState
94217
+ } = params;
94218
+ const notRunnable = isCommandNotRunnable(exitCode);
94219
+ if (notRunnable) {
94220
+ logger.error("acceptance", "Acceptance command could not run \u2014 check acceptance.command", {
94221
+ storyId,
94222
+ exitCode,
94223
+ cmd: testCmd,
94224
+ packageDir
94225
+ });
94226
+ } else {
94227
+ logger.error("acceptance", "Tests errored with no AC failures parsed", {
94228
+ storyId,
94229
+ exitCode,
94230
+ packageDir
94231
+ });
94232
+ }
94233
+ logTestOutput(logger, "acceptance", output);
94234
+ errorState.anyError = true;
94235
+ errorState.errorExitCode = exitCode;
94236
+ allFailedACs.push("AC-ERROR");
94237
+ allFindings.push(notRunnable ? {
94238
+ ...acSentinelToFinding("AC-ERROR", output),
94239
+ message: `Acceptance command could not run (exit ${exitCode}): ${testCmd}`
94240
+ } : acSentinelToFinding("AC-ERROR", output));
94241
+ failedPackages.push({
94242
+ testPath,
94243
+ packageDir,
94244
+ testFramework,
94245
+ commandOverride,
94246
+ output,
94247
+ failedACs: ["AC-ERROR"]
94248
+ });
94249
+ }
93835
94250
  var _acceptanceStageDeps, acceptanceStage;
93836
94251
  var init_acceptance3 = __esm(() => {
93837
94252
  init_acceptance2();
@@ -93888,8 +94303,7 @@ var init_acceptance3 = __esm(() => {
93888
94303
  const failedPackages = [];
93889
94304
  const missingTargets = [];
93890
94305
  const allOutputParts = [];
93891
- let anyError = false;
93892
- let errorExitCode = 0;
94306
+ const errorState = { anyError: false, errorExitCode: 0 };
93893
94307
  let hardeningPromoted = 0;
93894
94308
  for (const { testPath, packageDir, testFramework, commandOverride, storyCount, acceptanceEnabled } of testGroups) {
93895
94309
  const testFile = Bun.file(testPath);
@@ -93929,13 +94343,13 @@ var init_acceptance3 = __esm(() => {
93929
94343
  }
93930
94344
  const resolvedFramework = testFramework ?? ctx.config.project?.testFramework;
93931
94345
  const resolvedCommand = commandOverride ?? ctx.config.acceptance.command;
93932
- const testCmdParts = buildAcceptanceRunCommand(testPath, resolvedFramework, resolvedCommand, packageDir);
94346
+ const testCmd = buildAcceptanceRunCommand(testPath, resolvedFramework, resolvedCommand, packageDir);
93933
94347
  logger.info("acceptance", "Running acceptance command", {
93934
94348
  storyId: ctx.story.id,
93935
- cmd: testCmdParts.join(" "),
94349
+ cmd: testCmd,
93936
94350
  packageDir
93937
94351
  });
93938
- const execution = await executeWithTimeout(testCmdParts.map(shellQuoteArg).join(" "), Math.ceil(ctx.config.acceptance.timeoutMs / 1000), undefined, { cwd: packageDir });
94352
+ const execution = await executeWithTimeout(testCmd, Math.ceil(ctx.config.acceptance.timeoutMs / 1000), undefined, { cwd: packageDir });
93939
94353
  const exitCode = execution.exitCode ?? (execution.success ? 0 : 1);
93940
94354
  const output = execution.output ?? "";
93941
94355
  allOutputParts.push(output);
@@ -93951,17 +94365,21 @@ var init_acceptance3 = __esm(() => {
93951
94365
  });
93952
94366
  }
93953
94367
  if (failedACs.length === 0 && exitCode !== 0) {
93954
- logger.error("acceptance", "Tests errored with no AC failures parsed", {
94368
+ recordPackageCrash({
94369
+ logger,
93955
94370
  storyId: ctx.story.id,
93956
94371
  exitCode,
93957
- packageDir
94372
+ packageDir,
94373
+ testPath,
94374
+ testFramework,
94375
+ commandOverride,
94376
+ testCmd,
94377
+ output,
94378
+ allFailedACs,
94379
+ allFindings,
94380
+ failedPackages,
94381
+ errorState
93958
94382
  });
93959
- logTestOutput(logger, "acceptance", output);
93960
- anyError = true;
93961
- errorExitCode = exitCode;
93962
- allFailedACs.push("AC-ERROR");
93963
- allFindings.push(acSentinelToFinding("AC-ERROR", output));
93964
- failedPackages.push({ testPath, packageDir, testFramework, commandOverride, output, failedACs: ["AC-ERROR"] });
93965
94383
  continue;
93966
94384
  }
93967
94385
  if (exitCode !== 0 && failedACs.length > 0 && actualFailures.length === 0) {
@@ -93976,8 +94394,8 @@ var init_acceptance3 = __esm(() => {
93976
94394
  acTaggedFailures: taggedFailureCount
93977
94395
  });
93978
94396
  logTestOutput(logger, "acceptance", output);
93979
- anyError = true;
93980
- errorExitCode = exitCode;
94397
+ errorState.anyError = true;
94398
+ errorState.errorExitCode = exitCode;
93981
94399
  allFailedACs.push("AC-ERROR");
93982
94400
  allFindings.push(acSentinelToFinding("AC-ERROR", output));
93983
94401
  failedPackages.push({
@@ -94097,10 +94515,10 @@ var init_acceptance3 = __esm(() => {
94097
94515
  hardeningPromoted,
94098
94516
  durationMs
94099
94517
  });
94100
- if (anyError) {
94518
+ if (errorState.anyError) {
94101
94519
  return {
94102
94520
  action: "fail",
94103
- reason: `Acceptance tests errored (exit code ${errorExitCode}): syntax error, import failure, or unhandled exception`
94521
+ reason: `Acceptance tests errored (exit code ${errorState.errorExitCode}): syntax error, import failure, or unhandled exception`
94104
94522
  };
94105
94523
  }
94106
94524
  return {
@@ -94165,12 +94583,21 @@ async function runAcceptanceRedGate(ctx, entries, deps) {
94165
94583
  const runCmd = buildAcceptanceRunCommand(testPath, testFramework, commandOverride, packageDir);
94166
94584
  logger?.info("acceptance-setup", "Running acceptance RED gate command", {
94167
94585
  storyId: entry.storyId,
94168
- cmd: runCmd.join(" "),
94586
+ cmd: runCmd,
94169
94587
  packageDir
94170
94588
  });
94171
94589
  const first = await deps.runTest(testPath, packageDir, runCmd, config2.acceptance.timeoutMs);
94172
94590
  if (first.exitCode === 0)
94173
94591
  continue;
94592
+ if (isCommandNotRunnable(first.exitCode)) {
94593
+ logger?.error("acceptance-setup", "RED gate: acceptance command could not run \u2014 check acceptance.command", {
94594
+ storyId: entry.storyId,
94595
+ cmd: runCmd,
94596
+ exitCode: first.exitCode
94597
+ });
94598
+ redFailCount++;
94599
+ continue;
94600
+ }
94174
94601
  if (isCrash(first.output, first.exitCode)) {
94175
94602
  await handleCrash(ctx, entry, first.output, runCmd, deps);
94176
94603
  }
@@ -94533,7 +94960,7 @@ var init_acceptance_setup = __esm(() => {
94533
94960
  autoCommitIfDirty,
94534
94961
  loadGroupConfig: loadConfigForPackage,
94535
94962
  runTest: async (_testPath, _workdir, _cmd, timeoutMs = 1800000) => {
94536
- const execution = await executeWithTimeout(_cmd.map(shellQuoteArg).join(" "), Math.ceil(timeoutMs / 1000), undefined, {
94963
+ const execution = await executeWithTimeout(_cmd, Math.ceil(timeoutMs / 1000), undefined, {
94537
94964
  cwd: _workdir
94538
94965
  });
94539
94966
  return {
@@ -96101,11 +96528,11 @@ async function closeStaleAcpSession(worktreePath, sessionName) {
96101
96528
  });
96102
96529
  let timedOut = false;
96103
96530
  await new Promise((resolve31) => {
96104
- let settled = false;
96531
+ let settled2 = false;
96105
96532
  const finish = () => {
96106
- if (settled)
96533
+ if (settled2)
96107
96534
  return;
96108
- settled = true;
96535
+ settled2 = true;
96109
96536
  clearTimeout(timer);
96110
96537
  resolve31();
96111
96538
  };
@@ -102107,11 +102534,11 @@ function renderProposals(proposals, runId, observationCount, provenance) {
102107
102534
  }
102108
102535
 
102109
102536
  // src/plugins/builtin/curator/rollup.ts
102110
- import { appendFile as appendFile7, mkdir as mkdir27, writeFile as writeFile11 } from "fs/promises";
102537
+ import { appendFile as appendFile7, mkdir as mkdir28, writeFile as writeFile11 } from "fs/promises";
102111
102538
  import * as path21 from "path";
102112
102539
  async function appendToRollup(observations, rollupPath) {
102113
102540
  const dir = path21.dirname(rollupPath);
102114
- await mkdir27(dir, { recursive: true }).catch(() => {});
102541
+ await mkdir28(dir, { recursive: true }).catch(() => {});
102115
102542
  const { withPathFileLock: withPathFileLock2 } = await Promise.resolve().then(() => (init_path_file_lock(), exports_path_file_lock));
102116
102543
  await withPathFileLock2(rollupPath, () => appendToRollupUnlocked(observations, rollupPath)).catch(() => {});
102117
102544
  }
@@ -102198,7 +102625,7 @@ var init_rollup2 = __esm(() => {
102198
102625
  });
102199
102626
 
102200
102627
  // src/plugins/builtin/curator/index.ts
102201
- import { mkdir as mkdir28 } from "fs/promises";
102628
+ import { mkdir as mkdir29 } from "fs/promises";
102202
102629
  import * as path22 from "path";
102203
102630
  function getCuratorEnabled(context) {
102204
102631
  const cfg = context.config;
@@ -102276,7 +102703,7 @@ var init_curator = __esm(() => {
102276
102703
  if (context.outputDir) {
102277
102704
  const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
102278
102705
  const runDir = path22.dirname(observationsPath);
102279
- await mkdir28(runDir, { recursive: true });
102706
+ await mkdir29(runDir, { recursive: true });
102280
102707
  await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
102281
102708
  `) + (observations.length > 0 ? `
102282
102709
  ` : ""));
@@ -102364,10 +102791,52 @@ var init_plugin_logger = __esm(() => {
102364
102791
  });
102365
102792
 
102366
102793
  // src/plugins/registry.ts
102794
+ function warnPluginRegistration(pluginName, message, data) {
102795
+ getSafeLogger()?.warn("plugins", `Plugin '${pluginName}' ${message}`, { plugin: pluginName, ...data });
102796
+ }
102797
+ function stagePluginHandlers(pluginName, provider2) {
102798
+ const staged = [];
102799
+ let failed = false;
102800
+ const on = (event, handler) => {
102801
+ if (!LOOP_EVENT_NAMES.has(event)) {
102802
+ failed = true;
102803
+ warnPluginRegistration(pluginName, `registered a handler for unknown loop event '${event}'`, { event });
102804
+ return;
102805
+ }
102806
+ if (typeof handler !== "function") {
102807
+ failed = true;
102808
+ warnPluginRegistration(pluginName, `registered a non-function handler for '${event}'`, {
102809
+ event,
102810
+ handlerType: typeof handler
102811
+ });
102812
+ return;
102813
+ }
102814
+ staged.push({ plugin: pluginName, event, handler });
102815
+ };
102816
+ let returned;
102817
+ try {
102818
+ returned = provider2.register(on);
102819
+ } catch (err) {
102820
+ failed = true;
102821
+ warnPluginRegistration(pluginName, "loop-handler registration failed; its handlers are skipped", {
102822
+ error: errorMessage(err)
102823
+ });
102824
+ }
102825
+ if (isThenable(returned)) {
102826
+ failed = true;
102827
+ warnPluginRegistration(pluginName, "register() returned a promise; loop-handler registration must be synchronous");
102828
+ returned.then(undefined, (err) => {
102829
+ warnPluginRegistration(pluginName, "async register() rejected", { error: errorMessage(err) });
102830
+ });
102831
+ }
102832
+ return failed ? [] : staged;
102833
+ }
102834
+
102367
102835
  class PluginRegistry {
102368
102836
  plugins;
102369
102837
  sources;
102370
102838
  builtinPostRunActions;
102839
+ loopHandlers;
102371
102840
  constructor(loadedPlugins, builtinPostRunActions = []) {
102372
102841
  this.builtinPostRunActions = builtinPostRunActions.map((registration) => ("action" in registration) ? registration : { pluginName: registration.name, action: registration });
102373
102842
  if (loadedPlugins.length > 0 && "plugin" in loadedPlugins[0]) {
@@ -102417,6 +102886,25 @@ class PluginRegistry {
102417
102886
  });
102418
102887
  return [...pluginActions, ...this.builtinPostRunActions];
102419
102888
  }
102889
+ getLoopHandlers() {
102890
+ if (this.loopHandlers !== undefined) {
102891
+ return this.loopHandlers;
102892
+ }
102893
+ const entries = [];
102894
+ for (const plugin of this.plugins) {
102895
+ if (!plugin.provides.includes("loop-handlers")) {
102896
+ continue;
102897
+ }
102898
+ const provider2 = plugin.extensions.loopHandlers;
102899
+ if (provider2 === undefined) {
102900
+ warnPluginRegistration(plugin.name, "provides 'loop-handlers' but declares no loopHandlers extension");
102901
+ continue;
102902
+ }
102903
+ entries.push(...stagePluginHandlers(plugin.name, provider2));
102904
+ }
102905
+ this.loopHandlers = Object.freeze(entries);
102906
+ return this.loopHandlers;
102907
+ }
102420
102908
  async teardownAll() {
102421
102909
  const logger = getSafeLogger();
102422
102910
  for (const plugin of this.plugins) {
@@ -102430,8 +102918,11 @@ class PluginRegistry {
102430
102918
  }
102431
102919
  }
102432
102920
  }
102921
+ var LOOP_EVENT_NAMES;
102433
102922
  var init_registry5 = __esm(() => {
102923
+ init_loop_events();
102434
102924
  init_logger2();
102925
+ LOOP_EVENT_NAMES = new Set(LOOP_EVENTS);
102435
102926
  });
102436
102927
 
102437
102928
  // src/plugins/validator.ts
@@ -102507,6 +102998,8 @@ function validateExtension(pluginName, type, extensions) {
102507
102998
  return validateReporter(pluginName, extensions.reporter);
102508
102999
  case "post-run-action":
102509
103000
  return validatePostRunAction(pluginName, extensions.postRunAction);
103001
+ case "loop-handlers":
103002
+ return validateLoopHandlers(pluginName, extensions.loopHandlers);
102510
103003
  default:
102511
103004
  getSafeLogger6()?.warn("plugins", `Plugin '${pluginName}' validation failed: unknown extension type '${type}'`);
102512
103005
  return false;
@@ -102680,6 +103173,17 @@ function validatePostRunAction(pluginName, action) {
102680
103173
  }
102681
103174
  return true;
102682
103175
  }
103176
+ function validateLoopHandlers(pluginName, ext) {
103177
+ if (typeof ext !== "object" || ext === null) {
103178
+ getSafeLogger6()?.warn("plugins", `Plugin '${pluginName}' validation failed: loopHandlers extension must be an object`);
103179
+ return false;
103180
+ }
103181
+ if (typeof ext.register !== "function") {
103182
+ getSafeLogger6()?.warn("plugins", `Plugin '${pluginName}' validation failed: loopHandlers.register must be a function`);
103183
+ return false;
103184
+ }
103185
+ return true;
103186
+ }
102683
103187
  var VALID_PLUGIN_TYPES;
102684
103188
  var init_validator = __esm(() => {
102685
103189
  init_logger2();
@@ -102690,7 +103194,8 @@ var init_validator = __esm(() => {
102690
103194
  "reviewer",
102691
103195
  "context-provider",
102692
103196
  "reporter",
102693
- "post-run-action"
103197
+ "post-run-action",
103198
+ "loop-handlers"
102694
103199
  ];
102695
103200
  });
102696
103201
 
@@ -103846,6 +104351,7 @@ async function initializeAfterLock(options) {
103846
104351
  const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
103847
104352
  const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
103848
104353
  const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn, config2.reporters);
104354
+ runtime.sessionManager.configureLoopHandlers(pluginRegistry.getLoopHandlers());
103849
104355
  logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
103850
104356
  plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
103851
104357
  });
@@ -104795,11 +105301,14 @@ var init_logs2 = __esm(() => {
104795
105301
  // src/commands/migrate.ts
104796
105302
  var exports_migrate = {};
104797
105303
  __export(exports_migrate, {
105304
+ _gitDeps: () => _gitDeps,
105305
+ autoMigrateGeneratedContent: () => autoMigrateGeneratedContent,
104798
105306
  detectGeneratedContent: () => detectGeneratedContent,
104799
- migrateCommand: () => migrateCommand
105307
+ migrateCommand: () => migrateCommand,
105308
+ partitionTrackedCandidates: () => partitionTrackedCandidates
104800
105309
  });
104801
105310
  import { existsSync as existsSync30 } from "fs";
104802
- import { mkdir as mkdir29, readdir as readdir10, rename as rename9 } from "fs/promises";
105311
+ import { mkdir as mkdir30, readdir as readdir10, rename as rename9 } from "fs/promises";
104803
105312
  import path25 from "path";
104804
105313
  async function detectGeneratedContent(naxDir) {
104805
105314
  if (!existsSync30(naxDir))
@@ -104868,6 +105377,102 @@ async function detectGeneratedContent(naxDir) {
104868
105377
  }
104869
105378
  return candidates;
104870
105379
  }
105380
+ async function partitionTrackedCandidates(workdir, candidates) {
105381
+ const logger = getSafeLogger();
105382
+ let stdout;
105383
+ try {
105384
+ const result = await gitWithTimeout(["ls-files", "-z", "--", ".nax"], workdir);
105385
+ if (result.exitCode !== 0) {
105386
+ logger?.debug("migrate", "partitionTrackedCandidates: git ls-files returned non-zero, treating all candidates as migratable", {
105387
+ storyId: "_migrate",
105388
+ exitCode: result.exitCode,
105389
+ stderr: result.stderr.trim()
105390
+ });
105391
+ return { migratable: [...candidates], tracked: [] };
105392
+ }
105393
+ stdout = result.stdout;
105394
+ } catch (err) {
105395
+ logger?.debug("migrate", "partitionTrackedCandidates: git ls-files failed, treating all candidates as migratable", {
105396
+ storyId: "_migrate",
105397
+ error: err instanceof Error ? err.message : String(err)
105398
+ });
105399
+ return { migratable: [...candidates], tracked: [] };
105400
+ }
105401
+ const listedPaths = [];
105402
+ for (const listed of stdout.split("\x00")) {
105403
+ if (!listed || listed === ".nax")
105404
+ continue;
105405
+ if (listed.startsWith(".nax/"))
105406
+ listedPaths.push(listed);
105407
+ }
105408
+ const migratable = [];
105409
+ const trackedOut = [];
105410
+ for (const candidate of candidates) {
105411
+ const normalisedName = candidate.name.split("\\").join("/");
105412
+ const target = `.nax/${normalisedName}`;
105413
+ const targetWithSlash = `${target}/`;
105414
+ const isTracked = listedPaths.some((listed) => listed === target || listed.startsWith(targetWithSlash));
105415
+ if (isTracked) {
105416
+ trackedOut.push(candidate);
105417
+ } else {
105418
+ migratable.push(candidate);
105419
+ }
105420
+ }
105421
+ return { migratable, tracked: trackedOut };
105422
+ }
105423
+ async function autoMigrateGeneratedContent(workdir) {
105424
+ const logger = getSafeLogger();
105425
+ let candidates;
105426
+ try {
105427
+ candidates = await detectGeneratedContent(path25.join(workdir, ".nax"));
105428
+ } catch (err) {
105429
+ logger?.debug("migrate", "autoMigrateGeneratedContent: detectGeneratedContent failed, skipping migration", {
105430
+ storyId: "_setup",
105431
+ error: err instanceof Error ? err.message : String(err)
105432
+ });
105433
+ return;
105434
+ }
105435
+ if (candidates.length === 0)
105436
+ return;
105437
+ let partition;
105438
+ try {
105439
+ partition = await partitionTrackedCandidates(workdir, candidates);
105440
+ } catch (err) {
105441
+ logger?.debug("migrate", "autoMigrateGeneratedContent: partitionTrackedCandidates failed, treating all candidates as migratable", {
105442
+ storyId: "_setup",
105443
+ error: err instanceof Error ? err.message : String(err)
105444
+ });
105445
+ partition = { migratable: candidates, tracked: [] };
105446
+ }
105447
+ const { migratable, tracked } = partition;
105448
+ if (tracked.length > 0) {
105449
+ const paths = tracked.map((candidate) => path25.relative(workdir, candidate.srcPath)).slice(0, 5);
105450
+ const firstPath = paths[0];
105451
+ if (firstPath !== undefined) {
105452
+ logger?.warn("setup", "Skipping git-tracked generated content under .nax/ \u2014 untrack it with git rm -r --cached", {
105453
+ storyId: "_setup",
105454
+ count: tracked.length,
105455
+ paths,
105456
+ fix: `git rm -r --cached ${firstPath}`
105457
+ });
105458
+ }
105459
+ }
105460
+ if (migratable.length === 0)
105461
+ return;
105462
+ logger?.info("setup", "Found generated content under .nax/ \u2014 migrating to output dir", {
105463
+ storyId: "_setup",
105464
+ count: migratable.length
105465
+ });
105466
+ try {
105467
+ await migrateCommand({ workdir });
105468
+ logger?.info("setup", "Auto-migration complete", { storyId: "_setup" });
105469
+ } catch (err) {
105470
+ logger?.warn("setup", "Auto-migration failed \u2014 continuing without migration", {
105471
+ storyId: "_setup",
105472
+ error: err instanceof Error ? err.message : String(err)
105473
+ });
105474
+ }
105475
+ }
104871
105476
  async function migrateCommand(options) {
104872
105477
  const logger = getLogger();
104873
105478
  if (options.reclaim) {
@@ -104887,7 +105492,7 @@ async function migrateCommand(options) {
104887
105492
  }
104888
105493
  const archiveBase = path25.join(globalConfigDir(), "_archive");
104889
105494
  const archiveDest = path25.join(archiveBase, `${options.reclaim}-${Date.now()}`);
104890
- await mkdir29(archiveBase, { recursive: true });
105495
+ await mkdir30(archiveBase, { recursive: true });
104891
105496
  await rename9(src, archiveDest);
104892
105497
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
104893
105498
  return;
@@ -104950,19 +105555,36 @@ async function migrateCommand(options) {
104950
105555
  logger.info("migrate", "Nothing to migrate \u2014 already up to date", { storyId: "_migrate" });
104951
105556
  return;
104952
105557
  }
105558
+ const { migratable, tracked } = await partitionTrackedCandidates(options.workdir, candidates);
105559
+ if (migratable.length === 0) {
105560
+ for (const c of tracked) {
105561
+ const rel = path25.relative(options.workdir, c.srcPath);
105562
+ const msg = options.dryRun ? `[dry-run] Skip (git-tracked): ${rel}` : `Skipping git-tracked: ${rel}`;
105563
+ logger.info("migrate", msg, { storyId: "_migrate" });
105564
+ }
105565
+ return;
105566
+ }
104953
105567
  if (options.dryRun) {
104954
- for (const c of candidates) {
105568
+ for (const c of tracked) {
105569
+ const rel = path25.relative(options.workdir, c.srcPath);
105570
+ logger.info("migrate", `[dry-run] Skip (git-tracked): ${rel}`, { storyId: "_migrate" });
105571
+ }
105572
+ for (const c of migratable) {
104955
105573
  logger.info("migrate", `[dry-run] Would move: ${c.srcPath} -> ${path25.join(destBase, c.name)}`, {
104956
105574
  storyId: "_migrate"
104957
105575
  });
104958
105576
  }
104959
105577
  return;
104960
105578
  }
104961
- await mkdir29(destBase, { recursive: true });
105579
+ for (const c of tracked) {
105580
+ const rel = path25.relative(options.workdir, c.srcPath);
105581
+ logger.info("migrate", `Skipping git-tracked: ${rel}`, { storyId: "_migrate" });
105582
+ }
105583
+ await mkdir30(destBase, { recursive: true });
104962
105584
  let moved = 0;
104963
- for (const candidate of candidates) {
105585
+ for (const candidate of migratable) {
104964
105586
  const dest = path25.join(destBase, candidate.name);
104965
- await mkdir29(path25.dirname(dest), { recursive: true });
105587
+ await mkdir30(path25.dirname(dest), { recursive: true });
104966
105588
  if (existsSync30(dest)) {
104967
105589
  throw new NaxError(`Migration conflict: destination already exists.
104968
105590
  Source: ${candidate.srcPath}
@@ -104992,11 +105614,13 @@ async function migrateCommand(options) {
104992
105614
  moved++;
104993
105615
  logger.info("migrate", `Moved: ${candidate.name}`, { storyId: "_migrate" });
104994
105616
  }
104995
- await Bun.write(path25.join(destBase, ".migrated-from"), JSON.stringify({ from: options.workdir, migratedAt: new Date().toISOString() }, null, 2));
104996
- logger.info("migrate", `Migration complete: ${moved} entries moved`, {
104997
- storyId: "_migrate",
104998
- destBase
104999
- });
105617
+ if (moved > 0) {
105618
+ await Bun.write(path25.join(destBase, ".migrated-from"), JSON.stringify({ from: options.workdir, migratedAt: new Date().toISOString() }, null, 2));
105619
+ logger.info("migrate", `Migration complete: ${moved} entries moved`, {
105620
+ storyId: "_migrate",
105621
+ destBase
105622
+ });
105623
+ }
105000
105624
  }
105001
105625
  var GENERATED_NAMES, GENERATED_FEATURE_SUBNAMES;
105002
105626
  var init_migrate = __esm(() => {
@@ -105005,7 +105629,9 @@ var init_migrate = __esm(() => {
105005
105629
  init_errors();
105006
105630
  init_logger2();
105007
105631
  init_runtime2();
105632
+ init_git();
105008
105633
  init_git_env();
105634
+ init_git();
105009
105635
  GENERATED_NAMES = new Set([
105010
105636
  "runs",
105011
105637
  "prompt-audit",
@@ -105780,7 +106406,7 @@ var init_runs = __esm(() => {
105780
106406
  });
105781
106407
 
105782
106408
  // src/commands/unlock.ts
105783
- import { mkdir as mkdir30, unlink as unlink10 } from "fs/promises";
106409
+ import { mkdir as mkdir31, unlink as unlink10 } from "fs/promises";
105784
106410
  import { basename as basename14, join as join112 } from "path";
105785
106411
  function logUnlockInfo(message) {
105786
106412
  _unlockDeps.getSafeLogger()?.info(UNLOCK_LOG_STAGE, message);
@@ -105855,7 +106481,7 @@ async function unlockCommand(options) {
105855
106481
  const workdir = options.dir ?? process.cwd();
105856
106482
  if (options.feature !== undefined && options.feature.length > 0) {
105857
106483
  const { outputDir } = await resolveOutputDir(workdir);
105858
- await mkdir30(join112(outputDir, "features"), { recursive: true });
106484
+ await mkdir31(join112(outputDir, "features"), { recursive: true });
105859
106485
  const outcome = await tryRemoveFeatureLock(outputDir, options.feature, options.force ?? false);
105860
106486
  if (outcome === "skipped" || outcome === "parse-error") {
105861
106487
  process.exit(1);
@@ -105965,6 +106591,7 @@ __export(exports_commands, {
105965
106591
  _curatorCmdDeps: () => _curatorCmdDeps,
105966
106592
  _replayCmdDeps: () => _replayCmdDeps,
105967
106593
  _resumeCmdDeps: () => _resumeCmdDeps,
106594
+ autoMigrateGeneratedContent: () => autoMigrateGeneratedContent,
105968
106595
  curatorCommit: () => curatorCommit,
105969
106596
  curatorDryrun: () => curatorDryrun,
105970
106597
  curatorGc: () => curatorGc,
@@ -105973,6 +106600,7 @@ __export(exports_commands, {
105973
106600
  followLogs: () => followLogs,
105974
106601
  logsCommand: () => logsCommand,
105975
106602
  migrateCommand: () => migrateCommand,
106603
+ partitionTrackedCandidates: () => partitionTrackedCandidates,
105976
106604
  precheckCommand: () => precheckCommand,
105977
106605
  registerReplayCommand: () => registerReplayCommand,
105978
106606
  registerResumeCommand: () => registerResumeCommand,
@@ -106310,26 +106938,7 @@ async function setupRun(options) {
106310
106938
  const prd = await loadPRD(prdPath);
106311
106939
  const interactionChain = await initInteractionChain(config2, headless);
106312
106940
  statusWriter.setPrd(prd);
106313
- {
106314
- const { detectGeneratedContent: detectGeneratedContent2, migrateCommand: migrateCommand2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
106315
- const naxDir = path27.join(workdir, ".nax");
106316
- const candidates = await detectGeneratedContent2(naxDir).catch(() => []);
106317
- if (candidates.length > 0) {
106318
- logger?.info("setup", "Found generated content under .nax/ \u2014 migrating to output dir", {
106319
- storyId: "_setup",
106320
- count: candidates.length
106321
- });
106322
- try {
106323
- await migrateCommand2({ workdir });
106324
- logger?.info("setup", "Auto-migration complete", { storyId: "_setup" });
106325
- } catch (err) {
106326
- logger?.warn("setup", "Auto-migration failed \u2014 continuing without migration", {
106327
- storyId: "_setup",
106328
- error: err instanceof Error ? err.message : String(err)
106329
- });
106330
- }
106331
- }
106332
- }
106941
+ await _runSetupDeps.autoMigrateGeneratedContent(workdir);
106333
106942
  {
106334
106943
  const { claimProjectIdentity: claimProjectIdentity2 } = await Promise.resolve().then(() => (init_runtime2(), exports_runtime));
106335
106944
  let remoteUrl = null;
@@ -106490,7 +107099,11 @@ var init_run_setup = __esm(() => {
106490
107099
  sweepFeatureTranscripts,
106491
107100
  buildApprovalsSeal,
106492
107101
  acquireLock,
106493
- acquireFeatureLock
107102
+ acquireFeatureLock,
107103
+ async autoMigrateGeneratedContent(workdir) {
107104
+ const { autoMigrateGeneratedContent: autoMigrateGeneratedContent2 } = await Promise.resolve().then(() => (init_commands(), exports_commands));
107105
+ await autoMigrateGeneratedContent2(workdir);
107106
+ }
106494
107107
  };
106495
107108
  });
106496
107109
 
@@ -108017,7 +108630,7 @@ var init_run_cost_reconcile = __esm(() => {
108017
108630
  });
108018
108631
 
108019
108632
  // src/finish/audit.ts
108020
- import { appendFile as appendFile8, mkdir as mkdir31, readFile as readFile7, writeFile as writeFile12 } from "fs/promises";
108633
+ import { appendFile as appendFile8, mkdir as mkdir32, readFile as readFile7, writeFile as writeFile12 } from "fs/promises";
108021
108634
  import { join as join113 } from "path";
108022
108635
  function roundsPath(t) {
108023
108636
  return join113(t.auditDir, `${t.runId}.jsonl`);
@@ -108044,7 +108657,7 @@ async function updateLedger(t, result) {
108044
108657
  finishedAt: new Date().toISOString()
108045
108658
  };
108046
108659
  try {
108047
- await mkdir31(t.auditDir, { recursive: true });
108660
+ await mkdir32(t.auditDir, { recursive: true });
108048
108661
  await writeFile12(ledgerPath(t.auditDir), `${JSON.stringify(entry, null, 2)}
108049
108662
  `, "utf8");
108050
108663
  } catch {}
@@ -108068,7 +108681,7 @@ async function readLedger(auditDir) {
108068
108681
  }
108069
108682
  async function appendRound(t, round) {
108070
108683
  try {
108071
- await mkdir31(t.auditDir, { recursive: true });
108684
+ await mkdir32(t.auditDir, { recursive: true });
108072
108685
  await appendFile8(roundsPath(t), `${JSON.stringify(round)}
108073
108686
  `, "utf8");
108074
108687
  } catch {}
@@ -108094,7 +108707,7 @@ async function readRounds(t) {
108094
108707
  async function writeResult(t, result, options = {}) {
108095
108708
  const rounds = await readRounds(t);
108096
108709
  const withRounds = rounds.length > 0 ? { ...result, rounds } : result;
108097
- await mkdir31(t.auditDir, { recursive: true });
108710
+ await mkdir32(t.auditDir, { recursive: true });
108098
108711
  await writeFile12(resultPath(t), `${JSON.stringify(withRounds, null, 2)}
108099
108712
  `, "utf8");
108100
108713
  if (options.ledger === false)
@@ -110117,7 +110730,7 @@ async function runFinishPhase(ctx) {
110117
110730
  const costBefore = _finishPhaseDeps.snapshotCost(ctx.runtime);
110118
110731
  const { signal, dispose } = phaseSignal(ctx.abortSignal, settings.timeouts.flowMs);
110119
110732
  let result = null;
110120
- let failure;
110733
+ let failure2;
110121
110734
  let dispatchAsk;
110122
110735
  try {
110123
110736
  const audit = {
@@ -110189,13 +110802,13 @@ async function runFinishPhase(ctx) {
110189
110802
  timeouts: { acceptanceMs: settings.timeouts.acceptanceMs, gateMs: settings.timeouts.gateMs }
110190
110803
  });
110191
110804
  } catch (err) {
110192
- failure = errorMessage(err);
110805
+ failure2 = errorMessage(err);
110193
110806
  } finally {
110194
110807
  await dispatchAsk?.dispose();
110195
110808
  dispose();
110196
110809
  }
110197
110810
  const costUsd = _finishPhaseDeps.snapshotCost(ctx.runtime) - costBefore;
110198
- const passed = failure === undefined && result?.status !== "escalated";
110811
+ const passed = failure2 === undefined && result?.status !== "escalated";
110199
110812
  if (result?.skipReason) {
110200
110813
  getSafeLogger()?.info("finish", "Finish phase skipped", {
110201
110814
  reason: result.skipReason,
@@ -110227,8 +110840,8 @@ async function runFinishPhase(ctx) {
110227
110840
  escalationReason: result.escalationReason
110228
110841
  });
110229
110842
  }
110230
- if (failure) {
110231
- getSafeLogger()?.warn("finish", "Finish phase could not run", { storyId: "_run", error: failure });
110843
+ if (failure2) {
110844
+ getSafeLogger()?.warn("finish", "Finish phase could not run", { storyId: "_run", error: failure2 });
110232
110845
  }
110233
110846
  pipelineEventBus.emit({
110234
110847
  type: "postrun:phase:completed",
@@ -110660,7 +111273,7 @@ var init_runner_completion = __esm(() => {
110660
111273
  });
110661
111274
 
110662
111275
  // src/pipeline/subscribers/events-writer.ts
110663
- import { appendFile as appendFile9, mkdir as mkdir32 } from "fs/promises";
111276
+ import { appendFile as appendFile9, mkdir as mkdir33 } from "fs/promises";
110664
111277
  import { basename as basename15, join as join116 } from "path";
110665
111278
  function wireEventsWriter(bus, feature, runId, workdir) {
110666
111279
  const logger = getSafeLogger();
@@ -110672,7 +111285,7 @@ function wireEventsWriter(bus, feature, runId, workdir) {
110672
111285
  return (async () => {
110673
111286
  try {
110674
111287
  if (!dirReady) {
110675
- await mkdir32(eventsDir, { recursive: true });
111288
+ await mkdir33(eventsDir, { recursive: true });
110676
111289
  dirReady = true;
110677
111290
  }
110678
111291
  await appendFile9(eventsFile, `${JSON.stringify(line)}
@@ -110846,7 +111459,7 @@ var init_interaction2 = __esm(() => {
110846
111459
  });
110847
111460
 
110848
111461
  // src/pipeline/subscribers/registry.ts
110849
- import { mkdir as mkdir33, writeFile as writeFile13 } from "fs/promises";
111462
+ import { mkdir as mkdir34, writeFile as writeFile13 } from "fs/promises";
110850
111463
  import { basename as basename16, join as join117 } from "path";
110851
111464
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
110852
111465
  const logger = getSafeLogger();
@@ -110856,7 +111469,7 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
110856
111469
  const unsub = bus.on("run:started", (_ev) => {
110857
111470
  return (async () => {
110858
111471
  try {
110859
- await mkdir33(runDir, { recursive: true });
111472
+ await mkdir34(runDir, { recursive: true });
110860
111473
  const meta3 = {
110861
111474
  runId,
110862
111475
  project,
@@ -115829,7 +116442,7 @@ var init_rules_lint = __esm(() => {
115829
116442
  });
115830
116443
 
115831
116444
  // src/cli/rules-cli-deps.ts
115832
- import { mkdir as mkdir34 } from "fs/promises";
116445
+ import { mkdir as mkdir35 } from "fs/promises";
115833
116446
  import { join as join129 } from "path";
115834
116447
  var _rulesCLIDeps;
115835
116448
  var init_rules_cli_deps = __esm(() => {
@@ -115850,7 +116463,7 @@ var init_rules_cli_deps = __esm(() => {
115850
116463
  }
115851
116464
  },
115852
116465
  mkdir: async (path32) => {
115853
- await mkdir34(path32, { recursive: true });
116466
+ await mkdir35(path32, { recursive: true });
115854
116467
  },
115855
116468
  globCanonicalRuleFiles: (workdir) => _rulesLintDeps.globCanonicalRuleFiles(workdir),
115856
116469
  globHasMatch: (pattern, cwd) => _rulesLintDeps.globHasMatch(pattern, cwd),
@@ -119955,7 +120568,7 @@ Error generating stack: ` + x.message + `
119955
120568
  return p0;
119956
120569
  });
119957
120570
  }
119958
- function describeValue(value, maxLength) {
120571
+ function describeValue2(value, maxLength) {
119959
120572
  switch (typeof value) {
119960
120573
  case "string":
119961
120574
  return value = JSON.stringify(value), value.length > maxLength ? 5 > maxLength ? '"..."' : value.slice(0, maxLength - 4) + '..."' : value;
@@ -119975,7 +120588,7 @@ Error generating stack: ` + x.message + `
119975
120588
  var jsonPropName = JSON.stringify(propName);
119976
120589
  jsonPropName !== '"' + propName + '"' && (propName = jsonPropName);
119977
120590
  maxLength -= propName.length - 2;
119978
- jsonPropName = describeValue(value[propName], 15 > maxLength ? maxLength : 15);
120591
+ jsonPropName = describeValue2(value[propName], 15 > maxLength ? maxLength : 15);
119979
120592
  maxLength -= jsonPropName.length;
119980
120593
  if (0 > maxLength) {
119981
120594
  name += name === "" ? "..." : ", ...";
@@ -119993,7 +120606,7 @@ Error generating stack: ` + x.message + `
119993
120606
  }
119994
120607
  }
119995
120608
  function describePropValue(value, maxLength) {
119996
- return typeof value !== "string" || needsEscaping.test(value) ? "{" + describeValue(value, maxLength - 2) + "}" : value.length > maxLength - 2 ? 5 > maxLength ? '"..."' : '"' + value.slice(0, maxLength - 5) + '..."' : '"' + value + '"';
120609
+ return typeof value !== "string" || needsEscaping.test(value) ? "{" + describeValue2(value, maxLength - 2) + "}" : value.length > maxLength - 2 ? 5 > maxLength ? '"..."' : '"' + value.slice(0, maxLength - 5) + '..."' : '"' + value + '"';
119997
120610
  }
119998
120611
  function describeExpandedElement(type, props, rowPrefix) {
119999
120612
  var remainingRowLength = 120 - rowPrefix.length - type.length, properties = [], propName;
@@ -120016,14 +120629,14 @@ Error generating stack: ` + x.message + `
120016
120629
  for (propName in clientObject)
120017
120630
  if (clientObject.hasOwnProperty(propName)) {
120018
120631
  delete remainingServerProperties[propName];
120019
- var maxLength = 120 - 2 * indent - propName.length - 2, clientPropValue = describeValue(clientObject[propName], maxLength);
120020
- serverObject.hasOwnProperty(propName) ? (maxLength = describeValue(serverObject[propName], maxLength), properties += added(indent) + propName + ": " + clientPropValue + `
120632
+ var maxLength = 120 - 2 * indent - propName.length - 2, clientPropValue = describeValue2(clientObject[propName], maxLength);
120633
+ serverObject.hasOwnProperty(propName) ? (maxLength = describeValue2(serverObject[propName], maxLength), properties += added(indent) + propName + ": " + clientPropValue + `
120021
120634
  `, properties += removed(indent) + propName + ": " + maxLength + `
120022
120635
  `) : properties += added(indent) + propName + ": " + clientPropValue + `
120023
120636
  `;
120024
120637
  }
120025
120638
  for (var _propName in remainingServerProperties)
120026
- remainingServerProperties.hasOwnProperty(_propName) && (clientObject = describeValue(remainingServerProperties[_propName], 120 - 2 * indent - _propName.length - 2), properties += removed(indent) + _propName + ": " + clientObject + `
120639
+ remainingServerProperties.hasOwnProperty(_propName) && (clientObject = describeValue2(remainingServerProperties[_propName], 120 - 2 * indent - _propName.length - 2), properties += removed(indent) + _propName + ": " + clientObject + `
120027
120640
  `);
120028
120641
  return properties;
120029
120642
  }
@@ -132398,7 +133011,7 @@ var Wrap = /* @__PURE__ */ function(Wrap2) {
132398
133011
  Wrap2[Wrap2["WrapReverse"] = 2] = "WrapReverse";
132399
133012
  return Wrap2;
132400
133013
  }({});
132401
- var constants = {
133014
+ var constants2 = {
132402
133015
  ALIGN_AUTO: Align.Auto,
132403
133016
  ALIGN_FLEX_START: Align.FlexStart,
132404
133017
  ALIGN_CENTER: Align.Center,
@@ -132472,7 +133085,7 @@ var constants = {
132472
133085
  WRAP_WRAP: Wrap.Wrap,
132473
133086
  WRAP_WRAP_REVERSE: Wrap.WrapReverse
132474
133087
  };
132475
- var YGEnums_default = constants;
133088
+ var YGEnums_default = constants2;
132476
133089
 
132477
133090
  // node_modules/yoga-layout/dist/src/wrapAssembly.js
132478
133091
  function wrapAssembly(lib) {
@@ -136740,10 +137353,10 @@ async function writeQueueCommand(queueFilePath, command) {
136740
137353
  await withQueueFileLock(queueFilePath, () => appendFile10(queueFilePath, `${commandLine2}
136741
137354
  `, "utf8"));
136742
137355
  });
136743
- const settled = next.catch(() => {});
136744
- _writeChains.set(queueFilePath, settled);
136745
- settled.then(() => {
136746
- if (_writeChains.get(queueFilePath) === settled)
137356
+ const settled2 = next.catch(() => {});
137357
+ _writeChains.set(queueFilePath, settled2);
137358
+ settled2.then(() => {
137359
+ if (_writeChains.get(queueFilePath) === settled2)
136747
137360
  _writeChains.delete(queueFilePath);
136748
137361
  });
136749
137362
  await next;