@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/dist/pi.d.ts
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import type { EffortReport } from './effort.js';
|
|
2
|
+
import { type ProgressGuardLimits } from './progress-guard.js';
|
|
3
|
+
/**
|
|
4
|
+
* Per-completion output-token ceiling Pi requests (its model-entry `maxTokens`).
|
|
5
|
+
* Generous on purpose: a reasoning model (e.g. GLM-5.2) spends tokens on its
|
|
6
|
+
* `<think>` trace before the answer + tool calls, so a tight cap truncates it
|
|
7
|
+
* mid-reasoning and the agent never commits edits. It is a ceiling, not a target
|
|
8
|
+
* — unused output tokens are not billed and Workers AI clamps the request to the
|
|
9
|
+
* model's real max — so erring high is safe. Raised to 32k after a spec-writer run
|
|
10
|
+
* truncated an intermediate tool call at the old 16k cap; the document itself
|
|
11
|
+
* stopped well under it, so this is headroom for larger specs/diffs, with
|
|
12
|
+
* {@link runDiagnostics} flagging the rare case where even 32k is not enough.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PI_MAX_OUTPUT_TOKENS = 32768;
|
|
15
|
+
/**
|
|
16
|
+
* Normalise a phase label to what the backend will actually store: trimmed, lowercased,
|
|
17
|
+
* `[a-z0-9-]` only, bounded. `''` when the label is not a phase at all.
|
|
18
|
+
*
|
|
19
|
+
* A deliberate COPY of kernel's `normalizeCallPhase` — the container image is built from `src/`
|
|
20
|
+
* plus typescript alone, so the harness can carry no runtime dependency on a workspace package
|
|
21
|
+
* (the same constraint that forced `src/host-markdown.ts`). A copy that can drift is worse than
|
|
22
|
+
* no copy: if the harness rejected a label the backend would have accepted, the call would take
|
|
23
|
+
* the plain path and land unattributed, and if it accepted one the backend rejects it would
|
|
24
|
+
* spend a request on a segment destined for `''`. `test/llm-phase.conformity.test.ts` pins the
|
|
25
|
+
* two to identical verdicts over a corpus, so the alphabet can only be changed in both.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeProxyPhase(phase: string | undefined): string;
|
|
28
|
+
/**
|
|
29
|
+
* Point Pi's provider at the phase-tagged completions path for the pass about to run, so the
|
|
30
|
+
* backend can stamp WHICH slice of the run spent each call (the agent's own loop vs a pre-PR
|
|
31
|
+
* validation repair round vs a reproduction-proof repair round) — see
|
|
32
|
+
* `docs/initiatives/token-burn-instrumentation.md`. The harness drives those loops, so it is
|
|
33
|
+
* the only component that knows; reconstructing the boundary downstream from wall-clock
|
|
34
|
+
* timestamps is exactly the brittle inference this avoids.
|
|
35
|
+
*
|
|
36
|
+
* A URL segment because the harness does not make these requests: Pi does, from a config whose
|
|
37
|
+
* only per-run knobs are the base URL and the token — there is no per-request header to set.
|
|
38
|
+
*
|
|
39
|
+
* `supported` is the BACKEND's declaration that it serves the phase-tagged route, carried on the
|
|
40
|
+
* job body exactly as `webSearch` carries "point the search tool at my `/web-search`". Without it
|
|
41
|
+
* this function would encode a routing shape the receiving backend may not have: a runner pool
|
|
42
|
+
* pins its OWN harness image (`RunnerPoolManifest`), and `LOCAL_HARNESS_IMAGE` overrides the
|
|
43
|
+
* recommended pin outright, so "the image and the backend are a matched set" holds for the
|
|
44
|
+
* Cloudflare deployment and nowhere else. An image ahead of its backend would 404 EVERY model
|
|
45
|
+
* call — a dead run, not degraded telemetry. Absent/false ⇒ the plain path, and the calls land
|
|
46
|
+
* in the backend's unattributed slice.
|
|
47
|
+
*
|
|
48
|
+
* Pure so the join is unit-testable without spawning anything.
|
|
49
|
+
*/
|
|
50
|
+
export declare function phasedProxyBaseUrl(proxyBaseUrl: string, phase: string | undefined, supported: boolean | undefined): string;
|
|
51
|
+
/** Write the Pi provider config that routes all model calls through the proxy. */
|
|
52
|
+
export declare function writePiModelsConfig(opts: {
|
|
53
|
+
model: string;
|
|
54
|
+
proxyBaseUrl: string;
|
|
55
|
+
/** Output-token ceiling Pi may request per completion. Defaults to PI_MAX_OUTPUT_TOKENS. */
|
|
56
|
+
maxTokens?: number;
|
|
57
|
+
}): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Write the composed system prompt as Pi's GLOBAL agent context
|
|
60
|
+
* (`~/.pi/agent/AGENTS.md`), which Pi reads automatically and concatenates with
|
|
61
|
+
* any `AGENTS.md`/`CLAUDE.md` the repo itself ships (global file first, then the
|
|
62
|
+
* ones walked up from the run cwd). Deliberately OUTSIDE the checkout (the same
|
|
63
|
+
* `~/.pi/agent` dir `writePiModelsConfig` already uses) so the harness's
|
|
64
|
+
* instructions never enter the git working tree — they can't be committed into a
|
|
65
|
+
* PR and they never clobber a repo's own committed `AGENTS.md`.
|
|
66
|
+
*
|
|
67
|
+
* This relies on Pi's context-file resolution: the global `~/.pi/agent/AGENTS.md`
|
|
68
|
+
* is loaded before the project-trust decision, so it applies in non-interactive
|
|
69
|
+
* (`-p`) runs without a trust prompt. That contract is pinned by `PI_VERSION` in
|
|
70
|
+
* the Dockerfile — revisit this if that bump changes context-file resolution.
|
|
71
|
+
*/
|
|
72
|
+
export declare function writeAgentsContext(systemPrompt: string, opts?: {
|
|
73
|
+
webSearch?: boolean;
|
|
74
|
+
guidance?: string;
|
|
75
|
+
serviceDirectory?: string;
|
|
76
|
+
contextFiles?: ContextFileInfo[];
|
|
77
|
+
multiRepo?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the checkout actually ships a `blueprints/` folder. The blueprint orientation
|
|
80
|
+
* note is only appended when it does — otherwise it is ~10 lines of dead guidance (re-sent
|
|
81
|
+
* every turn) pointing at files that don't exist. Absent/false ⇒ the note is omitted.
|
|
82
|
+
*/
|
|
83
|
+
hasBlueprints?: boolean;
|
|
84
|
+
}): Promise<void>;
|
|
85
|
+
/** Directory in the checkout where linked-context files are materialised (see CONTEXT_DIR in agents). */
|
|
86
|
+
export declare const CONTEXT_DIR = ".cat-context";
|
|
87
|
+
/** The metadata the AGENTS.md context block needs to point an agent at a materialised file. */
|
|
88
|
+
export interface ContextFileInfo {
|
|
89
|
+
path: string;
|
|
90
|
+
title: string;
|
|
91
|
+
url: string;
|
|
92
|
+
content: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Write the backend-prepared linked-context files into {@link CONTEXT_DIR} in the
|
|
96
|
+
* checkout so the agent can read them on demand, and add a LOCAL git exclude entry so
|
|
97
|
+
* even `git add -A` never commits them into the agent's PR. Best-effort on the exclude
|
|
98
|
+
* (a scaffold-from-scratch checkout has no `.git` yet — the files just stay untracked).
|
|
99
|
+
*/
|
|
100
|
+
export declare function materializeContextFiles(cwd: string, files: ContextFileInfo[]): Promise<void>;
|
|
101
|
+
/** Subdirectory of {@link CONTEXT_DIR} where a skill's resources are materialised, per skill. */
|
|
102
|
+
export declare const SKILL_CONTEXT_SUBDIR = "skill";
|
|
103
|
+
/**
|
|
104
|
+
* Materialise the run's skills' RESOURCE files under `.cat-context/skill/<name>/` in the checkout
|
|
105
|
+
* — the path for every run that does NOT get a native install: Pi, codex, and ambient claude-code
|
|
106
|
+
* (no isolated `CLAUDE_CONFIG_DIR` to install into). Their agents read the checkout, and the
|
|
107
|
+
* skills' instructions are folded into their prompt by the backend (`renderSkillsForHarness`,
|
|
108
|
+
* which keys off ambient auth as well as the harness).
|
|
109
|
+
*
|
|
110
|
+
* Each skill gets its OWN subdirectory: several skills can apply to one run (a step's pick plus
|
|
111
|
+
* the kind's declared playbooks), and a flat directory would let two skills' `templates/report.md`
|
|
112
|
+
* overwrite each other — silently handing the agent the wrong template. The names were sanitized
|
|
113
|
+
* to a single safe path segment at the job boundary, as were the resource sub-paths (no
|
|
114
|
+
* traversal), so nested dirs are created as needed. Kept out of the agent's commits via the same
|
|
115
|
+
* `.cat-context/` git exclude entry. Skills with no resource bodies are a no-op.
|
|
116
|
+
*/
|
|
117
|
+
export declare function materializeSkillResources(cwd: string, skills: {
|
|
118
|
+
name: string;
|
|
119
|
+
resources: {
|
|
120
|
+
relPath: string;
|
|
121
|
+
content: string;
|
|
122
|
+
}[];
|
|
123
|
+
}[]): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* The active web-search backend for the rpiv-web-tools extension. Only the
|
|
126
|
+
* provider id is persisted to disk: the per-provider credential (and any base URL
|
|
127
|
+
* — `SEARXNG_URL`, `OLLAMA_HOST`) is read by the extension straight from the
|
|
128
|
+
* environment, so no key is ever written to the container's filesystem.
|
|
129
|
+
*/
|
|
130
|
+
export interface WebSearchConfig {
|
|
131
|
+
/** rpiv-web-tools provider id, e.g. `brave`, `tavily`, `exa`, `searxng`. */
|
|
132
|
+
provider: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Resolve the web-search configuration from the environment, or undefined when no
|
|
136
|
+
* provider is configured (⇒ the harness writes no rpiv-web-tools config and never
|
|
137
|
+
* nudges the agent towards the tools, so runs behave exactly as before). Enablement
|
|
138
|
+
* is CONDITIONAL on a provider being configured: if any provider's credential/URL
|
|
139
|
+
* env var is present, web search turns on with that provider (highest-priority one
|
|
140
|
+
* when several are set). `WEB_SEARCH_PROVIDER` is an explicit override that pins the
|
|
141
|
+
* active provider regardless of detection — but only when that provider's own
|
|
142
|
+
* credential/URL is also present, so a pin without a key never nudges the agent
|
|
143
|
+
* towards a tool that would error the moment it's called. No key passes through here
|
|
144
|
+
* — the extension reads each provider's own env var directly.
|
|
145
|
+
*/
|
|
146
|
+
export declare function webSearchConfigFromEnv(env?: NodeJS.ProcessEnv): WebSearchConfig | undefined;
|
|
147
|
+
/**
|
|
148
|
+
* The env that points the rpiv-web-tools SearXNG provider at the backend's
|
|
149
|
+
* search proxy: `SEARXNG_URL` = `${proxyBaseUrl}/web-search` (the controller mounted
|
|
150
|
+
* under the LLM proxy's `/v1`), and `SEARXNG_API_KEY` = the per-job session token,
|
|
151
|
+
* which the proxy verifies exactly like the LLM proxy. Handed to Pi's child via
|
|
152
|
+
* `runPi`'s `extraEnv`, so the search key never has to enter the sandbox — the search
|
|
153
|
+
* runs server-side under the deployment's own provider key.
|
|
154
|
+
*/
|
|
155
|
+
export declare function webSearchProxyEnv(proxyBaseUrl: string, sessionToken: string): {
|
|
156
|
+
SEARXNG_URL: string;
|
|
157
|
+
SEARXNG_API_KEY: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Select the active rpiv-web-tools provider by writing
|
|
161
|
+
* `~/.config/rpiv-web-tools/config.json` (the file the extension reads, falling
|
|
162
|
+
* back to `brave` when `provider` is absent). Only the provider id is written —
|
|
163
|
+
* credentials and base URLs come from the environment (env wins over the file in
|
|
164
|
+
* the extension's own resolution order), so no secret is committed to disk. Written
|
|
165
|
+
* 0600 to match the extension's own permissions for that path.
|
|
166
|
+
*/
|
|
167
|
+
export declare function writeWebToolsConfig(config: WebSearchConfig): Promise<string>;
|
|
168
|
+
/** One entry of the agent's todo list — its subject and current status. */
|
|
169
|
+
export interface TodoItem {
|
|
170
|
+
/** The task's subject text, as the agent wrote it. */
|
|
171
|
+
label: string;
|
|
172
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
173
|
+
}
|
|
174
|
+
/** Live subtask progress derived from Pi's `todo` tool — e.g. "3/8 done". */
|
|
175
|
+
export interface TodoProgress {
|
|
176
|
+
/** Tasks marked completed. */
|
|
177
|
+
completed: number;
|
|
178
|
+
/** Tasks currently being worked (rpiv-todo's `in_progress` status). */
|
|
179
|
+
inProgress: number;
|
|
180
|
+
/** Total live tasks (tombstoned/deleted tasks excluded). */
|
|
181
|
+
total: number;
|
|
182
|
+
/**
|
|
183
|
+
* The individual live tasks (label + status), in list order — so the board can
|
|
184
|
+
* render the actual task list, not just the count. Absent for the simpler
|
|
185
|
+
* `todos[].done` fallback shape, which carries no per-task subject.
|
|
186
|
+
*/
|
|
187
|
+
items?: TodoItem[];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* One tool invocation in Pi's loop, captured for the run's observability trace.
|
|
191
|
+
* Metadata only (name + timing + ok) — never the tool's args or result — so the
|
|
192
|
+
* harness buffer stays tiny. The backend drains these on its existing job poll and
|
|
193
|
+
* emits them as child spans under the run trace.
|
|
194
|
+
*/
|
|
195
|
+
export interface ToolSpan {
|
|
196
|
+
tool: string;
|
|
197
|
+
/** Epoch ms the tool call started (approximated as the previous tool's end). */
|
|
198
|
+
startedAt: number;
|
|
199
|
+
/** Epoch ms the tool call ended (when its `tool_execution_end` event arrived). */
|
|
200
|
+
endedAt: number;
|
|
201
|
+
ok: boolean;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* What the agent actually did this run, independent of any file changes. Used to
|
|
205
|
+
* tell a genuine no-op (the agent never reached the model / never acted) apart
|
|
206
|
+
* from a real run, so a bootstrap that produced nothing is failed rather than
|
|
207
|
+
* pushed as an empty repo. `toolCalls === 0 && assistantChars === 0` is the
|
|
208
|
+
* signature of a run where Pi never made a successful model call.
|
|
209
|
+
*/
|
|
210
|
+
export interface PiRunStats {
|
|
211
|
+
/** Tool calls the assistant emitted across the transcript (0 ⇒ it never acted). */
|
|
212
|
+
toolCalls: number;
|
|
213
|
+
/** Total characters of assistant text (0 ⇒ the model produced nothing). */
|
|
214
|
+
assistantChars: number;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Output-quality signals lifted from the agent's transcript, so the harness can fail
|
|
218
|
+
* LOUDLY on a malformed run instead of silently handing a half-baked artifact to the
|
|
219
|
+
* structured-output repair (which would manufacture a doc from garbage — the trap
|
|
220
|
+
* behind the spec-writer ⇄ companion rework loop). Two distinct invalid states, both
|
|
221
|
+
* seen in production from `kimi-k2.7-code`:
|
|
222
|
+
* - a completion that hit the output ceiling (its answer/tool call was cut off), and
|
|
223
|
+
* - a FINAL turn that carried no text at all (an empty `content: []` despite spending
|
|
224
|
+
* output tokens), so there is no answer to parse.
|
|
225
|
+
*/
|
|
226
|
+
export interface RunDiagnostics {
|
|
227
|
+
/** Some completion ended at the output-token ceiling — its content was cut off. */
|
|
228
|
+
truncated: boolean;
|
|
229
|
+
/** The agent's FINAL completion hit the ceiling: its ANSWER (not a mid-run step) was cut off. */
|
|
230
|
+
finalTruncated: boolean;
|
|
231
|
+
/** The agent's final turn carried no text content (e.g. an empty `content: []`). */
|
|
232
|
+
finalAnswerEmpty: boolean;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* One model call captured from a subscription harness's CLI event stream, shaped so
|
|
236
|
+
* the backend can record it into the same `llm_call_metrics` telemetry the LLM proxy
|
|
237
|
+
* writes for the Pi harness. The subscription harnesses (Claude Code / Codex) talk
|
|
238
|
+
* DIRECT to the vendor and never touch the proxy, so this is the only place their
|
|
239
|
+
* per-call bodies are observable. Claude Code's `stream-json --verbose` is a near-
|
|
240
|
+
* verbatim Anthropic Messages stream, so its calls carry full request/response
|
|
241
|
+
* bodies; Codex's `exec --json` only surfaces flat assistant text + per-turn tokens,
|
|
242
|
+
* so its rows are honestly thinner (no request transcript, no tool/command bodies).
|
|
243
|
+
*/
|
|
244
|
+
export interface HarnessCallMetric {
|
|
245
|
+
/** The vendor model that served this call (from the CLI event), when reported. */
|
|
246
|
+
model?: string;
|
|
247
|
+
/**
|
|
248
|
+
* The full request as an OpenAI-style chat array (`[{role, content}, …]`),
|
|
249
|
+
* JSON-stringified — the growing history as of this call. Matches the proxy's
|
|
250
|
+
* `promptText` shape so the telemetry chain delta-compresses + renders identically.
|
|
251
|
+
*/
|
|
252
|
+
promptText: string;
|
|
253
|
+
/** Number of messages encoded in {@link promptText} (the telemetry chain messageCount). */
|
|
254
|
+
messageCount: number;
|
|
255
|
+
/** The assistant's response text, as a plain string (`''` for a tool-only turn). */
|
|
256
|
+
responseText: string;
|
|
257
|
+
/** The reasoning/thinking trace, as a plain string (`''` when none). */
|
|
258
|
+
reasoningText: string;
|
|
259
|
+
/**
|
|
260
|
+
* FRESH (uncached) input tokens: exclusive of BOTH cache classes below, so the three
|
|
261
|
+
* are orthogonal and additive. Every producer normalises to this — reading the already
|
|
262
|
+
* exclusive field where the vendor reports the classes apart (Anthropic), subtracting
|
|
263
|
+
* the cached share where the vendor reports an inclusive prompt count (Codex/OpenAI).
|
|
264
|
+
*/
|
|
265
|
+
inputTokens: number;
|
|
266
|
+
/** Input tokens served from the vendor's prompt cache (~0.1× base input). */
|
|
267
|
+
cacheReadTokens: number;
|
|
268
|
+
/**
|
|
269
|
+
* Input tokens written INTO the vendor's cache (1.25–2× base input — dearer than fresh),
|
|
270
|
+
* kept apart from the reads so a loop that keeps re-writing the prefix is distinguishable
|
|
271
|
+
* from one riding a warm cache. 0 where the CLI reports no separate write class.
|
|
272
|
+
*/
|
|
273
|
+
cacheWriteTokens: number;
|
|
274
|
+
outputTokens: number;
|
|
275
|
+
/** The provider finish/stop reason when the CLI reports one (else null). */
|
|
276
|
+
finishReason: string | null;
|
|
277
|
+
/**
|
|
278
|
+
* This call's position in the JOB's telemetry sequence, stamped by the job registry the
|
|
279
|
+
* moment the call is emitted (see `RunOptions.onCallMetric`). It is what makes a call's
|
|
280
|
+
* recorded row id stable across the two channels that carry it: the live drain (per poll,
|
|
281
|
+
* so a run's telemetry is inspectable WHILE it runs) and the terminal result (the complete
|
|
282
|
+
* list, so a transport that doesn't drain still records everything). Both channels hold the
|
|
283
|
+
* SAME metric objects, so both mint the same `<jobId>-hc-<seq>` row id and the backend's
|
|
284
|
+
* second write of an already-recorded call is a no-op instead of a duplicate row.
|
|
285
|
+
*
|
|
286
|
+
* Absent only when a producer built a metric without emitting it live; the recorder then
|
|
287
|
+
* falls back to the array index, which is what it always used before streaming existed.
|
|
288
|
+
*/
|
|
289
|
+
seq?: number;
|
|
290
|
+
/**
|
|
291
|
+
* The run PHASE that spent this call (`agent` / `validation-repair` / `reproduction-repair` /
|
|
292
|
+
* …), stamped by the job registry from the same marker the handlers set as they enter each
|
|
293
|
+
* phase — so the phase axis on `llm_call_metrics` comes from the component that owns the
|
|
294
|
+
* boundary rather than from a downstream guess
|
|
295
|
+
* (`docs/initiatives/token-burn-instrumentation.md`).
|
|
296
|
+
*
|
|
297
|
+
* Stamped on the SAME object as {@link seq}, so the live drain and the terminal result can
|
|
298
|
+
* never disagree about which phase billed a call.
|
|
299
|
+
*/
|
|
300
|
+
phase?: string;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Publish one captured model call: append it to the run's list (which becomes the terminal
|
|
304
|
+
* result's `callMetrics`) AND hand the SAME object to the live stream, where the job registry
|
|
305
|
+
* stamps its {@link HarnessCallMetric.seq} and buffers it for the next poll to drain.
|
|
306
|
+
*
|
|
307
|
+
* Every producer goes through here rather than a bare `calls.push`, so the two channels can't
|
|
308
|
+
* drift: a call that reaches the terminal list but never the live stream would be invisible
|
|
309
|
+
* until the job ends, and one that reaches only the live stream would go unrecorded if the
|
|
310
|
+
* poll response were lost.
|
|
311
|
+
*
|
|
312
|
+
* A published call must be FINAL. The backend records it the moment the drain reaches it and
|
|
313
|
+
* IGNORES the terminal repeat (first write wins, so its stored prompt delta stays valid against
|
|
314
|
+
* the chain tip it was written against), which means a field mutated after publishing never
|
|
315
|
+
* reaches the store. A producer whose calls can still change (the cumulative-usage fallback,
|
|
316
|
+
* whose totals arrive with the CLI's terminal `result` event) publishes through
|
|
317
|
+
* {@link createCallMetricPublisher} instead, which withholds exactly those.
|
|
318
|
+
*/
|
|
319
|
+
export declare function publishCallMetric(calls: HarnessCallMetric[], call: HarnessCallMetric, onCallMetric?: (call: HarnessCallMetric) => void): void;
|
|
320
|
+
/** Appends captured calls to a run's list, streaming each one as soon as it is final. */
|
|
321
|
+
export interface CallMetricPublisher {
|
|
322
|
+
/** Append a captured call, streaming it now unless its tokens can still be rewritten. */
|
|
323
|
+
publish(call: HarnessCallMetric): void;
|
|
324
|
+
/** Stream whatever is still withheld. Call once the run's totals are attributed. */
|
|
325
|
+
flush(): void;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* A {@link publishCallMetric} wrapper for a producer whose per-call tokens may be filled in at
|
|
329
|
+
* the END of the run: a CLI that reports only a cumulative total leaves every turn at zero, and
|
|
330
|
+
* `attributeCumulativeUsage` pins the total onto the last call once the terminal `result` event
|
|
331
|
+
* arrives.
|
|
332
|
+
*
|
|
333
|
+
* Since a published call must be final (the backend stores it on the drain and ignores the
|
|
334
|
+
* terminal repeat), a call the CLI did NOT cost is appended to the list but WITHHELD from the
|
|
335
|
+
* live stream — otherwise it records as a zero-token row and the attributed numbers never land.
|
|
336
|
+
* The withholding window closes the moment any call IS costed: attribution can no longer fire, so
|
|
337
|
+
* everything held is final and released at once, in capture order, and every later call streams
|
|
338
|
+
* immediately whatever its tokens. {@link flush} covers the run that was never costed at all.
|
|
339
|
+
*/
|
|
340
|
+
export declare function createCallMetricPublisher(calls: HarnessCallMetric[], onCallMetric?: (call: HarnessCallMetric) => void): CallMetricPublisher;
|
|
341
|
+
/** Pi's assistant summary plus {@link PiRunStats} describing what it did. */
|
|
342
|
+
export interface PiRunOutcome {
|
|
343
|
+
summary: string;
|
|
344
|
+
stats: PiRunStats;
|
|
345
|
+
/**
|
|
346
|
+
* Tail of Pi's stderr (credential-scrubbed), captured even on a clean exit.
|
|
347
|
+
* On a no-op run this is where the real cause shows up — e.g. an unreachable
|
|
348
|
+
* proxy or a model the upstream rejected — so the failure is diagnosable
|
|
349
|
+
* without shelling into the (ephemeral) container.
|
|
350
|
+
*/
|
|
351
|
+
stderrTail?: string;
|
|
352
|
+
/**
|
|
353
|
+
* Token usage lifted from the agent CLI's own event stream. Reported by the
|
|
354
|
+
* subscription harnesses (Claude Code / Codex), whose traffic bypasses the LLM
|
|
355
|
+
* proxy — so the backend folds it into the leased token's rolling-window counters
|
|
356
|
+
* (usage-aware rotation) and telemetry. Absent for the proxy-metered Pi harness.
|
|
357
|
+
*/
|
|
358
|
+
usage?: {
|
|
359
|
+
inputTokens: number;
|
|
360
|
+
outputTokens: number;
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Per-model-call telemetry lifted from a subscription harness's CLI event stream
|
|
364
|
+
* (Claude Code / Codex), which the backend records into `llm_call_metrics` — the
|
|
365
|
+
* proxy-bypassing analogue of the per-call rows the LLM proxy writes for Pi. Absent
|
|
366
|
+
* for the proxy-metered Pi harness (the proxy is its metering point). See
|
|
367
|
+
* {@link HarnessCallMetric}.
|
|
368
|
+
*/
|
|
369
|
+
callMetrics?: HarnessCallMetric[];
|
|
370
|
+
/** Output-quality signals (truncation / empty final answer); see {@link RunDiagnostics}. */
|
|
371
|
+
diagnostics?: RunDiagnostics;
|
|
372
|
+
/**
|
|
373
|
+
* The agent's effort self-assessment, lifted from its sentinel file after the run (how hard the
|
|
374
|
+
* work was, what reduced its effectiveness, the key obstacles). Absent when the agent wrote none.
|
|
375
|
+
* See {@link EffortReport}.
|
|
376
|
+
*/
|
|
377
|
+
effortReport?: EffortReport;
|
|
378
|
+
}
|
|
379
|
+
export declare function parseTodoProgress(event: Record<string, unknown>): TodoProgress | undefined;
|
|
380
|
+
/**
|
|
381
|
+
* Run Pi non-interactively against `cwd` and return its assistant summary. Uses
|
|
382
|
+
* print + JSON mode (`-p --mode json`) with `--approve` so it runs unattended.
|
|
383
|
+
*
|
|
384
|
+
* The (untrusted) prompt is fed over stdin, never as an argv positional, so a
|
|
385
|
+
* prompt beginning with `-`/`--` can't be mis-parsed as a Pi CLI flag (Pi has no
|
|
386
|
+
* `--` end-of-options terminator, so a positional `-foo` errors as "Unknown
|
|
387
|
+
* option"). Pi's print mode reads the prompt from piped stdin; we write it and
|
|
388
|
+
* close the pipe so Pi gets an immediate EOF and proceeds (an open, never-closed
|
|
389
|
+
* stdin pipe would make print mode block forever waiting for EOF).
|
|
390
|
+
*/
|
|
391
|
+
export declare function runPi(opts: {
|
|
392
|
+
cwd: string;
|
|
393
|
+
model: string;
|
|
394
|
+
userPrompt: string;
|
|
395
|
+
sessionToken: string;
|
|
396
|
+
/** Aborting this kills Pi (the job's inactivity/max-duration watchdog). */
|
|
397
|
+
signal?: AbortSignal;
|
|
398
|
+
/** Called on every chunk of Pi output, so the watchdog sees the agent is alive. */
|
|
399
|
+
onActivity?: () => void;
|
|
400
|
+
/** Called with the latest subtask counts each time Pi updates its todo list. */
|
|
401
|
+
onProgress?: (progress: TodoProgress) => void;
|
|
402
|
+
/**
|
|
403
|
+
* Called once per completed tool call with a compact {@link ToolSpan}. Feeds the
|
|
404
|
+
* run's observability trace (drained by the backend on its job poll); a no-op when
|
|
405
|
+
* the container payload doesn't pass it, so production behaviour is unchanged.
|
|
406
|
+
*/
|
|
407
|
+
onSpan?: (span: ToolSpan) => void;
|
|
408
|
+
/**
|
|
409
|
+
* Called with every parsed Pi `--mode json` event, in stream order — the raw
|
|
410
|
+
* observability seam over the run. Used by offline tooling (the smoketest
|
|
411
|
+
* harness) to capture the full prompt/response/tool-call transcript for
|
|
412
|
+
* analysis; the container payload doesn't pass it, so production behaviour is
|
|
413
|
+
* unchanged. Throwing handlers are swallowed so a faulty observer can't break
|
|
414
|
+
* the run.
|
|
415
|
+
*/
|
|
416
|
+
onEvent?: (event: Record<string, unknown>) => void;
|
|
417
|
+
/** No-progress guard bounds; defaults to the env-configured limits. */
|
|
418
|
+
guardLimits?: ProgressGuardLimits;
|
|
419
|
+
/** Whether this run is expected to edit files (false for assess-only runs like the merger). */
|
|
420
|
+
expectsEdits?: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Extra environment for Pi's child process, merged over `process.env` (but under the
|
|
423
|
+
* proxy token). Used to hand the rpiv-web-tools extension its proxy-backed SearXNG
|
|
424
|
+
* config (`SEARXNG_URL` / `SEARXNG_API_KEY`) without mutating the harness's own env.
|
|
425
|
+
*/
|
|
426
|
+
extraEnv?: Record<string, string>;
|
|
427
|
+
}): Promise<PiRunOutcome>;
|
|
428
|
+
/**
|
|
429
|
+
* The terminal-failure message when Pi's run ended in a hard error (the model was
|
|
430
|
+
* unreachable / refused, and Pi exhausted its auto-retries), else undefined. Only
|
|
431
|
+
* the FINAL outcome counts: a mid-run hiccup the agent recovered from leaves a clean
|
|
432
|
+
* terminal `agent_end`, so it returns undefined. Scans from the end and decides on
|
|
433
|
+
* the first terminal signal it meets — the trailing `auto_retry_end` (its `success`
|
|
434
|
+
* flag) or the last `agent_end` (its `stopReason`). Pure so it is unit-testable over
|
|
435
|
+
* a fixed event sequence.
|
|
436
|
+
*/
|
|
437
|
+
export declare function terminalRunError(stdout: string): string | undefined;
|
|
438
|
+
/**
|
|
439
|
+
* Classify a terminal run error whose text points at the LLM PROXY rejecting every model call
|
|
440
|
+
* (auth / quota / rate-limit) into an actionable remedy, else undefined. All model traffic goes
|
|
441
|
+
* through the Worker's OpenAI-compatible proxy, so a 401/402/429 surfaced in Pi's `finalError`
|
|
442
|
+
* means the leased provider key was refused, is out of credit, or was rate-limited — none of
|
|
443
|
+
* which is an agent bug. This is the first-wrap-point for Pi's own error text (per the
|
|
444
|
+
* error-message initiative's I6): we match it ONCE here and let the caller stamp the structured
|
|
445
|
+
* `llm-upstream` cause + this remedy. Pure, so it is unit-tested over fixed error strings.
|
|
446
|
+
*/
|
|
447
|
+
export declare function classifyLlmUpstreamError(finalError: string): string | undefined;
|
|
448
|
+
/**
|
|
449
|
+
* Pi's assistant summary plus {@link PiRunStats}, derived from one pass over its
|
|
450
|
+
* output — the canonical close-of-run signal the harness uses both to report the
|
|
451
|
+
* answer and to detect a no-op run (the agent never acted).
|
|
452
|
+
*/
|
|
453
|
+
export declare function summarizePiRun(stdout: string): PiRunOutcome;
|
|
454
|
+
/**
|
|
455
|
+
* Output-quality signals over the canonical `agent_end` transcript: whether any
|
|
456
|
+
* completion hit the output ceiling (its content was cut off), whether the FINAL
|
|
457
|
+
* completion did, and whether that final turn carried no text at all. Pure so it is
|
|
458
|
+
* unit-testable over a fixed event sequence. Defaults to all-false when there is no
|
|
459
|
+
* terminal transcript (a no-op run is already caught by {@link agentNeverActed}).
|
|
460
|
+
*
|
|
461
|
+
* `cap` is the per-completion ceiling Pi requested ({@link PI_MAX_OUTPUT_TOKENS});
|
|
462
|
+
* truncation is detected by an assistant message whose `usage.output` reached it,
|
|
463
|
+
* which is reliable even when the model reports a non-`length` stop reason (Workers
|
|
464
|
+
* AI labelled a cut-off tool call `tool_calls`, not `length`).
|
|
465
|
+
*/
|
|
466
|
+
export declare function diagnosticsFromEvents(events: Record<string, unknown>[], cap?: number): RunDiagnostics;
|
|
467
|
+
/** {@link RunDiagnostics} over Pi's raw `--mode json` stdout (see {@link diagnosticsFromEvents}). */
|
|
468
|
+
export declare function runDiagnostics(stdout: string, cap?: number): RunDiagnostics;
|
|
469
|
+
/**
|
|
470
|
+
* Extract the assistant's final summary from Pi's JSON-lines output. Pi emits a
|
|
471
|
+
* terminal `agent_end` event whose `messages` is the full transcript, so the
|
|
472
|
+
* last assistant message there is the canonical answer. Falls back to scanning
|
|
473
|
+
* `message_end` events, then to a raw tail, so a schema tweak never loses output.
|
|
474
|
+
*/
|
|
475
|
+
export declare function parsePiOutput(stdout: string): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** The sentinel file the agent writes its PR description to (relative to the checkout root). */
|
|
2
|
+
export declare const PR_DESCRIPTION_FILE = ".cat-pr-description.md";
|
|
3
|
+
/**
|
|
4
|
+
* Ceiling on the agent-authored body.
|
|
5
|
+
*
|
|
6
|
+
* The engine appends its verification report to the SAME body later, and that section carries
|
|
7
|
+
* its own 50,000-character ceiling (`MAX_SECTION_CHARS` in kernel's `hostMarkdown`). GitHub
|
|
8
|
+
* rejects a body over 65,536 with a 422, and the report publisher swallows its own failures —
|
|
9
|
+
* so a briefing budget that does not leave the report room would surface as a report that
|
|
10
|
+
* silently never publishes. 15,000 + 50,000 stays under the limit with room to join them.
|
|
11
|
+
*
|
|
12
|
+
* Exported because the PR-TEMPLATE note states it to the agent (`pr-template.ts`): a filled
|
|
13
|
+
* template is the one briefing shape whose length is dictated by a file the agent did not write,
|
|
14
|
+
* so an agent that does not know the ceiling can answer a long template past it and have
|
|
15
|
+
* {@link capBody} cut the repo's last sections — the very failure the inline budget avoids on the
|
|
16
|
+
* way IN.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_PR_BODY_CHARS = 15000;
|
|
19
|
+
/** Opens the engine-managed region of a PR body (kept in sync with `kernel/domain/pr-report.ts`). */
|
|
20
|
+
export declare const PR_REPORT_MARKER_START = "<!-- cat-factory:verification-report:start -->";
|
|
21
|
+
/** Closes the engine-managed region of a PR body. */
|
|
22
|
+
export declare const PR_REPORT_MARKER_END = "<!-- cat-factory:verification-report:end -->";
|
|
23
|
+
/** An agent-authored PR description: an optional title plus the briefing body. */
|
|
24
|
+
export interface AgentPrDescription {
|
|
25
|
+
title?: string;
|
|
26
|
+
body?: string;
|
|
27
|
+
}
|
|
28
|
+
/** How to read a sentinel. */
|
|
29
|
+
export interface ReadPrDescriptionOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Whether a lone leading `# <title>` heading may be lifted off as the PR title (see
|
|
32
|
+
* {@link splitTitle}). Default `true` — that is what the description guidance asks a free-form
|
|
33
|
+
* briefing for.
|
|
34
|
+
*
|
|
35
|
+
* FALSE when the briefing is a FILLED TEMPLATE (`pr-template.ts`): then the headings are the
|
|
36
|
+
* repo's, not the agent's, and a template whose first heading is its only level-1 one — `#
|
|
37
|
+
* Pull Request` above a set of `##` sections, an entirely ordinary shape — would have that
|
|
38
|
+
* heading silently become the pull request's title, so the PR reads "Pull Request" instead of
|
|
39
|
+
* `<block> (<pipeline>)` and the body loses the heading the repo asked for. The heuristic below
|
|
40
|
+
* is sound for the shape the guidance describes and cannot be made to cover both, so the caller
|
|
41
|
+
* that KNOWS which shape it asked for says so.
|
|
42
|
+
*/
|
|
43
|
+
titleFromHeading?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read + parse + REMOVE the agent's PR-description sentinel from `dir`. Lenient: returns
|
|
47
|
+
* undefined when the file is absent (the agent wrote none) or carries nothing usable. Never
|
|
48
|
+
* throws — a bad description must never fail an otherwise-good run; the caller falls back to
|
|
49
|
+
* the dispatch-time text.
|
|
50
|
+
*
|
|
51
|
+
* A SINGLE `# <title>` heading on the first line sets the PR title; everything after it is the
|
|
52
|
+
* body (see {@link splitTitle} for why a LONE heading is required, and
|
|
53
|
+
* {@link ReadPrDescriptionOptions.titleFromHeading} for the caller that must switch it off). The
|
|
54
|
+
* whole text is secret-scrubbed, an over-budget body is truncated WITH a visible note (a silent
|
|
55
|
+
* cut would read as the complete briefing), and both halves are made inert for the host.
|
|
56
|
+
*
|
|
57
|
+
* On scrubbing: `redactSecrets`'s credential-assignment rule is deliberately eager, so a
|
|
58
|
+
* briefing sentence like "the token: handling changed" loses its next word. That is the right
|
|
59
|
+
* trade for a surface this public — the rule is shared with every other redaction path, and
|
|
60
|
+
* narrowing it so prose reads better would weaken all of them.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readPrDescription(dir: string, opts?: ReadPrDescriptionOptions): Promise<AgentPrDescription | undefined>;
|
|
63
|
+
/**
|
|
64
|
+
* Fold an agent-authored description over the dispatch-time fallback the job body carries.
|
|
65
|
+
* Field-wise: the agent's title/body each win when present, so a body-only briefing keeps the
|
|
66
|
+
* backend-composed title and vice versa.
|
|
67
|
+
*/
|
|
68
|
+
export declare function applyPrDescription(fallback: {
|
|
69
|
+
title: string;
|
|
70
|
+
body: string;
|
|
71
|
+
}, agent: AgentPrDescription | undefined): {
|
|
72
|
+
title: string;
|
|
73
|
+
body: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* The body to PATCH onto an ALREADY-OPEN pull request when a resumed run produced a fresh
|
|
77
|
+
* briefing: the new description followed by whatever the engine's managed verification-report
|
|
78
|
+
* region currently holds.
|
|
79
|
+
*
|
|
80
|
+
* Carrying the region across is what makes the refresh safe. The engine re-publishes the report
|
|
81
|
+
* on every step settlement, so dropping it here would usually self-heal — but "usually" is not
|
|
82
|
+
* a property to rest the one artefact a reviewer reads on, and a run that settles no further
|
|
83
|
+
* step (the work is already merged, the run failed after its push) would never restore it.
|
|
84
|
+
*/
|
|
85
|
+
export declare function preserveManagedSection(currentBody: string | undefined, nextBody: string): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { RepoSpec } from './job.js';
|
|
2
|
+
import type { Logger } from './logger.js';
|
|
3
|
+
/** The VCS providers a repo can live on. Bound to `RepoSpec` so the two cannot drift. */
|
|
4
|
+
type ProviderName = NonNullable<RepoSpec['provider']>;
|
|
5
|
+
/**
|
|
6
|
+
* How much template text is inlined into the agent's prompt.
|
|
7
|
+
*
|
|
8
|
+
* Over this, the template is NAMED rather than inlined and the agent is told to read it from the
|
|
9
|
+
* checkout — which it can, because the file is on disk. That is strictly better than the
|
|
10
|
+
* alternatives: truncating a template would have the agent fill a structure whose tail it never
|
|
11
|
+
* saw (silently dropping the repo's last sections), and skipping it entirely would abandon the
|
|
12
|
+
* feature on exactly the repos with the most demanding process.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_INLINE_PR_TEMPLATE_CHARS = 8000;
|
|
15
|
+
/**
|
|
16
|
+
* The shared inline budget across a multi-repo run's legs. Each repo's template competes for it in
|
|
17
|
+
* leg order, and a leg that does not fit is NAMED rather than inlined (as above) — so a workspace
|
|
18
|
+
* of four template-carrying repos cannot quietly consume 32k of the agent's prompt.
|
|
19
|
+
*/
|
|
20
|
+
export declare const MAX_TOTAL_INLINE_PR_TEMPLATE_CHARS = 12000;
|
|
21
|
+
/** A discovered pull-request template. */
|
|
22
|
+
export interface PrTemplate {
|
|
23
|
+
/** Repo-root-relative path, forward-slashed (it is prose an agent reads). */
|
|
24
|
+
path: string;
|
|
25
|
+
/**
|
|
26
|
+
* The template's size. ALWAYS the real one, including when {@link text} is absent: an over-budget
|
|
27
|
+
* template reporting `chars: 0` would read in the log exactly like an empty file, which is the
|
|
28
|
+
* one thing discovery treats as "no template at all".
|
|
29
|
+
*/
|
|
30
|
+
chars: number;
|
|
31
|
+
/** The template text. Absent ⇒ over budget, so the agent is told to read {@link path} itself. */
|
|
32
|
+
text?: string;
|
|
33
|
+
}
|
|
34
|
+
/** One checkout to look for a template in. */
|
|
35
|
+
export interface PrTemplateTarget {
|
|
36
|
+
/** The repository checkout root (NOT a monorepo service subtree — a template is a repo fact). */
|
|
37
|
+
repoDir: string;
|
|
38
|
+
provider?: ProviderName;
|
|
39
|
+
/** Names this repo in the note. Omit when the run has a single checkout ("this repository"). */
|
|
40
|
+
repoLabel?: string;
|
|
41
|
+
}
|
|
42
|
+
/** What {@link resolvePrTemplateNote} tells the run about the templates it found. */
|
|
43
|
+
export interface PrTemplateResolution {
|
|
44
|
+
/** The prompt note, or absent when no target ships a template (which is most repos). */
|
|
45
|
+
note?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The `repoDir`s whose briefing is a FILLED TEMPLATE. The push phase reads those sentinels with
|
|
48
|
+
* `titleFromHeading: false`, because the headings in them are the repo's — see
|
|
49
|
+
* `ReadPrDescriptionOptions.titleFromHeading`. A SET keyed by directory rather than a boolean
|
|
50
|
+
* because a multi-repo run's legs need not all ship a template.
|
|
51
|
+
*/
|
|
52
|
+
templated: ReadonlySet<string>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* THE entry point: find each target's pull-request template and build the prompt note that asks
|
|
56
|
+
* the agent to fill it, plus the set of checkouts whose sentinel will therefore hold a filled
|
|
57
|
+
* template rather than a free-form briefing (see {@link PrTemplateResolution}).
|
|
58
|
+
*
|
|
59
|
+
* Pass NO targets for a dispatch that opens no pull request — an in-place fixer amending someone
|
|
60
|
+
* else's PR, a read-only explore run. Asking such a run to fill a template would be asking for a
|
|
61
|
+
* document nothing publishes.
|
|
62
|
+
*
|
|
63
|
+
* Never rejects: a template is an improvement to a PR body, so no failure reading one may cost a
|
|
64
|
+
* run that otherwise succeeded. An unreadable or empty template is simply no template.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolvePrTemplateNote(args: {
|
|
67
|
+
targets: PrTemplateTarget[];
|
|
68
|
+
logger: Logger;
|
|
69
|
+
}): Promise<PrTemplateResolution>;
|
|
70
|
+
/**
|
|
71
|
+
* Locate the template in `repoDir`, probing the repo's OWN host convention first and the other
|
|
72
|
+
* host's second — a repo mirrored across both, or one whose provider the dispatcher did not set,
|
|
73
|
+
* still gets its template respected rather than falling to whichever list happened to be first.
|
|
74
|
+
*/
|
|
75
|
+
export declare function discoverPrTemplate(repoDir: string, provider?: ProviderName): Promise<PrTemplate | undefined>;
|
|
76
|
+
/**
|
|
77
|
+
* The prompt note. It has to do more than show the template, because the agent has already been
|
|
78
|
+
* told (by the backend-composed `PR_DESCRIPTION_GUIDANCE`) to write a free-form briefing, and the
|
|
79
|
+
* two genuinely conflict: a template that asks for a test plan or a checklist is asking for
|
|
80
|
+
* exactly the "restated diff" that guidance rules out. So the note states which wins, and states
|
|
81
|
+
* why the template is not already applied — an agent that believes the host will merge the
|
|
82
|
+
* template with its text has no reason to reproduce the structure itself.
|
|
83
|
+
*
|
|
84
|
+
* The template is delimited with `fencedOutput`, the same helper every other captured-text-into-a-
|
|
85
|
+
* prompt path uses. Templates routinely carry fenced blocks of their own, and a fixed three-tick
|
|
86
|
+
* wrapper closes on the first of them — spilling the rest of the template, and the instructions
|
|
87
|
+
* after it, into the prompt as prose. `fencedOutput` sizes the fence one tick longer than the
|
|
88
|
+
* longest run in the body, which is what CommonMark specifies for exactly this, so no template can
|
|
89
|
+
* break out of its own block. A plain `--- BEGIN/END ---` rule would read more nicely and is
|
|
90
|
+
* trivially forgeable by the template's own content, which is the whole thing being defended.
|
|
91
|
+
*/
|
|
92
|
+
export declare function buildPrTemplateNote(template: PrTemplate, repoLabel?: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Fold the note into a prompt. The sibling of `withDependencyNote`, and deliberately not inlined
|
|
95
|
+
* for the same reason: it rides EVERY agent pass, including the validation and reproduction
|
|
96
|
+
* REPAIR passes. Those start a fresh agent that still carries the description guidance in its
|
|
97
|
+
* system prompt, so one that is not also told about the template would rewrite the briefing
|
|
98
|
+
* free-form and undo the filled template the first pass produced.
|
|
99
|
+
*/
|
|
100
|
+
export declare function withPrTemplateNote(userPrompt: string, note: string | undefined): string;
|
|
101
|
+
export {};
|