@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
@@ -0,0 +1,16 @@
1
+ type Fields = Record<string, unknown>;
2
+ /** The logging surface: the four levels plus `child` to bind correlation fields once. */
3
+ export interface Logger {
4
+ debug: (msg: string, fields?: Fields) => void;
5
+ info: (msg: string, fields?: Fields) => void;
6
+ warn: (msg: string, fields?: Fields) => void;
7
+ error: (msg: string, fields?: Fields) => void;
8
+ /**
9
+ * Return a logger that merges `bound` into every line (e.g. `{ jobId, repo, branch }`),
10
+ * so a per-job logger carries its correlation context without each call site re-spreading
11
+ * it. Nestable — the returned logger's own `child` accumulates onto these bound fields.
12
+ */
13
+ child: (bound: Fields) => Logger;
14
+ }
15
+ export declare const log: Logger;
16
+ export {};
@@ -0,0 +1,24 @@
1
+ import type { Logger } from './logger.js';
2
+ /**
3
+ * The onboarding gates we pre-accept in a fresh config home. Kept as a single constant so
4
+ * the write and the assertion below can never drift, and so a new gate is added in exactly
5
+ * one place. If the CLI renames/adds a key, this is where the fix lands.
6
+ */
7
+ export declare const ONBOARDING_PRESEED_KEYS: {
8
+ readonly hasCompletedOnboarding: true;
9
+ readonly bypassPermissionsModeAccepted: true;
10
+ readonly hasTrustDialogAccepted: true;
11
+ };
12
+ /** Write the onboarding pre-seed into `<configHome>/.claude.json`. Best-effort; never throws. */
13
+ export declare function writeOnboardingPreseed(configHome: string): Promise<void>;
14
+ /**
15
+ * Verify the pre-seed actually landed and log the pinned onboarding keys alongside the
16
+ * installed CLI version — the "one-line assertion after the pre-seed" from D4. It cannot
17
+ * introspect the CLI's true first-run gate set (the CLI never exposes it), so it does the
18
+ * two things it CAN do cheaply and deterministically: confirm every key we intended is
19
+ * present + truthy in the written file (catching a botched write), and emit a structured
20
+ * record pairing the keys with the CLI version so a future onboarding regression — surfaced
21
+ * by the cold-start watchdog as a silent, output-less start — is diffable against a new gate.
22
+ * Best-effort; never throws.
23
+ */
24
+ export declare function assertOnboardingKeysCurrent(configHome: string, cliVersion: string | undefined, log: Logger | undefined): Promise<void>;
@@ -0,0 +1,32 @@
1
+ import type { PackageRegistrySpec } from './job.js';
2
+ /** Where the per-job npm auth lands in a container (the user npmrc, outside any checkout). */
3
+ export declare function npmrcPath(): string;
4
+ /**
5
+ * Per-job isolation for the rendered npmrc. Set `isolatedDir` when the harness process is
6
+ * SHARED across concurrent jobs and its HOME is the developer's own — i.e. the local native
7
+ * host-process transport, which is exactly the set of jobs carrying `ambientAuth`. Absent ⇒
8
+ * the container default (`~/.npmrc`).
9
+ */
10
+ export interface PackageRegistryScope {
11
+ /** A per-job directory (removed with the job) to hold this job's npmrc. */
12
+ isolatedDir?: string;
13
+ }
14
+ /**
15
+ * Render the job's registry entries as npmrc lines: each scope routed to its
16
+ * registry, plus one `_authToken` credential line per distinct host.
17
+ */
18
+ export declare function renderNpmrc(entries: readonly PackageRegistrySpec[]): string;
19
+ /**
20
+ * Write (or clear) the job's npmrc before the agent runs, and return the env the agent's child
21
+ * process needs to find it (empty for the container default, which npm picks up from HOME).
22
+ * Tokens are registered for output redaction so a token echoed in an npm error never reaches
23
+ * logs or stored output.
24
+ */
25
+ export declare function configurePackageRegistries(entries: readonly PackageRegistrySpec[] | undefined, scope?: PackageRegistryScope): Promise<Record<string, string>>;
26
+ /**
27
+ * The credential VALUES in npmrc content: the three keys npm accepts a secret under, on any host
28
+ * line. Used to register a seeded (developer-owned) file's tokens for redaction. An `${ENV_VAR}`
29
+ * reference is not itself a secret — npm expands it at read time — so it is skipped rather than
30
+ * registered as a literal to scrub.
31
+ */
32
+ export declare function npmrcCredentials(content: string): string[];
@@ -0,0 +1,194 @@
1
+ import type { RepoSpec } from './job.js';
2
+ import type { McpServerSpec, SkillSpec } from './agent-capabilities.js';
3
+ import { type ContextFileInfo, type PiRunOutcome, type PiRunStats, type RunDiagnostics } from './pi.js';
4
+ import { type ProgressGuardLimits } from './progress-guard.js';
5
+ import type { RunOptions } from './runner.js';
6
+ import { type SubscriptionHarness } from './agent-runner.js';
7
+ /** Which container harness runs an agent (the default Pi, or a subscription CLI). */
8
+ export type HarnessKind = 'pi' | SubscriptionHarness;
9
+ /**
10
+ * Run `fn` against a fresh temp working directory, always removing it afterwards
11
+ * (even on throw). `prefix` labels the directory (e.g. 'impl', 'merge').
12
+ *
13
+ * Teardown is **best-effort**: on Windows (native local mode) a just-exited child —
14
+ * git, or the developer's own `claude`/`codex` CLI — can still hold a transient handle
15
+ * on a file in the checkout, so a straight `rm` throws `EBUSY`/`EPERM` and, running in
16
+ * the `finally`, would fail an otherwise-successful run. We lean on `fs.rm`'s Windows
17
+ * backoff (`maxRetries`/`retryDelay`) and, if it STILL can't remove the dir, log and
18
+ * swallow: a leaked temp dir is harmless (the OS reclaims the temp root), a failed run
19
+ * is not.
20
+ */
21
+ export declare function withWorkspace<T>(prefix: string, fn: (dir: string) => Promise<T>): Promise<T>;
22
+ /**
23
+ * Run `fn` against a STABLE per-repo working directory (`<root>/<owner>/<repo>`) that is
24
+ * NOT removed afterwards — the persistent-checkout analogue of {@link withWorkspace}. The
25
+ * caller (via `prepareExistingCheckout`) clean-sweeps + fetches the dir into the right
26
+ * state before use; serialised per dir so concurrent jobs can't corrupt the shared tree.
27
+ */
28
+ export declare function withPersistentWorkspace<T>(repo: RepoSpec, fn: (dir: string) => Promise<T>): Promise<T>;
29
+ /**
30
+ * Acquire a working directory for a run: a STABLE, reused per-repo checkout when the job
31
+ * opted into persistent checkout (the warm-pool path), else a fresh ephemeral temp dir
32
+ * (every other runtime). The two flows differ ONLY in dir lifecycle — the caller populates
33
+ * the dir (clone vs `prepareExistingCheckout`) itself, so it can keep its flow-specific
34
+ * resume / full-clone / branch logic.
35
+ */
36
+ export declare function acquireRepoCheckout<T>(opts: {
37
+ persistent: boolean;
38
+ prefix: string;
39
+ repo: RepoSpec;
40
+ }, fn: (dir: string) => Promise<T>): Promise<T>;
41
+ /** What every agent needs to drive Pi against an already-prepared directory. */
42
+ export interface AgentRunSpec {
43
+ /** The prepared working directory (cloned/scaffolded by the caller). */
44
+ dir: string;
45
+ /** Composed role + best-practice fragments; written to Pi's global AGENTS.md context. */
46
+ systemPrompt: string;
47
+ /** The concrete task prompt handed to Pi. */
48
+ userPrompt: string;
49
+ model: string;
50
+ /**
51
+ * Which harness runs the agent. Absent ⇒ the default Pi harness (proxy +
52
+ * sessionToken). For `claude-code` / `codex`, `subscriptionToken` carries the
53
+ * leased credential and the proxy fields are unused (the CLI talks direct).
54
+ */
55
+ harness?: HarnessKind;
56
+ /** The leased subscription credential for `claude-code` / `codex`. */
57
+ subscriptionToken?: string;
58
+ /** Anthropic-compatible base URL for a non-Anthropic Claude-Code vendor (GLM/Kimi). */
59
+ subscriptionBaseUrl?: string;
60
+ /**
61
+ * Native local execution: run the developer's installed `claude` / `codex` with its
62
+ * OWN ambient login instead of a leased credential. Set only by the local native
63
+ * transport; a no-op for the Pi harness.
64
+ */
65
+ ambientAuth?: boolean;
66
+ /** Pi proxy base URL (Pi harness only). */
67
+ proxyBaseUrl?: string;
68
+ /**
69
+ * The backend serves the phase-tagged completions route, so this pass may tag the URL it
70
+ * points Pi at with the phase it is running under (see {@link HarnessAuthFields.proxyPhasePath}
71
+ * and `phasedProxyBaseUrl`). Absent ⇒ the plain path.
72
+ */
73
+ proxyPhasePath?: boolean;
74
+ /** Pi proxy session token (Pi harness only). */
75
+ sessionToken?: string;
76
+ /**
77
+ * For a monorepo service, the subdirectory (relative to the repo root) this run
78
+ * operates within — `spec.dir` already points there. Surfaced to the agent in
79
+ * AGENTS.md so it knows it's in a monorepo and where its service lives. Absent ⇒
80
+ * whole-repo run (no monorepo note).
81
+ */
82
+ serviceDirectory?: string;
83
+ /**
84
+ * Whether this run is expected to edit files. Defaults to true; set false for
85
+ * assess-only runs (the merger) so the no-progress guard's no-edit bound — which
86
+ * would otherwise fire on a run that correctly makes zero edits — is skipped.
87
+ */
88
+ expectsEdits?: boolean;
89
+ /**
90
+ * Per-knob overrides for the progress guard, set by the backend per agent kind (it
91
+ * only LOOSENS limits, never tightens). Each present knob overrides the env/default;
92
+ * absent knobs keep {@link progressGuardLimitsFromEnv}. Absent ⇒ env/default for all.
93
+ */
94
+ guardLimits?: Partial<ProgressGuardLimits>;
95
+ /**
96
+ * Per-kind web-search guidance composed by the backend (so it can speak to what
97
+ * this agent kind does). Surfaced in AGENTS.md only when web search is configured;
98
+ * absent ⇒ the generic blurb is used. See `writeAgentsContext`.
99
+ */
100
+ webToolsGuidance?: string;
101
+ /**
102
+ * Linked-context files the backend prepared (requirements / RFCs / PRDs / tracker
103
+ * issues). Materialised into CONTEXT_DIR in the checkout before the run and pointed at
104
+ * from AGENTS.md, so the agent reads them on demand. Absent ⇒ none.
105
+ */
106
+ contextFiles?: ContextFileInfo[];
107
+ /**
108
+ * The skills to make available for this run — a `skill` step's picked skill and/or the playbooks
109
+ * the running agent kind declares. Installed HARNESS-AWARE: the claude-code runner writes them
110
+ * natively into the config dir's `skills/`; for Pi/codex the resource files are materialised
111
+ * under `.cat-context/skill/<name>/` (their prompt already carries the folded-in instructions).
112
+ * Absent ⇒ no skills.
113
+ */
114
+ skills?: SkillSpec[];
115
+ /**
116
+ * Tool servers (MCP) to wire into the agent CLI. Served by the subscription harnesses only —
117
+ * Pi has no MCP client, and the BACKEND is what decides that (it drops an unservable server and
118
+ * tells the agent so), which is why this path simply forwards whatever it is given rather than
119
+ * re-deciding. Absent ⇒ the CLI's built-in tools only.
120
+ */
121
+ mcpServers?: McpServerSpec[];
122
+ /**
123
+ * Enable proxy-backed web search: point the rpiv-web-tools SearXNG provider at the
124
+ * backend's search proxy (`${proxyBaseUrl}/web-search`) with the session token as
125
+ * the bearer — so the search runs server-side under the deployment's key and no
126
+ * provider secret reaches the sandbox. Off ⇒ web search is on only if a provider key
127
+ * is present directly in the container env (the self-hosted runner-pool path).
128
+ */
129
+ webSearchProxy?: boolean;
130
+ /**
131
+ * Multi-repo run (service-connections phase 3): the cwd (`dir`) is the WORKSPACE ROOT with
132
+ * every involved repo checked out as a sibling under it. Suppresses the single-repo monorepo
133
+ * note in AGENTS.md and adds the multi-repo mechanics note instead. Absent ⇒ single-repo.
134
+ */
135
+ multiRepo?: boolean;
136
+ }
137
+ /**
138
+ * Whether the run's checkout actually ships a `blueprints/` folder — what gates the blueprint
139
+ * orientation note in AGENTS.md (an external repo has none, so the note would be ~10 lines of
140
+ * dead guidance pointing at files that don't exist, re-sent on every turn).
141
+ *
142
+ * A MULTI-REPO run's `dir` is the workspace ROOT with each repo checked out as a sibling under
143
+ * it, so the root itself never holds `blueprints/`: the legs are checked too, and the note is
144
+ * included when ANY leg ships one (it orients the agent to the concept, and the agent finds the
145
+ * per-repo folder from there). Best-effort throughout — any stat/readdir failure simply omits
146
+ * the note rather than failing the dispatch.
147
+ */
148
+ export declare function checkoutHasBlueprints(dir: string, multiRepo: boolean): Promise<boolean>;
149
+ /**
150
+ * Write Pi's global agent context (`~/.pi/agent/AGENTS.md`) + provider config,
151
+ * then run Pi once in `spec.dir` and return its summary/stats/stderr. The context
152
+ * lives outside the checkout so it never lands in a commit; the shared middle of
153
+ * every container agent.
154
+ */
155
+ export declare function runAgentInWorkspace(spec: AgentRunSpec, opts?: RunOptions): Promise<PiRunOutcome>;
156
+ /**
157
+ * Whether the claude-code runner will install this run's skills natively (into the CLI's config
158
+ * dir) rather than the caller materialising them into the checkout. True ONLY for a
159
+ * leased-credential claude-code run, which gets a throwaway per-run config home. An AMBIENT run
160
+ * uses the developer's own `~/.claude`, which the runner will not write a skill into — it would
161
+ * outlive the run in their personal setup, and two concurrent jobs carrying same-named skills
162
+ * would overwrite each other's.
163
+ */
164
+ export declare function installsSkillNatively(spec: Pick<AgentRunSpec, 'harness' | 'ambientAuth'>): boolean;
165
+ /**
166
+ * True when Pi exited cleanly without a single tool call or token of output — the
167
+ * signature of a run where it never reached the model. Used by every agent's
168
+ * no-op reason to point at the most likely cause (an unreachable proxy / rejected
169
+ * model) rather than a genuine "nothing to do".
170
+ */
171
+ export declare function agentNeverActed(stats: PiRunStats): boolean;
172
+ /** The full-sentence "never acted" cause shared by the structured no-op reasons. */
173
+ export declare const NEVER_ACTED_CAUSE = " The agent never acted (no tool calls, no model output) \u2014 it most likely could not reach the model.";
174
+ /**
175
+ * A human-readable cause when the agent's FINAL answer is unusable — its last turn was
176
+ * cut off at the output ceiling, or carried no text at all (an empty completion) — or
177
+ * `undefined` when the final answer looks fine.
178
+ *
179
+ * This is OPT-IN per agent, never a blanket harness rule. Only agents whose work
180
+ * product is a final text/document the pipeline hands ONWARD to be reviewed or parsed
181
+ * (the spec-writer, the blueprinter) should treat a non-undefined result as a hard
182
+ * failure — for them an empty/cut-off final turn means there is nothing trustworthy to
183
+ * review, which is exactly what drove the spec-writer ⇄ companion rework loop. Agents
184
+ * whose product is a side effect (a pushed PR/commit from the coder or ci-fixer, a
185
+ * self-contained validation) legitimately end with no final text and MUST NOT call this.
186
+ */
187
+ export declare function unusableFinalAnswerCause(diagnostics: RunDiagnostics | undefined): string | undefined;
188
+ /**
189
+ * The credential-scrubbed tail where a no-op's real cause shows up: a slice of Pi's
190
+ * stderr, or — when stderr is empty — a slice of its summary. Empty when neither is
191
+ * present. Shared by every agent's no-op reason so the cause is always diagnosable
192
+ * without shelling into the (ephemeral) container.
193
+ */
194
+ export declare function agentOutputTail(stderrTail: string | undefined, summary?: string): string;
@@ -182,6 +182,10 @@ export async function runAgentInWorkspace(spec, opts = {}) {
182
182
  expectsEdits: spec.expectsEdits ?? true,
183
183
  onActivity: opts.onActivity,
184
184
  onProgress: opts.onProgress,
185
+ // Per-slice review capture, so a parallel review's finished slices are persisted as they
186
+ // land rather than only in the terminal output. Only the subscription runners fan work out
187
+ // across subagents, so this is the only path that can produce it.
188
+ onSliceReviews: opts.onSliceReviews,
185
189
  // Stream this run's per-call telemetry to the job's live drain. The subscription
186
190
  // harnesses are the only producers of `callMetrics` (Pi's calls are metered by the LLM
187
191
  // proxy as they happen), so this is the only path that needs the hook.