@nathapp/nax 0.42.5 → 0.42.6
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 +71 -38
- package/package.json +1 -1
- package/src/agents/acp/adapter.ts +9 -3
- package/src/agents/acp/spawn-client.ts +74 -30
- package/src/agents/types-extended.ts +2 -0
- package/src/cli/plan.ts +4 -0
package/dist/nax.js
CHANGED
|
@@ -18963,6 +18963,8 @@ class SpawnAcpSession {
|
|
|
18963
18963
|
timeoutSeconds;
|
|
18964
18964
|
permissionMode;
|
|
18965
18965
|
env;
|
|
18966
|
+
pidRegistry;
|
|
18967
|
+
activeProc = null;
|
|
18966
18968
|
constructor(opts) {
|
|
18967
18969
|
this.agentName = opts.agentName;
|
|
18968
18970
|
this.sessionName = opts.sessionName;
|
|
@@ -18971,6 +18973,7 @@ class SpawnAcpSession {
|
|
|
18971
18973
|
this.timeoutSeconds = opts.timeoutSeconds;
|
|
18972
18974
|
this.permissionMode = opts.permissionMode;
|
|
18973
18975
|
this.env = opts.env;
|
|
18976
|
+
this.pidRegistry = opts.pidRegistry;
|
|
18974
18977
|
}
|
|
18975
18978
|
async prompt(text) {
|
|
18976
18979
|
const cmd = [
|
|
@@ -18997,35 +19000,50 @@ class SpawnAcpSession {
|
|
|
18997
19000
|
stderr: "pipe",
|
|
18998
19001
|
env: this.env
|
|
18999
19002
|
});
|
|
19000
|
-
proc
|
|
19001
|
-
proc.
|
|
19002
|
-
|
|
19003
|
-
const stdout = await new Response(proc.stdout).text();
|
|
19004
|
-
const stderr = await new Response(proc.stderr).text();
|
|
19005
|
-
if (exitCode !== 0) {
|
|
19006
|
-
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
19007
|
-
stderr: stderr.slice(0, 200)
|
|
19008
|
-
});
|
|
19009
|
-
return {
|
|
19010
|
-
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
19011
|
-
stopReason: "error"
|
|
19012
|
-
};
|
|
19013
|
-
}
|
|
19003
|
+
this.activeProc = proc;
|
|
19004
|
+
const processPid = proc.pid;
|
|
19005
|
+
await this.pidRegistry?.register(processPid);
|
|
19014
19006
|
try {
|
|
19015
|
-
|
|
19016
|
-
|
|
19017
|
-
|
|
19018
|
-
|
|
19019
|
-
|
|
19020
|
-
|
|
19021
|
-
|
|
19022
|
-
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19007
|
+
proc.stdin.write(text);
|
|
19008
|
+
proc.stdin.end();
|
|
19009
|
+
const exitCode = await proc.exited;
|
|
19010
|
+
const stdout = await new Response(proc.stdout).text();
|
|
19011
|
+
const stderr = await new Response(proc.stderr).text();
|
|
19012
|
+
if (exitCode !== 0) {
|
|
19013
|
+
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
19014
|
+
stderr: stderr.slice(0, 200)
|
|
19015
|
+
});
|
|
19016
|
+
return {
|
|
19017
|
+
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
19018
|
+
stopReason: "error"
|
|
19019
|
+
};
|
|
19020
|
+
}
|
|
19021
|
+
try {
|
|
19022
|
+
const parsed = parseAcpxJsonOutput(stdout);
|
|
19023
|
+
return {
|
|
19024
|
+
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
19025
|
+
stopReason: "end_turn",
|
|
19026
|
+
cumulative_token_usage: parsed.tokenUsage
|
|
19027
|
+
};
|
|
19028
|
+
} catch (err) {
|
|
19029
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
19030
|
+
stderr: stderr.slice(0, 200)
|
|
19031
|
+
});
|
|
19032
|
+
throw err;
|
|
19033
|
+
}
|
|
19034
|
+
} finally {
|
|
19035
|
+
this.activeProc = null;
|
|
19036
|
+
await this.pidRegistry?.unregister(processPid);
|
|
19026
19037
|
}
|
|
19027
19038
|
}
|
|
19028
19039
|
async close() {
|
|
19040
|
+
if (this.activeProc) {
|
|
19041
|
+
try {
|
|
19042
|
+
this.activeProc.kill(15);
|
|
19043
|
+
getSafeLogger()?.debug("acp-adapter", `Killed active prompt process PID ${this.activeProc.pid}`);
|
|
19044
|
+
} catch {}
|
|
19045
|
+
this.activeProc = null;
|
|
19046
|
+
}
|
|
19029
19047
|
const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
|
|
19030
19048
|
getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
|
|
19031
19049
|
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
@@ -19039,6 +19057,12 @@ class SpawnAcpSession {
|
|
|
19039
19057
|
}
|
|
19040
19058
|
}
|
|
19041
19059
|
async cancelActivePrompt() {
|
|
19060
|
+
if (this.activeProc) {
|
|
19061
|
+
try {
|
|
19062
|
+
this.activeProc.kill(15);
|
|
19063
|
+
getSafeLogger()?.debug("acp-adapter", `Killed active prompt process PID ${this.activeProc.pid}`);
|
|
19064
|
+
} catch {}
|
|
19065
|
+
}
|
|
19042
19066
|
const cmd = ["acpx", this.agentName, "cancel"];
|
|
19043
19067
|
getSafeLogger()?.debug("acp-adapter", `Cancelling active prompt: ${this.sessionName}`);
|
|
19044
19068
|
const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
@@ -19052,7 +19076,8 @@ class SpawnAcpClient {
|
|
|
19052
19076
|
cwd;
|
|
19053
19077
|
timeoutSeconds;
|
|
19054
19078
|
env;
|
|
19055
|
-
|
|
19079
|
+
pidRegistry;
|
|
19080
|
+
constructor(cmdStr, cwd, timeoutSeconds, pidRegistry) {
|
|
19056
19081
|
const parts = cmdStr.split(/\s+/);
|
|
19057
19082
|
const modelIdx = parts.indexOf("--model");
|
|
19058
19083
|
this.model = modelIdx >= 0 && parts[modelIdx + 1] ? parts[modelIdx + 1] : "default";
|
|
@@ -19064,6 +19089,7 @@ class SpawnAcpClient {
|
|
|
19064
19089
|
this.cwd = cwd || process.cwd();
|
|
19065
19090
|
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
19066
19091
|
this.env = buildAllowedEnv2();
|
|
19092
|
+
this.pidRegistry = pidRegistry;
|
|
19067
19093
|
}
|
|
19068
19094
|
async start() {}
|
|
19069
19095
|
async createSession(opts) {
|
|
@@ -19083,7 +19109,8 @@ class SpawnAcpClient {
|
|
|
19083
19109
|
model: this.model,
|
|
19084
19110
|
timeoutSeconds: this.timeoutSeconds,
|
|
19085
19111
|
permissionMode: opts.permissionMode,
|
|
19086
|
-
env: this.env
|
|
19112
|
+
env: this.env,
|
|
19113
|
+
pidRegistry: this.pidRegistry
|
|
19087
19114
|
});
|
|
19088
19115
|
}
|
|
19089
19116
|
async loadSession(sessionName, agentName) {
|
|
@@ -19100,13 +19127,14 @@ class SpawnAcpClient {
|
|
|
19100
19127
|
model: this.model,
|
|
19101
19128
|
timeoutSeconds: this.timeoutSeconds,
|
|
19102
19129
|
permissionMode: "approve-all",
|
|
19103
|
-
env: this.env
|
|
19130
|
+
env: this.env,
|
|
19131
|
+
pidRegistry: this.pidRegistry
|
|
19104
19132
|
});
|
|
19105
19133
|
}
|
|
19106
19134
|
async close() {}
|
|
19107
19135
|
}
|
|
19108
|
-
function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds) {
|
|
19109
|
-
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19136
|
+
function createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry) {
|
|
19137
|
+
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
|
|
19110
19138
|
}
|
|
19111
19139
|
var _spawnClientDeps;
|
|
19112
19140
|
var init_spawn_client = __esm(() => {
|
|
@@ -19367,7 +19395,7 @@ class AcpAgentAdapter {
|
|
|
19367
19395
|
}
|
|
19368
19396
|
async _runWithClient(options, startTime) {
|
|
19369
19397
|
const cmdStr = `acpx --model ${options.modelDef.model} ${this.name}`;
|
|
19370
|
-
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds);
|
|
19398
|
+
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds, options.pidRegistry);
|
|
19371
19399
|
await client.start();
|
|
19372
19400
|
let sessionName = options.acpSessionName;
|
|
19373
19401
|
if (!sessionName && options.featureName && options.storyId) {
|
|
@@ -19539,7 +19567,8 @@ class AcpAgentAdapter {
|
|
|
19539
19567
|
maxInteractionTurns: options.maxInteractionTurns,
|
|
19540
19568
|
featureName: options.featureName,
|
|
19541
19569
|
storyId: options.storyId,
|
|
19542
|
-
sessionRole: options.sessionRole
|
|
19570
|
+
sessionRole: options.sessionRole,
|
|
19571
|
+
pidRegistry: options.pidRegistry
|
|
19543
19572
|
});
|
|
19544
19573
|
if (!result.success) {
|
|
19545
19574
|
throw new Error(`[acp-adapter] plan() failed: ${result.output}`);
|
|
@@ -19609,8 +19638,8 @@ var init_adapter = __esm(() => {
|
|
|
19609
19638
|
async sleep(ms) {
|
|
19610
19639
|
await Bun.sleep(ms);
|
|
19611
19640
|
},
|
|
19612
|
-
createClient(cmdStr, cwd, timeoutSeconds) {
|
|
19613
|
-
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds);
|
|
19641
|
+
createClient(cmdStr, cwd, timeoutSeconds, pidRegistry) {
|
|
19642
|
+
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
|
|
19614
19643
|
}
|
|
19615
19644
|
};
|
|
19616
19645
|
});
|
|
@@ -21870,7 +21899,7 @@ var package_default;
|
|
|
21870
21899
|
var init_package = __esm(() => {
|
|
21871
21900
|
package_default = {
|
|
21872
21901
|
name: "@nathapp/nax",
|
|
21873
|
-
version: "0.42.
|
|
21902
|
+
version: "0.42.6",
|
|
21874
21903
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
21875
21904
|
type: "module",
|
|
21876
21905
|
bin: {
|
|
@@ -21943,8 +21972,8 @@ var init_version = __esm(() => {
|
|
|
21943
21972
|
NAX_VERSION = package_default.version;
|
|
21944
21973
|
NAX_COMMIT = (() => {
|
|
21945
21974
|
try {
|
|
21946
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
21947
|
-
return "
|
|
21975
|
+
if (/^[0-9a-f]{6,10}$/.test("deb8333"))
|
|
21976
|
+
return "deb8333";
|
|
21948
21977
|
} catch {}
|
|
21949
21978
|
try {
|
|
21950
21979
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -65602,6 +65631,7 @@ init_registry();
|
|
|
65602
65631
|
import { existsSync as existsSync9 } from "fs";
|
|
65603
65632
|
import { join as join10 } from "path";
|
|
65604
65633
|
import { createInterface } from "readline";
|
|
65634
|
+
init_pid_registry();
|
|
65605
65635
|
init_logger2();
|
|
65606
65636
|
|
|
65607
65637
|
// src/prd/schema.ts
|
|
@@ -65805,6 +65835,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
65805
65835
|
if (!adapter)
|
|
65806
65836
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
65807
65837
|
const interactionBridge = createCliInteractionBridge();
|
|
65838
|
+
const pidRegistry = new PidRegistry(workdir);
|
|
65808
65839
|
logger?.info("plan", "Starting interactive planning session...", { agent: agentName });
|
|
65809
65840
|
try {
|
|
65810
65841
|
await adapter.plan({
|
|
@@ -65817,9 +65848,11 @@ async function planCommand(workdir, config2, options) {
|
|
|
65817
65848
|
modelTier: config2?.plan?.model ?? "balanced",
|
|
65818
65849
|
dangerouslySkipPermissions: config2?.execution?.dangerouslySkipPermissions ?? false,
|
|
65819
65850
|
maxInteractionTurns: config2?.agent?.maxInteractionTurns,
|
|
65820
|
-
featureName: options.feature
|
|
65851
|
+
featureName: options.feature,
|
|
65852
|
+
pidRegistry
|
|
65821
65853
|
});
|
|
65822
65854
|
} finally {
|
|
65855
|
+
await pidRegistry.killAll().catch(() => {});
|
|
65823
65856
|
logger?.info("plan", "Interactive session ended");
|
|
65824
65857
|
}
|
|
65825
65858
|
if (!_deps2.existsSync(outputPath)) {
|
package/package.json
CHANGED
|
@@ -114,8 +114,13 @@ export const _acpAdapterDeps = {
|
|
|
114
114
|
* Default: spawn-based client (shells out to acpx CLI).
|
|
115
115
|
* Override in tests via: _acpAdapterDeps.createClient = mock(...)
|
|
116
116
|
*/
|
|
117
|
-
createClient(
|
|
118
|
-
|
|
117
|
+
createClient(
|
|
118
|
+
cmdStr: string,
|
|
119
|
+
cwd?: string,
|
|
120
|
+
timeoutSeconds?: number,
|
|
121
|
+
pidRegistry?: import("../../execution/pid-registry").PidRegistry,
|
|
122
|
+
): AcpClient {
|
|
123
|
+
return createSpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
|
|
119
124
|
},
|
|
120
125
|
};
|
|
121
126
|
|
|
@@ -436,7 +441,7 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
436
441
|
|
|
437
442
|
private async _runWithClient(options: AgentRunOptions, startTime: number): Promise<AgentResult> {
|
|
438
443
|
const cmdStr = `acpx --model ${options.modelDef.model} ${this.name}`;
|
|
439
|
-
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds);
|
|
444
|
+
const client = _acpAdapterDeps.createClient(cmdStr, options.workdir, options.timeoutSeconds, options.pidRegistry);
|
|
440
445
|
await client.start();
|
|
441
446
|
|
|
442
447
|
// 1. Resolve session name: explicit > sidecar > derived
|
|
@@ -673,6 +678,7 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
673
678
|
featureName: options.featureName,
|
|
674
679
|
storyId: options.storyId,
|
|
675
680
|
sessionRole: options.sessionRole,
|
|
681
|
+
pidRegistry: options.pidRegistry,
|
|
676
682
|
});
|
|
677
683
|
|
|
678
684
|
if (!result.success) {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* acpx <agent> cancel → session.cancelActivePrompt()
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import type { PidRegistry } from "../../execution/pid-registry";
|
|
15
16
|
import { getSafeLogger } from "../../logger";
|
|
16
17
|
import type { AcpClient, AcpSession, AcpSessionResponse } from "./adapter";
|
|
17
18
|
import { parseAcpxJsonOutput } from "./parser";
|
|
@@ -96,6 +97,8 @@ class SpawnAcpSession implements AcpSession {
|
|
|
96
97
|
private readonly timeoutSeconds: number;
|
|
97
98
|
private readonly permissionMode: string;
|
|
98
99
|
private readonly env: Record<string, string | undefined>;
|
|
100
|
+
private readonly pidRegistry?: PidRegistry;
|
|
101
|
+
private activeProc: { pid: number; kill(signal?: number): void } | null = null;
|
|
99
102
|
|
|
100
103
|
constructor(opts: {
|
|
101
104
|
agentName: string;
|
|
@@ -105,6 +108,7 @@ class SpawnAcpSession implements AcpSession {
|
|
|
105
108
|
timeoutSeconds: number;
|
|
106
109
|
permissionMode: string;
|
|
107
110
|
env: Record<string, string | undefined>;
|
|
111
|
+
pidRegistry?: PidRegistry;
|
|
108
112
|
}) {
|
|
109
113
|
this.agentName = opts.agentName;
|
|
110
114
|
this.sessionName = opts.sessionName;
|
|
@@ -113,6 +117,7 @@ class SpawnAcpSession implements AcpSession {
|
|
|
113
117
|
this.timeoutSeconds = opts.timeoutSeconds;
|
|
114
118
|
this.permissionMode = opts.permissionMode;
|
|
115
119
|
this.env = opts.env;
|
|
120
|
+
this.pidRegistry = opts.pidRegistry;
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
async prompt(text: string): Promise<AcpSessionResponse> {
|
|
@@ -143,40 +148,60 @@ class SpawnAcpSession implements AcpSession {
|
|
|
143
148
|
env: this.env,
|
|
144
149
|
});
|
|
145
150
|
|
|
146
|
-
proc
|
|
147
|
-
proc.
|
|
151
|
+
this.activeProc = proc;
|
|
152
|
+
const processPid = proc.pid;
|
|
153
|
+
await this.pidRegistry?.register(processPid);
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
155
|
+
try {
|
|
156
|
+
proc.stdin.write(text);
|
|
157
|
+
proc.stdin.end();
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
});
|
|
157
|
-
// Return error response so the adapter can handle it
|
|
158
|
-
return {
|
|
159
|
-
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
160
|
-
stopReason: "error",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
159
|
+
const exitCode = await proc.exited;
|
|
160
|
+
const stdout = await new Response(proc.stdout).text();
|
|
161
|
+
const stderr = await new Response(proc.stderr).text();
|
|
163
162
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
if (exitCode !== 0) {
|
|
164
|
+
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
165
|
+
stderr: stderr.slice(0, 200),
|
|
166
|
+
});
|
|
167
|
+
// Return error response so the adapter can handle it
|
|
168
|
+
return {
|
|
169
|
+
messages: [{ role: "assistant", content: stderr || `Exit code ${exitCode}` }],
|
|
170
|
+
stopReason: "error",
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const parsed = parseAcpxJsonOutput(stdout);
|
|
176
|
+
return {
|
|
177
|
+
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
178
|
+
stopReason: "end_turn",
|
|
179
|
+
cumulative_token_usage: parsed.tokenUsage,
|
|
180
|
+
};
|
|
181
|
+
} catch (err) {
|
|
182
|
+
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
183
|
+
stderr: stderr.slice(0, 200),
|
|
184
|
+
});
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
} finally {
|
|
188
|
+
this.activeProc = null;
|
|
189
|
+
await this.pidRegistry?.unregister(processPid);
|
|
176
190
|
}
|
|
177
191
|
}
|
|
178
192
|
|
|
179
193
|
async close(): Promise<void> {
|
|
194
|
+
// Kill in-flight prompt process first (if any)
|
|
195
|
+
if (this.activeProc) {
|
|
196
|
+
try {
|
|
197
|
+
this.activeProc.kill(15); // SIGTERM
|
|
198
|
+
getSafeLogger()?.debug("acp-adapter", `Killed active prompt process PID ${this.activeProc.pid}`);
|
|
199
|
+
} catch {
|
|
200
|
+
// Process may have already exited
|
|
201
|
+
}
|
|
202
|
+
this.activeProc = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
180
205
|
const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
|
|
181
206
|
getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
|
|
182
207
|
|
|
@@ -193,6 +218,16 @@ class SpawnAcpSession implements AcpSession {
|
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
async cancelActivePrompt(): Promise<void> {
|
|
221
|
+
// Kill in-flight prompt process directly (faster than acpx cancel)
|
|
222
|
+
if (this.activeProc) {
|
|
223
|
+
try {
|
|
224
|
+
this.activeProc.kill(15); // SIGTERM
|
|
225
|
+
getSafeLogger()?.debug("acp-adapter", `Killed active prompt process PID ${this.activeProc.pid}`);
|
|
226
|
+
} catch {
|
|
227
|
+
// Process may have already exited
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
196
231
|
const cmd = ["acpx", this.agentName, "cancel"];
|
|
197
232
|
getSafeLogger()?.debug("acp-adapter", `Cancelling active prompt: ${this.sessionName}`);
|
|
198
233
|
|
|
@@ -220,8 +255,9 @@ export class SpawnAcpClient implements AcpClient {
|
|
|
220
255
|
private readonly cwd: string;
|
|
221
256
|
private readonly timeoutSeconds: number;
|
|
222
257
|
private readonly env: Record<string, string | undefined>;
|
|
258
|
+
private readonly pidRegistry?: PidRegistry;
|
|
223
259
|
|
|
224
|
-
constructor(cmdStr: string, cwd?: string, timeoutSeconds?: number) {
|
|
260
|
+
constructor(cmdStr: string, cwd?: string, timeoutSeconds?: number, pidRegistry?: PidRegistry) {
|
|
225
261
|
// Parse: "acpx --model <model> <agentName>"
|
|
226
262
|
const parts = cmdStr.split(/\s+/);
|
|
227
263
|
const modelIdx = parts.indexOf("--model");
|
|
@@ -235,6 +271,7 @@ export class SpawnAcpClient implements AcpClient {
|
|
|
235
271
|
this.cwd = cwd || process.cwd();
|
|
236
272
|
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
237
273
|
this.env = buildAllowedEnv();
|
|
274
|
+
this.pidRegistry = pidRegistry;
|
|
238
275
|
}
|
|
239
276
|
|
|
240
277
|
async start(): Promise<void> {
|
|
@@ -268,6 +305,7 @@ export class SpawnAcpClient implements AcpClient {
|
|
|
268
305
|
timeoutSeconds: this.timeoutSeconds,
|
|
269
306
|
permissionMode: opts.permissionMode,
|
|
270
307
|
env: this.env,
|
|
308
|
+
pidRegistry: this.pidRegistry,
|
|
271
309
|
});
|
|
272
310
|
}
|
|
273
311
|
|
|
@@ -290,6 +328,7 @@ export class SpawnAcpClient implements AcpClient {
|
|
|
290
328
|
timeoutSeconds: this.timeoutSeconds,
|
|
291
329
|
permissionMode: "approve-all", // Default for resumed sessions
|
|
292
330
|
env: this.env,
|
|
331
|
+
pidRegistry: this.pidRegistry,
|
|
293
332
|
});
|
|
294
333
|
}
|
|
295
334
|
|
|
@@ -306,6 +345,11 @@ export class SpawnAcpClient implements AcpClient {
|
|
|
306
345
|
* Create a spawn-based ACP client. This is the default production factory.
|
|
307
346
|
* The cmdStr format is: "acpx --model <model> <agentName>"
|
|
308
347
|
*/
|
|
309
|
-
export function createSpawnAcpClient(
|
|
310
|
-
|
|
348
|
+
export function createSpawnAcpClient(
|
|
349
|
+
cmdStr: string,
|
|
350
|
+
cwd?: string,
|
|
351
|
+
timeoutSeconds?: number,
|
|
352
|
+
pidRegistry?: PidRegistry,
|
|
353
|
+
): AcpClient {
|
|
354
|
+
return new SpawnAcpClient(cmdStr, cwd, timeoutSeconds, pidRegistry);
|
|
311
355
|
}
|
|
@@ -55,6 +55,8 @@ export interface PlanOptions {
|
|
|
55
55
|
* Used to persist the name to status.json for plan→run session continuity.
|
|
56
56
|
*/
|
|
57
57
|
onAcpSessionCreated?: (sessionName: string) => Promise<void> | void;
|
|
58
|
+
/** PID registry for tracking spawned agent processes — cleanup on crash/SIGTERM */
|
|
59
|
+
pidRegistry?: import("../execution/pid-registry").PidRegistry;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
package/src/cli/plan.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { AgentAdapter } from "../agents/types";
|
|
|
15
15
|
import { scanCodebase } from "../analyze/scanner";
|
|
16
16
|
import type { CodebaseScan } from "../analyze/types";
|
|
17
17
|
import type { NaxConfig } from "../config";
|
|
18
|
+
import { PidRegistry } from "../execution/pid-registry";
|
|
18
19
|
import { getLogger } from "../logger";
|
|
19
20
|
import { validatePlanOutput } from "../prd/schema";
|
|
20
21
|
|
|
@@ -123,6 +124,7 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
|
|
|
123
124
|
const adapter = _deps.getAgent(agentName, config);
|
|
124
125
|
if (!adapter) throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
125
126
|
const interactionBridge = createCliInteractionBridge();
|
|
127
|
+
const pidRegistry = new PidRegistry(workdir);
|
|
126
128
|
logger?.info("plan", "Starting interactive planning session...", { agent: agentName });
|
|
127
129
|
try {
|
|
128
130
|
await adapter.plan({
|
|
@@ -136,8 +138,10 @@ export async function planCommand(workdir: string, config: NaxConfig, options: P
|
|
|
136
138
|
dangerouslySkipPermissions: config?.execution?.dangerouslySkipPermissions ?? false,
|
|
137
139
|
maxInteractionTurns: config?.agent?.maxInteractionTurns,
|
|
138
140
|
featureName: options.feature,
|
|
141
|
+
pidRegistry,
|
|
139
142
|
});
|
|
140
143
|
} finally {
|
|
144
|
+
await pidRegistry.killAll().catch(() => {});
|
|
141
145
|
logger?.info("plan", "Interactive session ended");
|
|
142
146
|
}
|
|
143
147
|
// Read back from file written by agent
|