@nathapp/nax 0.56.1 → 0.56.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 +162 -68
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -22056,7 +22056,7 @@ var package_default;
22056
22056
  var init_package = __esm(() => {
22057
22057
  package_default = {
22058
22058
  name: "@nathapp/nax",
22059
- version: "0.56.1",
22059
+ version: "0.56.2",
22060
22060
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22061
22061
  type: "module",
22062
22062
  bin: {
@@ -22135,8 +22135,8 @@ var init_version = __esm(() => {
22135
22135
  NAX_VERSION = package_default.version;
22136
22136
  NAX_COMMIT = (() => {
22137
22137
  try {
22138
- if (/^[0-9a-f]{6,10}$/.test("bfe07f9f"))
22139
- return "bfe07f9f";
22138
+ if (/^[0-9a-f]{6,10}$/.test("b590070f"))
22139
+ return "b590070f";
22140
22140
  } catch {}
22141
22141
  try {
22142
22142
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22389,6 +22389,18 @@ var DEFAULT_FALLBACK_AGENT = "claude";
22389
22389
  var init_resolvers = () => {};
22390
22390
 
22391
22391
  // src/debate/session.ts
22392
+ function resolveDebaterModel(debater, config2) {
22393
+ const tier = debater.model ?? "fast";
22394
+ if (!config2?.models)
22395
+ return debater.model;
22396
+ try {
22397
+ const defaultAgent = config2.autoMode?.defaultAgent ?? debater.agent;
22398
+ const modelDef = resolveModelForAgent(config2.models, debater.agent, tier, defaultAgent);
22399
+ return modelDef.model;
22400
+ } catch {
22401
+ return debater.model;
22402
+ }
22403
+ }
22392
22404
  function buildFailedResult(storyId, stage, stageConfig, totalCostUsd = 0) {
22393
22405
  return {
22394
22406
  storyId,
@@ -22411,10 +22423,12 @@ class DebateSession {
22411
22423
  storyId;
22412
22424
  stage;
22413
22425
  stageConfig;
22426
+ config;
22414
22427
  constructor(opts) {
22415
22428
  this.storyId = opts.storyId;
22416
22429
  this.stage = opts.stage;
22417
22430
  this.stageConfig = opts.stageConfig;
22431
+ this.config = opts.config;
22418
22432
  }
22419
22433
  async run(prompt) {
22420
22434
  const sessionMode = this.stageConfig.sessionMode ?? "one-shot";
@@ -22437,11 +22451,17 @@ class DebateSession {
22437
22451
  }
22438
22452
  resolved.push({ debater, adapter });
22439
22453
  }
22454
+ logger?.info("debate", "debate:start", {
22455
+ storyId: this.storyId,
22456
+ stage: this.stage,
22457
+ debaters: resolved.map((r) => r.debater.agent)
22458
+ });
22440
22459
  const sessions = [];
22441
22460
  try {
22442
22461
  for (let i = 0;i < resolved.length; i++) {
22443
22462
  const { debater, adapter } = resolved[i];
22444
- const cmdStr = `acpx --model ${debater.model} ${debater.agent}`;
22463
+ const resolvedModel = resolveDebaterModel(debater, this.config);
22464
+ const cmdStr = resolvedModel ? `acpx --model ${resolvedModel} ${debater.agent}` : `acpx ${debater.agent}`;
22445
22465
  const client = _debateSessionDeps.createSpawnAcpClient(cmdStr);
22446
22466
  const sessionName = `nax-debate-${this.storyId}-${i}`;
22447
22467
  try {
@@ -22457,9 +22477,19 @@ class DebateSession {
22457
22477
  }
22458
22478
  if (sessions.length < 2) {
22459
22479
  if (sessions.length === 1) {
22480
+ logger?.warn("debate", "debate:fallback", {
22481
+ storyId: this.storyId,
22482
+ stage: this.stage,
22483
+ reason: "only 1 session created"
22484
+ });
22460
22485
  const solo = sessions[0];
22461
22486
  const response = await solo.session.prompt(prompt);
22462
22487
  const output = extractSessionOutput(response);
22488
+ logger?.info("debate", "debate:result", {
22489
+ storyId: this.storyId,
22490
+ stage: this.stage,
22491
+ outcome: "passed"
22492
+ });
22463
22493
  return {
22464
22494
  storyId: this.storyId,
22465
22495
  stage: this.stage,
@@ -22471,6 +22501,11 @@ class DebateSession {
22471
22501
  totalCostUsd
22472
22502
  };
22473
22503
  }
22504
+ logger?.warn("debate", "debate:fallback", {
22505
+ storyId: this.storyId,
22506
+ stage: this.stage,
22507
+ reason: "no sessions created"
22508
+ });
22474
22509
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22475
22510
  }
22476
22511
  const proposalSettled = await Promise.allSettled(sessions.map(({ session }) => session.prompt(prompt)));
@@ -22486,8 +22521,22 @@ class DebateSession {
22486
22521
  }
22487
22522
  }
22488
22523
  if (successfulSessions.length < 2) {
22524
+ logger?.warn("debate", "debate:fallback", {
22525
+ storyId: this.storyId,
22526
+ stage: this.stage,
22527
+ reason: "fewer than 2 proposal rounds succeeded"
22528
+ });
22489
22529
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22490
22530
  }
22531
+ for (let i = 0;i < successfulSessions.length; i++) {
22532
+ const s = successfulSessions[i];
22533
+ logger?.info("debate", "debate:proposal", {
22534
+ storyId: this.storyId,
22535
+ stage: this.stage,
22536
+ debaterIndex: i,
22537
+ agent: s.entry.debater.agent
22538
+ });
22539
+ }
22491
22540
  let critiqueOutputs = [];
22492
22541
  if (config2.rounds > 1) {
22493
22542
  const proposalOutputs2 = successfulSessions.map((s) => s.output);
@@ -22506,6 +22555,11 @@ class DebateSession {
22506
22555
  debater: s.entry.debater,
22507
22556
  output: s.output
22508
22557
  }));
22558
+ logger?.info("debate", "debate:result", {
22559
+ storyId: this.storyId,
22560
+ stage: this.stage,
22561
+ outcome
22562
+ });
22509
22563
  return {
22510
22564
  storyId: this.storyId,
22511
22565
  stage: this.stage,
@@ -22534,16 +22588,40 @@ class DebateSession {
22534
22588
  }
22535
22589
  resolved.push({ debater, adapter });
22536
22590
  }
22537
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: debater.model }).then((output) => ({ debater, adapter, output, cost: 0 }))));
22591
+ logger?.info("debate", "debate:start", {
22592
+ storyId: this.storyId,
22593
+ stage: this.stage,
22594
+ debaters: resolved.map((r) => r.debater.agent)
22595
+ });
22596
+ const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: resolveDebaterModel(debater, this.config) }).then((output) => ({ debater, adapter, output, cost: 0 }))));
22538
22597
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22539
22598
  for (const r of proposalSettled) {
22540
22599
  if (r.status === "fulfilled") {
22541
22600
  totalCostUsd += r.value.cost;
22542
22601
  }
22543
22602
  }
22603
+ for (let i = 0;i < successful.length; i++) {
22604
+ logger?.info("debate", "debate:proposal", {
22605
+ storyId: this.storyId,
22606
+ stage: this.stage,
22607
+ debaterIndex: i,
22608
+ agent: successful[i].debater.agent,
22609
+ model: resolveDebaterModel(successful[i].debater, this.config)
22610
+ });
22611
+ }
22544
22612
  if (successful.length < 2) {
22545
22613
  if (successful.length === 1) {
22614
+ logger?.warn("debate", "debate:fallback", {
22615
+ storyId: this.storyId,
22616
+ stage: this.stage,
22617
+ reason: "only 1 debater succeeded"
22618
+ });
22546
22619
  const solo = successful[0];
22620
+ logger?.info("debate", "debate:result", {
22621
+ storyId: this.storyId,
22622
+ stage: this.stage,
22623
+ outcome: "passed"
22624
+ });
22547
22625
  return {
22548
22626
  storyId: this.storyId,
22549
22627
  stage: this.stage,
@@ -22557,8 +22635,20 @@ class DebateSession {
22557
22635
  }
22558
22636
  if (resolved.length > 0) {
22559
22637
  const { adapter: fallbackAdapter, debater: fallbackDebater } = resolved[0];
22638
+ logger?.warn("debate", "debate:fallback", {
22639
+ storyId: this.storyId,
22640
+ stage: this.stage,
22641
+ reason: "all debaters failed \u2014 retrying with first adapter"
22642
+ });
22560
22643
  try {
22561
- const fallbackOutput = await fallbackAdapter.complete(prompt, { model: fallbackDebater.model });
22644
+ const fallbackOutput = await fallbackAdapter.complete(prompt, {
22645
+ model: resolveDebaterModel(fallbackDebater, this.config)
22646
+ });
22647
+ logger?.info("debate", "debate:result", {
22648
+ storyId: this.storyId,
22649
+ stage: this.stage,
22650
+ outcome: "passed"
22651
+ });
22562
22652
  return {
22563
22653
  storyId: this.storyId,
22564
22654
  stage: this.stage,
@@ -22577,7 +22667,7 @@ class DebateSession {
22577
22667
  if (config2.rounds > 1) {
22578
22668
  const proposalOutputs2 = successful.map((p) => p.output);
22579
22669
  const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => adapter.complete(buildCritiquePrompt(prompt, proposalOutputs2, i), {
22580
- model: debater.model
22670
+ model: resolveDebaterModel(debater, this.config)
22581
22671
  })));
22582
22672
  for (const r of critiqueSettled) {
22583
22673
  if (r.status === "fulfilled") {
@@ -22593,6 +22683,11 @@ class DebateSession {
22593
22683
  debater: p.debater,
22594
22684
  output: p.output
22595
22685
  }));
22686
+ logger?.info("debate", "debate:result", {
22687
+ storyId: this.storyId,
22688
+ stage: this.stage,
22689
+ outcome
22690
+ });
22596
22691
  return {
22597
22692
  storyId: this.storyId,
22598
22693
  stage: this.stage,
@@ -22604,7 +22699,7 @@ class DebateSession {
22604
22699
  totalCostUsd
22605
22700
  };
22606
22701
  }
22607
- async resolve(proposalOutputs, critiqueOutputs, successful) {
22702
+ async resolve(proposalOutputs, critiqueOutputs, _successful) {
22608
22703
  const resolverConfig = this.stageConfig.resolver;
22609
22704
  if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
22610
22705
  return majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open");
@@ -22631,6 +22726,7 @@ var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
22631
22726
  var init_session = __esm(() => {
22632
22727
  init_spawn_client();
22633
22728
  init_registry();
22729
+ init_config();
22634
22730
  init_logger2();
22635
22731
  init_resolvers();
22636
22732
  _debateSessionDeps = {
@@ -26276,7 +26372,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
26276
26372
  const debateSession = _semanticDeps.createDebateSession({
26277
26373
  storyId: story.id,
26278
26374
  stage: "review",
26279
- stageConfig: reviewStageConfig
26375
+ stageConfig: reviewStageConfig,
26376
+ config: naxConfig
26280
26377
  });
26281
26378
  const debateResult = await debateSession.run(prompt);
26282
26379
  let passCount = 0;
@@ -69453,78 +69550,75 @@ async function planCommand(workdir, config2, options) {
69453
69550
  autoModel = resolveModelForAgent2(config2.models, defaultAgent, planTier, defaultAgent).model;
69454
69551
  }
69455
69552
  } catch {}
69456
- if (isAcp) {
69457
- logger?.info("plan", "Starting ACP auto planning session", {
69458
- agent: agentName,
69459
- model: autoModel ?? config2?.plan?.model ?? "balanced",
69460
- workdir,
69461
- feature: options.feature,
69462
- timeoutSeconds
69463
- });
69464
- const pidRegistry = new PidRegistry(workdir);
69465
- try {
69466
- await adapter.plan({
69467
- prompt,
69553
+ const runSingleAgentPlan = async () => {
69554
+ if (isAcp) {
69555
+ logger?.info("plan", "Starting ACP auto planning session", {
69556
+ agent: agentName,
69557
+ model: autoModel ?? config2?.plan?.model ?? "balanced",
69468
69558
  workdir,
69469
- interactive: false,
69470
- timeoutSeconds,
69471
- config: config2,
69472
- modelTier: config2?.plan?.model ?? "balanced",
69473
- dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69474
- maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69475
- featureName: options.feature,
69476
- pidRegistry,
69477
- sessionRole: "plan"
69478
- });
69479
- } finally {
69480
- await pidRegistry.killAll().catch(() => {});
69481
- }
69482
- if (!_planDeps.existsSync(outputPath)) {
69483
- throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69484
- }
69485
- rawResponse = await _planDeps.readFile(outputPath);
69486
- } else {
69487
- const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69488
- if (debateEnabled) {
69489
- const planStageConfig = config2.debate?.stages.plan;
69490
- const debateSession = _planDeps.createDebateSession({
69491
- storyId: options.feature,
69492
- stage: "plan",
69493
- stageConfig: planStageConfig
69559
+ feature: options.feature,
69560
+ timeoutSeconds
69494
69561
  });
69495
- const debateResult = await debateSession.run(prompt);
69496
- if (debateResult.outcome !== "failed" && debateResult.output) {
69497
- rawResponse = debateResult.output;
69498
- } else {
69499
- logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69500
- stage: "debate",
69501
- event: "fallback"
69502
- });
69503
- rawResponse = await adapter.complete(prompt, {
69504
- model: autoModel,
69505
- jsonMode: true,
69562
+ const pidRegistry = new PidRegistry(workdir);
69563
+ try {
69564
+ await adapter.plan({
69565
+ prompt,
69506
69566
  workdir,
69567
+ interactive: false,
69568
+ timeoutSeconds,
69507
69569
  config: config2,
69570
+ modelTier: config2?.plan?.model ?? "balanced",
69571
+ dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69572
+ maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69508
69573
  featureName: options.feature,
69574
+ pidRegistry,
69509
69575
  sessionRole: "plan"
69510
69576
  });
69577
+ } finally {
69578
+ await pidRegistry.killAll().catch(() => {});
69511
69579
  }
69512
- } else {
69513
- rawResponse = await adapter.complete(prompt, {
69514
- model: autoModel,
69515
- jsonMode: true,
69516
- workdir,
69517
- config: config2,
69518
- featureName: options.feature,
69519
- sessionRole: "plan"
69520
- });
69580
+ if (!_planDeps.existsSync(outputPath)) {
69581
+ throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69582
+ }
69583
+ return await _planDeps.readFile(outputPath);
69521
69584
  }
69585
+ let result = await adapter.complete(prompt, {
69586
+ model: autoModel,
69587
+ jsonMode: true,
69588
+ workdir,
69589
+ config: config2,
69590
+ featureName: options.feature,
69591
+ sessionRole: "plan"
69592
+ });
69522
69593
  try {
69523
- const envelope = JSON.parse(rawResponse);
69594
+ const envelope = JSON.parse(result);
69524
69595
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
69525
- rawResponse = envelope.result;
69596
+ result = envelope.result;
69526
69597
  }
69527
69598
  } catch {}
69599
+ return result;
69600
+ };
69601
+ const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69602
+ if (debateEnabled) {
69603
+ const planStageConfig = config2?.debate?.stages.plan;
69604
+ const debateSession = _planDeps.createDebateSession({
69605
+ storyId: options.feature,
69606
+ stage: "plan",
69607
+ stageConfig: planStageConfig,
69608
+ config: config2
69609
+ });
69610
+ const debateResult = await debateSession.run(prompt);
69611
+ if (debateResult.outcome !== "failed" && debateResult.output) {
69612
+ rawResponse = debateResult.output;
69613
+ } else {
69614
+ logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69615
+ stage: "debate",
69616
+ event: "fallback"
69617
+ });
69618
+ rawResponse = await runSingleAgentPlan();
69619
+ }
69620
+ } else {
69621
+ rawResponse = await runSingleAgentPlan();
69528
69622
  }
69529
69623
  } else {
69530
69624
  const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.56.1",
3
+ "version": "0.56.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {