@bridge_gpt/mcp-server 0.2.16 → 0.2.19
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/CONDUCTOR.md +75 -0
- package/README.md +2 -2
- package/build/agent-capabilities/probe-context.js +13 -3
- package/build/agent-capabilities/probes.js +262 -11
- package/build/agent-capabilities/reporter.js +1 -0
- package/build/agents.generated.js +3 -3
- package/build/backend-warnings.js +44 -0
- package/build/claude-settings.js +129 -0
- package/build/commands.generated.js +7 -6
- package/build/conductor/bridge-api-client.js +198 -18
- package/build/conductor/claude-hook.js +22 -4
- package/build/conductor/cli.js +76 -25
- package/build/conductor/deny-enforcement-preflight.js +96 -0
- package/build/conductor/doctor.js +183 -2
- package/build/conductor/done-gate.js +5 -0
- package/build/conductor/epic-reconcile.js +71 -14
- package/build/conductor/epic-runtime.js +839 -67
- package/build/conductor/epic-state.js +524 -63
- package/build/conductor/errors.js +156 -3
- package/build/conductor/event-accessors.js +252 -0
- package/build/conductor/file-scope-guard.js +201 -0
- package/build/conductor/github-mergeability.js +85 -0
- package/build/conductor/local-merge.js +47 -1
- package/build/conductor/merge-identity.js +41 -0
- package/build/conductor/merge-ledger.js +19 -72
- package/build/conductor/plan.js +12 -2
- package/build/conductor/pr-ci-producer.js +17 -2
- package/build/conductor/pr-discovery.js +11 -1
- package/build/conductor/producer-ledger.js +1 -1
- package/build/conductor/store.js +161 -18
- package/build/conductor/supervisor-config.js +4 -39
- package/build/conductor/supervisor-escalation.js +10 -26
- package/build/conductor/supervisor-ledger.js +5 -12
- package/build/conductor/supervisor-merge.js +32 -5
- package/build/conductor/supervisor-message-relay.js +2 -5
- package/build/conductor/supervisor-notification.js +1 -1
- package/build/conductor/supervisor-runtime.js +12 -54
- package/build/conductor/supervisor-state.js +4 -18
- package/build/conductor/supervisor-types.js +2 -2
- package/build/conductor/taxonomy.js +12 -0
- package/build/conductor/tools.js +28 -6
- package/build/conductor/worker-ledger-cli.js +244 -0
- package/build/conductor-bin.js +1800 -5166
- package/build/conductor-claude-hook-bin.js +4 -2
- package/build/doctor.js +40 -0
- package/build/executor/cli.js +229 -0
- package/build/executor/credentials.js +65 -0
- package/build/executor/deps.js +117 -0
- package/build/executor/env.js +79 -0
- package/build/executor/heartbeat.js +59 -0
- package/build/executor/http-client.js +131 -0
- package/build/executor/index.js +10 -0
- package/build/executor/job-errors.js +55 -0
- package/build/executor/job-log-registry.js +110 -0
- package/build/executor/job-runner.js +688 -0
- package/build/executor/job-types.js +60 -0
- package/build/executor/merge-job.js +155 -0
- package/build/executor/observation.js +123 -0
- package/build/executor/permissions.js +79 -0
- package/build/executor/preflight.js +144 -0
- package/build/executor/process.js +81 -0
- package/build/executor/prompt-spec.js +235 -0
- package/build/executor/results.js +134 -0
- package/build/executor/resume-pre-spawn.js +179 -0
- package/build/executor/runner.js +98 -0
- package/build/executor/terminal-mutation.js +34 -0
- package/build/executor/test-clock.js +109 -0
- package/build/executor/types.js +18 -0
- package/build/executor/verdict-artifact.js +53 -0
- package/build/executor/viewer-tabs.js +78 -0
- package/build/executor/watch-cli.js +113 -0
- package/build/executor/worker-command.js +106 -0
- package/build/executor/worker-finalization.js +97 -0
- package/build/executor/worker-log.js +92 -0
- package/build/executor/worktree-gc.js +134 -0
- package/build/executor/worktree-inspection.js +86 -0
- package/build/executor/worktree.js +103 -0
- package/build/index.js +13950 -9669
- package/build/install-bridge.js +25 -8
- package/build/install-doctor.js +387 -0
- package/build/mcp-invoke.js +19 -3
- package/build/mcp-provisioning.js +31 -25
- package/build/mcp-registration-doctor.js +27 -7
- package/build/mcp-server-invocation.js +152 -0
- package/build/pipelines.generated.js +31 -6
- package/build/readme.generated.js +1 -1
- package/build/regression-check.js +53 -1
- package/build/review-tickets.js +175 -21
- package/build/sfcc/reads-site-preference.js +52 -19
- package/build/start-tickets-conductor.js +47 -99
- package/build/start-tickets-prereqs.js +185 -4
- package/build/start-tickets.js +218 -180
- package/build/version.generated.js +1 -1
- package/build/visual-diff-worker.js +313 -0
- package/build/visual-diff.js +632 -0
- package/build/worktree-core.js +202 -0
- package/package.json +10 -6
- package/pipelines/review-ticket.json +24 -2
- package/public/css/main.min.css +3311 -1
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +7924 -1
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +5 -2
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-side conductor-ledger subprocess boundary (BAPI-527, durable execution).
|
|
3
|
+
*
|
|
4
|
+
* CORE INVARIANT: only the CAPTURED CONDUCTOR NODE touches the `better-sqlite3`
|
|
5
|
+
* native binary. A dispatched worker's Node process must NEVER import or load it.
|
|
6
|
+
* Every worker-facing ledger operation (`check_messages`, and the
|
|
7
|
+
* `wait_for_done_gate` event emit) therefore runs through this module, which
|
|
8
|
+
* shells out to the packaged `conductor-bin.js` CLI executed by the conductor
|
|
9
|
+
* Node runtime captured at dispatch time in `CONDUCTOR_NODE_PATH`. Conductor-OWNED
|
|
10
|
+
* processes (the CLI itself, the supervisor/epic runtimes) keep calling
|
|
11
|
+
* `store.ts` directly — they legitimately own the native load.
|
|
12
|
+
*
|
|
13
|
+
* Hard-fail contract: when `CONDUCTOR_NODE_PATH` is missing/invalid or
|
|
14
|
+
* `BAPI_CONDUCTOR_CLI_FILE` is absent, this boundary throws a typed
|
|
15
|
+
* {@link ConductorLedgerSubprocessRuntimeError} rather than silently falling back
|
|
16
|
+
* to the worker's own `process.execPath`. Falling back would re-introduce the
|
|
17
|
+
* native load into the worker Node — the exact failure mode this slice removes.
|
|
18
|
+
*
|
|
19
|
+
* Security: argv is always a fixed LIST (`execFile`, never `shell: true`, never a
|
|
20
|
+
* command string). Event JSON payloads cross via STDIN (`--data-json-stdin`), so
|
|
21
|
+
* raw payloads/secrets never enter the process argument list. Every failure is
|
|
22
|
+
* converted to a sanitized typed error — no raw caught error, path, argv, stdout,
|
|
23
|
+
* stderr, or credential value is ever attached.
|
|
24
|
+
*/
|
|
25
|
+
import * as nodeChildProcess from "node:child_process";
|
|
26
|
+
import { isAbsolute as pathIsAbsolute } from "node:path";
|
|
27
|
+
import { ConductorLedgerSubprocessRuntimeError, } from "./errors.js";
|
|
28
|
+
import { makeProducerDedupeKey, makeStableProducerEventId, } from "./producer-ledger.js";
|
|
29
|
+
/** The env key that carries the captured conductor Node executable path. */
|
|
30
|
+
export const CONDUCTOR_NODE_PATH_ENV = "CONDUCTOR_NODE_PATH";
|
|
31
|
+
/** The (pre-existing) env key that carries the packaged `conductor-bin.js` path. */
|
|
32
|
+
export const BAPI_CONDUCTOR_CLI_FILE_ENV = "BAPI_CONDUCTOR_CLI_FILE";
|
|
33
|
+
/**
|
|
34
|
+
* Bounded stdout buffer for a conductor CLI response. Conductor JSON responses
|
|
35
|
+
* (message batches / emit summaries) are small; 8 MiB is generous headroom while
|
|
36
|
+
* still capping a runaway subprocess.
|
|
37
|
+
*/
|
|
38
|
+
export const WORKER_LEDGER_CLI_MAX_BUFFER = 8 * 1024 * 1024;
|
|
39
|
+
/**
|
|
40
|
+
* Hard wall-clock cap for a single conductor CLI subprocess. A hung
|
|
41
|
+
* `conductor-bin.js` must never hang `check_messages` / `wait_for_done_gate`
|
|
42
|
+
* indefinitely; `execFile`'s `timeout` kills the child and surfaces the callback
|
|
43
|
+
* error (mapped to a sanitized `spawn_failed`). Generous relative to a fast local
|
|
44
|
+
* ledger read/write, but bounded.
|
|
45
|
+
*/
|
|
46
|
+
export const WORKER_LEDGER_CLI_TIMEOUT_MS = 30_000;
|
|
47
|
+
const defaultExecFile = (file, args, options, callback) =>
|
|
48
|
+
// The narrowed signature (utf-8 string stdout/stderr + list argv, no shell) is a
|
|
49
|
+
// safe structural subset of Node's execFile overloads.
|
|
50
|
+
nodeChildProcess.execFile(file, args, options, callback);
|
|
51
|
+
function nonEmpty(value) {
|
|
52
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate and resolve the worker-ledger subprocess runtime from the environment.
|
|
56
|
+
*
|
|
57
|
+
* `CONDUCTOR_NODE_PATH` must be set, non-empty, and an ABSOLUTE path (a relative
|
|
58
|
+
* value like `node` / `./node` is rejected as `invalid` — it must never resolve
|
|
59
|
+
* against `PATH` or the worker cwd). `BAPI_CONDUCTOR_CLI_FILE` must be set and
|
|
60
|
+
* non-empty. On any failure this throws a typed
|
|
61
|
+
* {@link ConductorLedgerSubprocessRuntimeError}; it NEVER falls back to
|
|
62
|
+
* `process.execPath`.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveWorkerLedgerCliRuntime(deps = {}) {
|
|
65
|
+
const env = deps.env ?? process.env;
|
|
66
|
+
const isAbsolute = deps.isAbsolute ?? pathIsAbsolute;
|
|
67
|
+
const nodePathRaw = env[CONDUCTOR_NODE_PATH_ENV];
|
|
68
|
+
if (!nonEmpty(nodePathRaw)) {
|
|
69
|
+
throw new ConductorLedgerSubprocessRuntimeError("missing", CONDUCTOR_NODE_PATH_ENV);
|
|
70
|
+
}
|
|
71
|
+
const nodePath = nodePathRaw.trim();
|
|
72
|
+
if (!isAbsolute(nodePath)) {
|
|
73
|
+
throw new ConductorLedgerSubprocessRuntimeError("invalid", CONDUCTOR_NODE_PATH_ENV);
|
|
74
|
+
}
|
|
75
|
+
const cliFileRaw = env[BAPI_CONDUCTOR_CLI_FILE_ENV];
|
|
76
|
+
if (!nonEmpty(cliFileRaw)) {
|
|
77
|
+
throw new ConductorLedgerSubprocessRuntimeError("cli_missing", BAPI_CONDUCTOR_CLI_FILE_ENV);
|
|
78
|
+
}
|
|
79
|
+
return { nodePath, cliFile: cliFileRaw.trim() };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Execute one conductor CLI subcommand under the captured conductor Node and
|
|
83
|
+
* resolve its raw stdout. argv is a fixed LIST — `[cliFile, ...subcommandArgs]` —
|
|
84
|
+
* spawned via `execFile` with `shell` disabled. An optional `stdin` payload is
|
|
85
|
+
* written to the child's stdin (used for `--data-json-stdin`) so raw payloads
|
|
86
|
+
* never enter argv. Any spawn error or abnormal exit is converted to a sanitized
|
|
87
|
+
* {@link ConductorLedgerSubprocessRuntimeError} — the raw error, stderr, and
|
|
88
|
+
* stdout are discarded so no path/secret leaks.
|
|
89
|
+
*/
|
|
90
|
+
export function execConductorCli(subcommandArgs, stdin, deps = {}) {
|
|
91
|
+
const runtime = resolveWorkerLedgerCliRuntime(deps);
|
|
92
|
+
const execFile = deps.execFile ?? defaultExecFile;
|
|
93
|
+
const maxBuffer = deps.maxBuffer ?? WORKER_LEDGER_CLI_MAX_BUFFER;
|
|
94
|
+
const timeout = deps.timeout ?? WORKER_LEDGER_CLI_TIMEOUT_MS;
|
|
95
|
+
const argv = [runtime.cliFile, ...subcommandArgs];
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
let child;
|
|
98
|
+
try {
|
|
99
|
+
child = execFile(runtime.nodePath, argv,
|
|
100
|
+
// A `timeout` bounds a hung child; the kill surfaces as the callback error
|
|
101
|
+
// (mapped below to a sanitized spawn_failed) rather than an infinite hang.
|
|
102
|
+
{ maxBuffer, encoding: "utf8", timeout }, (error, stdout) => {
|
|
103
|
+
if (error) {
|
|
104
|
+
// A non-zero exit, spawn failure, or timeout kill — never surface the
|
|
105
|
+
// raw error text (it can carry paths/secrets). Fixed reason only.
|
|
106
|
+
reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", CONDUCTOR_NODE_PATH_ENV));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
resolve(stdout);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
reject(new ConductorLedgerSubprocessRuntimeError("spawn_failed", CONDUCTOR_NODE_PATH_ENV));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (stdin !== undefined) {
|
|
117
|
+
// A writable-stream 'error' (e.g. EPIPE when the child exits early) is emitted
|
|
118
|
+
// ASYNCHRONOUSLY and, without a listener, becomes an uncaught exception that
|
|
119
|
+
// crashes the whole worker process. Attach a no-op handler so the failure is
|
|
120
|
+
// absorbed here; the exec callback still surfaces the underlying failure as a
|
|
121
|
+
// sanitized spawn_failed. Never echo the raw error.
|
|
122
|
+
child.stdin?.on("error", () => {
|
|
123
|
+
/* swallowed: the exec callback owns the failure signal */
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
child.stdin?.write(stdin);
|
|
127
|
+
child.stdin?.end();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// A synchronous write failure surfaces through the exec callback as a spawn
|
|
131
|
+
// error; do not throw here (and do not echo the raw failure).
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Parse compact CLI JSON from stdout. Malformed output becomes a sanitized typed
|
|
138
|
+
* {@link ConductorLedgerSubprocessRuntimeError} (`malformed_stdout`) — the raw
|
|
139
|
+
* stdout is never attached to the thrown error.
|
|
140
|
+
*/
|
|
141
|
+
export function parseCliJsonStdout(stdout) {
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(stdout);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new ConductorLedgerSubprocessRuntimeError("malformed_stdout", CONDUCTOR_NODE_PATH_ENV);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Read + ACK a worker's pending relay messages through the conductor CLI, i.e.
|
|
151
|
+
* `conductor-bin.js check-messages --run-id <id> --worker-id <id> [--limit <n>] --json`.
|
|
152
|
+
* Returns the parsed {@link CheckWorkerMessagesResult}. Never touches the local
|
|
153
|
+
* SQLite store from the worker Node.
|
|
154
|
+
*/
|
|
155
|
+
export async function checkWorkerMessagesViaCli(input, deps = {}) {
|
|
156
|
+
const args = ["check-messages", "--run-id", input.runId, "--worker-id", input.workerId];
|
|
157
|
+
if (input.limit !== undefined) {
|
|
158
|
+
args.push("--limit", String(input.limit));
|
|
159
|
+
}
|
|
160
|
+
args.push("--json");
|
|
161
|
+
const stdout = await execConductorCli(args, undefined, deps);
|
|
162
|
+
return parseCliJsonStdout(stdout);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Build the `emit-event` envelope argv (everything EXCEPT the normalized `data`
|
|
166
|
+
* object, which crosses via stdin through `--data-json-stdin`). Mirrors the
|
|
167
|
+
* claude-hook emit contract so raw payloads/secrets never reach the argument list.
|
|
168
|
+
*/
|
|
169
|
+
export function buildEmitEventArgs(event) {
|
|
170
|
+
const args = ["emit-event", "--type", event.type, "--source", event.source];
|
|
171
|
+
// Forward the deterministic event id so the conductor process dedups server-side
|
|
172
|
+
// on the `events.id` UNIQUE constraint — this is what lets the worker skip the
|
|
173
|
+
// in-process `eventAlreadyExists` poll (which would load better-sqlite3).
|
|
174
|
+
if (nonEmpty(event.id ?? undefined))
|
|
175
|
+
args.push("--id", event.id);
|
|
176
|
+
if (nonEmpty(event.subject ?? undefined))
|
|
177
|
+
args.push("--subject", event.subject);
|
|
178
|
+
if (nonEmpty(event.run_id ?? undefined))
|
|
179
|
+
args.push("--run-id", event.run_id);
|
|
180
|
+
if (nonEmpty(event.worker_id ?? undefined))
|
|
181
|
+
args.push("--worker-id", event.worker_id);
|
|
182
|
+
if (nonEmpty(event.producer ?? undefined))
|
|
183
|
+
args.push("--producer", event.producer);
|
|
184
|
+
if (nonEmpty(event.observed_via ?? undefined)) {
|
|
185
|
+
args.push("--observed-via", event.observed_via);
|
|
186
|
+
}
|
|
187
|
+
if (event.schema_version !== undefined) {
|
|
188
|
+
args.push("--schema-version", String(event.schema_version));
|
|
189
|
+
}
|
|
190
|
+
if (nonEmpty(event.time ?? undefined))
|
|
191
|
+
args.push("--time", event.time);
|
|
192
|
+
if (typeof event.confidence === "number") {
|
|
193
|
+
args.push("--confidence", String(event.confidence));
|
|
194
|
+
}
|
|
195
|
+
args.push("--data-json-stdin", "--json");
|
|
196
|
+
return args;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Emit one conductor event through the CLI, i.e.
|
|
200
|
+
* `conductor-bin.js emit-event ... --data-json-stdin --json`, passing the
|
|
201
|
+
* normalized `data` object over STDIN rather than argv. Returns the parsed CLI
|
|
202
|
+
* JSON result. Never touches the local SQLite store from the worker Node.
|
|
203
|
+
*/
|
|
204
|
+
export async function emitConductorEventViaCli(event, deps = {}) {
|
|
205
|
+
const args = buildEmitEventArgs(event);
|
|
206
|
+
const stdinPayload = JSON.stringify(event.data ?? {});
|
|
207
|
+
const stdout = await execConductorCli(args, stdinPayload, deps);
|
|
208
|
+
return parseCliJsonStdout(stdout);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Dedup-aware event emit through the CLI, with NO in-process ledger access.
|
|
212
|
+
*
|
|
213
|
+
* This is the worker gate path's replacement for the in-process
|
|
214
|
+
* {@link emitConductorEventIfNew}: the dedupe key + deterministic event id are
|
|
215
|
+
* derived PURELY (hashing only — no store read), the id is forwarded to the CLI
|
|
216
|
+
* (`emit-event --id`), and the conductor process performs the dedup server-side on
|
|
217
|
+
* the `events.id` UNIQUE constraint. Because the pre-check `eventAlreadyExists`
|
|
218
|
+
* poll is gone, a dispatched worker calling `wait_for_done_gate` never loads
|
|
219
|
+
* `better-sqlite3` — closing the exact gap BAPI-527 targets. A conductor-side
|
|
220
|
+
* duplicate is reported back as `{ ok: false, reason: "duplicate" }`.
|
|
221
|
+
*/
|
|
222
|
+
export async function emitConductorEventIfNewViaCli(input, dimensions, deps = {}) {
|
|
223
|
+
const dedupeKey = makeProducerDedupeKey(dimensions);
|
|
224
|
+
const eventId = makeStableProducerEventId(dedupeKey);
|
|
225
|
+
// Embed the dedupe key under data.details (mirrors emitConductorEventIfNew) so a
|
|
226
|
+
// later ledger scan can still recognize the event, without disturbing other
|
|
227
|
+
// normalized details the caller supplied.
|
|
228
|
+
const existingData = input.data ?? {};
|
|
229
|
+
const existingDetails = existingData.details && typeof existingData.details === "object" && !Array.isArray(existingData.details)
|
|
230
|
+
? existingData.details
|
|
231
|
+
: {};
|
|
232
|
+
const data = {
|
|
233
|
+
...existingData,
|
|
234
|
+
details: { ...existingDetails, dedupe_key: dedupeKey },
|
|
235
|
+
};
|
|
236
|
+
const result = (await emitConductorEventViaCli({ ...input, id: eventId, data }, deps));
|
|
237
|
+
// The conductor CLI reports a UNIQUE-constraint collision as a structured
|
|
238
|
+
// duplicate rather than an error — treat that as a no-op dedup, everything else
|
|
239
|
+
// as a successful emit of the deterministic id.
|
|
240
|
+
if (result && result.ok === false && result.reason === "duplicate") {
|
|
241
|
+
return { emitted: false, reason: "duplicate" };
|
|
242
|
+
}
|
|
243
|
+
return { emitted: true, event_id: eventId };
|
|
244
|
+
}
|