@nathapp/nax 0.42.4 → 0.42.5

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.
package/dist/nax.js CHANGED
@@ -3244,7 +3244,10 @@ async function executeComplete(binary, prompt, options) {
3244
3244
  if (options?.jsonMode) {
3245
3245
  cmd.push("--output-format", "json");
3246
3246
  }
3247
- const proc = _completeDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
3247
+ const spawnOpts = { stdout: "pipe", stderr: "pipe" };
3248
+ if (options?.workdir)
3249
+ spawnOpts.cwd = options.workdir;
3250
+ const proc = _completeDeps.spawn(cmd, spawnOpts);
3248
3251
  const exitCode = await proc.exited;
3249
3252
  const stdout = await new Response(proc.stdout).text();
3250
3253
  const stderr = await new Response(proc.stderr).text();
@@ -19053,7 +19056,11 @@ class SpawnAcpClient {
19053
19056
  const parts = cmdStr.split(/\s+/);
19054
19057
  const modelIdx = parts.indexOf("--model");
19055
19058
  this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
19056
- this.agentName = parts[parts.length - 1] || "claude";
19059
+ const lastToken = parts[parts.length - 1];
19060
+ if (!lastToken || lastToken.startsWith("-")) {
19061
+ throw new Error(`[acp-adapter] Could not parse agentName from cmdStr: "${cmdStr}"`);
19062
+ }
19063
+ this.agentName = lastToken;
19057
19064
  this.cwd = cwd || process.cwd();
19058
19065
  this.timeoutSeconds = timeoutSeconds || 1800;
19059
19066
  this.env = buildAllowedEnv2();
@@ -19061,8 +19068,8 @@ class SpawnAcpClient {
19061
19068
  async start() {}
19062
19069
  async createSession(opts) {
19063
19070
  const sessionName = opts.sessionName || `nax-${Date.now()}`;
19064
- const cmd = ["acpx", "--cwd", this.cwd, opts.agentName, "sessions", "new", "--name", sessionName];
19065
- getSafeLogger()?.debug("acp-adapter", `Creating new session: ${sessionName}`);
19071
+ const cmd = ["acpx", "--cwd", this.cwd, opts.agentName, "sessions", "ensure", "--name", sessionName];
19072
+ getSafeLogger()?.debug("acp-adapter", `Ensuring session: ${sessionName}`);
19066
19073
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
19067
19074
  const exitCode = await proc.exited;
19068
19075
  if (exitCode !== 0) {
@@ -19079,15 +19086,15 @@ class SpawnAcpClient {
19079
19086
  env: this.env
19080
19087
  });
19081
19088
  }
19082
- async loadSession(sessionName) {
19083
- const cmd = ["acpx", "--cwd", this.cwd, this.agentName, "sessions", "ensure", "--name", sessionName];
19089
+ async loadSession(sessionName, agentName) {
19090
+ const cmd = ["acpx", "--cwd", this.cwd, agentName, "sessions", "ensure", "--name", sessionName];
19084
19091
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
19085
19092
  const exitCode = await proc.exited;
19086
19093
  if (exitCode !== 0) {
19087
19094
  return null;
19088
19095
  }
19089
19096
  return new SpawnAcpSession({
19090
- agentName: this.agentName,
19097
+ agentName,
19091
19098
  sessionName,
19092
19099
  cwd: this.cwd,
19093
19100
  model: this.model,
@@ -19177,9 +19184,12 @@ function buildSessionName(workdir, featureName, storyId, sessionRole) {
19177
19184
  return parts.join("-");
19178
19185
  }
19179
19186
  async function ensureAcpSession(client, sessionName, agentName, permissionMode) {
19187
+ if (!agentName) {
19188
+ throw new Error("[acp-adapter] agentName is required for ensureAcpSession");
19189
+ }
19180
19190
  if (client.loadSession) {
19181
19191
  try {
19182
- const existing = await client.loadSession(sessionName);
19192
+ const existing = await client.loadSession(sessionName, agentName);
19183
19193
  if (existing) {
19184
19194
  getSafeLogger()?.debug("acp-adapter", `Resumed existing session: ${sessionName}`);
19185
19195
  return existing;
@@ -19445,10 +19455,11 @@ class AcpAgentAdapter {
19445
19455
  const model = _options?.model ?? "default";
19446
19456
  const timeoutMs = _options?.timeoutMs ?? 120000;
19447
19457
  const permissionMode = _options?.dangerouslySkipPermissions ? "approve-all" : "default";
19458
+ const workdir = _options?.workdir;
19448
19459
  let lastError;
19449
19460
  for (let attempt = 0;attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
19450
19461
  const cmdStr = `acpx --model ${model} ${this.name}`;
19451
- const client = _acpAdapterDeps.createClient(cmdStr);
19462
+ const client = _acpAdapterDeps.createClient(cmdStr, workdir);
19452
19463
  await client.start();
19453
19464
  let session = null;
19454
19465
  try {
@@ -21859,7 +21870,7 @@ var package_default;
21859
21870
  var init_package = __esm(() => {
21860
21871
  package_default = {
21861
21872
  name: "@nathapp/nax",
21862
- version: "0.42.4",
21873
+ version: "0.42.5",
21863
21874
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
21864
21875
  type: "module",
21865
21876
  bin: {
@@ -21932,8 +21943,8 @@ var init_version = __esm(() => {
21932
21943
  NAX_VERSION = package_default.version;
21933
21944
  NAX_COMMIT = (() => {
21934
21945
  try {
21935
- if (/^[0-9a-f]{6,10}$/.test("35ef278"))
21936
- return "35ef278";
21946
+ if (/^[0-9a-f]{6,10}$/.test("7b603fa"))
21947
+ return "7b603fa";
21937
21948
  } catch {}
21938
21949
  try {
21939
21950
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -65781,7 +65792,7 @@ async function planCommand(workdir, config2, options) {
65781
65792
  const cliAdapter = _deps2.getAgent(agentName);
65782
65793
  if (!cliAdapter)
65783
65794
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
65784
- rawResponse = await cliAdapter.complete(prompt, { jsonMode: true });
65795
+ rawResponse = await cliAdapter.complete(prompt, { jsonMode: true, workdir });
65785
65796
  try {
65786
65797
  const envelope = JSON.parse(rawResponse);
65787
65798
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
@@ -65805,7 +65816,8 @@ async function planCommand(workdir, config2, options) {
65805
65816
  config: config2,
65806
65817
  modelTier: config2?.plan?.model ?? "balanced",
65807
65818
  dangerouslySkipPermissions: config2?.execution?.dangerouslySkipPermissions ?? false,
65808
- maxInteractionTurns: config2?.agent?.maxInteractionTurns
65819
+ maxInteractionTurns: config2?.agent?.maxInteractionTurns,
65820
+ featureName: options.feature
65809
65821
  });
65810
65822
  } finally {
65811
65823
  logger?.info("plan", "Interactive session ended");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.42.4",
3
+ "version": "0.42.5",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -92,7 +92,7 @@ export interface AcpClient {
92
92
  start(): Promise<void>;
93
93
  createSession(opts: { agentName: string; permissionMode: string; sessionName?: string }): Promise<AcpSession>;
94
94
  /** Resume an existing named session. Returns null if the session is not found. */
95
- loadSession?(sessionName: string): Promise<AcpSession | null>;
95
+ loadSession?(sessionName: string, agentName: string): Promise<AcpSession | null>;
96
96
  close(): Promise<void>;
97
97
  }
98
98
 
@@ -180,10 +180,14 @@ export async function ensureAcpSession(
180
180
  agentName: string,
181
181
  permissionMode: string,
182
182
  ): Promise<AcpSession> {
183
+ if (!agentName) {
184
+ throw new Error("[acp-adapter] agentName is required for ensureAcpSession");
185
+ }
186
+
183
187
  // Try to resume existing session first
184
188
  if (client.loadSession) {
185
189
  try {
186
- const existing = await client.loadSession(sessionName);
190
+ const existing = await client.loadSession(sessionName, agentName);
187
191
  if (existing) {
188
192
  getSafeLogger()?.debug("acp-adapter", `Resumed existing session: ${sessionName}`);
189
193
  return existing;
@@ -554,12 +558,13 @@ export class AcpAgentAdapter implements AgentAdapter {
554
558
  const model = _options?.model ?? "default";
555
559
  const timeoutMs = _options?.timeoutMs ?? 120_000; // 2-min safety net by default
556
560
  const permissionMode = _options?.dangerouslySkipPermissions ? "approve-all" : "default";
561
+ const workdir = _options?.workdir;
557
562
 
558
563
  let lastError: Error | undefined;
559
564
 
560
565
  for (let attempt = 0; attempt < MAX_RATE_LIMIT_RETRIES; attempt++) {
561
566
  const cmdStr = `acpx --model ${model} ${this.name}`;
562
- const client = _acpAdapterDeps.createClient(cmdStr);
567
+ const client = _acpAdapterDeps.createClient(cmdStr, workdir);
563
568
  await client.start();
564
569
 
565
570
  let session: AcpSession | null = null;
@@ -211,7 +211,7 @@ class SpawnAcpSession implements AcpSession {
211
211
  * The cmdStr is parsed to extract --model and agent name:
212
212
  * "acpx --model claude-sonnet-4-5 claude" → model=claude-sonnet-4-5, agent=claude
213
213
  *
214
- * createSession() spawns: acpx --cwd <dir> <agent> sessions new --name <name>
214
+ * createSession() spawns: acpx <agent> sessions ensure --name <name>
215
215
  * loadSession() tries to resume an existing named session.
216
216
  */
217
217
  export class SpawnAcpClient implements AcpClient {
@@ -226,8 +226,12 @@ export class SpawnAcpClient implements AcpClient {
226
226
  const parts = cmdStr.split(/\s+/);
227
227
  const modelIdx = parts.indexOf("--model");
228
228
  this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
229
- // Agent name is the last non-flag token
230
- this.agentName = parts[parts.length - 1] || "claude";
229
+ // Agent name is the last non-flag token — must be present and not a flag
230
+ const lastToken = parts[parts.length - 1];
231
+ if (!lastToken || lastToken.startsWith("-")) {
232
+ throw new Error(`[acp-adapter] Could not parse agentName from cmdStr: "${cmdStr}"`);
233
+ }
234
+ this.agentName = lastToken;
231
235
  this.cwd = cwd || process.cwd();
232
236
  this.timeoutSeconds = timeoutSeconds || 1800;
233
237
  this.env = buildAllowedEnv();
@@ -244,9 +248,9 @@ export class SpawnAcpClient implements AcpClient {
244
248
  }): Promise<AcpSession> {
245
249
  const sessionName = opts.sessionName || `nax-${Date.now()}`;
246
250
 
247
- // Create new session via CLI (sessions new = always creates; sessions ensure = lookup only)
248
- const cmd = ["acpx", "--cwd", this.cwd, opts.agentName, "sessions", "new", "--name", sessionName];
249
- getSafeLogger()?.debug("acp-adapter", `Creating new session: ${sessionName}`);
251
+ // Ensure session exists via CLI
252
+ const cmd = ["acpx", "--cwd", this.cwd, opts.agentName, "sessions", "ensure", "--name", sessionName];
253
+ getSafeLogger()?.debug("acp-adapter", `Ensuring session: ${sessionName}`);
250
254
 
251
255
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
252
256
  const exitCode = await proc.exited;
@@ -267,9 +271,9 @@ export class SpawnAcpClient implements AcpClient {
267
271
  });
268
272
  }
269
273
 
270
- async loadSession(sessionName: string): Promise<AcpSession | null> {
274
+ async loadSession(sessionName: string, agentName: string): Promise<AcpSession | null> {
271
275
  // Try to ensure session exists — if it does, acpx returns success
272
- const cmd = ["acpx", "--cwd", this.cwd, this.agentName, "sessions", "ensure", "--name", sessionName];
276
+ const cmd = ["acpx", "--cwd", this.cwd, agentName, "sessions", "ensure", "--name", sessionName];
273
277
 
274
278
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
275
279
  const exitCode = await proc.exited;
@@ -279,7 +283,7 @@ export class SpawnAcpClient implements AcpClient {
279
283
  }
280
284
 
281
285
  return new SpawnAcpSession({
282
- agentName: this.agentName,
286
+ agentName,
283
287
  sessionName,
284
288
  cwd: this.cwd,
285
289
  model: this.model,
@@ -51,7 +51,9 @@ export async function executeComplete(binary: string, prompt: string, options?:
51
51
  cmd.push("--output-format", "json");
52
52
  }
53
53
 
54
- const proc = _completeDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
54
+ const spawnOpts: { stdout: "pipe"; stderr: "pipe"; cwd?: string } = { stdout: "pipe", stderr: "pipe" };
55
+ if (options?.workdir) spawnOpts.cwd = options.workdir;
56
+ const proc = _completeDeps.spawn(cmd, spawnOpts);
55
57
  const exitCode = await proc.exited;
56
58
 
57
59
  const stdout = await new Response(proc.stdout).text();
@@ -102,6 +102,12 @@ export interface CompleteOptions {
102
102
  model?: string;
103
103
  /** Whether to skip permission prompts (maps to permissionMode in ACP) */
104
104
  dangerouslySkipPermissions?: boolean;
105
+ /**
106
+ * Working directory for the completion call.
107
+ * Used by ACP adapter to set --cwd on the spawned acpx session.
108
+ * CLI adapter uses this as the process cwd when spawning the agent binary.
109
+ */
110
+ workdir?: string;
105
111
  /**
106
112
  * Timeout for the completion call in milliseconds.
107
113
  * Adapters that support it (e.g. ACP) will enforce this as a hard deadline.
package/src/cli/plan.ts CHANGED
@@ -107,7 +107,7 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
107
107
  const prompt = buildPlanningPrompt(specContent, codebaseContext);
108
108
  const cliAdapter = _deps.getAgent(agentName);
109
109
  if (!cliAdapter) throw new Error(`[plan] No agent adapter found for '${agentName}'`);
110
- rawResponse = await cliAdapter.complete(prompt, { jsonMode: true });
110
+ rawResponse = await cliAdapter.complete(prompt, { jsonMode: true, workdir });
111
111
  // CLI adapter returns {"type":"result","result":"..."} envelope — unwrap it
112
112
  try {
113
113
  const envelope = JSON.parse(rawResponse) as Record<string, unknown>;
@@ -135,6 +135,7 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
135
135
  modelTier: config?.plan?.model ?? "balanced",
136
136
  dangerouslySkipPermissions: config?.execution?.dangerouslySkipPermissions ?? false,
137
137
  maxInteractionTurns: config?.agent?.maxInteractionTurns,
138
+ featureName: options.feature,
138
139
  });
139
140
  } finally {
140
141
  logger?.info("plan", "Interactive session ended");