@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
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* inside `build/pipelines.generated.js`. `mcp_server/src/` does not exist there,
|
|
7
7
|
* so an instruction that told the agent to "call `validateConductorBundleInputs`"
|
|
8
8
|
* would be referencing a path only this repository has. This subcommand is the
|
|
9
|
-
* reachable surface,
|
|
10
|
-
*
|
|
9
|
+
* reachable surface, the same way `setup-epic` is a command rather than
|
|
10
|
+
* hand-rolled HTTP calls.
|
|
11
11
|
*
|
|
12
12
|
* It holds no policy of its own: it parses argv, delegates to
|
|
13
13
|
* `conductor-bundle-artifacts.ts`, and prints a JSON envelope. All I/O is behind
|
|
@@ -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 {
|
package/build/executor/cli.js
CHANGED
|
@@ -37,9 +37,10 @@ const DEFAULT_JOB_TIMEOUT_SECONDS = 20 * 60;
|
|
|
37
37
|
export const DEFAULT_MAX_CONCURRENT = 1;
|
|
38
38
|
export function getExecutorUsage() {
|
|
39
39
|
return [
|
|
40
|
-
"Usage: mcp-server executor --repo <name> [--repo <name> ...]
|
|
40
|
+
"Usage: mcp-server executor --repo <name> [--repo <name> ...]",
|
|
41
|
+
" (--epic-run-id <id> | --repo-wide) [options]",
|
|
41
42
|
"",
|
|
42
|
-
"Runs the Epic
|
|
43
|
+
"Runs the Epic Implementer local executor: poll → claim → spawn → heartbeat.",
|
|
43
44
|
"",
|
|
44
45
|
"Options:",
|
|
45
46
|
" --repo <name> Repo to serve (repeatable).",
|
|
@@ -53,6 +53,37 @@ export const SUPPORTED_COMPLETION_RUNTIME = {
|
|
|
53
53
|
platform: "darwin-arm64",
|
|
54
54
|
event_schema: "claude-stream-json.lifecycle.v1",
|
|
55
55
|
};
|
|
56
|
+
/** Oldest Claude CLI version whose lifecycle events were measured. */
|
|
57
|
+
export const MIN_CLI_VERSION = "2.1.282";
|
|
58
|
+
/** The four platform ids `<process.platform>-<process.arch>` the executor can produce. */
|
|
59
|
+
export const SUPPORTED_PLATFORMS = ["darwin-arm64", "darwin-x64", "linux-x64", "linux-arm64"];
|
|
60
|
+
/** Names the lifecycle parser version. A constant, never a probed value. */
|
|
61
|
+
export const EVENT_SCHEMA = "claude-stream-json.lifecycle.v1";
|
|
62
|
+
const CLI_VERSION_RE = /^(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,3})\.(0|[1-9][0-9]{0,5})(?: \(Claude Code\))?$/;
|
|
63
|
+
/**
|
|
64
|
+
* Parse `major.minor.patch` with an optional exact ` (Claude Code)` suffix into its
|
|
65
|
+
* numeric triple. Prereleases, whitespace, non-numeric segments, leading zeros, and
|
|
66
|
+
* non-strings are `null`. Mirror of `normalize_cli_version` in the Python contract.
|
|
67
|
+
*/
|
|
68
|
+
export function parseCliVersion(value) {
|
|
69
|
+
if (typeof value !== "string")
|
|
70
|
+
return null;
|
|
71
|
+
const match = CLI_VERSION_RE.exec(value);
|
|
72
|
+
return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
|
|
73
|
+
}
|
|
74
|
+
/** The normalized `major.minor.patch` text, or `null` when the version is not strict. */
|
|
75
|
+
export function normalizeCliVersion(value) {
|
|
76
|
+
const parsed = parseCliVersion(value);
|
|
77
|
+
return parsed ? parsed.join(".") : null;
|
|
78
|
+
}
|
|
79
|
+
/** Is version triple `a` at or above `b`? */
|
|
80
|
+
export function cliVersionAtLeast(a, b) {
|
|
81
|
+
for (let i = 0; i < 3; i += 1) {
|
|
82
|
+
if (a[i] !== b[i])
|
|
83
|
+
return a[i] > b[i];
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
56
87
|
export const EVIDENCE_VALUES = ["sufficient", "insufficient"];
|
|
57
88
|
export const INSUFFICIENT_REASON_VALUES = [
|
|
58
89
|
"no_init_event",
|
|
@@ -239,13 +270,23 @@ export function isCoveredJobType(jobType) {
|
|
|
239
270
|
export function claimRequiresCompletionEvidence(requiredCapabilities) {
|
|
240
271
|
return Array.isArray(requiredCapabilities) && requiredCapabilities.includes(COMPLETION_EVIDENCE_CAPABILITY);
|
|
241
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* True for the measured baseline or a newer supported runtime: adapter `claude`, the
|
|
275
|
+
* lifecycle event schema, a closed platform id, and a strict numeric version at or
|
|
276
|
+
* above {@link MIN_CLI_VERSION}. Mirror of `runtime_matches_supported` in Python.
|
|
277
|
+
*/
|
|
242
278
|
export function runtimeMatchesSupported(runtime) {
|
|
243
279
|
if (!runtime)
|
|
244
280
|
return false;
|
|
281
|
+
const version = parseCliVersion(runtime.cli_version);
|
|
282
|
+
const minimum = parseCliVersion(MIN_CLI_VERSION);
|
|
245
283
|
return (runtime.adapter === SUPPORTED_COMPLETION_RUNTIME.adapter &&
|
|
246
|
-
runtime.
|
|
247
|
-
runtime.platform ===
|
|
248
|
-
runtime.
|
|
284
|
+
runtime.event_schema === EVENT_SCHEMA &&
|
|
285
|
+
typeof runtime.platform === "string" &&
|
|
286
|
+
SUPPORTED_PLATFORMS.includes(runtime.platform) &&
|
|
287
|
+
version !== null &&
|
|
288
|
+
minimum !== null &&
|
|
289
|
+
cliVersionAtLeast(version, minimum));
|
|
249
290
|
}
|
|
250
291
|
export function unresolvedRequiredTasks(envelope) {
|
|
251
292
|
return envelope.tasks.filter((t) => t.required && !TASK_RESOLVING_STATES.has(t.state));
|
|
@@ -366,7 +407,9 @@ export function buildNotLaunchedEnvelope(input) {
|
|
|
366
407
|
binding: { ...input.binding },
|
|
367
408
|
session_id: null,
|
|
368
409
|
evidence: "insufficient",
|
|
369
|
-
insufficient_reasons:
|
|
410
|
+
insufficient_reasons: input.unsupportedRuntime
|
|
411
|
+
? ["worker_not_launched", "unsupported_runtime"]
|
|
412
|
+
: ["worker_not_launched"],
|
|
370
413
|
execution: "unknown",
|
|
371
414
|
verification: input.verification,
|
|
372
415
|
finalization: "unknown",
|
|
@@ -381,7 +424,7 @@ export function buildNotLaunchedEnvelope(input) {
|
|
|
381
424
|
denials: [],
|
|
382
425
|
missing_capabilities: [],
|
|
383
426
|
results_seen: 0,
|
|
384
|
-
cause: null,
|
|
427
|
+
cause: input.unsupportedRuntime ? "unsupported_completion_evidence" : null,
|
|
385
428
|
...(input.verificationFacts ? { verification_facts: input.verificationFacts } : {}),
|
|
386
429
|
};
|
|
387
430
|
}
|
|
@@ -223,6 +223,15 @@ 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 };
|
|
231
|
+
// BAPI-1357: the origin lookup could not be trusted, so the branch was not
|
|
232
|
+
// observed missing either. Unknown, and neither obligation is inferred met.
|
|
233
|
+
case "WorkerFinalizationOriginUnverifiable":
|
|
234
|
+
return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
|
|
226
235
|
default:
|
|
227
236
|
return { verdict: "unknown", pushSatisfied: false, prSatisfied: false };
|
|
228
237
|
}
|
|
@@ -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;
|
|
@@ -323,3 +329,22 @@ export function buildExecutorBaseWorkerEnv(parentEnv, options = {}) {
|
|
|
323
329
|
// per call site.
|
|
324
330
|
return env;
|
|
325
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* The environment for a headless agent run that is not a worker job, such as the
|
|
334
|
+
* executor's deny-enforcement probe (BAPI-1386).
|
|
335
|
+
*
|
|
336
|
+
* The probe must see what a worker sees. Spawned from an executor that is itself
|
|
337
|
+
* running inside a Claude session, it would otherwise inherit that session's
|
|
338
|
+
* nested-session variables, and the CLI can refuse to start a tool call there. So
|
|
339
|
+
* this reuses the worker allowlist and worker-injected defaults behind
|
|
340
|
+
* {@link buildExecutorBaseWorkerEnv} rather than keeping a deny-list of Claude
|
|
341
|
+
* variable names, which would go stale with every CLI release. No job-specific
|
|
342
|
+
* option is set: the result carries no branch, base, scope, or correction marker.
|
|
343
|
+
*
|
|
344
|
+
* Applied only where the executor builds the deny probe's context. The shared
|
|
345
|
+
* `runHeadless()` passes its supplied environment through unchanged, so standalone
|
|
346
|
+
* capability probes are not filtered.
|
|
347
|
+
*/
|
|
348
|
+
export function buildHeadlessAgentEnv(parentEnv) {
|
|
349
|
+
return buildExecutorBaseWorkerEnv(parentEnv);
|
|
350
|
+
}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* interval. This is TELEMETRY ONLY (R14): the marker is never persisted, never
|
|
23
23
|
* reported as `last_commit_sha`, and never drives an executor decision — the
|
|
24
24
|
* reconciler remains the sole consumer and the sole transition authority
|
|
25
|
-
* (`docs/
|
|
25
|
+
* (`docs/epic-implementer/standing-rules.md`).
|
|
26
26
|
*
|
|
27
27
|
* - SERVER STOP (WS-B executor half). A heartbeat response carrying the
|
|
28
28
|
* server's stop request kills the owned worker like `stale_claim` does, but
|
|
@@ -76,6 +76,22 @@ export function normalizeRequiredCapabilities(value) {
|
|
|
76
76
|
return [];
|
|
77
77
|
return value.every((entry) => typeof entry === "string") ? [...value] : [];
|
|
78
78
|
}
|
|
79
|
+
const FULL_SHA_PATTERN = /^[0-9a-fA-F]{40}$/;
|
|
80
|
+
/**
|
|
81
|
+
* BAPI-1367 — normalize a claim response's `prior_attempt_head_sha`. Absent or
|
|
82
|
+
* explicit null reads as null silently (an older server, or no prior head). A
|
|
83
|
+
* present value that is not a 40-hex string also reads as null, and `onMalformed`
|
|
84
|
+
* is told once so the caller can warn. The raw value is never passed on, so it
|
|
85
|
+
* can never reach a log line.
|
|
86
|
+
*/
|
|
87
|
+
export function normalizePriorAttemptHeadSha(value, onMalformed) {
|
|
88
|
+
if (value === undefined || value === null)
|
|
89
|
+
return null;
|
|
90
|
+
if (typeof value === "string" && FULL_SHA_PATTERN.test(value))
|
|
91
|
+
return value;
|
|
92
|
+
onMalformed();
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
79
95
|
/** Bound a server-provided diagnostic snippet included in error strings. */
|
|
80
96
|
function boundedDetail(text) {
|
|
81
97
|
const trimmed = (text ?? "").trim();
|
|
@@ -271,6 +287,10 @@ export function createExecutorHttpClient(config) {
|
|
|
271
287
|
if ("required_capabilities" in job) {
|
|
272
288
|
job.required_capabilities = normalizeRequiredCapabilities(job.required_capabilities);
|
|
273
289
|
}
|
|
290
|
+
// BAPI-1367: a malformed prior head fails closed to null with one
|
|
291
|
+
// bounded warning naming only the job, never the raw value or a SHA.
|
|
292
|
+
job.prior_attempt_head_sha = normalizePriorAttemptHeadSha(job.prior_attempt_head_sha, () => (config.warn ?? ((message) => console.error(message)))(`executor: ignoring malformed prior attempt head in the claim for job ${String(job.id)}; ` +
|
|
293
|
+
`treating it as a first dispatch.`));
|
|
274
294
|
return { kind: "claimed", job, reconcilerLiveness };
|
|
275
295
|
}
|
|
276
296
|
catch {
|
|
@@ -294,6 +314,18 @@ export function createExecutorHttpClient(config) {
|
|
|
294
314
|
// 5xx and any other unexpected status are retryable.
|
|
295
315
|
return { kind: "retryable", error: `claim failed (HTTP ${status}): ${boundedDetail(text)}` };
|
|
296
316
|
},
|
|
317
|
+
async scopeReadiness(job) {
|
|
318
|
+
const { status, text } = await post(`/${job.id}/scope-readiness`, { repo_name: job.repo_name, claim_token: job.claim_token }, job.repo_name);
|
|
319
|
+
if (status !== 200)
|
|
320
|
+
throw new Error(`scope readiness HTTP ${status}`);
|
|
321
|
+
const parsed = JSON.parse(text);
|
|
322
|
+
if (!parsed || typeof parsed !== "object" ||
|
|
323
|
+
!("decision" in parsed) ||
|
|
324
|
+
!["ready", "waiting", "blocked"].includes(String(parsed.decision))) {
|
|
325
|
+
throw new Error("scope readiness response malformed");
|
|
326
|
+
}
|
|
327
|
+
return parsed;
|
|
328
|
+
},
|
|
297
329
|
// Heartbeat does NOT go through `mutate()`: it is the one mutation whose
|
|
298
330
|
// response BODY carries information beyond the outcome (the BAPI-731 stop
|
|
299
331
|
// request), so it maps the response itself instead of discarding the body.
|
|
@@ -371,6 +403,9 @@ export function createExecutorHttpClient(config) {
|
|
|
371
403
|
error_kind: failure.error_kind,
|
|
372
404
|
error_message: failure.error_message,
|
|
373
405
|
classification: failure.classification,
|
|
406
|
+
// BAPI-1330: the observed process exit, only when the runner has one —
|
|
407
|
+
// a failure without it keeps its exact pre-BAPI-1330 body.
|
|
408
|
+
...(failure.exit_code !== undefined ? { exit_code: failure.exit_code } : {}),
|
|
374
409
|
local_commit_count: failure.local_commit_count,
|
|
375
410
|
last_commit_sha: failure.last_commit_sha,
|
|
376
411
|
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
|
|
@@ -40,6 +41,11 @@
|
|
|
40
41
|
* work reached origin") because this case has positive proof of recoverable
|
|
41
42
|
* state: an operator can resume/finalize from the existing branch rather than
|
|
42
43
|
* re-running from scratch; see `worker-finalization.ts`.
|
|
44
|
+
* - `WorkerFinalizationOriginUnverifiable` — an implementation-style job
|
|
45
|
+
* (BAPI-1357) exited cleanly, but the origin branch lookup could not be
|
|
46
|
+
* trusted (a throw, an expired bound, an unexpected exit, or unusable output).
|
|
47
|
+
* The branch was NOT observed absent, so this is distinct from
|
|
48
|
+
* `WorkerFinalizationMissingRemoteBranchAndPr`; see `worker-finalization.ts`.
|
|
43
49
|
* - `ContractError.WorkerConfigIsolation` — a per-job isolated Claude config
|
|
44
50
|
* directory could not be established (unsupported platform, unsupported auth
|
|
45
51
|
* layout, unseedable directory, or a missing required MCP registration), so
|
|
@@ -132,6 +138,32 @@ export const WorkerFinalizationMissingRemoteBranchAndPr = "WorkerFinalizationMis
|
|
|
132
138
|
export const WorkerFinalizationPrBaseMismatch = "WorkerFinalizationPrBaseMismatch";
|
|
133
139
|
export const WorkerFinalizationPrNotAttached = "WorkerFinalizationPrNotAttached";
|
|
134
140
|
export const WorkerFinalizationSavedButUnfinalized = "WorkerFinalizationSavedButUnfinalized";
|
|
141
|
+
/**
|
|
142
|
+
* BAPI-1330: a COVERED attempt's publication could not be proven — the worker's
|
|
143
|
+
* full HEAD was unknown, or the pull request reported no head commit. Distinct
|
|
144
|
+
* from `WorkerFinalizationPrNotAttached` because nothing here was observed to be
|
|
145
|
+
* missing: the facts needed to decide were unavailable, so the finalization
|
|
146
|
+
* verdict is `unknown`, never `unmet` and never `ok`.
|
|
147
|
+
*/
|
|
148
|
+
export const WorkerFinalizationPublicationUnverified = "WorkerFinalizationPublicationUnverified";
|
|
149
|
+
/**
|
|
150
|
+
* BAPI-1357: the origin branch lookup could not be TRUSTED — the command threw,
|
|
151
|
+
* its call bound expired, it exited with anything other than "found" (0) or "no
|
|
152
|
+
* matching ref" (2), or it exited 0 without a usable SHA for the exact ref. The
|
|
153
|
+
* branch was not observed absent, so this is distinct from
|
|
154
|
+
* `WorkerFinalizationMissingRemoteBranchAndPr`, which is reserved for an
|
|
155
|
+
* observed absence. The process itself exited clean. Nothing command-derived
|
|
156
|
+
* (stderr, error text, host, credentials) is carried by this kind.
|
|
157
|
+
*/
|
|
158
|
+
export const WorkerFinalizationOriginUnverifiable = "WorkerFinalizationOriginUnverifiable";
|
|
159
|
+
/**
|
|
160
|
+
* BAPI-1364: a LEGACY (non-covered) worker exited cleanly while the structured
|
|
161
|
+
* stream still showed a required background command with no terminal
|
|
162
|
+
* notification. The headless session had no later turn, so the command's result
|
|
163
|
+
* could never reach the worker. Carries only {@link WORKER_ENDED_WITH_PENDING_BACKGROUND_WORK_MESSAGE};
|
|
164
|
+
* no event text, command, path, or output.
|
|
165
|
+
*/
|
|
166
|
+
export const WorkerEndedWithPendingBackgroundWork = "WorkerEndedWithPendingBackgroundWork";
|
|
135
167
|
export const WorkerConfigIsolation = "ContractError.WorkerConfigIsolation";
|
|
136
168
|
export const PreSpawnVerification = "ContractError.PreSpawnVerification";
|
|
137
169
|
export const BridgeCredentialUnavailable = "ContractError.BridgeCredentialUnavailable";
|
|
@@ -273,6 +305,8 @@ export function formatMcpSurfaceMismatch(expected, observed) {
|
|
|
273
305
|
"Check that the installed Claude CLI supports --strict-mcp-config and that nothing adds MCP " +
|
|
274
306
|
"servers outside the worktree registration.");
|
|
275
307
|
}
|
|
308
|
+
/** The fixed message posted with {@link WorkerEndedWithPendingBackgroundWork}. */
|
|
309
|
+
export const WORKER_ENDED_WITH_PENDING_BACKGROUND_WORK_MESSAGE = "the worker ended its session while a background command was still running";
|
|
276
310
|
/**
|
|
277
311
|
* The fixed message posted with {@link WorkerStartupFatal}.
|
|
278
312
|
*
|