@bridge_gpt/mcp-server 0.2.58 → 0.2.60
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 +231 -113
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/agent-registry.js +15 -0
- package/build/agent-utils.js +16 -8
- package/build/claude-user-config-doctor.js +1 -35
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +7 -8
- package/build/conductor/bridge-api-client.js +202 -2
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/claude-hook.js +16 -16
- package/build/conductor/doctor.js +279 -72
- package/build/conductor/epic-implementer-cli.js +38 -5
- package/build/conductor/epic-reconcile.js +1 -1
- package/build/conductor/epic-runtime.js +21 -12
- package/build/conductor/errors.js +3 -3
- package/build/conductor/file-scope-guard.js +4 -4
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-inspection.js +2 -2
- package/build/conductor/install-doctor.js +28 -6
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/merge-ledger.js +2 -2
- package/build/conductor/pr-ci-producer.js +6 -6
- package/build/conductor/pr-review-producer.js +2 -2
- package/build/conductor/producer-ledger.js +2 -0
- package/build/conductor/readiness-sections.js +166 -9
- package/build/conductor/readiness.js +97 -16
- package/build/conductor/recovery-cli.js +243 -12
- package/build/conductor/recovery-operations.js +64 -6
- package/build/conductor/repair-contract.js +38 -4
- package/build/conductor/spec-review-producer.js +6 -6
- package/build/conductor/store.js +10 -10
- package/build/conductor/supervisor-config.js +14 -14
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-message-relay.js +2 -2
- package/build/conductor/worker-ledger-cli.js +19 -19
- package/build/conductor-bundle-artifacts.js +2 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +199 -303
- package/build/drive-epic.js +88 -71
- package/build/epic-implementer-bin.js +22 -17
- package/build/epic-implementer-claude-hook-bin.js +4 -2
- package/build/epic-implementer-env.js +184 -0
- package/build/epic-integration-pr.js +2 -2
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +79 -8
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/index.js +21 -4
- package/build/executor/job-errors.js +3 -3
- package/build/executor/job-runner.js +127 -1
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +46 -5
- package/build/executor/observation.js +1 -1
- package/build/executor/preflight.js +1 -1
- package/build/executor/prompt-spec.js +1 -1
- package/build/executor/reconcile-overlap-job.js +10 -130
- package/build/executor/reconcile-overlap-types.js +1 -1
- package/build/executor/runner.js +1 -1
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +320 -342
- package/build/init.js +20 -28
- package/build/install-bridge-conductor.js +208 -15
- package/build/install-bridge.js +555 -37
- package/build/mcp-host-entry-adapter.js +6 -0
- package/build/mcp-host-targets.js +2 -0
- package/build/mcp-install-state.js +42 -4
- package/build/mcp-invoke.js +5 -5
- package/build/mcp-profile.js +2 -2
- package/build/mcp-provisioning.js +2 -2
- package/build/mcp-server-invocation.js +3 -3
- package/build/merge-pull-request.js +118 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/model-routing-host.js +25 -0
- package/build/model-routing-preferences.js +99 -0
- package/build/pipelines.generated.js +10 -10
- package/build/{plan-epic-conductor-eligibility.js → plan-epic-implementer-eligibility.js} +18 -16
- package/build/plan-execution-ledger.js +8 -0
- package/build/plane/cli.js +24 -2
- package/build/plane/member-roster.js +5 -5
- package/build/plane/preflight.js +68 -0
- package/build/plane/supervisor.js +1 -1
- package/build/plane/types.js +5 -5
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +4 -4
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +171 -17
- package/build/start-tickets-conductor.js +32 -32
- package/build/start-tickets-prereqs.js +4 -9
- package/build/start-tickets.js +475 -91
- package/build/version.generated.js +3 -3
- package/docs/CONDUCTOR.md +69 -18
- package/docs/install/mcp-tool-integrations.md +2 -1
- package/package.json +4 -4
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +2 -2
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- /package/build/{implement-epic → epic-shared}/bridge-client.js +0 -0
- /package/build/{implement-epic → epic-shared}/cut-protocol.js +0 -0
package/build/init.js
CHANGED
|
@@ -818,41 +818,33 @@ export async function runInit(cwd, options = {}) {
|
|
|
818
818
|
// ---- Phase 4a: Scaffold GitHub Copilot command artifacts ----
|
|
819
819
|
// VS Code Local agent discovers prompt files; Agent Host discovers project
|
|
820
820
|
// skills. Both artifacts are rendered from COMMANDS above, never a second
|
|
821
|
-
// Copilot-specific command catalog.
|
|
821
|
+
// Copilot-specific command catalog. Each representation reports its own
|
|
822
|
+
// counts, so a written prompt never implies its skill exists (or vice versa).
|
|
822
823
|
if (ideDetection.vscode) {
|
|
823
824
|
const promptsDir = path.join(cwd, ".github", "prompts");
|
|
824
825
|
const skillsDir = path.join(cwd, ".github", "skills");
|
|
825
|
-
const copilotWritten = new Set();
|
|
826
|
-
const copilotSkipped = new Set();
|
|
827
|
-
const copilotOverwritten = new Set();
|
|
828
826
|
const artifacts = commandEntries.map(([filename, content]) => ({
|
|
829
|
-
filename,
|
|
830
827
|
commandName: filename.replace(/\.md$/, ""),
|
|
831
828
|
...translateCommandToCopilot(filename, content),
|
|
832
829
|
}));
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (copilotOverwritten.size > 0)
|
|
852
|
-
console.log(` Overwritten (content changed): ${copilotOverwritten.size}`);
|
|
853
|
-
if (copilotSkipped.size > 0)
|
|
854
|
-
console.log(` Skipped (unchanged): ${copilotSkipped.size}`);
|
|
855
|
-
console.log(" .github/prompts/ (VS Code Local agent) and .github/skills/ (Agent Host)");
|
|
830
|
+
const promptOutcomes = await Promise.all(artifacts.map(({ commandName, prompt }) => scaffoldFile(path.join(promptsDir, `${commandName}.prompt.md`), prompt, promptsDir)));
|
|
831
|
+
const skillOutcomes = await Promise.all(artifacts.map(({ commandName, skill }) => scaffoldFile(path.join(skillsDir, commandName, "SKILL.md"), skill, path.join(skillsDir, commandName))));
|
|
832
|
+
const copilotReports = [
|
|
833
|
+
["VS Code Local-agent prompt files", ".github/prompts/", promptOutcomes],
|
|
834
|
+
["Agent Host skills", ".github/skills/", skillOutcomes],
|
|
835
|
+
];
|
|
836
|
+
for (const [label, dir, outcomes] of copilotReports) {
|
|
837
|
+
const written = outcomes.filter((outcome) => outcome !== "skipped").length;
|
|
838
|
+
const overwritten = outcomes.filter((outcome) => outcome === "overwritten").length;
|
|
839
|
+
const skipped = outcomes.filter((outcome) => outcome === "skipped").length;
|
|
840
|
+
console.log(`\n${label}: scaffolded ${outcomes.length} into ${dir}`);
|
|
841
|
+
if (written > 0)
|
|
842
|
+
console.log(` Written: ${written}`);
|
|
843
|
+
if (overwritten > 0)
|
|
844
|
+
console.log(` Overwritten (content changed): ${overwritten}`);
|
|
845
|
+
if (skipped > 0)
|
|
846
|
+
console.log(` Skipped (unchanged): ${skipped}`);
|
|
847
|
+
}
|
|
856
848
|
}
|
|
857
849
|
// ---- Phase 4b: Scaffold shipped documentation assets ----
|
|
858
850
|
// Every doc asset the /install-bridge capability report points a human at,
|
|
@@ -54,6 +54,32 @@
|
|
|
54
54
|
* Neither can produce `complete`: they provision LOCAL capability, and the
|
|
55
55
|
* terminal outcome still turns only on the live server-observed executor claim.
|
|
56
56
|
*
|
|
57
|
+
* DURABLE-HOST MODE (BAPI-1137). The executor phase below is the supported
|
|
58
|
+
* bring-up for a *durable host*: an execution home that is not an interactive
|
|
59
|
+
* shell session, so a sleeping or closed operator laptop no longer stalls a run.
|
|
60
|
+
* The client command spelling is unchanged — it is still `install conductor` —
|
|
61
|
+
* and so is every primitive it drives; what this slice does is name the
|
|
62
|
+
* composition and make its boundaries explicit. Durable-host mode IS that
|
|
63
|
+
* composition, and nothing more:
|
|
64
|
+
*
|
|
65
|
+
* planning (pure)
|
|
66
|
+
* → separately consented unit writing
|
|
67
|
+
* → separately consented start
|
|
68
|
+
* → read-only local inspection (service state, session/linger durability)
|
|
69
|
+
* → bounded server-observed readiness
|
|
70
|
+
*
|
|
71
|
+
* `executor install-service` remains the standalone, lifecycle-free generator
|
|
72
|
+
* for operators who deliberately manage lifecycle themselves.
|
|
73
|
+
*
|
|
74
|
+
* HOST OWNERSHIP is an operational selection, not server policy. A durable host
|
|
75
|
+
* may be operator-operated or Bridge-operated, chosen per repository, and both
|
|
76
|
+
* choices run IDENTICAL generation and lifecycle logic: `BAPI_BASE_URL` is the
|
|
77
|
+
* only ownership/location input, while platform paths, unit format, and `PATH`
|
|
78
|
+
* are machine-specific details rather than ownership signals. Nothing about that
|
|
79
|
+
* choice reaches `RunPolicy` or any other server policy payload. Bridge-operated
|
|
80
|
+
* mode is also NON-ALLOCATING: this command can configure a host that has
|
|
81
|
+
* already been assigned, and it never provisions a fleet or allocates a machine.
|
|
82
|
+
*
|
|
57
83
|
* The executor phase (BAPI-779) is opt-in via `--executor-id` and runs behind
|
|
58
84
|
* TWO independent consents, per property 3: writing the unit is not consent to
|
|
59
85
|
* start a persistent daemon. Its shape is:
|
|
@@ -70,6 +96,13 @@
|
|
|
70
96
|
* non-fatal: the run prints the exact platform-specific start command and
|
|
71
97
|
* finishes at `complete-with-manual-steps`.
|
|
72
98
|
*
|
|
99
|
+
* MERGE AUTHORITY IS UNTOUCHED (BAPI-1137). Provisioning a durable host gives
|
|
100
|
+
* the executor no merge-decision route and no direct GitHub write operation: the
|
|
101
|
+
* reconciler remains the sole transition authority and the actuator writes only
|
|
102
|
+
* evidence. Readiness output above must never be worded so as to imply
|
|
103
|
+
* otherwise — `executor_ready` means an executor is claiming work, not that this
|
|
104
|
+
* host can merge anything.
|
|
105
|
+
*
|
|
73
106
|
* The command does NOT persist a repository-level review policy: that is
|
|
74
107
|
* BAPI-694's. The policy selected here is per-run and is reported as such.
|
|
75
108
|
*/
|
|
@@ -81,6 +114,7 @@ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
|
81
114
|
import { CLAUDE_REVIEW_WORKFLOW_RELPATH, renderClaudeReviewWorkflow, } from "./claude-review-workflow.js";
|
|
82
115
|
import { formatExecutorInstallPreflight, } from "./executor/install-preflight.js";
|
|
83
116
|
import { buildExecutorServiceStartPlan, formatExecutorServiceStartFailure, } from "./executor/service-lifecycle.js";
|
|
117
|
+
import { executorEpicRunIdRefusal, isValidExecutorEpicRunId, } from "./executor/claim-scope.js";
|
|
84
118
|
import { planExecutorServiceInstall, } from "./executor/service-unit.js";
|
|
85
119
|
// ---------------------------------------------------------------------------
|
|
86
120
|
// Options + parsing
|
|
@@ -117,6 +151,12 @@ export function getInstallBridgeConductorUsage() {
|
|
|
117
151
|
" --executor-id <id> Stable executor id. Supplying it OPTS IN to the",
|
|
118
152
|
" consented executor-provisioning phase below; the",
|
|
119
153
|
" phase is skipped entirely without it.",
|
|
154
|
+
" --epic-run-id <id> Claim scope for the generated unit (repeatable).",
|
|
155
|
+
" --repo-wide Claim-scope opt-out: claim from every authorized",
|
|
156
|
+
" repo, unscoped by run. EXACTLY ONE of --epic-run-id",
|
|
157
|
+
" or --repo-wide is required with --executor-id — the",
|
|
158
|
+
" executor refuses to start without one, so a unit",
|
|
159
|
+
" generated without it would fail on every boot.",
|
|
120
160
|
" --allowed-bot <slug> claude-review workflow: allowed bot login",
|
|
121
161
|
" --secret-name <NAME> claude-review workflow: OAuth token secret NAME",
|
|
122
162
|
" --branches a,b claude-review workflow: PR branch triggers",
|
|
@@ -136,7 +176,7 @@ export function getInstallBridgeConductorUsage() {
|
|
|
136
176
|
"production when unset). The bootstrap POST then asks its own, separate write",
|
|
137
177
|
"consent. Without a TTY there is no consent, so the command aborts.",
|
|
138
178
|
"",
|
|
139
|
-
"Tool visibility (its own consent): adds the `
|
|
179
|
+
"Tool visibility (its own consent): adds the `epic_implementer` token to the",
|
|
140
180
|
"BRIDGE_MCP_PROFILE of the Bridge entry in host MCP configurations that",
|
|
141
181
|
"ALREADY have one. Automatic merging covers .mcp.json, .vscode/mcp.json, and",
|
|
142
182
|
".cursor/mcp.json only — never Codex or Copilot, which are neither inspected",
|
|
@@ -152,8 +192,17 @@ export function getInstallBridgeConductorUsage() {
|
|
|
152
192
|
"hook consent; a degraded ledger reports the module, NODE_MODULE_VERSION, and",
|
|
153
193
|
"failure kind, and never triggers an install, rebuild, or migration.",
|
|
154
194
|
"",
|
|
155
|
-
"
|
|
156
|
-
"
|
|
195
|
+
"Durable-host mode: this command is the supported bring-up for a persistent,",
|
|
196
|
+
"laptop-independent executor. It composes planning, separately consented unit",
|
|
197
|
+
"writing, separately consented start, read-only local inspection, and bounded",
|
|
198
|
+
"server-observed readiness. `executor install-service` still generates a unit and",
|
|
199
|
+
"nothing else, and remains available for operators who manage lifecycle themselves.",
|
|
200
|
+
"",
|
|
201
|
+
"Host ownership (operator-operated or Bridge-operated) is an operational selection,",
|
|
202
|
+
"not server policy: both run identical generation and lifecycle logic, and",
|
|
203
|
+
"BAPI_BASE_URL is the only ownership/location input. Bridge-operated mode allocates",
|
|
204
|
+
"no host — it configures one already assigned. A repository with no assigned durable",
|
|
205
|
+
"host is NOT ready; escalate that to Bridge operations.",
|
|
157
206
|
"",
|
|
158
207
|
"With --executor-id, this command does the whole leg. A preflight checks the platform,",
|
|
159
208
|
"`wt` / `git` / `claude` on PATH, and an explicit base URL, then reports whether",
|
|
@@ -200,6 +249,8 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
200
249
|
let epicKey;
|
|
201
250
|
let planFile;
|
|
202
251
|
let executorId;
|
|
252
|
+
const epicRunIds = [];
|
|
253
|
+
let repoWide = false;
|
|
203
254
|
let allowedBot;
|
|
204
255
|
let secretName;
|
|
205
256
|
let branches;
|
|
@@ -231,6 +282,11 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
231
282
|
return { status: "error", message: "--skip-workflow takes no value" };
|
|
232
283
|
skipWorkflow = true;
|
|
233
284
|
break;
|
|
285
|
+
case "--repo-wide":
|
|
286
|
+
if (inline !== undefined)
|
|
287
|
+
return { status: "error", message: "--repo-wide takes no value" };
|
|
288
|
+
repoWide = true;
|
|
289
|
+
break;
|
|
234
290
|
case "--repo":
|
|
235
291
|
case "--required-check":
|
|
236
292
|
case "--required-checks":
|
|
@@ -238,6 +294,7 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
238
294
|
case "--epic-key":
|
|
239
295
|
case "--plan-file":
|
|
240
296
|
case "--executor-id":
|
|
297
|
+
case "--epic-run-id":
|
|
241
298
|
case "--allowed-bot":
|
|
242
299
|
case "--secret-name":
|
|
243
300
|
case "--branches": {
|
|
@@ -259,6 +316,8 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
259
316
|
planFile = value;
|
|
260
317
|
else if (flag === "--executor-id")
|
|
261
318
|
executorId = value;
|
|
319
|
+
else if (flag === "--epic-run-id")
|
|
320
|
+
epicRunIds.push(value);
|
|
262
321
|
else if (flag === "--allowed-bot")
|
|
263
322
|
allowedBot = value;
|
|
264
323
|
else if (flag === "--secret-name")
|
|
@@ -304,6 +363,47 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
304
363
|
"so the bootstrap refuses to write one.",
|
|
305
364
|
};
|
|
306
365
|
}
|
|
366
|
+
// BAPI-1137 — the claim scope is validated HERE, at parse time, for the same
|
|
367
|
+
// reason the required-check list is: a unit that cannot start is worse than a
|
|
368
|
+
// refused invocation, and discovering it costs a reboot to notice.
|
|
369
|
+
if (executorId !== undefined) {
|
|
370
|
+
if (epicRunIds.length > 0 && repoWide) {
|
|
371
|
+
return {
|
|
372
|
+
status: "error",
|
|
373
|
+
message: "--epic-run-id and --repo-wide are mutually exclusive: a generated unit carries exactly " +
|
|
374
|
+
"one claim scope.",
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (epicRunIds.length === 0 && !repoWide) {
|
|
378
|
+
return {
|
|
379
|
+
status: "error",
|
|
380
|
+
message: "--executor-id requires an explicit claim scope: pass --epic-run-id <id> (repeatable) or " +
|
|
381
|
+
"--repo-wide. The executor refuses to start without one, so a unit generated without it " +
|
|
382
|
+
"would install cleanly and then fail on every boot.",
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const seenRunIds = new Set();
|
|
386
|
+
for (const runId of epicRunIds) {
|
|
387
|
+
// The SAME shape check `executor install-service` applies, reused rather
|
|
388
|
+
// than restated: a value that reaches the rendered argument vector is
|
|
389
|
+
// validated at the point of entry, so a malformed id is one refusal here
|
|
390
|
+
// instead of a unit that installs and then fails on every boot.
|
|
391
|
+
if (!isValidExecutorEpicRunId(runId)) {
|
|
392
|
+
return { status: "error", message: executorEpicRunIdRefusal(runId) };
|
|
393
|
+
}
|
|
394
|
+
if (seenRunIds.has(runId)) {
|
|
395
|
+
return { status: "error", message: `Duplicate --epic-run-id: ${runId}` };
|
|
396
|
+
}
|
|
397
|
+
seenRunIds.add(runId);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
else if (epicRunIds.length > 0 || repoWide) {
|
|
401
|
+
return {
|
|
402
|
+
status: "error",
|
|
403
|
+
message: "--epic-run-id / --repo-wide configure the generated executor unit, so they require " +
|
|
404
|
+
"--executor-id.",
|
|
405
|
+
};
|
|
406
|
+
}
|
|
307
407
|
return {
|
|
308
408
|
status: "ok",
|
|
309
409
|
options: {
|
|
@@ -319,6 +419,8 @@ export function parseInstallBridgeConductorArgs(argv) {
|
|
|
319
419
|
branches,
|
|
320
420
|
skipWorkflow,
|
|
321
421
|
executorId,
|
|
422
|
+
...(epicRunIds.length > 0 ? { epicRunIds } : {}),
|
|
423
|
+
...(repoWide ? { repoWide: true } : {}),
|
|
322
424
|
},
|
|
323
425
|
};
|
|
324
426
|
}
|
|
@@ -435,7 +537,12 @@ function buildExecutorProvisioningPlans(executor, executorId, baseUrl,
|
|
|
435
537
|
* access rather than from a flag, so the unit can only ever serve the
|
|
436
538
|
* repository this install actually authenticated for.
|
|
437
539
|
*/
|
|
438
|
-
repos
|
|
540
|
+
repos,
|
|
541
|
+
/**
|
|
542
|
+
* The claim scope the unit starts with (BAPI-1137). Exactly one side is set,
|
|
543
|
+
* validated at parse time — a unit without a scope fails on every boot.
|
|
544
|
+
*/
|
|
545
|
+
claimScope) {
|
|
439
546
|
if (executor.platform !== "darwin" && executor.platform !== "linux") {
|
|
440
547
|
return {
|
|
441
548
|
ok: false,
|
|
@@ -455,6 +562,13 @@ repos) {
|
|
|
455
562
|
baseUrl,
|
|
456
563
|
homeDir: executor.homeDir,
|
|
457
564
|
envPath: executor.envPath,
|
|
565
|
+
// Forwarded so the supervised process starts with the scope the
|
|
566
|
+
// operator chose. Without it the executor refuses to start (R54) and the
|
|
567
|
+
// service manager restarts it forever.
|
|
568
|
+
...(claimScope.epicRunIds !== undefined && claimScope.epicRunIds.length > 0
|
|
569
|
+
? { epicRunIds: claimScope.epicRunIds }
|
|
570
|
+
: {}),
|
|
571
|
+
...(claimScope.repoWide === true ? { repoWide: true } : {}),
|
|
458
572
|
}, platform);
|
|
459
573
|
}
|
|
460
574
|
catch (err) {
|
|
@@ -523,23 +637,102 @@ async function runExecutorProvisioningPhase(deps, executor, plans, access) {
|
|
|
523
637
|
return null;
|
|
524
638
|
}
|
|
525
639
|
deps.log(" the service manager accepted the unit.");
|
|
640
|
+
// ---- Read-only local inspection (BAPI-1137) ---------------------------
|
|
641
|
+
// Reported as its own fact, deliberately BEFORE and separately from the server
|
|
642
|
+
// observation below. "The manager accepted the unit" and "the manager is
|
|
643
|
+
// running it" and "Bridge has seen it claim" are three different statements,
|
|
644
|
+
// and an operator who reads one as another is exactly how a dead durable host
|
|
645
|
+
// gets called healthy.
|
|
646
|
+
let localState = "unknown";
|
|
647
|
+
if (executor.inspectServiceState) {
|
|
648
|
+
try {
|
|
649
|
+
const inspection = await executor.inspectServiceState(plans.install);
|
|
650
|
+
localState = inspection.state;
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
// A probe that throws leaves the state unknown. Nothing here is allowed to
|
|
654
|
+
// upgrade "I could not ask" into a state.
|
|
655
|
+
localState = "unknown";
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
deps.log(` local service state (read-only): ${localState}`);
|
|
659
|
+
deps.log(" that is a LOCAL fact only — it says nothing about whether Bridge has observed a claim.");
|
|
660
|
+
if (executor.inspectDurability) {
|
|
661
|
+
let durability = null;
|
|
662
|
+
try {
|
|
663
|
+
durability = await executor.inspectDurability(plans.install);
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
durability = null;
|
|
667
|
+
}
|
|
668
|
+
if (durability === null || durability.prerequisite === "unverifiable") {
|
|
669
|
+
deps.log(" durable session: unverifiable — this run could not establish whether the executor " +
|
|
670
|
+
"survives logout.");
|
|
671
|
+
}
|
|
672
|
+
else if (durability.prerequisite === "configured") {
|
|
673
|
+
deps.log(" durable session: configured — the executor survives logout on this host.");
|
|
674
|
+
}
|
|
675
|
+
else if (durability.prerequisite === "session-bound") {
|
|
676
|
+
// The probe answered, and what it answered is "a login session exists" —
|
|
677
|
+
// not "this survives logout". Reported as the qualified fact it is, with
|
|
678
|
+
// the session prerequisite the operator has to keep true.
|
|
679
|
+
deps.log(" durable session: session-bound — a login session exists, but the executor is NOT " +
|
|
680
|
+
"logout-durable on this host.");
|
|
681
|
+
if (durability.remediation) {
|
|
682
|
+
deps.log(` manual action: ${durability.remediation}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else if (durability.prerequisite === "not-configured") {
|
|
686
|
+
// NOT durably configured, said plainly, with the command the OPERATOR runs.
|
|
687
|
+
// Bridge never runs it: enabling lingering changes how the host behaves
|
|
688
|
+
// after logout, and that is the operator's decision on their own machine.
|
|
689
|
+
deps.log(" durable session: NOT durably configured — the executor stops when you log out.");
|
|
690
|
+
if (durability.remediation) {
|
|
691
|
+
deps.log(` manual action: ${durability.remediation}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
// `not-applicable`. Unreachable in practice — this phase only runs on
|
|
696
|
+
// darwin and linux, both of which have a prerequisite — but stated rather
|
|
697
|
+
// than left as silence, because a fact that renders nothing is
|
|
698
|
+
// indistinguishable from a fact that was never collected.
|
|
699
|
+
deps.log(" durable session: not applicable on this platform.");
|
|
700
|
+
}
|
|
701
|
+
}
|
|
526
702
|
// ---- Bounded observation ----------------------------------------------
|
|
527
703
|
// Only reached after a SUCCESSFUL start: polling for an executor that was
|
|
528
704
|
// never started would burn the whole budget to learn what we already know.
|
|
529
705
|
deps.log(` watching the server for an executor claim (up to ${Math.round(EXECUTOR_OBSERVATION_BUDGET_MS / 1000)}s)…`);
|
|
530
706
|
const observation = await observeExecutorReadiness(access, deps.fetchJson, executor);
|
|
531
707
|
if (observation.status === "observed") {
|
|
532
|
-
deps.log(
|
|
708
|
+
deps.log(` the server observed an executor claim for this repository (requested executor id: ${plans.install.executorId}).`);
|
|
709
|
+
// BAPI-1137, honestly scoped: the readiness contract this client can read is
|
|
710
|
+
// REPOSITORY-scoped — `GET .../conductor-readiness?repo_name=` returns
|
|
711
|
+
// `executor.{liveness_readable,last_seen_at,last_seen_age_seconds,ready}`
|
|
712
|
+
// and carries no executor id, because the server derives liveness from the
|
|
713
|
+
// repository's `executor_jobs` rows. So this observation proves an executor
|
|
714
|
+
// is claiming for this repository; it cannot prove it is THIS executor id.
|
|
715
|
+
// The honest move is to say so rather than to invent a request or response
|
|
716
|
+
// field the endpoint does not implement — see the durable-host runbook's
|
|
717
|
+
// prerequisite note for the server-side contract this needs.
|
|
718
|
+
deps.log(" note: the readiness contract is repository-scoped and carries no executor id, so this");
|
|
719
|
+
deps.log(" confirms an executor is claiming for this repository — not specifically the one just started.");
|
|
720
|
+
deps.log(" if another executor already serves this repository, verify the requested id yourself before");
|
|
721
|
+
deps.log(" treating this host as the one doing the work.");
|
|
533
722
|
return observation.readiness;
|
|
534
723
|
}
|
|
535
724
|
// The honest degradation: name what WAS achieved and what was not, separately.
|
|
536
|
-
deps.log(
|
|
537
|
-
deps.log(
|
|
538
|
-
|
|
725
|
+
deps.log(` started locally, not observed (executor id: ${plans.install.executorId}).`);
|
|
726
|
+
deps.log(` local service state (read-only): ${localState}`);
|
|
727
|
+
deps.log(" the unit was written and the service manager started it, but the server did not observe " +
|
|
728
|
+
`an executor claim within the budget (${observation.attempts} read(s), ` +
|
|
729
|
+
`${observation.readFailures} unreadable).`);
|
|
539
730
|
deps.log(observation.lastReady === false
|
|
540
731
|
? " the server last reported this repository's executor as stale/not ready."
|
|
541
732
|
: " the server has not yet seen any executor claim for this repository.");
|
|
542
733
|
deps.log(" executor_ready is NOT claimed — only a live server observation can produce it.");
|
|
734
|
+
deps.log(" escalate to the host's operating party (operator-operated) or to Bridge operations " +
|
|
735
|
+
"(Bridge-operated) with the repository name and the executor id above.");
|
|
543
736
|
return null;
|
|
544
737
|
}
|
|
545
738
|
// ---------------------------------------------------------------------------
|
|
@@ -556,10 +749,10 @@ async function runExecutorProvisioningPhase(deps, executor, plans, access) {
|
|
|
556
749
|
* said nothing would reasonably conclude the tools were already visible.
|
|
557
750
|
*/
|
|
558
751
|
export function formatConductorRestartAdvisory(mergedProfile) {
|
|
559
|
-
return (`⚠️
|
|
752
|
+
return (`⚠️ Epic Implementer profile updated: BRIDGE_MCP_PROFILE is now set to \`${mergedProfile}\` ` +
|
|
560
753
|
"in your local MCP config file(s). The running MCP server captured its active groups " +
|
|
561
754
|
"at startup, so this activates on the next MCP server launch — restart your MCP " +
|
|
562
|
-
"client to gain access to the
|
|
755
|
+
"client to gain access to the Epic Implementer tools.");
|
|
563
756
|
}
|
|
564
757
|
/** Remediation printed when no supported host config carries a Bridge entry. */
|
|
565
758
|
export const CONDUCTOR_PROFILE_EMPTY_MERGE_REMEDIATION = `run \`npx ${MCP_PACKAGE_NAME} install\` to create and register the host MCP entry, ` +
|
|
@@ -570,14 +763,14 @@ export const CONDUCTOR_HOOKS_MANUAL_REMEDIATION = "re-run `install conductor` an
|
|
|
570
763
|
/**
|
|
571
764
|
* Phase: tool visibility.
|
|
572
765
|
*
|
|
573
|
-
* Adds the `
|
|
766
|
+
* Adds the `epic_implementer` token to EXISTING supported host MCP entries behind its
|
|
574
767
|
* own consent, then prints the restart advisory. It never creates a host config
|
|
575
768
|
* — the merge helper's never-create rule is what keeps a declined or
|
|
576
769
|
* unconfigured repository from acquiring files it did not ask for.
|
|
577
770
|
*/
|
|
578
771
|
async function runConductorToolVisibilityPhase(deps, local) {
|
|
579
772
|
const consented = await confirm(deps, "Update existing supported host MCP configurations (.mcp.json, .vscode/mcp.json, " +
|
|
580
|
-
".cursor/mcp.json) to add the `
|
|
773
|
+
".cursor/mcp.json) to add the `epic_implementer` profile? [y/N]: ");
|
|
581
774
|
if (!consented) {
|
|
582
775
|
deps.log(" declined — no host MCP configuration and no install state were written.");
|
|
583
776
|
return {
|
|
@@ -620,7 +813,7 @@ async function runConductorToolVisibilityPhase(deps, local) {
|
|
|
620
813
|
}
|
|
621
814
|
deps.log(carrying.length > 0
|
|
622
815
|
? ` updated: ${carrying.join(", ")}`
|
|
623
|
-
: " the
|
|
816
|
+
: " the Epic Implementer profile was merged into your supported host MCP config(s).");
|
|
624
817
|
// Coverage honesty: the merge path writes these three files and no others.
|
|
625
818
|
deps.log(" Codex and Copilot host configuration was NOT inspected or changed.");
|
|
626
819
|
deps.log(` ${formatConductorRestartAdvisory(mergedProfile)}`);
|
|
@@ -847,7 +1040,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
|
|
|
847
1040
|
// could not be resolved the doctor above has already produced a fatal
|
|
848
1041
|
// section, so this fallback only matters for the narrow window where a
|
|
849
1042
|
// caller injected a non-fatal doctor.
|
|
850
|
-
const planned = buildExecutorProvisioningPlans(executor, options.executorId, baseUrl, access ? [access.repoName] : []);
|
|
1043
|
+
const planned = buildExecutorProvisioningPlans(executor, options.executorId, baseUrl, access ? [access.repoName] : [], { epicRunIds: options.epicRunIds, repoWide: options.repoWide });
|
|
851
1044
|
if (!planned.ok) {
|
|
852
1045
|
deps.errorLog("");
|
|
853
1046
|
deps.errorLog(`Executor provisioning refused — ${planned.error}`);
|
|
@@ -1027,7 +1220,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
|
|
|
1027
1220
|
else {
|
|
1028
1221
|
deps.log(" skipped — this caller supplied no local-provisioning seams.");
|
|
1029
1222
|
manualActions.push("tool visibility: run the packaged `install conductor` command to add the " +
|
|
1030
|
-
"`
|
|
1223
|
+
"`epic_implementer` profile to your host MCP configuration.");
|
|
1031
1224
|
}
|
|
1032
1225
|
deps.log("");
|
|
1033
1226
|
deps.log("Phase 5/8 — conductor local observability…");
|