@bridge_gpt/mcp-server 0.2.50 → 0.2.52
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 +24 -8
- package/build/agent-capabilities/probe-context.js +15 -7
- package/build/agent-capabilities/probes.js +42 -6
- package/build/agent-launchers/claude-executor-adapter.js +98 -14
- package/build/commands.generated.js +1 -1
- package/build/conduct-epic/bridge-client.js +115 -1
- package/build/conduct-epic/cli.js +351 -33
- package/build/conduct-epic/cut-protocol.js +65 -0
- package/build/conductor/bridge-api-client.js +171 -5
- package/build/conductor/deny-enforcement-preflight.js +107 -10
- package/build/conductor/local-merge.js +170 -11
- package/build/conductor-bin.js +2 -2
- package/build/connect-bitbucket-api.js +370 -0
- package/build/connect-bitbucket.js +437 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +230 -1
- package/build/drive-epic.js +423 -11
- package/build/env-file-link.js +164 -0
- package/build/epic-integration-pr.js +290 -0
- package/build/executor/cli.js +41 -6
- package/build/executor/deps.js +5 -1
- package/build/executor/env-file-guard.js +113 -0
- package/build/executor/env.js +78 -1
- package/build/executor/heartbeat.js +9 -0
- package/build/executor/http-client.js +90 -22
- package/build/executor/job-errors.js +43 -2
- package/build/executor/job-runner.js +137 -29
- package/build/executor/merge-job.js +102 -6
- package/build/executor/permissions.js +106 -0
- package/build/executor/preflight.js +38 -13
- package/build/executor/resume-pre-spawn.js +2 -1
- package/build/executor/runner.js +175 -4
- package/build/executor/service-unit.js +15 -0
- package/build/executor/terminal-mutation.js +22 -1
- package/build/executor/types.js +86 -0
- package/build/executor/worker-command.js +21 -5
- package/build/executor/worker-guard-hook.js +939 -0
- package/build/executor/worker-log.js +56 -0
- package/build/executor/worktree.js +11 -0
- package/build/git-reachability.js +147 -0
- package/build/index.js +535 -95
- package/build/install-bridge.js +95 -0
- package/build/pipelines.generated.js +10 -2
- package/build/plan-epic-conductor-eligibility.js +213 -0
- package/build/plane/cli.js +78 -15
- package/build/plane/defaults.js +165 -0
- package/build/plane/manifest.js +63 -8
- package/build/plane/member-logs.js +6 -0
- package/build/plane/member-roster.js +195 -11
- package/build/plane/preflight.js +43 -0
- package/build/plane/shutdown.js +25 -3
- package/build/plane/status.js +11 -0
- package/build/plane/supervisor.js +343 -14
- package/build/plane/test-fakes.js +43 -0
- package/build/plane/types.js +82 -11
- package/build/pr-base-contract.js +20 -0
- package/build/readme.generated.js +1 -1
- package/build/review-synthesis-config.js +60 -0
- package/build/scripts/executor-protocol-contract-driver.js +311 -0
- package/build/setup-epic.js +592 -139
- package/build/sfcc/log-query.js +2 -1
- package/build/sfcc/reads-custom-object-def.js +10 -13
- package/build/sfcc/reads-site-preference.js +5 -5
- package/build/sfcc/reads-system-object.js +4 -4
- package/build/sfcc/writes-custom-object-def.js +7 -7
- package/build/sfcc/writes-site-preference.js +4 -3
- package/build/sfcc/writes-system-object.js +7 -6
- package/build/start-tickets-conductor.js +11 -2
- package/build/start-tickets.js +69 -2
- package/build/version.generated.js +3 -3
- package/build/worker-containment-diagnostic.js +97 -0
- package/build/worker-guard-hook-bin.js +6 -0
- package/docs/CONDUCTOR.md +27 -0
- package/docs/install/mcp-tool-integrations.md +3 -2
- package/package.json +5 -3
- package/pipelines/plan-epic.json +5 -0
package/build/doctor.js
CHANGED
|
@@ -28,9 +28,11 @@ import { createBridgeApiUrls } from "./bridge-api-urls.js";
|
|
|
28
28
|
import { probeToolSurface } from "./tool-surface-gating.js";
|
|
29
29
|
import { resolveBapiCredentials } from "./credential-store.js";
|
|
30
30
|
import { resolveConductorBridgeApiAccess } from "./conductor/bridge-api-client.js";
|
|
31
|
+
import { getLiveRepositoryConductors, getParseDispatcherHealth, } from "./conduct-epic/bridge-client.js";
|
|
31
32
|
import { resolveConductEpicStateDirectory } from "./conduct-epic/checkpoint-store.js";
|
|
32
33
|
import { isConductEpicLockOwnerAlive, parseConductEpicLock, } from "./conduct-epic/lock.js";
|
|
33
34
|
import { resolveRequiredStartTicketsRepoName } from "./start-tickets-repo.js";
|
|
35
|
+
import { collectWorkerContainmentDiagnostic, formatWorkerContainmentDiagnosticReport, } from "./worker-containment-diagnostic.js";
|
|
34
36
|
import { BRIDGE_PACKAGE_NAME, describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, parseLauncherPin as parseSharedLauncherPin, } from "./launcher-config-inspection.js";
|
|
35
37
|
import { MCP_SERVER_NAME, MCP_PACKAGE_NAME, } from "./mcp-identity.js";
|
|
36
38
|
import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, } from "./agent-registry.js";
|
|
@@ -96,7 +98,17 @@ export function getDoctorUsage() {
|
|
|
96
98
|
// scoping, and BAPI-791 deleted that doctor section together with the whole
|
|
97
99
|
// worker-auth strategy vocabulary. The section it described exists in no
|
|
98
100
|
// doctor output, so the claim is removed rather than reworded.
|
|
99
|
-
"
|
|
101
|
+
"It includes an advisory 'Worker containment' section (BAPI-1019) reporting the",
|
|
102
|
+
"DECLARED posture a conductor worker runs under: that the executor strips this",
|
|
103
|
+
"checkout's .env* files from a worker's worktree as the last step before spawn",
|
|
104
|
+
"(refusing to spawn with ContractError.WorkerEnvFilePresent when it cannot",
|
|
105
|
+
"confirm they are gone), that a worker's database environment names a target",
|
|
106
|
+
"that does not exist, and the run policy's default permission posture. The",
|
|
107
|
+
"section issues ZERO probes — no command, no file read, no network call — and",
|
|
108
|
+
"reports constants rather than measurements, so it never claims a live check",
|
|
109
|
+
"ran and never changes the exit code.",
|
|
110
|
+
"",
|
|
111
|
+
"MCP containment is still not reported here. A conductor worker is scoped",
|
|
100
112
|
"at spawn time with `--strict-mcp-config --mcp-config <file>`, and the",
|
|
101
113
|
"executor asserts that scope per spawn from the worker's own system/init",
|
|
102
114
|
"event — failing the job with ContractError.McpSurfaceMismatch on a confirmed",
|
|
@@ -1401,6 +1413,151 @@ export function formatConductEpicDiagnosticReport(diagnostic) {
|
|
|
1401
1413
|
lines.push(" lock, rewrites no checkpoint, and never changes the exit code.");
|
|
1402
1414
|
return lines.join("\n");
|
|
1403
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Collect the operational advisories, read-only.
|
|
1418
|
+
*
|
|
1419
|
+
* Local pilot conductors are identified from the SAME checkpoint-directory and
|
|
1420
|
+
* lock inspection `conduct-epic readiness` uses — `parseConductEpicLock` plus the
|
|
1421
|
+
* injected liveness seam — never from the process table. Only a lock proven live
|
|
1422
|
+
* on THIS host counts; malformed, unreadable, dead, and remote-host locks are
|
|
1423
|
+
* excluded from the live set, with the indeterminate ones counted separately.
|
|
1424
|
+
*/
|
|
1425
|
+
export async function collectOperationalAdvisories(deps) {
|
|
1426
|
+
const isAlive = deps.isProcessAlive ?? isConductEpicLockOwnerAlive;
|
|
1427
|
+
const now = deps.now ?? (() => new Date());
|
|
1428
|
+
const resolveAccess = deps.resolveAccess ?? resolveConductorBridgeApiAccess;
|
|
1429
|
+
let dispatcher = null;
|
|
1430
|
+
let conductorObservation = "unavailable";
|
|
1431
|
+
const conductors = [];
|
|
1432
|
+
let repo = null;
|
|
1433
|
+
let access = null;
|
|
1434
|
+
try {
|
|
1435
|
+
access = await resolveAccess({
|
|
1436
|
+
env: deps.env,
|
|
1437
|
+
cwd: deps.cwd,
|
|
1438
|
+
homedir: deps.homedir,
|
|
1439
|
+
platform: deps.platform,
|
|
1440
|
+
readFile: deps.readFile,
|
|
1441
|
+
stat: deps.stat,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
catch {
|
|
1445
|
+
access = null;
|
|
1446
|
+
}
|
|
1447
|
+
if (access !== null && access.ok) {
|
|
1448
|
+
repo = access.access.repoName;
|
|
1449
|
+
const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
|
|
1450
|
+
const health = await getParseDispatcherHealth(access.access, fetchImpl);
|
|
1451
|
+
dispatcher = health.ok
|
|
1452
|
+
? health.value
|
|
1453
|
+
: { observation: "unavailable", heartbeatState: null, respondingSchedulerRunning: false };
|
|
1454
|
+
const live = await getLiveRepositoryConductors(access.access, now(), fetchImpl);
|
|
1455
|
+
if (live.ok) {
|
|
1456
|
+
conductorObservation = live.value.observation;
|
|
1457
|
+
conductors.push(...live.value.conductors);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
// Local pilot locks. Read through the same path the readiness section uses; a
|
|
1461
|
+
// failure to read the directory is silence, not a claim that none are held.
|
|
1462
|
+
let indeterminateLocks = 0;
|
|
1463
|
+
if (repo !== null) {
|
|
1464
|
+
const stateDirectory = resolveConductEpicStateDirectory(repo, {
|
|
1465
|
+
env: deps.env,
|
|
1466
|
+
homedir: deps.homedir,
|
|
1467
|
+
});
|
|
1468
|
+
let entries = [];
|
|
1469
|
+
try {
|
|
1470
|
+
entries = await deps.readdir(stateDirectory);
|
|
1471
|
+
}
|
|
1472
|
+
catch {
|
|
1473
|
+
entries = [];
|
|
1474
|
+
}
|
|
1475
|
+
for (const entry of entries) {
|
|
1476
|
+
if (!entry.endsWith(".lock"))
|
|
1477
|
+
continue;
|
|
1478
|
+
const epic = entry.slice(0, -".lock".length);
|
|
1479
|
+
let raw;
|
|
1480
|
+
try {
|
|
1481
|
+
raw = await deps.readFile(path.join(stateDirectory, entry));
|
|
1482
|
+
}
|
|
1483
|
+
catch {
|
|
1484
|
+
indeterminateLocks += 1;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
const owner = parseConductEpicLock(raw);
|
|
1488
|
+
if (owner === null) {
|
|
1489
|
+
indeterminateLocks += 1;
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
const local = owner.host === deps.env.HOSTNAME || owner.host === os.hostname();
|
|
1493
|
+
if (!local) {
|
|
1494
|
+
indeterminateLocks += 1;
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1497
|
+
if (!isAlive(owner.owner_pid))
|
|
1498
|
+
continue;
|
|
1499
|
+
// A pilot lock and a v2 run are two DISTINCT conductor authorities. Even
|
|
1500
|
+
// when both name the same epic they are not deduplicated — that pairing is
|
|
1501
|
+
// precisely the overlap an operator most needs to see.
|
|
1502
|
+
//
|
|
1503
|
+
// The key comes from a FILENAME, so it is bounded against the epic-key shape
|
|
1504
|
+
// before it is rendered; an unrecognized name reports `unknown` rather than
|
|
1505
|
+
// putting arbitrary filesystem text into a diagnostic line.
|
|
1506
|
+
conductors.push({
|
|
1507
|
+
kind: "pilot",
|
|
1508
|
+
epicKey: validateTicketKey(epic, "epic key").ok ? epic : null,
|
|
1509
|
+
state: "lock held",
|
|
1510
|
+
livenessSource: "lock",
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
if (conductors.length > 0 && conductorObservation !== "observed") {
|
|
1514
|
+
conductorObservation = "observed";
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return { dispatcher, conductorObservation, conductors, indeterminateLocks };
|
|
1518
|
+
}
|
|
1519
|
+
/** Render the operational-advisories section. Advisory: never changes the exit code. */
|
|
1520
|
+
export function formatOperationalAdvisoryReport(diagnostic) {
|
|
1521
|
+
const lines = ["", "operational advisories (advisory)"];
|
|
1522
|
+
const dispatcher = diagnostic.dispatcher;
|
|
1523
|
+
if (dispatcher === null) {
|
|
1524
|
+
lines.push(" SKIP Parse dispatcher: not checked (no Bridge credential resolved).");
|
|
1525
|
+
}
|
|
1526
|
+
else if (dispatcher.observation === "observed") {
|
|
1527
|
+
lines.push(" OK Parse dispatcher: observable — queued repository parses are being swept.");
|
|
1528
|
+
}
|
|
1529
|
+
else if (dispatcher.observation === "absent") {
|
|
1530
|
+
lines.push(" WARN Parse dispatcher: NOT observed. Queued repository parses wait for the\n" +
|
|
1531
|
+
" `worker:` dyno's parse-queue sweep; nothing else dispatches them.");
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
lines.push(" SKIP Parse dispatcher: unavailable — the liveness read produced no verdict.\n" +
|
|
1535
|
+
" This is not evidence that no dispatcher is running.");
|
|
1536
|
+
}
|
|
1537
|
+
if (diagnostic.conductorObservation === "unavailable") {
|
|
1538
|
+
lines.push(" SKIP Concurrent conductors: unavailable — the repository-scoped read produced\n" +
|
|
1539
|
+
" no verdict.");
|
|
1540
|
+
}
|
|
1541
|
+
else if (diagnostic.conductors.length === 0) {
|
|
1542
|
+
lines.push(" OK Concurrent conductors: none live on this repository.");
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
lines.push(` WARN Concurrent conductors: ${diagnostic.conductors.length} live on this repository.`);
|
|
1546
|
+
for (const conductor of diagnostic.conductors) {
|
|
1547
|
+
lines.push(` ${conductor.kind} epic=${conductor.epicKey ?? "unknown"} ` +
|
|
1548
|
+
`state=${conductor.state} liveness=${conductor.livenessSource}`);
|
|
1549
|
+
}
|
|
1550
|
+
lines.push(" A distinct-epic overlap is informational and may be legitimate: separately\n" +
|
|
1551
|
+
" scoped epics do not share transition authority.");
|
|
1552
|
+
}
|
|
1553
|
+
if (diagnostic.indeterminateLocks > 0) {
|
|
1554
|
+
lines.push(` ${diagnostic.indeterminateLocks} local lock(s) could not be judged (remote host,\n` +
|
|
1555
|
+
" malformed, or unreadable) and are excluded from the live count.");
|
|
1556
|
+
}
|
|
1557
|
+
lines.push(" Read-only: this section issues two Bridge GETs and reads local lock state. It");
|
|
1558
|
+
lines.push(" runs NO command probe, repairs no scheduler, and never changes the exit code.");
|
|
1559
|
+
return lines.join("\n");
|
|
1560
|
+
}
|
|
1404
1561
|
/**
|
|
1405
1562
|
* CLI entry for the read-only `doctor` subcommand. Returns a process exit code.
|
|
1406
1563
|
* Help returns 0; parser errors return 1; otherwise it prints the report and
|
|
@@ -1550,6 +1707,34 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1550
1707
|
/* local-launcher diagnostics are advisory; never block the doctor report */
|
|
1551
1708
|
}
|
|
1552
1709
|
}
|
|
1710
|
+
// Worker containment advisory section (BAPI-1019/A.7). Placed beside the other
|
|
1711
|
+
// advisory sections and, uniquely, issuing NO probe of any kind — no command,
|
|
1712
|
+
// no file read, no network call. That is what keeps doctor's "an unsupported
|
|
1713
|
+
// platform issues zero command probes" guarantee intact: a section that cannot
|
|
1714
|
+
// probe cannot break it on any platform.
|
|
1715
|
+
//
|
|
1716
|
+
// Wrapped in the same swallowing `try` as its neighbours. It reports declared
|
|
1717
|
+
// constants, so it should not be able to throw at all — which is exactly why an
|
|
1718
|
+
// unexpected throw here must not take the whole report (or the exit code) with
|
|
1719
|
+
// it.
|
|
1720
|
+
if (overrides.workerContainment !== false) {
|
|
1721
|
+
try {
|
|
1722
|
+
log(formatWorkerContainmentDiagnosticReport(collectWorkerContainmentDiagnostic()));
|
|
1723
|
+
}
|
|
1724
|
+
catch {
|
|
1725
|
+
// Degrade VISIBLY rather than dropping a section the help text promises:
|
|
1726
|
+
// the formatter's own unusable-input path renders the heading with
|
|
1727
|
+
// "Not confirmed" rows, which is the honest answer and is exactly what a
|
|
1728
|
+
// reader needs to see instead of silence. Passing `undefined` reuses that
|
|
1729
|
+
// one fallback rather than inventing a second wording here.
|
|
1730
|
+
try {
|
|
1731
|
+
log(formatWorkerContainmentDiagnosticReport(undefined));
|
|
1732
|
+
}
|
|
1733
|
+
catch {
|
|
1734
|
+
/* worker-containment posture is advisory; never block the doctor report */
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1553
1738
|
// Advisory MCP tool-surface capability section (BAPI-641). Strictly read-only:
|
|
1554
1739
|
// one 500 ms GET (or none, under the kill switch). Any timeout, malformed
|
|
1555
1740
|
// response, or unexpected throw degrades to a "fail-open to full surface" line
|
|
@@ -1701,6 +1886,50 @@ export async function runDoctorCli(argv, overrides = {}) {
|
|
|
1701
1886
|
}));
|
|
1702
1887
|
}
|
|
1703
1888
|
}
|
|
1889
|
+
// Advisory operational-advisories section (BAPI-963), placed immediately after
|
|
1890
|
+
// `conduct-epic readiness` because both are read by the same operator at the
|
|
1891
|
+
// same moment — "can I conduct?" then "what else is already running?".
|
|
1892
|
+
//
|
|
1893
|
+
// Gated on `collection.ok` for the same reason the section above is: an
|
|
1894
|
+
// unsupported platform reports nothing. That gate is belt-and-braces here,
|
|
1895
|
+
// because this section issues NO command probe on any path — the two facts it
|
|
1896
|
+
// reports come from read-only Bridge GETs and from local lock state. The
|
|
1897
|
+
// "unsupported platform runs zero probes" pin is therefore untouched by it.
|
|
1898
|
+
//
|
|
1899
|
+
// Advisory with respect to the EXIT CODE: an absent dispatcher, a concurrent
|
|
1900
|
+
// conductor, and an unavailable read all leave doctor's pass/fail exactly as
|
|
1901
|
+
// the required prerequisites computed it.
|
|
1902
|
+
if (overrides.operationalAdvisories !== false && collection.ok) {
|
|
1903
|
+
try {
|
|
1904
|
+
const injectedFs = deps;
|
|
1905
|
+
const advisoryDeps = {
|
|
1906
|
+
env: overrides.operationalAdvisories?.env ?? deps.env,
|
|
1907
|
+
cwd: overrides.operationalAdvisories?.cwd ?? deps.cwd,
|
|
1908
|
+
platform: overrides.operationalAdvisories?.platform ?? deps.platform,
|
|
1909
|
+
homedir: overrides.operationalAdvisories?.homedir ?? injectedFs.homedir ?? os.homedir,
|
|
1910
|
+
readFile: overrides.operationalAdvisories?.readFile ??
|
|
1911
|
+
injectedFs.readFile ??
|
|
1912
|
+
((p) => readFile(p, "utf-8")),
|
|
1913
|
+
stat: overrides.operationalAdvisories?.stat ?? injectedFs.stat ?? ((p) => stat(p)),
|
|
1914
|
+
readdir: overrides.operationalAdvisories?.readdir ?? ((p) => readdir(p)),
|
|
1915
|
+
resolveAccess: overrides.operationalAdvisories?.resolveAccess,
|
|
1916
|
+
isProcessAlive: overrides.operationalAdvisories?.isProcessAlive,
|
|
1917
|
+
fetchImpl: overrides.operationalAdvisories?.fetchImpl,
|
|
1918
|
+
now: overrides.operationalAdvisories?.now,
|
|
1919
|
+
};
|
|
1920
|
+
log(formatOperationalAdvisoryReport(await collectOperationalAdvisories(advisoryDeps)));
|
|
1921
|
+
}
|
|
1922
|
+
catch {
|
|
1923
|
+
// Any unexpected failure still renders a sanitized advisory section rather
|
|
1924
|
+
// than dropping it or leaking exception text.
|
|
1925
|
+
log(formatOperationalAdvisoryReport({
|
|
1926
|
+
dispatcher: null,
|
|
1927
|
+
conductorObservation: "unavailable",
|
|
1928
|
+
conductors: [],
|
|
1929
|
+
indeterminateLocks: 0,
|
|
1930
|
+
}));
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1704
1933
|
// Opt-in stale-ticket-branch section (BAPI-948). Gated on argv, not on platform
|
|
1705
1934
|
// support: it runs ONLY when the operator named at least one `--stale-branch`
|
|
1706
1935
|
// ticket, so an ordinary `doctor` run issues zero extra command probes and
|