@nathapp/nax 0.57.0 → 0.57.1-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +101 -33
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -18048,31 +18048,36 @@ var init_defaults = __esm(() => {
18048
18048
  enabled: true,
18049
18049
  resolver: { type: "synthesis" },
18050
18050
  sessionMode: "stateful",
18051
- rounds: 3
18051
+ rounds: 3,
18052
+ timeoutSeconds: 600
18052
18053
  },
18053
18054
  review: {
18054
18055
  enabled: true,
18055
18056
  resolver: { type: "majority-fail-closed" },
18056
18057
  sessionMode: "one-shot",
18057
- rounds: 2
18058
+ rounds: 2,
18059
+ timeoutSeconds: 600
18058
18060
  },
18059
18061
  acceptance: {
18060
18062
  enabled: false,
18061
18063
  resolver: { type: "majority-fail-closed" },
18062
18064
  sessionMode: "one-shot",
18063
- rounds: 1
18065
+ rounds: 1,
18066
+ timeoutSeconds: 600
18064
18067
  },
18065
18068
  rectification: {
18066
18069
  enabled: false,
18067
18070
  resolver: { type: "synthesis" },
18068
18071
  sessionMode: "one-shot",
18069
- rounds: 1
18072
+ rounds: 1,
18073
+ timeoutSeconds: 600
18070
18074
  },
18071
18075
  escalation: {
18072
18076
  enabled: false,
18073
18077
  resolver: { type: "majority-fail-closed" },
18074
18078
  sessionMode: "one-shot",
18075
- rounds: 1
18079
+ rounds: 1,
18080
+ timeoutSeconds: 600
18076
18081
  }
18077
18082
  }
18078
18083
  }
@@ -18102,7 +18107,8 @@ var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema
18102
18107
  resolver: makeResolverSchema(defaults.resolverType),
18103
18108
  sessionMode: exports_external.enum(["one-shot", "stateful"]).default(defaults.sessionMode),
18104
18109
  rounds: exports_external.number().int().min(1).default(defaults.rounds),
18105
- debaters: exports_external.array(DebaterSchema).min(2, "debaters must have at least 2 entries").optional()
18110
+ debaters: exports_external.array(DebaterSchema).min(2, "debaters must have at least 2 entries").optional(),
18111
+ timeoutSeconds: exports_external.number().int().positive().default(600)
18106
18112
  })), DebateConfigSchema, NaxConfigSchema;
18107
18113
  var init_schemas3 = __esm(() => {
18108
18114
  init_zod();
@@ -18303,7 +18309,8 @@ var init_schemas3 = __esm(() => {
18303
18309
  });
18304
18310
  PlanConfigSchema = exports_external.object({
18305
18311
  model: ModelTierSchema,
18306
- outputPath: exports_external.string().min(1, "plan.outputPath must be non-empty")
18312
+ outputPath: exports_external.string().min(1, "plan.outputPath must be non-empty"),
18313
+ timeoutSeconds: exports_external.number().int().positive().default(600)
18307
18314
  });
18308
18315
  AcceptanceFixConfigSchema = exports_external.object({
18309
18316
  diagnoseModel: exports_external.string().min(1, "acceptance.fix.diagnoseModel must be non-empty"),
@@ -19846,9 +19853,9 @@ async function runPlan(binary, options, pidRegistry, buildAllowedEnv2) {
19846
19853
  modelDef,
19847
19854
  prompt: "",
19848
19855
  modelTier: options.modelTier || "balanced",
19849
- timeoutSeconds: 600
19856
+ timeoutSeconds: options.timeoutSeconds ?? 600
19850
19857
  };
19851
- const PLAN_TIMEOUT_MS = 600000;
19858
+ const planTimeoutMs = (options.timeoutSeconds ?? 600) * 1000;
19852
19859
  if (options.interactive) {
19853
19860
  const proc = Bun.spawn(cmd, {
19854
19861
  cwd: options.workdir,
@@ -19860,7 +19867,7 @@ async function runPlan(binary, options, pidRegistry, buildAllowedEnv2) {
19860
19867
  await pidRegistry.register(proc.pid);
19861
19868
  let exitCode;
19862
19869
  try {
19863
- const timeoutResult = await withProcessTimeout(proc, PLAN_TIMEOUT_MS, {
19870
+ const timeoutResult = await withProcessTimeout(proc, planTimeoutMs, {
19864
19871
  graceMs: 5000
19865
19872
  });
19866
19873
  exitCode = timeoutResult.exitCode;
@@ -19886,7 +19893,7 @@ async function runPlan(binary, options, pidRegistry, buildAllowedEnv2) {
19886
19893
  await pidRegistry.register(proc.pid);
19887
19894
  let exitCode;
19888
19895
  try {
19889
- const timeoutResult = await withProcessTimeout(proc, PLAN_TIMEOUT_MS, {
19896
+ const timeoutResult = await withProcessTimeout(proc, planTimeoutMs, {
19890
19897
  graceMs: 5000
19891
19898
  });
19892
19899
  exitCode = timeoutResult.exitCode;
@@ -22156,7 +22163,7 @@ var package_default;
22156
22163
  var init_package = __esm(() => {
22157
22164
  package_default = {
22158
22165
  name: "@nathapp/nax",
22159
- version: "0.57.0",
22166
+ version: "0.57.1-canary.1",
22160
22167
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22161
22168
  type: "module",
22162
22169
  bin: {
@@ -22235,8 +22242,8 @@ var init_version = __esm(() => {
22235
22242
  NAX_VERSION = package_default.version;
22236
22243
  NAX_COMMIT = (() => {
22237
22244
  try {
22238
- if (/^[0-9a-f]{6,10}$/.test("478df448"))
22239
- return "478df448";
22245
+ if (/^[0-9a-f]{6,10}$/.test("814ed29f"))
22246
+ return "814ed29f";
22240
22247
  } catch {}
22241
22248
  try {
22242
22249
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22523,10 +22530,11 @@ function modelTierFromDebater(debater) {
22523
22530
  function isTierLabel(value) {
22524
22531
  return value === "fast" || value === "balanced" || value === "powerful";
22525
22532
  }
22526
- async function runComplete(adapter, prompt, options, modelTier) {
22533
+ async function runComplete(adapter, prompt, options, modelTier, timeoutMs) {
22527
22534
  return adapter.complete(prompt, {
22528
22535
  ...options,
22529
- modelTier
22536
+ modelTier,
22537
+ ...timeoutMs !== undefined && { timeoutMs }
22530
22538
  });
22531
22539
  }
22532
22540
 
@@ -22538,6 +22546,9 @@ class DebateSession {
22538
22546
  workdir;
22539
22547
  featureName;
22540
22548
  timeoutSeconds;
22549
+ get timeoutMs() {
22550
+ return this.timeoutSeconds * 1000;
22551
+ }
22541
22552
  constructor(opts) {
22542
22553
  this.storyId = opts.storyId;
22543
22554
  this.stage = opts.stage;
@@ -22545,7 +22556,7 @@ class DebateSession {
22545
22556
  this.config = opts.config;
22546
22557
  this.workdir = opts.workdir ?? process.cwd();
22547
22558
  this.featureName = opts.featureName ?? opts.stage;
22548
- this.timeoutSeconds = opts.timeoutSeconds ?? opts.config?.execution?.sessionTimeoutSeconds ?? 600;
22559
+ this.timeoutSeconds = opts.timeoutSeconds ?? opts.stageConfig.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
22549
22560
  }
22550
22561
  pipelineStageForDebate() {
22551
22562
  switch (this.stage) {
@@ -22785,7 +22796,8 @@ class DebateSession {
22785
22796
  featureName: this.stage,
22786
22797
  config: this.config,
22787
22798
  storyId: this.storyId,
22788
- sessionRole: "debate-proposal"
22799
+ sessionRole: "debate-proposal",
22800
+ timeoutMs: this.timeoutMs
22789
22801
  }, modelTierFromDebater(debater)).then((result) => ({ debater, adapter, output: result.output, cost: result.costUsd }))));
22790
22802
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22791
22803
  for (const r of proposalSettled) {
@@ -22839,7 +22851,8 @@ class DebateSession {
22839
22851
  featureName: this.stage,
22840
22852
  config: this.config,
22841
22853
  storyId: this.storyId,
22842
- sessionRole: "debate-fallback"
22854
+ sessionRole: "debate-fallback",
22855
+ timeoutMs: this.timeoutMs
22843
22856
  }, modelTierFromDebater(fallbackDebater));
22844
22857
  totalCostUsd += fallbackResult.costUsd;
22845
22858
  logger?.info("debate", "debate:result", {
@@ -22869,7 +22882,8 @@ class DebateSession {
22869
22882
  featureName: this.stage,
22870
22883
  config: this.config,
22871
22884
  storyId: this.storyId,
22872
- sessionRole: "debate-critique"
22885
+ sessionRole: "debate-critique",
22886
+ timeoutMs: this.timeoutMs
22873
22887
  }, modelTierFromDebater(debater))));
22874
22888
  for (const r of critiqueSettled) {
22875
22889
  if (r.status === "fulfilled") {
@@ -23012,7 +23026,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
23012
23026
  model: resolveDebaterModel({ agent: agentName }, this.config),
23013
23027
  config: this.config,
23014
23028
  storyId: this.storyId,
23015
- sessionRole: "synthesis"
23029
+ sessionRole: "synthesis",
23030
+ timeoutMs: this.timeoutMs
23016
23031
  }
23017
23032
  });
23018
23033
  return {
@@ -23034,7 +23049,8 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
23034
23049
  model: resolveDebaterModel({ agent: agentName }, this.config),
23035
23050
  config: this.config,
23036
23051
  storyId: this.storyId,
23037
- sessionRole: "judge"
23052
+ sessionRole: "judge",
23053
+ timeoutMs: this.timeoutMs
23038
23054
  }
23039
23055
  });
23040
23056
  return {
@@ -23048,7 +23064,7 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
23048
23064
  };
23049
23065
  }
23050
23066
  }
23051
- var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
23067
+ var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps, DEFAULT_TIMEOUT_SECONDS = 600;
23052
23068
  var init_session = __esm(() => {
23053
23069
  init_registry();
23054
23070
  init_config();
@@ -23252,13 +23268,15 @@ class AutoInteractionPlugin {
23252
23268
  const modelDef = resolveModelForAgent(naxConfig.models, naxConfig.autoMode.defaultAgent, modelTier, naxConfig.autoMode.defaultAgent);
23253
23269
  modelArg = modelDef.model;
23254
23270
  }
23271
+ const timeoutMs = this.config.naxConfig ? (this.config.naxConfig.execution?.sessionTimeoutSeconds ?? 600) * 1000 : undefined;
23255
23272
  const result = await adapter.complete(prompt, {
23256
23273
  ...modelArg && { model: modelArg },
23257
23274
  jsonMode: true,
23258
23275
  ...this.config.naxConfig && { config: this.config.naxConfig },
23259
23276
  featureName: request.featureName,
23260
23277
  storyId: request.storyId,
23261
- sessionRole: "auto"
23278
+ sessionRole: "auto",
23279
+ ...timeoutMs !== undefined && { timeoutMs }
23262
23280
  });
23263
23281
  const output = typeof result === "string" ? result : result.output;
23264
23282
  return this.parseResponse(output);
@@ -31556,8 +31574,15 @@ async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
31556
31574
  if (resolved.length === 0) {
31557
31575
  return { output: null, totalCostUsd: 0 };
31558
31576
  }
31577
+ const timeoutMs = (config2?.execution?.sessionTimeoutSeconds ?? 600) * 1000;
31559
31578
  const startMs = Date.now();
31560
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: debater.model }).then((out) => typeof out === "string" ? out : out.output)));
31579
+ const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, {
31580
+ model: debater.model,
31581
+ config: config2,
31582
+ storyId,
31583
+ sessionRole: "debate-proposal",
31584
+ timeoutMs
31585
+ }).then((out) => typeof out === "string" ? out : out.output)));
31561
31586
  const durationMs = Date.now() - startMs;
31562
31587
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
31563
31588
  if (successful.length === 0) {
@@ -34882,6 +34907,16 @@ async function runDeferredRegression(options) {
34882
34907
  };
34883
34908
  }
34884
34909
  const testSummary = _regressionDeps.parseBunTestOutput(fullSuiteResult.output);
34910
+ if (testSummary.failed === 0 && testSummary.passed === 0) {
34911
+ logger?.warn("regression", "No test results parsed from output \u2014 test runner likely crashed or errored (not a regression, accepting as pass)", { output: fullSuiteResult.output.slice(0, 500) });
34912
+ return {
34913
+ success: true,
34914
+ failedTests: 0,
34915
+ passedTests: 0,
34916
+ rectificationAttempts: 0,
34917
+ affectedStories: []
34918
+ };
34919
+ }
34885
34920
  const affectedStories = new Set;
34886
34921
  const affectedStoriesObjs = new Map;
34887
34922
  logger?.warn("regression", "Regression detected", {
@@ -37063,13 +37098,25 @@ async function executeUnified(ctx, initialPrd) {
37063
37098
  prd = await loadPRD(ctx.prdPath);
37064
37099
  prdDirty = false;
37065
37100
  }
37101
+ const storyCounts = countStories(prd);
37102
+ logger?.debug("execution", "Loop iteration", {
37103
+ iteration: iterations,
37104
+ isComplete: isComplete(prd),
37105
+ passed: storyCounts.passed,
37106
+ pending: storyCounts.pending,
37107
+ failed: storyCounts.failed,
37108
+ total: storyCounts.total
37109
+ });
37066
37110
  if (isComplete(prd)) {
37111
+ logger?.debug("execution", "All stories complete \u2014 entering completion path");
37067
37112
  if (ctx.interactionChain && isTriggerEnabled("pre-merge", ctx.config)) {
37068
37113
  const shouldProceed = await checkPreMerge({ featureName: ctx.feature, totalStories: prd.userStories.length, cost: totalCost }, ctx.config, ctx.interactionChain);
37069
37114
  if (!shouldProceed)
37070
37115
  return buildResult2("pre-merge-aborted");
37071
37116
  }
37117
+ logger?.debug("execution", "Running deferred review");
37072
37118
  deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef);
37119
+ logger?.debug("execution", "Deferred review done \u2014 returning completed");
37073
37120
  return buildResult2("completed");
37074
37121
  }
37075
37122
  const costLimit = ctx.config.execution.costLimit;
@@ -37322,9 +37369,7 @@ async function executeUnified(ctx, initialPrd) {
37322
37369
  }, ctx.eventEmitter);
37323
37370
  }
37324
37371
  return buildResult2("max-iterations");
37325
- } finally {
37326
- stopHeartbeat();
37327
- }
37372
+ } finally {}
37328
37373
  }
37329
37374
  var _unifiedExecutorDeps;
37330
37375
  var init_unified_executor = __esm(() => {
@@ -70249,6 +70294,7 @@ function validatePlanOutput(raw, feature, branch) {
70249
70294
  }
70250
70295
 
70251
70296
  // src/cli/plan.ts
70297
+ var DEFAULT_TIMEOUT_SECONDS2 = 600;
70252
70298
  var _planDeps = {
70253
70299
  readFile: (path) => Bun.file(path).text(),
70254
70300
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
@@ -70299,7 +70345,7 @@ async function planCommand(workdir, config2, options) {
70299
70345
  const outputPath = join12(outputDir, "prd.json");
70300
70346
  await _planDeps.mkdirp(outputDir);
70301
70347
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
70302
- const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
70348
+ const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
70303
70349
  let rawResponse;
70304
70350
  const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
70305
70351
  if (debateEnabled) {
@@ -70383,6 +70429,7 @@ async function planCommand(workdir, config2, options) {
70383
70429
  }
70384
70430
  rawResponse = await _planDeps.readFile(outputPath);
70385
70431
  } else {
70432
+ const timeoutMs = (config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2) * 1000;
70386
70433
  const completeResult = await adapter.complete(prompt, {
70387
70434
  model: autoModel,
70388
70435
  jsonMode: true,
@@ -70390,7 +70437,7 @@ async function planCommand(workdir, config2, options) {
70390
70437
  config: config2,
70391
70438
  featureName: options.feature,
70392
70439
  sessionRole: "plan",
70393
- timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
70440
+ timeoutMs
70394
70441
  });
70395
70442
  let result = typeof completeResult === "string" ? completeResult : completeResult.output;
70396
70443
  try {
@@ -70763,6 +70810,8 @@ async function planDecomposeCommand(workdir, config2, options) {
70763
70810
  const stages = config2?.debate?.stages;
70764
70811
  const debateEnabled = config2?.debate?.enabled && stages?.decompose?.enabled;
70765
70812
  let rawResponse;
70813
+ const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
70814
+ const timeoutMs = timeoutSeconds * 1000;
70766
70815
  if (debateEnabled) {
70767
70816
  const stageConfig = stages?.decompose;
70768
70817
  const debateSession = _planDeps.createDebateSession({
@@ -70772,7 +70821,7 @@ async function planDecomposeCommand(workdir, config2, options) {
70772
70821
  config: config2,
70773
70822
  workdir,
70774
70823
  featureName: options.feature,
70775
- timeoutSeconds: config2?.execution?.sessionTimeoutSeconds
70824
+ timeoutSeconds
70776
70825
  });
70777
70826
  const debateResult = await debateSession.run(prompt);
70778
70827
  if (debateResult.outcome !== "failed" && debateResult.output) {
@@ -70784,7 +70833,7 @@ async function planDecomposeCommand(workdir, config2, options) {
70784
70833
  sessionRole: "decompose",
70785
70834
  featureName: options.feature,
70786
70835
  storyId: options.storyId,
70787
- timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
70836
+ timeoutMs
70788
70837
  });
70789
70838
  rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70790
70839
  }
@@ -70795,7 +70844,7 @@ async function planDecomposeCommand(workdir, config2, options) {
70795
70844
  sessionRole: "decompose",
70796
70845
  featureName: options.feature,
70797
70846
  storyId: options.storyId,
70798
- timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
70847
+ timeoutMs
70799
70848
  });
70800
70849
  rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70801
70850
  }
@@ -73438,6 +73487,10 @@ init_crash_recovery();
73438
73487
  init_story_context();
73439
73488
  async function runCompletionPhase(options) {
73440
73489
  const logger = getSafeLogger();
73490
+ logger?.debug("execution", "Completion phase started", {
73491
+ acceptanceEnabled: options.config.acceptance?.enabled,
73492
+ isComplete: isComplete(options.prd)
73493
+ });
73441
73494
  if (options.config.acceptance.enabled && isComplete(options.prd)) {
73442
73495
  const { runAcceptanceLoop: runAcceptanceLoop2 } = await Promise.resolve().then(() => (init_acceptance_loop(), exports_acceptance_loop));
73443
73496
  const acceptanceResult = await runAcceptanceLoop2({
@@ -73505,9 +73558,12 @@ async function runCompletionPhase(options) {
73505
73558
  formatterMode: options.formatterMode
73506
73559
  });
73507
73560
  }
73561
+ logger?.debug("execution", "Completion phase \u2014 stopping heartbeat and writing exit summary");
73508
73562
  stopHeartbeat();
73509
73563
  await writeExitSummary(options.logFilePath, options.totalCost, options.iterations, options.storiesCompleted, durationMs);
73564
+ logger?.debug("execution", "Completion phase \u2014 auto-committing dirty files");
73510
73565
  await autoCommitIfDirty(options.workdir, "run.complete", "run-summary", options.feature);
73566
+ logger?.debug("execution", "Completion phase done \u2014 returning to runner");
73511
73567
  return {
73512
73568
  durationMs,
73513
73569
  runCompletedAt
@@ -73645,6 +73701,12 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
73645
73701
  storiesCompleted = unifiedResult.storiesCompleted;
73646
73702
  totalCost = unifiedResult.totalCost;
73647
73703
  allStoryMetrics.push(...unifiedResult.allStoryMetrics);
73704
+ logger?.debug("execution", "Execution phase complete \u2014 handing off to completion phase", {
73705
+ exitReason: unifiedResult.exitReason,
73706
+ iterations,
73707
+ storiesCompleted,
73708
+ totalCost
73709
+ });
73648
73710
  return { prd, iterations, storiesCompleted, totalCost, allStoryMetrics };
73649
73711
  }
73650
73712
 
@@ -73803,15 +73865,20 @@ async function run(options) {
73803
73865
  durationMs
73804
73866
  };
73805
73867
  } finally {
73868
+ const logger2 = getSafeLogger();
73869
+ logger2?.debug("execution", "Runner finally block \u2014 starting cleanup");
73806
73870
  stopHeartbeat();
73807
73871
  cleanupCrashHandlers();
73872
+ logger2?.debug("execution", "Runner finally \u2014 sweeping ACP sessions");
73808
73873
  await sweepFeatureSessions(workdir, feature).catch(() => {});
73874
+ logger2?.debug("execution", "Runner finally \u2014 ACP sweep done");
73809
73875
  let branch = "";
73810
73876
  try {
73811
73877
  const { stdout, exitCode } = await gitWithTimeout(["branch", "--show-current"], workdir);
73812
73878
  if (exitCode === 0)
73813
73879
  branch = stdout.trim();
73814
73880
  } catch {}
73881
+ logger2?.debug("execution", "Runner finally \u2014 running cleanupRun");
73815
73882
  const { cleanupRun: cleanupRun2 } = await Promise.resolve().then(() => (init_run_cleanup(), exports_run_cleanup));
73816
73883
  await cleanupRun2({
73817
73884
  runId,
@@ -73827,6 +73894,7 @@ async function run(options) {
73827
73894
  branch,
73828
73895
  version: NAX_VERSION
73829
73896
  });
73897
+ logger2?.debug("execution", "Runner finally \u2014 cleanupRun done, run() returning");
73830
73898
  }
73831
73899
  }
73832
73900
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.57.0",
3
+ "version": "0.57.1-canary.1",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {