@cat-factory/executor-harness 1.134.0 → 1.137.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.
@@ -308,6 +308,90 @@ export async function checkoutHasBlueprints(dir: string, multiRepo: boolean): Pr
308
308
  return checks.some(Boolean)
309
309
  }
310
310
 
311
+ /**
312
+ * Run one pass on a SUBSCRIPTION harness (Claude Code / Codex): the leased-credential path, which
313
+ * shares only the checkout preparation with the Pi one.
314
+ *
315
+ * Split out of {@link runAgentInWorkspace} for its cyclomatic budget. It is also the honest seam:
316
+ * everything here is a decision about what the vendor's own CLI is handed, while everything left
317
+ * behind is about the proxy-backed Pi run.
318
+ */
319
+ async function runSubscriptionInWorkspace(
320
+ harness: 'claude-code' | 'codex',
321
+ spec: AgentRunSpec,
322
+ opts: RunOptions,
323
+ prepared: {
324
+ contextFiles: ContextFileInfo[]
325
+ imageGuidance: string
326
+ workspaceProbe: WorkspaceProbe
327
+ },
328
+ ): Promise<PiRunOutcome> {
329
+ const { contextFiles, imageGuidance, workspaceProbe } = prepared
330
+ // Ambient (native) mode authenticates with the developer's own CLI login, so no
331
+ // leased token is required; otherwise the leased subscription token is mandatory.
332
+ if (!spec.ambientAuth && !spec.subscriptionToken) {
333
+ throw new Error(`The ${harness} harness requires a subscription token`)
334
+ }
335
+ const subOutcome = await runSubscriptionHarness(harness, {
336
+ cwd: spec.dir,
337
+ model: spec.model,
338
+ systemPrompt: `${subscriptionSystemPrompt(spec.systemPrompt, contextFiles)}${imageGuidance}`,
339
+ userPrompt: spec.userPrompt,
340
+ ...(spec.subscriptionToken ? { subscriptionToken: spec.subscriptionToken } : {}),
341
+ subscriptionBaseUrl: spec.subscriptionBaseUrl,
342
+ ...(spec.ambientAuth ? { ambientAuth: true } : {}),
343
+ ...(spec.skills?.length ? { skills: spec.skills } : {}),
344
+ ...(spec.mcpServers?.length ? { mcpServers: spec.mcpServers } : {}),
345
+ // Codex's own image tool. Passed for both subscription harnesses because the option lives on
346
+ // the shared run options; `runClaudeCode` ignores it, since claude-code has no such tool and
347
+ // (unlike an MCP server) there is nothing to report as unservable — the backend never
348
+ // resolves a codex-served generator onto a claude-code step, because admission refuses it.
349
+ ...(spec.generateImages ? { generateImages: true } : {}),
350
+ // `spec.webSearchProxy` is deliberately NOT forwarded. It states whether OUR PROXY serves web
351
+ // research for this run's account, which is what Pi's tools ride and what they would fail
352
+ // without; neither subscription CLI touches that proxy. Claude Code's `WebSearch`/`WebFetch`
353
+ // are served by the vendor the leased subscription already pays and are declared
354
+ // unconditionally (see `CLAUDE_TOOL_SET`), and Codex's surface is per-tool config rather than
355
+ // a list. Passing the proxy's availability here would withhold working tools on the strength
356
+ // of an unrelated deployment's wiring.
357
+ ...(opts.agentEnv ? { extraEnv: opts.agentEnv } : {}),
358
+ signal: opts.signal,
359
+ // Run the SAME no-progress guard Pi gets (previously claude-code/codex had none): env
360
+ // defaults merged loosen-only with the kind's tuning + the backend's complexity-scaled
361
+ // no-edit allowance, so a claude-code run that stops making progress is killed early
362
+ // instead of burning the full wall-clock budget. The claude runner consumes it; codex
363
+ // ignores it for now (its stream isn't wired to the guard).
364
+ guardLimits: mergeGuardLimits(progressGuardLimitsFromEnv(), spec.guardLimits),
365
+ expectsEdits: spec.expectsEdits ?? true,
366
+ // What the guard's no-edit bound actually decides on (see `buildWorkspaceProbe`).
367
+ workspaceProbe,
368
+ onActivity: opts.onActivity,
369
+ onProgress: opts.onProgress,
370
+ // The run's tool-call trajectory, the same hook the Pi path feeds — so a subscription run
371
+ // and a proxied one produce the same evidence rather than one of them producing none.
372
+ onSpan: opts.onSpan,
373
+ // The tool-silence window (stuck-run audit F13), opened by whichever CLI actually runs.
374
+ // Wired for BOTH subscription harnesses: each reports tool activity on its own stream, so
375
+ // each can beat the window it opens.
376
+ beginToolWindow: opts.beginToolWindow,
377
+ // Per-slice review capture, so a parallel review's finished slices are persisted as they
378
+ // land rather than only in the terminal output. Only the subscription runners fan work out
379
+ // across subagents, so this is the only path that can produce it.
380
+ onSliceReviews: opts.onSliceReviews,
381
+ // What the CLI reported about the tool servers it loaded. Wired for BOTH subscription
382
+ // harnesses even though only claude-code's stream carries the report today: the hook is a
383
+ // pass-through, and a codex run that never calls it leaves the backend's record honestly
384
+ // absent rather than claiming every server it wired failed to start.
385
+ onToolServers: opts.onToolServers,
386
+ // Stream this run's per-call telemetry to the job's live drain. The subscription
387
+ // harnesses are the only producers of `callMetrics` (Pi's calls are metered by the LLM
388
+ // proxy as they happen), so this is the only path that needs the hook.
389
+ onCallMetric: opts.onCallMetric,
390
+ ...(opts.log ? { log: opts.log } : {}),
391
+ })
392
+ return withEffortReport(spec.dir, subOutcome)
393
+ }
394
+
311
395
  /**
312
396
  * Write Pi's global agent context (`~/.pi/agent/AGENTS.md`) + provider config,
313
397
  * then run Pi once in `spec.dir` and return its summary/stats/stderr. The context
@@ -362,67 +446,15 @@ export async function runAgentInWorkspace(
362
446
  // the half that matters there anyway.
363
447
  const workspaceProbe = await buildWorkspaceProbe(spec, opts.signal)
364
448
 
365
- // Subscription harnesses (Claude Code / Codex) authenticate with the leased
366
- // token and talk direct to the vendor no proxy config, no AGENTS.md. The
367
- // system prompt is passed straight to the CLI; everything around this (clone,
368
- // push, watchdogs) is unchanged.
449
+ // Subscription harnesses (Claude Code / Codex) authenticate with the leased token and talk
450
+ // direct to the vendor: no proxy config, no AGENTS.md. The system prompt is passed straight to
451
+ // the CLI; everything around this (clone, push, watchdogs) is unchanged.
369
452
  if (spec.harness === 'claude-code' || spec.harness === 'codex') {
370
- // Ambient (native) mode authenticates with the developer's own CLI login, so no
371
- // leased token is required; otherwise the leased subscription token is mandatory.
372
- if (!spec.ambientAuth && !spec.subscriptionToken) {
373
- throw new Error(`The ${spec.harness} harness requires a subscription token`)
374
- }
375
- const subOutcome = await runSubscriptionHarness(spec.harness, {
376
- cwd: spec.dir,
377
- model: spec.model,
378
- systemPrompt: `${subscriptionSystemPrompt(spec.systemPrompt, contextFiles)}${imageGuidance}`,
379
- userPrompt: spec.userPrompt,
380
- ...(spec.subscriptionToken ? { subscriptionToken: spec.subscriptionToken } : {}),
381
- subscriptionBaseUrl: spec.subscriptionBaseUrl,
382
- ...(spec.ambientAuth ? { ambientAuth: true } : {}),
383
- ...(spec.skills?.length ? { skills: spec.skills } : {}),
384
- ...(spec.mcpServers?.length ? { mcpServers: spec.mcpServers } : {}),
385
- // Codex's own image tool. Passed for both subscription harnesses because the option lives on
386
- // the shared run options; `runClaudeCode` ignores it, since claude-code has no such tool and
387
- // (unlike an MCP server) there is nothing to report as unservable — the backend never
388
- // resolves a codex-served generator onto a claude-code step, because admission refuses it.
389
- ...(spec.generateImages ? { generateImages: true } : {}),
390
- ...(opts.agentEnv ? { extraEnv: opts.agentEnv } : {}),
391
- signal: opts.signal,
392
- // Run the SAME no-progress guard Pi gets (previously claude-code/codex had none): env
393
- // defaults merged loosen-only with the kind's tuning + the backend's complexity-scaled
394
- // no-edit allowance, so a claude-code run that stops making progress is killed early
395
- // instead of burning the full wall-clock budget. The claude runner consumes it; codex
396
- // ignores it for now (its stream isn't wired to the guard).
397
- guardLimits: mergeGuardLimits(progressGuardLimitsFromEnv(), spec.guardLimits),
398
- expectsEdits: spec.expectsEdits ?? true,
399
- // What the guard's no-edit bound actually decides on (see `buildWorkspaceProbe`).
453
+ return await runSubscriptionInWorkspace(spec.harness, spec, opts, {
454
+ contextFiles,
455
+ imageGuidance,
400
456
  workspaceProbe,
401
- onActivity: opts.onActivity,
402
- onProgress: opts.onProgress,
403
- // The run's tool-call trajectory, the same hook the Pi path feeds — so a subscription run
404
- // and a proxied one produce the same evidence rather than one of them producing none.
405
- onSpan: opts.onSpan,
406
- // The tool-silence window (stuck-run audit F13), opened by whichever CLI actually runs.
407
- // Wired for BOTH subscription harnesses: each reports tool activity on its own stream, so
408
- // each can beat the window it opens.
409
- beginToolWindow: opts.beginToolWindow,
410
- // Per-slice review capture, so a parallel review's finished slices are persisted as they
411
- // land rather than only in the terminal output. Only the subscription runners fan work out
412
- // across subagents, so this is the only path that can produce it.
413
- onSliceReviews: opts.onSliceReviews,
414
- // What the CLI reported about the tool servers it loaded. Wired for BOTH subscription
415
- // harnesses even though only claude-code's stream carries the report today: the hook is a
416
- // pass-through, and a codex run that never calls it leaves the backend's record honestly
417
- // absent rather than claiming every server it wired failed to start.
418
- onToolServers: opts.onToolServers,
419
- // Stream this run's per-call telemetry to the job's live drain. The subscription
420
- // harnesses are the only producers of `callMetrics` (Pi's calls are metered by the LLM
421
- // proxy as they happen), so this is the only path that needs the hook.
422
- onCallMetric: opts.onCallMetric,
423
- ...(opts.log ? { log: opts.log } : {}),
424
457
  })
425
- return withEffortReport(spec.dir, subOutcome)
426
458
  }
427
459
  if (!spec.proxyBaseUrl || !spec.sessionToken) {
428
460
  throw new Error('The Pi harness requires proxyBaseUrl and sessionToken')