@bridge_gpt/mcp-server 0.2.39 → 0.2.42
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 +10 -10
- package/build/agent-capabilities/cli.js +2 -1
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/claude-user-config-doctor.js +42 -11
- package/build/cli-release.js +2 -1
- package/build/commands.generated.js +4 -4
- package/build/conduct-epic/bridge-client.js +354 -113
- package/build/conduct-epic/checkpoint-store.js +75 -2
- package/build/conduct-epic/cli.js +795 -109
- package/build/conduct-epic/cut-protocol.js +327 -0
- package/build/conduct-epic/pr-state.js +113 -24
- package/build/conduct-epic/spawn.js +14 -2
- package/build/conductor/bridge-api-client.js +27 -1
- package/build/conductor/cli.js +46 -1
- package/build/conductor/doctor.js +101 -16
- package/build/conductor/epic-reconcile.js +72 -19
- package/build/conductor/epic-runtime.js +15 -3
- package/build/conductor/errors.js +47 -0
- package/build/conductor/git-hooks.js +205 -11
- package/build/conductor/install-doctor.js +230 -1
- package/build/conductor/local-merge.js +130 -28
- package/build/conductor/tools.js +32 -3
- package/build/conductor/worker-ledger-cli.js +27 -1
- package/build/conductor-bin.js +15 -15
- package/build/credentials-cli.js +3 -2
- package/build/doctor.js +107 -41
- package/build/executor/cli.js +48 -1
- package/build/executor/env.js +21 -0
- package/build/executor/index-scope.js +39 -0
- package/build/executor/job-log-registry.js +69 -0
- package/build/executor/job-runner.js +148 -26
- package/build/executor/live-worker-registry.js +83 -0
- package/build/executor/observation.js +167 -6
- package/build/executor/platform.js +147 -3
- package/build/executor/process.js +58 -14
- package/build/executor/runner.js +235 -48
- package/build/executor/test-clock.js +3 -2
- package/build/index-scope-contract.js +96 -0
- package/build/index.js +153 -204
- package/build/init.js +83 -22
- package/build/install-bridge-conductor.js +323 -14
- package/build/install-bridge.js +202 -38
- package/build/install-doctor.js +23 -9
- package/build/install-reexec.js +2 -1
- package/build/launcher-config-inspection.js +83 -22
- package/build/mcp-host-config.js +331 -67
- package/build/mcp-host-targets.js +45 -21
- package/build/mcp-identity.js +92 -0
- package/build/mcp-install-state.js +94 -1
- package/build/mcp-invoke.js +2 -1
- package/build/mcp-provisioning.js +45 -12
- package/build/mcp-registration-doctor.js +35 -13
- package/build/mcp-server-invocation.js +4 -2
- package/build/merge-pull-request.js +208 -9
- package/build/pipelines.generated.js +3 -3
- package/build/plane/defaults.js +4 -1
- package/build/plane/preflight.js +81 -10
- package/build/plane/test-fakes.js +9 -1
- package/build/readme.generated.js +1 -1
- package/build/regression-check.js +3 -2
- package/build/review-tickets.js +8 -7
- package/build/run-unit-tests-launcher.js +74 -1
- package/build/schedule-run.js +3 -2
- package/build/setup-epic.js +453 -78
- package/build/sfcc/tool-wrapper.js +15 -0
- package/build/start-tickets-prereqs.js +11 -6
- package/build/start-tickets.js +91 -85
- package/build/update-check.js +3 -2
- package/build/upgrade-advice.js +2 -1
- package/build/upgrade-cli.js +50 -18
- package/build/version.generated.js +1 -1
- package/docs/CONDUCTOR.md +22 -0
- package/docs/install/mcp-tool-integrations.md +19 -3
- package/package.json +2 -2
|
@@ -485,38 +485,42 @@ export function makeLocalMergeExecutor(options = {}, deps = {}) {
|
|
|
485
485
|
}
|
|
486
486
|
// 3. Revalidate required CI green for the EXACT expected head, waiting briefly
|
|
487
487
|
// for transient post-remediation CI to turn green before failing ci_not_green.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
488
|
+
// Skipped under `skipCiWait` (BAPI-832), where the server's approval tail
|
|
489
|
+
// revalidated these same checks for this same head moments ago.
|
|
490
|
+
if (!options.skipCiWait) {
|
|
491
|
+
const ciWait = await waitForRequiredChecksGreen(pollCi, access, expectedSha, requiredChecks, ciWaitTimeoutMs, ciWaitPollIntervalMs, sleep, now, signal);
|
|
492
|
+
if (!ciWait.ok) {
|
|
493
|
+
// Any `ci_poll_*` reason (the legacy `ci_poll_failed` plus the distinct
|
|
494
|
+
// BAPI-577 kinds) is an AMBIGUOUS infrastructure read failure — a merge that
|
|
495
|
+
// GitHub already accepted could be masked by a flaky poll. Re-verify the
|
|
496
|
+
// merged state before reporting. ci_not_green and merge_aborted are
|
|
497
|
+
// deterministic (CI is red / the job was aborted); they never verify and
|
|
498
|
+
// never proceed to a provider merge.
|
|
499
|
+
if (isAmbiguousCiPollFailureReason(ciWait.reason)) {
|
|
500
|
+
const verified = await verifyMergedStateAfterAmbiguousFailure();
|
|
501
|
+
if (verified)
|
|
502
|
+
return verified;
|
|
503
|
+
}
|
|
504
|
+
return fail(ciWait.reason, ciWait.pollFailure);
|
|
505
|
+
}
|
|
506
|
+
// 3b. Re-read PR head/state after the CI wait — the head can drift, the PR can
|
|
507
|
+
// close, or the merge can already have landed while we waited for CI.
|
|
508
|
+
const secondRead = await readPrMergeState(run, ghEnv, pr);
|
|
509
|
+
if (!secondRead.ok) {
|
|
497
510
|
const verified = await verifyMergedStateAfterAmbiguousFailure();
|
|
498
511
|
if (verified)
|
|
499
512
|
return verified;
|
|
513
|
+
return fail(secondRead.reason);
|
|
514
|
+
}
|
|
515
|
+
if (isMergedAtExpectedHead(secondRead.state, secondRead.headOid, expectedSha)) {
|
|
516
|
+
return buildAlreadyMergedResponse(request, baseDetails, secondRead.mergeCommitOid);
|
|
517
|
+
}
|
|
518
|
+
if (typeof secondRead.headOid !== "string" || secondRead.headOid.toLowerCase() !== expectedSha.toLowerCase()) {
|
|
519
|
+
return fail("head_drift");
|
|
520
|
+
}
|
|
521
|
+
if (typeof secondRead.state === "string" && secondRead.state.toUpperCase() !== "OPEN") {
|
|
522
|
+
return fail("pr_not_open");
|
|
500
523
|
}
|
|
501
|
-
return fail(ciWait.reason, ciWait.pollFailure);
|
|
502
|
-
}
|
|
503
|
-
// 3b. Re-read PR head/state after the CI wait — the head can drift, the PR can
|
|
504
|
-
// close, or the merge can already have landed while we waited for CI.
|
|
505
|
-
const secondRead = await readPrMergeState(run, ghEnv, pr);
|
|
506
|
-
if (!secondRead.ok) {
|
|
507
|
-
const verified = await verifyMergedStateAfterAmbiguousFailure();
|
|
508
|
-
if (verified)
|
|
509
|
-
return verified;
|
|
510
|
-
return fail(secondRead.reason);
|
|
511
|
-
}
|
|
512
|
-
if (isMergedAtExpectedHead(secondRead.state, secondRead.headOid, expectedSha)) {
|
|
513
|
-
return buildAlreadyMergedResponse(request, baseDetails, secondRead.mergeCommitOid);
|
|
514
|
-
}
|
|
515
|
-
if (typeof secondRead.headOid !== "string" || secondRead.headOid.toLowerCase() !== expectedSha.toLowerCase()) {
|
|
516
|
-
return fail("head_drift");
|
|
517
|
-
}
|
|
518
|
-
if (typeof secondRead.state === "string" && secondRead.state.toUpperCase() !== "OPEN") {
|
|
519
|
-
return fail("pr_not_open");
|
|
520
524
|
}
|
|
521
525
|
// 4. Provider merge. Final abort guard: if the overall timeout / ownership
|
|
522
526
|
// abandonment fired during the CI wait or the post-CI re-read, do NOT fire
|
|
@@ -619,3 +623,101 @@ function buildConflictResponse(request, baseDetails, expectedSha, mergeability)
|
|
|
619
623
|
{ type: "merge.conflict", status: "failed", reason, details: conflictDetails },
|
|
620
624
|
]);
|
|
621
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Probe whether this host can merge with `gh` at all, BEFORE any merge is
|
|
628
|
+
* attempted.
|
|
629
|
+
*
|
|
630
|
+
* Without this the two operator-fixable failures are invisible: a missing `gh`
|
|
631
|
+
* and a logged-out `gh` both make `gh pr view` exit non-zero, which this module
|
|
632
|
+
* reports as `gh_pr_view_failed` — a reason that names no fix. Splitting them
|
|
633
|
+
* out is the difference between "run `gh auth login`" and "something went wrong
|
|
634
|
+
* reading the pull request".
|
|
635
|
+
*
|
|
636
|
+
* Deliberately async and built on the same `run` seam as the merge itself. The
|
|
637
|
+
* read-only equivalent in `conductor/doctor.ts` (`inspectLocalMerge`) probes
|
|
638
|
+
* synchronously, which is fine for a diagnostic command but would block the MCP
|
|
639
|
+
* server's event loop for up to its full timeout in the middle of a tool call.
|
|
640
|
+
* (This module's static contract forbids synchronous process execution outright,
|
|
641
|
+
* so the token itself cannot appear here — not even in a comment.)
|
|
642
|
+
*/
|
|
643
|
+
export async function preflightLocalGh(run, ghEnv) {
|
|
644
|
+
let version;
|
|
645
|
+
try {
|
|
646
|
+
version = await run("gh", ["--version"], ghEnv);
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
return { ok: false, reason: "local_gh_unavailable" };
|
|
650
|
+
}
|
|
651
|
+
if (version.status !== 0)
|
|
652
|
+
return { ok: false, reason: "local_gh_unavailable" };
|
|
653
|
+
let auth;
|
|
654
|
+
try {
|
|
655
|
+
auth = await run("gh", ["auth", "status"], ghEnv);
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
return { ok: false, reason: "local_gh_unauthenticated" };
|
|
659
|
+
}
|
|
660
|
+
if (auth.status !== 0)
|
|
661
|
+
return { ok: false, reason: "local_gh_unauthenticated" };
|
|
662
|
+
return { ok: true };
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Execute a merge the server has ALREADY approved, using the operator's `gh`.
|
|
666
|
+
*
|
|
667
|
+
* This is the local half of BAPI-832's split: the server ran the whole
|
|
668
|
+
* deterministic decision tail (action key, lease, head-SHA drift guard, review
|
|
669
|
+
* gate, workflow-file guard, CI revalidation) and returned
|
|
670
|
+
* `approved_for_local_execution` because its GitHub App installation is
|
|
671
|
+
* `contents: read` and cannot perform the write. Only the write happens here.
|
|
672
|
+
*
|
|
673
|
+
* It reuses {@link makeLocalMergeExecutor} rather than reimplementing the merge,
|
|
674
|
+
* so the head-drift guard, the `--match-head-commit` binding, and the entire
|
|
675
|
+
* ambiguity-resolution ladder are the same proven code v2's `merge` job runs.
|
|
676
|
+
* `skipCiWait` is set because the server revalidated CI moments ago.
|
|
677
|
+
*
|
|
678
|
+
* This function grants no authority of its own. It must only ever be called with
|
|
679
|
+
* a request built from a live approval for that exact PR and head SHA.
|
|
680
|
+
*/
|
|
681
|
+
export async function runApprovedLocalMerge(request, options = {}, deps = {}) {
|
|
682
|
+
const ghEnv = {
|
|
683
|
+
...deps.env,
|
|
684
|
+
GH_PROMPT_DISABLED: "1",
|
|
685
|
+
GH_NO_UPDATE_NOTIFIER: "1",
|
|
686
|
+
};
|
|
687
|
+
const rawRun = deps.runCommand ?? defaultRunCommand;
|
|
688
|
+
const run = (cmd, args, env) => Promise.resolve(rawRun(cmd, args, env, deps.signal));
|
|
689
|
+
const preflight = await preflightLocalGh(run, ghEnv);
|
|
690
|
+
if (!preflight.ok) {
|
|
691
|
+
return buildResponse(request, "failed", preflight.reason, false, [
|
|
692
|
+
{
|
|
693
|
+
type: "merge.failed",
|
|
694
|
+
status: "failed",
|
|
695
|
+
reason: preflight.reason,
|
|
696
|
+
details: {
|
|
697
|
+
action_key: request.action_key,
|
|
698
|
+
repo: request.repo_name,
|
|
699
|
+
pr_number: request.pr_number,
|
|
700
|
+
expected_head_sha: request.expected_head_sha,
|
|
701
|
+
executor: "local",
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
]);
|
|
705
|
+
}
|
|
706
|
+
const executor = makeLocalMergeExecutor({ method: options.method, skipCiWait: true }, deps);
|
|
707
|
+
// `access` is only ever used by the CI poll, which `skipCiWait` bypasses. The
|
|
708
|
+
// throwing stub makes that structural, not a comment: if a future edit ever
|
|
709
|
+
// reaches the poll from this path, it fails loudly in tests instead of
|
|
710
|
+
// silently polling with an unusable access record.
|
|
711
|
+
const unusedAccess = {
|
|
712
|
+
get baseUrl() {
|
|
713
|
+
throw new Error("runApprovedLocalMerge must not poll CI");
|
|
714
|
+
},
|
|
715
|
+
get apiKey() {
|
|
716
|
+
throw new Error("runApprovedLocalMerge must not poll CI");
|
|
717
|
+
},
|
|
718
|
+
get repoName() {
|
|
719
|
+
throw new Error("runApprovedLocalMerge must not poll CI");
|
|
720
|
+
},
|
|
721
|
+
};
|
|
722
|
+
return executor(unusedAccess, request);
|
|
723
|
+
}
|
package/build/conductor/tools.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { SEMANTIC_EVENT_TYPES } from "./taxonomy.js";
|
|
13
|
-
import { ConductorValidationError, toConductorErrorEnvelope } from "./errors.js";
|
|
13
|
+
import { ConductorValidationError, ConductorWorkerContextRequiredError, toConductorErrorEnvelope, } from "./errors.js";
|
|
14
14
|
import { emitConductorEvent, pollConductorEvents, waitForConductorEvent, getSupervisorSnapshot, sendWorkerMessage, } from "./store.js";
|
|
15
15
|
import { normalizePrNumber, normalizeSha } from "./git-ci-types.js";
|
|
16
16
|
import { waitForDoneGate, resolveDispatchRunIdForBinding } from "./pr-ci-producer.js";
|
|
@@ -266,6 +266,15 @@ function registerWaitForDoneGateTool(registerTool) {
|
|
|
266
266
|
// deterministic id are derived purely and the id is forwarded to the CLI,
|
|
267
267
|
// so dedup happens server-side on the events.id UNIQUE constraint — the
|
|
268
268
|
// worker path performs NO in-process ledger read or write.
|
|
269
|
+
//
|
|
270
|
+
// BAPI-772: this emit leg inherits the runtime contract from
|
|
271
|
+
// `resolveWorkerLedgerCliRuntime`, which is reached only when an emit is
|
|
272
|
+
// actually attempted — read-only gate work (binding resolution, CI
|
|
273
|
+
// polling) is never preemptively rejected. In a plain session with no
|
|
274
|
+
// worker env at all, that resolver raises WORKER_CONTEXT_REQUIRED, so the
|
|
275
|
+
// gate explains itself instead of surfacing an opaque 503; a partially
|
|
276
|
+
// configured or corrupted worker runtime still fails loud with
|
|
277
|
+
// LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE. Nothing falls back in-process.
|
|
269
278
|
emitIfNew: (input, dimensions) => emitConductorEventIfNewViaCli(input, dimensions),
|
|
270
279
|
});
|
|
271
280
|
return jsonResult({
|
|
@@ -351,8 +360,28 @@ function registerCheckMessagesTool(registerTool) {
|
|
|
351
360
|
limit: z.number().int().positive().max(100).optional().describe("Max messages to deliver/ack (default 10, max 100)."),
|
|
352
361
|
},
|
|
353
362
|
}, withConductorToolErrorHandling(async (args) => {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
363
|
+
const runIdArg = args.run_id;
|
|
364
|
+
const workerIdArg = args.worker_id;
|
|
365
|
+
const runIdEnv = process.env.BAPI_CONDUCTOR_RUN_ID;
|
|
366
|
+
const workerIdEnv = process.env.BAPI_CONDUCTOR_WORKER_ID;
|
|
367
|
+
// BAPI-772: a call with NEITHER argument supplied and NEITHER env var set is
|
|
368
|
+
// a plain MCP session — nothing is broken, the tool is simply worker-scoped.
|
|
369
|
+
// Return the typed guidance envelope instead of an opaque validation error.
|
|
370
|
+
// Any other shape (an explicit blank id, one id present and the other not, a
|
|
371
|
+
// partially-set worker env) is still malformed identity and keeps the
|
|
372
|
+
// existing fail-loud VALIDATION_ERROR: a broken worker context must never be
|
|
373
|
+
// relabeled as an ordinary session.
|
|
374
|
+
if (runIdArg === undefined &&
|
|
375
|
+
workerIdArg === undefined &&
|
|
376
|
+
runIdEnv === undefined &&
|
|
377
|
+
workerIdEnv === undefined) {
|
|
378
|
+
throw new ConductorWorkerContextRequiredError([
|
|
379
|
+
"BAPI_CONDUCTOR_RUN_ID",
|
|
380
|
+
"BAPI_CONDUCTOR_WORKER_ID",
|
|
381
|
+
]);
|
|
382
|
+
}
|
|
383
|
+
const runId = runIdArg ?? runIdEnv ?? "";
|
|
384
|
+
const workerId = workerIdArg ?? workerIdEnv ?? "";
|
|
356
385
|
if (runId.trim().length === 0 || workerId.trim().length === 0) {
|
|
357
386
|
throw new ConductorValidationError("Conductor worker identity is unavailable: provide run_id + worker_id, or set BAPI_CONDUCTOR_RUN_ID and BAPI_CONDUCTOR_WORKER_ID.");
|
|
358
387
|
}
|
|
@@ -16,6 +16,12 @@
|
|
|
16
16
|
* to the worker's own `process.execPath`. Falling back would re-introduce the
|
|
17
17
|
* native load into the worker Node — the exact failure mode this slice removes.
|
|
18
18
|
*
|
|
19
|
+
* BAPI-772 adds ONE distinction on top of that contract, and it changes no
|
|
20
|
+
* fallback behavior: when BOTH worker variables are undefined there is no worker
|
|
21
|
+
* at all (a plain MCP session), which raises the typed
|
|
22
|
+
* {@link ConductorWorkerContextRequiredError} guidance envelope instead. Every
|
|
23
|
+
* partially-configured or invalid runtime keeps failing loud exactly as before.
|
|
24
|
+
*
|
|
19
25
|
* Security: argv is always a fixed LIST (`execFile`, never `shell: true`, never a
|
|
20
26
|
* command string). Event JSON payloads cross via STDIN (`--data-json-stdin`), so
|
|
21
27
|
* raw payloads/secrets never enter the process argument list. Every failure is
|
|
@@ -24,7 +30,7 @@
|
|
|
24
30
|
*/
|
|
25
31
|
import * as nodeChildProcess from "node:child_process";
|
|
26
32
|
import { isAbsolute as pathIsAbsolute } from "node:path";
|
|
27
|
-
import { ConductorLedgerSubprocessRuntimeError, } from "./errors.js";
|
|
33
|
+
import { ConductorLedgerSubprocessRuntimeError, ConductorWorkerContextRequiredError, } from "./errors.js";
|
|
28
34
|
import { makeProducerDedupeKey, makeStableProducerEventId, } from "./producer-ledger.js";
|
|
29
35
|
/** The env key that carries the captured conductor Node executable path. */
|
|
30
36
|
export const CONDUCTOR_NODE_PATH_ENV = "CONDUCTOR_NODE_PATH";
|
|
@@ -60,10 +66,30 @@ function nonEmpty(value) {
|
|
|
60
66
|
* non-empty. On any failure this throws a typed
|
|
61
67
|
* {@link ConductorLedgerSubprocessRuntimeError}; it NEVER falls back to
|
|
62
68
|
* `process.execPath`.
|
|
69
|
+
*
|
|
70
|
+
* BAPI-772 splits ABSENCE from BREAKAGE, and only at the top:
|
|
71
|
+
*
|
|
72
|
+
* - BOTH variables undefined → {@link ConductorWorkerContextRequiredError}.
|
|
73
|
+
* There is no worker runtime because there is no worker: this is a plain MCP
|
|
74
|
+
* session, and the caller deserves guidance, not a 503.
|
|
75
|
+
* - anything else — one variable present and the other not, an empty or
|
|
76
|
+
* whitespace-only value, a relative path, an unusable CLI file — keeps the
|
|
77
|
+
* existing fail-loud `LEDGER_SUBPROCESS_RUNTIME_UNAVAILABLE` mapping. A
|
|
78
|
+
* half-configured or corrupted worker runtime is a real fault and must never
|
|
79
|
+
* be relabeled as an ordinary plain session.
|
|
63
80
|
*/
|
|
64
81
|
export function resolveWorkerLedgerCliRuntime(deps = {}) {
|
|
65
82
|
const env = deps.env ?? process.env;
|
|
66
83
|
const isAbsolute = deps.isAbsolute ?? pathIsAbsolute;
|
|
84
|
+
// `undefined` specifically — an env var SET to "" is present-but-invalid and
|
|
85
|
+
// falls through to the fail-loud path below.
|
|
86
|
+
if (env[CONDUCTOR_NODE_PATH_ENV] === undefined &&
|
|
87
|
+
env[BAPI_CONDUCTOR_CLI_FILE_ENV] === undefined) {
|
|
88
|
+
throw new ConductorWorkerContextRequiredError([
|
|
89
|
+
CONDUCTOR_NODE_PATH_ENV,
|
|
90
|
+
BAPI_CONDUCTOR_CLI_FILE_ENV,
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
67
93
|
const nodePathRaw = env[CONDUCTOR_NODE_PATH_ENV];
|
|
68
94
|
if (!nonEmpty(nodePathRaw)) {
|
|
69
95
|
throw new ConductorLedgerSubprocessRuntimeError("missing", CONDUCTOR_NODE_PATH_ENV);
|