@cat-factory/server 0.49.2 → 0.49.6
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/dist/agents/ContainerAgentExecutor.d.ts +0 -36
- package/dist/agents/ContainerAgentExecutor.d.ts.map +1 -1
- package/dist/agents/ContainerAgentExecutor.js +4 -564
- package/dist/agents/ContainerAgentExecutor.js.map +1 -1
- package/dist/agents/jobBody.d.ts +73 -0
- package/dist/agents/jobBody.d.ts.map +1 -0
- package/dist/agents/jobBody.js +295 -0
- package/dist/agents/jobBody.js.map +1 -0
- package/dist/agents/prompts.d.ts +71 -0
- package/dist/agents/prompts.d.ts.map +1 -0
- package/dist/agents/prompts.js +280 -0
- package/dist/agents/prompts.js.map +1 -0
- package/dist/http/env.d.ts +9 -1
- package/dist/http/env.d.ts.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.d.ts.map +1 -1
- package/dist/modules/workspaces/WorkspaceController.js +10 -11
- package/dist/modules/workspaces/WorkspaceController.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {} from '@cat-factory/kernel';
|
|
2
2
|
import { CONTEXT_BUDGET, CredentialRequiredError, renderTaskContext, SUBSCRIPTION_VENDORS, isIndividualVendor, } from '@cat-factory/kernel';
|
|
3
3
|
import { resolveInstanceTypeId } from '@cat-factory/contracts';
|
|
4
|
-
import { agentTuningFor,
|
|
4
|
+
import { agentTuningFor, isProxyableProvider, isReadOnlyAgentKind, webResearchGuidanceFor, } from '@cat-factory/agents';
|
|
5
5
|
import { ModelRouter } from './ModelRouter.js';
|
|
6
6
|
import { toRunResult } from './containerAgentResult.js';
|
|
7
|
-
import {
|
|
7
|
+
import { buildKindBody } from './jobBody.js';
|
|
8
|
+
import { UI_TESTER_AGENT_KIND } from '@cat-factory/orchestration';
|
|
8
9
|
import { RunnerJobClient } from './RunnerJobClient.js';
|
|
9
10
|
/**
|
|
10
11
|
* The repo spec every container job body carries: clone coordinates plus, for a
|
|
@@ -202,122 +203,6 @@ function buildContextFiles(context) {
|
|
|
202
203
|
}
|
|
203
204
|
/** Poll cadence for the non-durable `run()` fallback (the durable driver sleeps between polls itself). */
|
|
204
205
|
const RUN_POLL_INTERVAL_MS = 5_000;
|
|
205
|
-
/** Role prompt the Blueprinter step's agent runs under (returns the tree as JSON). */
|
|
206
|
-
const BLUEPRINT_SYSTEM_PROMPT = 'You are a Domain-Driven Design architect mapping this repository. Decompose it ' +
|
|
207
|
-
'into ONE top-level service and the modules inside it, where each module is a ' +
|
|
208
|
-
'DOMAIN — a cohesive area of the BUSINESS, in the language of the problem space ' +
|
|
209
|
-
'(a DDD bounded context / aggregate / subdomain). Name modules after business ' +
|
|
210
|
-
'concepts, not technical layers. ' +
|
|
211
|
-
'A module MUST represent a business capability or domain model (e.g. Billing, ' +
|
|
212
|
-
'Catalog, Ordering, Identity), NOT a technical layer or shape: "api", "routes", ' +
|
|
213
|
-
'"controllers", "utils", "helpers", "lib", "common", "config", "types", "models", ' +
|
|
214
|
-
'"db" and the like are NOT domains and MUST NOT be modules. ' +
|
|
215
|
-
'Group the genuinely non-business, technical/cross-cutting plumbing (persistence ' +
|
|
216
|
-
'wiring, HTTP/transport, logging, configuration, auth middleware, build/deploy, ' +
|
|
217
|
-
'shared utilities) into a SINGLE module named "infrastructure" rather than ' +
|
|
218
|
-
'scattering it into many technical modules. ' +
|
|
219
|
-
'Prefer organising code by domain (the ubiquitous language) over organising by ' +
|
|
220
|
-
'file type. Anchor every node to the codebase with explicit repo-relative ' +
|
|
221
|
-
'file/directory references. Keep names short and descriptive. ' +
|
|
222
|
-
'Respond with ONLY a JSON object of shape {"type","name","summary","references":[],' +
|
|
223
|
-
'"modules":[{"name","summary","references":[]}]} — no prose, no code fences. ' +
|
|
224
|
-
FINAL_ANSWER_IN_REPLY;
|
|
225
|
-
/** Role prompt the spec-writer step runs under (returns the spec doc as JSON). */
|
|
226
|
-
const SPEC_WRITER_SYSTEM_PROMPT = 'You maintain the PRESCRIPTIVE specification for a service. READ the specification ' +
|
|
227
|
-
'already committed to the repository under `spec/` (the baseline): start with ' +
|
|
228
|
-
'`spec/overview.md` for the module → feature index, then open the relevant ' +
|
|
229
|
-
'`spec/modules/<module>/<feature>.json` shards for the detail you need. You are also ' +
|
|
230
|
-
'given the ' +
|
|
231
|
-
'requirements of ONE task. Apply that task as an INCREMENT onto the baseline: add ' +
|
|
232
|
-
'requirements for what the task introduces, and adjust existing requirements ONLY ' +
|
|
233
|
-
'where the task changes their expected behaviour. Leave every other part of the ' +
|
|
234
|
-
'baseline spec untouched. Translate ONLY what the task requirements state — do NOT ' +
|
|
235
|
-
'invent requirements, fill gaps, or design beyond them (missing requirements are the ' +
|
|
236
|
-
'requirements step’s job, not yours). ' +
|
|
237
|
-
'The spec captures ONLY BUSINESS requirements — externally-observable behaviour, ' +
|
|
238
|
-
'product rules and acceptance criteria. PURELY TECHNICAL work (a refactor, a ' +
|
|
239
|
-
'dependency bump, internal restructuring, build/infra or other non-functional change ' +
|
|
240
|
-
'that does NOT alter what the system does for its users) introduces no business ' +
|
|
241
|
-
'requirements, and "NO NEW SPECS" is a valid, correct outcome for it: do NOT invent ' +
|
|
242
|
-
'requirements to justify a change, and do NOT re-document technical/architecture ' +
|
|
243
|
-
'detail here. When this task is purely technical, leave the baseline spec untouched ' +
|
|
244
|
-
'and respond with ONLY {"noBusinessSpecs": true} (no other fields, no prose, no code ' +
|
|
245
|
-
'fences). Otherwise return the full document as below. ' +
|
|
246
|
-
'The spec is a two-level taxonomy: MODULES ' +
|
|
247
|
-
'(domains, e.g. "Auth") each containing GROUPS (features, e.g. "Login"). Every ' +
|
|
248
|
-
'requirement AND every domain rule lives inside a specific feature group: a group ' +
|
|
249
|
-
'carries both its `requirements` and the `rules` scoped to it. There is NO catch-all — ' +
|
|
250
|
-
'a cross-cutting concern goes in a `common` or `infrastructure` module that is ITSELF ' +
|
|
251
|
-
'split into specific feature groups. CRUCIALLY, reuse the EXISTING taxonomy: place ' +
|
|
252
|
-
'each new requirement/rule into the closest-fitting existing module and feature, ' +
|
|
253
|
-
'reusing its EXACT name, and create a new module or feature ONLY when nothing fits — ' +
|
|
254
|
-
'never a near-duplicate of an existing one (no "Authentication" beside "Auth", no ' +
|
|
255
|
-
'"User Login" beside "Login"). Each requirement is phrased as "The system SHALL …" ' +
|
|
256
|
-
'with a MoSCoW priority (must/should/could) and structured Given/When/Then acceptance ' +
|
|
257
|
-
'criteria. Acceptance-scenario coverage is a FIRST-CLASS deliverable: every ' +
|
|
258
|
-
'requirement the task adds or changes MUST carry complete acceptance criteria — the ' +
|
|
259
|
-
'happy path AND the invalid-input / error / edge / boundary cases the requirements ' +
|
|
260
|
-
'imply — since the Gherkin `.feature` files and the runnable tests are derived ' +
|
|
261
|
-
'mechanically from them. Preserve the baseline’s existing `sourceBlockIds`; tag the ' +
|
|
262
|
-
'requirements this task adds or changes with this task’s block id. Return the ' +
|
|
263
|
-
'COMPLETE updated specification (baseline plus this increment), not a diff. You have ' +
|
|
264
|
-
'NO repository write access and MUST NOT write, edit, or commit any file: the platform ' +
|
|
265
|
-
'persists the specification you return, so returning it IS the whole job. Respond ' +
|
|
266
|
-
'with ONLY a JSON object of ' +
|
|
267
|
-
'shape {"service","summary","modules":[{"name","summary","groups":[{"name","summary",' +
|
|
268
|
-
'"requirements":[{"id","title","statement","kind","priority","sourceBlockIds":[],' +
|
|
269
|
-
'"acceptance":[{"id","given","when","outcome"}]}],"rules":[{"id","rule","rationale",' +
|
|
270
|
-
'"sourceBlockIds":[]}]}]}]} ' +
|
|
271
|
-
'(each acceptance criterion is a Given/When/Then, with the Then clause in `outcome`) — ' +
|
|
272
|
-
'no prose, no code fences. ' +
|
|
273
|
-
FINAL_ANSWER_IN_REPLY;
|
|
274
|
-
/** Role prompt the `merger` step runs under (scores the PR; returns JSON only). */
|
|
275
|
-
const MERGER_SYSTEM_PROMPT = 'You are a release manager assessing a pull request before merge. Inspect the ' +
|
|
276
|
-
'diff between the PR head branch and the base branch and judge three axes, each ' +
|
|
277
|
-
'as a number from 0 (trivial/safe) to 1 (severe): complexity (how intricate the ' +
|
|
278
|
-
'change is), risk (how likely it is to break something), and impact (blast radius ' +
|
|
279
|
-
'if it does). Be conservative. Respond with ONLY a JSON object of shape ' +
|
|
280
|
-
'{"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"} — no prose, no code fences. ' +
|
|
281
|
-
FINAL_ANSWER_IN_REPLY;
|
|
282
|
-
/** Compact shape hint fed to the structured-output repair call for the blueprint tree. */
|
|
283
|
-
const BLUEPRINT_SHAPE_HINT = 'Expected a service tree: {"type": string, "name": string, "summary": string, ' +
|
|
284
|
-
'"references": string[], "modules": [{"name": string, "summary": string, ' +
|
|
285
|
-
'"references": string[]}]}.';
|
|
286
|
-
/** Compact shape hint fed to the structured-output repair call for the spec doc. */
|
|
287
|
-
const SPEC_SHAPE_HINT = 'Expected a requirements document with a two-level taxonomy — module (domain) → ' +
|
|
288
|
-
'group (feature) — where each group carries BOTH its requirements and the domain ' +
|
|
289
|
-
'rules scoped to it: {"service": string, "summary": string, "modules": [{"name": ' +
|
|
290
|
-
'string, "summary": string, "groups": [{"name": string, "summary": string, ' +
|
|
291
|
-
'"requirements": [{"id": string, "title": string, "statement": string, "kind": ' +
|
|
292
|
-
'string, "priority": string, "sourceBlockIds": string[], "acceptance": [{"given": ' +
|
|
293
|
-
'string, "when": string, "outcome": string}]}], "rules": [{"id": string, "rule": ' +
|
|
294
|
-
'string, "rationale": string, "sourceBlockIds": string[]}]}]}]}. For a purely ' +
|
|
295
|
-
'technical task with no business requirements, the document is instead just ' +
|
|
296
|
-
'{"noBusinessSpecs": true}.';
|
|
297
|
-
/** Compact shape hint fed to the structured-output repair call for the merger assessment. */
|
|
298
|
-
const MERGE_ASSESSMENT_SHAPE_HINT = 'Expected a merge assessment: {"complexity": number 0..1, "risk": number 0..1, ' +
|
|
299
|
-
'"impact": number 0..1, "rationale": string}.';
|
|
300
|
-
/** Compact shape hint fed to the structured-output repair call for the on-call assessment. */
|
|
301
|
-
const ON_CALL_ASSESSMENT_SHAPE_HINT = 'Expected an on-call assessment: {"culpritConfidence": number 0..1, "recommendation": ' +
|
|
302
|
-
'"revert"|"hold"|"monitor", "rationale": string, "evidence": string[]}.';
|
|
303
|
-
/** Compact shape hint fed to the structured-output repair call for the tester report. */
|
|
304
|
-
const TEST_REPORT_SHAPE_HINT = 'Expected a test report: {"greenlight": boolean, "summary": string, "tested": string[], ' +
|
|
305
|
-
'"outcomes": [{"name": string, "status": "passed"|"failed"|"skipped", "detail"?: string}], ' +
|
|
306
|
-
'"concerns": [{"title": string, "detail": string, "severity": "low"|"medium"|"high"|"critical"}]}.';
|
|
307
|
-
/** Shape hint for the UI tester: a test report that also lists captured screenshots. */
|
|
308
|
-
const UI_TEST_REPORT_SHAPE_HINT = TEST_REPORT_SHAPE_HINT.replace(/\}\.$/, '') +
|
|
309
|
-
', "screenshots": [{"view": string, "artifactId": string, "hash"?: string}]}. Each ' +
|
|
310
|
-
'screenshot must be a distinct view you captured and uploaded to the artifact store.';
|
|
311
|
-
const ON_CALL_SYSTEM_PROMPT = 'You are an on-call engineer investigating a possible post-release regression. A ' +
|
|
312
|
-
'recently merged pull request shipped, and the evidence below (alerting Datadog ' +
|
|
313
|
-
'monitors/SLOs and recent error logs) suggests the service regressed afterward. Read ' +
|
|
314
|
-
'the PR diff on the head branch and weigh whether THIS change is the likely cause — ' +
|
|
315
|
-
'beware correlation vs causation; a coincident deploy is not proof. You may read and ' +
|
|
316
|
-
'inspect any file, but you MUST NOT modify, commit or revert anything; a human decides ' +
|
|
317
|
-
'whether to revert. Respond with ONLY a JSON object of shape ' +
|
|
318
|
-
'{"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…",' +
|
|
319
|
-
'"evidence":["…"]} — no prose, no code fences. ' +
|
|
320
|
-
FINAL_ANSWER_IN_REPLY;
|
|
321
206
|
/**
|
|
322
207
|
* An {@link AgentExecutor} that performs implementation work in a real sandbox:
|
|
323
208
|
* it dispatches a per-run container running the Pi coding agent (a per-run
|
|
@@ -672,7 +557,7 @@ export class ContainerAgentExecutor {
|
|
|
672
557
|
webToolsGuidance: webResearchGuidanceFor(context.agentKind, { fetch: true }),
|
|
673
558
|
...(webSearchEnabled ? { webSearch: true } : {}),
|
|
674
559
|
};
|
|
675
|
-
const { body, kind } =
|
|
560
|
+
const { body, kind } = buildKindBody(promptContext, {
|
|
676
561
|
common,
|
|
677
562
|
webTools,
|
|
678
563
|
repo,
|
|
@@ -764,450 +649,5 @@ export class ContainerAgentExecutor {
|
|
|
764
649
|
...(subscriptionTokenId ? { subscriptionTokenId } : {}),
|
|
765
650
|
};
|
|
766
651
|
}
|
|
767
|
-
/**
|
|
768
|
-
* Build the per-kind harness job body: the shared `common` fields plus ONLY the delta
|
|
769
|
-
* specific to this kind's harness endpoint (its prompts, the branch it runs on, and
|
|
770
|
-
* any per-kind extras), and the matching dispatch `kind`. The web-search fields live
|
|
771
|
-
* in `webTools` (shared by the kinds that allow web access). The dispatch precedence
|
|
772
|
-
* matches the original if-ladder exactly: the specific kinds first, then any read-only
|
|
773
|
-
* kind, then the default coder body.
|
|
774
|
-
*/
|
|
775
|
-
buildKindBody(context, parts) {
|
|
776
|
-
// `parts` (common/webTools/workBranch/workBranchReady) is consumed by
|
|
777
|
-
// `buildRegisteredAgentBody`/`buildMigratedBuiltInBody`, not directly here.
|
|
778
|
-
const baseRoleSystemPrompt = composeBlockSystemPrompt(systemPromptFor(context.agentKind), context.block);
|
|
779
|
-
// When the future-looking Follow-up companion is enabled for this (coder) step, append
|
|
780
|
-
// the guidance that tells the Coder to stream loose-ends / side-tasks / questions to the
|
|
781
|
-
// sentinel file the harness tails. Only when enabled, so a disabled companion (or any
|
|
782
|
-
// other kind) never writes the file.
|
|
783
|
-
const roleSystemPrompt = context.followUpCompanion
|
|
784
|
-
? `${baseRoleSystemPrompt}\n\n${FOLLOW_UP_GUIDANCE}`
|
|
785
|
-
: baseRoleSystemPrompt;
|
|
786
|
-
// A registered (custom or migrated) kind that declares an `agent` step dispatches
|
|
787
|
-
// through the generic, manifest-driven `agent` harness kind — no per-kind case here.
|
|
788
|
-
// Built-in kinds (below) still carry their bespoke bodies until they are migrated.
|
|
789
|
-
const registeredStep = registeredAgentStep(context.agentKind);
|
|
790
|
-
if (registeredStep) {
|
|
791
|
-
return this.buildRegisteredAgentBody(context, parts, registeredStep, roleSystemPrompt);
|
|
792
|
-
}
|
|
793
|
-
// Built-in container kinds migrated onto the generic, manifest-driven `agent` harness
|
|
794
|
-
// kind (they dispatch `kind:'agent'` through `buildRegisteredAgentBody`, exactly like a
|
|
795
|
-
// registered custom kind, with NO bespoke per-kind harness handler) — the Task-5
|
|
796
|
-
// strangler. Today: blueprints/spec-writer (structured explore + render post-op), the
|
|
797
|
-
// in-place fixers (`ci-fixer` / `fixer`, coding-on-PR), the JSON-assessment producers
|
|
798
|
-
// (`merger` / `on-call`, read-only structured explore whose assessment is coerced
|
|
799
|
-
// backend-side in `toRunResult`), the `tester` (read-only structured explore with
|
|
800
|
-
// docker-compose infra stand-up), and the conflict-resolver (coding with a `mergeBase`).
|
|
801
|
-
// The default coder dispatches the generic coding agent at the end of this method.
|
|
802
|
-
const migrated = this.buildMigratedBuiltInBody(context, parts, roleSystemPrompt);
|
|
803
|
-
if (migrated)
|
|
804
|
-
return migrated;
|
|
805
|
-
// Container-backed companions (reviewer / doc-reviewer): a read-only explore that clones
|
|
806
|
-
// the producer's PR branch and reads the ACTUAL repository (changed files / committed
|
|
807
|
-
// document) before rating it, returning the verdict as structured JSON. Surfaced to the
|
|
808
|
-
// engine as `result.custom` (the default `toRunResult` branch) and parsed back into a
|
|
809
|
-
// CompanionAssessment by `CompanionController.resolveContainerVerdict`. The companion
|
|
810
|
-
// review system prompt (which already instructs the JSON shape and, for these kinds, to
|
|
811
|
-
// read the checkout) wins in `systemPromptFor`, so no per-kind prompt wiring is needed.
|
|
812
|
-
if (isContainerBackedCompanion(context.agentKind)) {
|
|
813
|
-
return this.buildRegisteredAgentBody(context, parts, { surface: 'container-explore', clone: { branch: 'pr' }, output: { kind: 'structured' } }, roleSystemPrompt);
|
|
814
|
-
}
|
|
815
|
-
// Read-only agents (architect, analysis) explore a real checkout but never edit it:
|
|
816
|
-
// they clone a branch, produce a prose report/proposal and return it as `output`,
|
|
817
|
-
// making no commit and opening no PR (and — unlike a coding run — an edit-free run is
|
|
818
|
-
// the expected, correct outcome, not a failure). They dispatch through the generic,
|
|
819
|
-
// manifest-driven `agent` kind in `explore` mode — the SAME path a registered
|
|
820
|
-
// `container-explore` kind takes — instead of a bespoke per-kind harness handler. A
|
|
821
|
-
// synthesized read-only step (no clone target ⇒ the shared work-branch fallback, so
|
|
822
|
-
// e.g. the architect reads the spec-writer's committed `spec/` and any in-progress
|
|
823
|
-
// implementation, falling back to base when no work/PR branch exists) yields a body
|
|
824
|
-
// byte-identical to the old `/explore` job, minus only the harness-internal temp-dir
|
|
825
|
-
// label. This is the first built-in migrated onto the generic agent surface (the
|
|
826
|
-
// Task-5 strangler); the now-dead `/explore` harness handler is deleted in a
|
|
827
|
-
// follow-up once parity is confirmed on CI.
|
|
828
|
-
if (isReadOnlyAgentKind(context.agentKind)) {
|
|
829
|
-
return this.buildRegisteredAgentBody(context, parts, { surface: 'container-explore' }, roleSystemPrompt);
|
|
830
|
-
}
|
|
831
|
-
// The default coder (and any other write-and-PR kind): the build-phase role plus the
|
|
832
|
-
// block's selected best-practice fragments. Dispatches the generic `container-coding`
|
|
833
|
-
// agent onto the deterministic per-task work branch (`clone: 'work'` ⇒ branch off base,
|
|
834
|
-
// push the work branch, open a PR). The work-branch name is deterministic per task
|
|
835
|
-
// (block), NOT per dispatch — a retry mints a fresh executionId but keeps the blockId —
|
|
836
|
-
// so every re-dispatch targets the SAME branch; `runCodingAgent` checkpoints commits to
|
|
837
|
-
// it and RESUMES on it if it already exists, so an evicted/failed run's work survives.
|
|
838
|
-
// This is behaviour-equivalent to the old bespoke `/run` body (handleAgent coding mode
|
|
839
|
-
// is built on the same `runCodingAgent` primitive); the dead `/run` handler is removed
|
|
840
|
-
// in the harness-cleanup step.
|
|
841
|
-
return this.buildRegisteredAgentBody(context, parts, { surface: 'container-coding', clone: { branch: 'work' } }, roleSystemPrompt);
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Build the generic `agent` job body for a registered kind from its declarative
|
|
845
|
-
* {@link AgentStepSpec} — the single dispatch path that replaces the per-kind cases as
|
|
846
|
-
* built-ins migrate. `container-explore` clones a branch read-only and returns prose
|
|
847
|
-
* (or, for `output.kind==='structured'`, a parsed `custom` JSON object the kind's
|
|
848
|
-
* post-op renders from); `container-coding` clones, edits, pushes and (off the work
|
|
849
|
-
* branch) opens a PR. The clone target maps `base`/`pr`/`work` to a concrete branch
|
|
850
|
-
* exactly as the built-in bodies do.
|
|
851
|
-
*/
|
|
852
|
-
buildRegisteredAgentBody(context, parts, step, roleSystemPrompt,
|
|
853
|
-
/**
|
|
854
|
-
* The concrete task prompt. Defaults to the generic `userPromptFor` (block context +
|
|
855
|
-
* prior outputs) — the same prompt a registered custom kind gets. A migrated built-in
|
|
856
|
-
* (merger / on-call) overrides it with its bespoke, JSON-instructing prompt so its
|
|
857
|
-
* body matches the old per-kind handler's.
|
|
858
|
-
*/
|
|
859
|
-
userPrompt = userPromptFor(context, { materialized: true })) {
|
|
860
|
-
const { common, webTools, repo, workBranch, workBranchReady } = parts;
|
|
861
|
-
const prBranch = context.block.pullRequest?.branch;
|
|
862
|
-
const onPr = step.clone?.branch === 'pr';
|
|
863
|
-
const exploreBranch = step.clone?.branch === 'base'
|
|
864
|
-
? repo.baseBranch
|
|
865
|
-
: onPr
|
|
866
|
-
? (prBranch ?? repo.baseBranch)
|
|
867
|
-
: workBranchReady
|
|
868
|
-
? workBranch
|
|
869
|
-
: (prBranch ?? repo.baseBranch);
|
|
870
|
-
if (step.surface === 'container-coding') {
|
|
871
|
-
// `pr` clone ⇒ work in place on the PR branch and push back (fixer-like, no new PR);
|
|
872
|
-
// otherwise branch off base onto the work branch, push it and open a PR (coder-like).
|
|
873
|
-
return {
|
|
874
|
-
kind: 'agent',
|
|
875
|
-
body: {
|
|
876
|
-
...common,
|
|
877
|
-
mode: 'coding',
|
|
878
|
-
systemPrompt: roleSystemPrompt,
|
|
879
|
-
userPrompt,
|
|
880
|
-
branch: onPr ? (prBranch ?? repo.baseBranch) : repo.baseBranch,
|
|
881
|
-
...(onPr ? {} : { newBranch: workBranch }),
|
|
882
|
-
pushBranch: onPr ? (prBranch ?? workBranch) : workBranch,
|
|
883
|
-
...(onPr
|
|
884
|
-
? { noChangesIsError: false }
|
|
885
|
-
: {
|
|
886
|
-
pr: {
|
|
887
|
-
title: `${context.block.title} (${context.pipelineName})`,
|
|
888
|
-
body: prBody(context),
|
|
889
|
-
},
|
|
890
|
-
}),
|
|
891
|
-
...(step.clone?.full ? { full: true } : {}),
|
|
892
|
-
// The Coder (follow-up companion enabled) streams forward-looking items out via
|
|
893
|
-
// the sentinel file; tell the harness to tail it. Only on the implementer path.
|
|
894
|
-
...(context.followUpCompanion && !onPr ? { streamFollowUps: true } : {}),
|
|
895
|
-
...webTools,
|
|
896
|
-
},
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
// container-explore (read-only): prose, or a structured JSON object as `custom`.
|
|
900
|
-
return {
|
|
901
|
-
kind: 'agent',
|
|
902
|
-
body: {
|
|
903
|
-
...common,
|
|
904
|
-
mode: 'explore',
|
|
905
|
-
systemPrompt: roleSystemPrompt,
|
|
906
|
-
userPrompt,
|
|
907
|
-
branch: exploreBranch,
|
|
908
|
-
...(step.clone?.full ? { full: true } : {}),
|
|
909
|
-
...(step.output?.kind === 'structured'
|
|
910
|
-
? {
|
|
911
|
-
output: {
|
|
912
|
-
kind: 'structured',
|
|
913
|
-
...(step.output.shapeHint ? { shapeHint: step.output.shapeHint } : {}),
|
|
914
|
-
...(step.output.repair === false ? { repair: false } : {}),
|
|
915
|
-
...(step.output.failOnUnusableFinal ? { failOnUnusableFinal: true } : {}),
|
|
916
|
-
},
|
|
917
|
-
}
|
|
918
|
-
: {}),
|
|
919
|
-
...webTools,
|
|
920
|
-
},
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
/**
|
|
924
|
-
* Build the generic `agent` body for a BUILT-IN container kind being migrated onto the
|
|
925
|
-
* manifest-driven path (the Task-5 strangler), or undefined when `context.agentKind` is
|
|
926
|
-
* not a migrated built-in (the caller falls through to the remaining bespoke switch). Each
|
|
927
|
-
* migrated kind is expressed as a synthesized {@link AgentStepSpec} routed through
|
|
928
|
-
* {@link buildRegisteredAgentBody} — the SAME dispatch a registered custom kind takes — so
|
|
929
|
-
* there is no bespoke harness handler:
|
|
930
|
-
* - `ci-fixer` / `fixer`: coding-on-PR (clone the PR branch, push back, no new PR; a
|
|
931
|
-
* no-op is non-fatal). Requires the implementation PR branch.
|
|
932
|
-
* - `merger` / `on-call`: read-only structured explore (full clone) that returns ONLY a
|
|
933
|
-
* JSON assessment; the conservative coercion that used to live in the harness runs
|
|
934
|
-
* backend-side in {@link toRunResult}.
|
|
935
|
-
* - `conflict-resolver`: coding (full clone of the PR branch) with a `mergeBase` — the
|
|
936
|
-
* harness merges the base in to surface the conflicts, the agent resolves them, and the
|
|
937
|
-
* harness completes the merge commit + pushes back onto the same branch (no new PR).
|
|
938
|
-
*/
|
|
939
|
-
buildMigratedBuiltInBody(context, parts, roleSystemPrompt) {
|
|
940
|
-
const { repo } = parts;
|
|
941
|
-
const prBranch = context.block.pullRequest?.branch;
|
|
942
|
-
switch (context.agentKind) {
|
|
943
|
-
// The Blueprinter maps the repo into the service → modules tree. It now runs as a
|
|
944
|
-
// read-only structured explore (clone the PR branch when present, else the default
|
|
945
|
-
// branch — exactly its old `prBranch ?? baseBranch` clone), returning ONLY the tree
|
|
946
|
-
// as JSON; the deterministic render + commit of the `blueprints/` artifact that used
|
|
947
|
-
// to live in the harness `/blueprint` handler is the backend `blueprintPostOp` (run
|
|
948
|
-
// from ExecutionService), and `toRunResult` coerces the JSON into `blueprintService`
|
|
949
|
-
// for the board reconcile + that post-op.
|
|
950
|
-
case BLUEPRINTS_AGENT_KIND:
|
|
951
|
-
return this.buildRegisteredAgentBody(context, parts, {
|
|
952
|
-
surface: 'container-explore',
|
|
953
|
-
clone: { branch: 'pr' },
|
|
954
|
-
output: { kind: 'structured', shapeHint: BLUEPRINT_SHAPE_HINT },
|
|
955
|
-
}, BLUEPRINT_SYSTEM_PROMPT, blueprintUserPrompt());
|
|
956
|
-
// The spec-writer maintains the prescriptive `spec/` document. It now runs as a
|
|
957
|
-
// read-only structured explore on the per-block WORK branch (clone `work` — the
|
|
958
|
-
// deterministic `cat-factory/<blockId>` the coder resumes, created from base when
|
|
959
|
-
// absent; it runs BEFORE the coder, so it SEEDS that branch). The agent READS the
|
|
960
|
-
// baseline spec from its own checkout (`spec/`), applies this ONE task as an increment,
|
|
961
|
-
// and returns the COMPLETE tree as JSON; the deterministic SHARD + commit of the
|
|
962
|
-
// `spec/` artifact that used to live in the harness `/spec` handler is the backend
|
|
963
|
-
// `specPostOp` (run from ExecutionService), and `toRunResult` coerces the JSON into the
|
|
964
|
-
// `spec` channel the engine strict-validates + that post-op renders/commits from. It
|
|
965
|
-
// NEVER targets base: the spec is prescriptive for not-yet-landed work, so it merges
|
|
966
|
-
// WITH the feature, never reaching `main` ahead of it.
|
|
967
|
-
case SPEC_WRITER_AGENT_KIND:
|
|
968
|
-
return this.buildRegisteredAgentBody(context, parts, {
|
|
969
|
-
surface: 'container-explore',
|
|
970
|
-
clone: { branch: 'work' },
|
|
971
|
-
// The spec doc is handed onward to be sharded + committed by `specPostOp`, so a
|
|
972
|
-
// final answer cut off at the output ceiling must FAIL LOUDLY (the bespoke `/spec`
|
|
973
|
-
// handler's `unusableFinalAnswerCause` gate) rather than be laundered into a
|
|
974
|
-
// half-baked spec by the structured repair — exactly what drove the old
|
|
975
|
-
// spec-writer ⇄ companion rework loop.
|
|
976
|
-
output: { kind: 'structured', shapeHint: SPEC_SHAPE_HINT, failOnUnusableFinal: true },
|
|
977
|
-
}, SPEC_WRITER_SYSTEM_PROMPT, specWriterUserPrompt(context));
|
|
978
|
-
// In-place fixers: clone the PR head branch, push fixes back onto it (no new PR);
|
|
979
|
-
// a no-op run is a clean non-event (the gate/loop re-checks the real signal).
|
|
980
|
-
case CI_FIXER_AGENT_KIND:
|
|
981
|
-
if (!prBranch)
|
|
982
|
-
throw new Error('CI-fixer needs the implementation PR branch to push fixes to');
|
|
983
|
-
return this.buildRegisteredAgentBody(context, parts, { surface: 'container-coding', clone: { branch: 'pr' } }, roleSystemPrompt);
|
|
984
|
-
case FIXER_AGENT_KIND:
|
|
985
|
-
if (!prBranch)
|
|
986
|
-
throw new Error('Fixer needs the implementation PR branch to push fixes to');
|
|
987
|
-
return this.buildRegisteredAgentBody(context, parts, { surface: 'container-coding', clone: { branch: 'pr' } }, roleSystemPrompt);
|
|
988
|
-
// The conflict-resolver clones the PR head branch (full history), merges the base in
|
|
989
|
-
// to surface the conflicts, resolves them and pushes back onto the SAME branch (no new
|
|
990
|
-
// branch / PR) so the PR becomes mergeable and CI re-runs. It dispatches the generic
|
|
991
|
-
// coding agent with a `mergeBase` (the harness merges `origin/<mergeBase>` in before the
|
|
992
|
-
// agent runs); the harness leads the prompt with the actual conflict hunks it discovers.
|
|
993
|
-
//
|
|
994
|
-
// Unlike the CI-fixer it is deliberately NOT given `userPromptFor(context)`: that renders
|
|
995
|
-
// the full task brief + every prior agent's output (the spec-writer's whole spec, etc.),
|
|
996
|
-
// which buries the one-line "resolve a conflict" role and drifts the model onto
|
|
997
|
-
// re-implementing the feature (observed in prod: a resolver that returned a "test report
|
|
998
|
-
// is ready" answer and never touched the markers). The backend supplies only a compact
|
|
999
|
-
// task reference for intent.
|
|
1000
|
-
case CONFLICT_RESOLVER_AGENT_KIND: {
|
|
1001
|
-
if (!prBranch) {
|
|
1002
|
-
throw new Error('Conflict-resolver needs the implementation PR branch to resolve conflicts on');
|
|
1003
|
-
}
|
|
1004
|
-
const description = context.block.description?.trim();
|
|
1005
|
-
const built = this.buildRegisteredAgentBody(context, parts, { surface: 'container-coding', clone: { branch: 'pr', full: true } }, roleSystemPrompt, `Task: ${context.block.title}${description ? `\n\n${description}` : ''}`);
|
|
1006
|
-
return { kind: built.kind, body: { ...built.body, mergeBase: repo.baseBranch } };
|
|
1007
|
-
}
|
|
1008
|
-
// The merger clones the PR head (full, to diff vs base) and returns ONLY the
|
|
1009
|
-
// complexity/risk/impact assessment JSON; the engine performs the real merge.
|
|
1010
|
-
case MERGER_AGENT_KIND:
|
|
1011
|
-
return this.buildRegisteredAgentBody(context, parts, {
|
|
1012
|
-
surface: 'container-explore',
|
|
1013
|
-
clone: { branch: 'pr', full: true },
|
|
1014
|
-
output: { kind: 'structured', shapeHint: MERGE_ASSESSMENT_SHAPE_HINT },
|
|
1015
|
-
}, MERGER_SYSTEM_PROMPT, mergerUserPrompt(context, repo));
|
|
1016
|
-
// The on-call agent clones the BASE branch (full, to locate + diff the merged
|
|
1017
|
-
// release commit) and returns ONLY the regression assessment JSON.
|
|
1018
|
-
case ON_CALL_AGENT_KIND:
|
|
1019
|
-
return this.buildRegisteredAgentBody(context, parts, {
|
|
1020
|
-
surface: 'container-explore',
|
|
1021
|
-
clone: { branch: 'base', full: true },
|
|
1022
|
-
output: { kind: 'structured', shapeHint: ON_CALL_ASSESSMENT_SHAPE_HINT },
|
|
1023
|
-
}, ON_CALL_SYSTEM_PROMPT, onCallUserPrompt(context, repo));
|
|
1024
|
-
// The tester clones the PR head branch (read-only — it makes NO commits), stands up
|
|
1025
|
-
// its dependencies (locally via the service's docker-compose, or against the
|
|
1026
|
-
// provisioned ephemeral env — the task's `tester.environment` config picks which) and
|
|
1027
|
-
// returns ONLY a structured JSON report. It runs as a generic structured explore with
|
|
1028
|
-
// an `infra` spec the harness uses to stand the docker-compose dependencies up for the
|
|
1029
|
-
// run; `toRunResult` coerces the JSON into `testReport` (the conservative greenlight /
|
|
1030
|
-
// blocking-concern rule the harness applied now runs backend-side, and the engine's
|
|
1031
|
-
// TesterController re-applies it). The role prompt + the run-mode/ephemeral-URL guidance
|
|
1032
|
-
// come from the standard `roleSystemPrompt` + `userPromptFor` (which already carry them),
|
|
1033
|
-
// so the harness adds none. The engine loops the `fixer` on a withheld greenlight.
|
|
1034
|
-
case TESTER_AGENT_KIND: {
|
|
1035
|
-
const built = this.buildRegisteredAgentBody(context, parts, {
|
|
1036
|
-
surface: 'container-explore',
|
|
1037
|
-
clone: { branch: 'pr' },
|
|
1038
|
-
output: { kind: 'structured', shapeHint: TEST_REPORT_SHAPE_HINT },
|
|
1039
|
-
}, roleSystemPrompt);
|
|
1040
|
-
return { kind: built.kind, body: { ...built.body, infra: testerInfraSpec(context) } };
|
|
1041
|
-
}
|
|
1042
|
-
// The UI tester is the Tester's browser-driven sibling: same read-only structured
|
|
1043
|
-
// explore + infra stand-up, but it drives Playwright (supplied by the UI-tester
|
|
1044
|
-
// image, routed via the `image:'ui'` dispatch option) to capture a non-redundant
|
|
1045
|
-
// screenshot of each distinct view, uploads them to the artifact store, and reports
|
|
1046
|
-
// them under `screenshots[]`. The role prompt carries the capture guidance.
|
|
1047
|
-
case UI_TESTER_AGENT_KIND: {
|
|
1048
|
-
const built = this.buildRegisteredAgentBody(context, parts, {
|
|
1049
|
-
surface: 'container-explore',
|
|
1050
|
-
clone: { branch: 'pr' },
|
|
1051
|
-
output: { kind: 'structured', shapeHint: UI_TEST_REPORT_SHAPE_HINT },
|
|
1052
|
-
}, roleSystemPrompt);
|
|
1053
|
-
return { kind: built.kind, body: { ...built.body, infra: testerInfraSpec(context) } };
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
return undefined;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
/**
|
|
1060
|
-
* The Blueprinter's task prompt. The agent now reads any existing blueprint from its own
|
|
1061
|
-
* read-only checkout (the harness no longer pre-injects the baseline tree), so the prompt
|
|
1062
|
-
* tells it to read `blueprints/` and update-or-create, then return the complete tree as
|
|
1063
|
-
* JSON. The backend `blueprintPostOp` renders + commits the artifact from that tree.
|
|
1064
|
-
*/
|
|
1065
|
-
function blueprintUserPrompt() {
|
|
1066
|
-
return [
|
|
1067
|
-
'Map this repository into the canonical service → modules blueprint, anchored to real ' +
|
|
1068
|
-
'file/directory references.',
|
|
1069
|
-
'',
|
|
1070
|
-
'If a blueprint already exists in the repository (read `blueprints/blueprint.json` and ' +
|
|
1071
|
-
'`blueprints/overview.md`), UPDATE it to reflect the current code: keep accurate ' +
|
|
1072
|
-
'modules, add new ones, and refine summaries + references. Otherwise create it from ' +
|
|
1073
|
-
'scratch. Return the COMPLETE tree (not a diff).',
|
|
1074
|
-
'',
|
|
1075
|
-
'Respond with ONLY the JSON object for the service tree — no prose, no code fences.',
|
|
1076
|
-
].join('\n');
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* The spec-writer's task prompt — the instructions + baseline-read + taxonomy-reuse guidance
|
|
1080
|
-
* the bespoke harness `/spec` handler used to build (`buildUserPrompt`/`renderTaxonomyInventory`,
|
|
1081
|
-
* which used to inject the baseline doc + its module→feature inventory). The agent now reads
|
|
1082
|
-
* the baseline from its own read-only checkout under `spec/`, so the prompt tells it to read +
|
|
1083
|
-
* reuse the existing taxonomy rather than pre-injecting it. Carries ONLY this task's
|
|
1084
|
-
* requirements (the block description IS the task's reworked/incorporated requirements), so an
|
|
1085
|
-
* unmerged sibling task's work never bleeds in. The backend `specPostOp` shards + commits the
|
|
1086
|
-
* returned tree.
|
|
1087
|
-
*/
|
|
1088
|
-
function specWriterUserPrompt(context) {
|
|
1089
|
-
const block = context.block;
|
|
1090
|
-
const header = `### ${block.title || '(untitled task)'}${block.id ? ` (block ${block.id})` : ''}`;
|
|
1091
|
-
// Honour an explicit human-set BUSINESS/TECHNICAL label: a task pinned business HAS
|
|
1092
|
-
// business requirements, so the "no new specs" escape hatch is withdrawn; a task pinned
|
|
1093
|
-
// technical is told the empty outcome is expected. Left unset, the writer self-determines.
|
|
1094
|
-
const technicalGuidance = block.technical === false
|
|
1095
|
-
? 'This task is explicitly flagged BUSINESS: it HAS business requirements, so you MUST ' +
|
|
1096
|
-
'return the full updated specification. Do NOT respond with {"noBusinessSpecs": true}.'
|
|
1097
|
-
: block.technical === true
|
|
1098
|
-
? 'This task is explicitly flagged TECHNICAL (a refactor / dependency bump / internal ' +
|
|
1099
|
-
'or non-functional change with NO new externally-observable behaviour): "no business ' +
|
|
1100
|
-
'requirements" is the expected outcome — respond with ONLY {"noBusinessSpecs": true} ' +
|
|
1101
|
-
'and change nothing, unless you find genuine externally-observable behaviour to spec.'
|
|
1102
|
-
: 'If this task is purely TECHNICAL (a refactor / dependency bump / internal or ' +
|
|
1103
|
-
'non-functional change that introduces NO new externally-observable behaviour), it ' +
|
|
1104
|
-
'has no business requirements: respond with ONLY {"noBusinessSpecs": true} and ' +
|
|
1105
|
-
'change nothing.';
|
|
1106
|
-
return [
|
|
1107
|
-
'Apply this ONE task as an INCREMENT onto the service specification.',
|
|
1108
|
-
'',
|
|
1109
|
-
'First READ the specification already committed to the repository under `spec/` (the ' +
|
|
1110
|
-
'baseline as merged before this task): open `spec/overview.md` for the module → feature ' +
|
|
1111
|
-
'index, then the relevant `spec/modules/<module>/<feature>.json` shards. Keep every part ' +
|
|
1112
|
-
'of the baseline this task does not touch exactly as-is, preserving its `sourceBlockIds`; ' +
|
|
1113
|
-
'adjust an existing requirement only where this task changes its behaviour. Map each new ' +
|
|
1114
|
-
'requirement/rule into the closest-fitting EXISTING module and feature, reusing its EXACT ' +
|
|
1115
|
-
'name — create a new module or feature ONLY when nothing fits (never a near-duplicate). ' +
|
|
1116
|
-
'If no spec exists yet, start one as a module (domain) → feature (group) taxonomy.',
|
|
1117
|
-
'',
|
|
1118
|
-
'Requirements for the ONE task to apply (its clarified description). Translate ONLY what ' +
|
|
1119
|
-
'these state into BUSINESS requirements (externally-observable behaviour, product rules, ' +
|
|
1120
|
-
'acceptance criteria) with COMPLETE acceptance-scenario coverage — do NOT invent ' +
|
|
1121
|
-
'requirements or fill gaps they leave:',
|
|
1122
|
-
'',
|
|
1123
|
-
`${header}\n\n${block.description?.trim() || '(no description)'}`,
|
|
1124
|
-
'',
|
|
1125
|
-
technicalGuidance +
|
|
1126
|
-
' Otherwise return the COMPLETE updated document (baseline plus this task’s ' +
|
|
1127
|
-
'increment), not a diff. Respond with ONLY the JSON object — no prose, no code fences.',
|
|
1128
|
-
].join('\n');
|
|
1129
|
-
}
|
|
1130
|
-
/**
|
|
1131
|
-
* The merger's task prompt — the instructions + diff guidance the bespoke harness `/merge`
|
|
1132
|
-
* handler used to build. Kept backend-side now that the merger dispatches the generic
|
|
1133
|
-
* explore agent. Names the PR/branches so the agent diffs against the right base.
|
|
1134
|
-
*/
|
|
1135
|
-
function mergerUserPrompt(context, repo) {
|
|
1136
|
-
const prNumber = context.block.pullRequest?.number;
|
|
1137
|
-
const branch = context.block.pullRequest?.branch ?? repo.baseBranch;
|
|
1138
|
-
const pr = prNumber !== undefined ? ` (PR #${prNumber})` : '';
|
|
1139
|
-
return [
|
|
1140
|
-
'Assess the pull request on the head branch against the base branch and return the ' +
|
|
1141
|
-
'complexity / risk / impact scores + rationale as JSON.',
|
|
1142
|
-
'',
|
|
1143
|
-
`The pull request${pr} is on branch \`${branch}\`; the base branch is ` +
|
|
1144
|
-
`\`${repo.baseBranch}\`. Inspect the change (e.g. \`git fetch origin ${repo.baseBranch}\` ` +
|
|
1145
|
-
`then \`git diff origin/${repo.baseBranch}...HEAD\`) and score complexity, risk and impact.`,
|
|
1146
|
-
'',
|
|
1147
|
-
'Respond with ONLY a JSON object {"complexity":0.0,"risk":0.0,"impact":0.0,"rationale":"…"}.',
|
|
1148
|
-
].join('\n');
|
|
1149
|
-
}
|
|
1150
|
-
/**
|
|
1151
|
-
* The on-call agent's task prompt — the regression evidence (the generic block/prior-output
|
|
1152
|
-
* prompt) plus the locate-the-merged-commit guidance the bespoke harness `/on-call` handler
|
|
1153
|
-
* used to build. The released PR already merged into the base branch (its work branch is
|
|
1154
|
-
* gone), so the agent is on the base branch and is told how to find the merged commit.
|
|
1155
|
-
*/
|
|
1156
|
-
function onCallUserPrompt(context, repo) {
|
|
1157
|
-
const prNumber = context.block.pullRequest?.number;
|
|
1158
|
-
const headBranch = context.block.pullRequest?.branch;
|
|
1159
|
-
const pr = prNumber !== undefined ? `#${prNumber}` : '';
|
|
1160
|
-
const locate = prNumber
|
|
1161
|
-
? `It merged as a commit referencing ${pr} — find it with \`git log --oneline -n 50\` ` +
|
|
1162
|
-
`(squash/merge commits include \`(${pr})\`; a merge commit mentions \`#${prNumber}\`), then ` +
|
|
1163
|
-
`inspect it with \`git show <sha>\`.`
|
|
1164
|
-
: headBranch
|
|
1165
|
-
? `Its work branch was \`${headBranch}\` (now deleted) — find the merged commit in ` +
|
|
1166
|
-
`\`git log --oneline -n 50\` and inspect it with \`git show <sha>\`.`
|
|
1167
|
-
: `Find the most recent merge/feature commit with \`git log --oneline -n 50\` and inspect ` +
|
|
1168
|
-
`it with \`git show <sha>\`.`;
|
|
1169
|
-
return [
|
|
1170
|
-
userPromptFor(context, { materialized: true }),
|
|
1171
|
-
'',
|
|
1172
|
-
`You are on the base branch \`${repo.baseBranch}\`, which already contains the released ` +
|
|
1173
|
-
`pull request ${pr}. ${locate} Correlate that change with the regression evidence above. ` +
|
|
1174
|
-
`Beware correlation vs causation.`,
|
|
1175
|
-
'',
|
|
1176
|
-
'Respond with ONLY a JSON object {"culpritConfidence":0.0,"recommendation":"revert"|"hold"|"monitor","rationale":"…","evidence":["…"]}.',
|
|
1177
|
-
].join('\n');
|
|
1178
|
-
}
|
|
1179
|
-
/**
|
|
1180
|
-
* The tester's infra stand-up spec for the generic agent job, from the block's
|
|
1181
|
-
* `tester.environment` config + the resolved service: a `local` run carries the
|
|
1182
|
-
* docker-compose path (or the explicit no-infra flag) for the harness to stand the
|
|
1183
|
-
* dependencies up + tear them down around the run; an `ephemeral` run carries the
|
|
1184
|
-
* provisioned environment URL. Byte-identical to the old bespoke `/test` body's `test`
|
|
1185
|
-
* object — only the field name changed (`test` → `infra`).
|
|
1186
|
-
*/
|
|
1187
|
-
function testerInfraSpec(context) {
|
|
1188
|
-
const env = context.block.agentConfig?.['tester.environment'] === 'local' ? 'local' : 'ephemeral';
|
|
1189
|
-
const service = context.service;
|
|
1190
|
-
return {
|
|
1191
|
-
environment: env,
|
|
1192
|
-
...(env === 'local'
|
|
1193
|
-
? {
|
|
1194
|
-
noInfraDependencies: service?.noInfraDependencies === true,
|
|
1195
|
-
...(service?.testComposePath ? { composePath: service.testComposePath } : {}),
|
|
1196
|
-
}
|
|
1197
|
-
: {}),
|
|
1198
|
-
...(env === 'ephemeral' && context.environment?.url
|
|
1199
|
-
? { environmentUrl: context.environment.url }
|
|
1200
|
-
: {}),
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
function prBody(context) {
|
|
1204
|
-
const lines = [
|
|
1205
|
-
`Automated implementation for block **${context.block.title}** (${context.block.type}).`,
|
|
1206
|
-
'',
|
|
1207
|
-
context.block.description || '(no description)',
|
|
1208
|
-
'',
|
|
1209
|
-
`Pipeline: ${context.pipelineName}`,
|
|
1210
|
-
];
|
|
1211
|
-
return lines.join('\n');
|
|
1212
652
|
}
|
|
1213
653
|
//# sourceMappingURL=ContainerAgentExecutor.js.map
|