@cat-factory/executor-harness 1.50.2 → 1.50.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-runner.js +65 -18
- package/dist/coding-agent.js +380 -321
- package/package.json +3 -3
- package/src/agent-runner.ts +72 -18
- package/src/coding-agent.ts +449 -336
package/dist/agent-runner.js
CHANGED
|
@@ -34,10 +34,11 @@ function attributeCumulativeUsage(calls, usage) {
|
|
|
34
34
|
* never argv), `onActivity` on every chunk, abort kills the child, and the close
|
|
35
35
|
* handler resolves/rejects. The caller's `onEvent` accumulates the outcome.
|
|
36
36
|
*
|
|
37
|
-
* `prompt` is fed over stdin: for Claude Code that is just the task prompt (the
|
|
38
|
-
* system prompt rides `--append-system-prompt`)
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* `prompt` is fed over stdin: for Claude Code that is normally just the task prompt (the
|
|
38
|
+
* system prompt rides `--append-system-prompt`), unless the system prompt is too large for
|
|
39
|
+
* argv, in which case it is folded into `prompt` (see `carryClaudeSystemPrompt`); for Codex
|
|
40
|
+
* — which has no system-prompt flag — the caller always prepends the composed system prompt
|
|
41
|
+
* so the role + best-practice context is not lost.
|
|
41
42
|
*/
|
|
42
43
|
function streamCli(cli, prompt, opts, env, secrets, onEvent) {
|
|
43
44
|
const { command, args } = cli;
|
|
@@ -122,6 +123,40 @@ function streamCli(cli, prompt, opts, env, secrets, onEvent) {
|
|
|
122
123
|
});
|
|
123
124
|
});
|
|
124
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Fold a composed system prompt into the task prompt so the role + best-practice context
|
|
128
|
+
* rides stdin as a single user turn. Used by the Codex runner (no system-prompt flag) and
|
|
129
|
+
* by the Claude runner's argv-overflow fallback. Empty system prompt ⇒ the task prompt is
|
|
130
|
+
* returned unchanged.
|
|
131
|
+
*/
|
|
132
|
+
function foldSystemPrompt(systemPrompt, userPrompt) {
|
|
133
|
+
return systemPrompt ? `${systemPrompt}\n\n---\n\n${userPrompt}` : userPrompt;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Linux caps a SINGLE argv string at MAX_ARG_STRLEN (32 pages = 128 KiB) — a per-string limit,
|
|
137
|
+
* distinct from (and reached long before) the far larger total ARG_MAX for argv + env combined. A
|
|
138
|
+
* system prompt with best-practice fragments folded in can exceed that per-string cap, and `execve`
|
|
139
|
+
* then fails the whole spawn with `E2BIG` before the agent runs at all — the failure mode seen on
|
|
140
|
+
* the `pr-reviewer` step (a ~150 KiB composed prompt). The binding constraint is that per-string
|
|
141
|
+
* cap; 96 KiB stays comfortably under 128 KiB so the system-prompt argv can never approach it.
|
|
142
|
+
*/
|
|
143
|
+
const MAX_ARGV_STRING_BYTES = 96 * 1024;
|
|
144
|
+
/**
|
|
145
|
+
* Decide how the Claude Code runner carries the composed system prompt. Small prompts ride
|
|
146
|
+
* `--append-system-prompt` (a real system turn, cacheable) as before; a prompt too large for a
|
|
147
|
+
* single argv string is instead folded into the stdin task prompt (like the Codex runner), which
|
|
148
|
+
* has no size ceiling. Pure so the branch is unit-testable without spawning the CLI.
|
|
149
|
+
*/
|
|
150
|
+
export function carryClaudeSystemPrompt(systemPrompt, userPrompt) {
|
|
151
|
+
if (Buffer.byteLength(systemPrompt, 'utf8') <= MAX_ARGV_STRING_BYTES) {
|
|
152
|
+
return {
|
|
153
|
+
appendArgs: ['--append-system-prompt', systemPrompt],
|
|
154
|
+
prompt: userPrompt,
|
|
155
|
+
folded: false,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return { appendArgs: [], prompt: foldSystemPrompt(systemPrompt, userPrompt), folded: true };
|
|
159
|
+
}
|
|
125
160
|
// ---------------------------------------------------------------------------
|
|
126
161
|
// Claude Code
|
|
127
162
|
// ---------------------------------------------------------------------------
|
|
@@ -161,18 +196,32 @@ export async function runClaudeCode(opts) {
|
|
|
161
196
|
const stats = { toolCalls: 0, assistantChars: 0 };
|
|
162
197
|
let summary = '';
|
|
163
198
|
let usage;
|
|
199
|
+
// Decide how the composed system prompt is carried up front, so the telemetry seed below
|
|
200
|
+
// reflects what actually reaches the model: a small prompt rides `--append-system-prompt`
|
|
201
|
+
// (a real system turn), while an argv-overflowing prompt is folded into the first user turn
|
|
202
|
+
// — in which case NO system turn of ours is sent (the `E2BIG` fallback).
|
|
203
|
+
const { appendArgs, prompt, folded } = carryClaudeSystemPrompt(opts.systemPrompt, opts.userPrompt);
|
|
204
|
+
if (folded) {
|
|
205
|
+
opts.log?.warn('system prompt exceeds argv limit; folding into the task prompt', {
|
|
206
|
+
bytes: Buffer.byteLength(opts.systemPrompt, 'utf8'),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
164
209
|
// Reconstruct the full per-call request/response bodies for telemetry from the
|
|
165
210
|
// stream. `--output-format stream-json --verbose` emits each turn as a near-verbatim
|
|
166
211
|
// Anthropic Messages envelope, so `assistant` events carry the complete response
|
|
167
212
|
// (text + tool_use blocks + usage), and `user` events carry the tool_result blocks
|
|
168
|
-
// fed back — together the growing prompt transcript. We seed it with the
|
|
169
|
-
//
|
|
170
|
-
//
|
|
213
|
+
// fed back — together the growing prompt transcript. We seed it with the inputs the
|
|
214
|
+
// harness supplies (they never appear in the stream): the system + first user message
|
|
215
|
+
// when the prompt rides argv, or a single folded user turn when it doesn't — so the
|
|
216
|
+
// reconstruction never shows a system turn that was never sent. Bodies are
|
|
217
|
+
// credential-scrubbed (they can echo the leased token).
|
|
171
218
|
const secrets = opts.subscriptionToken ? secretsToRedact(opts.subscriptionToken) : [];
|
|
172
|
-
const messages =
|
|
173
|
-
{ role: '
|
|
174
|
-
|
|
175
|
-
|
|
219
|
+
const messages = folded
|
|
220
|
+
? [{ role: 'user', content: prompt }]
|
|
221
|
+
: [
|
|
222
|
+
{ role: 'system', content: opts.systemPrompt },
|
|
223
|
+
{ role: 'user', content: opts.userPrompt },
|
|
224
|
+
];
|
|
176
225
|
const calls = [];
|
|
177
226
|
const onEvent = (event) => {
|
|
178
227
|
const type = event.type;
|
|
@@ -287,10 +336,9 @@ export async function runClaudeCode(opts) {
|
|
|
287
336
|
'bypassPermissions',
|
|
288
337
|
'--model',
|
|
289
338
|
opts.model,
|
|
290
|
-
|
|
291
|
-
opts.systemPrompt,
|
|
339
|
+
...appendArgs,
|
|
292
340
|
],
|
|
293
|
-
},
|
|
341
|
+
}, prompt, opts, env, opts.subscriptionToken ? secretsToRedact(opts.subscriptionToken) : [], onEvent);
|
|
294
342
|
attributeCumulativeUsage(calls, usage);
|
|
295
343
|
return {
|
|
296
344
|
summary,
|
|
@@ -422,10 +470,9 @@ export async function runCodex(opts) {
|
|
|
422
470
|
await writeFile(join(codexHome, 'config.toml'), 'cli_auth_credentials_store = "file"\n', 'utf8');
|
|
423
471
|
}
|
|
424
472
|
// Codex has no system-prompt flag, so fold the composed role + best-practice
|
|
425
|
-
// context into the prompt itself (Claude Code instead rides --append-system-prompt
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
: opts.userPrompt;
|
|
473
|
+
// context into the prompt itself (Claude Code instead rides --append-system-prompt,
|
|
474
|
+
// falling back to this same fold when the prompt overflows argv).
|
|
475
|
+
const prompt = foldSystemPrompt(opts.systemPrompt, opts.userPrompt);
|
|
429
476
|
// Codex's `exec --json` is far thinner than Claude Code's stream: it surfaces only
|
|
430
477
|
// flat assistant text and (on `token_count` events) the per-turn `last_token_usage`
|
|
431
478
|
// plus a cumulative total. It never exposes the request transcript or structured
|