@bridge_gpt/mcp-server 0.2.69 → 0.2.70
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 +140 -292
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/agents.generated.js +4 -4
- package/build/artifact-diff.js +110 -0
- package/build/bin-entrypoint.js +36 -0
- package/build/bridge-permissions.js +23 -4
- package/build/claude-review-workflow-drift.js +3 -3
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +598 -19
- package/build/conductor/bring-up-facts.js +9 -9
- package/build/conductor/doctor.js +466 -18
- package/build/conductor/epic-child-continuation.js +54 -3
- package/build/conductor/epic-implementer-cli.js +132 -34
- package/build/conductor/epic-runtime.js +2 -2
- package/build/conductor/epic-state.js +1 -1
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/install-doctor.js +4 -2
- package/build/conductor/launch-need-probes.js +337 -0
- package/build/conductor/local-merge.js +241 -56
- package/build/conductor/merge-credential.js +172 -0
- package/build/conductor/merge-execution-eligibility.js +1 -1
- package/build/conductor/merged-child-check.js +95 -0
- package/build/conductor/plan-trim.js +30 -0
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +11 -6
- package/build/conductor/readiness-prerequisites.js +41 -0
- package/build/conductor/readiness-sections.js +345 -55
- package/build/conductor/readiness.js +99 -87
- package/build/conductor/recovery-cli.js +606 -21
- package/build/conductor/recovery-operations.js +306 -7
- package/build/conductor/repair-contract.js +47 -2
- package/build/conductor/repository-workflow-remedy.js +8 -6
- package/build/conductor/stored-merge-credential.js +76 -0
- package/build/conductor/stored-merge-token-probe.js +28 -0
- package/build/conductor/temporary-plan.js +50 -0
- package/build/conductor/wait-run-cli.js +759 -0
- package/build/conductor/worker-database-doctor-inspector.js +49 -0
- package/build/conductor-bin.js +2 -2
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +2 -5
- package/build/conductor-removed-stub-bin.js +2 -5
- package/build/credential-store.js +356 -13
- package/build/docs.generated.js +26 -13
- package/build/doctor.js +94 -32
- package/build/drive-epic-help.js +212 -0
- package/build/drive-epic.js +1840 -373
- package/build/epic-ci-workflow-drift.js +77 -0
- package/build/epic-ci-workflow-install.js +183 -0
- package/build/epic-ci-workflow.js +157 -0
- package/build/epic-contract-context.js +326 -0
- package/build/epic-implementer-bin.js +57 -41
- package/build/epic-implementer-claude-hook-bin.js +2 -2
- package/build/escalation-reachability.js +134 -0
- package/build/executor/cleanup-report.js +99 -0
- package/build/executor/cli.js +12 -1
- package/build/executor/completion-contract.js +66 -11
- package/build/executor/completion-supervision.js +99 -0
- package/build/executor/delivery-outcome.js +179 -0
- package/build/executor/deps.js +3 -0
- package/build/executor/env.js +71 -0
- package/build/executor/failure-reason-codes.js +126 -0
- package/build/executor/heartbeat.js +38 -0
- package/build/executor/http-client.js +72 -0
- package/build/executor/integration-job.js +298 -5
- package/build/executor/job-errors.js +36 -6
- package/build/executor/job-runner.js +720 -36
- package/build/executor/likely-tests.js +36 -2
- package/build/executor/merge-job.js +145 -2
- package/build/executor/observation.js +73 -4
- package/build/executor/permissions.js +193 -97
- package/build/executor/preflight.js +14 -3
- package/build/executor/process-containment.js +28 -2
- package/build/executor/prompt-spec.js +50 -5
- package/build/executor/rebase-finalization.js +38 -3
- package/build/executor/reconcile-overlap-job.js +35 -7
- package/build/executor/required-work-ledger.js +236 -81
- package/build/executor/resume-pre-spawn.js +38 -1
- package/build/executor/runner.js +91 -3
- package/build/executor/test-clock.js +8 -0
- package/build/executor/types.js +37 -2
- package/build/executor/worker-cli-launcher.js +259 -0
- package/build/executor/worker-command.js +41 -4
- package/build/executor/worker-database-target.js +91 -0
- package/build/executor/worker-database.js +908 -0
- package/build/executor/worker-finalization.js +106 -0
- package/build/executor/worker-guard-hook.js +203 -0
- package/build/executor/worker-resources.js +47 -0
- package/build/executor/worker-verification-guidance.js +15 -2
- package/build/executor/worktree.js +303 -15
- package/build/executor-launch.js +11 -1
- package/build/generated-assets-doctor.js +9 -1
- package/build/generated-assets-health.js +13 -4
- package/build/generated-assets-legacy.js +39 -0
- package/build/generated-assets-paths.js +12 -1
- package/build/generated-assets-repair.js +4 -1
- package/build/generated-assets-test-support.js +12 -0
- package/build/github-remote.js +33 -0
- package/build/github-sign-in.js +62 -0
- package/build/index.js +1067 -536
- package/build/init.js +39 -1
- package/build/install-bridge-conductor-gate.js +78 -0
- package/build/install-bridge-conductor-setup.js +413 -0
- package/build/install-bridge-conductor.js +1226 -267
- package/build/install-bridge.js +376 -60
- package/build/mcp-install-state.js +8 -2
- package/build/merge-pull-request.js +6 -0
- package/build/merge-workflow-install.js +763 -345
- package/build/phase-result-artifacts.js +75 -20
- package/build/pipeline-orchestrator.js +247 -4
- package/build/pipelines.generated.js +35 -35
- package/build/plan-discovery.js +15 -2
- package/build/plan-execution-ledger.js +306 -17
- package/build/plan-phase-routing.js +48 -13
- package/build/plane/cli.js +105 -51
- package/build/plane/defaults.js +3 -0
- package/build/plane/plane-freshness.js +294 -0
- package/build/plane/schema-compatibility.js +137 -0
- package/build/plane/shutdown-deps.js +38 -0
- package/build/plane/shutdown.js +83 -8
- package/build/plane/terminal-run-cleanup.js +303 -0
- package/build/printed-remedy-commands.js +26 -4
- package/build/readiness-check.js +6 -2
- package/build/readiness-prerequisites.generated.js +532 -0
- package/build/readme.generated.js +1 -1
- package/build/setup-epic.js +400 -30
- package/build/shared-resources.generated.js +5 -0
- package/build/start-tickets.js +28 -15
- package/build/test-command-detection.js +247 -0
- package/build/test-command-validation.js +73 -0
- package/build/testing-config-client.js +150 -0
- package/build/ticket-delivery-artifacts.js +258 -0
- package/build/ticket-delivery-enrollment.js +306 -0
- package/build/ticket-delivery.js +205 -0
- package/build/ticket-review-summary.js +14 -0
- package/build/upgrade-cli.js +13 -0
- package/build/v2-readiness-requirements.js +552 -0
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +2 -2
- package/build/worktree-core.js +22 -7
- package/docs/BRIDGE-DIRECTORY-README.md +48 -0
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +12 -867
- package/docs/EPIC-IMPLEMENTER.md +104 -63
- package/docs/epic-implementer/getting-started.md +131 -0
- package/docs/epic-implementer/self-hosting.md +133 -0
- package/package.json +3 -3
- package/pipelines/idea-to-ticket.json +1 -1
- package/pipelines/implement-ticket.json +8 -8
- package/pipelines/review-ticket.json +2 -2
- package/design-assets/favicon/favicon-32x32.png +0 -0
- package/design-assets/just-logo-rough-draft.png +0 -0
package/build/install-bridge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { validateBootstrapDisplayName, validateBootstrapServerIdentity } from "./bootstrap-project-identity.js";
|
|
2
|
-
import { lookupModernBootstrapRecord, mutateModernBootstrapRecord, prepareModernBootstrapInvite, prepareModernBootstrapSignup, transitionModernBootstrapSignup, lookupLegacyBootstrapInvite, checkpointModernBootstrapIdentity, promoteModernBootstrapCredential } from "./credential-store.js";
|
|
2
|
+
import { BRIDGE_SUPPORT_EMAIL, archiveAndReplaceModernBootstrapSignup, archiveExpiredLegacySignupAndInstallModern, lookupModernBootstrapRecord, mutateModernBootstrapRecord, prepareModernBootstrapInvite, prepareModernBootstrapSignup, transitionModernBootstrapSignup, lookupLegacyBootstrapInvite, checkpointModernBootstrapIdentity, promoteModernBootstrapCredential } from "./credential-store.js";
|
|
3
3
|
import { collectFirstBuildAdvisory, formatFirstBuildAdvisory } from "./first-build-advisory.js";
|
|
4
4
|
/**
|
|
5
5
|
* install-bridge — the one-command Bridge API project bootstrap (BAPI-429).
|
|
@@ -145,6 +145,14 @@ import { spawn } from "child_process";
|
|
|
145
145
|
import { randomBytes as cryptoRandomBytes, createHash } from "crypto";
|
|
146
146
|
import os from "os";
|
|
147
147
|
import path from "path";
|
|
148
|
+
import { detectClaudeLogin } from "./claude-login.js";
|
|
149
|
+
import { checkGithubSignIn } from "./github-sign-in.js";
|
|
150
|
+
import { readLocalGithubRemote } from "./conductor/stored-merge-credential.js";
|
|
151
|
+
import { probeStoredMergeToken } from "./conductor/stored-merge-token-probe.js";
|
|
152
|
+
import { readGithubMergeCredential, removeGithubMergeCredential, upsertGithubMergeCredential, } from "./credential-store.js";
|
|
153
|
+
import { createBridgeApiUrls } from "./bridge-api-urls.js";
|
|
154
|
+
import { installEpicCiWorkflow } from "./epic-ci-workflow-install.js";
|
|
155
|
+
import { fetchParseStatus, triggerRepositoryParse, } from "./conductor/bridge-api-client.js";
|
|
148
156
|
import readline from "readline";
|
|
149
157
|
import { runInit, InitPreflightError, buildBridgeApiEntry, buildBridgeConfigManifest, mergeBridgeApiProfileToken, resolveInitScaffoldAssets, refreshBridgeApiPackageSpec, currentBridgePackageSpec, classifyTrackedProbeExitCode, decideExistingProjectRoot, formatUndeterminedRootReplacedWarning, } from "./init.js";
|
|
150
158
|
import { hasProjectRootMarker as sharedHasProjectRootMarker } from "./project-root.js";
|
|
@@ -1467,6 +1475,19 @@ export function createDefaultInstallBridgeDeps() {
|
|
|
1467
1475
|
// Read-only service-state probe (BAPI-779). Typed to return a state,
|
|
1468
1476
|
// never a runner, so the doctor cannot start/enable/reload anything.
|
|
1469
1477
|
inspectExecutorServiceState: params.inspectExecutorServiceState,
|
|
1478
|
+
// BAPI-1461: only the converging setup passes these; without them the
|
|
1479
|
+
// conductor doctor is built exactly as before.
|
|
1480
|
+
...(params.skipDenyProbe !== undefined || params.storedMergeCredential !== undefined
|
|
1481
|
+
? {
|
|
1482
|
+
conductorDoctorDeps: {
|
|
1483
|
+
env: params.env,
|
|
1484
|
+
...(params.skipDenyProbe === true ? { skipDenyProbe: true } : {}),
|
|
1485
|
+
...(params.storedMergeCredential !== undefined
|
|
1486
|
+
? { storedMergeCredential: params.storedMergeCredential }
|
|
1487
|
+
: {}),
|
|
1488
|
+
},
|
|
1489
|
+
}
|
|
1490
|
+
: {}),
|
|
1470
1491
|
installDoctorDeps: {
|
|
1471
1492
|
env: params.env,
|
|
1472
1493
|
cwd: params.cwd,
|
|
@@ -1632,6 +1653,54 @@ export function getMergeWorkflowInstallUsage() {
|
|
|
1632
1653
|
* `shell: false` — and `gh` additionally receives the non-interactive pins so a
|
|
1633
1654
|
* prompt can never hang an install. The resulting environment is never printed.
|
|
1634
1655
|
*/
|
|
1656
|
+
/**
|
|
1657
|
+
* BAPI-1461 — the production seams for the converging `install conductor`.
|
|
1658
|
+
*
|
|
1659
|
+
* Each one is a thin adapter over an implementation that already exists: the
|
|
1660
|
+
* Claude login reader, one bounded `gh auth status`, the credential store's
|
|
1661
|
+
* typed merge-token functions (locked, durable writes), the shared capability
|
|
1662
|
+
* probe, the stored-test-command client, the CI workflow artifact-set installer
|
|
1663
|
+
* (sharing the merge-workflow installer's git/gh runners), and the parse routes.
|
|
1664
|
+
*/
|
|
1665
|
+
export function buildConductorSetupDeps(deps, log, errorLog) {
|
|
1666
|
+
const installerDeps = buildMergeWorkflowInstallDeps(deps, log, errorLog);
|
|
1667
|
+
const storeDeps = modernWriteDeps(deps);
|
|
1668
|
+
return {
|
|
1669
|
+
detectClaudeLogin: () => detectClaudeLogin({ homedir: deps.homedir, readFile: deps.readFile }),
|
|
1670
|
+
// An injected installer runner (tests) is used for these reads too, so a
|
|
1671
|
+
// harness never reaches the real `gh` or `git`.
|
|
1672
|
+
checkGithubSignIn: () => deps.mergeWorkflowRunGh
|
|
1673
|
+
? checkGithubSignIn(async (args) => ({ exitCode: (await deps.mergeWorkflowRunGh(args)).status }))
|
|
1674
|
+
: checkGithubSignIn(),
|
|
1675
|
+
readLocalGithubRemote: () => readLocalGithubRemote(deps.cwd, deps.mergeWorkflowRunGit
|
|
1676
|
+
? async () => {
|
|
1677
|
+
const r = await deps.mergeWorkflowRunGit(["remote", "get-url", "origin"]);
|
|
1678
|
+
return r.status === 0 ? r.stdout : null;
|
|
1679
|
+
}
|
|
1680
|
+
: undefined),
|
|
1681
|
+
...(deps.isTTY && deps.promptSecret ? { promptSecret: deps.promptSecret } : {}),
|
|
1682
|
+
readMergeToken: (identity) => readGithubMergeCredential(identity, storeDeps),
|
|
1683
|
+
storeMergeToken: (identity, token) => upsertGithubMergeCredential(identity, token, storeDeps),
|
|
1684
|
+
removeMergeToken: (identity) => removeGithubMergeCredential(identity, storeDeps),
|
|
1685
|
+
probeMergeToken: (args) => probeStoredMergeToken({ ...args, env: deps.env }),
|
|
1686
|
+
testingConfig: (access) => ({
|
|
1687
|
+
repoName: access.repoName,
|
|
1688
|
+
urls: createBridgeApiUrls(access.baseUrl),
|
|
1689
|
+
fetch: deps.fetch,
|
|
1690
|
+
getGetHeaders: async () => ({ "X-API-Key": access.apiKey }),
|
|
1691
|
+
getPostHeaders: async () => ({ "X-API-Key": access.apiKey, "Content-Type": "application/json" }),
|
|
1692
|
+
}),
|
|
1693
|
+
readRepositoryFile: (relPath) => deps.readFile(path.join(deps.cwd, relPath)),
|
|
1694
|
+
installCiWorkflow: (testCommand, options, confirmWrite) => installEpicCiWorkflow(testCommand, { dryRun: options.dryRun }, {
|
|
1695
|
+
...installerDeps,
|
|
1696
|
+
...(options.quiet === true ? { log: () => { }, errorLog: () => { } } : {}),
|
|
1697
|
+
confirmWrite,
|
|
1698
|
+
}),
|
|
1699
|
+
runGh: installerDeps.runGh,
|
|
1700
|
+
fetchParseStatus: (access) => fetchParseStatus(access, deps.fetch),
|
|
1701
|
+
triggerParse: (access) => triggerRepositoryParse(access, deps.fetch),
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1635
1704
|
export function buildMergeWorkflowInstallDeps(deps, log, errorLog) {
|
|
1636
1705
|
const toResult = (r) => ({
|
|
1637
1706
|
status: r.exitCode,
|
|
@@ -3865,7 +3934,7 @@ export async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, ke
|
|
|
3865
3934
|
return {
|
|
3866
3935
|
ok: false,
|
|
3867
3936
|
kind: "failed",
|
|
3868
|
-
message: `The bootstrap exchange failed (HTTP ${resp.status}). Preserve proof and rerun the same attempt; verify server compatibility and contact
|
|
3937
|
+
message: `The bootstrap exchange failed (HTTP ${resp.status}). Preserve proof and rerun the same attempt; verify server compatibility and contact Bridge support at ${BRIDGE_SUPPORT_EMAIL} if it persists.`,
|
|
3869
3938
|
};
|
|
3870
3939
|
}
|
|
3871
3940
|
/** The invite-email prompt (BAPI-1263), shown once after an `email_required` refusal. */
|
|
@@ -4179,6 +4248,95 @@ export const INSTALL_BRIDGE_SIGNUP_BLOCKED = "Error: Bridge signup is not availa
|
|
|
4179
4248
|
export function formatSelfServeUnexpectedStatus(status) {
|
|
4180
4249
|
return `Error: Bridge signup returned an unexpected response (status ${status}). Try again in a few minutes.`;
|
|
4181
4250
|
}
|
|
4251
|
+
// ===================================================================
|
|
4252
|
+
// BAPI-1449 — modern-path verification recovery copy (S1–S7).
|
|
4253
|
+
//
|
|
4254
|
+
// The wording is settled; only the sender comes from the shared
|
|
4255
|
+
// INSTALL_BRIDGE_VERIFICATION_SENDER constant. None of these says WHY a server
|
|
4256
|
+
// rejection happened: the word "expired" appears only in S4a, which is chosen from
|
|
4257
|
+
// the locally stored expiry and never from a server reply.
|
|
4258
|
+
// ===================================================================
|
|
4259
|
+
/** The existing end-of-run line for a prompt that gave up or timed out empty. */
|
|
4260
|
+
export const INSTALL_BRIDGE_VERIFICATION_INCOMPLETE = "Verification incomplete. Resume the same signup from this folder.";
|
|
4261
|
+
/** S1 — the first empty Enter at the modern code prompt. */
|
|
4262
|
+
export const INSTALL_BRIDGE_EMPTY_CODE_GUIDANCE = [
|
|
4263
|
+
"No code yet? Type r to send a new one, or check spam for mail from",
|
|
4264
|
+
`${INSTALL_BRIDGE_VERIFICATION_SENDER}.`,
|
|
4265
|
+
"Wrong address? Press Ctrl-C and re-run install with a different email.",
|
|
4266
|
+
"Have a teammate on Bridge? Ask them for an invite and choose",
|
|
4267
|
+
'"I already have a token".',
|
|
4268
|
+
].join("\n");
|
|
4269
|
+
/** S2 — after a rejected code while resends may remain. */
|
|
4270
|
+
export const INSTALL_BRIDGE_REJECTED_RESEND_HINT = "Type r to send a new code, or enter the code again.";
|
|
4271
|
+
/** S3 — `r` after the server reported no resends left. */
|
|
4272
|
+
export const INSTALL_BRIDGE_NO_MORE_CODES = [
|
|
4273
|
+
"Bridge cannot send more codes for this signup.",
|
|
4274
|
+
"Enter a code from an email you already received.",
|
|
4275
|
+
].join("\n");
|
|
4276
|
+
/** S4a — the saved challenge is past its locally stored expiry. */
|
|
4277
|
+
export const INSTALL_BRIDGE_RECOVERY_REASON_EXPIRED = "The code for this signup has expired.";
|
|
4278
|
+
/** S4b — the saved signup has no challenge handle (or expiry). */
|
|
4279
|
+
export const INSTALL_BRIDGE_RECOVERY_REASON_NO_CODE = "The last signup in this folder did not finish, so no code is waiting.";
|
|
4280
|
+
/** S4c — this run's email differs from the saved signup's email. */
|
|
4281
|
+
export const INSTALL_BRIDGE_RECOVERY_REASON_DIFFERENT_EMAIL = "This folder has a signup waiting for a different email address.";
|
|
4282
|
+
/** S5 — the rejection cap was reached (state-neutral). */
|
|
4283
|
+
export const INSTALL_BRIDGE_RECOVERY_REASON_CAP = "That signup could not be verified. Nothing has been created.";
|
|
4284
|
+
/** S6 — the user declined a fresh signup. */
|
|
4285
|
+
export const INSTALL_BRIDGE_FRESH_SIGNUP_DECLINED = [
|
|
4286
|
+
"Nothing has been created. Your saved signup is kept.",
|
|
4287
|
+
"Re-run install from this folder when you are ready.",
|
|
4288
|
+
].join("\n");
|
|
4289
|
+
/** S7 — the user accepted; printed after the old attempt is archived. */
|
|
4290
|
+
export const INSTALL_BRIDGE_FRESH_SIGNUP_STARTING = "Starting a new signup. The old attempt was set aside, not deleted.";
|
|
4291
|
+
/**
|
|
4292
|
+
* The default-No question after S4a/S4b/S4c/S5. Names only the MASKED address of
|
|
4293
|
+
* THIS run's email; the full address is never printed.
|
|
4294
|
+
*/
|
|
4295
|
+
export function formatFreshSignupQuestion(thisRunEmail) {
|
|
4296
|
+
return `Start a new signup? We will email a new code to ${maskSignupEmail(thisRunEmail)}. [y/N]: `;
|
|
4297
|
+
}
|
|
4298
|
+
/** The legacy "could not be requested" line for a rejected email (HTTP 400/422). */
|
|
4299
|
+
export const INSTALL_BRIDGE_SELF_SERVE_REQUEST_INVALID = "Error: Self-serve setup could not be requested. Check the email value and try again.";
|
|
4300
|
+
/**
|
|
4301
|
+
* Map a failed challenge REQUEST to its user-facing line, with the legacy loop's
|
|
4302
|
+
* category mapping. Carries no response body, email, or exception text.
|
|
4303
|
+
*/
|
|
4304
|
+
export function formatSelfServeChallengeRequestFailure(failure) {
|
|
4305
|
+
switch (failure.category) {
|
|
4306
|
+
case "rate-limited":
|
|
4307
|
+
return `Error: ${INSTALL_BRIDGE_RESEND_THROTTLED}`;
|
|
4308
|
+
case "unavailable":
|
|
4309
|
+
case "network-error":
|
|
4310
|
+
return INSTALL_BRIDGE_SIGNUP_UNAVAILABLE;
|
|
4311
|
+
case "invalid":
|
|
4312
|
+
return INSTALL_BRIDGE_SELF_SERVE_REQUEST_INVALID;
|
|
4313
|
+
case "blocked":
|
|
4314
|
+
return INSTALL_BRIDGE_SIGNUP_BLOCKED;
|
|
4315
|
+
case "unexpected":
|
|
4316
|
+
return formatSelfServeUnexpectedStatus(failure.status ?? 0);
|
|
4317
|
+
default:
|
|
4318
|
+
return INSTALL_BRIDGE_SIGNUP_UPGRADE_REQUIRED;
|
|
4319
|
+
}
|
|
4320
|
+
}
|
|
4321
|
+
/**
|
|
4322
|
+
* Map a failed VERIFY (other than the uniform `rejected`) to its user-facing line,
|
|
4323
|
+
* with the legacy loop's mapping. Carries no response body, code, or handle.
|
|
4324
|
+
*/
|
|
4325
|
+
export function formatSelfServeVerifyFailure(failure) {
|
|
4326
|
+
switch (failure.category) {
|
|
4327
|
+
case "rate-limited":
|
|
4328
|
+
return `Error: ${INSTALL_BRIDGE_VERIFY_THROTTLED}`;
|
|
4329
|
+
case "server-error":
|
|
4330
|
+
case "network-error":
|
|
4331
|
+
return INSTALL_BRIDGE_VERIFY_SERVER_ERROR;
|
|
4332
|
+
case "blocked":
|
|
4333
|
+
return INSTALL_BRIDGE_SIGNUP_BLOCKED;
|
|
4334
|
+
case "unexpected":
|
|
4335
|
+
return formatSelfServeUnexpectedStatus(failure.status ?? 0);
|
|
4336
|
+
default:
|
|
4337
|
+
return INSTALL_BRIDGE_SIGNUP_UPGRADE_REQUIRED;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4182
4340
|
/**
|
|
4183
4341
|
* Mask an address for display: first character, then the domain.
|
|
4184
4342
|
*
|
|
@@ -4220,23 +4378,10 @@ export function formatChallengeRemaining(expiresAtIso, nowMs) {
|
|
|
4220
4378
|
const hours = Math.floor(minutes / 60);
|
|
4221
4379
|
return `This code is valid for about another ${hours} hours.`;
|
|
4222
4380
|
}
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
* network call.
|
|
4226
|
-
*
|
|
4227
|
-
* Local validation is a courtesy, not a security control — the server re-validates
|
|
4228
|
-
* everything — but it keeps a mistyped code from consuming one of the five attempts
|
|
4229
|
-
* that permanently kill a challenge. The user gets the prompt back instead.
|
|
4230
|
-
*
|
|
4231
|
-
* Pasted input is trimmed: copying a code out of a mail client routinely brings
|
|
4232
|
-
* whitespace with it, and refusing that would be an avoidable dead end.
|
|
4233
|
-
*
|
|
4234
|
-
* The entered value is returned and never echoed, never logged, and never written
|
|
4235
|
-
* to disk. `null` means the user gave up (an empty line, or an unreadable prompt).
|
|
4236
|
-
*/
|
|
4237
|
-
export async function promptForVerificationCode(deps, log, maxAttempts = 3) {
|
|
4381
|
+
export async function promptForVerificationCode(deps, log, maxAttempts = 3, options = {}) {
|
|
4382
|
+
const allowResend = options.allowResend ?? false;
|
|
4238
4383
|
if (!deps.promptLine)
|
|
4239
|
-
return
|
|
4384
|
+
return { kind: "give-up" };
|
|
4240
4385
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
4241
4386
|
let entered;
|
|
4242
4387
|
try {
|
|
@@ -4245,17 +4390,19 @@ export async function promptForVerificationCode(deps, log, maxAttempts = 3) {
|
|
|
4245
4390
|
catch {
|
|
4246
4391
|
// Secret-free by construction: the caught value is never surfaced or
|
|
4247
4392
|
// inspected, because a readline failure can carry the partial input.
|
|
4248
|
-
return
|
|
4393
|
+
return { kind: "give-up" };
|
|
4249
4394
|
}
|
|
4250
4395
|
if (entered.length === 0)
|
|
4251
|
-
return
|
|
4396
|
+
return { kind: "empty" };
|
|
4397
|
+
if (allowResend && entered.toLowerCase() === "r")
|
|
4398
|
+
return { kind: "resend" };
|
|
4252
4399
|
if (new RegExp(`^[0-9]{${SIGNUP_VERIFICATION_CODE_LENGTH}}$`).test(entered)) {
|
|
4253
|
-
return entered;
|
|
4400
|
+
return { kind: "code", code: entered };
|
|
4254
4401
|
}
|
|
4255
4402
|
// Describes the SHAPE, never the value the user typed.
|
|
4256
4403
|
log(` That does not look like a code — it is ${SIGNUP_VERIFICATION_CODE_LENGTH} digits.`);
|
|
4257
4404
|
}
|
|
4258
|
-
return
|
|
4405
|
+
return { kind: "give-up" };
|
|
4259
4406
|
}
|
|
4260
4407
|
/*
|
|
4261
4408
|
* `mintSelfServeInvite` and `SelfServeMintResult` were REMOVED in BAPI-757.
|
|
@@ -5457,6 +5604,9 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5457
5604
|
// Built from the FINAL merged deps for the same reason (BAPI-775): a test
|
|
5458
5605
|
// that overrides only `readFile` or only `cwd` must see its override here.
|
|
5459
5606
|
localProvisioning: buildConductorLocalDeps(deps),
|
|
5607
|
+
// BAPI-1461: the converging setup's seams (sign-ins, the stored merge token,
|
|
5608
|
+
// the stored test command, the CI workflow installer, and indexing).
|
|
5609
|
+
setup: deps.conductorSetup ?? buildConductorSetupDeps(deps, log, errorLog),
|
|
5460
5610
|
// BAPI-1391: the read-only discovery filesystem over THIS working tree, and
|
|
5461
5611
|
// the short-lived qualifying executor (the existing executor CLI with a
|
|
5462
5612
|
// `runExecutor` override limiting it to `gate_qualification` jobs).
|
|
@@ -5856,6 +6006,9 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5856
6006
|
return 1;
|
|
5857
6007
|
}
|
|
5858
6008
|
let modernAttempt;
|
|
6009
|
+
// BAPI-1449: an expired basename-keyed signup is offered a fresh modern signup by
|
|
6010
|
+
// the modern path (consent first), instead of ending the run here.
|
|
6011
|
+
let legacyExpired;
|
|
5859
6012
|
if (options.tools?.length === 0 && !options.agentName) {
|
|
5860
6013
|
log(buildManualInstallBridgeContinuation("empty-selection", []));
|
|
5861
6014
|
return 0;
|
|
@@ -5881,22 +6034,22 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
5881
6034
|
}
|
|
5882
6035
|
if (legacy.state === "resumable" || legacy.state === "challenge")
|
|
5883
6036
|
options.repo = oldName.value;
|
|
5884
|
-
if (legacy.state === "challenge-expired")
|
|
5885
|
-
|
|
5886
|
-
return 1;
|
|
5887
|
-
}
|
|
6037
|
+
if (legacy.state === "challenge-expired")
|
|
6038
|
+
legacyExpired = { repoName: oldName.value, snapshot: legacy.snapshot };
|
|
5888
6039
|
}
|
|
5889
6040
|
}
|
|
5890
6041
|
if (options.repo === undefined) {
|
|
5891
6042
|
try {
|
|
5892
|
-
modernAttempt = await redeemModernInstallAttempt(deps, options, baseUrl, inviteToken, signupEmail, selfServeSignupMode);
|
|
6043
|
+
modernAttempt = await redeemModernInstallAttempt(deps, options, baseUrl, inviteToken, signupEmail, selfServeSignupMode, legacyExpired);
|
|
5893
6044
|
apiKey = modernAttempt.record.keySecret;
|
|
5894
6045
|
options.repo = modernAttempt.repoName;
|
|
5895
6046
|
bootstrapInviteMode = false;
|
|
5896
6047
|
deps.log(`Project '${modernAttempt.displayName}' recovered; completing installation.`);
|
|
5897
6048
|
}
|
|
5898
6049
|
catch (error) {
|
|
5899
|
-
|
|
6050
|
+
if (error instanceof ModernInstallStopped)
|
|
6051
|
+
return 1;
|
|
6052
|
+
errorLog(error instanceof ModernInstallRecoveryError ? error.message : `Installation could not complete. Preserve recovery proof and resume from the same folder and invite/signup; contact Bridge support at ${BRIDGE_SUPPORT_EMAIL} if needed.`);
|
|
5900
6053
|
return 1;
|
|
5901
6054
|
}
|
|
5902
6055
|
}
|
|
@@ -6290,7 +6443,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
6290
6443
|
reportFatal(INSTALL_BRIDGE_SIGNUP_UNAVAILABLE);
|
|
6291
6444
|
}
|
|
6292
6445
|
else if (requested.category === "invalid") {
|
|
6293
|
-
reportFatal(
|
|
6446
|
+
reportFatal(INSTALL_BRIDGE_SELF_SERVE_REQUEST_INVALID);
|
|
6294
6447
|
}
|
|
6295
6448
|
else if (requested.category === "blocked") {
|
|
6296
6449
|
// BAPI-1171: the request was refused before signup logic ran — an
|
|
@@ -6346,12 +6499,15 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
6346
6499
|
// never succeed, with Ctrl-C as the only way out.
|
|
6347
6500
|
let rejections = 0;
|
|
6348
6501
|
for (;;) {
|
|
6349
|
-
|
|
6350
|
-
|
|
6502
|
+
// Resend recognition stays off here: on the legacy path `r` is still malformed
|
|
6503
|
+
// input, and an empty line or give-up ends the run as it always has.
|
|
6504
|
+
const prompted = await promptForVerificationCode(deps, log);
|
|
6505
|
+
if (prompted.kind !== "code") {
|
|
6351
6506
|
reportFatal("Error: No verification code entered — nothing has been set up. Re-run this command " +
|
|
6352
6507
|
"to continue; the code already emailed to you stays valid until it expires.");
|
|
6353
6508
|
return false;
|
|
6354
6509
|
}
|
|
6510
|
+
const code = prompted.code;
|
|
6355
6511
|
const verified = await verifySelfServeChallenge(deps, baseUrl, challengeHandle, code);
|
|
6356
6512
|
if (verified.ok) {
|
|
6357
6513
|
inviteToken = verified.token;
|
|
@@ -7259,13 +7415,23 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
|
|
|
7259
7415
|
}
|
|
7260
7416
|
class ModernInstallRecoveryError extends Error {
|
|
7261
7417
|
}
|
|
7418
|
+
/** The run already printed its own output (for example S6) and stops with exit 1. */
|
|
7419
|
+
class ModernInstallStopped extends Error {
|
|
7420
|
+
}
|
|
7421
|
+
/**
|
|
7422
|
+
* A record that may hold redeemed proof. Presence, not truthiness: an empty-string
|
|
7423
|
+
* field still counts. Such a record is never offered a fresh signup.
|
|
7424
|
+
*/
|
|
7425
|
+
function isProtectedModernRecord(record) {
|
|
7426
|
+
return ["fingerprint", "replayToken", "canonical"].some(field => Object.prototype.hasOwnProperty.call(record, field));
|
|
7427
|
+
}
|
|
7262
7428
|
function modernWriteDeps(deps) {
|
|
7263
7429
|
return { env: deps.env, homedir: deps.homedir, platform: deps.platform, readFile: deps.readFile,
|
|
7264
7430
|
mkdir: deps.mkdir, writeFile: deps.writeFile, rename: deps.rename, chmod: deps.chmod, unlink: deps.unlink, open: deps.open };
|
|
7265
7431
|
}
|
|
7266
|
-
async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupEmail, selfServe) {
|
|
7432
|
+
async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupEmail, selfServe, legacyExpired) {
|
|
7267
7433
|
if (!deps.realpath)
|
|
7268
|
-
throw new ModernInstallRecoveryError(
|
|
7434
|
+
throw new ModernInstallRecoveryError(`Canonical folder resolution is unavailable. Contact Bridge support at ${BRIDGE_SUPPORT_EMAIL}.`);
|
|
7269
7435
|
const folder = await deps.realpath(deps.cwd);
|
|
7270
7436
|
const folderDigest = fingerprintBootstrapFields("bridge-bootstrap-folder-v1", [baseUrl, folder]);
|
|
7271
7437
|
const signupLocator = `bootstrap-pending:signup:${folderDigest}`;
|
|
@@ -7277,9 +7443,12 @@ async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupE
|
|
|
7277
7443
|
if (!loaded.ok)
|
|
7278
7444
|
throw new Error(loaded.error);
|
|
7279
7445
|
let record = loaded.record;
|
|
7280
|
-
|
|
7446
|
+
let resumed = record !== null;
|
|
7281
7447
|
let label = record?.label ?? options.displayName;
|
|
7282
|
-
|
|
7448
|
+
// BAPI-1449: an expired legacy basename signup asks for consent BEFORE any label
|
|
7449
|
+
// question, so its label is derived only after the user accepts.
|
|
7450
|
+
const deferLabelForLegacy = selfServe && record === null && legacyExpired !== undefined;
|
|
7451
|
+
const deriveLabel = async () => {
|
|
7283
7452
|
if (!deps.isTTY || !deps.promptLine)
|
|
7284
7453
|
throw new ModernInstallRecoveryError("Modern invite creation requires --display-name in a non-interactive terminal.");
|
|
7285
7454
|
// BAPI-1448: a valid folder name is the label, with no question. The prompt
|
|
@@ -7289,24 +7458,35 @@ async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupE
|
|
|
7289
7458
|
suggestion = validateBootstrapDisplayName(path.basename(folder));
|
|
7290
7459
|
}
|
|
7291
7460
|
catch { /* explicit label required */ }
|
|
7292
|
-
if (suggestion)
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
}
|
|
7461
|
+
if (suggestion)
|
|
7462
|
+
return suggestion;
|
|
7463
|
+
return validateBootstrapDisplayName(await deps.promptLine("Project display name: "));
|
|
7464
|
+
};
|
|
7465
|
+
if (label === undefined && !deferLabelForLegacy)
|
|
7466
|
+
label = await deriveLabel();
|
|
7299
7467
|
if (selfServe) {
|
|
7300
7468
|
if (!deps.isTTY || !deps.promptLine)
|
|
7301
7469
|
throw new ModernInstallRecoveryError("Self-serve email verification requires an interactive terminal.");
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7470
|
+
// At most one fresh signup per invocation, whichever route accepted it.
|
|
7471
|
+
let freshSignupAccepted = false;
|
|
7472
|
+
const generateKeySecret = () => generateBootstrapKeySecret(deps.randomBytes);
|
|
7473
|
+
const generateArchiveSuffix = () => deps.randomBytes(16).toString("hex");
|
|
7474
|
+
// Print the reason, then ask the default-No question. A decline (or EOF, an empty
|
|
7475
|
+
// line, a thrown prompt) prints S6 and stops with the credential store untouched.
|
|
7476
|
+
const askFreshSignup = async (reason) => {
|
|
7477
|
+
deps.log(reason);
|
|
7478
|
+
if (!(await requestCredentialRouteSwitchConfirmation(deps, formatFreshSignupQuestion(signupEmail)))) {
|
|
7479
|
+
deps.log(INSTALL_BRIDGE_FRESH_SIGNUP_DECLINED);
|
|
7480
|
+
throw new ModernInstallStopped();
|
|
7481
|
+
}
|
|
7482
|
+
};
|
|
7483
|
+
// Request a challenge for THIS run's email, then persist its handle and expiry
|
|
7484
|
+
// BEFORE the prompt. The request happens outside the credential-store lock. On
|
|
7485
|
+
// any failure the saved record stays and is never replaced again in this run.
|
|
7486
|
+
const requestAndSaveChallenge = async () => {
|
|
7307
7487
|
const requested = await requestSelfServeChallenge(deps, baseUrl, signupEmail);
|
|
7308
7488
|
if (!requested.ok)
|
|
7309
|
-
throw new ModernInstallRecoveryError(
|
|
7489
|
+
throw new ModernInstallRecoveryError(formatSelfServeChallengeRequestFailure(requested));
|
|
7310
7490
|
const saved = await mutateModernBootstrapRecord(scope, writeDeps, current => {
|
|
7311
7491
|
if (!current)
|
|
7312
7492
|
throw new ModernInstallRecoveryError("Signup proof missing.");
|
|
@@ -7315,25 +7495,161 @@ async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupE
|
|
|
7315
7495
|
if (!saved.ok || !saved.record)
|
|
7316
7496
|
throw new Error(saved.ok ? "Signup proof missing." : saved.error);
|
|
7317
7497
|
record = saved.record;
|
|
7498
|
+
};
|
|
7499
|
+
// Archive the observed record, install a fresh signup for this run's email, then
|
|
7500
|
+
// (only after that is durable) print S7 and request the new challenge.
|
|
7501
|
+
const replaceWithFreshSignup = async (observed) => {
|
|
7502
|
+
const replaced = await archiveAndReplaceModernBootstrapSignup({ scope, observed, email: signupEmail, generateKeySecret, generateArchiveSuffix }, writeDeps);
|
|
7503
|
+
if (!replaced.ok)
|
|
7504
|
+
throw new ModernInstallRecoveryError(replaced.error);
|
|
7505
|
+
freshSignupAccepted = true;
|
|
7506
|
+
resumed = false;
|
|
7507
|
+
record = replaced.record;
|
|
7508
|
+
deps.log(INSTALL_BRIDGE_FRESH_SIGNUP_STARTING);
|
|
7509
|
+
await requestAndSaveChallenge();
|
|
7510
|
+
};
|
|
7511
|
+
if (!record) {
|
|
7512
|
+
if (legacyExpired) {
|
|
7513
|
+
// The basename-keyed legacy signup is the "no modern record" case: ask, derive
|
|
7514
|
+
// the label from the folder basename, then archive it and install the modern
|
|
7515
|
+
// signup in one write.
|
|
7516
|
+
await askFreshSignup(INSTALL_BRIDGE_RECOVERY_REASON_EXPIRED);
|
|
7517
|
+
label = options.displayName ?? await deriveLabel();
|
|
7518
|
+
const replaced = await archiveExpiredLegacySignupAndInstallModern({ repoName: legacyExpired.repoName, observed: legacyExpired.snapshot, now: deps.now(), scope, label, email: signupEmail, generateKeySecret, generateArchiveSuffix }, writeDeps);
|
|
7519
|
+
if (!replaced.ok)
|
|
7520
|
+
throw new ModernInstallRecoveryError(replaced.error);
|
|
7521
|
+
freshSignupAccepted = true;
|
|
7522
|
+
record = replaced.record;
|
|
7523
|
+
deps.log(INSTALL_BRIDGE_FRESH_SIGNUP_STARTING);
|
|
7524
|
+
await requestAndSaveChallenge();
|
|
7525
|
+
}
|
|
7526
|
+
else {
|
|
7527
|
+
const intent = await prepareModernBootstrapSignup(scope, label, signupEmail, generateKeySecret, writeDeps);
|
|
7528
|
+
if (!intent.ok || !intent.record)
|
|
7529
|
+
throw new Error(intent.ok ? "Signup proof missing." : intent.error);
|
|
7530
|
+
record = intent.record;
|
|
7531
|
+
await requestAndSaveChallenge();
|
|
7532
|
+
}
|
|
7318
7533
|
}
|
|
7534
|
+
else if (!isProtectedModernRecord(record)) {
|
|
7535
|
+
// A saved, unresolved signup. Reasons are checked in this order, and the
|
|
7536
|
+
// expiry is read from the stored value only, never from a server reply.
|
|
7537
|
+
// A malformed stored expiry is treated as unexpired.
|
|
7538
|
+
let reason;
|
|
7539
|
+
if (typeof record.signupEmail === "string" && record.signupEmail.trim().toLowerCase() !== signupEmail.trim().toLowerCase()) {
|
|
7540
|
+
reason = INSTALL_BRIDGE_RECOVERY_REASON_DIFFERENT_EMAIL;
|
|
7541
|
+
}
|
|
7542
|
+
else if (!record.challengeHandle || !record.challengeExpiresAt) {
|
|
7543
|
+
reason = INSTALL_BRIDGE_RECOVERY_REASON_NO_CODE;
|
|
7544
|
+
}
|
|
7545
|
+
else if (Date.parse(record.challengeExpiresAt) <= deps.now()) {
|
|
7546
|
+
reason = INSTALL_BRIDGE_RECOVERY_REASON_EXPIRED;
|
|
7547
|
+
}
|
|
7548
|
+
if (reason !== undefined) {
|
|
7549
|
+
await askFreshSignup(reason);
|
|
7550
|
+
await replaceWithFreshSignup(record);
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
if (!record)
|
|
7554
|
+
throw new ModernInstallRecoveryError("Recovery proof missing.");
|
|
7319
7555
|
if (record.replayToken && record.fingerprint) {
|
|
7320
7556
|
token = record.replayToken;
|
|
7321
7557
|
fingerprint = record.fingerprint;
|
|
7322
7558
|
scope = makeScope(`bootstrap-pending:invite:${fingerprint}`);
|
|
7323
7559
|
}
|
|
7324
7560
|
else {
|
|
7561
|
+
// Only a protected record can reach these two guards unresolved: an unprotected
|
|
7562
|
+
// one was either offered a fresh signup above or already holds a live challenge.
|
|
7325
7563
|
if (!record.challengeHandle || !record.challengeExpiresAt)
|
|
7326
|
-
throw new ModernInstallRecoveryError(
|
|
7327
|
-
if (Date.parse(record.challengeExpiresAt) <= deps.now())
|
|
7328
|
-
throw new ModernInstallRecoveryError(
|
|
7329
|
-
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7564
|
+
throw new ModernInstallRecoveryError(`The previous signup response is unresolved. Preserve proof and contact Bridge support at ${BRIDGE_SUPPORT_EMAIL} before restarting.`);
|
|
7565
|
+
if (isProtectedModernRecord(record) && Date.parse(record.challengeExpiresAt) <= deps.now())
|
|
7566
|
+
throw new ModernInstallRecoveryError(`The saved verification challenge expired. Preserve proof and contact Bridge support at ${BRIDGE_SUPPORT_EMAIL}.`);
|
|
7567
|
+
// One pass per challenge. Everything challenge-local resets only when a new
|
|
7568
|
+
// challenge begins; the loop leaves it on a verified code or by throwing.
|
|
7569
|
+
let verifiedToken;
|
|
7570
|
+
for (;;) {
|
|
7571
|
+
const handle = record.challengeHandle;
|
|
7572
|
+
logVerificationCodeLeadIn(deps.log, record.signupEmail ?? signupEmail);
|
|
7573
|
+
let rejections = 0;
|
|
7574
|
+
let consecutiveEmpty = 0;
|
|
7575
|
+
let emptyGuidanceShown = false;
|
|
7576
|
+
// Unknown until a resend in THIS session reports it, including on a resumed
|
|
7577
|
+
// challenge: the first `r` then makes a real request.
|
|
7578
|
+
let resendsRemaining;
|
|
7579
|
+
let capReached = false;
|
|
7580
|
+
// Count a rejection; true when the shared cap was just reached.
|
|
7581
|
+
const countRejection = () => {
|
|
7582
|
+
deps.errorLog(INSTALL_BRIDGE_VERIFICATION_REJECTED);
|
|
7583
|
+
rejections += 1;
|
|
7584
|
+
capReached = rejections >= INSTALL_BRIDGE_MAX_CODE_REJECTIONS;
|
|
7585
|
+
return capReached;
|
|
7586
|
+
};
|
|
7587
|
+
while (!capReached && verifiedToken === undefined) {
|
|
7588
|
+
const prompted = await promptForVerificationCode(deps, deps.log, 3, { allowResend: true });
|
|
7589
|
+
if (prompted.kind === "give-up")
|
|
7590
|
+
throw new ModernInstallRecoveryError(INSTALL_BRIDGE_VERIFICATION_INCOMPLETE);
|
|
7591
|
+
if (prompted.kind === "empty") {
|
|
7592
|
+
consecutiveEmpty += 1;
|
|
7593
|
+
if (consecutiveEmpty >= 2)
|
|
7594
|
+
throw new ModernInstallRecoveryError(INSTALL_BRIDGE_VERIFICATION_INCOMPLETE);
|
|
7595
|
+
if (!emptyGuidanceShown) {
|
|
7596
|
+
deps.log(INSTALL_BRIDGE_EMPTY_CODE_GUIDANCE);
|
|
7597
|
+
emptyGuidanceShown = true;
|
|
7598
|
+
}
|
|
7599
|
+
continue;
|
|
7600
|
+
}
|
|
7601
|
+
consecutiveEmpty = 0;
|
|
7602
|
+
if (prompted.kind === "resend") {
|
|
7603
|
+
if (resendsRemaining === 0) {
|
|
7604
|
+
deps.log(INSTALL_BRIDGE_NO_MORE_CODES);
|
|
7605
|
+
continue;
|
|
7606
|
+
}
|
|
7607
|
+
// The server enforces the cooldown and the resend budget; there is no
|
|
7608
|
+
// client timer or cap. The stored expiry is never touched.
|
|
7609
|
+
const resent = await resendSelfServeChallenge(deps, baseUrl, handle);
|
|
7610
|
+
if (resent.ok) {
|
|
7611
|
+
deps.log(` ${INSTALL_BRIDGE_RESEND_SENT}`);
|
|
7612
|
+
resendsRemaining = resent.resendsRemaining;
|
|
7613
|
+
}
|
|
7614
|
+
else if (resent.category === "cooldown") {
|
|
7615
|
+
deps.log(` ${INSTALL_BRIDGE_RESEND_COOLDOWN}`);
|
|
7616
|
+
}
|
|
7617
|
+
else if (resent.category === "rejected") {
|
|
7618
|
+
// The uniform 401 also covers "no resends left", so the challenge may
|
|
7619
|
+
// still be alive: count it, but do not end the run on its own.
|
|
7620
|
+
countRejection();
|
|
7621
|
+
}
|
|
7622
|
+
else {
|
|
7623
|
+
deps.log(` ${INSTALL_BRIDGE_RESEND_THROTTLED}`);
|
|
7624
|
+
}
|
|
7625
|
+
continue;
|
|
7626
|
+
}
|
|
7627
|
+
const verified = await verifySelfServeChallenge(deps, baseUrl, handle, prompted.code);
|
|
7628
|
+
if (verified.ok) {
|
|
7629
|
+
// BAPI-1173: only an EXPLICIT false prints.
|
|
7630
|
+
if (verified.trialGranted === false)
|
|
7631
|
+
deps.log(` ${INSTALL_BRIDGE_TRIAL_ALREADY_USED}`);
|
|
7632
|
+
verifiedToken = verified.token;
|
|
7633
|
+
break;
|
|
7634
|
+
}
|
|
7635
|
+
if (verified.category === "rejected") {
|
|
7636
|
+
if (!countRejection() && resendsRemaining !== 0)
|
|
7637
|
+
deps.log(INSTALL_BRIDGE_REJECTED_RESEND_HINT);
|
|
7638
|
+
continue;
|
|
7639
|
+
}
|
|
7640
|
+
// Every other category ends the run and keeps the saved record.
|
|
7641
|
+
throw new ModernInstallRecoveryError(formatSelfServeVerifyFailure(verified));
|
|
7642
|
+
}
|
|
7643
|
+
if (verifiedToken !== undefined)
|
|
7644
|
+
break;
|
|
7645
|
+
// Rejection cap: offer one fresh signup (S5), unless one was already taken.
|
|
7646
|
+
if (freshSignupAccepted) {
|
|
7647
|
+
throw new ModernInstallRecoveryError(`${INSTALL_BRIDGE_VERIFICATION_GIVING_UP}\n\nRun it again from this same folder.`);
|
|
7648
|
+
}
|
|
7649
|
+
await askFreshSignup(INSTALL_BRIDGE_RECOVERY_REASON_CAP);
|
|
7650
|
+
await replaceWithFreshSignup(record);
|
|
7651
|
+
}
|
|
7652
|
+
token = verifiedToken;
|
|
7337
7653
|
fingerprint = fingerprintBootstrapInvite(token, baseUrl);
|
|
7338
7654
|
const inviteScope = makeScope(`bootstrap-pending:invite:${fingerprint}`);
|
|
7339
7655
|
const transitioned = await transitionModernBootstrapSignup(makeScope(signupLocator), inviteScope, fingerprint, token, record.challengeHandle, writeDeps);
|
|
@@ -7369,7 +7685,7 @@ async function redeemModernInstallAttempt(deps, options, baseUrl, token, signupE
|
|
|
7369
7685
|
if (!exchange.ok && exchange.message.includes("Creation outcome is unknown"))
|
|
7370
7686
|
throw new ModernInstallRecoveryError("Creation outcome is unknown. Preserve recovery proof and rerun the same attempt from the original folder and invite/signup.");
|
|
7371
7687
|
if (!exchange.ok || !exchange.displayName)
|
|
7372
|
-
throw new ModernInstallRecoveryError(
|
|
7688
|
+
throw new ModernInstallRecoveryError(`Creation or replay did not complete. Preserve proof and rerun from this folder with the same invite/signup; verify server compatibility or contact Bridge support at ${BRIDGE_SUPPORT_EMAIL}.`);
|
|
7373
7689
|
const checkpoint = await checkpointModernBootstrapIdentity(scope, record, exchange.repoName, exchange.displayName, writeDeps);
|
|
7374
7690
|
if (!checkpoint.ok || !checkpoint.record)
|
|
7375
7691
|
throw new Error(checkpoint.ok ? "Checkpoint missing." : checkpoint.error);
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* unit-testable with in-memory fakes.
|
|
18
18
|
*/
|
|
19
19
|
import { validPackagedAssetInventory } from "./generated-assets-context.js";
|
|
20
|
+
import { currentDestinationForLegacyDoc } from "./generated-assets-legacy.js";
|
|
20
21
|
import { executorIdFromLaunchdFilename, executorIdFromSystemdFilename, } from "./executor/service-unit.js";
|
|
21
22
|
import { HOST_PLATFORM_ORDER, isHostPlatformId, } from "./mcp-host-targets.js";
|
|
22
23
|
/** Current on-disk schema version. */
|
|
@@ -503,12 +504,17 @@ export async function recordPackagedAssetInventory(cwd, inventory, deps) {
|
|
|
503
504
|
return { ok: false, error: "Existing installation state cannot be safely preserved." };
|
|
504
505
|
const state = prior.status === "valid" ? prior.state : undefined;
|
|
505
506
|
const records = new Map();
|
|
507
|
+
// BAPI-1470: a record whose id or destination is one of the six moved procedure
|
|
508
|
+
// paths is dropped from both sides, so future inventories name only current ones.
|
|
509
|
+
const isMovedRecord = (a) => currentDestinationForLegacyDoc(a.id) !== undefined || currentDestinationForLegacyDoc(a.destination) !== undefined;
|
|
506
510
|
if (state?.packagedAssets?.rootBinding === inventory.rootBinding) {
|
|
507
511
|
for (const a of state.packagedAssets.assets)
|
|
508
|
-
|
|
512
|
+
if (!isMovedRecord(a))
|
|
513
|
+
records.set(`${a.host}:${a.id}`, a);
|
|
509
514
|
}
|
|
510
515
|
for (const a of inventory.assets)
|
|
511
|
-
|
|
516
|
+
if (!isMovedRecord(a))
|
|
517
|
+
records.set(`${a.host}:${a.id}`, a);
|
|
512
518
|
return writeMcpInstallState(cwd, { selectedPlatforms: state?.selectedPlatforms ?? [],
|
|
513
519
|
projectConfigPaths: state?.projectConfigPaths ?? [], packagedAssets: { ...inventory, assets: [...records.values()] } }, deps);
|
|
514
520
|
}
|
|
@@ -689,6 +689,12 @@ const LOCAL_GH_HINTS = {
|
|
|
689
689
|
machine_merge_credential_invalid: `The configured ${LOCAL_MERGE_CREDENTIAL_ENV_KEYS.machine_token} was rejected by GitHub. Rotate it and relaunch this MCP server.`,
|
|
690
690
|
local_merge_credential_unpermitted: `The configured ${LOCAL_MERGE_CREDENTIAL_ENV_KEYS.local} authenticates but cannot write to this repository. Grant it merge permission.`,
|
|
691
691
|
machine_merge_credential_unpermitted: `The configured ${LOCAL_MERGE_CREDENTIAL_ENV_KEYS.machine_token} authenticates but cannot write to this repository. Grant the machine user merge permission.`,
|
|
692
|
+
// BAPI-1452: GitHub gave no usable answer about the credential's access. Not
|
|
693
|
+
// proof of a missing permission, but a merge still refuses without evidence.
|
|
694
|
+
local_merge_probe_unavailable: `Could not confirm that ${LOCAL_MERGE_CREDENTIAL_ENV_KEYS.local} can push to this repository and read its pull requests ` +
|
|
695
|
+
"(GitHub returned no usable answer). Retry shortly; if it persists, check the token's repository access and GitHub's status.",
|
|
696
|
+
machine_merge_probe_unavailable: `Could not confirm that ${LOCAL_MERGE_CREDENTIAL_ENV_KEYS.machine_token} can push to this repository and read its pull requests ` +
|
|
697
|
+
"(GitHub returned no usable answer). Retry shortly; if it persists, check the machine user's repository access and GitHub's status.",
|
|
692
698
|
};
|
|
693
699
|
/**
|
|
694
700
|
* Read the repository's LEGACY merge execution mode.
|