@nathapp/nax 0.61.1 → 0.61.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 +152 -108
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -2634,17 +2634,6 @@ function formatDuration(durationMs) {
2634
2634
  function formatCost(cost) {
2635
2635
  return `$${cost.toFixed(4)}`;
2636
2636
  }
2637
- function getStageEmoji(stage) {
2638
- if (stage.includes("routing"))
2639
- return EMOJI.routing;
2640
- if (stage.includes("execution") || stage.includes("agent"))
2641
- return EMOJI.execution;
2642
- if (stage.includes("review"))
2643
- return EMOJI.review;
2644
- if (stage.includes("tdd"))
2645
- return EMOJI.tdd;
2646
- return EMOJI.info;
2647
- }
2648
2637
  function shouldDisplay(entry, mode) {
2649
2638
  if (mode === "json")
2650
2639
  return true;
@@ -2685,7 +2674,7 @@ function formatLogEntry(entry, options) {
2685
2674
  }
2686
2675
  return formatDefault(entry, colorize, timestamp, mode);
2687
2676
  }
2688
- function formatRunStart(entry, c, timestamp, mode) {
2677
+ function formatRunStart(entry, c, timestamp, _mode) {
2689
2678
  const data = entry.data;
2690
2679
  const lines = [];
2691
2680
  lines.push("");
@@ -2704,7 +2693,7 @@ function formatRunStart(entry, c, timestamp, mode) {
2704
2693
  shouldDisplay: true
2705
2694
  };
2706
2695
  }
2707
- function formatStoryStart(entry, c, timestamp, mode) {
2696
+ function formatStoryStart(entry, c, _timestamp, mode) {
2708
2697
  const data = entry.data;
2709
2698
  const storyId = String(data.storyId || entry.storyId || "unknown");
2710
2699
  const title = String(data.storyTitle || data.title || "Untitled story");
@@ -2734,7 +2723,7 @@ function formatStoryStart(entry, c, timestamp, mode) {
2734
2723
  shouldDisplay: true
2735
2724
  };
2736
2725
  }
2737
- function formatStoryComplete(entry, c, timestamp, mode) {
2726
+ function formatStoryComplete(entry, c, _timestamp, mode) {
2738
2727
  const data = entry.data;
2739
2728
  const storyId = String(data.storyId || entry.storyId || "unknown");
2740
2729
  const success = data.success ?? true;
@@ -2766,7 +2755,7 @@ function formatStoryComplete(entry, c, timestamp, mode) {
2766
2755
  shouldDisplay: true
2767
2756
  };
2768
2757
  }
2769
- function formatTDDSession(entry, c, timestamp, mode) {
2758
+ function formatTDDSession(entry, c, _timestamp, mode) {
2770
2759
  if (mode === "quiet") {
2771
2760
  return { output: "", shouldDisplay: false };
2772
2761
  }
@@ -2781,7 +2770,6 @@ function formatTDDSession(entry, c, timestamp, mode) {
2781
2770
  function formatDefault(entry, c, timestamp, mode) {
2782
2771
  const levelEmoji = entry.level === "error" ? EMOJI.failure : entry.level === "warn" ? EMOJI.warning : EMOJI.info;
2783
2772
  const levelColor = entry.level === "error" ? c.red : entry.level === "warn" ? c.yellow : c.gray;
2784
- const stageEmoji = getStageEmoji(entry.stage);
2785
2773
  const parts = [c.gray(`[${timestamp}]`), levelColor(`${levelEmoji} ${entry.stage}`)];
2786
2774
  if (entry.storyId) {
2787
2775
  parts.push(c.dim(`[${entry.storyId}]`));
@@ -3626,7 +3614,14 @@ var init_env = __esm(() => {
3626
3614
 
3627
3615
  // src/agents/acp/parser.ts
3628
3616
  function createParseState() {
3629
- return { text: "", tokenUsage: undefined, exactCostUsd: undefined, stopReason: undefined, error: undefined };
3617
+ return {
3618
+ text: "",
3619
+ tokenUsage: undefined,
3620
+ exactCostUsd: undefined,
3621
+ stopReason: undefined,
3622
+ error: undefined,
3623
+ retryable: false
3624
+ };
3630
3625
  }
3631
3626
  function parseAcpxJsonLine(line, state) {
3632
3627
  try {
@@ -3657,6 +3652,20 @@ function parseAcpxJsonLine(line, state) {
3657
3652
  };
3658
3653
  }
3659
3654
  }
3655
+ if (event.error && typeof event.error === "object") {
3656
+ const err = event.error;
3657
+ let errorMsg = typeof err.message === "string" ? err.message : JSON.stringify(event.error);
3658
+ if (err.data && typeof err.data === "object") {
3659
+ const data = err.data;
3660
+ const suffix = [data.acpxCode, data.detailCode].filter(Boolean).join("/");
3661
+ if (suffix)
3662
+ errorMsg = `${errorMsg} [${suffix}]`;
3663
+ if (!state.error && data.retryable === true)
3664
+ state.retryable = true;
3665
+ }
3666
+ if (!state.error)
3667
+ state.error = errorMsg;
3668
+ }
3660
3669
  return;
3661
3670
  }
3662
3671
  if (event.content && typeof event.content === "string")
@@ -3691,7 +3700,8 @@ function finalizeParseState(state) {
3691
3700
  tokenUsage: state.tokenUsage,
3692
3701
  exactCostUsd: state.exactCostUsd,
3693
3702
  stopReason: state.stopReason,
3694
- error: state.error
3703
+ error: state.error,
3704
+ retryable: state.retryable
3695
3705
  };
3696
3706
  }
3697
3707
 
@@ -3807,13 +3817,17 @@ class SpawnAcpSession {
3807
3817
  Promise.race([stderrPromise, drainB.promise]).finally(() => drainB.cancel())
3808
3818
  ]);
3809
3819
  if (exitCode !== 0) {
3820
+ const parsedOnError = finalizeParseState(parseState);
3821
+ const errorContent = parsedOnError.error || stderr || `Exit code ${exitCode}`;
3810
3822
  getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
3811
3823
  exitCode,
3812
- stderr: stderr.slice(0, 500)
3824
+ error: errorContent.slice(0, 500),
3825
+ ...stderr && stderr !== errorContent ? { banner: stderr.trim().slice(0, 200) } : {}
3813
3826
  });
3814
3827
  return {
3815
- messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
3816
- stopReason: "error"
3828
+ messages: [{ role: "assistant", content: errorContent }],
3829
+ stopReason: "error",
3830
+ retryable: parsedOnError.retryable
3817
3831
  };
3818
3832
  }
3819
3833
  try {
@@ -3889,7 +3903,6 @@ class SpawnAcpSession {
3889
3903
  }
3890
3904
 
3891
3905
  class SpawnAcpClient {
3892
- agentName;
3893
3906
  model;
3894
3907
  cwd;
3895
3908
  timeoutSeconds;
@@ -3903,7 +3916,6 @@ class SpawnAcpClient {
3903
3916
  if (!lastToken || lastToken.startsWith("-")) {
3904
3917
  throw new Error(`[acp-adapter] Could not parse agentName from cmdStr: "${cmdStr}"`);
3905
3918
  }
3906
- this.agentName = lastToken;
3907
3919
  this.cwd = cwd || process.cwd();
3908
3920
  this.timeoutSeconds = timeoutSeconds || 1800;
3909
3921
  this.env = buildAllowedEnv();
@@ -4117,17 +4129,6 @@ function estimateCostByDuration(modelTier, durationMs) {
4117
4129
  confidence: "fallback"
4118
4130
  };
4119
4131
  }
4120
- function formatCostWithConfidence(estimate) {
4121
- const formattedCost = `$${estimate.cost.toFixed(2)}`;
4122
- switch (estimate.confidence) {
4123
- case "exact":
4124
- return formattedCost;
4125
- case "estimated":
4126
- return `~${formattedCost}`;
4127
- case "fallback":
4128
- return `~${formattedCost} (duration-based)`;
4129
- }
4130
- }
4131
4132
  function estimateCostFromTokenUsage(usage, model) {
4132
4133
  const pricing = MODEL_PRICING[model];
4133
4134
  if (!pricing) {
@@ -18162,6 +18163,8 @@ var init_schemas3 = __esm(() => {
18162
18163
  iterationDelayMs: exports_external.number().int().nonnegative(),
18163
18164
  costLimit: exports_external.number().positive({ message: "costLimit must be > 0" }),
18164
18165
  sessionTimeoutSeconds: exports_external.number().int().positive({ message: "sessionTimeoutSeconds must be > 0" }).default(3600),
18166
+ sessionErrorMaxRetries: exports_external.number().int().min(0).max(5).default(1),
18167
+ sessionErrorRetryableMaxRetries: exports_external.number().int().min(0).max(10).default(3),
18165
18168
  verificationTimeoutSeconds: exports_external.number().int().min(1).max(3600).default(300),
18166
18169
  maxStoriesPerFeature: exports_external.number().int().positive(),
18167
18170
  rectification: RectificationConfigSchema,
@@ -19080,19 +19083,28 @@ class AcpAgentAdapter {
19080
19083
  });
19081
19084
  let currentAgent = this.resolveCurrentAgent(config2);
19082
19085
  const rateLimitedRetryAfter = new Map;
19083
- let sessionErrorRetried = false;
19086
+ let sessionErrorRetries = 0;
19087
+ const SESSION_ERROR_MAX_RETRIES = config2?.execution?.sessionErrorMaxRetries ?? 1;
19088
+ const SESSION_ERROR_RETRYABLE_MAX_RETRIES = config2?.execution?.sessionErrorRetryableMaxRetries ?? 3;
19084
19089
  let legacyAttempt = 0;
19085
19090
  let retryCount = 0;
19086
19091
  while (true) {
19087
19092
  try {
19088
19093
  const result = await this._runWithClient(options, startTime, currentAgent);
19089
19094
  if (!result.success) {
19090
- getSafeLogger()?.warn("acp-adapter", `Run failed for ${currentAgent}`, { exitCode: result.exitCode });
19091
- if (result.sessionError && _acpAdapterDeps.shouldRetrySessionError && !sessionErrorRetried) {
19092
- sessionErrorRetried = true;
19095
+ getSafeLogger()?.warn("acp-adapter", `Run failed for ${currentAgent}`, {
19096
+ exitCode: result.exitCode,
19097
+ ...result.output ? { output: result.output.slice(0, 500) } : {}
19098
+ });
19099
+ const maxSessionRetries = result.sessionErrorRetryable ? SESSION_ERROR_RETRYABLE_MAX_RETRIES : SESSION_ERROR_MAX_RETRIES;
19100
+ if (result.sessionError && _acpAdapterDeps.shouldRetrySessionError && sessionErrorRetries < maxSessionRetries) {
19101
+ sessionErrorRetries += 1;
19093
19102
  getSafeLogger()?.warn("acp-adapter", "Session error \u2014 retrying with fresh session", {
19094
19103
  storyId: options.storyId,
19095
- featureName: options.featureName
19104
+ featureName: options.featureName,
19105
+ retryable: result.sessionErrorRetryable,
19106
+ attempt: sessionErrorRetries,
19107
+ maxAttempts: maxSessionRetries
19096
19108
  });
19097
19109
  if (options.featureName && options.storyId) {
19098
19110
  await clearAcpSession(options.workdir, options.featureName, options.storyId, options.sessionRole);
@@ -19311,6 +19323,7 @@ class AcpAgentAdapter {
19311
19323
  }
19312
19324
  const success2 = lastResponse?.stopReason === "end_turn";
19313
19325
  const isSessionError = lastResponse?.stopReason === "error";
19326
+ const isSessionErrorRetryable = isSessionError && lastResponse?.retryable === true;
19314
19327
  const output = extractOutput(lastResponse);
19315
19328
  const estimatedCost = totalExactCostUsd ?? (totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0);
19316
19329
  const tokenUsage = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? {
@@ -19329,6 +19342,7 @@ class AcpAgentAdapter {
19329
19342
  output: output.slice(-MAX_AGENT_OUTPUT_CHARS),
19330
19343
  rateLimited: false,
19331
19344
  sessionError: isSessionError,
19345
+ sessionErrorRetryable: isSessionErrorRetryable,
19332
19346
  durationMs,
19333
19347
  estimatedCost,
19334
19348
  tokenUsage
@@ -23002,7 +23016,7 @@ class AutoInteractionPlugin {
23002
23016
  };
23003
23017
  }
23004
23018
  async destroy() {}
23005
- async send(request) {}
23019
+ async send(_request) {}
23006
23020
  async receive(_requestId, _timeout = 60000) {
23007
23021
  throw new Error("Auto plugin requires full request context (not just requestId)");
23008
23022
  }
@@ -24500,7 +24514,7 @@ async function checkHomeEnvValid() {
24500
24514
  message: passed ? `HOME env is valid: ${home}` : home === "" ? "HOME env is not set \u2014 agent may write files to unexpected locations" : `HOME env is not an absolute path ("${home}") \u2014 may cause literal "~" directories in repo`
24501
24515
  };
24502
24516
  }
24503
- async function checkLanguageTools(profile, workdir) {
24517
+ async function checkLanguageTools(profile, _workdir) {
24504
24518
  if (!profile || !profile.language) {
24505
24519
  return {
24506
24520
  name: "language-tools-available",
@@ -25229,7 +25243,7 @@ function countStories(prd) {
25229
25243
  decomposed: prd.userStories.filter((s) => s.status === "decomposed").length
25230
25244
  };
25231
25245
  }
25232
- function markStoryPassed(prd, storyId, statusWriter) {
25246
+ function markStoryPassed(prd, storyId, _statusWriter) {
25233
25247
  const story = prd.userStories.find((s) => s.id === storyId);
25234
25248
  if (story) {
25235
25249
  story.passes = true;
@@ -26162,7 +26176,7 @@ function parseAcceptanceCriteria(specContent) {
26162
26176
  }
26163
26177
  return criteria;
26164
26178
  }
26165
- function buildAcceptanceTestPrompt(criteria, featureName, codebaseContext, testPathConfig, language) {
26179
+ function buildAcceptanceTestPrompt(criteria, featureName, _codebaseContext, testPathConfig, language) {
26166
26180
  const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join(`
26167
26181
  `);
26168
26182
  const resolvedTestPath = resolveAcceptanceTestFile2(language, testPathConfig);
@@ -26311,7 +26325,7 @@ ${tests || " // No acceptance criteria found"}
26311
26325
  });
26312
26326
  `;
26313
26327
  }
26314
- function generateGoSkeletonTests(featureName, criteria) {
26328
+ function generateGoSkeletonTests(_featureName, criteria) {
26315
26329
  const sanitize = (text) => text.replace(/[^a-zA-Z0-9 ]/g, "").split(" ").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
26316
26330
  const tests = criteria.map((ac) => {
26317
26331
  const funcName = `Test${sanitize(ac.text) || ac.id.replace("-", "")}`;
@@ -27496,40 +27510,8 @@ var init_claude = __esm(() => {
27496
27510
  function validateAgentForTier(agent, tier) {
27497
27511
  return agent.capabilities.supportedTiers.includes(tier);
27498
27512
  }
27499
- function validateAgentFeature(agent, feature) {
27500
- return agent.capabilities.features.has(feature);
27501
- }
27502
- function describeAgentCapabilities(agent) {
27503
- const tiers = agent.capabilities.supportedTiers.join(",");
27504
- const features = Array.from(agent.capabilities.features).join(",");
27505
- const maxTokens = agent.capabilities.maxContextTokens;
27506
- return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
27507
- }
27508
27513
 
27509
27514
  // src/agents/index.ts
27510
- var exports_agents = {};
27511
- __export(exports_agents, {
27512
- validateAgentForTier: () => validateAgentForTier,
27513
- validateAgentFeature: () => validateAgentFeature,
27514
- parseTokenUsage: () => parseTokenUsage,
27515
- getInstalledAgents: () => getInstalledAgents,
27516
- getAllAgentNames: () => getAllAgentNames,
27517
- getAgentVersions: () => getAgentVersions,
27518
- getAgentVersion: () => getAgentVersion,
27519
- getAgent: () => getAgent,
27520
- formatCostWithConfidence: () => formatCostWithConfidence,
27521
- estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
27522
- estimateCostFromOutput: () => estimateCostFromOutput,
27523
- estimateCostByDuration: () => estimateCostByDuration,
27524
- estimateCost: () => estimateCost,
27525
- describeAgentCapabilities: () => describeAgentCapabilities,
27526
- checkAgentHealth: () => checkAgentHealth,
27527
- MODEL_PRICING: () => MODEL_PRICING,
27528
- CompleteError: () => CompleteError,
27529
- ClaudeCodeAdapter: () => ClaudeCodeAdapter,
27530
- COST_RATES: () => COST_RATES,
27531
- AllAgentsUnavailableError: () => AllAgentsUnavailableError
27532
- });
27533
27515
  var init_agents = __esm(() => {
27534
27516
  init_types2();
27535
27517
  init_claude();
@@ -28096,7 +28078,7 @@ ${stat}
28096
28078
  return `${statPreamble}${truncated}
28097
28079
  ... (truncated at ${DIFF_CAP_BYTES} bytes, showing ${visibleFiles}/${totalFiles} files)`;
28098
28080
  }
28099
- function buildPrompt(story, semanticConfig, diff, stat) {
28081
+ function buildPrompt(story, semanticConfig, diff, _stat) {
28100
28082
  const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
28101
28083
  `);
28102
28084
  const customRulesSection = semanticConfig.rules.length > 0 ? `
@@ -29984,9 +29966,7 @@ function deriveTestPatterns(contextFiles) {
29984
29966
  async function detectTestDir(workdir) {
29985
29967
  for (const dir of COMMON_TEST_DIRS) {
29986
29968
  const fullPath = path7.join(workdir, dir);
29987
- const file3 = Bun.file(path7.join(fullPath, "."));
29988
29969
  try {
29989
- const dirStat = await Bun.file(fullPath).exists();
29990
29970
  const proc = Bun.spawn(["test", "-d", fullPath], { stdout: "pipe", stderr: "pipe" });
29991
29971
  const exitCode = await proc.exited;
29992
29972
  if (exitCode === 0)
@@ -30787,7 +30767,7 @@ function globToRegex(pattern) {
30787
30767
  const regexStr = filePattern.replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replace(/\{([^}]+)\}/g, (_, group) => `(${group.replace(/,/g, "|")})`).replace(/\\\.\\\*/g, "\\.[^/]*");
30788
30768
  return new RegExp(`${regexStr}$`);
30789
30769
  }
30790
- async function isGreenfieldStory(story, workdir, testPattern = "**/*.{test,spec}.{ts,js,tsx,jsx}") {
30770
+ async function isGreenfieldStory(_story, workdir, testPattern = "**/*.{test,spec}.{ts,js,tsx,jsx}") {
30791
30771
  try {
30792
30772
  const regex = globToRegex(testPattern);
30793
30773
  const testFiles = await scanForTestFiles(workdir, regex);
@@ -32381,7 +32361,8 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
32381
32361
  role,
32382
32362
  storyId: story.id,
32383
32363
  durationMs: Date.now() - startTime,
32384
- exitCode: result.exitCode
32364
+ exitCode: result.exitCode,
32365
+ ...result.output ? { output: result.output.slice(0, 500) } : {}
32385
32366
  });
32386
32367
  }
32387
32368
  await _sessionRunnerDeps.autoCommitIfDirty(workdir, "tdd", role, story.id);
@@ -33404,7 +33385,7 @@ var init_optimizer2 = __esm(() => {
33404
33385
  init_optimizer();
33405
33386
  optimizerStage = {
33406
33387
  name: "optimizer",
33407
- enabled: (ctx) => {
33388
+ enabled: (_ctx) => {
33408
33389
  return true;
33409
33390
  },
33410
33391
  async execute(ctx) {
@@ -34754,7 +34735,7 @@ var init_classify = __esm(() => {
34754
34735
  });
34755
34736
 
34756
34737
  // src/routing/strategies/llm-prompts.ts
34757
- function buildRoutingPrompt(story, config2) {
34738
+ function buildRoutingPrompt(story, _config) {
34758
34739
  const { title, description, acceptanceCriteria, tags } = story;
34759
34740
  const criteria = acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join(`
34760
34741
  `);
@@ -34788,7 +34769,7 @@ Respond with:
34788
34769
  {"complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}`;
34789
34770
  return wrapJsonPrompt(core2);
34790
34771
  }
34791
- function buildBatchRoutingPrompt(stories, config2) {
34772
+ function buildBatchRoutingPrompt(stories, _config) {
34792
34773
  const storyBlocks = stories.map((story, idx) => {
34793
34774
  const criteria = story.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join(`
34794
34775
  `);
@@ -36657,7 +36638,7 @@ var package_default;
36657
36638
  var init_package = __esm(() => {
36658
36639
  package_default = {
36659
36640
  name: "@nathapp/nax",
36660
- version: "0.61.1",
36641
+ version: "0.61.2",
36661
36642
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
36662
36643
  type: "module",
36663
36644
  bin: {
@@ -36737,8 +36718,8 @@ var init_version = __esm(() => {
36737
36718
  NAX_VERSION = package_default.version;
36738
36719
  NAX_COMMIT = (() => {
36739
36720
  try {
36740
- if (/^[0-9a-f]{6,10}$/.test("a11d3b57"))
36741
- return "a11d3b57";
36721
+ if (/^[0-9a-f]{6,10}$/.test("16490524"))
36722
+ return "16490524";
36742
36723
  } catch {}
36743
36724
  try {
36744
36725
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -37085,7 +37066,7 @@ function parseImportStatements(content) {
37085
37066
  }
37086
37067
  return imports;
37087
37068
  }
37088
- function resolveImportPaths(imports, workdir) {
37069
+ function resolveImportPaths(imports, _workdir) {
37089
37070
  const resolved = [];
37090
37071
  for (const imp of imports) {
37091
37072
  if (imp.startsWith(".")) {
@@ -38338,7 +38319,7 @@ function wireHooks(bus, hooks, workdir, feature) {
38338
38319
  return fn().catch((err) => logger?.warn("hooks-subscriber", `Hook "${name}" failed`, { error: String(err) })).catch(() => {});
38339
38320
  };
38340
38321
  const unsubs = [];
38341
- unsubs.push(bus.on("run:started", (ev) => {
38322
+ unsubs.push(bus.on("run:started", (_ev) => {
38342
38323
  return safe("on-start", () => fireHook(hooks, "on-start", hookCtx(feature, { status: "running" }), workdir));
38343
38324
  }));
38344
38325
  unsubs.push(bus.on("story:started", (ev) => {
@@ -38359,7 +38340,7 @@ function wireHooks(bus, hooks, workdir, feature) {
38359
38340
  unsubs.push(bus.on("run:completed", (ev) => {
38360
38341
  return safe("on-complete", () => fireHook(hooks, "on-complete", hookCtx(feature, { status: "complete", cost: ev.totalCost ?? 0 }), workdir));
38361
38342
  }));
38362
- unsubs.push(bus.on("run:resumed", (ev) => {
38343
+ unsubs.push(bus.on("run:resumed", (_ev) => {
38363
38344
  return safe("on-resume", () => fireHook(hooks, "on-resume", hookCtx(feature, { status: "running" }), workdir));
38364
38345
  }));
38365
38346
  unsubs.push(bus.on("story:completed", (ev) => {
@@ -39112,7 +39093,6 @@ var init_pipeline_result_handler = __esm(() => {
39112
39093
  // src/execution/iteration-runner.ts
39113
39094
  import { join as join45 } from "path";
39114
39095
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
39115
- const logger = getSafeLogger();
39116
39096
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
39117
39097
  if (ctx.dryRun) {
39118
39098
  const dryRunResult = await handleDryRun({
@@ -39236,7 +39216,6 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
39236
39216
  var _iterationRunnerDeps;
39237
39217
  var init_iteration_runner = __esm(() => {
39238
39218
  init_loader();
39239
- init_logger2();
39240
39219
  init_runner();
39241
39220
  init_stages();
39242
39221
  init_prd();
@@ -39354,7 +39333,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
39354
39333
  };
39355
39334
  }
39356
39335
  }
39357
- async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
39336
+ async function executeParallelBatch(stories, _projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
39358
39337
  const logger = getSafeLogger();
39359
39338
  const results = {
39360
39339
  pipelinePassed: [],
@@ -40505,7 +40484,7 @@ function detectType(pkg) {
40505
40484
  return "cli";
40506
40485
  return;
40507
40486
  }
40508
- async function detectTestFramework(workdir, language, pkg) {
40487
+ async function detectTestFramework(_workdir, language, pkg) {
40509
40488
  if (language === "go")
40510
40489
  return "go-test";
40511
40490
  if (language === "rust")
@@ -40965,7 +40944,6 @@ import { join as join50 } from "path";
40965
40944
  async function reconcileState(prd, prdPath, workdir, config2) {
40966
40945
  const logger = getSafeLogger();
40967
40946
  let reconciledCount = 0;
40968
- let modified = false;
40969
40947
  for (const story of prd.userStories) {
40970
40948
  if (story.status !== "failed")
40971
40949
  continue;
@@ -41000,7 +40978,6 @@ async function reconcileState(prd, prdPath, workdir, config2) {
41000
40978
  });
41001
40979
  markStoryPassed(prd, story.id);
41002
40980
  reconciledCount++;
41003
- modified = true;
41004
40981
  }
41005
40982
  if (reconciledCount > 0) {
41006
40983
  logger?.info("reconciliation", `Reconciled ${reconciledCount} failed stories from git history`);
@@ -41012,7 +40989,6 @@ async function checkAgentInstalled(config2, dryRun, agentGetFn) {
41012
40989
  if (dryRun)
41013
40990
  return;
41014
40991
  const logger = getSafeLogger();
41015
- const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
41016
40992
  const agent = (agentGetFn ?? _reconcileDeps.getAgent)(config2.autoMode.defaultAgent);
41017
40993
  if (!agent) {
41018
40994
  logger?.error("execution", "Agent not found", {
@@ -41081,6 +41057,68 @@ var init_run_initialization = __esm(() => {
41081
41057
  };
41082
41058
  });
41083
41059
 
41060
+ // src/execution/lifecycle/paused-story-prompts.ts
41061
+ var exports_paused_story_prompts = {};
41062
+ __export(exports_paused_story_prompts, {
41063
+ promptForPausedStories: () => promptForPausedStories
41064
+ });
41065
+ async function promptForPausedStories(prd, chain, featureName) {
41066
+ const logger = getSafeLogger();
41067
+ const summary = { resumed: [], skipped: [], kept: [] };
41068
+ const pausedStories = prd.userStories.filter((s) => s.status === "paused");
41069
+ for (const story of pausedStories) {
41070
+ const lastReason = (story.priorErrors?.slice(-1)[0] ?? "no reason recorded").replace(/\n/g, " ").slice(0, 200);
41071
+ logger?.info("run-initialization", "Paused story found \u2014 prompting user", {
41072
+ storyId: story.id,
41073
+ attempts: story.attempts
41074
+ });
41075
+ const response = await chain.prompt({
41076
+ id: `ix-${story.id}-paused-resume`,
41077
+ type: "choose",
41078
+ featureName,
41079
+ storyId: story.id,
41080
+ stage: "pre-flight",
41081
+ summary: `Story ${story.id} is paused \u2014 how to proceed?`,
41082
+ detail: `"${story.title}"
41083
+ Last reason: ${lastReason}
41084
+ Attempts so far: ${story.attempts}`,
41085
+ options: [
41086
+ { key: "resume", label: "Resume", description: "Reset to pending and retry on this run" },
41087
+ { key: "skip", label: "Skip", description: "Mark as skipped, won't be retried" },
41088
+ { key: "keep", label: "Keep paused", description: "Leave paused, skip for this run" }
41089
+ ],
41090
+ timeout: 300000,
41091
+ fallback: "continue",
41092
+ createdAt: Date.now()
41093
+ });
41094
+ const effectiveAction = chain.applyFallback(response, "continue");
41095
+ const resolvedKey = effectiveAction === "approve" ? "keep" : response.action;
41096
+ switch (resolvedKey) {
41097
+ case "resume": {
41098
+ story.status = "pending";
41099
+ summary.resumed.push(story.id);
41100
+ logger?.info("run-initialization", "User resumed paused story", { storyId: story.id });
41101
+ break;
41102
+ }
41103
+ case "skip": {
41104
+ story.status = "skipped";
41105
+ summary.skipped.push(story.id);
41106
+ logger?.info("run-initialization", "User skipped paused story", { storyId: story.id });
41107
+ break;
41108
+ }
41109
+ default: {
41110
+ summary.kept.push(story.id);
41111
+ logger?.info("run-initialization", "Keeping story paused", { storyId: story.id });
41112
+ break;
41113
+ }
41114
+ }
41115
+ }
41116
+ return summary;
41117
+ }
41118
+ var init_paused_story_prompts = __esm(() => {
41119
+ init_logger2();
41120
+ });
41121
+
41084
41122
  // src/execution/lifecycle/run-setup.ts
41085
41123
  var exports_run_setup = {};
41086
41124
  __export(exports_run_setup, {
@@ -41209,7 +41247,16 @@ async function setupRun(options) {
41209
41247
  agentGetFn: options.agentGetFn
41210
41248
  });
41211
41249
  prd = initResult.prd;
41212
- const counts = initResult.storyCounts;
41250
+ statusWriter.setPrd(prd);
41251
+ let counts = initResult.storyCounts;
41252
+ if (counts.paused > 0 && interactionChain !== null) {
41253
+ const { promptForPausedStories: promptForPausedStories2 } = await Promise.resolve().then(() => (init_paused_story_prompts(), exports_paused_story_prompts));
41254
+ const pausedSummary = await promptForPausedStories2(prd, interactionChain, feature);
41255
+ if (pausedSummary.resumed.length > 0 || pausedSummary.skipped.length > 0) {
41256
+ await savePRD(prd, prdPath);
41257
+ counts = countStories(prd);
41258
+ }
41259
+ }
41213
41260
  return {
41214
41261
  statusWriter,
41215
41262
  pidRegistry,
@@ -74802,7 +74849,7 @@ import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
74802
74849
  import { join as join30 } from "path";
74803
74850
 
74804
74851
  // src/cli/diagnose-analysis.ts
74805
- function detectFailurePattern(story, prd, status) {
74852
+ function detectFailurePattern(story, _prd, status) {
74806
74853
  if (story.status === "passed" && story.priorErrors?.some((err) => err.toLowerCase().includes("greenfield-no-tests"))) {
74807
74854
  return "AUTO_RECOVERED";
74808
74855
  }
@@ -76748,7 +76795,6 @@ async function run(options) {
76748
76795
  let totalCost = 0;
76749
76796
  let runCompleted = false;
76750
76797
  const allStoryMetrics = [];
76751
- const logger = getSafeLogger();
76752
76798
  const registry2 = createAgentRegistry(config2);
76753
76799
  const agentGetFn = registry2.getAgent.bind(registry2);
76754
76800
  let prd;
@@ -76849,20 +76895,20 @@ async function run(options) {
76849
76895
  durationMs
76850
76896
  };
76851
76897
  } finally {
76852
- const logger2 = getSafeLogger();
76853
- logger2?.debug("execution", "Runner finally block \u2014 starting cleanup");
76898
+ const logger = getSafeLogger();
76899
+ logger?.debug("execution", "Runner finally block \u2014 starting cleanup");
76854
76900
  stopHeartbeat();
76855
76901
  cleanupCrashHandlers();
76856
- logger2?.debug("execution", "Runner finally \u2014 sweeping ACP sessions");
76902
+ logger?.debug("execution", "Runner finally \u2014 sweeping ACP sessions");
76857
76903
  await sweepFeatureSessions(workdir, feature).catch(() => {});
76858
- logger2?.debug("execution", "Runner finally \u2014 ACP sweep done");
76904
+ logger?.debug("execution", "Runner finally \u2014 ACP sweep done");
76859
76905
  let branch = "";
76860
76906
  try {
76861
76907
  const { stdout, exitCode } = await gitWithTimeout(["branch", "--show-current"], workdir);
76862
76908
  if (exitCode === 0)
76863
76909
  branch = stdout.trim();
76864
76910
  } catch {}
76865
- logger2?.debug("execution", "Runner finally \u2014 running cleanupRun");
76911
+ logger?.debug("execution", "Runner finally \u2014 running cleanupRun");
76866
76912
  const { cleanupRun: cleanupRun2 } = await Promise.resolve().then(() => (init_run_cleanup(), exports_run_cleanup));
76867
76913
  await cleanupRun2({
76868
76914
  runId,
@@ -76879,7 +76925,7 @@ async function run(options) {
76879
76925
  version: NAX_VERSION,
76880
76926
  runCompleted
76881
76927
  });
76882
- logger2?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
76928
+ logger?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
76883
76929
  }
76884
76930
  }
76885
76931
 
@@ -83865,7 +83911,6 @@ function usePty(options) {
83865
83911
  isRunning: false
83866
83912
  }));
83867
83913
  const [handle, setHandle] = import_react33.useState(null);
83868
- const [ptyProcess, setPtyProcess] = import_react33.useState(null);
83869
83914
  const command = options?.command;
83870
83915
  const argsJson = JSON.stringify(options?.args);
83871
83916
  const cwd2 = options?.cwd;
@@ -83881,7 +83926,6 @@ function usePty(options) {
83881
83926
  stdout: "pipe",
83882
83927
  stderr: "inherit"
83883
83928
  });
83884
- setPtyProcess(proc);
83885
83929
  setState((prev) => ({ ...prev, isRunning: true }));
83886
83930
  (async () => {
83887
83931
  let currentLine = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.61.1",
3
+ "version": "0.61.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {