@nathapp/nax 0.44.0 → 0.46.0
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/CHANGELOG.md +19 -0
- package/bin/nax.ts +7 -6
- package/dist/nax.js +266 -161
- package/package.json +1 -1
- package/src/agents/acp/adapter.ts +34 -6
- package/src/agents/acp/index.ts +0 -2
- package/src/agents/acp/parser.ts +57 -104
- package/src/agents/acp/spawn-client.ts +2 -1
- package/src/agents/{claude.ts → claude/adapter.ts} +15 -12
- package/src/agents/{claude-complete.ts → claude/complete.ts} +3 -3
- package/src/agents/{cost.ts → claude/cost.ts} +1 -1
- package/src/agents/{claude-execution.ts → claude/execution.ts} +5 -5
- package/src/agents/claude/index.ts +3 -0
- package/src/agents/{claude-interactive.ts → claude/interactive.ts} +4 -4
- package/src/agents/{claude-plan.ts → claude/plan.ts} +12 -9
- package/src/agents/index.ts +5 -5
- package/src/agents/registry.ts +5 -5
- package/src/agents/{claude-decompose.ts → shared/decompose.ts} +7 -22
- package/src/agents/{model-resolution.ts → shared/model-resolution.ts} +2 -2
- package/src/agents/{types-extended.ts → shared/types-extended.ts} +4 -4
- package/src/agents/{validation.ts → shared/validation.ts} +2 -2
- package/src/agents/{version-detection.ts → shared/version-detection.ts} +3 -3
- package/src/agents/types.ts +8 -4
- package/src/cli/agents.ts +1 -1
- package/src/cli/plan.ts +4 -11
- package/src/config/test-strategy.ts +70 -0
- package/src/execution/lifecycle/acceptance-loop.ts +2 -0
- package/src/execution/parallel-coordinator.ts +3 -1
- package/src/execution/parallel-executor.ts +3 -0
- package/src/execution/runner-execution.ts +16 -2
- package/src/execution/story-context.ts +6 -0
- package/src/pipeline/stages/acceptance.ts +5 -8
- package/src/pipeline/stages/regression.ts +2 -0
- package/src/pipeline/stages/verify.ts +5 -10
- package/src/prd/schema.ts +4 -14
- package/src/precheck/checks-agents.ts +1 -1
- package/src/utils/log-test-output.ts +25 -0
- /package/src/agents/{adapters/aider.ts → aider/adapter.ts} +0 -0
- /package/src/agents/{adapters/codex.ts → codex/adapter.ts} +0 -0
- /package/src/agents/{adapters/gemini.ts → gemini/adapter.ts} +0 -0
- /package/src/agents/{adapters/opencode.ts → opencode/adapter.ts} +0 -0
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ import { createHash } from "node:crypto";
|
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { resolvePermissions } from "../../config/permissions";
|
|
17
17
|
import { getSafeLogger } from "../../logger";
|
|
18
|
-
import { buildDecomposePrompt, parseDecomposeOutput } from "../
|
|
18
|
+
import { buildDecomposePrompt, parseDecomposeOutput } from "../shared/decompose";
|
|
19
19
|
import { createSpawnAcpClient } from "./spawn-client";
|
|
20
20
|
|
|
21
21
|
import type {
|
|
@@ -80,7 +80,14 @@ const DEFAULT_ENTRY: AgentRegistryEntry = {
|
|
|
80
80
|
export interface AcpSessionResponse {
|
|
81
81
|
messages: Array<{ role: string; content: string }>;
|
|
82
82
|
stopReason: string;
|
|
83
|
-
cumulative_token_usage?: {
|
|
83
|
+
cumulative_token_usage?: {
|
|
84
|
+
input_tokens: number;
|
|
85
|
+
output_tokens: number;
|
|
86
|
+
cache_read_input_tokens?: number;
|
|
87
|
+
cache_creation_input_tokens?: number;
|
|
88
|
+
};
|
|
89
|
+
/** Exact cost in USD from acpx usage_update event. Preferred over token-based estimation. */
|
|
90
|
+
exactCostUsd?: number;
|
|
84
91
|
}
|
|
85
92
|
|
|
86
93
|
export interface AcpSession {
|
|
@@ -555,7 +562,13 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
555
562
|
// Tracks whether the run completed successfully — used by finally to decide
|
|
556
563
|
// whether to close the session (success) or keep it open for retry (failure).
|
|
557
564
|
const runState = { succeeded: false };
|
|
558
|
-
const totalTokenUsage = {
|
|
565
|
+
const totalTokenUsage = {
|
|
566
|
+
input_tokens: 0,
|
|
567
|
+
output_tokens: 0,
|
|
568
|
+
cache_read_input_tokens: 0,
|
|
569
|
+
cache_creation_input_tokens: 0,
|
|
570
|
+
};
|
|
571
|
+
let totalExactCostUsd: number | undefined;
|
|
559
572
|
|
|
560
573
|
try {
|
|
561
574
|
// 5. Multi-turn loop
|
|
@@ -577,10 +590,16 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
577
590
|
lastResponse = turnResult.response;
|
|
578
591
|
if (!lastResponse) break;
|
|
579
592
|
|
|
580
|
-
// Accumulate token usage
|
|
593
|
+
// Accumulate token usage and exact cost
|
|
581
594
|
if (lastResponse.cumulative_token_usage) {
|
|
582
595
|
totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
|
|
583
596
|
totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
|
|
597
|
+
totalTokenUsage.cache_read_input_tokens += lastResponse.cumulative_token_usage.cache_read_input_tokens ?? 0;
|
|
598
|
+
totalTokenUsage.cache_creation_input_tokens +=
|
|
599
|
+
lastResponse.cumulative_token_usage.cache_creation_input_tokens ?? 0;
|
|
600
|
+
}
|
|
601
|
+
if (lastResponse.exactCostUsd !== undefined) {
|
|
602
|
+
totalExactCostUsd = (totalExactCostUsd ?? 0) + lastResponse.exactCostUsd;
|
|
584
603
|
}
|
|
585
604
|
|
|
586
605
|
// Check for agent question → route to interaction bridge
|
|
@@ -643,10 +662,12 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
643
662
|
const success = lastResponse?.stopReason === "end_turn";
|
|
644
663
|
const output = extractOutput(lastResponse);
|
|
645
664
|
|
|
665
|
+
// Prefer exact cost from acpx usage_update; fall back to token-based estimation
|
|
646
666
|
const estimatedCost =
|
|
647
|
-
|
|
667
|
+
totalExactCostUsd ??
|
|
668
|
+
(totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0
|
|
648
669
|
? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model)
|
|
649
|
-
: 0;
|
|
670
|
+
: 0);
|
|
650
671
|
|
|
651
672
|
return {
|
|
652
673
|
success,
|
|
@@ -719,6 +740,13 @@ export class AcpAgentAdapter implements AgentAdapter {
|
|
|
719
740
|
throw new CompleteError("complete() returned empty output");
|
|
720
741
|
}
|
|
721
742
|
|
|
743
|
+
if (response.exactCostUsd !== undefined) {
|
|
744
|
+
getSafeLogger()?.info("acp-adapter", "complete() cost", {
|
|
745
|
+
costUsd: response.exactCostUsd,
|
|
746
|
+
model,
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
722
750
|
return unwrapped;
|
|
723
751
|
} catch (err) {
|
|
724
752
|
const error = err instanceof Error ? err : new Error(String(err));
|
package/src/agents/acp/index.ts
CHANGED
|
@@ -4,6 +4,4 @@
|
|
|
4
4
|
|
|
5
5
|
export { AcpAgentAdapter, _acpAdapterDeps } from "./adapter";
|
|
6
6
|
export { createSpawnAcpClient } from "./spawn-client";
|
|
7
|
-
export { estimateCostFromTokenUsage } from "./cost";
|
|
8
|
-
export type { SessionTokenUsage } from "./cost";
|
|
9
7
|
export type { AgentRegistryEntry } from "./types";
|
package/src/agents/acp/parser.ts
CHANGED
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* ACP adapter — NDJSON and JSON-RPC output parsing helpers.
|
|
3
3
|
*
|
|
4
4
|
* Extracted from adapter.ts to keep that file within the 800-line limit.
|
|
5
|
-
* Used
|
|
5
|
+
* Used by SpawnAcpSession.prompt() to parse acpx stdout.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { AgentRunOptions } from "../types";
|
|
9
|
-
|
|
10
8
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
9
|
// Types
|
|
12
10
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -15,131 +13,86 @@ import type { AgentRunOptions } from "../types";
|
|
|
15
13
|
export interface AcpxTokenUsage {
|
|
16
14
|
input_tokens: number;
|
|
17
15
|
output_tokens: number;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/** JSON-RPC message from acpx --format json --json-strict */
|
|
21
|
-
interface JsonRpcMessage {
|
|
22
|
-
jsonrpc: "2.0";
|
|
23
|
-
method?: string;
|
|
24
|
-
params?: {
|
|
25
|
-
sessionId: string;
|
|
26
|
-
update?: {
|
|
27
|
-
sessionUpdate: string;
|
|
28
|
-
content?: { type: string; text?: string };
|
|
29
|
-
used?: number;
|
|
30
|
-
size?: number;
|
|
31
|
-
cost?: { amount: number; currency: string };
|
|
32
|
-
};
|
|
33
|
-
};
|
|
34
|
-
id?: number | string;
|
|
35
|
-
result?: unknown;
|
|
36
|
-
error?: { code: number; message: string };
|
|
16
|
+
cache_read_input_tokens?: number;
|
|
17
|
+
cache_creation_input_tokens?: number;
|
|
37
18
|
}
|
|
38
19
|
|
|
39
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
-
//
|
|
21
|
+
// parseAcpxJsonOutput
|
|
41
22
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
23
|
|
|
43
24
|
/**
|
|
44
|
-
*
|
|
25
|
+
* Parse acpx NDJSON output for assistant text, token usage, and exact cost.
|
|
26
|
+
*
|
|
27
|
+
* Handles the JSON-RPC envelope format emitted by acpx:
|
|
28
|
+
* - session/update agent_message_chunk → text accumulation
|
|
29
|
+
* - session/update usage_update → exact cost (cost.amount) + context size
|
|
30
|
+
* - id/result → token breakdown (inputTokens, outputTokens, cachedWriteTokens, cachedReadTokens)
|
|
31
|
+
*
|
|
32
|
+
* Also handles legacy flat NDJSON format for backward compatibility.
|
|
45
33
|
*/
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
export function parseAcpxJsonOutput(rawOutput: string): {
|
|
35
|
+
text: string;
|
|
36
|
+
tokenUsage?: AcpxTokenUsage;
|
|
37
|
+
exactCostUsd?: number;
|
|
38
|
+
stopReason?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
} {
|
|
41
|
+
const lines = rawOutput.split("\n").filter((l) => l.trim());
|
|
42
|
+
let text = "";
|
|
52
43
|
let tokenUsage: AcpxTokenUsage | undefined;
|
|
53
|
-
|
|
54
|
-
let
|
|
55
|
-
|
|
56
|
-
const reader = stdout.getReader();
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
while (true) {
|
|
60
|
-
const { done, value } = await reader.read();
|
|
61
|
-
if (done) break;
|
|
62
|
-
|
|
63
|
-
buffer += decoder.decode(value, { stream: true });
|
|
64
|
-
const lines = buffer.split("\n");
|
|
65
|
-
buffer = lines.pop() ?? "";
|
|
66
|
-
|
|
67
|
-
for (const line of lines) {
|
|
68
|
-
if (!line.trim()) continue;
|
|
44
|
+
let exactCostUsd: number | undefined;
|
|
45
|
+
let stopReason: string | undefined;
|
|
46
|
+
let error: string | undefined;
|
|
69
47
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} catch {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
try {
|
|
50
|
+
const event = JSON.parse(line);
|
|
76
51
|
|
|
77
|
-
|
|
78
|
-
|
|
52
|
+
// ── JSON-RPC envelope format (acpx v0.3+) ──────────────────────────────
|
|
53
|
+
if (event.jsonrpc === "2.0") {
|
|
54
|
+
// session/update events
|
|
55
|
+
if (event.method === "session/update" && event.params?.update) {
|
|
56
|
+
const update = event.params.update;
|
|
79
57
|
|
|
58
|
+
// Text chunks
|
|
80
59
|
if (
|
|
81
60
|
update.sessionUpdate === "agent_message_chunk" &&
|
|
82
61
|
update.content?.type === "text" &&
|
|
83
62
|
update.content.text
|
|
84
63
|
) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (bridge?.detectQuestion && bridge.onQuestionDetected) {
|
|
88
|
-
const isQuestion = await bridge.detectQuestion(accumulatedText);
|
|
89
|
-
if (isQuestion) {
|
|
90
|
-
const response = await bridge.onQuestionDetected(accumulatedText);
|
|
91
|
-
accumulatedText += `\n\n[Human response: ${response}]`;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
64
|
+
text += update.content.text;
|
|
94
65
|
}
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
input_tokens: Math.floor(total * 0.3),
|
|
100
|
-
output_tokens: Math.floor(total * 0.7),
|
|
101
|
-
};
|
|
67
|
+
// Exact cost from usage_update
|
|
68
|
+
if (update.sessionUpdate === "usage_update" && typeof update.cost?.amount === "number") {
|
|
69
|
+
exactCostUsd = update.cost.amount;
|
|
102
70
|
}
|
|
103
71
|
}
|
|
104
72
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
accumulatedText += result;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
} finally {
|
|
114
|
-
reader.releaseLock();
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { text: accumulatedText.trim(), tokenUsage };
|
|
118
|
-
}
|
|
73
|
+
// Final result with token breakdown (camelCase from acpx)
|
|
74
|
+
if (event.id !== undefined && event.result && typeof event.result === "object") {
|
|
75
|
+
const result = event.result as Record<string, unknown>;
|
|
119
76
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
if (result.stopReason) stopReason = result.stopReason as string;
|
|
78
|
+
if (result.stop_reason) stopReason = result.stop_reason as string;
|
|
123
79
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let tokenUsage: AcpxTokenUsage | undefined;
|
|
136
|
-
let stopReason: string | undefined;
|
|
137
|
-
let error: string | undefined;
|
|
80
|
+
if (result.usage && typeof result.usage === "object") {
|
|
81
|
+
const u = result.usage as Record<string, unknown>;
|
|
82
|
+
tokenUsage = {
|
|
83
|
+
input_tokens: (u.inputTokens as number) ?? (u.input_tokens as number) ?? 0,
|
|
84
|
+
output_tokens: (u.outputTokens as number) ?? (u.output_tokens as number) ?? 0,
|
|
85
|
+
cache_read_input_tokens: (u.cachedReadTokens as number) ?? (u.cache_read_input_tokens as number) ?? 0,
|
|
86
|
+
cache_creation_input_tokens:
|
|
87
|
+
(u.cachedWriteTokens as number) ?? (u.cache_creation_input_tokens as number) ?? 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
138
91
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const event = JSON.parse(line);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
142
94
|
|
|
95
|
+
// ── Legacy flat NDJSON format ───────────────────────────────────────────
|
|
143
96
|
if (event.content && typeof event.content === "string") text += event.content;
|
|
144
97
|
if (event.text && typeof event.text === "string") text += event.text;
|
|
145
98
|
if (event.result && typeof event.result === "string") text = event.result;
|
|
@@ -162,5 +115,5 @@ export function parseAcpxJsonOutput(rawOutput: string): {
|
|
|
162
115
|
}
|
|
163
116
|
}
|
|
164
117
|
|
|
165
|
-
return { text: text.trim(), tokenUsage, stopReason, error };
|
|
118
|
+
return { text: text.trim(), tokenUsage, exactCostUsd, stopReason, error };
|
|
166
119
|
}
|
|
@@ -180,8 +180,9 @@ class SpawnAcpSession implements AcpSession {
|
|
|
180
180
|
const parsed = parseAcpxJsonOutput(stdout);
|
|
181
181
|
return {
|
|
182
182
|
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
183
|
-
stopReason: "end_turn",
|
|
183
|
+
stopReason: parsed.stopReason ?? "end_turn",
|
|
184
184
|
cumulative_token_usage: parsed.tokenUsage,
|
|
185
|
+
exactCostUsd: parsed.exactCostUsd,
|
|
185
186
|
};
|
|
186
187
|
} catch (err) {
|
|
187
188
|
getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
|
|
@@ -4,15 +4,11 @@
|
|
|
4
4
|
* Main adapter class coordinating execution, completion, decomposition, and interactive modes.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { resolvePermissions } from "
|
|
8
|
-
import { PidRegistry } from "
|
|
9
|
-
import { withProcessTimeout } from "
|
|
10
|
-
import { getLogger } from "
|
|
11
|
-
import {
|
|
12
|
-
import { buildDecomposePrompt, parseDecomposeOutput } from "./claude-decompose";
|
|
13
|
-
import { _runOnceDeps, buildAllowedEnv, buildCommand, executeOnce } from "./claude-execution";
|
|
14
|
-
import { runInteractiveMode } from "./claude-interactive";
|
|
15
|
-
import { runPlan } from "./claude-plan";
|
|
7
|
+
import { resolvePermissions } from "../../config/permissions";
|
|
8
|
+
import { PidRegistry } from "../../execution/pid-registry";
|
|
9
|
+
import { withProcessTimeout } from "../../execution/timeout-handler";
|
|
10
|
+
import { getLogger } from "../../logger";
|
|
11
|
+
import { buildDecomposePrompt, parseDecomposeOutput } from "../shared/decompose";
|
|
16
12
|
import type {
|
|
17
13
|
AgentAdapter,
|
|
18
14
|
AgentCapabilities,
|
|
@@ -25,7 +21,11 @@ import type {
|
|
|
25
21
|
PlanOptions,
|
|
26
22
|
PlanResult,
|
|
27
23
|
PtyHandle,
|
|
28
|
-
} from "
|
|
24
|
+
} from "../types";
|
|
25
|
+
import { _completeDeps, executeComplete } from "./complete";
|
|
26
|
+
import { _runOnceDeps, buildAllowedEnv, buildCommand, executeOnce } from "./execution";
|
|
27
|
+
import { runInteractiveMode } from "./interactive";
|
|
28
|
+
import { runPlan } from "./plan";
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Injectable dependencies for decompose() — allows tests to intercept
|
|
@@ -174,7 +174,7 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async decompose(options: DecomposeOptions): Promise<DecomposeResult> {
|
|
177
|
-
const { resolveBalancedModelDef } = await import("
|
|
177
|
+
const { resolveBalancedModelDef } = await import("../shared/model-resolution");
|
|
178
178
|
|
|
179
179
|
const prompt = buildDecomposePrompt(options);
|
|
180
180
|
|
|
@@ -186,7 +186,10 @@ export class ClaudeCodeAdapter implements AgentAdapter {
|
|
|
186
186
|
modelDef = resolveBalancedModelDef(options.config);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
const { skipPermissions } = resolvePermissions(
|
|
189
|
+
const { skipPermissions } = resolvePermissions(
|
|
190
|
+
options.config as import("../../config").NaxConfig | undefined,
|
|
191
|
+
"run",
|
|
192
|
+
);
|
|
190
193
|
const cmd = [this.binary, "--model", modelDef.model, "-p", prompt];
|
|
191
194
|
if (skipPermissions) {
|
|
192
195
|
cmd.splice(cmd.length - 2, 0, "--dangerously-skip-permissions");
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Standalone completion endpoint for simple prompts.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { resolvePermissions } from "
|
|
8
|
-
import type { CompleteOptions } from "
|
|
9
|
-
import { CompleteError } from "
|
|
7
|
+
import { resolvePermissions } from "../../config/permissions";
|
|
8
|
+
import type { CompleteOptions } from "../types";
|
|
9
|
+
import { CompleteError } from "../types";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Injectable dependencies for complete() — allows tests to intercept
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* Handles building commands, preparing environment, and process execution.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { resolvePermissions } from "
|
|
8
|
-
import type { PidRegistry } from "
|
|
9
|
-
import { withProcessTimeout } from "
|
|
10
|
-
import { getLogger } from "
|
|
7
|
+
import { resolvePermissions } from "../../config/permissions";
|
|
8
|
+
import type { PidRegistry } from "../../execution/pid-registry";
|
|
9
|
+
import { withProcessTimeout } from "../../execution/timeout-handler";
|
|
10
|
+
import { getLogger } from "../../logger";
|
|
11
|
+
import type { AgentResult, AgentRunOptions } from "../types";
|
|
11
12
|
import { estimateCostByDuration, estimateCostFromOutput } from "./cost";
|
|
12
|
-
import type { AgentResult, AgentRunOptions } from "./types";
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Maximum characters to capture from agent stdout.
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Handles terminal UI interactions with the Claude agent.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { PidRegistry } from "
|
|
8
|
-
import { getLogger } from "
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
7
|
+
import type { PidRegistry } from "../../execution/pid-registry";
|
|
8
|
+
import { getLogger } from "../../logger";
|
|
9
|
+
import type { AgentRunOptions, InteractiveRunOptions, PtyHandle } from "../types";
|
|
10
|
+
import { buildAllowedEnv } from "./execution";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Run Claude agent in interactive (TTY) mode for TUI output.
|
|
@@ -7,13 +7,13 @@ import { join } from "node:path";
|
|
|
7
7
|
* Extracted from claude.ts: plan(), buildPlanCommand()
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { resolvePermissions } from "
|
|
11
|
-
import type { PidRegistry } from "
|
|
12
|
-
import { withProcessTimeout } from "
|
|
13
|
-
import { getLogger } from "
|
|
14
|
-
import { resolveBalancedModelDef } from "
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
10
|
+
import { resolvePermissions } from "../../config/permissions";
|
|
11
|
+
import type { PidRegistry } from "../../execution/pid-registry";
|
|
12
|
+
import { withProcessTimeout } from "../../execution/timeout-handler";
|
|
13
|
+
import { getLogger } from "../../logger";
|
|
14
|
+
import { resolveBalancedModelDef } from "../shared/model-resolution";
|
|
15
|
+
import type { PlanOptions, PlanResult } from "../shared/types-extended";
|
|
16
|
+
import type { AgentRunOptions } from "../types";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Build the CLI command for plan mode.
|
|
@@ -32,7 +32,10 @@ export function buildPlanCommand(binary: string, options: PlanOptions): string[]
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
// Resolve permission mode from config
|
|
35
|
-
const { skipPermissions } = resolvePermissions(
|
|
35
|
+
const { skipPermissions } = resolvePermissions(
|
|
36
|
+
options.config as import("../../config").NaxConfig | undefined,
|
|
37
|
+
"plan",
|
|
38
|
+
);
|
|
36
39
|
if (skipPermissions) {
|
|
37
40
|
cmd.push("--dangerously-skip-permissions");
|
|
38
41
|
}
|
|
@@ -75,7 +78,7 @@ export async function runPlan(
|
|
|
75
78
|
pidRegistry: PidRegistry,
|
|
76
79
|
buildAllowedEnv: (options: AgentRunOptions) => Record<string, string | undefined>,
|
|
77
80
|
): Promise<PlanResult> {
|
|
78
|
-
const { resolveBalancedModelDef } = await import("
|
|
81
|
+
const { resolveBalancedModelDef } = await import("../shared/model-resolution");
|
|
79
82
|
|
|
80
83
|
const cmd = buildPlanCommand(binary, options);
|
|
81
84
|
|
package/src/agents/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { AgentAdapter, AgentCapabilities, AgentResult, AgentRunOptions, Com
|
|
|
2
2
|
export { CompleteError } from "./types";
|
|
3
3
|
export { ClaudeCodeAdapter } from "./claude";
|
|
4
4
|
export { getAllAgentNames, getAgent, getInstalledAgents, checkAgentHealth } from "./registry";
|
|
5
|
-
export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence } from "./cost";
|
|
5
|
+
export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence } from "./claude/cost";
|
|
6
6
|
export {
|
|
7
7
|
COST_RATES,
|
|
8
8
|
parseTokenUsage,
|
|
@@ -10,7 +10,7 @@ export {
|
|
|
10
10
|
estimateCostFromOutput,
|
|
11
11
|
estimateCostByDuration,
|
|
12
12
|
formatCostWithConfidence,
|
|
13
|
-
} from "./cost";
|
|
14
|
-
export { validateAgentForTier, validateAgentFeature, describeAgentCapabilities } from "./validation";
|
|
15
|
-
export type { AgentVersionInfo } from "./version-detection";
|
|
16
|
-
export { getAgentVersion, getAgentVersions } from "./version-detection";
|
|
13
|
+
} from "./claude/cost";
|
|
14
|
+
export { validateAgentForTier, validateAgentFeature, describeAgentCapabilities } from "./shared/validation";
|
|
15
|
+
export type { AgentVersionInfo } from "./shared/version-detection";
|
|
16
|
+
export { getAgentVersion, getAgentVersions } from "./shared/version-detection";
|
package/src/agents/registry.ts
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
import type { NaxConfig } from "../config/schema";
|
|
8
8
|
import { getLogger } from "../logger";
|
|
9
9
|
import { AcpAgentAdapter } from "./acp/adapter";
|
|
10
|
-
import { AiderAdapter } from "./
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
import { AiderAdapter } from "./aider/adapter";
|
|
11
|
+
import { ClaudeCodeAdapter } from "./claude/adapter";
|
|
12
|
+
import { CodexAdapter } from "./codex/adapter";
|
|
13
|
+
import { GeminiAdapter } from "./gemini/adapter";
|
|
14
|
+
import { OpenCodeAdapter } from "./opencode/adapter";
|
|
15
15
|
import type { AgentAdapter } from "./types";
|
|
16
16
|
|
|
17
17
|
/** All known agent adapters */
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* parseDecomposeOutput(), validateComplexity()
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { COMPLEXITY_GUIDE, GROUPING_RULES, TEST_STRATEGY_GUIDE, resolveTestStrategy } from "../../config/test-strategy";
|
|
9
|
+
import type { DecomposeOptions, DecomposeResult, DecomposedStory } from "../types";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Build the decompose prompt combining spec content and codebase context.
|
|
@@ -31,24 +32,13 @@ Decompose this spec into user stories. For each story, provide:
|
|
|
31
32
|
9. reasoning: Why this complexity level
|
|
32
33
|
10. estimatedLOC: Estimated lines of code to change
|
|
33
34
|
11. risks: Array of implementation risks
|
|
34
|
-
12. testStrategy: "three-session-tdd" | "
|
|
35
|
+
12. testStrategy: "test-after" | "tdd-simple" | "three-session-tdd" | "three-session-tdd-lite"
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
- "three-session-tdd": ONLY for complex/expert tasks that are security-critical (auth, encryption, tokens, credentials) or define public API contracts consumers depend on
|
|
38
|
-
- "test-after": for all other tasks including simple/medium complexity
|
|
39
|
-
- A "simple" complexity task should almost never be "three-session-tdd"
|
|
37
|
+
${COMPLEXITY_GUIDE}
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
- simple: 1-3 files, <100 LOC, straightforward implementation, existing patterns
|
|
43
|
-
- medium: 3-6 files, 100-300 LOC, moderate logic, some new patterns
|
|
44
|
-
- complex: 6+ files, 300-800 LOC, architectural changes, cross-cutting concerns
|
|
45
|
-
- expert: Security/crypto/real-time/distributed systems, >800 LOC, new infrastructure
|
|
39
|
+
${TEST_STRATEGY_GUIDE}
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
- Combine small, related tasks (e.g., multiple utility functions, interfaces) into a single "simple" or "medium" story.
|
|
49
|
-
- Do NOT create separate stories for every single file or function unless complex.
|
|
50
|
-
- Aim for coherent units of value (e.g., "Implement User Authentication" vs "Create User Interface", "Create Login Service").
|
|
51
|
-
- Maximum recommended stories: 10-15 per feature. Group aggressively if list grows too long.
|
|
41
|
+
${GROUPING_RULES}
|
|
52
42
|
|
|
53
43
|
Consider:
|
|
54
44
|
1. Does infrastructure exist? (e.g., "add caching" when no cache layer exists = complex)
|
|
@@ -141,12 +131,7 @@ export function parseDecomposeOutput(output: string): DecomposedStory[] {
|
|
|
141
131
|
reasoning: String(record.reasoning || "No reasoning provided"),
|
|
142
132
|
estimatedLOC: Number(record.estimatedLOC) || 0,
|
|
143
133
|
risks: Array.isArray(record.risks) ? record.risks : [],
|
|
144
|
-
testStrategy:
|
|
145
|
-
record.testStrategy === "three-session-tdd"
|
|
146
|
-
? "three-session-tdd"
|
|
147
|
-
: record.testStrategy === "test-after"
|
|
148
|
-
? "test-after"
|
|
149
|
-
: undefined,
|
|
134
|
+
testStrategy: resolveTestStrategy(typeof record.testStrategy === "string" ? record.testStrategy : undefined),
|
|
150
135
|
};
|
|
151
136
|
});
|
|
152
137
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* Implementation placeholder — logic to be filled in by the implementer.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { resolveModel } from "
|
|
11
|
-
import type { ModelDef, NaxConfig } from "
|
|
10
|
+
import { resolveModel } from "../../config/schema";
|
|
11
|
+
import type { ModelDef, NaxConfig } from "../../config/schema";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Resolve the balanced model definition from config, with optional adapter default fallback.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Separated from core types to keep each file under 400 lines.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ModelDef, ModelTier, NaxConfig } from "
|
|
8
|
+
import type { ModelDef, ModelTier, NaxConfig } from "../../config/schema";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Configuration options for running an agent in plan mode.
|
|
@@ -56,7 +56,7 @@ export interface PlanOptions {
|
|
|
56
56
|
*/
|
|
57
57
|
onAcpSessionCreated?: (sessionName: string) => Promise<void> | void;
|
|
58
58
|
/** PID registry for tracking spawned agent processes — cleanup on crash/SIGTERM */
|
|
59
|
-
pidRegistry?: import("
|
|
59
|
+
pidRegistry?: import("../../execution/pid-registry").PidRegistry;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
@@ -117,7 +117,7 @@ export interface DecomposedStory {
|
|
|
117
117
|
/** Implementation risks */
|
|
118
118
|
risks: string[];
|
|
119
119
|
/** Test strategy recommendation from LLM */
|
|
120
|
-
testStrategy?: "
|
|
120
|
+
testStrategy?: import("../../config/test-strategy").TestStrategy;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
@@ -161,4 +161,4 @@ export interface InteractiveRunOptions extends AgentRunOptions {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// Re-import for the extends clause
|
|
164
|
-
import type { AgentRunOptions } from "
|
|
164
|
+
import type { AgentRunOptions } from "../types";
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Runtime validation for agent capabilities and tier compatibility.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ModelTier } from "
|
|
8
|
-
import type { AgentAdapter } from "
|
|
7
|
+
import type { ModelTier } from "../../config/schema";
|
|
8
|
+
import type { AgentAdapter } from "../types";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Check if an agent supports a given model tier.
|