@cat-factory/executor-harness 1.78.0 → 1.82.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.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/dist/agent-capabilities.d.ts +130 -0
  3. package/dist/agent-runner.d.ts +114 -0
  4. package/dist/agent-runner.js +15 -1
  5. package/dist/agent-shared.d.ts +18 -0
  6. package/dist/agent.d.ts +66 -0
  7. package/dist/bootstrap-mode.d.ts +20 -0
  8. package/dist/captured-command.d.ts +58 -0
  9. package/dist/claude-call-aggregator.d.ts +164 -0
  10. package/dist/claude-call-aggregator.js +123 -17
  11. package/dist/claude-stream.d.ts +56 -0
  12. package/dist/claude-stream.js +23 -0
  13. package/dist/coding-agent.d.ts +263 -0
  14. package/dist/dependency-install.d.ts +111 -0
  15. package/dist/effort.d.ts +19 -0
  16. package/dist/embed.d.ts +4 -0
  17. package/dist/failure.d.ts +42 -0
  18. package/dist/follow-ups.d.ts +28 -0
  19. package/dist/frontend-infra.d.ts +25 -0
  20. package/dist/fs-utils.d.ts +2 -0
  21. package/dist/git.d.ts +394 -0
  22. package/dist/host-markdown.d.ts +28 -0
  23. package/dist/inline.d.ts +10 -0
  24. package/dist/job.d.ts +666 -0
  25. package/dist/logger.d.ts +16 -0
  26. package/dist/onboarding-preseed.d.ts +24 -0
  27. package/dist/package-registries.d.ts +32 -0
  28. package/dist/pi-workspace.d.ts +194 -0
  29. package/dist/pi-workspace.js +4 -0
  30. package/dist/pi.d.ts +475 -0
  31. package/dist/pr-description.d.ts +85 -0
  32. package/dist/pr-template.d.ts +101 -0
  33. package/dist/process-exit.d.ts +7 -0
  34. package/dist/process.d.ts +19 -0
  35. package/dist/progress-guard.d.ts +88 -0
  36. package/dist/progress.d.ts +87 -0
  37. package/dist/redact.d.ts +31 -0
  38. package/dist/reproduction-proof.d.ts +224 -0
  39. package/dist/runner.d.ts +282 -0
  40. package/dist/runner.js +3 -0
  41. package/dist/server.d.ts +3 -0
  42. package/dist/structured-output.d.ts +75 -0
  43. package/dist/subagents.d.ts +88 -0
  44. package/dist/subagents.js +74 -4
  45. package/dist/transcript-retention.d.ts +21 -0
  46. package/dist/validation-checks.d.ts +159 -0
  47. package/dist/vcs-api.d.ts +73 -0
  48. package/dist/version.d.ts +2 -0
  49. package/package.json +9 -5
  50. package/src/agent-runner.ts +21 -2
  51. package/src/claude-call-aggregator.ts +181 -32
  52. package/src/claude-stream.ts +21 -0
  53. package/src/pi-workspace.ts +4 -0
  54. package/src/runner.ts +24 -0
  55. package/src/subagents.ts +57 -3
package/dist/job.d.ts ADDED
@@ -0,0 +1,666 @@
1
+ import type { HarnessCallMetric, PiRunStats } from './pi.js';
2
+ import type { HarnessKind } from './pi-workspace.js';
3
+ import type { FailureCause } from './failure.js';
4
+ import type { EffortReport } from './effort.js';
5
+ import { type ValidationChecksSpec, type ValidationReport } from './validation-checks.js';
6
+ import { type ReproductionReport, type ReproductionSpec } from './reproduction-proof.js';
7
+ import { type DependencyInstallSpec } from './dependency-install.js';
8
+ import { type McpServerSpec, type SkillResourceSpec, type SkillSpec } from './agent-capabilities.js';
9
+ export type { McpServerSpec, SkillResourceSpec, SkillSpec };
10
+ /**
11
+ * Per-job auth fields, shared across every job shape. The Pi harness carries the
12
+ * proxy base URL + a model-locked session token; the subscription harnesses
13
+ * (Claude Code / Codex) carry a leased subscription token instead and talk direct
14
+ * to the vendor. `harness` selects which; absent ⇒ Pi.
15
+ */
16
+ export interface HarnessAuthFields {
17
+ harness?: HarnessKind;
18
+ /** Worker LLM proxy base URL, including /v1 (Pi harness only). */
19
+ proxyBaseUrl?: string;
20
+ /**
21
+ * The backend declaring that it serves the phase-tagged completions route
22
+ * (`${proxyBaseUrl}/phase/<phase>/chat/completions`), so this run may attribute each model
23
+ * call to the phase that spent it (`docs/initiatives/token-burn-instrumentation.md`). The
24
+ * same shape as {@link AgentJob.webSearch}: the backend states what IT serves, and the
25
+ * harness points Pi accordingly.
26
+ *
27
+ * Not a capability handshake — the harness never asks and never adapts to an answer. It
28
+ * exists because the harness image and the backend are only a matched set on the Cloudflare
29
+ * deployment: a runner pool pins its own image and `LOCAL_HARNESS_IMAGE` overrides the
30
+ * recommended pin, so an image ahead of its backend would otherwise 404 every model call.
31
+ * Absent ⇒ the plain path, and the run's calls are recorded as unattributed.
32
+ */
33
+ proxyPhasePath?: boolean;
34
+ /** Signed, model-locked proxy session token (Pi harness only). */
35
+ sessionToken?: string;
36
+ /** Leased subscription credential (Claude Code OAuth token / Codex auth.json). */
37
+ subscriptionToken?: string;
38
+ /**
39
+ * Anthropic-compatible base URL for a non-Anthropic Claude-Code vendor (GLM via
40
+ * Z.ai, Kimi via Moonshot). Present ⇒ the claude-code runner points
41
+ * ANTHROPIC_BASE_URL there with ANTHROPIC_AUTH_TOKEN; absent ⇒ Anthropic itself
42
+ * (CLAUDE_CODE_OAUTH_TOKEN against api.anthropic.com).
43
+ */
44
+ subscriptionBaseUrl?: string;
45
+ /**
46
+ * Native local execution: the `claude-code` / `codex` CLI runs with the developer's
47
+ * OWN ambient login (`~/.claude` / `~/.codex`) instead of a leased subscription token.
48
+ * Set only by the local native transport; when true `subscriptionToken` is not required.
49
+ */
50
+ ambientAuth?: boolean;
51
+ }
52
+ export interface RepoSpec {
53
+ owner: string;
54
+ name: string;
55
+ baseBranch: string;
56
+ cloneUrl: string;
57
+ /**
58
+ * The VCS provider the repo lives on, when the dispatcher set it. Selects GitHub-PR vs
59
+ * GitLab-MR for the "open the PR" call AUTHORITATIVELY (rather than guessing from the
60
+ * clone URL host, which can't recognise an arbitrarily-named self-managed GitLab). Absent
61
+ * ⇒ inferred from the clone URL.
62
+ */
63
+ provider?: 'github' | 'gitlab';
64
+ /**
65
+ * For a monorepo service, the subdirectory (relative to the repo root, e.g.
66
+ * `packages/api`) the agent should run within. Sanitised on parse to a safe
67
+ * relative path so it can never escape the checkout. Absent ⇒ run at the repo root.
68
+ */
69
+ serviceDirectory?: string;
70
+ }
71
+ export interface PrSpec {
72
+ title: string;
73
+ body: string;
74
+ }
75
+ /**
76
+ * A connected service's repo to check out as a SIBLING alongside the primary during a
77
+ * multi-repo coding run (service-connections phase 3). The agent clones every peer repo
78
+ * into a sibling directory under the workspace root, makes the coherent cross-service
79
+ * change, and the harness opens ONE PR per peer repo it actually changed. The clone URL is
80
+ * host-allowlisted exactly like the primary `repo.cloneUrl`.
81
+ */
82
+ export interface PeerRepoSpec {
83
+ repo: RepoSpec;
84
+ /** The involved service frame this repo resolved from, echoed back on the peer PR. */
85
+ frameId?: string;
86
+ /**
87
+ * The work branch to create off the peer's base and push (the shared `cat-factory/<block>`).
88
+ * Present for a COING fan-out (coder / ci-fixer). Absent for a READ-ONLY explore fan-out
89
+ * (the bug-investigator), which only clones the peer to read it and never pushes.
90
+ */
91
+ newBranch?: string;
92
+ /**
93
+ * The EXISTING branch to check the peer out at for a READ-ONLY explore fan-out (the `merger`
94
+ * scoring the combined diff clones each peer at its PR branch so the diff sees the PR change).
95
+ * Absent ⇒ the peer is cloned at its repo default branch (the bug-investigator). Ignored on the
96
+ * coding fan-out, which creates `newBranch` instead.
97
+ */
98
+ cloneBranch?: string;
99
+ /** Open a PR/MR in this peer when set AND the run changed the peer (skipped for a clean repo). */
100
+ pr?: PrSpec;
101
+ /** Per-repo GitHub token; defaults to the job's `ghToken` (one installation per workspace today). */
102
+ ghToken?: string;
103
+ }
104
+ /**
105
+ * A repository checked out READ-ONLY as a sibling alongside the primary during a
106
+ * document-authoring coding run — the doc-writer reads it (to reuse existing solutions as a
107
+ * reference) but the harness never creates a branch, commits, or opens a PR for it. Deliberately
108
+ * carries NO branch/PR fields (unlike {@link PeerRepoSpec}), so it is structurally impossible to
109
+ * push: the read-only guarantee is enforced by the shape itself, by cloning at the repo's own
110
+ * base branch with no work branch, and by skipping the leg in the push phase. The clone URL is
111
+ * host-allowlisted exactly like the primary `repo.cloneUrl`.
112
+ */
113
+ export interface ReferenceRepoSpec {
114
+ repo: RepoSpec;
115
+ /** Per-repo GitHub token; defaults to the job's `ghToken` (one installation per workspace today). */
116
+ ghToken?: string;
117
+ }
118
+ /** Hosts the harness is willing to send the installation token to. */
119
+ export declare function allowedGithubHosts(env?: NodeJS.ProcessEnv): Set<string>;
120
+ /** One private-registry entry: rendered into `~/.npmrc` before the agent runs. */
121
+ export interface PackageRegistrySpec {
122
+ ecosystem: 'npm';
123
+ /** Registry host, e.g. `registry.npmjs.org` — allowlisted, never a full URL. */
124
+ host: string;
125
+ /** npm scopes (`@org`) routed to this registry; EMPTY ⇒ authenticate the host only. */
126
+ scopes: string[];
127
+ token: string;
128
+ }
129
+ /** npm registry hosts the harness is willing to send a registry token to. */
130
+ export declare function allowedNpmRegistryHosts(env?: NodeJS.ProcessEnv): Set<string>;
131
+ /** Validate the optional `packageRegistries` list (see {@link PackageRegistrySpec}). */
132
+ export declare function parsePackageRegistries(value: unknown, env?: NodeJS.ProcessEnv): PackageRegistrySpec[];
133
+ /**
134
+ * One sensitive test credential the tester receives: an env-var name + its (secret) value.
135
+ * The backend seals these at rest and decrypts them at dispatch; the harness injects each as an
136
+ * environment variable the tester's shell can read (out of band — the value is NEVER in the
137
+ * prompt/telemetry). See {@link parseTestSecrets}.
138
+ */
139
+ export interface TestSecretSpec {
140
+ key: string;
141
+ value: string;
142
+ }
143
+ /**
144
+ * Validate the optional tester `testSecrets` list — `{ key, value }` env pairs the harness
145
+ * injects into the run environment. Keys must be valid env-var names; toolchain-critical /
146
+ * reserved names ({@link isReservedEnvName}) and duplicates are dropped so a drifted body can't
147
+ * clobber PATH/NODE_OPTIONS/etc. Absent ⇒ no secrets injected.
148
+ */
149
+ export declare function parseTestSecrets(value: unknown): TestSecretSpec[];
150
+ /** The new repository a repo-bootstrap run force-pushes its fresh history to. */
151
+ export interface BootstrapTargetSpec {
152
+ owner: string;
153
+ name: string;
154
+ cloneUrl: string;
155
+ defaultBranch: string;
156
+ }
157
+ /** How the generic agent runs: read-only exploration, or edit-and-push coding. */
158
+ export type AgentMode = 'explore' | 'coding' | 'preview';
159
+ /**
160
+ * Explore mode: how a container agent stands its dependencies up before the run (the
161
+ * tester). Two shapes, discriminated by `kind` (absent ⇒ `service`, the backend tester):
162
+ * - `service` — a backend service under test: `local` brings the service's
163
+ * docker-compose infra up on localhost for the run; `ephemeral` is a no-op stand-up
164
+ * (the env is already deployed and its URL reaches the agent through its prompt).
165
+ * - `frontend` — a frontend app under test (the self-contained UI-test flow): build the
166
+ * frontend, stand WireMock up for its mocked upstreams, serve the built app, and point
167
+ * the (`tester-ui`) agent at it. Everything runs as localhost PROCESSES in the one
168
+ * container (no Docker-in-Docker), so it works on Cloudflare + Apple `container` too.
169
+ * Absent ⇒ the harness manages no infra.
170
+ */
171
+ export type AgentInfraSpec = ServiceInfraSpec | FrontendInfraSpec;
172
+ /** Backend-service tester infra (docker-compose local, or a deployed ephemeral env). */
173
+ export interface ServiceInfraSpec {
174
+ /** Discriminant. Absent ⇒ `service` (the backend tester). */
175
+ kind?: 'service';
176
+ /** `local` stands infra up via docker-compose; `ephemeral` tests a deployed env. */
177
+ environment: 'local' | 'ephemeral';
178
+ /** Local mode: the service declared no infra dependencies (spin nothing up). */
179
+ noInfraDependencies?: boolean;
180
+ /** Local mode: repo-relative docker-compose path to stand the dependencies up. */
181
+ composePath?: string;
182
+ /** Ephemeral mode: the provisioned environment URL (echoed for context only). */
183
+ environmentUrl?: string;
184
+ /**
185
+ * The connected services "directly involved" in this task that have a LIVE ephemeral env this
186
+ * run (service title → URL), so a cross-service integration test can reach a peer's real
187
+ * environment. Echoed for context only (surfaced in the agent's prompt); the harness stands
188
+ * nothing up for it. Absent when no involved peer is live.
189
+ */
190
+ peerEnvironments?: Record<string, string>;
191
+ }
192
+ /**
193
+ * Frontend UI-test infra (the self-contained `tester-ui` flow). The backend has already
194
+ * resolved every backend upstream to a concrete URL — the bound service's live ephemeral
195
+ * env URL for the service under test, `http://localhost:<wiremockPort>` for every mocked
196
+ * upstream — and handed them here as {@link env}. The harness installs, builds (injecting
197
+ * `env` at build time, or writing a `window.env` shim for runtime injection), stands
198
+ * WireMock up on {@link wiremockPort} seeded from {@link wiremockMappingsPath}, serves the
199
+ * built app on {@link servePort}, health-checks it, and tells the agent the serve URL.
200
+ */
201
+ export interface FrontendInfraSpec {
202
+ kind: 'frontend';
203
+ /**
204
+ * The frontend app's subdirectory within the checkout (a monorepo frontend). Absent ⇒ the
205
+ * checkout root. When set, install/build/serve run there and `outputDir`/`wiremockMappingsPath`
206
+ * are resolved relative to it.
207
+ */
208
+ directory?: string;
209
+ /** Package manager for install/build. Default `pnpm`. */
210
+ packageManager?: 'pnpm' | 'npm' | 'yarn';
211
+ /** Explicit install command, overriding the one derived from `packageManager`. */
212
+ install?: string;
213
+ /** package.json script that produces the built app. Default `build`. */
214
+ buildScript?: string;
215
+ /** The build's output directory, served in `static` mode. Default `dist`. */
216
+ outputDir?: string;
217
+ /** How the built app is served: static server of `outputDir`, or run `serveScript`. */
218
+ serveMode?: 'static' | 'command';
219
+ /** package.json script to run when `serveMode: 'command'` (e.g. `preview`). */
220
+ serveScript?: string;
221
+ /** The port the served app listens on inside the container. Default 4173. */
222
+ servePort?: number;
223
+ /** Build-time env vars vs a runtime `window.env` shim. Default `build`. */
224
+ envInjection?: 'build' | 'runtime';
225
+ /** Resolved backend upstream env vars (name → URL) to inject. Empty names filtered out. */
226
+ env?: Record<string, string>;
227
+ /** The WireMock mappings directory in the FE repo. Default `mocks/`. */
228
+ wiremockMappingsPath?: string;
229
+ /** The port WireMock listens on inside the container. Default 8089. */
230
+ wiremockPort?: number;
231
+ }
232
+ /**
233
+ * Coding mode (repo bootstrap): the divergent push of a bootstrap run. Instead of pushing
234
+ * a work branch on the cloned repo, the agent's result is force-pushed as a fresh
235
+ * single-commit history to a SEPARATE, pre-created target repository's default branch.
236
+ * Clone-and-adapt: `job.repo` is the reference architecture to clone + adapt, `target` is
237
+ * the new repo. From-scratch (`fromScratch`): start from an empty directory (the agent
238
+ * scaffolds), `job.repo` is unused as a clone source. Absent ⇒ the ordinary coding flow.
239
+ */
240
+ export interface AgentBootstrapSpec {
241
+ /** The new repository the bootstrapped contents are pushed to (the push target). */
242
+ target: BootstrapTargetSpec;
243
+ /** Scaffold from an empty directory instead of cloning `job.repo` (no reference). */
244
+ fromScratch?: boolean;
245
+ }
246
+ /**
247
+ * A linked-context file the backend prepared (requirements / RFC / PRD / tracker issue)
248
+ * for the harness to materialise under CONTEXT_DIR in the checkout, so the agent can read
249
+ * it on demand. The harness can't reach Jira/GitHub itself, so all such context is fetched
250
+ * and shipped here up front. `path` is sanitised to a safe basename on parse.
251
+ */
252
+ export interface ContextFileSpec {
253
+ path: string;
254
+ title: string;
255
+ url: string;
256
+ content: string;
257
+ }
258
+ /** How an explore agent's reply is consumed. */
259
+ export interface AgentOutputSpec {
260
+ /** `prose` keeps the reply text; `structured` parses (and optionally repairs) it to JSON. */
261
+ kind: 'prose' | 'structured';
262
+ /** Compact shape description fed to the one-shot structured-output repair call. */
263
+ shapeHint?: string;
264
+ /** Whether to attempt the one-shot repair on a malformed reply (structured only). */
265
+ repair?: boolean;
266
+ /**
267
+ * Fail the run LOUDLY when the FINAL answer is unusable (cut off at the output ceiling,
268
+ * or empty) instead of repairing it — opt-in for kinds whose JSON deliverable is handed
269
+ * onward to be parsed/committed (e.g. the spec-writer). Absent ⇒ off.
270
+ */
271
+ failOnUnusableFinal?: boolean;
272
+ }
273
+ /**
274
+ * The generic agent job. `mode` selects the flow; the remaining fields are the union
275
+ * the flows need. Explore: clone `branch`, run read-only, return prose (or a parsed
276
+ * `custom` JSON object when `output.kind==='structured'`). Coding: clone `branch` (or
277
+ * resume `newBranch`), run, commit + push to `pushBranch`, and open `pr` when one is set
278
+ * and the run produced changes. Preview (local/node only): clone `branch`, build + serve
279
+ * the frontend (`infra.kind==='frontend'`) with its other upstreams mocked and KEEP IT
280
+ * RUNNING — no agent runs and the serve is deliberately not torn down when the job returns
281
+ * (see {@link AgentResult.preview}).
282
+ */
283
+ /**
284
+ * Coding mode (Ralph loop): the programmatic completion criterion. After the coding agent
285
+ * commits + pushes, the harness runs {@link command} in the checkout and reports its exit
286
+ * code back on {@link AgentResult.ralphVerdict} — exit 0 means the loop is done. This is the
287
+ * whole point of a Ralph loop's exit condition being a REAL check: the harness runs it, not
288
+ * the model. The command runs only inside the sandboxed run container (same trust boundary
289
+ * as the coding agent). Absent for every non-`ralph` coding run.
290
+ */
291
+ export interface ValidationSpec {
292
+ /** The shell command the harness runs against the checkout (exit 0 = the criterion is met). */
293
+ command: string;
294
+ /** Repo-relative progress-log path the agent maintains (informational; the harness doesn't write it). */
295
+ progressPath?: string;
296
+ /** 1-based iteration number, echoed back on the verdict for the engine's attempt log. */
297
+ iteration?: number;
298
+ }
299
+ export interface AgentJob extends HarnessAuthFields {
300
+ jobId: string;
301
+ /**
302
+ * The backend run this job belongs to, bound onto the per-job logger beside `jobId` and used
303
+ * for NOTHING else. The container is the far side of the platform's longest seam: the backend
304
+ * knows a run as `executionId` and the harness knew it only as `jobId`, so a container log line
305
+ * could not be joined to the run that dispatched it except through the
306
+ * `${executionId}-${agentKind}` job-id naming convention. Optional because a body predating the
307
+ * field (or a hand-rolled acceptance fixture) must still run — an absent id costs correlation,
308
+ * never the job.
309
+ */
310
+ workspaceId?: string;
311
+ executionId?: string;
312
+ mode: AgentMode;
313
+ systemPrompt: string;
314
+ userPrompt: string;
315
+ model: string;
316
+ ghToken: string;
317
+ repo: RepoSpec;
318
+ /** The branch to clone (the backend resolves base/pr/work to a concrete name). */
319
+ branch: string;
320
+ githubApiBase?: string;
321
+ webToolsGuidance?: string;
322
+ webSearch?: boolean;
323
+ /** Full-history clone (needed to diff against / merge the base). Default shallow. */
324
+ full?: boolean;
325
+ /**
326
+ * Coding mode (conflict-resolver): merge `origin/<mergeBase>` into the cloned PR branch
327
+ * to surface the Git conflicts, run the agent to resolve them, then complete the merge
328
+ * commit and push back onto the SAME branch (no new branch / PR). Requires `full` so the
329
+ * merge base + `origin/<mergeBase>` are present. Absent ⇒ the ordinary coding flow.
330
+ */
331
+ mergeBase?: string;
332
+ /**
333
+ * Coding mode (repo bootstrap): force-push the agent's output as a fresh single-commit
334
+ * history to a separate, pre-created target repo (clone + adapt `repo`, or scaffold from
335
+ * scratch). Absent ⇒ the ordinary clone-edit-push-on-the-same-repo coding flow.
336
+ */
337
+ bootstrap?: AgentBootstrapSpec;
338
+ /** Explore mode: how to consume the reply. Absent ⇒ prose. */
339
+ output?: AgentOutputSpec;
340
+ /**
341
+ * Linked-context files to materialise under CONTEXT_DIR before the run (both modes).
342
+ * The agent reads them on demand; they are kept out of any commit. Absent ⇒ none.
343
+ */
344
+ contextFiles?: ContextFileSpec[];
345
+ /**
346
+ * Private package-registry auth (npm private orgs, GitHub Packages), rendered into
347
+ * `~/.npmrc` before the run so the checkout's installs — the agent's own and the
348
+ * frontend-infra stand-up's — resolve private dependencies. Hosts are hard-allowlisted
349
+ * (see {@link allowedNpmRegistryHosts}). Absent ⇒ any stale `~/.npmrc` from a prior
350
+ * job on a reused container is removed.
351
+ */
352
+ packageRegistries?: PackageRegistrySpec[];
353
+ /**
354
+ * The skills to make available for this run (see {@link SkillSpec}) — a `skill` step's picked
355
+ * skill and/or the playbooks the running agent kind declares. Materialised harness-aware before
356
+ * the run: natively into `CLAUDE_CONFIG_DIR/skills/<name>/` for claude-code, or
357
+ * `.cat-context/skill/<name>/<relPath>` for Pi/codex. Absent ⇒ no skills installed.
358
+ */
359
+ skills?: SkillSpec[];
360
+ /**
361
+ * Tool servers (MCP) to wire into the agent CLI for this run (see {@link McpServerSpec}). The
362
+ * backend has already dropped anything this harness cannot serve, so every entry here is
363
+ * expected to work. SECRET-BEARING (`env`/`headers` carry resolved credentials), so the config
364
+ * files written from it live outside the checkout and are never logged. Absent ⇒ the CLI's
365
+ * built-in tools only.
366
+ */
367
+ mcpServers?: McpServerSpec[];
368
+ /**
369
+ * Tester kinds only: sensitive test credentials injected into the run's ENVIRONMENT (out of
370
+ * band) as `{ key, value }` env pairs, so the tester's shell can read `$KEY` without the value
371
+ * ever appearing in the prompt or telemetry. Reserved/toolchain env names are dropped at parse.
372
+ * Absent ⇒ no secrets injected.
373
+ */
374
+ testSecrets?: TestSecretSpec[];
375
+ /**
376
+ * Explore mode: stand the service's dependencies up before the agent runs (the
377
+ * tester). Brings the docker-compose infra up on localhost for the duration of the
378
+ * run and tears it down afterward; a stand-up failure is non-fatal (surfaced to the
379
+ * agent as a note). The agent makes no commits regardless. Absent ⇒ no infra managed.
380
+ *
381
+ * Preview mode: REQUIRED and must be the `frontend` variant — it is the whole job (build
382
+ * + serve + WireMock, kept alive). No agent runs and, unlike the tester, the stand-up is
383
+ * NOT torn down when the job returns.
384
+ */
385
+ infra?: AgentInfraSpec;
386
+ /** Coding mode: a fresh branch to create off the clone before running (else work on `branch`). */
387
+ newBranch?: string;
388
+ /** Coding mode: branch the produced change is pushed to (defaults to `newBranch ?? branch`). */
389
+ pushBranch?: string;
390
+ /** Coding mode: commit message for any work the agent left uncommitted. */
391
+ commitMessage?: string;
392
+ /** Coding mode: open this PR when the run pushed changes. Absent ⇒ push only, no PR. */
393
+ pr?: PrSpec;
394
+ /**
395
+ * Coding mode (implementer): connected services' repos to clone as SIBLINGS for a MULTI-REPO
396
+ * change (service-connections phase 3). When present, the agent works with its cwd at the
397
+ * workspace ROOT (all repos are sibling checkouts under it), and the harness opens one PR per
398
+ * peer repo it actually changed — in addition to the primary. Absent ⇒ single-repo run.
399
+ */
400
+ peerRepos?: PeerRepoSpec[];
401
+ /**
402
+ * Coding mode (doc-writer): repositories to clone READ-ONLY as SIBLINGS for the agent to
403
+ * reference while it drafts the document. When present the agent works at the workspace ROOT
404
+ * (all checkouts are siblings under it); the harness clones each reference at its own base
405
+ * branch and NEVER creates a branch, commits, or opens a PR for it. Only the primary is
406
+ * pushed. Absent ⇒ single-repo run. Independent of {@link peerRepos} (those are writable).
407
+ */
408
+ referenceRepos?: ReferenceRepoSpec[];
409
+ /**
410
+ * Pre-existing branch names of the PRIMARY repo attached to the task as READ-ONLY reference
411
+ * points (the apriori-branches reference mode). After the primary checkout the harness fetches
412
+ * each into its `origin/<b>` tracking ref (best-effort per branch) so the agent can inspect it —
413
+ * `git log origin/<b>`, two-dot `git diff origin/<b>`, `git show origin/<b>:<path>` — but never
414
+ * commits to or pushes it (that guarantee lives in the prompt guidance, not a git constraint).
415
+ * Distinct from {@link referenceRepos}: those are separate sibling repos; these are branches of
416
+ * the same primary repo. Absent ⇒ none. Consumed by the coding + explore flows.
417
+ */
418
+ referenceBranches?: string[];
419
+ /**
420
+ * Explore mode (the `pr-reviewer`): the reviewed PR/MR number. Present ⇒ after the base
421
+ * checkout the harness fetches that PR's HEAD into `origin/pr-head` (best-effort) so the
422
+ * read-only reviewer can diff/read the PROPOSED code — files the PR adds are otherwise absent
423
+ * from the base checkout, and the agent has no git credential to fetch the head itself. The
424
+ * GitHub-vs-GitLab pull ref is chosen from `repo.provider` (host-inferred when absent). Absent
425
+ * ⇒ no head fetch (every non-review run). See {@link file://./git.ts} `fetchPullRequestHead`.
426
+ */
427
+ reviewPrNumber?: number;
428
+ /**
429
+ * Coding mode: whether a no-op run (nothing changed) is a failure. The implementer
430
+ * fails on a no-op; the in-place fixers (ci-fix / fix-tests) treat it as a non-fatal
431
+ * no-op. Default true.
432
+ */
433
+ noChangesIsError?: boolean;
434
+ /**
435
+ * Reuse a STABLE per-repo checkout (clean-sweep + fetch + switch branch) instead of a
436
+ * fresh clone into a throwaway temp dir. Set ONLY by the local warm-pool transport,
437
+ * whose containers are reused across runs; absent everywhere else, so every other
438
+ * runtime keeps the ephemeral fresh-clone behaviour. The explore + ordinary coding
439
+ * flows honour it; bootstrap (resets `.git`) and conflict-resolution (needs full
440
+ * multi-branch state) always run ephemeral regardless.
441
+ */
442
+ persistentCheckout?: boolean;
443
+ /**
444
+ * Coding mode (implementer): tail the Coder's follow-up sentinel file and stream the
445
+ * forward-looking items it surfaces (loose ends / side-tasks / questions) out on the job
446
+ * view, so the backend lifts them onto the run's step (the Follow-up companion). Set only
447
+ * for the `coder` dispatch when the companion is enabled. Absent ⇒ no follow-up streaming.
448
+ */
449
+ streamFollowUps?: boolean;
450
+ /**
451
+ * Per-job overrides for the anti-rabbithole progress guard, set by the backend per
452
+ * AGENT KIND (a read-heavy kind tolerates more web/exploration before it counts as a
453
+ * stall). Each knob is optional and falls back to the env / built-in default
454
+ * ({@link progressGuardLimitsFromEnv}); only the knobs present here override. These are
455
+ * loosen-only: `mergeGuardLimits` clamps each override up to the base, so a value
456
+ * tighter than the default is ignored and a legitimately-progressing run is never
457
+ * killed for a kind's normal working pattern. Absent ⇒ env/default for all knobs.
458
+ */
459
+ guardLimits?: GuardLimitsSpec;
460
+ /**
461
+ * Coding mode (Ralph loop): the programmatic completion command the harness runs after the
462
+ * agent commits + pushes. Present only for a `ralph` iteration. See {@link ValidationSpec}.
463
+ */
464
+ validation?: ValidationSpec;
465
+ /**
466
+ * Coding mode: the service's PRE-PR VALIDATION CHECKS — commands the harness runs against the
467
+ * checkout after the agent settles and BEFORE opening a PR, feeding a failure back to the agent
468
+ * until they pass or the budget is spent. Present only on a dispatch that opens a PR and whose
469
+ * service configured checks; absent ⇒ the run behaves exactly as before. Deliberately keyed off
470
+ * job DATA, not the agent kind. See {@link ValidationChecksSpec}.
471
+ */
472
+ validationChecks?: ValidationChecksSpec;
473
+ /**
474
+ * Coding mode: the run's BUGFIX REPRODUCTION PROOF — the declared reproduction command, the
475
+ * test file(s) that constitute it, and an optional setup command. When set, the harness runs
476
+ * that command against the pre-fix tree AND the tree the PR will open from, and reports both
477
+ * exit codes: only red-then-green is proof. Present only on a dispatch that opens a PR and
478
+ * whose run carries a reproduction declaration; absent ⇒ the run behaves exactly as before.
479
+ * Deliberately keyed off job DATA, not the agent kind. See
480
+ * `docs/initiatives/bugfix-reproduction-proof.md`.
481
+ */
482
+ reproduction?: ReproductionSpec;
483
+ /**
484
+ * DEPENDENCY PREPOPULATION: the service's install command, run against the checkout BEFORE the
485
+ * agent's first turn so it reads a tree whose dependencies are present rather than inferring
486
+ * them from a manifest. Applies to EVERY mode that gets a checkout (explore as well as coding)
487
+ * — unlike {@link validationChecks}, which is a pre-PR gate — because an agent reading or
488
+ * reviewing a tree needs its dependencies as much as one changing it.
489
+ *
490
+ * Best-effort: a failure becomes a note in the agent's prompt, never a failed job. Absent ⇒ the
491
+ * run behaves exactly as before. Deliberately keyed off job DATA, not the agent kind. See
492
+ * `docs/initiatives/agent-dependency-prepopulation.md`.
493
+ */
494
+ dependencyInstall?: DependencyInstallSpec;
495
+ }
496
+ /** Per-job, per-knob progress-guard overrides (see {@link AgentJob.guardLimits}). */
497
+ export interface GuardLimitsSpec {
498
+ maxToolCallsWithoutEdit?: number;
499
+ maxConsecutiveErrors?: number;
500
+ maxConsecutiveWebCalls?: number;
501
+ }
502
+ /**
503
+ * The record of standing the service's docker-compose dependencies up before a tester
504
+ * run (explore mode, `infra.environment === 'local'`). The compose stand-up happens
505
+ * INSIDE the container, so its output never reaches the orchestrator's provisioning-log
506
+ * store (which records only the backend-side container/env spin-up); this carries the
507
+ * captured (redacted + bounded) command output back structurally so the test window can
508
+ * show WHY the dependencies failed to come up — previously this was trapped in the
509
+ * harness's own logs. Absent for ephemeral / no-infra / no-compose-path runs.
510
+ */
511
+ export interface InfraSetupRecord {
512
+ /** Whether `docker compose up --wait` succeeded (the dependencies are up). */
513
+ started: boolean;
514
+ /** The repo-relative compose file that was stood up. */
515
+ composePath?: string;
516
+ /** Epoch ms the stand-up attempt finished. */
517
+ at: number;
518
+ /** Wall-clock of the stand-up attempt, ms. */
519
+ durationMs?: number;
520
+ /** Captured (redacted, tail-bounded) stdout+stderr of the stand-up command. */
521
+ logs?: string;
522
+ /** The verbatim (redacted) failure message when stand-up failed, else absent. */
523
+ error?: string;
524
+ }
525
+ /** The generic agent response. `custom` carries a structured explore result. */
526
+ export interface AgentResult {
527
+ summary?: string;
528
+ stats?: PiRunStats;
529
+ /** Structured explore output (the parsed JSON object) when `output.kind==='structured'`. */
530
+ custom?: unknown;
531
+ /**
532
+ * The tester's docker-compose stand-up record (explore mode, local infra). Carried back
533
+ * so the backend can surface the in-container dependency stand-up logs on the Tester step
534
+ * — the failure-class artifact the orchestrator-side provisioning logs can't capture.
535
+ */
536
+ infraSetup?: InfraSetupRecord;
537
+ /**
538
+ * The PRE-PR VALIDATION report: the outcome of running the service's configured check commands
539
+ * against the checkout after the agent settled and before opening a PR, plus how many repair
540
+ * rounds the harness spent. Present on BOTH outcomes — a passing report accompanies the opened
541
+ * PR (the captured proof), and a failing one accompanies the run's `error` (no PR was opened).
542
+ * Absent when the job carried no {@link AgentJob.validationChecks}.
543
+ */
544
+ validationReport?: ValidationReport;
545
+ /**
546
+ * The BUGFIX REPRODUCTION PROOF: the declared reproduction command's verdict across the pre-fix
547
+ * tree and the final tree, computed by the harness from exit codes. Present on every outcome of
548
+ * a job that carried {@link AgentJob.reproduction} — a verdict is evidence, not a gate, so an
549
+ * `inconclusive` one accompanies the opened PR exactly like a `reproduced` one does. Absent
550
+ * when the job carried no reproduction declaration.
551
+ */
552
+ reproductionReport?: ReproductionReport;
553
+ /**
554
+ * Preview mode: the in-container URL the built app is served at (e.g. `http://localhost:4173`).
555
+ * This is NOT host-reachable on its own — the container runtime publishes the serve port to an
556
+ * ephemeral host port and the backend forms the browsable URL from that; this is echoed for
557
+ * logging/context. Present only on a successful preview stand-up.
558
+ */
559
+ preview?: {
560
+ url: string;
561
+ };
562
+ /** Coding mode: whether a change was pushed. */
563
+ pushed?: boolean;
564
+ prUrl?: string;
565
+ branch?: string;
566
+ /**
567
+ * Coding mode (Ralph loop): the harness-computed verdict of the post-commit validation
568
+ * command — whether it exited 0, its exit code, and a bounded, redacted output tail. The
569
+ * engine reads this (never a model self-report) to decide whether the loop is done or must
570
+ * iterate again. Present only for a `ralph` iteration ({@link AgentJob.validation} set).
571
+ */
572
+ ralphVerdict?: {
573
+ validationPassed: boolean;
574
+ exitCode: number;
575
+ validationOutputTail?: string;
576
+ iteration?: number;
577
+ /**
578
+ * The work-branch HEAD the command was judged against. The engine compares it across
579
+ * consecutive failing iterations to end a loop that has stopped committing anything,
580
+ * instead of spending the rest of its budget re-learning that. Absent when unreadable.
581
+ */
582
+ headSha?: string;
583
+ };
584
+ /**
585
+ * Coding mode (multi-repo): the PRs opened in the connected services' PEER repos, one per
586
+ * repo the run actually changed (service-connections phase 3). Beside the own-service
587
+ * `prUrl`/`branch`; the backend lifts these onto the block's `peerPullRequests`. Absent for
588
+ * a single-repo run.
589
+ */
590
+ peerPullRequests?: {
591
+ repo: string;
592
+ frameId?: string;
593
+ prUrl: string;
594
+ branch: string;
595
+ }[];
596
+ /** Coding mode (bootstrap): the default branch the bootstrapped contents were pushed to. */
597
+ defaultBranch?: string;
598
+ error?: string;
599
+ /**
600
+ * The structured failure cause set alongside `error` on a clean-exit failure (no usable
601
+ * output, no changes to push, unresolved conflicts, …). The job registry copies it onto
602
+ * the job view so the backend classifies the failure without regex. See {@link FailureCause}.
603
+ */
604
+ failureCause?: FailureCause;
605
+ usage?: {
606
+ inputTokens: number;
607
+ outputTokens: number;
608
+ };
609
+ /**
610
+ * Per-model-call telemetry from a subscription harness's CLI stream (absent for the
611
+ * proxy-metered Pi harness). The backend records these into `llm_call_metrics`. See
612
+ * {@link HarnessCallMetric}.
613
+ */
614
+ callMetrics?: HarnessCallMetric[];
615
+ /**
616
+ * The agent's effort self-assessment (how hard the work was, what reduced its effectiveness,
617
+ * the key obstacles), lifted from its sentinel file after the run. The backend forwards it onto
618
+ * the job result and records it on the step for run details. Absent when the agent wrote none.
619
+ */
620
+ effortReport?: EffortReport;
621
+ }
622
+ /** The one-shot inline completion job. `harness` must be a subscription harness. */
623
+ export interface InlineJob extends HarnessAuthFields {
624
+ jobId: string;
625
+ /** Real vendor model id, e.g. `claude-opus-4-8` / `gpt-5.5-codex`. */
626
+ model: string;
627
+ /** Composed role + best-practice fragments (Claude: `--append-system-prompt`; Codex: prepended). */
628
+ systemPrompt: string;
629
+ /** The concrete task/user prompt fed to the CLI over stdin. */
630
+ userPrompt: string;
631
+ /** Advisory output cap, forwarded for parity; the one-shot CLIs don't all honour it. */
632
+ maxOutputTokens?: number;
633
+ }
634
+ /** The inline completion result: the reply text plus lifted token usage / per-call telemetry. */
635
+ export interface InlineResult {
636
+ text: string;
637
+ /** `length` when the model hit its output cap (the reviewer rejects a truncated doc). */
638
+ finishReason?: 'stop' | 'length';
639
+ /**
640
+ * The job's token usage with the input side split into its three ORTHOGONAL classes:
641
+ * `inputTokens` is FRESH input only, so the total input is
642
+ * `inputTokens + cacheReadTokens + cacheWriteTokens`. Folded from the per-call metrics below,
643
+ * which is the only channel that knows the split; a CLI that streamed none falls back to the
644
+ * coarse total with both cache classes 0 — honest, since on that shape nothing is known to
645
+ * have been cached.
646
+ */
647
+ usage?: {
648
+ inputTokens: number;
649
+ cacheReadTokens: number;
650
+ cacheWriteTokens: number;
651
+ outputTokens: number;
652
+ };
653
+ /** Per-model-call telemetry lifted from the CLI stream (recorded into `llm_call_metrics`). */
654
+ callMetrics?: HarnessCallMetric[];
655
+ /** A structured failure marks a job-level failure even on a clean HTTP exit (see JobResultBase). */
656
+ error?: string;
657
+ }
658
+ /**
659
+ * Validate + narrow an untrusted body into an {@link InlineJob}. The harness MUST be a
660
+ * subscription harness (`claude-code` / `codex`) — the inline path never runs Pi (that goes
661
+ * through the LLM proxy inline, not a container CLI). Reuses {@link parseHarnessAuth}, so a
662
+ * non-ambient job requires `subscriptionToken`.
663
+ */
664
+ export declare function parseInlineJob(input: unknown): InlineJob;
665
+ /** Validate + narrow an untrusted body into an {@link AgentJob}, throwing on bad input. */
666
+ export declare function parseAgentJob(input: unknown): AgentJob;