@bridge_gpt/mcp-server 0.2.65 → 0.2.67
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 +269 -82
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +55 -34
- package/build/agent-launchers/executor-adapter.js +34 -4
- package/build/agents.generated.js +1 -1
- package/build/bounded-wait.js +67 -10
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +12 -12
- package/build/conductor/bridge-api-client.js +372 -10
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/doctor.js +3 -3
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +34 -6
- package/build/conductor/errors.js +4 -3
- package/build/conductor/guided-repair.js +327 -0
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/merge-execution-contract.js +27 -8
- package/build/conductor/merge-execution-eligibility.js +3 -2
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +46 -9
- package/build/conductor/readiness-sections.js +41 -6
- package/build/conductor/readiness.js +204 -30
- package/build/conductor/recovery-cli.js +703 -20
- package/build/conductor/recovery-operations.js +392 -3
- package/build/conductor/repair-contract.js +112 -3
- package/build/conductor/repository-workflow-remedy.js +125 -0
- package/build/conductor/tools.js +2 -2
- package/build/conductor-bundle-artifacts.js +8 -0
- package/build/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +8 -3
- package/build/doctor.js +70 -10
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +479 -131
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-implementer-bin.js +30 -26
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/cli.js +3 -2
- package/build/executor/completion-contract.js +48 -5
- package/build/executor/completion-supervision.js +9 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +25 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +35 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +38 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +485 -74
- package/build/executor/job-types.js +8 -13
- package/build/executor/observation.js +6 -1
- package/build/executor/preflight.js +136 -10
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/required-work-ledger.js +6 -1
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/runner.js +31 -2
- package/build/executor/service-unit.js +7 -1
- 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 +161 -43
- package/build/executor/worker-guard-hook.js +152 -12
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/executor/worktree.js +82 -1
- package/build/index.js +482 -660
- package/build/init.js +107 -4
- package/build/install-bridge-conductor.js +8 -6
- package/build/install-bridge.js +287 -23
- 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/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipelines.generated.js +19 -25
- package/build/plane/alembic-head.js +12 -3
- package/build/plane/base-worktree-guard.js +147 -0
- package/build/plane/cli.js +217 -17
- package/build/plane/code-snapshot.js +468 -0
- package/build/plane/defaults.js +48 -3
- package/build/plane/endpoint.js +41 -0
- package/build/plane/manifest.js +120 -1
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +30 -2
- package/build/plane/process-cwd.js +67 -0
- package/build/plane/restart.js +542 -0
- package/build/plane/shutdown.js +31 -0
- package/build/plane/status.js +145 -6
- package/build/plane/supervisor.js +588 -177
- package/build/plane/types.js +54 -2
- package/build/plane/worker-exit-code.js +2 -0
- package/build/printed-remedy-commands.js +37 -0
- package/build/project-root.js +99 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +22 -0
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +224 -127
- package/build/sfcc/config.js +9 -0
- package/build/sfcc/credentials.js +100 -38
- package/build/sfcc/hostname.js +67 -0
- package/build/sfcc/permissions.js +17 -13
- package/build/sfcc/register.js +1 -1
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +41 -43
- package/build/sfcc/tools-configured.js +96 -0
- package/build/sfcc/write-grants.js +17 -11
- package/build/sfcc/write-result.js +4 -3
- package/build/sfcc/writes-site-preference.js +20 -8
- package/build/start-tickets.js +332 -79
- package/build/tool-error-envelope.js +61 -0
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +3 -5
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +136 -9
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
- package/docs/EPIC-IMPLEMENTER.md +662 -0
- package/docs/install/mcp-tool-integrations.md +25 -15
- package/docs/install/sfcc-integration.md +21 -8
- package/package.json +1 -1
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +0 -5
- package/pipelines/review-ticket.json +1 -1
- package/docs/CONDUCTOR.md +0 -605
- package/pipelines/idea-to-pr.json +0 -49
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
* Single source of truth for which claimed job types the executor spawns and
|
|
5
5
|
* which of those are RECOVERY jobs. Recovery jobs (`remediate`/`ci_fix`/`rebase`/
|
|
6
6
|
* `reconcile_overlap`) resume the ticket's existing branch/PR rather than cutting
|
|
7
|
-
* a fresh branch off base
|
|
8
|
-
*
|
|
9
|
-
* (BAPI-1296: never the full recipe), `reconcile_overlap` through its own job
|
|
10
|
-
* body. Dispatch
|
|
7
|
+
* a fresh branch off base. Each recovery worker requires a versioned focused
|
|
8
|
+
* correction intent; `ci_fix` also uses its focused prompt spec. Dispatch
|
|
11
9
|
* (`job-runner.ts` `runClaimedJob`), prompt synthesis (`worker-command.ts`
|
|
12
10
|
* `resolveExecutorPrompt`), and worktree reuse (`job-runner.ts` `prepareSpawn` →
|
|
13
11
|
* `worktree.ts` `ensureExecutorWorktree`) all read from here so the recovery /
|
|
@@ -24,16 +22,13 @@
|
|
|
24
22
|
* `reconcile_overlap`). They work on the ticket's EXISTING branch/PR, so the
|
|
25
23
|
* executor reuses that branch instead of cutting a fresh one off base.
|
|
26
24
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* refuses to synthesize `/implement-ticket` for `ci_fix`, and `job-runner.ts`
|
|
31
|
-
* renders its server-authored focused prompt spec instead.
|
|
25
|
+
* `ci_fix` stays a recovery job for branch reuse, the worktree lock, and
|
|
26
|
+
* finalization. `job-runner.ts` renders its server-authored focused prompt spec
|
|
27
|
+
* after the correction intent is validated.
|
|
32
28
|
*
|
|
33
|
-
*
|
|
34
|
-
* resolution
|
|
35
|
-
*
|
|
36
|
-
* `worker-command.ts`, and `job-runner.ts` dispatches it to its own body.
|
|
29
|
+
* `reconcile_overlap` is a recovery job for worktree reuse and prompt
|
|
30
|
+
* resolution. Its server-rendered focused prompt is available only after
|
|
31
|
+
* validating the correction intent, and `job-runner.ts` dispatches its body.
|
|
37
32
|
*
|
|
38
33
|
* BAPI-1160: `ci_rerun` is DELIBERATELY ABSENT from this set and from
|
|
39
34
|
* `SPAWN_JOB_TYPES` below. It is a deterministic actuator — one bounded
|
|
@@ -34,9 +34,14 @@ export async function collectGitTelemetry(deps, worktreePath, baseBranch) {
|
|
|
34
34
|
/* degraded: omit last_commit_sha */
|
|
35
35
|
}
|
|
36
36
|
try {
|
|
37
|
+
// `--no-optional-locks`: a plain `git status` refreshes the index and takes
|
|
38
|
+
// `.git/index.lock`. This runs on every heartbeat against the LIVE worker's
|
|
39
|
+
// worktree, so without it a worker's own `git add`/`commit` can fail with
|
|
40
|
+
// "index.lock: File exists".
|
|
37
41
|
const status = await deps.runCommand("git", [
|
|
38
42
|
"-C",
|
|
39
43
|
worktreePath,
|
|
44
|
+
"--no-optional-locks",
|
|
40
45
|
"status",
|
|
41
46
|
"--porcelain",
|
|
42
47
|
]);
|
|
@@ -293,7 +298,7 @@ const RATE_LIMIT_CONSEQUENCE_TEXT = "This warning is advisory only and triggers
|
|
|
293
298
|
"rejection and the server advertised capacity recovery for this job; otherwise " +
|
|
294
299
|
"the failure follows ordinary failure handling.";
|
|
295
300
|
/** Fixed runbook reference appended to every rate-limit diagnostic (BAPI-898). */
|
|
296
|
-
const RATE_LIMIT_RUNBOOK_REFERENCE = "See docs/
|
|
301
|
+
const RATE_LIMIT_RUNBOOK_REFERENCE = "See docs/epic-implementer/operator-runbook.md §8.";
|
|
297
302
|
/**
|
|
298
303
|
* Render the operator-facing rate-limit line (BAPI-828, BAPI-898).
|
|
299
304
|
*
|
|
@@ -2,10 +2,17 @@ import { DEFAULT_PROBE_TIMEOUT_MS } from "../agent-capabilities/types.js";
|
|
|
2
2
|
import { inspectExecutorAgentAdapter, inspectionForFailedResolution, } from "../agent-launchers/executor-adapter-inspection.js";
|
|
3
3
|
import { resolveExecutorAgentAdapter } from "../agent-launchers/executor-adapter-registry.js";
|
|
4
4
|
import { resolveCompletionEvidenceCapability } from "../agent-launchers/executor-adapter.js";
|
|
5
|
-
import { COMPLETION_EVIDENCE_CAPABILITY, REQUIRED_VERIFICATION_CAPABILITY, runtimeMatchesSupported, } from "./completion-contract.js";
|
|
5
|
+
import { COMPLETION_EVIDENCE_CAPABILITY, REQUIRED_VERIFICATION_CAPABILITY, cliVersionAtLeast, normalizeCliVersion, parseCliVersion, runtimeMatchesSupported, } from "./completion-contract.js";
|
|
6
|
+
import { buildHeadlessAgentEnv } from "./env.js";
|
|
6
7
|
import { resolveExecutorAgentId } from "./agent-identity.js";
|
|
8
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { defaultResolveBinPath } from "../start-tickets-conductor.js";
|
|
13
|
+
import { CORRECTION_INTENT_CAPABILITY } from "./correction-dispatch.js";
|
|
7
14
|
import { resolveAllExecutorApiAccess, resolveBaseUrl, EXECUTOR_BASE_URL_REQUIRED_MESSAGE, } from "./credentials.js";
|
|
8
|
-
import { FOCUSED_REBASE_CAPABILITY } from "./types.js";
|
|
15
|
+
import { FOCUSED_REBASE_CAPABILITY, SCOPE_WAIT_CAPABILITY } from "./types.js";
|
|
9
16
|
/**
|
|
10
17
|
* Environment override for the deny probe's headless timeout (BAPI-722).
|
|
11
18
|
*
|
|
@@ -117,20 +124,34 @@ export async function evaluateCompletionEvidenceQualification(adapter, deps) {
|
|
|
117
124
|
if (probed === null) {
|
|
118
125
|
return { qualified: false, refusal: "runtime identity probe did not run", runtime: null };
|
|
119
126
|
}
|
|
120
|
-
const declared = capability.value.
|
|
127
|
+
const declared = capability.value.predicate;
|
|
121
128
|
const mismatches = [];
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
129
|
+
if (probed.adapter !== declared.adapter) {
|
|
130
|
+
mismatches.push(`adapter ${String(probed.adapter).slice(0, 40)} != ${declared.adapter}`);
|
|
131
|
+
}
|
|
132
|
+
if (probed.event_schema !== declared.event_schema) {
|
|
133
|
+
mismatches.push(`event_schema ${String(probed.event_schema).slice(0, 40)} != ${declared.event_schema}`);
|
|
126
134
|
}
|
|
127
135
|
if (mismatches.length > 0) {
|
|
128
136
|
return {
|
|
129
137
|
qualified: false,
|
|
130
|
-
refusal: bounded(`probed runtime differs from the
|
|
138
|
+
refusal: bounded(`probed runtime differs from the declared predicate: ${mismatches.join("; ")}`),
|
|
131
139
|
runtime: probed,
|
|
132
140
|
};
|
|
133
141
|
}
|
|
142
|
+
// Fixed reasons below: the probed version and platform are never echoed, because
|
|
143
|
+
// an unparseable value is arbitrary probe output.
|
|
144
|
+
const version = parseCliVersion(probed.cli_version);
|
|
145
|
+
const minimum = parseCliVersion(declared.min_cli_version);
|
|
146
|
+
if (version === null || minimum === null) {
|
|
147
|
+
return { qualified: false, refusal: "probed CLI version is not a recognized version", runtime: probed };
|
|
148
|
+
}
|
|
149
|
+
if (!cliVersionAtLeast(version, minimum)) {
|
|
150
|
+
return { qualified: false, refusal: "probed CLI version is older than the minimum", runtime: probed };
|
|
151
|
+
}
|
|
152
|
+
if (!declared.platforms.includes(probed.platform)) {
|
|
153
|
+
return { qualified: false, refusal: "probed platform is not a supported platform", runtime: probed };
|
|
154
|
+
}
|
|
134
155
|
if (!runtimeMatchesSupported(probed)) {
|
|
135
156
|
return {
|
|
136
157
|
qualified: false,
|
|
@@ -138,7 +159,12 @@ export async function evaluateCompletionEvidenceQualification(adapter, deps) {
|
|
|
138
159
|
runtime: probed,
|
|
139
160
|
};
|
|
140
161
|
}
|
|
141
|
-
|
|
162
|
+
// Normalized (suffix stripped) so the retained identity and the envelope carry one form.
|
|
163
|
+
const normalized = normalizeCliVersion(probed.cli_version);
|
|
164
|
+
if (normalized === null) {
|
|
165
|
+
return { qualified: false, refusal: "probed CLI version is not a recognized version", runtime: probed };
|
|
166
|
+
}
|
|
167
|
+
return { qualified: true, refusal: null, runtime: { ...probed, cli_version: normalized } };
|
|
142
168
|
}
|
|
143
169
|
/** Fail-fast report shape for a preflight that refused before probing. */
|
|
144
170
|
function refusedPreflight(fatalFindings, warnings, adapter) {
|
|
@@ -154,6 +180,68 @@ function refusedPreflight(fatalFindings, warnings, adapter) {
|
|
|
154
180
|
adapter,
|
|
155
181
|
};
|
|
156
182
|
}
|
|
183
|
+
export const CORRECTION_HOOK_FIXTURE_ID = "correction-entry-nested-v2";
|
|
184
|
+
const spawnCorrectionHook = (args, input, env) => {
|
|
185
|
+
const result = spawnSync(process.execPath, args, { input, encoding: "utf8", env, timeout: 5000 });
|
|
186
|
+
return { status: result.status, ...(result.error ? { error: result.error } : {}) };
|
|
187
|
+
};
|
|
188
|
+
/** Exercise the packaged hook process, including a nested-agent entry attempt. */
|
|
189
|
+
export function probeInstalledCorrectionHook(worktreePath, binPath = defaultResolveBinPath("worker-guard-hook-bin.js"), run = spawnCorrectionHook) {
|
|
190
|
+
const args = [binPath, "--worktree-root", worktreePath,
|
|
191
|
+
"--correction-intent-version", "1"];
|
|
192
|
+
const env = { ...process.env, BAPI_CORRECTION_INTENT_VERSION: "1" };
|
|
193
|
+
const fixtures = [
|
|
194
|
+
{ payload: { tool_name: "Skill", tool_input: { skill: "bridge-implement-ticket" } }, status: 2 },
|
|
195
|
+
{ payload: { tool_name: "Task", tool_input: { prompt: "/implement-ticket BAPI-1 --auto" } }, status: 2 },
|
|
196
|
+
{ payload: { tool_name: "Read", tool_input: { file_path: "commands/src/implement-ticket.md" } }, status: 0 },
|
|
197
|
+
{
|
|
198
|
+
payload: { tool_name: "Bash", tool_input: { command: 'claude -p "/implement-ticket BAPI-1"' } },
|
|
199
|
+
status: 2,
|
|
200
|
+
},
|
|
201
|
+
{ payload: { tool_name: "mcp__bridge_api__request_plan_generation", tool_input: {} }, status: 2 },
|
|
202
|
+
{ payload: { tool_name: "mcp__bridge_api__get_plan", tool_input: { ticket_number: "BAPI-1" } }, status: 0 },
|
|
203
|
+
];
|
|
204
|
+
return fixtures.every(({ payload, status }) => {
|
|
205
|
+
const result = run(args, JSON.stringify(payload), env);
|
|
206
|
+
return result.error === undefined && result.status === status;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
export async function qualifyCorrectionIntentRuntime(adapter, deps, probeHook = probeInstalledCorrectionHook) {
|
|
210
|
+
if (adapter.identity.strategyId !== "claude-strict-mcp-v1" ||
|
|
211
|
+
adapter.denyEnforcement?.supported !== true || !deps.writeFile || !deps.mkdir) {
|
|
212
|
+
return { qualified: false, evidence: null };
|
|
213
|
+
}
|
|
214
|
+
const worktree = await (deps.mkdtemp ?? mkdtemp)(join((deps.tmpdir ?? tmpdir)(), "bridge-correction-hook-"));
|
|
215
|
+
try {
|
|
216
|
+
const bin = defaultResolveBinPath("worker-guard-hook-bin.js");
|
|
217
|
+
const installed = await adapter.denyEnforcement.value.provisionWorktreeDenyLayer({
|
|
218
|
+
worktreePath: worktree, baseBranch: "main", homedir: deps.homedir(),
|
|
219
|
+
workerBranch: "feature/probe", workerGuardHookBinPath: bin,
|
|
220
|
+
correctionIntentVersion: 1,
|
|
221
|
+
});
|
|
222
|
+
const settings = await deps.readFile(join(worktree, ".claude", "settings.local.json"));
|
|
223
|
+
const hookInstalled = installed.ok && settings.includes(bin) &&
|
|
224
|
+
settings.includes("--correction-intent-version 1") && settings.includes("PreToolUse");
|
|
225
|
+
const nestedDenial = hookInstalled && probeHook(worktree, bin);
|
|
226
|
+
return {
|
|
227
|
+
qualified: hookInstalled && nestedDenial,
|
|
228
|
+
evidence: {
|
|
229
|
+
version: 1, hook_installed: hookInstalled, nested_denial: nestedDenial,
|
|
230
|
+
fixture_id: CORRECTION_HOOK_FIXTURE_ID,
|
|
231
|
+
runtime: `${adapter.identity.adapterId}:${process.version}`,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return { qualified: false, evidence: null };
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
if (deps.rmRecursive)
|
|
240
|
+
await deps.rmRecursive(worktree);
|
|
241
|
+
else
|
|
242
|
+
await rm(worktree, { recursive: true, force: true });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
157
245
|
/**
|
|
158
246
|
* Collect the executor preflight report. Populates the manifest fields and
|
|
159
247
|
* accumulates fatal findings — the runner refuses to claim when any exist.
|
|
@@ -176,7 +264,15 @@ export async function collectExecutorPreflight(options, deps, seams = {}) {
|
|
|
176
264
|
// preflight's own injected `readFile` keeps the inspection hermetic in
|
|
177
265
|
// tests and read-only in production — the adapter never opens a file
|
|
178
266
|
// handle this module did not hand it.
|
|
179
|
-
claude: {
|
|
267
|
+
claude: {
|
|
268
|
+
readFile: deps.readFile,
|
|
269
|
+
...(deps.writeFile && deps.mkdir ? {
|
|
270
|
+
denyProvisioningDeps: {
|
|
271
|
+
readFile: deps.readFile, writeFile: deps.writeFile,
|
|
272
|
+
mkdir: deps.mkdir, homedir: deps.homedir,
|
|
273
|
+
},
|
|
274
|
+
} : {}),
|
|
275
|
+
},
|
|
180
276
|
}));
|
|
181
277
|
const resolution = resolveAdapter(agentId);
|
|
182
278
|
if (!resolution.supported) {
|
|
@@ -341,8 +437,11 @@ export async function collectExecutorPreflight(options, deps, seams = {}) {
|
|
|
341
437
|
if (denyCapability?.supported !== true) {
|
|
342
438
|
throw new Error("adapter declares no deny-enforcement capability");
|
|
343
439
|
}
|
|
440
|
+
// The probe gets a worker-shaped environment so a nested-session variable in
|
|
441
|
+
// the executor's own process cannot change its verdict (BAPI-1386).
|
|
344
442
|
const verdict = await denyCapability.value.probeEnforcement({
|
|
345
443
|
timeoutMs: resolveDenyProbeTimeoutMs(deps.env),
|
|
444
|
+
env: buildHeadlessAgentEnv(deps.env),
|
|
346
445
|
});
|
|
347
446
|
deny = {
|
|
348
447
|
enforced: verdict.enforced,
|
|
@@ -416,10 +515,32 @@ export async function collectExecutorPreflight(options, deps, seams = {}) {
|
|
|
416
515
|
if (!completion.qualified && completion.refusal !== null) {
|
|
417
516
|
warnings.push(`completion evidence not advertised: ${completion.refusal}`);
|
|
418
517
|
}
|
|
518
|
+
// One notice per qualifying collection, built only from validated fields: the
|
|
519
|
+
// adapter id the predicate matched, the normalized version, and a closed platform id.
|
|
520
|
+
const notices = [];
|
|
521
|
+
if (completion.qualified && completion.runtime !== null) {
|
|
522
|
+
notices.push(`completion evidence advertised: ${completion.runtime.adapter} ` +
|
|
523
|
+
`${completion.runtime.cli_version} ${completion.runtime.platform}`);
|
|
524
|
+
}
|
|
525
|
+
let correction = {
|
|
526
|
+
qualified: false, evidence: null,
|
|
527
|
+
};
|
|
528
|
+
if (fatalFindings.length === 0) {
|
|
529
|
+
try {
|
|
530
|
+
correction = await qualifyCorrectionIntentRuntime(adapter, deps);
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
correction = { qualified: false, evidence: null };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (!correction.qualified) {
|
|
537
|
+
warnings.push("correction intent not advertised: installed nested hook unverified");
|
|
538
|
+
}
|
|
419
539
|
return {
|
|
420
540
|
ok: fatalFindings.length === 0,
|
|
421
541
|
fatalFindings,
|
|
422
542
|
warnings,
|
|
543
|
+
notices,
|
|
423
544
|
agentVersion,
|
|
424
545
|
wtVersion,
|
|
425
546
|
diskFreeGb,
|
|
@@ -429,6 +550,8 @@ export async function collectExecutorPreflight(options, deps, seams = {}) {
|
|
|
429
550
|
completionEvidenceQualified: completion.qualified,
|
|
430
551
|
completionEvidenceRefusal: completion.refusal,
|
|
431
552
|
completionRuntime: completion.runtime,
|
|
553
|
+
correctionIntentQualified: correction.qualified,
|
|
554
|
+
correctionQualificationEvidence: correction.evidence,
|
|
432
555
|
};
|
|
433
556
|
}
|
|
434
557
|
/**
|
|
@@ -490,6 +613,9 @@ export function buildClaimManifest(report, options, freeSlots) {
|
|
|
490
613
|
? [COMPLETION_EVIDENCE_CAPABILITY, REQUIRED_VERIFICATION_CAPABILITY]
|
|
491
614
|
: []),
|
|
492
615
|
FOCUSED_REBASE_CAPABILITY,
|
|
616
|
+
SCOPE_WAIT_CAPABILITY,
|
|
617
|
+
...(report.correctionIntentQualified === true ? [CORRECTION_INTENT_CAPABILITY] : []),
|
|
618
|
+
...(report.correctionIntentQualified === true ? ["ci_evidence.v1"] : []),
|
|
493
619
|
],
|
|
494
620
|
};
|
|
495
621
|
}
|
|
@@ -114,6 +114,93 @@ function hasControlCharacter(value) {
|
|
|
114
114
|
}
|
|
115
115
|
return false;
|
|
116
116
|
}
|
|
117
|
+
function sanitizeCiText(value, maxBytes = 512) {
|
|
118
|
+
const redacted = value
|
|
119
|
+
.replace(/gh[pousr]_[A-Za-z0-9]{20,}/giu, "[REDACTED]")
|
|
120
|
+
.replace(/bearer\s+[^\s,;]+/giu, "Bearer [REDACTED]")
|
|
121
|
+
.replace(/authorization:\s*[^\s,;]+/giu, "Authorization: [REDACTED]")
|
|
122
|
+
.replace(/https?:\/\/[^\s/@<>]+:[^\s/@<>]+@[^\s<>]+/giu, "[REDACTED URL]")
|
|
123
|
+
.replace(/https?:\/\/[^\s<>]+\?[^\s<>]+/giu, "[REDACTED URL]");
|
|
124
|
+
return sliceUtf8(redacted, maxBytes).replace(/[\x00-\x08\x0B-\x1F\x7F]/gu, " ");
|
|
125
|
+
}
|
|
126
|
+
function safeCiSourceRef(raw, repository) {
|
|
127
|
+
if (typeof raw !== "string" || raw.length > 1024)
|
|
128
|
+
return null;
|
|
129
|
+
try {
|
|
130
|
+
const url = new URL(raw);
|
|
131
|
+
if (url.protocol !== "https:" || url.hostname !== "github.com" ||
|
|
132
|
+
url.username || url.password || url.search || url.hash ||
|
|
133
|
+
!url.pathname.toLowerCase().startsWith(`/${repository.toLowerCase()}/`))
|
|
134
|
+
return null;
|
|
135
|
+
return url.toString();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function readCiEvidence(raw, headSha, failing) {
|
|
142
|
+
if (raw === null || raw === undefined)
|
|
143
|
+
return { status: "unavailable", reason: "legacy_no_snapshot" };
|
|
144
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
145
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
146
|
+
}
|
|
147
|
+
const item = raw;
|
|
148
|
+
let serializedLength;
|
|
149
|
+
try {
|
|
150
|
+
serializedLength = JSON.stringify(item).length;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
154
|
+
}
|
|
155
|
+
if (item.version !== 1 || item.head_sha !== headSha ||
|
|
156
|
+
typeof item.repository !== "string" || !item.repository ||
|
|
157
|
+
typeof item.target_branch !== "string" || !item.target_branch ||
|
|
158
|
+
typeof item.policy_source !== "string" ||
|
|
159
|
+
typeof item.policy_fingerprint !== "string" ||
|
|
160
|
+
!Array.isArray(item.required_failures) || item.required_failures.length === 0 ||
|
|
161
|
+
item.required_failures.length > 20 || serializedLength > 24_000) {
|
|
162
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
163
|
+
}
|
|
164
|
+
const required = [];
|
|
165
|
+
for (const candidate of item.required_failures) {
|
|
166
|
+
if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
167
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
168
|
+
}
|
|
169
|
+
const record = candidate;
|
|
170
|
+
if (typeof record.name !== "string" || !failing.has(record.name)) {
|
|
171
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
172
|
+
}
|
|
173
|
+
required.push({
|
|
174
|
+
name: record.name,
|
|
175
|
+
sourceKind: typeof record.source_kind === "string" ? sanitizeCiText(record.source_kind, 32) : "unknown",
|
|
176
|
+
conclusion: typeof record.conclusion === "string" ? sanitizeCiText(record.conclusion, 32) : "unknown",
|
|
177
|
+
runId: typeof record.run_id === "number" && Number.isSafeInteger(record.run_id) && record.run_id > 0 ? record.run_id : null,
|
|
178
|
+
summary: typeof record.summary === "string" ? sanitizeCiText(record.summary) : "",
|
|
179
|
+
sourceRef: safeCiSourceRef(record.source_ref, item.repository),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (required.length !== failing.size ||
|
|
183
|
+
new Set(required.map(record => record.name)).size !== failing.size) {
|
|
184
|
+
return { status: "unavailable", reason: "malformed_snapshot" };
|
|
185
|
+
}
|
|
186
|
+
const related = Array.isArray(item.related_failures) ? item.related_failures.slice(0, 8)
|
|
187
|
+
.filter((record) => !!record && typeof record === "object" && !Array.isArray(record))
|
|
188
|
+
.map((record) => typeof record.name === "string" ? sanitizeCiText(record.name, 200) : "(unnamed)") : [];
|
|
189
|
+
return {
|
|
190
|
+
status: "available",
|
|
191
|
+
policySource: sanitizeCiText(item.policy_source, 32),
|
|
192
|
+
policyFingerprint: sanitizeCiText(item.policy_fingerprint, 64),
|
|
193
|
+
repository: sanitizeCiText(item.repository, 200),
|
|
194
|
+
targetBranch: sanitizeCiText(item.target_branch, 200),
|
|
195
|
+
headSha,
|
|
196
|
+
observedAt: typeof item.observed_at === "string" ? sanitizeCiText(item.observed_at, 64) : "unavailable",
|
|
197
|
+
freshness: typeof item.freshness === "string" ? sanitizeCiText(item.freshness, 64) : "unavailable",
|
|
198
|
+
coverage: typeof item.coverage === "string" ? sanitizeCiText(item.coverage, 64) : "unknown",
|
|
199
|
+
availability: typeof item.availability === "string" ? sanitizeCiText(item.availability, 64) : "unavailable",
|
|
200
|
+
required,
|
|
201
|
+
related,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
117
204
|
/** Validate + de-duplicate one check-name list; `null` when a name is unusable. */
|
|
118
205
|
function readCheckNames(raw) {
|
|
119
206
|
if (raw === undefined || raw === null)
|
|
@@ -286,21 +373,38 @@ export function readCiFixDispatchContext(job) {
|
|
|
286
373
|
missingChecks: missing,
|
|
287
374
|
requiredClaimChecks: required,
|
|
288
375
|
logExcerpts,
|
|
376
|
+
evidence: readCiEvidence(raw.evidence_snapshot, headSha, new Set(failing)),
|
|
289
377
|
};
|
|
290
378
|
}
|
|
291
379
|
function formatCheckList(names, none) {
|
|
292
|
-
return names.length > 0 ? names.map((name) => `- ${name}`).join("\n") : none;
|
|
380
|
+
return names.length > 0 ? names.map((name) => `- ${sanitizeCiText(name, 200)}`).join("\n") : none;
|
|
293
381
|
}
|
|
294
|
-
function formatLogExcerpts(
|
|
382
|
+
function formatLogExcerpts(context) {
|
|
383
|
+
const evidence = context.evidence;
|
|
384
|
+
const header = evidence.status === "unavailable"
|
|
385
|
+
? `CI snapshot UNAVAILABLE (${evidence.reason}).` : [
|
|
386
|
+
`Policy: ${evidence.policySource} (${evidence.policyFingerprint})`,
|
|
387
|
+
`Target: ${evidence.repository} ${evidence.targetBranch} @ ${evidence.headSha}`,
|
|
388
|
+
`Observed: ${evidence.observedAt}; freshness: ${evidence.freshness}`,
|
|
389
|
+
`Coverage: ${evidence.coverage}; detail: ${evidence.availability}`,
|
|
390
|
+
"Selected required failures:",
|
|
391
|
+
...evidence.required.map((item) => `- ${sanitizeCiText(item.name, 200)} [${item.sourceKind}; run ${item.runId ?? "unavailable"}; ${item.conclusion}]${item.sourceRef ? ` ${item.sourceRef}` : ""}`),
|
|
392
|
+
"Related failures — contribution not established:",
|
|
393
|
+
...evidence.related.map((name) => `- ${name}`),
|
|
394
|
+
].join("\n");
|
|
395
|
+
const excerpts = context.logExcerpts;
|
|
295
396
|
if (excerpts.status === "unavailable") {
|
|
296
|
-
return ("UNAVAILABLE — no log excerpt was supplied with this job " +
|
|
397
|
+
return header + "\n## UNTRUSTED PROVIDER DIAGNOSTICS BEGIN ##\n" + ("UNAVAILABLE — no log excerpt was supplied with this job " +
|
|
297
398
|
`(reason: ${excerpts.reason}). No log content is provided below or ` +
|
|
298
399
|
"anywhere else in this prompt. Diagnose from the check names, the code, " +
|
|
299
|
-
"and local reproduction; do not assume what any log said.");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
.
|
|
303
|
-
|
|
400
|
+
"and local reproduction; do not assume what any log said.") + "\n## UNTRUSTED PROVIDER DIAGNOSTICS END ##";
|
|
401
|
+
}
|
|
402
|
+
const summaries = evidence.status === "available"
|
|
403
|
+
? evidence.required.filter((item) => item.summary).map((item) => `${sanitizeCiText(item.name, 200)} selected summary: ${item.summary}`)
|
|
404
|
+
: [];
|
|
405
|
+
return header + "\n## UNTRUSTED PROVIDER DIAGNOSTICS BEGIN ##\n" +
|
|
406
|
+
[...summaries, ...excerpts.excerpts.map((e) => `--- check: ${sanitizeCiText(e.check, 200)} (source: ${sanitizeCiText(e.source, 200)}) ---\n${sanitizeCiText(e.text)}`)].join("\n\n") +
|
|
407
|
+
"\n## UNTRUSTED PROVIDER DIAGNOSTICS END ##";
|
|
304
408
|
}
|
|
305
409
|
/**
|
|
306
410
|
* Explicit "no stale-citation advisory" value (BAPI-722) — never an empty string.
|
|
@@ -487,7 +591,7 @@ export async function buildPromptSpecPlaceholderValues(job, spec, context) {
|
|
|
487
591
|
const ci = readCiFixDispatchContext(job);
|
|
488
592
|
values.FAILING_CHECKS = formatCheckList(ci.failingChecks, "(none — no check was observed failing on the dispatched head)");
|
|
489
593
|
values.MISSING_CHECKS = formatCheckList(ci.missingChecks, "(none — every required check reported on the dispatched head)");
|
|
490
|
-
values.LOG_EXCERPTS = formatLogExcerpts(ci
|
|
594
|
+
values.LOG_EXCERPTS = formatLogExcerpts(ci);
|
|
491
595
|
break;
|
|
492
596
|
}
|
|
493
597
|
case "SPEC_CITATION_FRESHNESS_WARNING": {
|
|
@@ -877,6 +877,10 @@ export function createRequiredWorkLedger(init) {
|
|
|
877
877
|
const readyToClose = resultsSeen > 0 && latestIsResult && !turnOpen && liveRequired.length === 0 && snap.required.length === 0;
|
|
878
878
|
return { readyToClose, liveRequired, liveOptional, activitySeq, latestIsResult };
|
|
879
879
|
};
|
|
880
|
+
// One predicate for "a required task is still pending", shared by the envelope
|
|
881
|
+
// and the observe-only query so the two cannot drift apart.
|
|
882
|
+
const hasPendingRequiredWork = () => effectiveTasks().some(({ o, state }) => o.required && state === "pending");
|
|
883
|
+
const observeLiveRequiredWork = () => hasPendingRequiredWork() ? "live_required_work" : null;
|
|
880
884
|
const buildEnvelope = (facts) => {
|
|
881
885
|
const reasons = new Set(insufficient);
|
|
882
886
|
if (!lifecycleSupported || !runtimeMatchesSupported(facts.runtime))
|
|
@@ -897,7 +901,7 @@ export function createRequiredWorkLedger(init) {
|
|
|
897
901
|
reasons.add("forced_kill_no_cli_teardown");
|
|
898
902
|
const effective = effectiveTasks();
|
|
899
903
|
// R6: a required task with no terminal after the stream ended is missing evidence.
|
|
900
|
-
if (
|
|
904
|
+
if (hasPendingRequiredWork())
|
|
901
905
|
reasons.add("live_required_work");
|
|
902
906
|
// R6b: a stale snapshot, or a snapshot-only task nothing ever explained.
|
|
903
907
|
const snap = liveSnapshotIds();
|
|
@@ -993,6 +997,7 @@ export function createRequiredWorkLedger(init) {
|
|
|
993
997
|
recordDeclarationRefused: () => {
|
|
994
998
|
insufficient.add("declaration_refused");
|
|
995
999
|
},
|
|
1000
|
+
observeLiveRequiredWork,
|
|
996
1001
|
buildEnvelope,
|
|
997
1002
|
};
|
|
998
1003
|
}
|
|
@@ -149,6 +149,8 @@ export function fillResumePromptPlaceholders(template, gitLog, gitDiff) {
|
|
|
149
149
|
// ---------------------------------------------------------------------------
|
|
150
150
|
/** The deterministic WIP checkpoint message for a continuation. */
|
|
151
151
|
export const CONTINUATION_WIP_COMMIT_MESSAGE = "wip: auto-checkpoint before continuation";
|
|
152
|
+
/** BAPI-1367: the WIP checkpoint message when a retry resumes its prior attempt's work. */
|
|
153
|
+
export const PRIOR_ATTEMPT_WIP_COMMIT_MESSAGE = "wip: auto-checkpoint before retry resume";
|
|
152
154
|
const FULL_SHA = /^[0-9a-f]{40}$/;
|
|
153
155
|
/** Read the continuation binding from a claimed job's payload (pure). */
|
|
154
156
|
export function readContinuationBinding(job) {
|
package/build/executor/runner.js
CHANGED
|
@@ -70,7 +70,7 @@ export function formatDispatcherUnavailableDiagnostic(liveness) {
|
|
|
70
70
|
return (`executor: no fresh reconciler/dispatcher heartbeat is available ` +
|
|
71
71
|
`(reconciler_liveness=${liveness}); the dispatcher may not be running. ` +
|
|
72
72
|
"Check DISABLE_SCHEDULER and GET /automation/health; see " +
|
|
73
|
-
"docs/
|
|
73
|
+
"docs/epic-implementer/operator-runbook.md. Continuing to poll.");
|
|
74
74
|
}
|
|
75
75
|
/** The recovery diagnostic, emitted once when an absent dispatcher comes back. */
|
|
76
76
|
export function formatDispatcherRecoveredDiagnostic() {
|
|
@@ -94,7 +94,8 @@ export const PROCESS_HEARTBEAT_INTERVAL_MS = 60_000;
|
|
|
94
94
|
/** Delivery-failure diagnostic. Bounded, credential-free, stderr-only. */
|
|
95
95
|
export function formatProcessHeartbeatFailureDiagnostic() {
|
|
96
96
|
return ("executor: process-heartbeat delivery is failing; the server may report this " +
|
|
97
|
-
"executor as stale.
|
|
97
|
+
"executor as stale. If the server is unreachable, job heartbeats and claims " +
|
|
98
|
+
"can fail too, and a running job can lose its lease.");
|
|
98
99
|
}
|
|
99
100
|
/** Emitted once when delivery starts working again. */
|
|
100
101
|
export function formatProcessHeartbeatRecoveredDiagnostic() {
|
|
@@ -151,6 +152,8 @@ export async function runExecutor(options, deps, httpClient, seams = {}) {
|
|
|
151
152
|
// optional mtime boundary (keeps GC conservative → retains).
|
|
152
153
|
statMtimeMs: deps.statMtimeMs ?? (async () => null),
|
|
153
154
|
platform: deps.platform,
|
|
155
|
+
// BAPI-1330: finds the per-attempt worker logs for the activity probe.
|
|
156
|
+
...(deps.readdir ? { listDir: deps.readdir } : {}),
|
|
154
157
|
}));
|
|
155
158
|
// BAPI-1301 — before the first claim, inspect ownership records left by a
|
|
156
159
|
// previous executor process on this host. Each record holds the real per-job
|
|
@@ -557,6 +560,7 @@ export async function runExecutor(options, deps, httpClient, seams = {}) {
|
|
|
557
560
|
* a failure is reported to stderr and the loop keeps going. It cannot fail a
|
|
558
561
|
* claim, fail a job, or end the run.
|
|
559
562
|
*/
|
|
563
|
+
let latestCorrectionQualification = null;
|
|
560
564
|
const runProcessHeartbeatLoop = async () => {
|
|
561
565
|
const publish = httpClient.processHeartbeat;
|
|
562
566
|
// A client without the capability (every object-literal test fake) simply
|
|
@@ -589,10 +593,20 @@ export async function runExecutor(options, deps, httpClient, seams = {}) {
|
|
|
589
593
|
? { claim_mode: "scoped", epic_run_id: observedEpicRunId }
|
|
590
594
|
: { claim_mode: "scoped" },
|
|
591
595
|
};
|
|
596
|
+
const baseMetadata = request.metadata;
|
|
592
597
|
// Suppression state: one message per CHANGE of delivery state, so a long
|
|
593
598
|
// outage produces one line rather than one per minute.
|
|
594
599
|
let lastFailed = false;
|
|
595
600
|
for (;;) {
|
|
601
|
+
request.metadata = {
|
|
602
|
+
...baseMetadata,
|
|
603
|
+
...(latestCorrectionQualification
|
|
604
|
+
? { correction_intent: latestCorrectionQualification }
|
|
605
|
+
: {}),
|
|
606
|
+
...(latestCorrectionQualification?.qualified === true
|
|
607
|
+
? { ci_evidence: { version: 1, qualified: true } }
|
|
608
|
+
: {}),
|
|
609
|
+
};
|
|
596
610
|
let outcome;
|
|
597
611
|
try {
|
|
598
612
|
outcome = await publish.call(httpClient, request);
|
|
@@ -781,6 +795,16 @@ export async function runExecutor(options, deps, httpClient, seams = {}) {
|
|
|
781
795
|
try {
|
|
782
796
|
for (;;) {
|
|
783
797
|
const report = await collectPreflight(options, deps, preflightSeams);
|
|
798
|
+
latestCorrectionQualification = report.correctionIntentQualified === true &&
|
|
799
|
+
report.correctionQualificationEvidence?.hook_installed === true &&
|
|
800
|
+
report.correctionQualificationEvidence.nested_denial === true
|
|
801
|
+
? {
|
|
802
|
+
version: 1, qualified: true, hook_installed: true,
|
|
803
|
+
nested_denial: true,
|
|
804
|
+
fixture_id: report.correctionQualificationEvidence.fixture_id,
|
|
805
|
+
runtime: report.correctionQualificationEvidence.runtime,
|
|
806
|
+
}
|
|
807
|
+
: null;
|
|
784
808
|
// First heartbeat goes out immediately once preflight passes, so a freshly
|
|
785
809
|
// started executor is visible to the server without waiting one interval.
|
|
786
810
|
// Gated on `report.ok` because an executor that preflight refuses is not
|
|
@@ -796,6 +820,11 @@ export async function runExecutor(options, deps, httpClient, seams = {}) {
|
|
|
796
820
|
for (const warning of report.warnings) {
|
|
797
821
|
deps.errorLog(`executor preflight warning: ${warning}`);
|
|
798
822
|
}
|
|
823
|
+
// BAPI-1386: informational lines (the completion-evidence advertisement) go to
|
|
824
|
+
// stderr like every other diagnostic here, never stdout.
|
|
825
|
+
for (const notice of report.notices ?? []) {
|
|
826
|
+
deps.errorLog(`executor preflight: ${notice}`);
|
|
827
|
+
}
|
|
799
828
|
if (!report.ok) {
|
|
800
829
|
deps.errorLog(`executor preflight refused claiming: ${report.fatalFindings.join("; ")}`);
|
|
801
830
|
}
|
|
@@ -929,7 +929,8 @@ export function resolvePackagedExecutorInvocation(deps) {
|
|
|
929
929
|
/** User-facing usage for `executor install-service`. */
|
|
930
930
|
export function getExecutorInstallServiceUsage() {
|
|
931
931
|
return [
|
|
932
|
-
"Usage: mcp-server executor install-service --executor-id <id> --repo <name> [--repo <name> ...]
|
|
932
|
+
"Usage: mcp-server executor install-service --executor-id <id> --repo <name> [--repo <name> ...]",
|
|
933
|
+
" (--epic-run-id <id> | --repo-wide) [options]",
|
|
933
934
|
"",
|
|
934
935
|
"The FILE-GENERATION LEG of durable-host mode. It writes one unit and stops there.",
|
|
935
936
|
"For the supported composed bring-up — planning, consented write, consented start,",
|
|
@@ -947,6 +948,11 @@ export function getExecutorInstallServiceUsage() {
|
|
|
947
948
|
" needs a durable identity, so the foreground",
|
|
948
949
|
" <hostname>-<pid> default is never used here.",
|
|
949
950
|
" --repo <name> Repo to serve (repeatable; --repos=a,b also works).",
|
|
951
|
+
" --epic-run-id <id> Claim scope: dedicate the unit to an epic run",
|
|
952
|
+
" (repeatable). Or:",
|
|
953
|
+
" --repo-wide Claim scope: claim from every authorized repo,",
|
|
954
|
+
" unscoped by run. Exactly one of the two is required;",
|
|
955
|
+
" the executor refuses to start without one.",
|
|
950
956
|
" --base-url <url> Bridge API endpoint, or set BAPI_BASE_URL.",
|
|
951
957
|
" There is NO production default.",
|
|
952
958
|
"",
|
|
@@ -25,10 +25,11 @@ export const CONDUCTOR_CRITIQUE_FILE = "critique.md";
|
|
|
25
25
|
/**
|
|
26
26
|
* The files a prior attempt may leave behind, and the ONLY ones removed here.
|
|
27
27
|
*
|
|
28
|
-
* `worker.lock` and
|
|
29
|
-
* coordination state, and
|
|
30
|
-
*
|
|
31
|
-
* file inside it are left
|
|
28
|
+
* `worker.lock` and the worker logs are deliberately absent: the lock is live
|
|
29
|
+
* coordination state, and each attempt's log is forensic evidence that its owner
|
|
30
|
+
* (`worker-log.ts`) creates exclusively and never truncates (BAPI-1330). The
|
|
31
|
+
* `.conductor/` directory itself and any unrelated file inside it are left
|
|
32
|
+
* untouched.
|
|
32
33
|
*/
|
|
33
34
|
export const STALE_ATTEMPT_ARTIFACT_FILES = [
|
|
34
35
|
CONDUCTOR_RESULT_FILE,
|
package/build/executor/types.js
CHANGED
|
@@ -52,6 +52,7 @@ export const EXECUTOR_CLASSIFICATION_VALUES = [
|
|
|
52
52
|
* evidence: `rebase` is not a covered job type.
|
|
53
53
|
*/
|
|
54
54
|
export const FOCUSED_REBASE_CAPABILITY = "focused_rebase.v1";
|
|
55
|
+
export const SCOPE_WAIT_CAPABILITY = "scope_wait.v1";
|
|
55
56
|
/** The focused context version; the same string as the capability on purpose. */
|
|
56
57
|
export const FOCUSED_REBASE_CONTEXT_VERSION = FOCUSED_REBASE_CAPABILITY;
|
|
57
58
|
/**
|