@bridge_gpt/mcp-server 0.2.66 → 0.2.68

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 (171) hide show
  1. package/README.md +421 -83
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +70 -16
  4. package/build/agent-launchers/executor-adapter.js +7 -4
  5. package/build/agents.generated.js +22 -2
  6. package/build/bitbucket-workspace-remote.js +136 -0
  7. package/build/bootstrap-project-identity.js +24 -0
  8. package/build/bounded-wait.js +67 -10
  9. package/build/bridge-permissions.js +829 -0
  10. package/build/claude-settings.js +18 -0
  11. package/build/cli-help.js +21 -0
  12. package/build/command-assets-doctor.js +24 -70
  13. package/build/command-provisioning.js +29 -5
  14. package/build/commands.generated.js +12 -12
  15. package/build/conductor/bridge-api-client.js +857 -1
  16. package/build/conductor/bring-up-facts.js +1 -1
  17. package/build/conductor/command-drift-production.js +92 -0
  18. package/build/conductor/command-drift.js +89 -0
  19. package/build/conductor/doctor.js +72 -29
  20. package/build/conductor/epic-branch-ci-inspection.js +1375 -0
  21. package/build/conductor/epic-implementer-cli.js +98 -28
  22. package/build/conductor/errors.js +4 -3
  23. package/build/conductor/gate-discovery.js +890 -0
  24. package/build/conductor/guided-repair.js +6 -2
  25. package/build/conductor/hook-runtime-qualification.js +1 -1
  26. package/build/conductor/install-doctor.js +15 -3
  27. package/build/conductor/json-output.js +63 -0
  28. package/build/conductor/merge-execution-contract.js +27 -8
  29. package/build/conductor/merge-execution-eligibility.js +3 -2
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +113 -12
  32. package/build/conductor/readiness-sections.js +410 -9
  33. package/build/conductor/readiness.js +388 -33
  34. package/build/conductor/recovery-cli.js +310 -12
  35. package/build/conductor/recovery-operations.js +167 -3
  36. package/build/conductor/repair-contract.js +54 -3
  37. package/build/conductor/repository-workflow-remedy.js +125 -0
  38. package/build/conductor/tools.js +2 -2
  39. package/build/conductor-bundle-artifacts.js +8 -0
  40. package/build/connect-bitbucket-api.js +41 -6
  41. package/build/connect-bitbucket.js +106 -33
  42. package/build/credential-store.js +207 -47
  43. package/build/docs.generated.js +62 -3
  44. package/build/doctor.js +92 -1
  45. package/build/drive-epic-key.js +6 -5
  46. package/build/drive-epic.js +129 -61
  47. package/build/epic-coherence-input.js +2 -1
  48. package/build/epic-coherence-input.test-fixtures.js +4 -3
  49. package/build/epic-implementer-bin.js +1379 -28
  50. package/build/epic-implementer-bundle-cli.js +2 -2
  51. package/build/epic-workarounds-path.js +39 -0
  52. package/build/executor/cli.js +33 -2
  53. package/build/executor/completion-contract.js +66 -10
  54. package/build/executor/completion-supervision.js +4 -0
  55. package/build/executor/deps.js +4 -1
  56. package/build/executor/env.js +19 -0
  57. package/build/executor/gate-qualification-job.js +499 -0
  58. package/build/executor/guarded-fast-forward-push.js +129 -0
  59. package/build/executor/heartbeat.js +1 -1
  60. package/build/executor/http-client.js +20 -0
  61. package/build/executor/integration-job.js +122 -8
  62. package/build/executor/job-errors.js +33 -1
  63. package/build/executor/job-runner.js +813 -87
  64. package/build/executor/likely-tests.js +290 -0
  65. package/build/executor/live-worker-registry.js +1 -1
  66. package/build/executor/observation.js +37 -3
  67. package/build/executor/packaged-recipe.js +106 -0
  68. package/build/executor/permissions.js +115 -28
  69. package/build/executor/platform.js +1 -1
  70. package/build/executor/preflight.js +112 -26
  71. package/build/executor/process-containment.js +36 -12
  72. package/build/executor/process.js +15 -7
  73. package/build/executor/prompt-spec.js +33 -1
  74. package/build/executor/recently-failed-tests-file.js +71 -0
  75. package/build/executor/reconcile-overlap-job.js +41 -23
  76. package/build/executor/required-verification.js +1 -1
  77. package/build/executor/required-work-ledger.js +111 -18
  78. package/build/executor/resume-pre-spawn.js +2 -0
  79. package/build/executor/retirement.js +69 -0
  80. package/build/executor/runner.js +122 -13
  81. package/build/executor/service-unit.js +7 -1
  82. package/build/executor/test-clock.js +15 -0
  83. package/build/executor/types.js +32 -0
  84. package/build/executor/viewer-tabs.js +58 -8
  85. package/build/executor/worker-command.js +23 -7
  86. package/build/executor/worker-diagnostics.js +37 -0
  87. package/build/executor/worker-finalization.js +113 -34
  88. package/build/executor/worker-guard-hook.js +316 -58
  89. package/build/executor/worker-log.js +4 -3
  90. package/build/executor/worker-verification-guidance.js +37 -0
  91. package/build/executor/worktree.js +166 -8
  92. package/build/first-build-advisory.js +97 -0
  93. package/build/generated-assets-advisory.js +14 -0
  94. package/build/generated-assets-catalog.js +66 -0
  95. package/build/generated-assets-context.js +46 -0
  96. package/build/generated-assets-dependencies.js +26 -0
  97. package/build/generated-assets-doctor.js +50 -0
  98. package/build/generated-assets-health.js +65 -0
  99. package/build/generated-assets-host.js +17 -0
  100. package/build/generated-assets-inventory.js +17 -0
  101. package/build/generated-assets-paths.js +60 -0
  102. package/build/generated-assets-publisher.js +133 -0
  103. package/build/generated-assets-remedy.js +25 -0
  104. package/build/generated-assets-repair.js +72 -0
  105. package/build/generated-assets-session.js +71 -0
  106. package/build/generated-assets-test-support.js +35 -0
  107. package/build/index.js +1224 -1054
  108. package/build/init.js +54 -177
  109. package/build/install-bridge-conductor-gate.js +1372 -0
  110. package/build/install-bridge-conductor.js +296 -41
  111. package/build/install-bridge.js +547 -40
  112. package/build/mcp-install-state.js +50 -4
  113. package/build/merge-workflow-install.js +3 -4
  114. package/build/phase-context-git-probe.js +68 -0
  115. package/build/phase-result-artifacts.js +173 -5
  116. package/build/pipeline-utils.js +6 -2
  117. package/build/pipelines.generated.js +34 -160
  118. package/build/plan-execution-ledger.js +2 -32
  119. package/build/plane/alembic-head.js +12 -3
  120. package/build/plane/base-worktree-guard.js +147 -0
  121. package/build/plane/cli.js +217 -17
  122. package/build/plane/code-snapshot.js +468 -0
  123. package/build/plane/defaults.js +48 -3
  124. package/build/plane/endpoint.js +41 -0
  125. package/build/plane/manifest.js +136 -2
  126. package/build/plane/member-roster.js +94 -19
  127. package/build/plane/preflight.js +83 -2
  128. package/build/plane/process-cwd.js +67 -0
  129. package/build/plane/restart.js +542 -0
  130. package/build/plane/shutdown.js +31 -0
  131. package/build/plane/status.js +143 -8
  132. package/build/plane/supervisor.js +586 -179
  133. package/build/plane/types.js +54 -2
  134. package/build/printed-remedy-commands.js +52 -0
  135. package/build/readiness-check.js +1 -1
  136. package/build/readme.generated.js +1 -1
  137. package/build/repair-assets.js +93 -0
  138. package/build/review-generation.js +22 -0
  139. package/build/run-unit-tests-launcher.js +130 -49
  140. package/build/secret-redaction.js +33 -0
  141. package/build/setup-epic.js +152 -22
  142. package/build/sfcc/config.js +9 -0
  143. package/build/sfcc/credentials.js +100 -38
  144. package/build/sfcc/hostname.js +67 -0
  145. package/build/sfcc/permissions.js +17 -13
  146. package/build/sfcc/register.js +1 -1
  147. package/build/sfcc/tool-wrapper.js +39 -43
  148. package/build/sfcc/write-grants.js +17 -11
  149. package/build/sfcc/write-result.js +4 -3
  150. package/build/sfcc/writes-site-preference.js +20 -8
  151. package/build/start-tickets-prereqs.js +70 -16
  152. package/build/start-tickets.js +208 -36
  153. package/build/tool-error-envelope.js +61 -0
  154. package/build/tool-surface-gating.js +36 -0
  155. package/build/upgrade-cli.js +55 -1
  156. package/build/version.generated.js +3 -3
  157. package/build/visual-diff.js +22 -1
  158. package/build/worker-guard-hook-bin.js +4 -6
  159. package/build/worker-guard-hook-main.js +47 -0
  160. package/build/worktree-core.js +145 -6
  161. package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
  162. package/docs/EPIC-IMPLEMENTER.md +728 -0
  163. package/docs/install/mcp-tool-integrations.md +24 -15
  164. package/docs/install/sfcc-integration.md +12 -4
  165. package/package.json +3 -3
  166. package/pipelines/greenfield-setup.json +4 -128
  167. package/pipelines/implement-ticket.json +3 -2
  168. package/pipelines/plan-epic.json +0 -5
  169. package/pipelines/review-ticket.json +1 -1
  170. package/docs/CONDUCTOR.md +0 -605
  171. package/pipelines/idea-to-pr.json +0 -49
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Infer the Bitbucket workspace slug from this project's `origin` remote (BAPI-1401).
3
+ *
4
+ * `connect-bitbucket` sends the workspace it reads here to Bridge as a *claim*: it only
5
+ * decides which workspace's Forge settings page lists the connection request. It is not
6
+ * an identity and authorizes nothing — the approval itself happens on the Atlassian side
7
+ * and binds only within the workspace Bridge derives from the signed Forge invocation.
8
+ *
9
+ * Only bitbucket.org repository remotes are recognized, in three shapes:
10
+ *
11
+ * https://[user@]bitbucket.org/<workspace>/<repo>[.git]
12
+ * git@bitbucket.org:<workspace>/<repo>[.git]
13
+ * ssh://git@bitbucket.org[:port]/<workspace>/<repo>[.git]
14
+ *
15
+ * Anything else (another host, a lookalike host, a path without both segments, a
16
+ * malformed URL, blank output, a failed command) yields no workspace, and the caller
17
+ * keeps the legacy code flow. The remote URL and any process error are never printed:
18
+ * a remote can embed a credential (`https://user:token@…`).
19
+ *
20
+ * Git runs through an injected argument-array seam (never a shell), so this is
21
+ * unit-testable without a real repository.
22
+ */
23
+ import { execFile } from "child_process";
24
+ const BITBUCKET_HOST = "bitbucket.org";
25
+ /** Mirrors the server's workspace-slug allowlist (`normalize_bitbucket_slug`). */
26
+ const WORKSPACE_SLUG_RE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
27
+ const WORKSPACE_SLUG_MAX_LENGTH = 255;
28
+ /** Bounded so a hung git (a credential prompt, a slow filesystem) cannot stall the CLI. */
29
+ const GIT_TIMEOUT_MS = 5_000;
30
+ /** The default runner: `execFile` with an argument array, no shell, short timeout. */
31
+ export const defaultGitRemoteRunner = (args, cwd) => new Promise((resolve) => {
32
+ try {
33
+ execFile("git", args, { cwd, shell: false, timeout: GIT_TIMEOUT_MS, windowsHide: true }, (error, stdout) => {
34
+ if (error) {
35
+ resolve(null);
36
+ return;
37
+ }
38
+ resolve(typeof stdout === "string" ? stdout : String(stdout));
39
+ });
40
+ }
41
+ catch {
42
+ resolve(null);
43
+ }
44
+ });
45
+ function workspaceFromPath(pathPart) {
46
+ const segments = pathPart.replace(/^\/+/, "").split("/");
47
+ // A repository remote has exactly <workspace>/<repo>, optionally with a trailing slash.
48
+ const meaningful = segments.filter((segment, index) => segment !== "" || index < segments.length - 1);
49
+ if (meaningful.length !== 2)
50
+ return null;
51
+ const [workspace, repo] = meaningful;
52
+ if (!workspace || !repo || repo === ".git")
53
+ return null;
54
+ if (workspace.length > WORKSPACE_SLUG_MAX_LENGTH || !WORKSPACE_SLUG_RE.test(workspace)) {
55
+ return null;
56
+ }
57
+ return workspace;
58
+ }
59
+ /**
60
+ * Parse a remote URL into its Bitbucket workspace slug, or null when it is not a
61
+ * recognizable bitbucket.org repository remote. Pure; never throws.
62
+ */
63
+ export function parseBitbucketWorkspaceFromRemote(remote) {
64
+ if (typeof remote !== "string")
65
+ return null;
66
+ const value = remote.trim();
67
+ if (!value || /\s/.test(value) || /[\u0000-\u001F\u007F]/.test(value))
68
+ return null;
69
+ // scp-like SSH: git@bitbucket.org:<workspace>/<repo>.git
70
+ const scp = /^([A-Za-z0-9._-]+)@([^:/]+):(.+)$/.exec(value);
71
+ if (scp && !value.includes("://")) {
72
+ if (scp[2].toLowerCase() !== BITBUCKET_HOST)
73
+ return null;
74
+ return workspaceFromPath(scp[3]);
75
+ }
76
+ let parsed;
77
+ try {
78
+ parsed = new URL(value);
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ if (!["https:", "ssh:", "git+ssh:"].includes(parsed.protocol))
84
+ return null;
85
+ if (parsed.hostname.toLowerCase() !== BITBUCKET_HOST)
86
+ return null;
87
+ if (parsed.search || parsed.hash)
88
+ return null;
89
+ let pathname;
90
+ try {
91
+ pathname = decodeURIComponent(parsed.pathname);
92
+ }
93
+ catch {
94
+ return null;
95
+ }
96
+ return workspaceFromPath(pathname);
97
+ }
98
+ /**
99
+ * Read `git remote get-url origin` in `cwd` and return its Bitbucket workspace slug, or
100
+ * null. Never throws and never prints.
101
+ */
102
+ export async function inferBitbucketWorkspaceFromGitRemote(cwd, runGit = defaultGitRemoteRunner) {
103
+ let stdout;
104
+ try {
105
+ stdout = await runGit(["remote", "get-url", "origin"], cwd);
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ if (typeof stdout !== "string")
111
+ return null;
112
+ const firstLine = stdout.split(/\r?\n/)[0] ?? "";
113
+ return parseBitbucketWorkspaceFromRemote(firstLine);
114
+ }
115
+ /**
116
+ * Resolve the Bitbucket workspace for a connect run: an explicit `--workspace` value
117
+ * first (already validated by the caller's argument parser), otherwise the `origin`
118
+ * remote of `cwd`. An inferred slug has already passed the same allowlist as the flag
119
+ * (see {@link parseBitbucketWorkspaceFromRemote}) and is shown to the user, so a wrong
120
+ * remote is visible. Returns undefined when nothing usable is found — the connect flow
121
+ * then uses code mode. Never prints the remote URL.
122
+ *
123
+ * Lives here, not in `connect-bitbucket.ts`, so `install-bridge.ts` can share it
124
+ * without importing the connect flow module.
125
+ */
126
+ export async function resolveBitbucketWorkspace(explicit, cwd, runGit, log) {
127
+ if (explicit)
128
+ return explicit;
129
+ if (!runGit)
130
+ return undefined;
131
+ const inferred = await inferBitbucketWorkspaceFromGitRemote(cwd, runGit);
132
+ if (!inferred)
133
+ return undefined;
134
+ log(`Using Bitbucket workspace ${inferred}`);
135
+ return inferred;
136
+ }
@@ -0,0 +1,24 @@
1
+ /** Bootstrap labels are display data, never credential selectors. */
2
+ const whitespace = " \u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000";
3
+ const controls = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/u;
4
+ export function validateBootstrapDisplayName(value) {
5
+ if (typeof value !== "string" || controls.test(value))
6
+ throw new Error("Invalid project label.");
7
+ const label = value.replace(new RegExp(`^[${whitespace}]+|[${whitespace}]+$`, "gu"), "");
8
+ if (Array.from(label).length < 1 || Array.from(label).length > 255)
9
+ throw new Error("Invalid project label.");
10
+ return label;
11
+ }
12
+ export function validateBootstrapRepoName(value) {
13
+ if (typeof value !== "string" || value.length < 1 || value.length > 100 || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value))
14
+ throw new Error("Invalid project identity.");
15
+ return value;
16
+ }
17
+ export function validateBootstrapServerIdentity(value) {
18
+ if (!value || typeof value !== "object")
19
+ throw new Error("Invalid project identity response.");
20
+ const identity = value;
21
+ if (identity.identity_mode !== "server_assigned")
22
+ throw new Error("Unsupported project identity response.");
23
+ return { repo_name: validateBootstrapRepoName(identity.repo_name), repo_display_name: validateBootstrapDisplayName(identity.repo_display_name), identity_mode: "server_assigned" };
24
+ }
@@ -24,8 +24,8 @@
24
24
  * keeps the reason vocabulary below closed: the primitive can only report states
25
25
  * it can itself observe.
26
26
  *
27
- * NO RAW UPSTREAM DATA ESCAPES. A result carries a closed-vocabulary reason and
28
- * nothing else. Exception text, response bodies, headers, URLs, and credentials
27
+ * NO RAW UPSTREAM DATA ESCAPES. A result carries a closed-vocabulary reason plus
28
+ * whatever already-sanitized detail the caller's probe attached. Exception text, response bodies, headers, URLs, and credentials
29
29
  * are never read into a result — a caller that needs upstream detail must obtain
30
30
  * it inside its own probe and sanitize it there, the same bar
31
31
  * `formatRecoverablePollGiveUp` sets for the existing poller.
@@ -48,7 +48,39 @@ const TERMINAL_REASON_SENTENCES = {
48
48
  lost_after_retries: "The server restarted during the run and gave up after its automatic retries.",
49
49
  handler_failed: "The run failed on the server.",
50
50
  expired: "The run expired on the server before it finished.",
51
+ insufficient_ticket_detail: "The ticket description is too short for the server to work from.",
52
+ completed_without_artifact: "The run finished on the server without producing a document.",
51
53
  };
54
+ /** Plain sentences for the probe-named terminal reasons (BAPI-1382). */
55
+ const WAIT_REASON_SENTENCES = {
56
+ NO_REVIEW_REQUESTED: "No review request is known for this ticket, and none is running on the server.",
57
+ NO_REVIEW_RUNNING: "A review was submitted, but the server has no review running and no document for it.",
58
+ };
59
+ /** The plain sentence for a probe-named wait reason, or `null` for the others. */
60
+ export function formatWaitReasonSentence(reason) {
61
+ return WAIT_REASON_SENTENCES[reason] ?? null;
62
+ }
63
+ function isWholeNonNegative(value) {
64
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
65
+ }
66
+ /**
67
+ * The insufficient-detail sentence. It names whichever measurements the server
68
+ * sent — "26 of 200 characters" with both, the minimum alone when the length
69
+ * was not stored (the spawn-mode and review-run paths) — and otherwise falls
70
+ * back to the generic sentence, so a missing field is never printed as
71
+ * `undefined`. The server's `retry_hint` carries the next step.
72
+ */
73
+ function insufficientDetailSentence(detail) {
74
+ const measured = detail?.measuredLength;
75
+ const threshold = detail?.threshold;
76
+ if (isWholeNonNegative(measured) && isWholeNonNegative(threshold)) {
77
+ return `The ticket description is too short (${measured} of ${threshold} characters).`;
78
+ }
79
+ if (isWholeNonNegative(threshold)) {
80
+ return `The ticket description is shorter than the ${threshold}-character minimum.`;
81
+ }
82
+ return TERMINAL_REASON_SENTENCES.insufficient_ticket_detail;
83
+ }
52
84
  /**
53
85
  * Build the plain recovery message for a terminal outcome, or `null` when the
54
86
  * server sent neither a reason nor a retry hint (the caller then keeps its
@@ -60,6 +92,14 @@ export function formatTerminalRecoveryMessage(detail) {
60
92
  if (!reason && !retryHint)
61
93
  return null;
62
94
  const parts = [];
95
+ if (reason === "insufficient_ticket_detail") {
96
+ parts.push(insufficientDetailSentence(detail));
97
+ // The server's hint for this reason is a full sentence naming the fix and
98
+ // the tool call, so it is printed as-is rather than after "Retry with:".
99
+ parts.push(retryHint ||
100
+ "Lengthen the ticket description (use update_ticket_description), then submit a new request.");
101
+ return parts.join(" ");
102
+ }
63
103
  if (reason) {
64
104
  parts.push(TERMINAL_REASON_SENTENCES[reason] ?? `The server reported: ${reason}.`);
65
105
  }
@@ -123,12 +163,21 @@ export async function boundedWait(options) {
123
163
  signal?.addEventListener("abort", finish, { once: true });
124
164
  });
125
165
  let consecutiveFailures = 0;
166
+ // BAPI-1356: latched, never reset. Set only where the server actually
167
+ // answered; a throw, a probe timeout, or a transient streak leaves it alone.
168
+ let observed = false;
169
+ const pending = (reason) => ({
170
+ state: "pending",
171
+ reason,
172
+ observed,
173
+ windowMs: effectiveBudgetMs,
174
+ });
126
175
  try {
127
176
  while (true) {
128
177
  if (signal?.aborted)
129
178
  return { state: "error", reason: "ABORTED" };
130
179
  if (remaining() <= 0) {
131
- return { state: "pending", reason: "WAIT_WINDOW_ELAPSED" };
180
+ return pending("WAIT_WINDOW_ELAPSED");
132
181
  }
133
182
  // One controller per probe, aborted by EITHER the per-probe timeout or the
134
183
  // caller's signal. The probe therefore never outlives its own budget, and a
@@ -164,15 +213,22 @@ export async function boundedWait(options) {
164
213
  if (threw || timedOut || outcome === null) {
165
214
  consecutiveFailures += 1;
166
215
  if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
167
- return { state: "pending", reason: "UPSTREAM_TRANSIENT" };
216
+ return pending("UPSTREAM_TRANSIENT");
168
217
  }
169
218
  }
170
219
  else if (outcome.kind === "terminal") {
171
- // Recovery detail is additive: it is attached only when the probe
172
- // captured some, so a legacy terminal result keeps its exact shape.
173
- return outcome.recovery
174
- ? { state: "error", reason: "UPSTREAM_TERMINAL", recovery: outcome.recovery }
175
- : { state: "error", reason: "UPSTREAM_TERMINAL" };
220
+ // Recovery detail and upstream evidence are additive: each is attached
221
+ // only when the probe captured some, so a legacy terminal result keeps
222
+ // its exact shape.
223
+ const terminal = {
224
+ state: "error",
225
+ reason: outcome.reason ?? "UPSTREAM_TERMINAL",
226
+ };
227
+ if (outcome.recovery)
228
+ terminal.recovery = outcome.recovery;
229
+ if (outcome.upstream)
230
+ terminal.upstream = outcome.upstream;
231
+ return terminal;
176
232
  }
177
233
  else if (outcome.kind === "malformed") {
178
234
  return { state: "error", reason: "INVALID_RESPONSE" };
@@ -181,6 +237,7 @@ export async function boundedWait(options) {
181
237
  // The server answered. Reset the streak BEFORE evaluating readiness — an
182
238
  // answer of "not yet" is still proof the transport is healthy.
183
239
  consecutiveFailures = 0;
240
+ observed = true;
184
241
  if (outcome.ready === true) {
185
242
  const value = outcome.value;
186
243
  if (isReady === undefined || isReady(value)) {
@@ -189,7 +246,7 @@ export async function boundedWait(options) {
189
246
  }
190
247
  }
191
248
  if (remaining() <= 0) {
192
- return { state: "pending", reason: "WAIT_WINDOW_ELAPSED" };
249
+ return pending("WAIT_WINDOW_ELAPSED");
193
250
  }
194
251
  // The existing poller's curve plus bounded jitter, clamped to whatever is
195
252
  // actually left so a backoff can never overrun the deadline it is inside.