@bridge_gpt/mcp-server 0.2.38 → 0.2.39

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.
Files changed (85) hide show
  1. package/README.md +189 -14
  2. package/build/agent-capabilities/probe-context.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +392 -0
  4. package/build/agent-launchers/executor-adapter-inspection.js +163 -0
  5. package/build/agent-launchers/executor-adapter-registry.js +90 -0
  6. package/build/agent-launchers/executor-adapter.js +136 -0
  7. package/build/agent-registry.js +28 -0
  8. package/build/agents.generated.js +1 -1
  9. package/build/claude-login.js +85 -0
  10. package/build/claude-user-config-doctor.js +59 -33
  11. package/build/commands.generated.js +12 -11
  12. package/build/conduct-epic/bridge-client.js +345 -0
  13. package/build/conduct-epic/checkpoint-store.js +423 -0
  14. package/build/conduct-epic/cli.js +1732 -0
  15. package/build/conduct-epic/lock.js +302 -0
  16. package/build/conduct-epic/pr-state.js +197 -0
  17. package/build/conduct-epic/spawn.js +101 -0
  18. package/build/conductor/bridge-api-client.js +37 -2
  19. package/build/conductor/doctor.js +11 -1
  20. package/build/conductor/install-doctor.js +184 -10
  21. package/build/conductor-bin.js +7 -7
  22. package/build/credential-store.js +10 -4
  23. package/build/credentials-cli.js +34 -19
  24. package/build/docs.generated.js +1 -1
  25. package/build/doctor.js +579 -88
  26. package/build/executor/agent-identity.js +32 -0
  27. package/build/executor/cli.js +50 -39
  28. package/build/executor/deps.js +15 -1
  29. package/build/executor/env.js +56 -45
  30. package/build/executor/index.js +9 -1
  31. package/build/executor/install-preflight.js +138 -0
  32. package/build/executor/job-errors.js +200 -0
  33. package/build/executor/job-runner.js +619 -268
  34. package/build/executor/observation.js +165 -0
  35. package/build/executor/permissions.js +163 -36
  36. package/build/executor/platform.js +54 -0
  37. package/build/executor/preflight.js +175 -67
  38. package/build/executor/process.js +39 -7
  39. package/build/executor/runner.js +19 -0
  40. package/build/executor/service-lifecycle.js +269 -0
  41. package/build/executor/service-unit.js +121 -12
  42. package/build/executor/stale-artifacts.js +70 -0
  43. package/build/executor/test-clock.js +188 -24
  44. package/build/executor/worker-command.js +22 -58
  45. package/build/executor/worker-log.js +82 -0
  46. package/build/executor/worktree-lock.js +264 -0
  47. package/build/index.js +527 -357
  48. package/build/install-bridge-conductor.js +376 -38
  49. package/build/install-bridge.js +414 -114
  50. package/build/install-doctor.js +13 -0
  51. package/build/install-reexec.js +5 -3
  52. package/build/mcp-install-state.js +130 -0
  53. package/build/mcp-profile.js +11 -2
  54. package/build/mcp-provisioning.js +15 -0
  55. package/build/merge-pull-request.js +562 -0
  56. package/build/phase-result-artifacts.js +450 -0
  57. package/build/pipeline-orchestrator.js +4 -0
  58. package/build/pipeline-utils.js +16 -0
  59. package/build/pipelines.generated.js +7 -7
  60. package/build/plane/preflight.js +18 -14
  61. package/build/plane/supervisor.js +8 -1
  62. package/build/project-root.js +34 -0
  63. package/build/readme.generated.js +1 -1
  64. package/build/run-unit-tests-launcher.js +36 -9
  65. package/build/setup-epic.js +57 -4
  66. package/build/sfcc/permissions.js +25 -6
  67. package/build/sfcc/reads-site-preference.js +6 -0
  68. package/build/sfcc/register.js +61 -23
  69. package/build/sfcc/registration-inventory.js +89 -0
  70. package/build/sfcc/setup-status.js +18 -34
  71. package/build/sfcc/tool-wrapper.js +294 -17
  72. package/build/sfcc/write-grants.js +33 -1
  73. package/build/sfcc/write-guard.js +41 -12
  74. package/build/sfcc/writes-custom-object-def.js +6 -2
  75. package/build/sfcc/writes-site-preference.js +6 -1
  76. package/build/sfcc/writes-system-object.js +11 -2
  77. package/build/sfcc/writes.js +13 -8
  78. package/build/start-tickets-prereqs.js +25 -15
  79. package/build/start-tickets.js +123 -21
  80. package/build/version.generated.js +1 -1
  81. package/build/worktree-core.js +9 -3
  82. package/docs/install/mcp-tool-integrations.md +54 -9
  83. package/docs/install/sfcc-integration.md +71 -24
  84. package/package.json +3 -3
  85. package/build/executor/worker-config-isolation.js +0 -287
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import path from "path";
23
23
  import { resolveBapiCredentials } from "./credential-store.js";
24
+ import { detectClaudeLogin, formatClaudeLoginAdvisory } from "./claude-login.js";
24
25
  /** Default production base URL (mirrors install-bridge's DEFAULT_BAPI_BASE_URL). */
25
26
  const DEFAULT_BASE_URL = "https://bridgegpt-api.com";
26
27
  /**
@@ -181,6 +182,18 @@ function readGithubConfiguredFlag(body) {
181
182
  */
182
183
  export async function collectInstallStatusChecks(deps) {
183
184
  const checks = [];
185
+ // Advisory-only Claude login marker (BAPI-791), independent of Bridge repo
186
+ // identity/credential resolution below — a worker authenticates through the
187
+ // executor host's own interactive `claude login`, not through anything this
188
+ // install-status section resolves. Bridge stores or diagnoses no Anthropic
189
+ // credential of its own, so this can only ever advise, never confirm.
190
+ const loginResult = await detectClaudeLogin({ homedir: deps.homedir, readFile: deps.readFile });
191
+ checks.push({
192
+ id: "claude-login",
193
+ label: "Claude login",
194
+ status: loginResult.detected ? "PASS" : "INFO",
195
+ detail: formatClaudeLoginAdvisory(loginResult),
196
+ });
184
197
  const target = await resolveInstallDoctorTarget(deps);
185
198
  if (!target.repoName) {
186
199
  checks.push({
@@ -163,9 +163,11 @@ export async function runInstallBridgeWithLatestCli(argv, deps = {}) {
163
163
  const errorLog = deps.errorLog ?? ((message) => console.error(message));
164
164
  const localVersion = deps.localVersion ?? VERSION;
165
165
  const { args: cleanedArgs, sentinelPresent } = stripInternalReexecSentinels(argv);
166
- // Terminating condition, and the nested-conductor bypass. Both skip the
167
- // registry entirely.
168
- if (sentinelPresent || cleanedArgs[0] === "conductor") {
166
+ // Terminating condition, the nested-conductor bypass, and `--dry-run`
167
+ // (BAPI-818, R2) all skip the registry entirely. A dry run's documented
168
+ // contract is "no network" — a version-freshness lookup is still a network
169
+ // call, so it cannot run first even though it is fail-open and unauthenticated.
170
+ if (sentinelPresent || cleanedArgs[0] === "conductor" || cleanedArgs.includes("--dry-run")) {
169
171
  return runLocal(cleanedArgs);
170
172
  }
171
173
  // Default the comparison target to the local version: an unusable registry
@@ -15,6 +15,7 @@
15
15
  * All filesystem access is through injected dependencies so the module is fully
16
16
  * unit-testable with in-memory fakes.
17
17
  */
18
+ import { executorIdFromLaunchdFilename, executorIdFromSystemdFilename, } from "./executor/service-unit.js";
18
19
  import { HOST_PLATFORM_ORDER, isHostPlatformId, } from "./mcp-host-targets.js";
19
20
  /** Current on-disk schema version. */
20
21
  export const MCP_INSTALL_STATE_VERSION = 1;
@@ -60,6 +61,74 @@ function normalizeProjectPaths(paths) {
60
61
  return out;
61
62
  }
62
63
  // ---------------------------------------------------------------------------
64
+ // Executor service-unit locators (BAPI-779)
65
+ // ---------------------------------------------------------------------------
66
+ /** Home-relative prefix of the darwin LaunchAgents convention. */
67
+ const EXECUTOR_LAUNCHD_LOCATOR_PREFIX = "~/Library/LaunchAgents/";
68
+ /** Home-relative prefix of the linux systemd `--user` convention. */
69
+ const EXECUTOR_SYSTEMD_LOCATOR_PREFIX = "~/.config/systemd/user/";
70
+ /**
71
+ * Accept ONLY the two generated-unit conventions, and only in home-relative
72
+ * form.
73
+ *
74
+ * Everything else is rejected: an arbitrary absolute path (`/tmp/evil.service`),
75
+ * a system-domain unit (`/etc/systemd/system/x.service`), a traversal segment,
76
+ * a wrong extension, or a filename the generator's own parsers do not recognize
77
+ * as one of its units. Install state is a diagnostic record of what THIS tool
78
+ * generated — letting it name any path on the filesystem would turn a
79
+ * secret-free inventory into an attacker-influenced pointer list.
80
+ */
81
+ function isGeneratedExecutorUnitLocator(value) {
82
+ if (typeof value !== "string" || value.length === 0)
83
+ return false;
84
+ if (value.includes("..") || value.includes("\\") || value.includes("\0"))
85
+ return false;
86
+ if (value.startsWith(EXECUTOR_LAUNCHD_LOCATOR_PREFIX)) {
87
+ const filename = value.slice(EXECUTOR_LAUNCHD_LOCATOR_PREFIX.length);
88
+ return filename.length > 0 && executorIdFromLaunchdFilename(filename) !== null;
89
+ }
90
+ if (value.startsWith(EXECUTOR_SYSTEMD_LOCATOR_PREFIX)) {
91
+ const filename = value.slice(EXECUTOR_SYSTEMD_LOCATOR_PREFIX.length);
92
+ return filename.length > 0 && executorIdFromSystemdFilename(filename) !== null;
93
+ }
94
+ return false;
95
+ }
96
+ /** Validate, dedupe, and stably order generated-unit locators. */
97
+ function normalizeExecutorServiceUnitPaths(paths) {
98
+ const seen = new Set();
99
+ const out = [];
100
+ for (const candidate of paths) {
101
+ if (typeof candidate !== "string")
102
+ continue;
103
+ if (!isGeneratedExecutorUnitLocator(candidate))
104
+ continue;
105
+ if (seen.has(candidate))
106
+ continue;
107
+ seen.add(candidate);
108
+ out.push(candidate);
109
+ }
110
+ out.sort();
111
+ return out;
112
+ }
113
+ /**
114
+ * Convert an absolute generated-unit path into its canonical home-relative
115
+ * locator, or `null` when it is not one of this tool's generated units.
116
+ *
117
+ * Returning `null` rather than a best-effort string is deliberate: a caller that
118
+ * cannot produce a canonical locator must record nothing, not record a path the
119
+ * schema would have rejected anyway.
120
+ */
121
+ export function canonicalExecutorServiceUnitLocator(unitPath, homeDir) {
122
+ if (typeof unitPath !== "string" || typeof homeDir !== "string" || homeDir.length === 0) {
123
+ return null;
124
+ }
125
+ const normalizedHome = homeDir.endsWith("/") ? homeDir.slice(0, -1) : homeDir;
126
+ if (!unitPath.startsWith(`${normalizedHome}/`))
127
+ return null;
128
+ const locator = `~/${unitPath.slice(normalizedHome.length + 1)}`;
129
+ return isGeneratedExecutorUnitLocator(locator) ? locator : null;
130
+ }
131
+ // ---------------------------------------------------------------------------
63
132
  // Read
64
133
  // ---------------------------------------------------------------------------
65
134
  function isEnoent(err) {
@@ -116,12 +185,24 @@ export async function readMcpInstallState(cwd, deps) {
116
185
  const projectConfigPaths = Array.isArray(rawPaths)
117
186
  ? rawPaths.filter((p) => typeof p === "string")
118
187
  : [];
188
+ // An ABSENT field is the ordinary legacy-document case and must read as an
189
+ // empty roster, not as an invalid document — every state file written before
190
+ // BAPI-779 lacks it. A present-but-wrong-type field is a real schema
191
+ // violation and is reported as such.
192
+ const rawUnitPaths = doc.executorServiceUnitPaths;
193
+ if (rawUnitPaths !== undefined && !Array.isArray(rawUnitPaths)) {
194
+ return { status: "invalid", reason: "executorServiceUnitPaths is not an array" };
195
+ }
119
196
  return {
120
197
  status: "valid",
121
198
  state: {
122
199
  version: MCP_INSTALL_STATE_VERSION,
123
200
  selectedPlatforms: normalizePlatforms(rawPlatforms),
124
201
  projectConfigPaths: normalizeProjectPaths(projectConfigPaths),
202
+ // Entries that are not generated-unit locators are DROPPED rather than
203
+ // failing the read: they cannot be acted on, and losing the whole
204
+ // document over one bad entry would take the platform roster with it.
205
+ executorServiceUnitPaths: normalizeExecutorServiceUnitPaths(Array.isArray(rawUnitPaths) ? rawUnitPaths : []),
125
206
  },
126
207
  };
127
208
  }
@@ -135,6 +216,10 @@ export function serializeMcpInstallState(state) {
135
216
  version: state.version,
136
217
  selectedPlatforms: state.selectedPlatforms,
137
218
  projectConfigPaths: state.projectConfigPaths,
219
+ // Re-normalized at serialization time, not trusted from the input object:
220
+ // this function is the last gate before bytes hit disk, and a caller could
221
+ // hand it a hand-built state carrying an arbitrary path.
222
+ executorServiceUnitPaths: normalizeExecutorServiceUnitPaths(state.executorServiceUnitPaths ?? []),
138
223
  };
139
224
  return JSON.stringify(ordered, null, 2) + "\n";
140
225
  }
@@ -146,10 +231,16 @@ export function serializeMcpInstallState(state) {
146
231
  * secret-free error without reporting success.
147
232
  */
148
233
  export async function writeMcpInstallState(cwd, input, deps) {
234
+ const existing = await readMcpInstallState(cwd, deps);
235
+ const priorUnitPaths = existing.status === "valid" ? existing.state.executorServiceUnitPaths : [];
149
236
  const state = {
150
237
  version: MCP_INSTALL_STATE_VERSION,
151
238
  selectedPlatforms: normalizePlatforms(input.selectedPlatforms),
152
239
  projectConfigPaths: normalizeProjectPaths(input.projectConfigPaths),
240
+ executorServiceUnitPaths: normalizeExecutorServiceUnitPaths([
241
+ ...priorUnitPaths,
242
+ ...(input.executorServiceUnitPaths ?? []),
243
+ ]),
153
244
  };
154
245
  const finalPath = installStatePath(cwd);
155
246
  return persistMcpInstallState(cwd, state, finalPath, deps);
@@ -198,10 +289,49 @@ export async function recordInstalledProjectArtifact(cwd, relPath, deps) {
198
289
  const existing = await readMcpInstallState(cwd, deps);
199
290
  const priorPlatforms = existing.status === "valid" ? existing.state.selectedPlatforms : [];
200
291
  const priorPaths = existing.status === "valid" ? existing.state.projectConfigPaths : [];
292
+ const priorUnitPaths = existing.status === "valid" ? existing.state.executorServiceUnitPaths : [];
201
293
  const state = {
202
294
  version: MCP_INSTALL_STATE_VERSION,
203
295
  selectedPlatforms: normalizePlatforms(priorPlatforms),
204
296
  projectConfigPaths: normalizeProjectPaths([...priorPaths, relPath]),
297
+ // Preserved, not rebuilt: recording a workflow file must not drop the
298
+ // executor units a previous conductor run recorded (the same additive rule
299
+ // that motivated this helper for the platform roster).
300
+ executorServiceUnitPaths: normalizeExecutorServiceUnitPaths(priorUnitPaths),
301
+ };
302
+ return persistMcpInstallState(cwd, state, installStatePath(cwd), deps);
303
+ }
304
+ /**
305
+ * Record a generated executor service unit WITHOUT clobbering existing state.
306
+ *
307
+ * Mirrors {@link recordInstalledProjectArtifact}: read the current document,
308
+ * write back the union of every dimension. The caller supplies the ABSOLUTE
309
+ * unit path the generator returned plus the home directory it was rendered
310
+ * against; this helper canonicalizes them into the home-relative locator the
311
+ * schema accepts, and refuses anything that is not one of the two generated
312
+ * conventions.
313
+ *
314
+ * No secret can reach the file: the locator is a fixed-shape path under two
315
+ * known directories, and the serializer emits only schema-approved keys.
316
+ */
317
+ export async function recordInstalledExecutorServiceUnit(cwd, unitPath, homeDir, deps) {
318
+ const locator = canonicalExecutorServiceUnitLocator(unitPath, homeDir);
319
+ if (locator === null) {
320
+ return {
321
+ ok: false,
322
+ error: "the unit path is not a generated executor service unit under the supported " +
323
+ "darwin LaunchAgents or linux systemd --user convention; nothing was recorded.",
324
+ };
325
+ }
326
+ const existing = await readMcpInstallState(cwd, deps);
327
+ const priorPlatforms = existing.status === "valid" ? existing.state.selectedPlatforms : [];
328
+ const priorPaths = existing.status === "valid" ? existing.state.projectConfigPaths : [];
329
+ const priorUnitPaths = existing.status === "valid" ? existing.state.executorServiceUnitPaths : [];
330
+ const state = {
331
+ version: MCP_INSTALL_STATE_VERSION,
332
+ selectedPlatforms: normalizePlatforms(priorPlatforms),
333
+ projectConfigPaths: normalizeProjectPaths(priorPaths),
334
+ executorServiceUnitPaths: normalizeExecutorServiceUnitPaths([...priorUnitPaths, locator]),
205
335
  };
206
336
  return persistMcpInstallState(cwd, state, installStatePath(cwd), deps);
207
337
  }
@@ -14,16 +14,19 @@ const VALID_GROUPS = new Set([
14
14
  "conductor",
15
15
  "pipeline-authoring",
16
16
  "sfcc",
17
+ "sfcc-write",
17
18
  ]);
18
19
  /**
19
20
  * Resolve the active MCP groups from a raw env-var string.
20
21
  *
21
22
  * - `undefined` or blank → `{"core"}` (fail-safe to smallest surface).
22
- * - `"full"` (any case) → all four groups.
23
+ * - `"full"` (any case) → all five groups, `sfcc-write` included.
23
24
  * - Comma-separated list: each token is trimmed, lowercased, and checked
24
25
  * against VALID_GROUPS. Unknown tokens are dropped. Known tokens are unioned
25
26
  * with the implicit `"core"` baseline.
26
27
  * - All-unknown tokens → `{"core"}`.
28
+ * - No group implies another beyond the `"core"` baseline — in particular
29
+ * `sfcc` and `sfcc-write` are independent in both directions (BAPI-817).
27
30
  * - Raw input is never echoed in the output (security NFR).
28
31
  */
29
32
  export function resolveProfiles(raw) {
@@ -35,7 +38,13 @@ export function resolveProfiles(raw) {
35
38
  return baseline;
36
39
  const tokens = trimmed.split(",").map((t) => t.trim().toLowerCase());
37
40
  if (tokens.some((t) => t === "full")) {
38
- return new Set(["core", "conductor", "pipeline-authoring", "sfcc"]);
41
+ return new Set([
42
+ "core",
43
+ "conductor",
44
+ "pipeline-authoring",
45
+ "sfcc",
46
+ "sfcc-write",
47
+ ]);
39
48
  }
40
49
  for (const token of tokens) {
41
50
  if (VALID_GROUPS.has(token)) {
@@ -307,6 +307,21 @@ function withWarnings(row, warnings) {
307
307
  /**
308
308
  * Provision MCP registrations for one created worktree row.
309
309
  *
310
+ * FAIL-OPEN, and BAPI-790 makes that posture worth stating explicitly, because a
311
+ * fail-CLOSED requirement now sits directly downstream of it. This function
312
+ * ATTEMPTS the write and degrades to warnings; the executor then separately
313
+ * VERIFIES that a usable `.mcp.json` exists and registers `bridge-api`, and
314
+ * refuses to spawn when it does not (`verifyRequiredWorktreeMcpRegistration` in
315
+ * `executor/job-runner.ts`). The split is deliberate: a provisioning hiccup on a
316
+ * Tier-2 target must not kill a job, but a worker that loads MCP servers strictly
317
+ * from this file must not start if the file cannot serve that role. Keep the two
318
+ * postures distinct — folding the requirement in here would put it behind this
319
+ * function's fail-open catch and silently disarm it.
320
+ *
321
+ * The same file is also the source of the `--mcp-config` path and of the expected
322
+ * server-name set the per-spawn init-event assertion checks, so what this writes
323
+ * is exactly what a worker is later held to.
324
+ *
310
325
  * - Non-`created` rows and rows without a path are returned unchanged.
311
326
  * - A missing / malformed manifest yields a non-fatal warning and leaves the row
312
327
  * status unchanged.