@harness-control/runner 0.1.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/LICENSE +202 -0
- package/README.md +21 -0
- package/dist/audit/index.d.ts +18 -0
- package/dist/audit/index.js +28 -0
- package/dist/config/index.d.ts +179 -0
- package/dist/config/index.js +124 -0
- package/dist/connection/index.d.ts +2 -0
- package/dist/connection/index.js +2 -0
- package/dist/connection/runner-connection.d.ts +22 -0
- package/dist/connection/runner-connection.js +631 -0
- package/dist/harnesses/adapters/providers/claude-runtime.d.ts +5 -0
- package/dist/harnesses/adapters/providers/claude-runtime.js +186 -0
- package/dist/harnesses/adapters/providers/claude.d.ts +24 -0
- package/dist/harnesses/adapters/providers/claude.js +189 -0
- package/dist/harnesses/adapters/providers/cli-process.d.ts +44 -0
- package/dist/harnesses/adapters/providers/cli-process.js +195 -0
- package/dist/harnesses/adapters/providers/codex-models.d.ts +4 -0
- package/dist/harnesses/adapters/providers/codex-models.js +62 -0
- package/dist/harnesses/adapters/providers/codex-rpc.d.ts +21 -0
- package/dist/harnesses/adapters/providers/codex-rpc.js +114 -0
- package/dist/harnesses/adapters/providers/codex-runtime.d.ts +3 -0
- package/dist/harnesses/adapters/providers/codex-runtime.js +267 -0
- package/dist/harnesses/adapters/providers/codex.d.ts +22 -0
- package/dist/harnesses/adapters/providers/codex.js +161 -0
- package/dist/harnesses/adapters/providers/mock.d.ts +13 -0
- package/dist/harnesses/adapters/providers/mock.js +64 -0
- package/dist/harnesses/adapters/providers/native-process.d.ts +9 -0
- package/dist/harnesses/adapters/providers/native-process.js +41 -0
- package/dist/harnesses/adapters/providers/native-turn.d.ts +17 -0
- package/dist/harnesses/adapters/providers/native-turn.js +139 -0
- package/dist/harnesses/adapters/providers/opencode.d.ts +44 -0
- package/dist/harnesses/adapters/providers/opencode.js +416 -0
- package/dist/harnesses/adapters/providers/shared.d.ts +9 -0
- package/dist/harnesses/adapters/providers/shared.js +97 -0
- package/dist/harnesses/adapters/registry.d.ts +12 -0
- package/dist/harnesses/adapters/registry.js +47 -0
- package/dist/harnesses/adapters/types.d.ts +54 -0
- package/dist/harnesses/adapters/types.js +9 -0
- package/dist/harnesses/adapters.d.ts +8 -0
- package/dist/harnesses/adapters.js +8 -0
- package/dist/harnesses/index.d.ts +93 -0
- package/dist/harnesses/index.js +620 -0
- package/dist/host/provider-registry.d.ts +34 -0
- package/dist/host/provider-registry.js +162 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +201 -0
- package/dist/local-actions/dispatcher.d.ts +28 -0
- package/dist/local-actions/dispatcher.js +407 -0
- package/dist/local-actions/executors.d.ts +159 -0
- package/dist/local-actions/executors.js +1103 -0
- package/dist/local-actions/index.d.ts +74 -0
- package/dist/local-actions/index.js +275 -0
- package/dist/logs/index.d.ts +6 -0
- package/dist/logs/index.js +9 -0
- package/dist/mcp/McpAttachmentClient.d.ts +111 -0
- package/dist/mcp/McpAttachmentClient.js +345 -0
- package/dist/mcp/McpProxyServer.d.ts +18 -0
- package/dist/mcp/McpProxyServer.js +188 -0
- package/dist/mcp/McpStdioProfileClient.d.ts +19 -0
- package/dist/mcp/McpStdioProfileClient.js +91 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/redaction.d.ts +3 -0
- package/dist/mcp/redaction.js +40 -0
- package/dist/pairing/index.d.ts +38 -0
- package/dist/pairing/index.js +180 -0
- package/dist/state/index.d.ts +76 -0
- package/dist/state/index.js +242 -0
- package/dist/workspaces/index.d.ts +13 -0
- package/dist/workspaces/index.js +110 -0
- package/package.json +76 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { query, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { HarnessAdapterError } from "../types.js";
|
|
4
|
+
import { adapterMcpServers, assertCliMcpAttachmentProxied } from "./shared.js";
|
|
5
|
+
import { selectedEffort } from "./native-turn.js";
|
|
6
|
+
import { NativeProcess } from "./native-process.js";
|
|
7
|
+
const resultSchema = z.object({
|
|
8
|
+
type: z.literal("result"),
|
|
9
|
+
subtype: z.literal("success"),
|
|
10
|
+
is_error: z.literal(false),
|
|
11
|
+
result: z.string(),
|
|
12
|
+
api_error_status: z.number().nullable().optional(),
|
|
13
|
+
terminal_reason: z.string().optional(),
|
|
14
|
+
stop_reason: z.string().nullable().optional(),
|
|
15
|
+
total_cost_usd: z.number().nonnegative().optional(),
|
|
16
|
+
modelUsage: z
|
|
17
|
+
.record(z.string(), z.object({
|
|
18
|
+
inputTokens: z.number().int().nonnegative(),
|
|
19
|
+
outputTokens: z.number().int().nonnegative(),
|
|
20
|
+
cacheReadInputTokens: z.number().int().nonnegative(),
|
|
21
|
+
cacheCreationInputTokens: z.number().int().nonnegative(),
|
|
22
|
+
}))
|
|
23
|
+
.optional(),
|
|
24
|
+
});
|
|
25
|
+
export function createClaudeTurn(queryFactory = query) {
|
|
26
|
+
return async (input, signal, emit) => {
|
|
27
|
+
const selection = input.payload.model_selection ?? input.startPayload.model_selection;
|
|
28
|
+
const effort = selectedEffort(selection, "claude");
|
|
29
|
+
const mcpServers = {};
|
|
30
|
+
for (const attachment of adapterMcpServers(input.mcpServers, input.startPayload)) {
|
|
31
|
+
assertCliMcpAttachmentProxied(attachment, "Claude Code", "claude");
|
|
32
|
+
mcpServers[attachment.name] = { type: "http", url: attachment.url };
|
|
33
|
+
}
|
|
34
|
+
let processHandle;
|
|
35
|
+
const abort = () => {
|
|
36
|
+
if (processHandle)
|
|
37
|
+
void processHandle.stop();
|
|
38
|
+
};
|
|
39
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
40
|
+
let stream;
|
|
41
|
+
try {
|
|
42
|
+
signal.throwIfAborted();
|
|
43
|
+
stream = queryFactory({
|
|
44
|
+
prompt: input.payload.input,
|
|
45
|
+
options: {
|
|
46
|
+
pathToClaudeCodeExecutable: input.provider.executable_path ?? "claude",
|
|
47
|
+
cwd: input.startPayload.cwd,
|
|
48
|
+
model: selection.model,
|
|
49
|
+
...(effort ? { effort } : {}),
|
|
50
|
+
env: {
|
|
51
|
+
...globalThis.process.env,
|
|
52
|
+
...input.provider.env,
|
|
53
|
+
...(input.provider.home
|
|
54
|
+
? { CLAUDE_CONFIG_DIR: input.provider.home }
|
|
55
|
+
: {}),
|
|
56
|
+
},
|
|
57
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
58
|
+
settingSources: [],
|
|
59
|
+
persistSession: false,
|
|
60
|
+
includePartialMessages: true,
|
|
61
|
+
strictMcpConfig: true,
|
|
62
|
+
mcpServers,
|
|
63
|
+
permissionMode: "bypassPermissions",
|
|
64
|
+
allowDangerouslySkipPermissions: true,
|
|
65
|
+
disallowedTools: [
|
|
66
|
+
"AskUserQuestion",
|
|
67
|
+
"EnterPlanMode",
|
|
68
|
+
"ExitPlanMode",
|
|
69
|
+
"Agent",
|
|
70
|
+
"Task",
|
|
71
|
+
],
|
|
72
|
+
spawnClaudeCodeProcess: (options) => {
|
|
73
|
+
processHandle = new NativeProcess(options.command, options.args, input.startPayload.cwd, options.env);
|
|
74
|
+
processHandle.child.stderr.resume();
|
|
75
|
+
if (signal.aborted)
|
|
76
|
+
abort();
|
|
77
|
+
return processHandle.child;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
let result;
|
|
82
|
+
let streamed = false;
|
|
83
|
+
for await (const message of stream) {
|
|
84
|
+
if (message.type === "stream_event" &&
|
|
85
|
+
message.event.type === "content_block_delta") {
|
|
86
|
+
const delta = message.event.delta;
|
|
87
|
+
if (delta.type === "text_delta" || delta.type === "thinking_delta") {
|
|
88
|
+
if (delta.type === "text_delta")
|
|
89
|
+
streamed = true;
|
|
90
|
+
emit({
|
|
91
|
+
event_type: delta.type === "text_delta"
|
|
92
|
+
? "content.delta"
|
|
93
|
+
: "reasoning.delta",
|
|
94
|
+
turn_id: input.payload.turn_id,
|
|
95
|
+
data: {
|
|
96
|
+
delta: delta.type === "text_delta" ? delta.text : delta.thinking,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (message.type === "assistant") {
|
|
102
|
+
for (const block of message.message.content) {
|
|
103
|
+
if (block.type === "tool_use")
|
|
104
|
+
emit({
|
|
105
|
+
event_type: "item.started",
|
|
106
|
+
turn_id: input.payload.turn_id,
|
|
107
|
+
data: {
|
|
108
|
+
item_id: block.id,
|
|
109
|
+
item_type: "tool_call",
|
|
110
|
+
summary: block.name,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (message.type === "user" &&
|
|
116
|
+
Array.isArray(message.message.content)) {
|
|
117
|
+
for (const block of message.message.content) {
|
|
118
|
+
if (block.type === "tool_result")
|
|
119
|
+
emit({
|
|
120
|
+
event_type: "item.completed",
|
|
121
|
+
turn_id: input.payload.turn_id,
|
|
122
|
+
data: {
|
|
123
|
+
item_id: block.tool_use_id,
|
|
124
|
+
item_type: "tool_call",
|
|
125
|
+
status: block.is_error ? "failed" : "completed",
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (message.type === "result") {
|
|
131
|
+
const parsed = resultSchema.safeParse(message);
|
|
132
|
+
if (!parsed.success || result !== undefined)
|
|
133
|
+
throw new HarnessAdapterError("claude_result_error", "Claude returned an unsuccessful or malformed terminal result.");
|
|
134
|
+
result = parsed.data;
|
|
135
|
+
if ((result.api_error_status != null &&
|
|
136
|
+
result.api_error_status >= 400) ||
|
|
137
|
+
(result.terminal_reason !== undefined &&
|
|
138
|
+
result.terminal_reason !== "completed") ||
|
|
139
|
+
(result.stop_reason != null &&
|
|
140
|
+
!["end_turn", "stop_sequence"].includes(result.stop_reason))) {
|
|
141
|
+
throw new HarnessAdapterError("claude_result_error", "Claude ended with a provider error or execution limit.");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!result)
|
|
146
|
+
throw new HarnessAdapterError("claude_missing_result", "Claude closed without a terminal result.");
|
|
147
|
+
if (!streamed && result.result)
|
|
148
|
+
emit({
|
|
149
|
+
event_type: "content.delta",
|
|
150
|
+
turn_id: input.payload.turn_id,
|
|
151
|
+
data: { delta: result.result },
|
|
152
|
+
});
|
|
153
|
+
let inputTokens = 0;
|
|
154
|
+
let outputTokens = 0;
|
|
155
|
+
for (const usage of Object.values(result.modelUsage ?? {})) {
|
|
156
|
+
inputTokens +=
|
|
157
|
+
usage.inputTokens +
|
|
158
|
+
usage.cacheReadInputTokens +
|
|
159
|
+
usage.cacheCreationInputTokens;
|
|
160
|
+
outputTokens += usage.outputTokens;
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
final_text: result.result,
|
|
164
|
+
usage: {
|
|
165
|
+
...(result.modelUsage
|
|
166
|
+
? {
|
|
167
|
+
input_tokens: inputTokens,
|
|
168
|
+
output_tokens: outputTokens,
|
|
169
|
+
total_tokens: inputTokens + outputTokens,
|
|
170
|
+
}
|
|
171
|
+
: {}),
|
|
172
|
+
...(result.total_cost_usd !== undefined
|
|
173
|
+
? { cost_usd: result.total_cost_usd }
|
|
174
|
+
: {}),
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
signal.removeEventListener("abort", abort);
|
|
180
|
+
stream?.close();
|
|
181
|
+
if (processHandle)
|
|
182
|
+
await processHandle.stop();
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=claude-runtime.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProviderInstanceConfig } from "../../../config/index.js";
|
|
2
|
+
import type { ProviderDriverStatus } from "../../../host/provider-registry.js";
|
|
3
|
+
import { type HarnessAdapter, type HarnessAdapterStartInput, type HarnessAdapterSession, type HarnessAdapterTurnInput, type HarnessAdapterEvent, type HarnessAdapterCancelInput, type HarnessAdapterStopInput } from "../types.js";
|
|
4
|
+
import { type CliProcessSpawner } from "./cli-process.js";
|
|
5
|
+
import { type ClaudeQueryFactory } from "./claude-runtime.js";
|
|
6
|
+
export type ClaudeHarnessAdapterOptions = {
|
|
7
|
+
processSpawner?: CliProcessSpawner;
|
|
8
|
+
probeTimeoutMs?: number;
|
|
9
|
+
turnTimeoutMs?: number;
|
|
10
|
+
processKillGraceMs?: number;
|
|
11
|
+
queryFactory?: ClaudeQueryFactory;
|
|
12
|
+
};
|
|
13
|
+
export declare class ClaudeHarnessAdapter implements HarnessAdapter {
|
|
14
|
+
#private;
|
|
15
|
+
readonly driverKind = "claude";
|
|
16
|
+
constructor(options?: ClaudeHarnessAdapterOptions);
|
|
17
|
+
probe(provider: ProviderInstanceConfig): Promise<ProviderDriverStatus>;
|
|
18
|
+
validateStart(input: HarnessAdapterStartInput): Promise<void>;
|
|
19
|
+
startSession(input: HarnessAdapterStartInput): Promise<HarnessAdapterSession>;
|
|
20
|
+
sendTurn(input: HarnessAdapterTurnInput): Promise<HarnessAdapterEvent[]>;
|
|
21
|
+
cancelTurn(input: HarnessAdapterCancelInput): Promise<HarnessAdapterEvent[]>;
|
|
22
|
+
stopSession(input: HarnessAdapterStopInput): Promise<HarnessAdapterEvent[]>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { firstLine, processFailureMessage, spawnProviderCliProcess, startManagedCliProcess, } from "./cli-process.js";
|
|
2
|
+
import { normalizeProviderModels, adapterMcpServers, assertCliMcpAttachmentProxied, } from "./shared.js";
|
|
3
|
+
import { NativeTurns, nativeExecutionCapabilities, validateNativeStart, } from "./native-turn.js";
|
|
4
|
+
import { createClaudeTurn } from "./claude-runtime.js";
|
|
5
|
+
export class ClaudeHarnessAdapter {
|
|
6
|
+
driverKind = "claude";
|
|
7
|
+
#processSpawner;
|
|
8
|
+
#probeTimeoutMs;
|
|
9
|
+
#processKillGraceMs;
|
|
10
|
+
#turns;
|
|
11
|
+
#execute;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.#processSpawner = options.processSpawner ?? spawnProviderCliProcess;
|
|
14
|
+
this.#probeTimeoutMs = options.probeTimeoutMs ?? 5_000;
|
|
15
|
+
this.#processKillGraceMs = options.processKillGraceMs ?? 1_000;
|
|
16
|
+
this.#turns = new NativeTurns("claude", options.turnTimeoutMs ?? 10 * 60 * 1000);
|
|
17
|
+
this.#execute = createClaudeTurn(options.queryFactory);
|
|
18
|
+
}
|
|
19
|
+
async probe(provider) {
|
|
20
|
+
const executable = provider.executable_path ?? "claude";
|
|
21
|
+
const diagnosticPaths = claudeDiagnosticPaths(provider, executable, process.cwd());
|
|
22
|
+
const launchArgs = claudeLaunchArgs(provider);
|
|
23
|
+
const versionResult = await this.#runProcess(executable, [...launchArgs, "--version"], {
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
env: claudeEnvironment(provider),
|
|
26
|
+
}, this.#probeTimeoutMs);
|
|
27
|
+
if (versionResult.timedOut ||
|
|
28
|
+
versionResult.error ||
|
|
29
|
+
versionResult.exitCode !== 0) {
|
|
30
|
+
return {
|
|
31
|
+
provider_instance_id: provider.id,
|
|
32
|
+
driver_kind: "claude",
|
|
33
|
+
execution_capabilities: nativeExecutionCapabilities("claude"),
|
|
34
|
+
installed: false,
|
|
35
|
+
available: false,
|
|
36
|
+
status: "unavailable",
|
|
37
|
+
message: versionResult.timedOut
|
|
38
|
+
? "Claude Code version probe timed out."
|
|
39
|
+
: processFailureMessage(versionResult, "Claude Code executable is not available.", diagnosticPaths),
|
|
40
|
+
models: normalizeProviderModels(provider.models),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const authResult = await this.#runProcess(executable, [...launchArgs, "auth", "status", "--json"], {
|
|
44
|
+
cwd: process.cwd(),
|
|
45
|
+
env: claudeEnvironment(provider),
|
|
46
|
+
}, this.#probeTimeoutMs);
|
|
47
|
+
const version = firstLine(versionResult.stdout);
|
|
48
|
+
if (authResult.timedOut || authResult.error) {
|
|
49
|
+
return {
|
|
50
|
+
provider_instance_id: provider.id,
|
|
51
|
+
driver_kind: "claude",
|
|
52
|
+
execution_capabilities: nativeExecutionCapabilities("claude"),
|
|
53
|
+
installed: true,
|
|
54
|
+
available: false,
|
|
55
|
+
status: "unavailable",
|
|
56
|
+
...(version ? { version } : {}),
|
|
57
|
+
message: authResult.timedOut
|
|
58
|
+
? "Claude Code authentication probe timed out."
|
|
59
|
+
: processFailureMessage(authResult, "Claude Code authentication probe failed.", diagnosticPaths),
|
|
60
|
+
models: normalizeProviderModels(provider.models),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (authResult.exitCode !== 0 ||
|
|
64
|
+
claudeAuthStatus(authResult.stdout) === false) {
|
|
65
|
+
return {
|
|
66
|
+
provider_instance_id: provider.id,
|
|
67
|
+
driver_kind: "claude",
|
|
68
|
+
execution_capabilities: nativeExecutionCapabilities("claude"),
|
|
69
|
+
installed: true,
|
|
70
|
+
available: false,
|
|
71
|
+
status: "unauthenticated",
|
|
72
|
+
...(version ? { version } : {}),
|
|
73
|
+
message: processFailureMessage(authResult, "Claude Code is not authenticated.", diagnosticPaths),
|
|
74
|
+
models: normalizeProviderModels(provider.models),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
provider_instance_id: provider.id,
|
|
79
|
+
driver_kind: "claude",
|
|
80
|
+
execution_capabilities: nativeExecutionCapabilities("claude"),
|
|
81
|
+
installed: true,
|
|
82
|
+
available: true,
|
|
83
|
+
status: "ready",
|
|
84
|
+
...(version ? { version } : {}),
|
|
85
|
+
authStatus: "authenticated",
|
|
86
|
+
models: provider.models.length > 0
|
|
87
|
+
? normalizeProviderModels(provider.models)
|
|
88
|
+
: [
|
|
89
|
+
{
|
|
90
|
+
id: "sonnet",
|
|
91
|
+
label: "Claude Sonnet",
|
|
92
|
+
is_default: true,
|
|
93
|
+
capabilities: {
|
|
94
|
+
option_descriptors: [
|
|
95
|
+
{
|
|
96
|
+
id: "effort",
|
|
97
|
+
label: "Effort",
|
|
98
|
+
type: "select",
|
|
99
|
+
values: ["low", "medium", "high", "xhigh", "max"].map((value) => ({ value, label: value })),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "opus",
|
|
106
|
+
label: "Claude Opus",
|
|
107
|
+
capabilities: { option_descriptors: [] },
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "haiku",
|
|
111
|
+
label: "Claude Haiku",
|
|
112
|
+
capabilities: { option_descriptors: [] },
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async validateStart(input) {
|
|
118
|
+
validateNativeStart(input, "claude");
|
|
119
|
+
}
|
|
120
|
+
async startSession(input) {
|
|
121
|
+
await this.validateStart(input);
|
|
122
|
+
for (const attachment of adapterMcpServers(input.mcpServers, input.payload))
|
|
123
|
+
assertCliMcpAttachmentProxied(attachment, "Claude", "claude");
|
|
124
|
+
return { adapter_session_id: input.payload.session_id };
|
|
125
|
+
}
|
|
126
|
+
async sendTurn(input) {
|
|
127
|
+
return this.#turns.run(input, async (request, signal, emit) => {
|
|
128
|
+
await this.validateStart({
|
|
129
|
+
payload: request.startPayload,
|
|
130
|
+
provider: request.provider,
|
|
131
|
+
});
|
|
132
|
+
return this.#execute(request, signal, emit);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async cancelTurn(input) {
|
|
136
|
+
return this.#turns.cancel(input.sessionId, input.turnId);
|
|
137
|
+
}
|
|
138
|
+
async stopSession(input) {
|
|
139
|
+
return this.#turns.stop(input.sessionId);
|
|
140
|
+
}
|
|
141
|
+
#runProcess(executable, argv, options, timeoutMs) {
|
|
142
|
+
return this.#startProcess(executable, argv, options, timeoutMs).completion;
|
|
143
|
+
}
|
|
144
|
+
#startProcess(executable, argv, options, timeoutMs) {
|
|
145
|
+
return startManagedCliProcess({
|
|
146
|
+
processSpawner: this.#processSpawner,
|
|
147
|
+
executable,
|
|
148
|
+
argv,
|
|
149
|
+
runOptions: options,
|
|
150
|
+
timeoutMs,
|
|
151
|
+
processKillGraceMs: this.#processKillGraceMs,
|
|
152
|
+
timeoutErrorMessage: "Claude Code execution timed out.",
|
|
153
|
+
terminatedErrorMessage: "Claude Code process was terminated.",
|
|
154
|
+
startFailureMessage: "Claude Code process failed before start.",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function claudeDiagnosticPaths(provider, executable, ...paths) {
|
|
159
|
+
return [provider.executable_path, provider.home, executable, ...paths].filter((value) => value !== undefined && value.length > 0);
|
|
160
|
+
}
|
|
161
|
+
function claudeEnvironment(provider) {
|
|
162
|
+
return {
|
|
163
|
+
...provider.env,
|
|
164
|
+
...(provider.home ? { CLAUDE_CONFIG_DIR: provider.home } : {}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function claudeLaunchArgs(provider) {
|
|
168
|
+
return provider.launch_args;
|
|
169
|
+
}
|
|
170
|
+
function claudeAuthStatus(stdout) {
|
|
171
|
+
let parsed;
|
|
172
|
+
try {
|
|
173
|
+
parsed = JSON.parse(stdout);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (error instanceof Error) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
if (!isJsonObject(parsed) || typeof parsed["loggedIn"] !== "boolean") {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
return parsed["loggedIn"];
|
|
185
|
+
}
|
|
186
|
+
function isJsonObject(value) {
|
|
187
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
export type CliProcessRunOptions = {
|
|
3
|
+
cwd: string;
|
|
4
|
+
env: Record<string, string>;
|
|
5
|
+
};
|
|
6
|
+
export type CliProcessResult = {
|
|
7
|
+
exitCode: number | null;
|
|
8
|
+
signal: NodeJS.Signals | null;
|
|
9
|
+
stdout: string;
|
|
10
|
+
stderr: string;
|
|
11
|
+
error: string | undefined;
|
|
12
|
+
timedOut: boolean;
|
|
13
|
+
};
|
|
14
|
+
export type CliProcessHandle = {
|
|
15
|
+
readonly result: Promise<CliProcessResult>;
|
|
16
|
+
kill(signal?: NodeJS.Signals): void;
|
|
17
|
+
};
|
|
18
|
+
export type CliProcessSpawner = (executable: string, argv: string[], options: CliProcessRunOptions) => CliProcessHandle;
|
|
19
|
+
export type CodexProcessRunOptions = CliProcessRunOptions;
|
|
20
|
+
export type CodexProcessResult = CliProcessResult;
|
|
21
|
+
export type CodexProcessHandle = CliProcessHandle;
|
|
22
|
+
export type CodexProcessSpawner = CliProcessSpawner;
|
|
23
|
+
export type CliManagedProcess = {
|
|
24
|
+
readonly completion: Promise<CliProcessResult>;
|
|
25
|
+
terminate(): void;
|
|
26
|
+
};
|
|
27
|
+
export type CliManagedProcessOptions = {
|
|
28
|
+
processSpawner: CliProcessSpawner;
|
|
29
|
+
executable: string;
|
|
30
|
+
argv: string[];
|
|
31
|
+
runOptions: CliProcessRunOptions;
|
|
32
|
+
timeoutMs: number;
|
|
33
|
+
processKillGraceMs: number;
|
|
34
|
+
timeoutErrorMessage: string;
|
|
35
|
+
terminatedErrorMessage: string;
|
|
36
|
+
startFailureMessage: string;
|
|
37
|
+
};
|
|
38
|
+
export declare function startManagedCliProcess(options: CliManagedProcessOptions): CliManagedProcess;
|
|
39
|
+
export declare function spawnProviderCliProcess(executable: string, argv: string[], options: CliProcessRunOptions): CliProcessHandle;
|
|
40
|
+
export declare function processFailureDetails(result: CliProcessResult): Record<string, unknown>;
|
|
41
|
+
export declare function processFailureMessage(result: CliProcessResult, fallback: string, diagnosticPaths: string[]): string;
|
|
42
|
+
export declare function firstLine(value: string): string | undefined;
|
|
43
|
+
export declare function killChildProcess(child: ChildProcessWithoutNullStreams, signal: NodeJS.Signals): void;
|
|
44
|
+
//# sourceMappingURL=cli-process.d.ts.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { redactValue } from "../../../mcp/redaction.js";
|
|
3
|
+
const MAX_CAPTURED_CLI_OUTPUT_BYTES = 64 * 1024;
|
|
4
|
+
export function startManagedCliProcess(options) {
|
|
5
|
+
const handle = spawnCliProcess(options);
|
|
6
|
+
let settled = false;
|
|
7
|
+
let timedOut = false;
|
|
8
|
+
let timeout;
|
|
9
|
+
let forceKill;
|
|
10
|
+
let settleProcess = () => { };
|
|
11
|
+
const completion = new Promise((resolve) => {
|
|
12
|
+
settleProcess = (result) => {
|
|
13
|
+
if (settled) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
settled = true;
|
|
17
|
+
if (timeout) {
|
|
18
|
+
clearTimeout(timeout);
|
|
19
|
+
}
|
|
20
|
+
if (forceKill) {
|
|
21
|
+
clearTimeout(forceKill);
|
|
22
|
+
}
|
|
23
|
+
resolve({
|
|
24
|
+
...result,
|
|
25
|
+
timedOut: result.timedOut || timedOut,
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
const terminate = (asTimeout) => {
|
|
30
|
+
if (settled)
|
|
31
|
+
return;
|
|
32
|
+
if (asTimeout) {
|
|
33
|
+
timedOut = true;
|
|
34
|
+
}
|
|
35
|
+
handle.kill("SIGTERM");
|
|
36
|
+
if (!forceKill) {
|
|
37
|
+
forceKill = setTimeout(() => {
|
|
38
|
+
handle.kill("SIGKILL");
|
|
39
|
+
}, options.processKillGraceMs);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
timeout = setTimeout(() => terminate(true), options.timeoutMs);
|
|
43
|
+
handle.result.then((result) => settleProcess(result), (error) => settleProcess(processExceptionResult(error, options.startFailureMessage)));
|
|
44
|
+
return {
|
|
45
|
+
completion,
|
|
46
|
+
terminate: () => terminate(false),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function spawnProviderCliProcess(executable, argv, options) {
|
|
50
|
+
const child = spawn(executable, argv, {
|
|
51
|
+
cwd: options.cwd,
|
|
52
|
+
env: {
|
|
53
|
+
...process.env,
|
|
54
|
+
...options.env,
|
|
55
|
+
},
|
|
56
|
+
stdio: "pipe",
|
|
57
|
+
detached: process.platform !== "win32",
|
|
58
|
+
});
|
|
59
|
+
let stdout = "";
|
|
60
|
+
let stderr = "";
|
|
61
|
+
let settled = false;
|
|
62
|
+
child.stdin.end();
|
|
63
|
+
const result = new Promise((resolve) => {
|
|
64
|
+
const settle = (processResult) => {
|
|
65
|
+
if (settled) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
settled = true;
|
|
69
|
+
resolve(processResult);
|
|
70
|
+
};
|
|
71
|
+
child.stdout.on("data", (chunk) => {
|
|
72
|
+
stdout = appendLimitedProcessOutput(stdout, chunk);
|
|
73
|
+
});
|
|
74
|
+
child.stderr.on("data", (chunk) => {
|
|
75
|
+
stderr = appendLimitedProcessOutput(stderr, chunk);
|
|
76
|
+
});
|
|
77
|
+
child.once("error", (error) => {
|
|
78
|
+
settle({
|
|
79
|
+
exitCode: null,
|
|
80
|
+
signal: null,
|
|
81
|
+
stdout,
|
|
82
|
+
stderr,
|
|
83
|
+
error: error.message,
|
|
84
|
+
timedOut: false,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
child.once("close", (exitCode, signal) => {
|
|
88
|
+
settle({
|
|
89
|
+
exitCode,
|
|
90
|
+
signal,
|
|
91
|
+
stdout,
|
|
92
|
+
stderr,
|
|
93
|
+
error: undefined,
|
|
94
|
+
timedOut: false,
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
result,
|
|
100
|
+
kill(signal = "SIGTERM") {
|
|
101
|
+
killChildProcess(child, signal);
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function processFailureDetails(result) {
|
|
106
|
+
return {
|
|
107
|
+
...(result.exitCode !== null ? { exit_code: result.exitCode } : {}),
|
|
108
|
+
...(result.signal !== null ? { signal: result.signal } : {}),
|
|
109
|
+
...(result.timedOut ? { timed_out: true } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function processFailureMessage(result, fallback, diagnosticPaths) {
|
|
113
|
+
const rawMessage = firstNonEmpty(result.error ?? "", result.stderr, result.stdout, fallback);
|
|
114
|
+
const redactedValue = redactValue(redactLocalPaths(rawMessage, diagnosticPaths));
|
|
115
|
+
return typeof redactedValue === "string" ? redactedValue : fallback;
|
|
116
|
+
}
|
|
117
|
+
export function firstLine(value) {
|
|
118
|
+
const line = value.split(/\r?\n/).find((candidate) => candidate.trim().length > 0);
|
|
119
|
+
return line?.trim();
|
|
120
|
+
}
|
|
121
|
+
function spawnCliProcess(options) {
|
|
122
|
+
try {
|
|
123
|
+
return options.processSpawner(options.executable, options.argv, options.runOptions);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
return resolvedProcessHandle(processExceptionResult(error, options.startFailureMessage));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function appendLimitedProcessOutput(existing, chunk) {
|
|
130
|
+
const combined = Buffer.concat([Buffer.from(existing), chunk]);
|
|
131
|
+
if (combined.byteLength <= MAX_CAPTURED_CLI_OUTPUT_BYTES) {
|
|
132
|
+
return combined.toString("utf8");
|
|
133
|
+
}
|
|
134
|
+
return combined.subarray(0, MAX_CAPTURED_CLI_OUTPUT_BYTES).toString("utf8");
|
|
135
|
+
}
|
|
136
|
+
export function killChildProcess(child, signal) {
|
|
137
|
+
try {
|
|
138
|
+
if (process.platform === "win32" && child.pid !== undefined) {
|
|
139
|
+
const argv = ["/pid", String(child.pid), "/T"];
|
|
140
|
+
if (signal === "SIGKILL") {
|
|
141
|
+
argv.push("/F");
|
|
142
|
+
}
|
|
143
|
+
const killer = spawn("taskkill", argv, { stdio: "ignore" });
|
|
144
|
+
killer.once("error", () => {
|
|
145
|
+
child.kill(signal);
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (process.platform !== "win32" && child.pid !== undefined) {
|
|
150
|
+
process.kill(-child.pid, signal);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
child.kill(signal);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (error instanceof Error && "code" in error && error.code === "ESRCH") {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function resolvedProcessHandle(result) {
|
|
163
|
+
return {
|
|
164
|
+
result: Promise.resolve(result),
|
|
165
|
+
kill() {
|
|
166
|
+
return;
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function processExceptionResult(error, fallback = "Provider process failed before start.") {
|
|
171
|
+
return {
|
|
172
|
+
exitCode: null,
|
|
173
|
+
signal: null,
|
|
174
|
+
stdout: "",
|
|
175
|
+
stderr: "",
|
|
176
|
+
error: error instanceof Error ? error.message : fallback,
|
|
177
|
+
timedOut: false,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function firstNonEmpty(...values) {
|
|
181
|
+
const found = values.find((value) => value.trim().length > 0);
|
|
182
|
+
return found?.trim() ?? "Provider command failed.";
|
|
183
|
+
}
|
|
184
|
+
function redactLocalPaths(value, paths) {
|
|
185
|
+
let redacted = value;
|
|
186
|
+
const sortedPaths = [...new Set(paths)].sort((left, right) => right.length - left.length);
|
|
187
|
+
for (const path of sortedPaths) {
|
|
188
|
+
redacted = redacted.split(path).join("<local-path>");
|
|
189
|
+
}
|
|
190
|
+
return redacted
|
|
191
|
+
.replace(/\/(?:Users|home|private|tmp|var|opt|Applications|Volumes)\/[^\s'"]+/g, "<local-path>")
|
|
192
|
+
.replace(/[A-Za-z]:\\[^\s'"]+/g, "<local-path>")
|
|
193
|
+
.trim();
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=cli-process.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HarnessModel } from "@harness-control/protocol";
|
|
2
|
+
import type { ProviderInstanceConfig } from "../../../config/index.js";
|
|
3
|
+
export declare function codexModels(provider: ProviderInstanceConfig, timeoutMs: number): Promise<HarnessModel[]>;
|
|
4
|
+
//# sourceMappingURL=codex-models.d.ts.map
|