@bridge_gpt/mcp-server 0.2.65 → 0.2.66
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 +25 -13
- package/build/agent-launchers/claude-executor-adapter.js +12 -18
- package/build/agent-launchers/executor-adapter.js +27 -0
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +230 -10
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +12 -0
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness.js +23 -1
- package/build/conductor/recovery-cli.js +507 -16
- package/build/conductor/recovery-operations.js +325 -2
- package/build/conductor/repair-contract.js +61 -3
- package/build/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +70 -10
- package/build/drive-epic.js +395 -78
- package/build/epic-implementer-bin.js +24 -22
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/completion-supervision.js +5 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +6 -0
- package/build/executor/http-client.js +15 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +13 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +260 -33
- package/build/executor/job-types.js +8 -13
- package/build/executor/preflight.js +91 -2
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/runner.js +23 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/types.js +1 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +43 -43
- package/build/executor/worker-finalization.js +80 -15
- package/build/executor/worker-guard-hook.js +46 -10
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/index.js +177 -329
- package/build/init.js +107 -4
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +1 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +4 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +180 -108
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +4 -3
- package/build/worktree-core.js +6 -5
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +1 -1
- package/pipelines/implement-ticket.json +1 -1
package/build/init.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* effects and depends only on leaf modules.
|
|
11
11
|
*/
|
|
12
12
|
import { writeFile, mkdir, readFile, stat } from "fs/promises";
|
|
13
|
+
import { execFile } from "child_process";
|
|
13
14
|
import path from "path";
|
|
14
15
|
import os from "os";
|
|
15
16
|
import { COMMANDS } from "./commands.generated.js";
|
|
@@ -19,7 +20,7 @@ import { AGENTS } from "./agents.generated.js";
|
|
|
19
20
|
import { DOCS } from "./docs.generated.js";
|
|
20
21
|
import { VERSION, LAUNCHER_ARGS } from "./version.generated.js";
|
|
21
22
|
import { reconstructAgentMarkdown, translateAgentToCopilot, translateCommandToCopilot, } from "./agent-utils.js";
|
|
22
|
-
import { validateRepoName, resolveRepoNameForProjectRoot } from "./bridge-config.js";
|
|
23
|
+
import { BRIDGE_CONFIG_TEAMMATE_NOTICE_LINES, validateBridgeConfigRepoNameRepresentability, validateRepoName, resolveRepoNameForProjectRoot, } from "./bridge-config.js";
|
|
23
24
|
import { ensureGitignored as ensureGitignoredShared } from "./git-ignore-utils.js";
|
|
24
25
|
import { resolveBapiCredentials } from "./credential-store.js";
|
|
25
26
|
import { describeLauncherReason, DUPLICATE_REGISTRATION_GUIDANCE, inspectLauncherConfigs, isPrerequisiteFailure, } from "./launcher-config-inspection.js";
|
|
@@ -133,6 +134,8 @@ export function buildBridgeConfigManifest(repoName) {
|
|
|
133
134
|
"# Inherited by every git worktree; start-tickets worktree provisioning reads it",
|
|
134
135
|
"# to decide which Bridge API MCP registrations to write. Do not add credentials,",
|
|
135
136
|
"# connection URLs, or machine-specific paths here — those are resolved at runtime.",
|
|
137
|
+
"#",
|
|
138
|
+
...BRIDGE_CONFIG_TEAMMATE_NOTICE_LINES,
|
|
136
139
|
"",
|
|
137
140
|
`repo_name = "${repoName}"`,
|
|
138
141
|
"",
|
|
@@ -389,6 +392,65 @@ export class InitPreflightError extends Error {
|
|
|
389
392
|
this.failures = failures;
|
|
390
393
|
}
|
|
391
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Classify `git ls-files --error-unmatch -- <relPath>` from its exit code: 0 is
|
|
397
|
+
* tracked, 1 is untracked, anything else is undetermined. Shared by `runInit`
|
|
398
|
+
* and install so both reach the same verdict from the same probe.
|
|
399
|
+
*/
|
|
400
|
+
export function classifyTrackedProbeExitCode(exitCode) {
|
|
401
|
+
if (exitCode === 0)
|
|
402
|
+
return "tracked";
|
|
403
|
+
if (exitCode === 1)
|
|
404
|
+
return "untracked";
|
|
405
|
+
return "undetermined";
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Default tracked-config probe for `runInit`: runs git with an ARGUMENT LIST,
|
|
409
|
+
* never a shell string, in `cwd`. Any failure is `undetermined`, which callers
|
|
410
|
+
* treat as untracked — the probe fails OPEN, so a missing git or a non-repo
|
|
411
|
+
* folder still gets today's `BAPI_PROJECT_ROOT = cwd` rewrite.
|
|
412
|
+
*/
|
|
413
|
+
export function defaultIsTrackedConfig(cwd) {
|
|
414
|
+
return (relPath) => new Promise((resolve) => {
|
|
415
|
+
try {
|
|
416
|
+
execFile("git", ["ls-files", "--error-unmatch", "--", relPath], { cwd, windowsHide: true, timeout: 10_000 }, (error) => {
|
|
417
|
+
if (!error)
|
|
418
|
+
return resolve("tracked");
|
|
419
|
+
const code = error.code;
|
|
420
|
+
resolve(classifyTrackedProbeExitCode(typeof code === "number" ? code : null));
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
resolve("undetermined");
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Decide the `BAPI_PROJECT_ROOT` an EXISTING Bridge entry should carry (BAPI-1318).
|
|
430
|
+
* A git-tracked config keeps its existing non-blank value exactly: the runtime
|
|
431
|
+
* check in `project-root.ts` makes a committed path harmless for teammates, and
|
|
432
|
+
* rewriting it would put this machine's path into everyone's diff. Untracked and
|
|
433
|
+
* undetermined configs, and blank or absent values, get `cwd` as before.
|
|
434
|
+
*
|
|
435
|
+
* `warnReplaced` is true only when the probe was undetermined AND a different,
|
|
436
|
+
* non-blank value is being replaced — the one case where a tracked path may have
|
|
437
|
+
* been rewritten without the protection applying.
|
|
438
|
+
*/
|
|
439
|
+
export function decideExistingProjectRoot(existing, cwd, tracked) {
|
|
440
|
+
const current = typeof existing === "string" ? existing : "";
|
|
441
|
+
const blank = current.trim().length === 0;
|
|
442
|
+
if (tracked === "tracked" && !blank)
|
|
443
|
+
return { value: current, warnReplaced: false };
|
|
444
|
+
return {
|
|
445
|
+
value: cwd,
|
|
446
|
+
warnReplaced: tracked === "undetermined" && !blank && current !== cwd,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
/** The one secret-safe warning for an undetermined probe that replaced a root. */
|
|
450
|
+
export function formatUndeterminedRootReplacedWarning(relPath) {
|
|
451
|
+
return (` Warning: could not tell whether ${relPath} is tracked by git, so its BAPI_PROJECT_ROOT ` +
|
|
452
|
+
"was rewritten to this folder. Review its diff before committing.");
|
|
453
|
+
}
|
|
392
454
|
/**
|
|
393
455
|
* Resolve the non-secret identity a NEW Bridge entry should carry.
|
|
394
456
|
*
|
|
@@ -518,6 +580,20 @@ export async function runInit(cwd, options = {}) {
|
|
|
518
580
|
const targetSpec = `${MCP_PACKAGE_NAME}@${targetVersion}`;
|
|
519
581
|
const say = options.stdout ?? ((m) => console.log(m));
|
|
520
582
|
const warn = options.stderr ?? ((m) => console.error(m));
|
|
583
|
+
const trackedProbe = options.isTrackedConfig ?? defaultIsTrackedConfig(cwd);
|
|
584
|
+
const probeTracked = async (relPath) => {
|
|
585
|
+
try {
|
|
586
|
+
const verdict = await trackedProbe(relPath);
|
|
587
|
+
if (verdict === true)
|
|
588
|
+
return "tracked";
|
|
589
|
+
if (verdict === false)
|
|
590
|
+
return "untracked";
|
|
591
|
+
return verdict === "tracked" || verdict === "untracked" ? verdict : "undetermined";
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
return "undetermined";
|
|
595
|
+
}
|
|
596
|
+
};
|
|
521
597
|
// ---- Phase 1: IDE Detection ----
|
|
522
598
|
// BAPI-708: detection lives in `detectInitIdes` so the commit-safe asset
|
|
523
599
|
// manifest resolves the SAME conditional targets this scaffolder writes.
|
|
@@ -701,7 +777,17 @@ export async function runInit(cwd, options = {}) {
|
|
|
701
777
|
entry.args = refreshed;
|
|
702
778
|
if (!entry.env)
|
|
703
779
|
entry.env = {};
|
|
704
|
-
|
|
780
|
+
// BAPI-1318: a git-tracked config keeps its committed BAPI_PROJECT_ROOT.
|
|
781
|
+
// The probe runs only when there is a non-blank value to preserve.
|
|
782
|
+
const existingRoot = entry.env.BAPI_PROJECT_ROOT;
|
|
783
|
+
let trackedVerdict = "untracked";
|
|
784
|
+
if (typeof existingRoot === "string" && existingRoot.trim().length > 0 && existingRoot !== cwd) {
|
|
785
|
+
trackedVerdict = await probeTracked(target.path);
|
|
786
|
+
}
|
|
787
|
+
const rootDecision = decideExistingProjectRoot(existingRoot, cwd, trackedVerdict);
|
|
788
|
+
entry.env.BAPI_PROJECT_ROOT = rootDecision.value;
|
|
789
|
+
if (rootDecision.warnReplaced)
|
|
790
|
+
warn(formatUndeterminedRootReplacedWarning(target.path));
|
|
705
791
|
await io.writeFile(fullPath, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
706
792
|
const kind = found.action === "already-current"
|
|
707
793
|
? "already-current"
|
|
@@ -1094,9 +1180,24 @@ says to serialize structured output.
|
|
|
1094
1180
|
// the credential landed and which generated assets are safe to commit. Removed
|
|
1095
1181
|
// outright rather than behind an output-mode flag — parameterizing would change
|
|
1096
1182
|
// the injected `deps.runInit` seam signature for no user-visible gain.
|
|
1183
|
+
// BAPI-1318: a caller that already resolved the project name (install) is
|
|
1184
|
+
// authoritative, so the manifest agrees with the MCP entry. `--init` and
|
|
1185
|
+
// `upgrade` pass no creation context and keep the folder-name default.
|
|
1186
|
+
let bridgeConfigCreated = false;
|
|
1187
|
+
let bridgeConfigRepoName;
|
|
1097
1188
|
if (!bridgeConfigExists) {
|
|
1098
|
-
|
|
1099
|
-
|
|
1189
|
+
const scaffoldName = options.creationContext?.repoName ?? chooseScaffoldRepoName(cwd);
|
|
1190
|
+
const representable = validateBridgeConfigRepoNameRepresentability(scaffoldName);
|
|
1191
|
+
if (representable.ok) {
|
|
1192
|
+
await mkdir(path.dirname(bridgeConfigPath), { recursive: true });
|
|
1193
|
+
await writeFile(bridgeConfigPath, buildBridgeConfigManifest(representable.value), "utf-8");
|
|
1194
|
+
bridgeConfigCreated = true;
|
|
1195
|
+
bridgeConfigRepoName = representable.value;
|
|
1196
|
+
}
|
|
1197
|
+
else {
|
|
1198
|
+
warn(" Warning: .bridge/config was not created because this project name cannot be written " +
|
|
1199
|
+
`there (${representable.error}). Worktrees and shell tools will fall back to the git folder name.`);
|
|
1200
|
+
}
|
|
1100
1201
|
}
|
|
1101
1202
|
// BAPI-728: authoritative, secret-free outcome. `ok` is false whenever any
|
|
1102
1203
|
// entry authenticates as nobody, so no caller can report full success over a
|
|
@@ -1106,5 +1207,7 @@ says to serialize structured output.
|
|
|
1106
1207
|
configActions: structuredActions,
|
|
1107
1208
|
readinessWarnings,
|
|
1108
1209
|
targetVersion,
|
|
1210
|
+
bridgeConfigCreated,
|
|
1211
|
+
...(bridgeConfigRepoName !== undefined ? { bridgeConfigRepoName } : {}),
|
|
1109
1212
|
};
|
|
1110
1213
|
}
|
package/build/install-bridge.js
CHANGED
|
@@ -143,10 +143,11 @@ import { randomBytes as cryptoRandomBytes, createHash } from "crypto";
|
|
|
143
143
|
import os from "os";
|
|
144
144
|
import path from "path";
|
|
145
145
|
import readline from "readline";
|
|
146
|
-
import { runInit, InitPreflightError, buildBridgeApiEntry, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, } from "./init.js";
|
|
146
|
+
import { runInit, InitPreflightError, buildBridgeApiEntry, buildBridgeConfigManifest, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, classifyTrackedProbeExitCode, decideExistingProjectRoot, formatUndeterminedRootReplacedWarning, } from "./init.js";
|
|
147
147
|
import { hasProjectRootMarker as sharedHasProjectRootMarker } from "./project-root.js";
|
|
148
148
|
import { VERSION } from "./version.generated.js";
|
|
149
|
-
import { validateRepoName } from "./bridge-config.js";
|
|
149
|
+
import { bridgeConfigPath, parseBridgeConfigToml, rewriteBridgeConfigRepoName, validateBridgeConfigRepoNameRepresentability, validateRepoName, } from "./bridge-config.js";
|
|
150
|
+
import { renderDiagnosticValue } from "./diagnostic-text.js";
|
|
150
151
|
import { MCP_HOST_TARGETS, HOST_PLATFORM_ORDER, allHostTargets, agentForPlatform, isHostPlatformId, detectDefaultPlatforms, hostAdapterForTarget, } from "./mcp-host-targets.js";
|
|
151
152
|
import { provisionHostTarget, createDefaultVendorProcessDeps, inspectBridgeApiProfileToken, } from "./mcp-host-config.js";
|
|
152
153
|
import { adaptBridgeEntryForHostTarget, } from "./mcp-host-entry-adapter.js";
|
|
@@ -190,6 +191,8 @@ import { fetchBitbucketConfigurationState } from "./connect-bitbucket-api.js";
|
|
|
190
191
|
import { createDefaultConnectBitbucketDeps, runBitbucketConnectionFlow } from "./connect-bitbucket.js";
|
|
191
192
|
import { resolveAgentSpec, isAgentName, formatValidAgentNames, resolveModelAlias, } from "./agent-registry.js";
|
|
192
193
|
import { buildGenericAgentShellCommand, getDefaultSpawnTerminalTabForPlatform, detectTerminal, createDefaultStartTicketsDeps, materializeWorkerLaunchCommand, MAX_TERMINAL_COMMAND_BYTES, validateResolvedModelAliasForAgent, } from "./start-tickets.js";
|
|
194
|
+
import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
|
|
195
|
+
import { macosTerminalPermissionGuidance } from "./macos-terminal-permission-guidance.js";
|
|
193
196
|
/** Redaction sentinel — the API-key value is NEVER printed; this stands in. */
|
|
194
197
|
export const REDACTED_API_KEY = "<REDACTED>";
|
|
195
198
|
/**
|
|
@@ -1261,11 +1264,32 @@ export function sanitizePrewarmEnv(env) {
|
|
|
1261
1264
|
delete sanitized[INSTALL_REEXEC_KEY_SOURCE_ENV];
|
|
1262
1265
|
return sanitized;
|
|
1263
1266
|
}
|
|
1264
|
-
function spawnPrewarmDefault(command, args, env) {
|
|
1267
|
+
export function spawnPrewarmDefault(command, args, env, options = {}) {
|
|
1268
|
+
const doSpawn = options.spawn ?? spawn;
|
|
1265
1269
|
return new Promise((resolve) => {
|
|
1266
1270
|
const sanitizedEnv = sanitizePrewarmEnv(env);
|
|
1271
|
+
// WINDOWS (BAPI-1319): a bare `npx` is ENOENT without a shell, so the
|
|
1272
|
+
// pre-warm never ran. Map it to npm's `npx-cli.js` through Node, resolved
|
|
1273
|
+
// against the SANITIZED env the child actually receives. Other platforms
|
|
1274
|
+
// pass through unchanged.
|
|
1275
|
+
let spawnCommand = command;
|
|
1276
|
+
let spawnArgs = args;
|
|
1277
|
+
if (command === "npx") {
|
|
1278
|
+
const resolved = resolveNpmEntry("npx", args, {
|
|
1279
|
+
platform: options.platform ?? process.platform,
|
|
1280
|
+
env: sanitizedEnv,
|
|
1281
|
+
execPath: options.execPath ?? process.execPath,
|
|
1282
|
+
fileExists: options.fileExists ?? defaultNpmEntryFileExists,
|
|
1283
|
+
});
|
|
1284
|
+
if (resolved.status !== "resolved") {
|
|
1285
|
+
resolve({ ok: false, warning: "the pre-warm process could not be started" });
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
spawnCommand = resolved.command;
|
|
1289
|
+
spawnArgs = resolved.args;
|
|
1290
|
+
}
|
|
1267
1291
|
try {
|
|
1268
|
-
const child =
|
|
1292
|
+
const child = doSpawn(spawnCommand, spawnArgs, {
|
|
1269
1293
|
shell: false,
|
|
1270
1294
|
stdio: "ignore",
|
|
1271
1295
|
timeout: 60_000,
|
|
@@ -1929,7 +1953,10 @@ export async function resolveInviteToken(options, deps) {
|
|
|
1929
1953
|
const INSTALL_BRIDGE_SIGNUP_EMAIL_SHAPE = /^[^@\s]+@[^@\s]+$/;
|
|
1930
1954
|
/** The one hint line printed after a blank/malformed email, before the single re-prompt. */
|
|
1931
1955
|
export const INSTALL_BRIDGE_SIGNUP_EMAIL_HINT = "Enter a valid email address (e.g. you@example.com).";
|
|
1932
|
-
export
|
|
1956
|
+
export function buildInstallBridgeSignupPrivacyNotice(baseUrl) {
|
|
1957
|
+
return `Privacy Policy: ${baseUrl.replace(/\/+$/, "")}/privacy`;
|
|
1958
|
+
}
|
|
1959
|
+
export async function resolveSignupEmail(options, deps, baseUrl = DEFAULT_BAPI_BASE_URL) {
|
|
1933
1960
|
// BAPI-757 (R-3 / AC-9): self-serve signup now REQUIRES an interactive terminal,
|
|
1934
1961
|
// and the check runs FIRST — before `--email` and before `BAPI_SIGNUP_EMAIL` —
|
|
1935
1962
|
// so an unattended run fails before any request is made, no verification email
|
|
@@ -1940,6 +1967,7 @@ export async function resolveSignupEmail(options, deps) {
|
|
|
1940
1967
|
if (!deps.isTTY || !deps.promptLine) {
|
|
1941
1968
|
return { ok: false, error: INSTALL_BRIDGE_SIGNUP_REQUIRES_TTY };
|
|
1942
1969
|
}
|
|
1970
|
+
deps.log(buildInstallBridgeSignupPrivacyNotice(baseUrl));
|
|
1943
1971
|
if (typeof options.email === "string" && options.email.trim().length > 0) {
|
|
1944
1972
|
return { ok: true, value: options.email.trim() };
|
|
1945
1973
|
}
|
|
@@ -2987,23 +3015,25 @@ function trackedConfigWarning(relPath) {
|
|
|
2987
3015
|
* Read-only probe: is `relPath` already tracked by git in the project (BAPI-666)?
|
|
2988
3016
|
* Runs `git ls-files --error-unmatch -- <relPath>` through the injected, list-based
|
|
2989
3017
|
* command seam (never a shell string) with `cwd = deps.cwd`. ONLY exit code 0 means
|
|
2990
|
-
* tracked; every other outcome —
|
|
2991
|
-
* directory, or a thrown command failure — is
|
|
2992
|
-
*
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
3018
|
+
* tracked; exit 1 is untracked, and every other outcome — another exit code (a
|
|
3019
|
+
* non-git directory), or a thrown command failure — is `undetermined` (BAPI-1318).
|
|
3020
|
+
* Every caller treats `undetermined` as NOT tracked, preserving the required
|
|
3021
|
+
* fail-open behavior (a probe failure must never block the install). Emits at most a
|
|
3022
|
+
* fixed debug-level line (safe relative path + exit classification) and never the
|
|
3023
|
+
* command's stdout/stderr, exception text, env, or config content.
|
|
2995
3024
|
*/
|
|
2996
3025
|
async function isTrackedProjectConfig(deps, relPath) {
|
|
2997
3026
|
try {
|
|
2998
3027
|
const result = await deps.startTicketsDeps.runCommand("git", ["ls-files", "--error-unmatch", "--", relPath], { cwd: deps.cwd });
|
|
2999
|
-
|
|
3000
|
-
|
|
3028
|
+
const verdict = classifyTrackedProbeExitCode(result.exitCode);
|
|
3029
|
+
if (verdict === "tracked")
|
|
3030
|
+
return verdict;
|
|
3001
3031
|
deps.debugLog(`install-bridge: git-tracked probe for ${relPath} → not tracked (exit ${result.exitCode})`);
|
|
3002
|
-
return
|
|
3032
|
+
return verdict;
|
|
3003
3033
|
}
|
|
3004
3034
|
catch {
|
|
3005
3035
|
deps.debugLog(`install-bridge: git-tracked probe for ${relPath} → not tracked (probe failed)`);
|
|
3006
|
-
return
|
|
3036
|
+
return "undetermined";
|
|
3007
3037
|
}
|
|
3008
3038
|
}
|
|
3009
3039
|
/**
|
|
@@ -3038,12 +3068,24 @@ async function requestTrackedConfigConsent(deps, relPath) {
|
|
|
3038
3068
|
* cannot recognize — the new template args (which DO include `serve`) are used
|
|
3039
3069
|
* verbatim. `command` and `env` always come from the freshly built entry: this
|
|
3040
3070
|
* helper governs launcher-token migration only, never credentials.
|
|
3071
|
+
*
|
|
3072
|
+
* BAPI-1318: the one exception is `BAPI_PROJECT_ROOT`. With `preserveProjectRoot`
|
|
3073
|
+
* set (the config is git-tracked), an existing non-blank value is kept exactly
|
|
3074
|
+
* rather than replaced by this machine's `cwd`; a blank or absent value still
|
|
3075
|
+
* takes the fresh entry's root.
|
|
3041
3076
|
*/
|
|
3042
|
-
export function preserveExistingLauncherArgs(existing, entry) {
|
|
3077
|
+
export function preserveExistingLauncherArgs(existing, entry, options = {}) {
|
|
3043
3078
|
const existingEntry = asRecord(existing) ?? {};
|
|
3044
3079
|
const existingArgs = asRecord(existing)?.args;
|
|
3045
3080
|
const preserved = refreshBridgeApiPackageSpec(existingArgs, currentBridgePackageSpec());
|
|
3046
|
-
|
|
3081
|
+
const merged = { ...existingEntry, ...entry, ...(preserved ? { args: preserved } : {}) };
|
|
3082
|
+
if (options.preserveProjectRoot) {
|
|
3083
|
+
const existingRoot = asRecord(existingEntry.env)?.BAPI_PROJECT_ROOT;
|
|
3084
|
+
if (typeof existingRoot === "string" && existingRoot.trim().length > 0) {
|
|
3085
|
+
merged.env = { ...merged.env, BAPI_PROJECT_ROOT: existingRoot };
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
return merged;
|
|
3047
3089
|
}
|
|
3048
3090
|
/**
|
|
3049
3091
|
* Read-only pre-scan for both-key configs across EVERY project target (BAPI-807).
|
|
@@ -3107,7 +3149,8 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
|
|
|
3107
3149
|
}
|
|
3108
3150
|
// Choose the entry variant. A real key only lands in an untracked target, or a
|
|
3109
3151
|
// tracked target the user EXPLICITLY consented to on a TTY.
|
|
3110
|
-
const
|
|
3152
|
+
const trackedVerdict = trackedState.get(target.relPath) ?? "untracked";
|
|
3153
|
+
const tracked = trackedVerdict === "tracked";
|
|
3111
3154
|
let entry = entries.real;
|
|
3112
3155
|
let mode = "real-key";
|
|
3113
3156
|
if (tracked) {
|
|
@@ -3154,7 +3197,18 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
|
|
|
3154
3197
|
// real-key and secret-free variants: credential safety must not decide whether
|
|
3155
3198
|
// a launcher is migrated.
|
|
3156
3199
|
const physicalEntry = adaptBridgeEntryForHostTarget(entry, target.target);
|
|
3157
|
-
|
|
3200
|
+
// BAPI-1318: the tracked-root rule matches runInit's, for BOTH variants: a
|
|
3201
|
+
// tracked config keeps its committed non-blank BAPI_PROJECT_ROOT. An
|
|
3202
|
+
// undetermined probe fails open (cwd is written) and warns once when that
|
|
3203
|
+
// replaces a different existing value.
|
|
3204
|
+
const existingRoot = asRecord(asRecord(topLevel[registrationKey])?.env)?.BAPI_PROJECT_ROOT;
|
|
3205
|
+
const rootDecision = decideExistingProjectRoot(existingRoot, deps.cwd, trackedVerdict);
|
|
3206
|
+
if (asRecord(topLevel[registrationKey]) && rootDecision.warnReplaced) {
|
|
3207
|
+
deps.errorLog(formatUndeterminedRootReplacedWarning(target.relPath));
|
|
3208
|
+
}
|
|
3209
|
+
topLevel[registrationKey] = preserveExistingLauncherArgs(topLevel[registrationKey], physicalEntry, {
|
|
3210
|
+
preserveProjectRoot: tracked,
|
|
3211
|
+
});
|
|
3158
3212
|
config[target.topLevelKey] = topLevel;
|
|
3159
3213
|
await deps.mkdir(path.dirname(fullPath), { recursive: true });
|
|
3160
3214
|
await deps.writeFile(fullPath, JSON.stringify(config, null, 2) + "\n", {
|
|
@@ -3164,6 +3218,160 @@ async function writeHostConfigs(deps, targets, entries, trackedState, ctx) {
|
|
|
3164
3218
|
}
|
|
3165
3219
|
return { written, skipped, duplicateRegistrations: [] };
|
|
3166
3220
|
}
|
|
3221
|
+
// ---------------------------------------------------------------------------
|
|
3222
|
+
// BAPI-1318: `.bridge/config` final-name reconciliation and consented repair
|
|
3223
|
+
// ---------------------------------------------------------------------------
|
|
3224
|
+
/** The doctor command every `.bridge/config` repair warning points at. */
|
|
3225
|
+
const BRIDGE_CONFIG_DOCTOR_HINT = `Then run "npx -y ${MCP_PACKAGE_NAME} doctor" to confirm.`;
|
|
3226
|
+
/** Mismatch warning with the exact fix line (non-TTY or declined repair). */
|
|
3227
|
+
export function formatBridgeConfigMismatchWarning(fileName, provenName) {
|
|
3228
|
+
const file = renderDiagnosticValue(fileName);
|
|
3229
|
+
const proven = renderDiagnosticValue(provenName);
|
|
3230
|
+
return (`.bridge/config names '${file}' but this project is '${proven}'. Worktrees and shell tools ` +
|
|
3231
|
+
`will use the wrong project. Fix: set repo_name = "${proven}" in .bridge/config. ` +
|
|
3232
|
+
BRIDGE_CONFIG_DOCTOR_HINT);
|
|
3233
|
+
}
|
|
3234
|
+
/** Warning for a proven name the manifest's TOML subset cannot hold: no fix line. */
|
|
3235
|
+
export function formatBridgeConfigUnrepresentableWarning(fileName, provenName) {
|
|
3236
|
+
const proven = renderDiagnosticValue(provenName);
|
|
3237
|
+
const lead = fileName === null
|
|
3238
|
+
? `.bridge/config could not be updated to '${proven}'`
|
|
3239
|
+
: `.bridge/config names '${renderDiagnosticValue(fileName)}' but this project is '${proven}'`;
|
|
3240
|
+
return (`${lead}, and that name cannot be written in .bridge/config (quotes and backslashes are not ` +
|
|
3241
|
+
`supported). Automatic repair is unavailable. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
|
|
3242
|
+
}
|
|
3243
|
+
/** Warning for a `.bridge/config` that does not parse. */
|
|
3244
|
+
export function formatBridgeConfigMalformedWarning() {
|
|
3245
|
+
return (".bridge/config could not be parsed, so its project name was not checked or changed. " +
|
|
3246
|
+
`Fix the file by hand. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
|
|
3247
|
+
}
|
|
3248
|
+
/** Warning for a read, prompt, or write failure during the advisory repair. */
|
|
3249
|
+
export function formatBridgeConfigRepairFailedWarning() {
|
|
3250
|
+
return (".bridge/config could not be checked or updated (a read or write failed); it was left " +
|
|
3251
|
+
`unchanged. Check its repo_name by hand. ${BRIDGE_CONFIG_DOCTOR_HINT}`);
|
|
3252
|
+
}
|
|
3253
|
+
/**
|
|
3254
|
+
* Replace `.bridge/config` atomically: write a sibling temp file with the
|
|
3255
|
+
* original file mode, then rename it over the target. A failure removes the temp
|
|
3256
|
+
* file (best effort) and rethrows so the advisory caller can warn.
|
|
3257
|
+
*/
|
|
3258
|
+
async function writeBridgeConfigAtomically(deps, configPath, text) {
|
|
3259
|
+
let mode;
|
|
3260
|
+
try {
|
|
3261
|
+
const info = (await deps.stat(configPath));
|
|
3262
|
+
if (typeof info?.mode === "number")
|
|
3263
|
+
mode = info.mode & 0o777;
|
|
3264
|
+
}
|
|
3265
|
+
catch {
|
|
3266
|
+
mode = undefined;
|
|
3267
|
+
}
|
|
3268
|
+
const tempPath = `${configPath}.${deps.randomBytes(6).toString("hex")}.tmp`;
|
|
3269
|
+
try {
|
|
3270
|
+
await deps.writeFile(tempPath, text, mode !== undefined ? { encoding: "utf-8", mode } : { encoding: "utf-8" });
|
|
3271
|
+
if (mode !== undefined)
|
|
3272
|
+
await deps.chmod(tempPath, mode);
|
|
3273
|
+
await deps.rename(tempPath, configPath);
|
|
3274
|
+
}
|
|
3275
|
+
catch (error) {
|
|
3276
|
+
try {
|
|
3277
|
+
await deps.unlink(tempPath);
|
|
3278
|
+
}
|
|
3279
|
+
catch {
|
|
3280
|
+
/* best-effort temp cleanup only; the original file is untouched */
|
|
3281
|
+
}
|
|
3282
|
+
throw error;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
/**
|
|
3286
|
+
* After the ping has PROVEN `provenName`, make `<cwd>/.bridge/config` agree with
|
|
3287
|
+
* it (BAPI-1318). Only the install folder's own file is read — never a parent's,
|
|
3288
|
+
* so a monorepo subfolder that is its own Bridge project stays safe.
|
|
3289
|
+
*
|
|
3290
|
+
* - `createdText` is the exact text THIS run scaffolded (null when it did not
|
|
3291
|
+
* create the file). If the file still holds exactly that text, it is this
|
|
3292
|
+
* run's own output: a later rename (409 loop or invite exchange) is written
|
|
3293
|
+
* through without a prompt, with one line saying so.
|
|
3294
|
+
* - Otherwise the file is user-owned. A differing name is repaired only after a
|
|
3295
|
+
* default-No TTY consent; a non-TTY run or a "no" leaves it byte-identical and
|
|
3296
|
+
* warns with the exact fix.
|
|
3297
|
+
*
|
|
3298
|
+
* Advisory by contract: every failure warns and returns; nothing here can fail
|
|
3299
|
+
* the install. Warnings name only the two project names, never file content.
|
|
3300
|
+
*/
|
|
3301
|
+
export async function reconcileBridgeConfigAfterPing(deps, provenName, createdText) {
|
|
3302
|
+
const configPath = bridgeConfigPath(deps.cwd);
|
|
3303
|
+
try {
|
|
3304
|
+
let text;
|
|
3305
|
+
try {
|
|
3306
|
+
text = await deps.readFile(configPath);
|
|
3307
|
+
}
|
|
3308
|
+
catch (error) {
|
|
3309
|
+
if (error?.code === "ENOENT")
|
|
3310
|
+
return;
|
|
3311
|
+
deps.errorLog(formatBridgeConfigRepairFailedWarning());
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
const parsed = parseBridgeConfigToml(text);
|
|
3315
|
+
if (!parsed.ok) {
|
|
3316
|
+
deps.errorLog(formatBridgeConfigMalformedWarning());
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
const fileName = parsed.manifest.repoName;
|
|
3320
|
+
if (fileName === provenName)
|
|
3321
|
+
return;
|
|
3322
|
+
const representable = validateBridgeConfigRepoNameRepresentability(provenName);
|
|
3323
|
+
if (!representable.ok) {
|
|
3324
|
+
deps.errorLog(formatBridgeConfigUnrepresentableWarning(fileName, provenName));
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
const ownFile = createdText !== null && text === createdText;
|
|
3328
|
+
if (!ownFile) {
|
|
3329
|
+
if (!deps.isTTY || !deps.promptLine) {
|
|
3330
|
+
deps.errorLog(formatBridgeConfigMismatchWarning(fileName, provenName));
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
const answer = (await deps.promptLine(`.bridge/config names '${renderDiagnosticValue(fileName)}' but this project is ` +
|
|
3334
|
+
`'${renderDiagnosticValue(provenName)}'. Update repo_name in .bridge/config? (y/N) `)).trim().toLowerCase();
|
|
3335
|
+
if (answer !== "y" && answer !== "yes") {
|
|
3336
|
+
deps.errorLog(formatBridgeConfigMismatchWarning(fileName, provenName));
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
// This run's own file already carries the notice from the scaffold, so a
|
|
3341
|
+
// rewrite adds it only to an older user-owned file the user consented to.
|
|
3342
|
+
const rewritten = rewriteBridgeConfigRepoName(text, provenName);
|
|
3343
|
+
if (!rewritten.ok) {
|
|
3344
|
+
deps.errorLog(rewritten.kind === "unrepresentable-name"
|
|
3345
|
+
? formatBridgeConfigUnrepresentableWarning(fileName, provenName)
|
|
3346
|
+
: formatBridgeConfigMalformedWarning());
|
|
3347
|
+
return;
|
|
3348
|
+
}
|
|
3349
|
+
await writeBridgeConfigAtomically(deps, configPath, rewritten.text);
|
|
3350
|
+
deps.log(` .bridge/config: repo_name updated from '${renderDiagnosticValue(fileName)}' to ` +
|
|
3351
|
+
`'${renderDiagnosticValue(provenName)}'`);
|
|
3352
|
+
}
|
|
3353
|
+
catch {
|
|
3354
|
+
deps.errorLog(formatBridgeConfigRepairFailedWarning());
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Best-effort cleanup for a run that ends in failure before the ping proved the
|
|
3359
|
+
* name (BAPI-1318): remove the `.bridge/config` this run created, but ONLY when
|
|
3360
|
+
* its bytes still equal what this run wrote. A retry then scaffolds it fresh
|
|
3361
|
+
* with the right name. A pre-existing or since-edited file is never removed, and
|
|
3362
|
+
* a cleanup failure never masks the original failure.
|
|
3363
|
+
*/
|
|
3364
|
+
async function removeUnprovenBridgeConfig(deps, createdText) {
|
|
3365
|
+
const configPath = bridgeConfigPath(deps.cwd);
|
|
3366
|
+
try {
|
|
3367
|
+
if ((await deps.readFile(configPath)) !== createdText)
|
|
3368
|
+
return;
|
|
3369
|
+
await deps.unlink(configPath);
|
|
3370
|
+
}
|
|
3371
|
+
catch {
|
|
3372
|
+
/* advisory cleanup only */
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3167
3375
|
/**
|
|
3168
3376
|
* Provision the selected GLOBAL (Codex) and MANUAL (Windsurf)
|
|
3169
3377
|
* targets through the registry-driven emitter (BAPI-635). Project JSON targets
|
|
@@ -4949,6 +5157,24 @@ export async function resolveValidatedPremiumModelAlias(deps, agent) {
|
|
|
4949
5157
|
* parser/resolution errors return 1; a ping failure halts (return 1) BEFORE the
|
|
4950
5158
|
* credential persist and agent spawn. The API key is never logged on any path.
|
|
4951
5159
|
*/
|
|
5160
|
+
/**
|
|
5161
|
+
* The fixed message a bare install prints under Git Bash / mintty (BAPI-1319).
|
|
5162
|
+
* mintty gives Node no TTY, so no prompt can be shown there; this replaces the
|
|
5163
|
+
* confusing "no interactive terminal" credential error with the actual remedy.
|
|
5164
|
+
*/
|
|
5165
|
+
export const INSTALL_BRIDGE_MINTTY_MESSAGE = "This terminal (Git Bash / mintty) can't show Bridge's interactive prompts. " +
|
|
5166
|
+
"Run the install from PowerShell or Windows Terminal, or pass --api-key <key> --repo <name>.";
|
|
5167
|
+
/**
|
|
5168
|
+
* True for a native-Windows, non-TTY Git Bash / mintty session (BAPI-1319).
|
|
5169
|
+
* Pure. Prompts are deliberately NOT enabled here: GitHub's Windows runners use
|
|
5170
|
+
* bash with `MSYSTEM` set, and a prompt there would hang CI.
|
|
5171
|
+
*/
|
|
5172
|
+
export function isMinttyNonInteractiveInstall(platform, env, isTTY) {
|
|
5173
|
+
if (platform !== "win32" || isTTY)
|
|
5174
|
+
return false;
|
|
5175
|
+
const msystem = env.MSYSTEM;
|
|
5176
|
+
return (typeof msystem === "string" && msystem.trim().length > 0) || env.TERM_PROGRAM === "mintty";
|
|
5177
|
+
}
|
|
4952
5178
|
export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
4953
5179
|
const deps = { ...createDefaultInstallBridgeDeps(), ...overrides };
|
|
4954
5180
|
// When the caller overrode only `fetch` (the common test seam), bind the
|
|
@@ -5120,6 +5346,16 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5120
5346
|
// every downstream `deps.env` consumer observes it. The caller-owned object (and
|
|
5121
5347
|
// `process.env`) is never mutated.
|
|
5122
5348
|
deps.env = { ...deps.env, BAPI_BASE_URL: baseUrl };
|
|
5349
|
+
// ---- BAPI-1319: bare install under Git Bash / mintty ----
|
|
5350
|
+
// A bare run there has no TTY and no credential, so it would fall through to
|
|
5351
|
+
// the "no interactive terminal" credential error. Say what actually helps
|
|
5352
|
+
// instead, before anything is prompted for or written. Runs with arguments or
|
|
5353
|
+
// an env credential are untouched.
|
|
5354
|
+
const hasEnvCredential = (deps.env.BAPI_API_KEY ?? "").trim().length > 0 || (deps.env.BAPI_INVITE ?? "").trim().length > 0;
|
|
5355
|
+
if (argv.length === 0 && !hasEnvCredential && isMinttyNonInteractiveInstall(deps.platform, deps.env, deps.isTTY)) {
|
|
5356
|
+
errorLog(INSTALL_BRIDGE_MINTTY_MESSAGE);
|
|
5357
|
+
return 1;
|
|
5358
|
+
}
|
|
5123
5359
|
// ---- BAPI-818 (R2): --dry-run short-circuits HERE — genuinely inert ----
|
|
5124
5360
|
// Fires immediately after argument parsing and the pure base-URL validation
|
|
5125
5361
|
// above, and strictly BEFORE the project-root confirmation prompt, onboarding
|
|
@@ -5296,7 +5532,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5296
5532
|
// key-authentication ping, so there is no source to attribute.
|
|
5297
5533
|
let apiKeySource;
|
|
5298
5534
|
if (selfServeSignupMode) {
|
|
5299
|
-
const emailResult = await resolveSignupEmail(options, deps);
|
|
5535
|
+
const emailResult = await resolveSignupEmail(options, deps, baseUrl);
|
|
5300
5536
|
if (!emailResult.ok) {
|
|
5301
5537
|
errorLog(`Error: ${emailResult.error}`);
|
|
5302
5538
|
return 1;
|
|
@@ -5674,6 +5910,10 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5674
5910
|
// an exception that ESCAPED all of them.
|
|
5675
5911
|
let currentStep = INSTALL_BRIDGE_STEP_LABELS.scaffold;
|
|
5676
5912
|
let bootstrapExchangeSucceeded = false;
|
|
5913
|
+
// BAPI-1318: the `.bridge/config` text Step 1 created (null when it created
|
|
5914
|
+
// none), and whether it still awaits the ping that proves its name.
|
|
5915
|
+
let scaffoldedBridgeConfigText = null;
|
|
5916
|
+
let unprovenBridgeConfigPending = false;
|
|
5677
5917
|
try {
|
|
5678
5918
|
// ---- Step 1 — scaffold (secret-free placeholders only) ----
|
|
5679
5919
|
currentStep = INSTALL_BRIDGE_STEP_LABELS.scaffold;
|
|
@@ -5683,9 +5923,17 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5683
5923
|
// Step 2 mints/verifies the key and Step 4 persists it, so a placeholder
|
|
5684
5924
|
// warning here would be a false alarm about a config the installer is
|
|
5685
5925
|
// actively configuring. The key value itself never crosses this boundary.
|
|
5686
|
-
await deps.runInit(deps.cwd, {
|
|
5926
|
+
const initResult = await deps.runInit(deps.cwd, {
|
|
5687
5927
|
creationContext: { repoName, baseUrl, credentialAvailable: true },
|
|
5928
|
+
isTrackedConfig: (relPath) => isTrackedProjectConfig(deps, relPath),
|
|
5688
5929
|
});
|
|
5930
|
+
// BAPI-1318: remember the exact `.bridge/config` text this run scaffolded. The
|
|
5931
|
+
// name can still change (409 rename, server-returned invite name) before the
|
|
5932
|
+
// ping proves it; until then a failed run removes its own unchanged file.
|
|
5933
|
+
if (initResult && initResult.bridgeConfigCreated) {
|
|
5934
|
+
scaffoldedBridgeConfigText = buildBridgeConfigManifest(initResult.bridgeConfigRepoName ?? repoName);
|
|
5935
|
+
unprovenBridgeConfigPending = true;
|
|
5936
|
+
}
|
|
5689
5937
|
// ---- Step 2 — mint (invite mode) or verify (normal mode) the credential ----
|
|
5690
5938
|
let inviteFingerprint = "";
|
|
5691
5939
|
if (bootstrapInviteMode) {
|
|
@@ -6229,6 +6477,11 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
6229
6477
|
: `Error: ${ping.message}`);
|
|
6230
6478
|
}
|
|
6231
6479
|
log(" connectivity OK");
|
|
6480
|
+
// BAPI-1318: the name is proven now. Make `.bridge/config` agree with it —
|
|
6481
|
+
// a rename of this run's own scaffold, or a consented repair of an older
|
|
6482
|
+
// file. Advisory: it warns on any problem and never fails the install.
|
|
6483
|
+
unprovenBridgeConfigPending = false;
|
|
6484
|
+
await reconcileBridgeConfigAfterPing(deps, repoName, scaffoldedBridgeConfigText);
|
|
6232
6485
|
// ---- BAPI-1221 (S2): persist the captured posture, now that connectivity
|
|
6233
6486
|
// is validated and `repoName`/`apiKey` are both final. Overwrites any
|
|
6234
6487
|
// existing value unconditionally — the old value is never read first. A
|
|
@@ -6567,6 +6820,10 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
6567
6820
|
// deterministic setup is complete, so this is a non-fatal warning (exit 0), with
|
|
6568
6821
|
// the same shared continuation so the remediation is consistent.
|
|
6569
6822
|
errorLog(`Warning: setup steps completed, but the agent session could not be opened (${spawnResult.error}).`);
|
|
6823
|
+
// BAPI-1319: on macOS the usual cause is a missing Automation or
|
|
6824
|
+
// Accessibility grant — the same guidance start-tickets prints.
|
|
6825
|
+
if (deps.platform === "darwin")
|
|
6826
|
+
errorLog(macosTerminalPermissionGuidance());
|
|
6570
6827
|
log(buildManualInstallBridgeContinuation("configured", labelsForPlatforms(selectedPlatforms)));
|
|
6571
6828
|
return 0;
|
|
6572
6829
|
}
|
|
@@ -6643,4 +6900,11 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
6643
6900
|
errorLog(line);
|
|
6644
6901
|
return 1;
|
|
6645
6902
|
}
|
|
6903
|
+
finally {
|
|
6904
|
+
// BAPI-1318: every exit before the ping proved the name is a failure, so a
|
|
6905
|
+
// still-pending scaffold is removed when it is unchanged (see helper).
|
|
6906
|
+
if (unprovenBridgeConfigPending && scaffoldedBridgeConfigText !== null) {
|
|
6907
|
+
await removeUnprovenBridgeConfig(deps, scaffoldedBridgeConfigText);
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6646
6910
|
}
|