@bridge_gpt/mcp-server 0.2.65 → 0.2.66
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 +25 -13
- package/build/agent-launchers/claude-executor-adapter.js +12 -18
- package/build/agent-launchers/executor-adapter.js +27 -0
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +230 -10
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +12 -0
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness.js +23 -1
- package/build/conductor/recovery-cli.js +507 -16
- package/build/conductor/recovery-operations.js +325 -2
- package/build/conductor/repair-contract.js +61 -3
- package/build/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +70 -10
- package/build/drive-epic.js +395 -78
- package/build/epic-implementer-bin.js +24 -22
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/completion-supervision.js +5 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +6 -0
- package/build/executor/http-client.js +15 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +13 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +260 -33
- package/build/executor/job-types.js +8 -13
- package/build/executor/preflight.js +91 -2
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/runner.js +23 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/types.js +1 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +43 -43
- package/build/executor/worker-finalization.js +80 -15
- package/build/executor/worker-guard-hook.js +46 -10
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/index.js +177 -329
- package/build/init.js +107 -4
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +1 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +4 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +180 -108
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +4 -3
- package/build/worktree-core.js +6 -5
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +1 -1
- package/pipelines/implement-ticket.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Keep operator-controlled components out of trusted epic-launch refusal text. */
|
|
2
|
+
export function diagnosticComponent(value) {
|
|
3
|
+
// A short identifier here is a schema-field label, not its stored value.
|
|
4
|
+
if (/^field '[A-Za-z][A-Za-z0-9]{0,39}'$/.test(value))
|
|
5
|
+
return value;
|
|
6
|
+
if (value.length > 512 ||
|
|
7
|
+
/[\x00-\x1f\x7f]/.test(value) ||
|
|
8
|
+
/(?:sk[-_][A-Za-z0-9]+|api[-_]?key[-_:=][A-Za-z0-9]+|secret|token|password|bearer|authorization)/i.test(value) ||
|
|
9
|
+
/%[0-9a-f]{2}/i.test(value) && decodedLooksSensitive(value))
|
|
10
|
+
return "<redacted>";
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function decodedLooksSensitive(value) {
|
|
14
|
+
try {
|
|
15
|
+
const decoded = decodeURIComponent(value);
|
|
16
|
+
return decoded !== value && diagnosticComponent(decoded) === "<redacted>";
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** URL validation alone permits paths and queries that look like credentials. */
|
|
23
|
+
export function safeEpicLaunchTargetUrl(baseUrl) {
|
|
24
|
+
try {
|
|
25
|
+
const url = new URL(baseUrl);
|
|
26
|
+
return !url.username && !url.password && diagnosticComponent(baseUrl) === baseUrl;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -26,11 +26,13 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import os from "node:os";
|
|
28
28
|
import path from "node:path";
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
29
30
|
import { chmod, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
30
31
|
import { durablyReplaceCredentialStoreJson, getPrimaryCredentialStorePath, withCredentialStoreLock, } from "./credential-store.js";
|
|
31
32
|
import { validateHttpBaseUrl, describeBaseUrlRejection } from "./base-url.js";
|
|
32
33
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
33
34
|
import { isTransientNpxEntryPath } from "./mcp-server-invocation.js";
|
|
35
|
+
import { diagnosticComponent } from "./epic-launch-diagnostic.js";
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
35
37
|
// Types
|
|
36
38
|
// ---------------------------------------------------------------------------
|
|
@@ -45,7 +47,7 @@ export const EPIC_LAUNCH_EXECUTION_MODES = ["local", "durable"];
|
|
|
45
47
|
/** One-time migration guidance for a profile this build cannot read. */
|
|
46
48
|
export function epicLaunchProfileSetupGuidance(name) {
|
|
47
49
|
return (`Re-run guided setup to (re)write it: npx -y ${MCP_PACKAGE_NAME} install conductor ` +
|
|
48
|
-
`--launch-profile ${EPIC_LAUNCH_PROFILE_NAME_PATTERN.test(name) ? name : "<name>"} --base-url <url> ...`);
|
|
50
|
+
`--launch-profile ${EPIC_LAUNCH_PROFILE_NAME_PATTERN.test(name) ? diagnosticComponent(name) : "<name>"} --base-url <url> ...`);
|
|
49
51
|
}
|
|
50
52
|
// ---------------------------------------------------------------------------
|
|
51
53
|
// Validation (pure)
|
|
@@ -73,7 +75,7 @@ function malformed(name, field, why) {
|
|
|
73
75
|
return {
|
|
74
76
|
ok: false,
|
|
75
77
|
kind: "profile-malformed",
|
|
76
|
-
message: `Epic launch profile '${name}' is unusable: ${field} ${why}. ${epicLaunchProfileSetupGuidance(name)}`,
|
|
78
|
+
message: `Epic launch profile '${diagnosticComponent(name)}' is unusable: ${diagnosticComponent(field)} ${why}. ${epicLaunchProfileSetupGuidance(name)}`,
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
function validateRuntime(name, raw) {
|
|
@@ -118,7 +120,7 @@ export function validateEpicLaunchProfile(raw, selector = "<unnamed>") {
|
|
|
118
120
|
return {
|
|
119
121
|
ok: false,
|
|
120
122
|
kind: "profile-unsupported-version",
|
|
121
|
-
message: `Epic launch profile '${selector}' uses an unsupported schema version; this build reads ` +
|
|
123
|
+
message: `Epic launch profile '${diagnosticComponent(selector)}' uses an unsupported schema version; this build reads ` +
|
|
122
124
|
`version ${EPIC_LAUNCH_PROFILE_VERSION} only. ${epicLaunchProfileSetupGuidance(selector)}`,
|
|
123
125
|
};
|
|
124
126
|
}
|
|
@@ -204,23 +206,23 @@ async function readStoreDocument(deps) {
|
|
|
204
206
|
if (code === "ENOENT") {
|
|
205
207
|
return { ok: true, present: false, document: { version: EPIC_LAUNCH_PROFILE_VERSION, profiles: {} } };
|
|
206
208
|
}
|
|
207
|
-
return { ok: false, kind: "store-unreadable", message: `Unable to read ${storePath}.` };
|
|
209
|
+
return { ok: false, kind: "store-unreadable", message: `Unable to read ${diagnosticComponent(storePath)}.` };
|
|
208
210
|
}
|
|
209
211
|
let parsed;
|
|
210
212
|
try {
|
|
211
213
|
parsed = JSON.parse(raw);
|
|
212
214
|
}
|
|
213
215
|
catch {
|
|
214
|
-
return { ok: false, kind: "store-unreadable", message: `${storePath} is not valid JSON.` };
|
|
216
|
+
return { ok: false, kind: "store-unreadable", message: `${diagnosticComponent(storePath)} is not valid JSON.` };
|
|
215
217
|
}
|
|
216
218
|
if (!isPlainObject(parsed) || !isPlainObject(parsed.profiles)) {
|
|
217
|
-
return { ok: false, kind: "store-unreadable", message: `${storePath} has no profiles object.` };
|
|
219
|
+
return { ok: false, kind: "store-unreadable", message: `${diagnosticComponent(storePath)} has no profiles object.` };
|
|
218
220
|
}
|
|
219
221
|
if (parsed.version !== EPIC_LAUNCH_PROFILE_VERSION) {
|
|
220
222
|
return {
|
|
221
223
|
ok: false,
|
|
222
224
|
kind: "profile-unsupported-version",
|
|
223
|
-
message: `${storePath} uses an unsupported schema version; this build reads version ` +
|
|
225
|
+
message: `${diagnosticComponent(storePath)} uses an unsupported schema version; this build reads version ` +
|
|
224
226
|
`${EPIC_LAUNCH_PROFILE_VERSION} only. ${epicLaunchProfileSetupGuidance("<name>")}`,
|
|
225
227
|
};
|
|
226
228
|
}
|
|
@@ -249,7 +251,7 @@ export async function readEpicLaunchProfile(name, deps) {
|
|
|
249
251
|
return {
|
|
250
252
|
ok: false,
|
|
251
253
|
kind: "profile-missing",
|
|
252
|
-
message: `No epic launch profile named '${name}' exists on this host. ${epicLaunchProfileSetupGuidance(name)}`,
|
|
254
|
+
message: `No epic launch profile named '${diagnosticComponent(name)}' exists on this host. ${epicLaunchProfileSetupGuidance(name)}`,
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
257
|
const validated = validateEpicLaunchProfile(store.document.profiles[name], name);
|
|
@@ -259,7 +261,7 @@ export async function readEpicLaunchProfile(name, deps) {
|
|
|
259
261
|
return {
|
|
260
262
|
ok: false,
|
|
261
263
|
kind: "profile-host-mismatch",
|
|
262
|
-
message: `Epic launch profile '${name}' was approved on a different host. Profiles are host-local; ` +
|
|
264
|
+
message: `Epic launch profile '${diagnosticComponent(name)}' was approved on a different host. Profiles are host-local; ` +
|
|
263
265
|
epicLaunchProfileSetupGuidance(name),
|
|
264
266
|
};
|
|
265
267
|
}
|
|
@@ -309,6 +311,34 @@ export async function writeEpicLaunchProfile(profile, deps) {
|
|
|
309
311
|
return { ok: true, path: storePath, action: existing === undefined ? "created" : "updated" };
|
|
310
312
|
}, (error) => ({ ok: false, kind: "store-write-failed", message: error }));
|
|
311
313
|
}
|
|
314
|
+
/** Safe for text and JSON; no policy recipient or webhook value is retained. */
|
|
315
|
+
export function formatEpicLaunchChannelIdentity(identity) {
|
|
316
|
+
return identity;
|
|
317
|
+
}
|
|
318
|
+
const pinnedPolicyContents = new WeakMap();
|
|
319
|
+
/** Store selected content once, without making it serializable with the selection. */
|
|
320
|
+
export function pinEpicLaunchPolicy(selection, policy) {
|
|
321
|
+
pinnedPolicyContents.set(selection, policy);
|
|
322
|
+
return selection;
|
|
323
|
+
}
|
|
324
|
+
export function getPinnedEpicLaunchPolicy(selection) {
|
|
325
|
+
return pinnedPolicyContents.get(selection);
|
|
326
|
+
}
|
|
327
|
+
export function epicLaunchPolicyDigest(raw) {
|
|
328
|
+
return createHash("sha256").update(raw).digest("hex");
|
|
329
|
+
}
|
|
330
|
+
/** Finalize the already selected target after the run's existence is known. */
|
|
331
|
+
export function resolveEpicLaunchSelection(args) {
|
|
332
|
+
return Object.freeze({
|
|
333
|
+
baseUrl: args.baseUrl,
|
|
334
|
+
repoName: args.repoName,
|
|
335
|
+
targetSource: args.targetSource,
|
|
336
|
+
profileName: args.profile?.name ?? null,
|
|
337
|
+
topology: args.topology,
|
|
338
|
+
policy: Object.freeze(args.policy),
|
|
339
|
+
escalationChannel: args.escalationChannel,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
312
342
|
/**
|
|
313
343
|
* Normalize every applicable target source and decide ONE target, before any
|
|
314
344
|
* credential is resolved and before any request is made.
|
|
@@ -338,7 +368,7 @@ export function resolveEpicLaunchTarget(inputs) {
|
|
|
338
368
|
const flag = normalize(inputs.flagBaseUrl, "--base-url");
|
|
339
369
|
if (!flag.ok)
|
|
340
370
|
return { ok: false, kind: "target-invalid", message: flag.message };
|
|
341
|
-
const env = normalize(inputs.env.BAPI_BASE_URL, "BAPI_BASE_URL");
|
|
371
|
+
const env = normalize(inputs.profileSource === "profile-env" ? undefined : inputs.env.BAPI_BASE_URL, "BAPI_BASE_URL");
|
|
342
372
|
if (!env.ok)
|
|
343
373
|
return { ok: false, kind: "target-invalid", message: env.message };
|
|
344
374
|
const profile = inputs.profile;
|
|
@@ -347,7 +377,7 @@ export function resolveEpicLaunchTarget(inputs) {
|
|
|
347
377
|
return {
|
|
348
378
|
ok: false,
|
|
349
379
|
kind: "target-conflict",
|
|
350
|
-
message: `--base-url names a different endpoint than launch profile '${profile.name}'. An override ` +
|
|
380
|
+
message: `--base-url names a different endpoint than launch profile '${diagnosticComponent(profile.name)}'. An override ` +
|
|
351
381
|
"does not rebind a profile; re-run guided setup to change its target.",
|
|
352
382
|
};
|
|
353
383
|
}
|
|
@@ -355,7 +385,7 @@ export function resolveEpicLaunchTarget(inputs) {
|
|
|
355
385
|
return {
|
|
356
386
|
ok: false,
|
|
357
387
|
kind: "target-conflict",
|
|
358
|
-
message: `BAPI_BASE_URL names a different endpoint than launch profile '${profile.name}'. Unset it, ` +
|
|
388
|
+
message: `BAPI_BASE_URL names a different endpoint than launch profile '${diagnosticComponent(profile.name)}'. Unset it, ` +
|
|
359
389
|
"or re-run guided setup to change the profile's target.",
|
|
360
390
|
};
|
|
361
391
|
}
|
|
@@ -364,10 +394,10 @@ export function resolveEpicLaunchTarget(inputs) {
|
|
|
364
394
|
return {
|
|
365
395
|
ok: false,
|
|
366
396
|
kind: "repository-conflict",
|
|
367
|
-
message: `--repo names a different repository than launch profile '${profile.name}'.`,
|
|
397
|
+
message: `--repo names a different repository than launch profile '${diagnosticComponent(profile.name)}'.`,
|
|
368
398
|
};
|
|
369
399
|
}
|
|
370
|
-
return { ok: true, baseUrl: profile.baseUrl, source: "profile", repoName: profile.repoName };
|
|
400
|
+
return { ok: true, baseUrl: profile.baseUrl, source: inputs.profileSource ?? "profile", repoName: profile.repoName };
|
|
371
401
|
}
|
|
372
402
|
if (flag.value !== null && env.value !== null && flag.value !== env.value) {
|
|
373
403
|
return {
|
|
@@ -223,6 +223,11 @@ export function finalizationFacts(outcome) {
|
|
|
223
223
|
case "WorkerFinalizationMissingRemoteBranchAndPr":
|
|
224
224
|
case "WorkerFinalizationSavedButUnfinalized":
|
|
225
225
|
return { verdict: "unmet", pushSatisfied: false, prSatisfied: false };
|
|
226
|
+
// BAPI-1330: the worker's full HEAD, or the PR's head, could not be
|
|
227
|
+
// established. Nothing was observed MISSING, so the verdict is `unknown`
|
|
228
|
+
// and neither push nor PR is inferred satisfied.
|
|
229
|
+
case "WorkerFinalizationPublicationUnverified":
|
|
230
|
+
return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
|
|
226
231
|
default:
|
|
227
232
|
return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
|
|
228
233
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** BAPI-1328: persisted remaining-work authority, independent of prompt text. */
|
|
2
|
+
export const CORRECTION_INTENT_CAPABILITY = "correction_intent.v1";
|
|
3
|
+
export const CORRECTION_INTENT_VERSION = 1;
|
|
4
|
+
export const CORRECTION_KINDS = [
|
|
5
|
+
"review_fix", "operator_remediation", "ci_fix", "focused_rebase",
|
|
6
|
+
"overlap_reconciliation", "approved_spec_delta", "continuation",
|
|
7
|
+
"required_checks", "finalization", "integration_correction",
|
|
8
|
+
];
|
|
9
|
+
function nonblank(value) {
|
|
10
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= 4096;
|
|
11
|
+
}
|
|
12
|
+
function reference(value) {
|
|
13
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) &&
|
|
14
|
+
Object.keys(value).length > 0 && Object.entries(value).every(([key, item]) => nonblank(key) && ((typeof item === "string" && nonblank(item)) ||
|
|
15
|
+
(typeof item === "number" && Number.isInteger(item))));
|
|
16
|
+
}
|
|
17
|
+
/** A prompt or job type never grants correction authority. */
|
|
18
|
+
export function validateCorrectionIntent(value, row) {
|
|
19
|
+
const refuse = (code, detail) => ({ ok: false, code, detail });
|
|
20
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
21
|
+
return refuse("correction_context_missing", "correction intent is absent");
|
|
22
|
+
const intent = value;
|
|
23
|
+
if (intent.version !== CORRECTION_INTENT_VERSION)
|
|
24
|
+
return refuse("correction_contract_version_unsupported", "unsupported correction intent version");
|
|
25
|
+
if (!CORRECTION_KINDS.includes(intent.kind))
|
|
26
|
+
return refuse("correction_contract_invalid", "unknown correction kind");
|
|
27
|
+
for (const key of ["repo_name", "epic_run_id", "ticket_key"]) {
|
|
28
|
+
if (!nonblank(row[key]) || intent[key] !== row[key])
|
|
29
|
+
return refuse("correction_contract_invalid", `${key} differs from row`);
|
|
30
|
+
}
|
|
31
|
+
if (!nonblank(intent.branch) || (row.expected_branch && intent.branch !== row.expected_branch))
|
|
32
|
+
return refuse("correction_context_missing", "retained branch is unavailable or changed");
|
|
33
|
+
if (!nonblank(intent.head_sha) || (row.expected_head_sha && intent.head_sha !== row.expected_head_sha))
|
|
34
|
+
return refuse("correction_context_missing", "retained head is unavailable or changed");
|
|
35
|
+
if (!nonblank(intent.obligation) || !reference(intent.context_ref))
|
|
36
|
+
return refuse("correction_context_missing", "remaining obligation or context reference is absent");
|
|
37
|
+
if (intent.resume !== undefined && intent.resume !== null && intent.resume !== false)
|
|
38
|
+
return refuse("correction_contract_invalid", "unsupported resume metadata");
|
|
39
|
+
if (intent.kind === "approved_spec_delta") {
|
|
40
|
+
const delta = intent.approved_delta;
|
|
41
|
+
if (!delta || typeof delta !== "object" || Array.isArray(delta))
|
|
42
|
+
return refuse("renewed_scope_approval_required", "approved same-ticket delta is absent");
|
|
43
|
+
const d = delta;
|
|
44
|
+
if (!["old_identity", "new_identity", "obligation"].every(key => nonblank(d[key])) ||
|
|
45
|
+
!["file_boundary", "dependency_boundary"].every(key => Array.isArray(d[key]) && (key !== "file_boundary" || d[key].length > 0) &&
|
|
46
|
+
d[key].every(nonblank)))
|
|
47
|
+
return refuse("renewed_scope_approval_required", "approved delta identity or boundary is absent");
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, intent };
|
|
50
|
+
}
|
|
51
|
+
export function isRemainingWork(jobType, payload) {
|
|
52
|
+
if (["remediate", "rebase", "ci_fix", "reconcile_overlap", "resume"].includes(jobType))
|
|
53
|
+
return true;
|
|
54
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
55
|
+
return false;
|
|
56
|
+
const p = payload;
|
|
57
|
+
return p.correction_intent !== undefined || p.continuation !== undefined ||
|
|
58
|
+
p.continuation_of_job_id !== undefined || p.finalization !== undefined ||
|
|
59
|
+
p.remaining_checks !== undefined || p.required_checks_remaining !== undefined;
|
|
60
|
+
}
|
package/build/executor/env.js
CHANGED
|
@@ -118,6 +118,7 @@ const SECRET_NAME_FRAGMENTS = ["TOKEN", "SECRET", "PASSWORD", "API_KEY"];
|
|
|
118
118
|
* without anything in the executor choosing that.
|
|
119
119
|
*/
|
|
120
120
|
export const EXPLICIT_DENY_KEYS = [
|
|
121
|
+
"BAPI_CORRECTION_INTENT_VERSION",
|
|
121
122
|
"BRIDGE_MCP_PROFILE",
|
|
122
123
|
// BAPI-1301: the per-job ownership tag. The executor injects a fresh,
|
|
123
124
|
// unpredictable value AFTER this builder and the adapter have run
|
|
@@ -287,6 +288,11 @@ export function buildExecutorBaseWorkerEnv(parentEnv, options = {}) {
|
|
|
287
288
|
env.POSTGRES_DATABASE = WORKER_SENTINEL_DATABASE_NAME;
|
|
288
289
|
env.POSTGRES_DB = WORKER_SENTINEL_DATABASE_NAME;
|
|
289
290
|
env.PGDATABASE = WORKER_SENTINEL_DATABASE_NAME;
|
|
291
|
+
if (options.correctionIntentVersion !== undefined) {
|
|
292
|
+
if (options.correctionIntentVersion !== 1)
|
|
293
|
+
throw new Error("unsupported correction intent version");
|
|
294
|
+
env.BAPI_CORRECTION_INTENT_VERSION = "1";
|
|
295
|
+
}
|
|
290
296
|
if (typeof options.effectiveBaseBranch === "string" &&
|
|
291
297
|
options.effectiveBaseBranch.length > 0) {
|
|
292
298
|
env[PR_BASE_BRANCH_ENV_VAR] = options.effectiveBaseBranch;
|
|
@@ -294,6 +294,18 @@ export function createExecutorHttpClient(config) {
|
|
|
294
294
|
// 5xx and any other unexpected status are retryable.
|
|
295
295
|
return { kind: "retryable", error: `claim failed (HTTP ${status}): ${boundedDetail(text)}` };
|
|
296
296
|
},
|
|
297
|
+
async scopeReadiness(job) {
|
|
298
|
+
const { status, text } = await post(`/${job.id}/scope-readiness`, { repo_name: job.repo_name, claim_token: job.claim_token }, job.repo_name);
|
|
299
|
+
if (status !== 200)
|
|
300
|
+
throw new Error(`scope readiness HTTP ${status}`);
|
|
301
|
+
const parsed = JSON.parse(text);
|
|
302
|
+
if (!parsed || typeof parsed !== "object" ||
|
|
303
|
+
!("decision" in parsed) ||
|
|
304
|
+
!["ready", "waiting", "blocked"].includes(String(parsed.decision))) {
|
|
305
|
+
throw new Error("scope readiness response malformed");
|
|
306
|
+
}
|
|
307
|
+
return parsed;
|
|
308
|
+
},
|
|
297
309
|
// Heartbeat does NOT go through `mutate()`: it is the one mutation whose
|
|
298
310
|
// response BODY carries information beyond the outcome (the BAPI-731 stop
|
|
299
311
|
// request), so it maps the response itself instead of discarding the body.
|
|
@@ -371,6 +383,9 @@ export function createExecutorHttpClient(config) {
|
|
|
371
383
|
error_kind: failure.error_kind,
|
|
372
384
|
error_message: failure.error_message,
|
|
373
385
|
classification: failure.classification,
|
|
386
|
+
// BAPI-1330: the observed process exit, only when the runner has one —
|
|
387
|
+
// a failure without it keeps its exact pre-BAPI-1330 body.
|
|
388
|
+
...(failure.exit_code !== undefined ? { exit_code: failure.exit_code } : {}),
|
|
374
389
|
local_commit_count: failure.local_commit_count,
|
|
375
390
|
last_commit_sha: failure.last_commit_sha,
|
|
376
391
|
telemetry: failure.telemetry,
|
|
@@ -703,13 +703,18 @@ export function buildIntegrationCorrectionJob(action, timeoutSeconds) {
|
|
|
703
703
|
job_type: "rebase",
|
|
704
704
|
ticket_key: action.epic_key,
|
|
705
705
|
epic_run_id: action.epic_run_id,
|
|
706
|
-
payload: {
|
|
706
|
+
payload: {
|
|
707
|
+
prompt: action.correction_prompt ?? "", base_branch: action.base_branch,
|
|
708
|
+
correction_intent: action.correction_intent ?? null,
|
|
709
|
+
integration_action_id: action.action_id,
|
|
710
|
+
},
|
|
707
711
|
expected_branch: action.epic_branch,
|
|
708
712
|
expected_head_sha: action.expected_epic_head_sha,
|
|
709
713
|
lease_expires_at: action.lease_expires_at,
|
|
710
714
|
attempt_count: 1,
|
|
711
715
|
max_attempts: 1,
|
|
712
716
|
timeout_seconds: timeoutSeconds,
|
|
717
|
+
required_capabilities: ["correction_intent.v1"],
|
|
713
718
|
};
|
|
714
719
|
}
|
|
715
720
|
/**
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
* exited cleanly and its exact HEAD is durable on origin, but no usable pull
|
|
29
29
|
* request exists for that branch: none is visible, its URL is blank, its
|
|
30
30
|
* description is blank (BAPI-824 folded in), or it does not yet carry the
|
|
31
|
-
* pushed head. The
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
31
|
+
* pushed head. The commits are PUSHED and the attachment is missing; whether
|
|
32
|
+
* the required work is finished or its checks passed is NOT established by
|
|
33
|
+
* this kind (BAPI-1330). It stays distinct from every other finalization
|
|
34
|
+
* failure, and its guidance names attachment and warns against deleting or
|
|
35
|
+
* rebasing the branch; see `worker-finalization.ts`.
|
|
35
36
|
* - `WorkerFinalizationSavedButUnfinalized` — an implementation-style job
|
|
36
37
|
* (BAPI-762) exited cleanly, and an origin branch exists at an authoritative
|
|
37
38
|
* tip that does NOT match the worker's own HEAD — durable work exists on
|
|
@@ -132,6 +133,14 @@ export const WorkerFinalizationMissingRemoteBranchAndPr = "WorkerFinalizationMis
|
|
|
132
133
|
export const WorkerFinalizationPrBaseMismatch = "WorkerFinalizationPrBaseMismatch";
|
|
133
134
|
export const WorkerFinalizationPrNotAttached = "WorkerFinalizationPrNotAttached";
|
|
134
135
|
export const WorkerFinalizationSavedButUnfinalized = "WorkerFinalizationSavedButUnfinalized";
|
|
136
|
+
/**
|
|
137
|
+
* BAPI-1330: a COVERED attempt's publication could not be proven — the worker's
|
|
138
|
+
* full HEAD was unknown, or the pull request reported no head commit. Distinct
|
|
139
|
+
* from `WorkerFinalizationPrNotAttached` because nothing here was observed to be
|
|
140
|
+
* missing: the facts needed to decide were unavailable, so the finalization
|
|
141
|
+
* verdict is `unknown`, never `unmet` and never `ok`.
|
|
142
|
+
*/
|
|
143
|
+
export const WorkerFinalizationPublicationUnverified = "WorkerFinalizationPublicationUnverified";
|
|
135
144
|
export const WorkerConfigIsolation = "ContractError.WorkerConfigIsolation";
|
|
136
145
|
export const PreSpawnVerification = "ContractError.PreSpawnVerification";
|
|
137
146
|
export const BridgeCredentialUnavailable = "ContractError.BridgeCredentialUnavailable";
|