@nathapp/nax 0.50.2 → 0.51.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 +30 -0
- package/dist/nax.js +579 -373
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -423
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -135
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -218
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -217
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -520
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -425
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -280
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -140
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Aider Agent Adapter — implements AgentAdapter interface
|
|
3
|
-
*
|
|
4
|
-
* Provides uniform interface for spawning Aider agent processes,
|
|
5
|
-
* supporting one-shot completions in headless mode.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
AgentAdapter,
|
|
10
|
-
AgentCapabilities,
|
|
11
|
-
AgentResult,
|
|
12
|
-
AgentRunOptions,
|
|
13
|
-
CompleteOptions,
|
|
14
|
-
DecomposeOptions,
|
|
15
|
-
DecomposeResult,
|
|
16
|
-
PlanOptions,
|
|
17
|
-
PlanResult,
|
|
18
|
-
} from "../types";
|
|
19
|
-
import { CompleteError } from "../types";
|
|
20
|
-
|
|
21
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
|
-
// Injectable dependencies — matches the _deps pattern used in other adapters
|
|
23
|
-
// These are replaced in unit tests to intercept Bun.spawn and Bun.which calls.
|
|
24
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
export const _aiderCompleteDeps = {
|
|
27
|
-
which(name: string): string | null {
|
|
28
|
-
return Bun.which(name);
|
|
29
|
-
},
|
|
30
|
-
spawn(
|
|
31
|
-
cmd: string[],
|
|
32
|
-
opts: { stdout: "pipe"; stderr: "pipe" | "inherit" },
|
|
33
|
-
): {
|
|
34
|
-
stdout: ReadableStream<Uint8Array>;
|
|
35
|
-
stderr: ReadableStream<Uint8Array>;
|
|
36
|
-
exited: Promise<number>;
|
|
37
|
-
pid: number;
|
|
38
|
-
} {
|
|
39
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
40
|
-
stdout: ReadableStream<Uint8Array>;
|
|
41
|
-
stderr: ReadableStream<Uint8Array>;
|
|
42
|
-
exited: Promise<number>;
|
|
43
|
-
pid: number;
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
-
// AiderAdapter implementation
|
|
50
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Maximum characters to capture from agent stdout.
|
|
54
|
-
*/
|
|
55
|
-
const MAX_AGENT_OUTPUT_CHARS = 5000;
|
|
56
|
-
|
|
57
|
-
export class AiderAdapter implements AgentAdapter {
|
|
58
|
-
readonly name = "aider";
|
|
59
|
-
readonly displayName = "Aider";
|
|
60
|
-
readonly binary = "aider";
|
|
61
|
-
|
|
62
|
-
readonly capabilities: AgentCapabilities = {
|
|
63
|
-
supportedTiers: ["balanced"],
|
|
64
|
-
maxContextTokens: 20_000,
|
|
65
|
-
features: new Set<"tdd" | "review" | "refactor" | "batch">(["refactor"]),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
async isInstalled(): Promise<boolean> {
|
|
69
|
-
const path = _aiderCompleteDeps.which("aider");
|
|
70
|
-
return path !== null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
buildCommand(options: AgentRunOptions): string[] {
|
|
74
|
-
return ["aider", "--message", options.prompt, "--yes"];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async run(options: AgentRunOptions): Promise<AgentResult> {
|
|
78
|
-
const cmd = this.buildCommand(options);
|
|
79
|
-
const startTime = Date.now();
|
|
80
|
-
|
|
81
|
-
const proc = _aiderCompleteDeps.spawn(cmd, {
|
|
82
|
-
stdout: "pipe",
|
|
83
|
-
stderr: "inherit",
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const exitCode = await proc.exited;
|
|
87
|
-
const stdout = await new Response(proc.stdout).text();
|
|
88
|
-
const durationMs = Date.now() - startTime;
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
success: exitCode === 0,
|
|
92
|
-
exitCode,
|
|
93
|
-
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS),
|
|
94
|
-
rateLimited: false,
|
|
95
|
-
durationMs,
|
|
96
|
-
estimatedCost: 0,
|
|
97
|
-
pid: proc.pid,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async complete(prompt: string, options?: CompleteOptions): Promise<string> {
|
|
102
|
-
const cmd = ["aider", "--message", prompt, "--yes"];
|
|
103
|
-
|
|
104
|
-
if (options?.model) {
|
|
105
|
-
cmd.push("--model", options.model);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const proc = _aiderCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
109
|
-
const exitCode = await proc.exited;
|
|
110
|
-
|
|
111
|
-
const stdout = await new Response(proc.stdout).text();
|
|
112
|
-
const stderr = await new Response(proc.stderr).text();
|
|
113
|
-
const trimmed = stdout.trim();
|
|
114
|
-
|
|
115
|
-
if (exitCode !== 0) {
|
|
116
|
-
const errorDetails = stderr.trim() || trimmed;
|
|
117
|
-
const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
|
|
118
|
-
throw new CompleteError(errorMessage, exitCode);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (!trimmed) {
|
|
122
|
-
throw new CompleteError("complete() returned empty output");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return trimmed;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async plan(_options: PlanOptions): Promise<PlanResult> {
|
|
129
|
-
throw new Error("AiderAdapter.plan() not implemented");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async decompose(_options: DecomposeOptions): Promise<DecomposeResult> {
|
|
133
|
-
throw new Error("AiderAdapter.decompose() not implemented");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Agent Adapter
|
|
3
|
-
*
|
|
4
|
-
* Main adapter class coordinating execution, completion, decomposition, and interactive modes.
|
|
5
|
-
*/
|
|
6
|
-
|
|
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";
|
|
12
|
-
import type {
|
|
13
|
-
AgentAdapter,
|
|
14
|
-
AgentCapabilities,
|
|
15
|
-
AgentResult,
|
|
16
|
-
AgentRunOptions,
|
|
17
|
-
CompleteOptions,
|
|
18
|
-
DecomposeOptions,
|
|
19
|
-
DecomposeResult,
|
|
20
|
-
InteractiveRunOptions,
|
|
21
|
-
PlanOptions,
|
|
22
|
-
PlanResult,
|
|
23
|
-
PtyHandle,
|
|
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
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Injectable dependencies for decompose() — allows tests to intercept
|
|
32
|
-
* Bun.spawn calls and verify correct CLI args without the claude binary.
|
|
33
|
-
*
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
export const _decomposeDeps = {
|
|
37
|
-
spawn(
|
|
38
|
-
cmd: string[],
|
|
39
|
-
opts: { cwd?: string; stdout: "pipe"; stderr: "inherit" | "pipe"; env?: Record<string, string | undefined> },
|
|
40
|
-
): {
|
|
41
|
-
stdout: ReadableStream<Uint8Array>;
|
|
42
|
-
stderr?: ReadableStream<Uint8Array>;
|
|
43
|
-
exited: Promise<number>;
|
|
44
|
-
pid: number;
|
|
45
|
-
kill(signal?: NodeJS.Signals | number): void;
|
|
46
|
-
} {
|
|
47
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
48
|
-
stdout: ReadableStream<Uint8Array>;
|
|
49
|
-
stderr?: ReadableStream<Uint8Array>;
|
|
50
|
-
exited: Promise<number>;
|
|
51
|
-
pid: number;
|
|
52
|
-
kill(signal?: NodeJS.Signals | number): void;
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// Re-export deps for testing
|
|
58
|
-
export { _runOnceDeps, _completeDeps };
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Injectable dependencies for ClaudeCodeAdapter retry loop.
|
|
62
|
-
* Exported so tests can replace sleep with a no-op spy.
|
|
63
|
-
*
|
|
64
|
-
* @internal
|
|
65
|
-
*/
|
|
66
|
-
export const _claudeAdapterDeps = {
|
|
67
|
-
sleep: (ms: number): Promise<void> => Bun.sleep(ms),
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Claude Code agent adapter implementation.
|
|
72
|
-
*
|
|
73
|
-
* Implements the AgentAdapter interface for Claude Code CLI,
|
|
74
|
-
* supporting model routing, rate limit retry, and cost tracking.
|
|
75
|
-
*/
|
|
76
|
-
export class ClaudeCodeAdapter implements AgentAdapter {
|
|
77
|
-
readonly name = "claude";
|
|
78
|
-
readonly displayName = "Claude Code";
|
|
79
|
-
readonly binary = "claude";
|
|
80
|
-
|
|
81
|
-
readonly capabilities: AgentCapabilities = {
|
|
82
|
-
supportedTiers: ["fast", "balanced", "powerful"],
|
|
83
|
-
maxContextTokens: 200_000,
|
|
84
|
-
features: new Set(["tdd", "review", "refactor", "batch"]),
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
private pidRegistries: Map<string, PidRegistry> = new Map();
|
|
88
|
-
|
|
89
|
-
private getPidRegistry(workdir: string): PidRegistry {
|
|
90
|
-
if (!this.pidRegistries.has(workdir)) {
|
|
91
|
-
this.pidRegistries.set(workdir, new PidRegistry(workdir));
|
|
92
|
-
}
|
|
93
|
-
const registry = this.pidRegistries.get(workdir);
|
|
94
|
-
if (!registry) {
|
|
95
|
-
throw new Error(`PidRegistry not found for workdir: ${workdir}`);
|
|
96
|
-
}
|
|
97
|
-
return registry;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async isInstalled(): Promise<boolean> {
|
|
101
|
-
try {
|
|
102
|
-
const proc = Bun.spawn(["which", this.binary], { stdout: "pipe", stderr: "pipe" });
|
|
103
|
-
const code = await proc.exited;
|
|
104
|
-
return code === 0;
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const logger = getLogger();
|
|
107
|
-
logger?.debug("agent", "Failed to check if agent is installed", { error });
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
buildCommand(options: AgentRunOptions): string[] {
|
|
113
|
-
return buildCommand(this.binary, options);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
buildAllowedEnv(options: AgentRunOptions): Record<string, string | undefined> {
|
|
117
|
-
return buildAllowedEnv(options);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async run(options: AgentRunOptions): Promise<AgentResult> {
|
|
121
|
-
const maxRetries = 3;
|
|
122
|
-
let lastError: Error | null = null;
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
126
|
-
try {
|
|
127
|
-
const pidRegistry = this.getPidRegistry(options.workdir);
|
|
128
|
-
const result = await executeOnce(this.binary, options, pidRegistry);
|
|
129
|
-
|
|
130
|
-
if (result.rateLimited && attempt < maxRetries) {
|
|
131
|
-
const backoffMs = 2 ** attempt * 1000;
|
|
132
|
-
const logger = getLogger();
|
|
133
|
-
logger.warn("agent", "Rate limited, retrying", { backoffSeconds: backoffMs / 1000, attempt, maxRetries });
|
|
134
|
-
await _claudeAdapterDeps.sleep(backoffMs);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return result;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
lastError = error as Error;
|
|
141
|
-
const isSpawnError = lastError.message.includes("spawn") || lastError.message.includes("ENOENT");
|
|
142
|
-
|
|
143
|
-
if (isSpawnError && attempt < maxRetries) {
|
|
144
|
-
const backoffMs = 2 ** attempt * 1000;
|
|
145
|
-
const logger = getLogger();
|
|
146
|
-
logger.warn("agent", "Agent spawn error, retrying", {
|
|
147
|
-
error: lastError.message,
|
|
148
|
-
backoffSeconds: backoffMs / 1000,
|
|
149
|
-
attempt,
|
|
150
|
-
maxRetries,
|
|
151
|
-
});
|
|
152
|
-
await _claudeAdapterDeps.sleep(backoffMs);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
throw lastError;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
throw lastError || new Error("Agent execution failed after all retries");
|
|
161
|
-
} finally {
|
|
162
|
-
// Clean up pidRegistry entry for this workdir to prevent unbounded Map growth
|
|
163
|
-
this.pidRegistries.delete(options.workdir);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async complete(prompt: string, options?: CompleteOptions): Promise<string> {
|
|
168
|
-
return executeComplete(this.binary, prompt, options);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async plan(options: PlanOptions): Promise<PlanResult> {
|
|
172
|
-
const pidRegistry = this.getPidRegistry(options.workdir);
|
|
173
|
-
return runPlan(this.binary, options, pidRegistry, this.buildAllowedEnv.bind(this));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async decompose(options: DecomposeOptions): Promise<DecomposeResult> {
|
|
177
|
-
const { resolveBalancedModelDef } = await import("../shared/model-resolution");
|
|
178
|
-
|
|
179
|
-
const prompt = buildDecomposePrompt(options);
|
|
180
|
-
|
|
181
|
-
let modelDef = options.modelDef;
|
|
182
|
-
if (!modelDef) {
|
|
183
|
-
if (!options.config) {
|
|
184
|
-
throw new Error("decompose() requires either modelDef or config with models.balanced configured");
|
|
185
|
-
}
|
|
186
|
-
modelDef = resolveBalancedModelDef(options.config);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const { skipPermissions } = resolvePermissions(
|
|
190
|
-
options.config as import("../../config").NaxConfig | undefined,
|
|
191
|
-
"run",
|
|
192
|
-
);
|
|
193
|
-
const cmd = [this.binary, "--model", modelDef.model, "-p", prompt];
|
|
194
|
-
if (skipPermissions) {
|
|
195
|
-
cmd.splice(cmd.length - 2, 0, "--dangerously-skip-permissions");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const pidRegistry = this.getPidRegistry(options.workdir);
|
|
199
|
-
|
|
200
|
-
const proc = _decomposeDeps.spawn(cmd, {
|
|
201
|
-
cwd: options.workdir,
|
|
202
|
-
stdout: "pipe",
|
|
203
|
-
stderr: "inherit",
|
|
204
|
-
env: this.buildAllowedEnv({
|
|
205
|
-
workdir: options.workdir,
|
|
206
|
-
modelDef,
|
|
207
|
-
prompt: "",
|
|
208
|
-
modelTier: options.modelTier || "balanced",
|
|
209
|
-
timeoutSeconds: 600,
|
|
210
|
-
}),
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
await pidRegistry.register(proc.pid);
|
|
214
|
-
|
|
215
|
-
const DECOMPOSE_TIMEOUT_MS = 300_000;
|
|
216
|
-
let timedOut = false;
|
|
217
|
-
|
|
218
|
-
let exitCode: number;
|
|
219
|
-
try {
|
|
220
|
-
const timeoutResult = await withProcessTimeout(proc, DECOMPOSE_TIMEOUT_MS, {
|
|
221
|
-
graceMs: 5000,
|
|
222
|
-
onTimeout: () => {
|
|
223
|
-
timedOut = true;
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
exitCode = timeoutResult.exitCode;
|
|
227
|
-
} finally {
|
|
228
|
-
await pidRegistry.unregister(proc.pid);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (timedOut) {
|
|
232
|
-
throw new Error(`Decompose timed out after ${DECOMPOSE_TIMEOUT_MS / 1000}s`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let stdoutTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
236
|
-
const stdout = await Promise.race([
|
|
237
|
-
new Response(proc.stdout).text(),
|
|
238
|
-
new Promise<string>((resolve) => {
|
|
239
|
-
stdoutTimeoutId = setTimeout(() => resolve(""), 5000);
|
|
240
|
-
}),
|
|
241
|
-
]);
|
|
242
|
-
clearTimeout(stdoutTimeoutId);
|
|
243
|
-
const stderr = await new Response(proc.stderr).text();
|
|
244
|
-
|
|
245
|
-
if (exitCode !== 0) {
|
|
246
|
-
throw new Error(`Decompose failed with exit code ${exitCode}: ${stderr}`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const stories = parseDecomposeOutput(stdout);
|
|
250
|
-
|
|
251
|
-
return { stories };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
runInteractive(options: InteractiveRunOptions): PtyHandle {
|
|
255
|
-
const pidRegistry = this.getPidRegistry(options.workdir);
|
|
256
|
-
return runInteractiveMode(this.binary, options, pidRegistry);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Agent - Completion API
|
|
3
|
-
*
|
|
4
|
-
* Standalone completion endpoint for simple prompts.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { resolvePermissions } from "../../config/permissions";
|
|
8
|
-
import type { CompleteOptions } from "../types";
|
|
9
|
-
import { CompleteError } from "../types";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Injectable dependencies for complete() — allows tests to intercept
|
|
13
|
-
* Bun.spawn calls and verify correct CLI args without the claude binary.
|
|
14
|
-
*
|
|
15
|
-
* @internal
|
|
16
|
-
*/
|
|
17
|
-
export const _completeDeps = {
|
|
18
|
-
spawn(
|
|
19
|
-
cmd: string[],
|
|
20
|
-
opts: { stdout: "pipe"; stderr: "pipe" | "inherit" },
|
|
21
|
-
): { stdout: ReadableStream<Uint8Array>; stderr: ReadableStream<Uint8Array>; exited: Promise<number>; pid: number } {
|
|
22
|
-
return Bun.spawn(cmd, opts) as unknown as {
|
|
23
|
-
stdout: ReadableStream<Uint8Array>;
|
|
24
|
-
stderr: ReadableStream<Uint8Array>;
|
|
25
|
-
exited: Promise<number>;
|
|
26
|
-
pid: number;
|
|
27
|
-
};
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Execute a simple completion request without starting a full agent session.
|
|
33
|
-
*
|
|
34
|
-
* @param binary - Path to claude binary
|
|
35
|
-
* @param prompt - Prompt text
|
|
36
|
-
* @param options - Completion options (model, tokens, format)
|
|
37
|
-
* @returns Completion text output
|
|
38
|
-
* @throws CompleteError if execution fails
|
|
39
|
-
*/
|
|
40
|
-
export async function executeComplete(binary: string, prompt: string, options?: CompleteOptions): Promise<string> {
|
|
41
|
-
const cmd = [binary, "-p", prompt];
|
|
42
|
-
|
|
43
|
-
if (options?.model) {
|
|
44
|
-
cmd.push("--model", options.model);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Note: Claude Code CLI does not support --max-tokens; the option is accepted in
|
|
48
|
-
// CompleteOptions for future use or non-Claude adapters, but is intentionally not
|
|
49
|
-
// forwarded to the claude binary here.
|
|
50
|
-
|
|
51
|
-
if (options?.jsonMode) {
|
|
52
|
-
cmd.push("--output-format", "json");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const { skipPermissions } = resolvePermissions(options?.config, "complete");
|
|
56
|
-
if (skipPermissions) {
|
|
57
|
-
cmd.push("--dangerously-skip-permissions");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const spawnOpts: { stdout: "pipe"; stderr: "pipe"; cwd?: string } = { stdout: "pipe", stderr: "pipe" };
|
|
61
|
-
if (options?.workdir) spawnOpts.cwd = options.workdir;
|
|
62
|
-
const proc = _completeDeps.spawn(cmd, spawnOpts);
|
|
63
|
-
const exitCode = await proc.exited;
|
|
64
|
-
|
|
65
|
-
const stdout = await new Response(proc.stdout).text();
|
|
66
|
-
const stderr = await new Response(proc.stderr).text();
|
|
67
|
-
const trimmed = stdout.trim();
|
|
68
|
-
|
|
69
|
-
if (exitCode !== 0) {
|
|
70
|
-
const errorDetails = stderr.trim() || trimmed;
|
|
71
|
-
const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
|
|
72
|
-
throw new CompleteError(errorMessage, exitCode);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!trimmed) {
|
|
76
|
-
throw new CompleteError("complete() returned empty output");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return trimmed;
|
|
80
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cost Tracking — re-exports from the shared src/agents/cost/ module.
|
|
3
|
-
*
|
|
4
|
-
* Kept for zero-breakage backward compatibility.
|
|
5
|
-
* Import directly from src/agents/cost for new code.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export type { ModelCostRates, TokenUsage, CostEstimate, TokenUsageWithConfidence } from "../cost";
|
|
9
|
-
export {
|
|
10
|
-
COST_RATES,
|
|
11
|
-
parseTokenUsage,
|
|
12
|
-
estimateCost,
|
|
13
|
-
estimateCostFromOutput,
|
|
14
|
-
estimateCostByDuration,
|
|
15
|
-
formatCostWithConfidence,
|
|
16
|
-
} from "../cost";
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Claude Code Agent - Execution Layer
|
|
3
|
-
*
|
|
4
|
-
* Handles building commands, preparing environment, and process execution.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
|
-
import { isAbsolute } from "node:path";
|
|
9
|
-
import { resolvePermissions } from "../../config/permissions";
|
|
10
|
-
import type { PidRegistry } from "../../execution/pid-registry";
|
|
11
|
-
import { withProcessTimeout } from "../../execution/timeout-handler";
|
|
12
|
-
import { getLogger } from "../../logger";
|
|
13
|
-
import type { AgentResult, AgentRunOptions } from "../types";
|
|
14
|
-
import { estimateCostByDuration, estimateCostFromOutput } from "./cost";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Maximum characters to capture from agent stdout.
|
|
18
|
-
*/
|
|
19
|
-
const MAX_AGENT_OUTPUT_CHARS = 5000;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Maximum characters to capture from agent stderr.
|
|
23
|
-
*/
|
|
24
|
-
const MAX_AGENT_STDERR_CHARS = 1000;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Grace period in ms between SIGTERM and SIGKILL on timeout.
|
|
28
|
-
*/
|
|
29
|
-
const SIGKILL_GRACE_PERIOD_MS = 5000;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Injectable dependencies for runOnce() — allows tests to verify
|
|
33
|
-
* that PID cleanup (unregister) always runs even if kill() throws.
|
|
34
|
-
*
|
|
35
|
-
* @internal
|
|
36
|
-
*/
|
|
37
|
-
export const _runOnceDeps = {
|
|
38
|
-
killProc(proc: { kill(signal?: number | NodeJS.Signals): void }, signal: NodeJS.Signals): void {
|
|
39
|
-
proc.kill(signal);
|
|
40
|
-
},
|
|
41
|
-
buildCmd(binary: string, options: AgentRunOptions): string[] {
|
|
42
|
-
return buildCommand(binary, options);
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Build Claude Code command with model and permissions.
|
|
48
|
-
*
|
|
49
|
-
* @param binary - Path to claude binary
|
|
50
|
-
* @param options - Agent run options
|
|
51
|
-
* @returns Command array for Bun.spawn()
|
|
52
|
-
*/
|
|
53
|
-
export function buildCommand(binary: string, options: AgentRunOptions): string[] {
|
|
54
|
-
const model = options.modelDef.model;
|
|
55
|
-
const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
|
|
56
|
-
const permArgs = skipPermissions ? ["--dangerously-skip-permissions"] : [];
|
|
57
|
-
return [binary, "--model", model, ...permArgs, "-p", options.prompt];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Build allowed environment variables for spawned agents.
|
|
62
|
-
* SEC-4: Only pass essential env vars to prevent leaking sensitive data.
|
|
63
|
-
*
|
|
64
|
-
* @param options - Agent run options
|
|
65
|
-
* @returns Filtered environment variables
|
|
66
|
-
*/
|
|
67
|
-
export function buildAllowedEnv(options: AgentRunOptions): Record<string, string | undefined> {
|
|
68
|
-
const allowed: Record<string, string | undefined> = {};
|
|
69
|
-
|
|
70
|
-
const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
71
|
-
for (const varName of essentialVars) {
|
|
72
|
-
if (process.env[varName]) {
|
|
73
|
-
allowed[varName] = process.env[varName];
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Sanitize HOME — must be absolute path. Unexpanded "~" causes literal ~/dir in cwd.
|
|
78
|
-
const rawHome = process.env.HOME ?? "";
|
|
79
|
-
const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
|
|
80
|
-
if (rawHome !== safeHome) {
|
|
81
|
-
const logger = getLogger();
|
|
82
|
-
logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
|
|
83
|
-
}
|
|
84
|
-
allowed.HOME = safeHome;
|
|
85
|
-
|
|
86
|
-
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
|
|
87
|
-
for (const varName of apiKeyVars) {
|
|
88
|
-
if (process.env[varName]) {
|
|
89
|
-
allowed[varName] = process.env[varName];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_"];
|
|
94
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
95
|
-
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
96
|
-
allowed[key] = value;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (options.modelDef.env) {
|
|
101
|
-
Object.assign(allowed, options.modelDef.env);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (options.env) {
|
|
105
|
-
Object.assign(allowed, options.env);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return allowed;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Execute agent process once with timeout and signal handling.
|
|
113
|
-
*
|
|
114
|
-
* @param binary - Path to claude binary
|
|
115
|
-
* @param options - Agent run options
|
|
116
|
-
* @param pidRegistry - PID registry for cleanup
|
|
117
|
-
* @returns Agent execution result
|
|
118
|
-
*
|
|
119
|
-
* @internal
|
|
120
|
-
*/
|
|
121
|
-
export async function executeOnce(
|
|
122
|
-
binary: string,
|
|
123
|
-
options: AgentRunOptions,
|
|
124
|
-
pidRegistry: PidRegistry,
|
|
125
|
-
): Promise<AgentResult> {
|
|
126
|
-
const cmd = _runOnceDeps.buildCmd(binary, options);
|
|
127
|
-
const startTime = Date.now();
|
|
128
|
-
|
|
129
|
-
// Log session-related options for traceability. CLI adapter doesn't use sessions,
|
|
130
|
-
// but the pipeline passes these uniformly. Logged so future CLI session support
|
|
131
|
-
// can verify they're threaded correctly.
|
|
132
|
-
if (options.sessionRole || options.acpSessionName || options.keepSessionOpen) {
|
|
133
|
-
const logger = getLogger();
|
|
134
|
-
logger.debug("agent", "CLI mode: session options received (unused)", {
|
|
135
|
-
sessionRole: options.sessionRole,
|
|
136
|
-
acpSessionName: options.acpSessionName,
|
|
137
|
-
keepSessionOpen: options.keepSessionOpen,
|
|
138
|
-
featureName: options.featureName,
|
|
139
|
-
storyId: options.storyId,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const proc = Bun.spawn(cmd, {
|
|
144
|
-
cwd: options.workdir,
|
|
145
|
-
stdout: "pipe",
|
|
146
|
-
stderr: "inherit",
|
|
147
|
-
env: buildAllowedEnv(options),
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const processPid = proc.pid;
|
|
151
|
-
await pidRegistry.register(processPid);
|
|
152
|
-
|
|
153
|
-
let timedOut = false;
|
|
154
|
-
let exitCode: number;
|
|
155
|
-
try {
|
|
156
|
-
const timeoutResult = await withProcessTimeout(proc, options.timeoutSeconds * 1000, {
|
|
157
|
-
graceMs: SIGKILL_GRACE_PERIOD_MS,
|
|
158
|
-
onTimeout: () => {
|
|
159
|
-
timedOut = true;
|
|
160
|
-
},
|
|
161
|
-
killFn: (p, signal) => _runOnceDeps.killProc(p, signal),
|
|
162
|
-
});
|
|
163
|
-
exitCode = timeoutResult.exitCode;
|
|
164
|
-
timedOut = timeoutResult.timedOut;
|
|
165
|
-
} finally {
|
|
166
|
-
await pidRegistry.unregister(processPid);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
let stdoutTimeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
170
|
-
const stdout = await Promise.race([
|
|
171
|
-
new Response(proc.stdout).text(),
|
|
172
|
-
new Promise<string>((resolve) => {
|
|
173
|
-
stdoutTimeoutId = setTimeout(() => resolve(""), 5000);
|
|
174
|
-
}),
|
|
175
|
-
]);
|
|
176
|
-
clearTimeout(stdoutTimeoutId); // prevent leaked timer when stdout resolves first
|
|
177
|
-
const stderr = proc.stderr ? await new Response(proc.stderr).text() : "";
|
|
178
|
-
const durationMs = Date.now() - startTime;
|
|
179
|
-
|
|
180
|
-
const fullOutput = stdout + stderr;
|
|
181
|
-
const rateLimited =
|
|
182
|
-
fullOutput.toLowerCase().includes("rate limit") ||
|
|
183
|
-
fullOutput.includes("429") ||
|
|
184
|
-
fullOutput.toLowerCase().includes("too many requests");
|
|
185
|
-
|
|
186
|
-
let costEstimate = estimateCostFromOutput(options.modelTier, fullOutput);
|
|
187
|
-
const logger = getLogger();
|
|
188
|
-
if (!costEstimate) {
|
|
189
|
-
const fallbackEstimate = estimateCostByDuration(options.modelTier, durationMs);
|
|
190
|
-
costEstimate = {
|
|
191
|
-
cost: fallbackEstimate.cost * 1.5,
|
|
192
|
-
confidence: "fallback",
|
|
193
|
-
};
|
|
194
|
-
logger.warn("agent", "Cost estimation fallback (duration-based)", {
|
|
195
|
-
modelTier: options.modelTier,
|
|
196
|
-
cost: costEstimate.cost,
|
|
197
|
-
});
|
|
198
|
-
} else if (costEstimate.confidence === "estimated") {
|
|
199
|
-
logger.warn("agent", "Cost estimation using regex parsing (estimated confidence)", { cost: costEstimate.cost });
|
|
200
|
-
}
|
|
201
|
-
const cost = costEstimate.cost;
|
|
202
|
-
|
|
203
|
-
const actualExitCode = timedOut ? 124 : exitCode;
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
success: exitCode === 0 && !timedOut,
|
|
207
|
-
exitCode: actualExitCode,
|
|
208
|
-
output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS),
|
|
209
|
-
stderr: stderr.slice(-MAX_AGENT_STDERR_CHARS),
|
|
210
|
-
rateLimited,
|
|
211
|
-
durationMs,
|
|
212
|
-
estimatedCost: cost,
|
|
213
|
-
pid: processPid,
|
|
214
|
-
};
|
|
215
|
-
}
|