@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.
- package/README.md +1 -0
- package/dist/agent-capabilities.d.ts +130 -0
- package/dist/agent-runner.d.ts +114 -0
- package/dist/agent-runner.js +15 -1
- package/dist/agent-shared.d.ts +18 -0
- package/dist/agent.d.ts +66 -0
- package/dist/bootstrap-mode.d.ts +20 -0
- package/dist/captured-command.d.ts +58 -0
- package/dist/claude-call-aggregator.d.ts +164 -0
- package/dist/claude-call-aggregator.js +123 -17
- package/dist/claude-stream.d.ts +56 -0
- package/dist/claude-stream.js +23 -0
- package/dist/coding-agent.d.ts +263 -0
- package/dist/dependency-install.d.ts +111 -0
- package/dist/effort.d.ts +19 -0
- package/dist/embed.d.ts +4 -0
- package/dist/failure.d.ts +42 -0
- package/dist/follow-ups.d.ts +28 -0
- package/dist/frontend-infra.d.ts +25 -0
- package/dist/fs-utils.d.ts +2 -0
- package/dist/git.d.ts +394 -0
- package/dist/host-markdown.d.ts +28 -0
- package/dist/inline.d.ts +10 -0
- package/dist/job.d.ts +666 -0
- package/dist/logger.d.ts +16 -0
- package/dist/onboarding-preseed.d.ts +24 -0
- package/dist/package-registries.d.ts +32 -0
- package/dist/pi-workspace.d.ts +194 -0
- package/dist/pi-workspace.js +4 -0
- package/dist/pi.d.ts +475 -0
- package/dist/pr-description.d.ts +85 -0
- package/dist/pr-template.d.ts +101 -0
- package/dist/process-exit.d.ts +7 -0
- package/dist/process.d.ts +19 -0
- package/dist/progress-guard.d.ts +88 -0
- package/dist/progress.d.ts +87 -0
- package/dist/redact.d.ts +31 -0
- package/dist/reproduction-proof.d.ts +224 -0
- package/dist/runner.d.ts +282 -0
- package/dist/runner.js +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/structured-output.d.ts +75 -0
- package/dist/subagents.d.ts +88 -0
- package/dist/subagents.js +74 -4
- package/dist/transcript-retention.d.ts +21 -0
- package/dist/validation-checks.d.ts +159 -0
- package/dist/vcs-api.d.ts +73 -0
- package/dist/version.d.ts +2 -0
- package/package.json +9 -5
- package/src/agent-runner.ts +21 -2
- package/src/claude-call-aggregator.ts +181 -32
- package/src/claude-stream.ts +21 -0
- package/src/pi-workspace.ts +4 -0
- package/src/runner.ts +24 -0
- package/src/subagents.ts +57 -3
package/src/runner.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { redactSecrets } from './redact.js'
|
|
|
2
2
|
import type { FollowUpLine } from './follow-ups.js'
|
|
3
3
|
import type { ValidationReport } from './validation-checks.js'
|
|
4
4
|
import type { ReproductionReport } from './reproduction-proof.js'
|
|
5
|
+
import type { SliceReview } from './subagents.js'
|
|
5
6
|
import type { HarnessCallMetric, TodoProgress, ToolSpan } from './pi.js'
|
|
6
7
|
import { log, type Logger } from './logger.js'
|
|
7
8
|
import {
|
|
@@ -47,6 +48,14 @@ export interface RunOptions {
|
|
|
47
48
|
* attempt is final, and the loop republishes a whole new one — with a fresh `at` — per round.
|
|
48
49
|
*/
|
|
49
50
|
onReproductionProof?: (report: ReproductionReport) => void
|
|
51
|
+
/**
|
|
52
|
+
* Receives the full set of per-slice reviews a parallel review has captured, republished each
|
|
53
|
+
* time a slice's subagent returns. Latest-wins (NOT a drain buffer), for the same reason as
|
|
54
|
+
* {@link onValidationReport} but with more at stake: these carry the slices' actual review work,
|
|
55
|
+
* and a review whose aggregation never finishes is recoverable ONLY from what the backend
|
|
56
|
+
* already persisted. Absent for a job that dispatched no subagents.
|
|
57
|
+
*/
|
|
58
|
+
onSliceReviews?: (reviews: SliceReview[]) => void
|
|
50
59
|
/**
|
|
51
60
|
* Receives each per-call telemetry row the moment the agent's CLI stream yields it, so a
|
|
52
61
|
* run's model calls reach `llm_call_metrics` WHILE it runs rather than only in its terminal
|
|
@@ -203,6 +212,18 @@ export interface JobView<TResult extends JobResultBase = JobResultBase> {
|
|
|
203
212
|
* that carried no reproduction declaration.
|
|
204
213
|
*/
|
|
205
214
|
reproductionReport?: ReproductionReport
|
|
215
|
+
/**
|
|
216
|
+
* The per-slice reviews captured so far on a parallel (subagent-fanned) review — each slice's
|
|
217
|
+
* label, whether its subagent returned, and its verbatim report. A whole-value latest publish
|
|
218
|
+
* like {@link validationReport}, not drain-on-read.
|
|
219
|
+
*
|
|
220
|
+
* This is the durable half of a PR review. The reviewer returns `slices`/`findings` only in its
|
|
221
|
+
* TERMINAL structured output, so before this existed a review killed mid-run (or one whose
|
|
222
|
+
* aggregation pass wedged) lost every finished slice and could only be re-run from zero. The
|
|
223
|
+
* backend persists these onto the step as they arrive, which is what a manual resume re-aggregates
|
|
224
|
+
* from. Absent for a job that dispatched no subagents.
|
|
225
|
+
*/
|
|
226
|
+
sliceReviews?: SliceReview[]
|
|
206
227
|
}
|
|
207
228
|
|
|
208
229
|
interface JobEntry<TResult extends JobResultBase> extends JobView<TResult> {
|
|
@@ -473,6 +494,9 @@ export class JobRegistry<TJob = unknown, TResult extends JobResultBase = JobResu
|
|
|
473
494
|
onValidationReport: (report) => {
|
|
474
495
|
entry.validationReport = report
|
|
475
496
|
},
|
|
497
|
+
onSliceReviews: (reviews) => {
|
|
498
|
+
entry.sliceReviews = reviews
|
|
499
|
+
},
|
|
476
500
|
onReproductionProof: (report) => {
|
|
477
501
|
entry.reproductionReport = report
|
|
478
502
|
},
|
package/src/subagents.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { basename, join } from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
claudeAssistantContent,
|
|
6
6
|
claudeCallUsage,
|
|
7
|
+
claudeToolResultText,
|
|
7
8
|
isObject,
|
|
8
9
|
redactBody,
|
|
9
10
|
SUBAGENT_TOOL_NAMES,
|
|
@@ -54,20 +55,52 @@ import { publishCallMetric, type HarnessCallMetric, type TodoProgress } from './
|
|
|
54
55
|
// Slice / progress tracking off the PARENT stream (D2.1)
|
|
55
56
|
// ---------------------------------------------------------------------------
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* How much of one slice's terminal report is kept. A slice review is prose (findings for a handful
|
|
60
|
+
* of files), not a transcript, so this is far above a real report while still bounding what a
|
|
61
|
+
* runaway subagent can push onto the step — the reports ride the job view on every poll and are
|
|
62
|
+
* persisted on the run.
|
|
63
|
+
*/
|
|
64
|
+
export const SLICE_REPORT_MAX_CHARS = 24_000
|
|
65
|
+
|
|
57
66
|
interface TrackedSlice {
|
|
58
67
|
/** The dispatch's tool_use id, used to pair the terminal tool_result. */
|
|
59
68
|
toolUseId: string
|
|
60
69
|
/** The subagent's description (`Review <slice> slice`), rendered as the progress label. */
|
|
61
70
|
description: string
|
|
62
71
|
done: boolean
|
|
72
|
+
/**
|
|
73
|
+
* The subagent's verbatim terminal report, captured from the paired `tool_result`; undefined
|
|
74
|
+
* until it lands. This is the slice's actual review work — the reason a stuck aggregation no
|
|
75
|
+
* longer costs the whole run (see `prReviewSliceReviewSchema`). Truncated to
|
|
76
|
+
* {@link SLICE_REPORT_MAX_CHARS} and scrubbed of leased credentials before it leaves here.
|
|
77
|
+
*/
|
|
78
|
+
report?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** One slice's live review, as published on the job view. Mirrors `prReviewSliceReviewSchema`. */
|
|
82
|
+
export interface SliceReview {
|
|
83
|
+
label: string
|
|
84
|
+
status: 'in_progress' | 'completed'
|
|
85
|
+
report?: string | null
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
/** Tracks parallel subagents seen on the parent stream to derive slice progress. */
|
|
66
89
|
export interface SliceTracker {
|
|
67
90
|
/** Feed an `assistant` message's content blocks: registers any subagent dispatches. */
|
|
68
91
|
onAssistant(content: unknown[]): void
|
|
69
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Feed a `user` message's content blocks: marks the paired subagent(s) complete AND captures
|
|
94
|
+
* each one's terminal report (see {@link SliceTracker.sliceReviews}).
|
|
95
|
+
*/
|
|
70
96
|
onUser(content: unknown[]): void
|
|
97
|
+
/**
|
|
98
|
+
* Every dispatched slice with its status and captured report, in dispatch order — the durable
|
|
99
|
+
* half of this tracker. Published as a whole value on each poll (NOT drain-on-read): the set
|
|
100
|
+
* only grows, and a dropped poll response must never permanently lose a finished slice's
|
|
101
|
+
* review, which is the entire point of capturing it. Empty when nothing was dispatched.
|
|
102
|
+
*/
|
|
103
|
+
sliceReviews(): SliceReview[]
|
|
71
104
|
/** Whether any `Task` subagent has been dispatched (⇒ this run parallelised). */
|
|
72
105
|
hasSlices(): boolean
|
|
73
106
|
/**
|
|
@@ -80,7 +113,12 @@ export interface SliceTracker {
|
|
|
80
113
|
progress(): TodoProgress | undefined
|
|
81
114
|
}
|
|
82
115
|
|
|
83
|
-
|
|
116
|
+
/**
|
|
117
|
+
* @param secrets Leased-credential strings scrubbed from every captured report. A subagent can
|
|
118
|
+
* echo a token it saw in the checkout, and these reports are persisted on the run, so they are
|
|
119
|
+
* redacted on the way in rather than trusting each consumer to do it.
|
|
120
|
+
*/
|
|
121
|
+
export function createSliceTracker(secrets: string[] = []): SliceTracker {
|
|
84
122
|
// Insertion-ordered so the progress `items` render in dispatch order.
|
|
85
123
|
const slices = new Map<string, TrackedSlice>()
|
|
86
124
|
|
|
@@ -106,9 +144,25 @@ export function createSliceTracker(): SliceTracker {
|
|
|
106
144
|
if (!isObject(block) || block.type !== 'tool_result') continue
|
|
107
145
|
const id = typeof block.tool_use_id === 'string' ? block.tool_use_id : undefined
|
|
108
146
|
const slice = id ? slices.get(id) : undefined
|
|
109
|
-
if (slice)
|
|
147
|
+
if (!slice) continue
|
|
148
|
+
slice.done = true
|
|
149
|
+
// The report is captured here or nowhere: this `tool_result` is the only place the
|
|
150
|
+
// subagent's findings appear on the parent stream, and the next poll may be the last one
|
|
151
|
+
// this job ever answers.
|
|
152
|
+
const report = redactBody(claudeToolResultText(block), secrets).trim()
|
|
153
|
+
if (report) slice.report = report.slice(0, SLICE_REPORT_MAX_CHARS)
|
|
110
154
|
}
|
|
111
155
|
},
|
|
156
|
+
sliceReviews() {
|
|
157
|
+
return [...slices.values()].map((s) => ({
|
|
158
|
+
label: s.description,
|
|
159
|
+
status: (s.done ? 'completed' : 'in_progress') as 'completed' | 'in_progress',
|
|
160
|
+
// A slice that finished but whose result carried no readable text is reported as
|
|
161
|
+
// completed with a null report rather than being dropped: a resume must still know it
|
|
162
|
+
// does not need re-reviewing, and silently omitting it would send it round again.
|
|
163
|
+
report: s.report ?? null,
|
|
164
|
+
}))
|
|
165
|
+
},
|
|
112
166
|
hasSlices() {
|
|
113
167
|
return slices.size > 0
|
|
114
168
|
},
|