@bridge_gpt/mcp-server 0.2.54 → 0.2.56

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 (89) hide show
  1. package/README.md +45 -59
  2. package/build/agent-capabilities/types.js +4 -0
  3. package/build/agent-guidance.generated.js +3 -0
  4. package/build/agent-launchers/claude-executor-adapter.js +3 -0
  5. package/build/agent-notes.js +178 -0
  6. package/build/agent-registry.js +25 -5
  7. package/build/agent-utils.js +58 -0
  8. package/build/agents.generated.js +1 -1
  9. package/build/codex-launch-scope.js +49 -0
  10. package/build/codex-skill-adapter.js +58 -0
  11. package/build/command-host.js +9 -0
  12. package/build/command-provisioning.js +35 -4
  13. package/build/commands.generated.js +9 -8
  14. package/build/conductor/bridge-api-client.js +199 -6
  15. package/build/conductor/bring-up-facts.js +187 -0
  16. package/build/conductor/claude-hook.js +7 -5
  17. package/build/conductor/cli.js +28 -0
  18. package/build/conductor/doctor.js +80 -9
  19. package/build/conductor/epic-implementer-cli.js +1298 -0
  20. package/build/conductor/epic-runtime.js +1 -1
  21. package/build/conductor/errors.js +2 -2
  22. package/build/conductor/git-ci-types.js +1 -1
  23. package/build/conductor/git-hooks.js +28 -14
  24. package/build/conductor/install-doctor.js +11 -5
  25. package/build/conductor/readiness-cli.js +10 -10
  26. package/build/conductor/readiness-sections.js +58 -9
  27. package/build/conductor/readiness.js +37 -6
  28. package/build/conductor/recovery-cli.js +289 -10
  29. package/build/conductor/recovery-operations.js +125 -2
  30. package/build/conductor/repair-contract.js +58 -0
  31. package/build/conductor/store.js +2 -2
  32. package/build/conductor/supervisor-runtime.js +1 -1
  33. package/build/conductor-bin.js +2 -139
  34. package/build/conductor-claude-hook-bin.js +2 -2
  35. package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
  36. package/build/conductor-removed-stub-bin.js +30 -0
  37. package/build/docs.generated.js +1 -1
  38. package/build/doctor.js +81 -18
  39. package/build/drive-epic.js +541 -115
  40. package/build/epic-implementer-bin.js +145 -0
  41. package/build/epic-implementer-bundle-cli.js +264 -0
  42. package/build/epic-implementer-claude-hook-bin.js +3 -0
  43. package/build/epic-integration-pr.js +5 -3
  44. package/build/executor/env.js +6 -0
  45. package/build/executor/interrupted-worktree.js +60 -0
  46. package/build/executor/job-errors.js +45 -0
  47. package/build/executor/job-runner.js +274 -9
  48. package/build/executor/job-types.js +25 -9
  49. package/build/executor/merge-tree-classifier.js +171 -0
  50. package/build/executor/reconcile-overlap-governance.js +129 -0
  51. package/build/executor/reconcile-overlap-job.js +989 -0
  52. package/build/executor/reconcile-overlap-types.js +14 -0
  53. package/build/executor/spawn-job-driver.js +1 -0
  54. package/build/executor/types.js +2 -0
  55. package/build/executor/worker-finalization.js +25 -2
  56. package/build/executor/worker-guard-hook.js +15 -7
  57. package/build/implement-epic/bridge-client.js +773 -0
  58. package/build/implement-epic/checkpoint-store.js +542 -0
  59. package/build/implement-epic/cli.js +3158 -0
  60. package/build/implement-epic/cut-protocol.js +392 -0
  61. package/build/implement-epic/lock.js +302 -0
  62. package/build/implement-epic/pr-state.js +286 -0
  63. package/build/implement-epic/spawn.js +113 -0
  64. package/build/index.js +617 -168
  65. package/build/init.js +74 -9
  66. package/build/install-bridge-conductor.js +5 -5
  67. package/build/install-bridge.js +425 -79
  68. package/build/mcp-host-config.js +22 -60
  69. package/build/mcp-host-entry-adapter.js +18 -0
  70. package/build/mcp-host-targets.js +19 -32
  71. package/build/mcp-provisioning.js +1 -1
  72. package/build/mcp-registration-doctor.js +1 -1
  73. package/build/merge-pull-request.js +1 -1
  74. package/build/pipelines.generated.js +7 -7
  75. package/build/plan-epic-conductor-eligibility.js +1 -1
  76. package/build/plane/cli.js +36 -5
  77. package/build/plane/preflight.js +128 -12
  78. package/build/plane/shutdown.js +4 -4
  79. package/build/readiness-check.js +3 -3
  80. package/build/readme.generated.js +1 -1
  81. package/build/review-tickets.js +50 -35
  82. package/build/run-unit-tests-launcher.js +1 -1
  83. package/build/setup-epic.js +69 -31
  84. package/build/start-tickets-conductor.js +8 -7
  85. package/build/start-tickets.js +266 -48
  86. package/build/version.generated.js +3 -3
  87. package/build/worker-guard-hook-bin.js +1 -1
  88. package/docs/CONDUCTOR.md +8 -6
  89. package/package.json +5 -3
@@ -56,6 +56,7 @@
56
56
  import { execFile } from "child_process";
57
57
  import { readFile, writeFile, mkdir, mkdtemp, stat, lstat, symlink, unlink, copyFile, readdir, rm, } from "fs/promises";
58
58
  import os from "node:os";
59
+ import { createHash } from "node:crypto";
59
60
  import path from "path";
60
61
  import { VERSION } from "./version.generated.js";
61
62
  import { resolveBapiCredentials, getPrimaryCredentialStorePath, } from "./credential-store.js";
@@ -74,7 +75,10 @@ import { TICKET_KEY_PATTERN as SHARED_TICKET_KEY_PATTERN, TICKET_KEY_USAGE_SUMMA
74
75
  // can never drift. `start-tickets.ts` imports VALUES from there; the prereqs
75
76
  // module imports only TYPES back, so the runtime graph stays acyclic.
76
77
  import { WORKTRUNK_BINARY_OVERRIDE_ENV, WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, DEFAULT_WINDOWS_WORKTRUNK_BINARY, DEFAULT_POSIX_WORKTRUNK_BINARY, TMUX_COMMAND, GIT_FOR_WINDOWS_BASH_HINT, isSupportedStartTicketsPlatform, unsupportedPlatformMessage, resolveWorktrunkBinary, commandSucceeded, getCommandProbe, isCommandOnPath, resolveFirstCommandOnPath, enforcePreflightPrerequisites, appendDoctorHint, } from "./start-tickets-prereqs.js";
77
- import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, formatValidAgentNames, resolveModelAlias, isValidModelAlias, isModelTier, } from "./agent-registry.js";
78
+ import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, resolveModelAlias, isValidModelAlias, isModelTier, } from "./agent-registry.js";
79
+ import { agentForPlatform, getHostTarget, getWorktreeHostTargets, isRunnableHostTarget, isHostPlatformId } from "./mcp-host-targets.js";
80
+ import { resolveCodexLaunchScope } from "./codex-launch-scope.js";
81
+ import { tmpdir } from "node:os";
78
82
  import { createStartTicketsConductorContext, provisionConductorHooksForRows, emitStartTicketsRunStarted, injectConductorEnvIntoShellCommand, buildSupervisorTabCommand, isSupervisorLaunchEnabled, supervisorSpawnKey, } from "./start-tickets-conductor.js";
79
83
  import { transitionEpicDispatch, resolveConductorBridgeApiAccess, } from "./conductor/bridge-api-client.js";
80
84
  import { PR_BASE_BRANCH_ENV_VAR, buildPrBaseContractLaunchInstruction, } from "./pr-base-contract.js";
@@ -125,9 +129,11 @@ export function getStartTicketsUsage() {
125
129
  ` npx -y ${MCP_PACKAGE_NAME} start-tickets [flags] KEY [KEY ...]`,
126
130
  "",
127
131
  "Flags:",
128
- " --agent claude|cursor-agent Agent command to launch in each worktree (default: claude)",
129
- " --workflow implement|review-and-implement Slash command each spawned worktree runs (default: implement). review-and-implement runs /review-ticket then, after a per-ticket halt gate, /implement-ticket in the same session; --auto applies to the selected workflow.",
130
- " --tier cheap|basic|premium Coarse model-routing override: bypasses the per-ticket difficulty/tier lookup and applies this tier to every ticket. It is still mapped to a model through the agent registry and any configured difficulty_model_tier_overrides, then validated — it is NOT a raw --model alias, and never carries an API key or credential. A malformed value fails open to premium routing.",
132
+ " --agent claude|cursor-agent|codex Agent command to launch in each worktree (overrides --host; default: claude)",
133
+ " --host claude-code|cursor|codex|copilot-vscode|windsurf Current host when --agent is omitted; Claude Code, Cursor, and Codex can launch sessions",
134
+ " Cursor with --auto launches headlessly after a bounded readiness check; without --auto it stays interactive for the review gate.",
135
+ " --workflow implement|review-and-implement Command each spawned worktree runs (default: implement). review-and-implement runs review-ticket then, after a per-ticket halt gate, hands off to a fresh implement-ticket session; --auto applies to both phases.",
136
+ " --tier cheap|basic|premium Coarse model-routing override: bypasses the per-ticket difficulty/tier lookup and applies this tier to every ticket. Claude/Cursor use registry aliases and repository overrides; Codex uses the authenticated Bridge catalog. It is NOT a raw --model alias and never carries an API key or credential. A malformed value requests premium routing when available.",
131
137
  " --rounds 1|2 Review round count forwarded to the review phase; review-only, valid only with --workflow review-and-implement",
132
138
  " --terminal terminal|iterm Override the macOS terminal app (default: auto-detect via $TERM_PROGRAM); honored on macOS only",
133
139
  " --dry-run Print intended actions; creates no worktrees and opens no tabs, but DOES resolve model routing read-only (may compute+cache a ticket's difficulty) to preview the --model each tab would use",
@@ -163,6 +169,13 @@ export function getStartTicketsUsage() {
163
169
  TICKET_KEY_USAGE_SUMMARY,
164
170
  ].join("\n");
165
171
  }
172
+ /** Agent names backed by a runnable, worktree-scoped host registration. */
173
+ function formatStartTicketsAgentNames() {
174
+ return getWorktreeHostTargets()
175
+ .filter(isRunnableHostTarget)
176
+ .map((target) => target.launchAgent)
177
+ .join(", ");
178
+ }
166
179
  /**
167
180
  * Parse argv strictly and without an arg-parser dependency. Returns a
168
181
  * discriminated result: help, a validation error, or validated options.
@@ -177,7 +190,8 @@ export function parseStartTicketsArgs(argv) {
177
190
  let autoApprove = false;
178
191
  let refreshMain = true;
179
192
  let maxParallelRaw;
180
- let agentName = DEFAULT_AGENT_NAME;
193
+ let agentName;
194
+ let hostPlatform;
181
195
  let baseBranch = "main";
182
196
  let conductorEnabled = false;
183
197
  // BAPI-948: `--guard-stale-branch` (and `--conductor`, which implies it) opt the
@@ -217,12 +231,20 @@ export function parseStartTicketsArgs(argv) {
217
231
  if (!isAgentName(value)) {
218
232
  return {
219
233
  status: "error",
220
- message: `Invalid --agent value: '${value}' (allowed agents: ${formatValidAgentNames()}).`,
234
+ message: `Invalid --agent value: '${value}' (launchable agents: ${formatStartTicketsAgentNames()}).`,
221
235
  };
222
236
  }
223
237
  agentName = value;
224
238
  continue;
225
239
  }
240
+ if (arg === "--host" || arg.startsWith("--host=")) {
241
+ const value = arg.startsWith("--host=") ? arg.slice("--host=".length) : takeValue();
242
+ if (!value || !isHostPlatformId(value)) {
243
+ return { status: "error", message: `Invalid --host value: '${value ?? ""}'.` };
244
+ }
245
+ hostPlatform = value;
246
+ continue;
247
+ }
226
248
  if (arg === "--workflow" || arg.startsWith("--workflow=")) {
227
249
  let value;
228
250
  if (arg.startsWith("--workflow=")) {
@@ -452,6 +474,14 @@ export function parseStartTicketsArgs(argv) {
452
474
  }
453
475
  reviewRounds = reviewRoundsRaw === "1" ? 1 : 2;
454
476
  }
477
+ const target = hostPlatform ? getHostTarget(hostPlatform) : undefined;
478
+ if (!agentName && target && !isRunnableHostTarget(target)) {
479
+ return {
480
+ status: "error",
481
+ message: `${target.label} supports MCP configuration but cannot spawn a Bridge session. Continue the workflow manually or pass an explicit --agent override.`,
482
+ };
483
+ }
484
+ const resolvedAgentName = agentName ?? (hostPlatform ? agentForPlatform(hostPlatform) : DEFAULT_AGENT_NAME);
455
485
  return {
456
486
  status: "ok",
457
487
  options: {
@@ -462,7 +492,7 @@ export function parseStartTicketsArgs(argv) {
462
492
  refreshMain,
463
493
  maxParallel,
464
494
  branchOverrides,
465
- agentName,
495
+ agentName: resolvedAgentName,
466
496
  baseBranch,
467
497
  conductorEnabled,
468
498
  workflow,
@@ -550,7 +580,7 @@ baseBranch) {
550
580
  // falls back to the basename-derived repo name (the 403 root cause), and
551
581
  // (BAPI-586/BAPI-801) the run base so the spawned worker opens its PR
552
582
  // against it. The base assignment stays outermost.
553
- buildAgentShellCommand: (key, worktreePath, modelAlias) => prependBaseBranchEnvAssignment(prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch), repoName, platform), prBaseBranch, platform),
583
+ buildAgentShellCommand: (key, worktreePath, modelAlias, launchArgs) => prependBaseBranchEnvAssignment(prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch, launchArgs), repoName, platform), prBaseBranch, platform),
554
584
  spawnTerminalTab: deps.spawnTerminalTab,
555
585
  },
556
586
  };
@@ -775,6 +805,11 @@ export async function runPreflight(deps, options, warn = (message) => console.wa
775
805
  const enforceLiveSourceGuard = options.nonMutatingBase === true || options.epic !== undefined;
776
806
  const result = await enforcePreflightPrerequisites(deps, { enforceLiveSourceGuard });
777
807
  if (result.ok) {
808
+ if (options.agentName === "cursor-agent" && options.autoApprove) {
809
+ const cursorReady = await preflightHeadlessCursor(deps);
810
+ if (!cursorReady.ok)
811
+ return cursorReady;
812
+ }
778
813
  // Overridden collision: not silent — surface the warning through the channel.
779
814
  if (result.warning)
780
815
  warn(result.warning);
@@ -786,6 +821,52 @@ export async function runPreflight(deps, options, warn = (message) => console.wa
786
821
  // Both missing-prerequisite and live-source-collision are doctor-diagnosable.
787
822
  return { ok: false, error: appendDoctorHint(result.error) };
788
823
  }
824
+ /** Validate the actual headless path before creating any ticket worktree. */
825
+ async function preflightHeadlessCursor(deps) {
826
+ let help;
827
+ try {
828
+ help = await deps.runCommand("cursor-agent", ["--help"], { timeoutMs: 5000 });
829
+ }
830
+ catch {
831
+ return { ok: false, error: "Cursor Agent is unavailable. Install cursor-agent and ensure it is on PATH." };
832
+ }
833
+ if (help.exitCode !== 0) {
834
+ return { ok: false, error: "Cursor Agent is unavailable. Install cursor-agent and ensure it is on PATH." };
835
+ }
836
+ for (const flag of ["--print", "--output-format", "--trust", "--workspace", "--force", "--approve-mcps"]) {
837
+ if (!help.stdout.includes(flag)) {
838
+ return { ok: false, error: `Cursor Agent does not support ${flag}. Update cursor-agent before using --auto.` };
839
+ }
840
+ }
841
+ let probeDir;
842
+ try {
843
+ probeDir = await mkdtemp(path.join(tmpdir(), "bridge-cursor-ready-"));
844
+ }
845
+ catch {
846
+ return { ok: false, error: "Could not create a temporary workspace for the Cursor Agent readiness check." };
847
+ }
848
+ try {
849
+ const probe = await deps.runCommand("cursor-agent", [
850
+ "-p", "--output-format", "text", "--trust", "--workspace", probeDir,
851
+ "Reply with exactly BAPI_CURSOR_READY. Do not use tools.",
852
+ ], { cwd: probeDir, timeoutMs: 60000 });
853
+ if (probe.exitCode !== 0 || !probe.stdout.includes("BAPI_CURSOR_READY")) {
854
+ return { ok: false, error: "Cursor Agent could not complete a headless authentication check. Run cursor-agent login or configure CURSOR_API_KEY, then retry." };
855
+ }
856
+ return { ok: true };
857
+ }
858
+ catch {
859
+ return { ok: false, error: "Cursor Agent headless readiness check failed. Verify authentication and retry." };
860
+ }
861
+ finally {
862
+ try {
863
+ await rm(probeDir, { recursive: true, force: true });
864
+ }
865
+ catch {
866
+ // Cleanup failure must not turn a bounded readiness result into a raw error.
867
+ }
868
+ }
869
+ }
789
870
  // ---------------------------------------------------------------------------
790
871
  // main refresh + worktree parsing
791
872
  // ---------------------------------------------------------------------------
@@ -1192,17 +1273,23 @@ export function buildAgentPrompt(key, opts = {}) {
1192
1273
  // injected as a `--model` flag (see buildAgentInvocationArgv), never embedded
1193
1274
  // in the prompt text.
1194
1275
  const workflow = opts.workflow ?? "implement";
1195
- const head = workflow === "review-and-implement" ? "/review-and-implement" : "/implement-ticket";
1276
+ const commandName = workflow === "review-and-implement" ? "review-and-implement" : "implement-ticket";
1277
+ // Codex skills use explicit invocation names; Claude and Cursor use commands.
1278
+ const head = opts.agentName === "codex" ? `$bridge-${commandName}` : `/${commandName}`;
1196
1279
  let command = `${head} ${key}${opts.autoApprove ? " --auto" : ""}`;
1197
1280
  // Review-only arguments (`--rounds`, non-default `--base-branch`) must never
1198
1281
  // leak into the legacy `/implement-ticket` prompt.
1199
1282
  if (workflow === "review-and-implement") {
1283
+ if (opts.agentName === "cursor-agent")
1284
+ command += " --agent=cursor-agent";
1200
1285
  if (opts.reviewRounds !== undefined) {
1201
1286
  command += ` --rounds=${opts.reviewRounds}`;
1202
1287
  }
1203
1288
  if (opts.baseBranch !== undefined && opts.baseBranch !== "main") {
1204
1289
  command += ` --base-branch='${shSquoteInner(opts.baseBranch)}'`;
1205
1290
  }
1291
+ if (opts.agentName === "codex")
1292
+ command += " --agent codex --host codex";
1206
1293
  }
1207
1294
  const parts = [command];
1208
1295
  if (opts.conductorEnabled) {
@@ -1224,8 +1311,13 @@ export function buildAgentPrompt(key, opts = {}) {
1224
1311
  * otherwise they are omitted entirely (fail-open). The prompt is always the
1225
1312
  * final argument. Pure and never throws on an invalid alias.
1226
1313
  */
1227
- export function buildAgentInvocationArgv(agent, prompt, modelAlias) {
1314
+ export function buildAgentInvocationArgv(agent, prompt, modelAlias, launchArgsOrCursorWorkspace = []) {
1228
1315
  const argv = [agent.command];
1316
+ if (agent.name === "codex" && Array.isArray(launchArgsOrCursorWorkspace))
1317
+ argv.push(...launchArgsOrCursorWorkspace);
1318
+ if (agent.name === "cursor-agent" && typeof launchArgsOrCursorWorkspace === "string" && launchArgsOrCursorWorkspace) {
1319
+ argv.push("-p", "--output-format", "text", "--trust", "--approve-mcps", "--force", "--workspace", launchArgsOrCursorWorkspace);
1320
+ }
1229
1321
  if (agent.supportsModelOverride &&
1230
1322
  typeof modelAlias === "string" &&
1231
1323
  isValidModelAlias(modelAlias)) {
@@ -1241,10 +1333,10 @@ export function buildAgentInvocationArgv(agent, prompt, modelAlias) {
1241
1333
  * `--model <alias>` pair and the prompt — is run through the platform-correct
1242
1334
  * `quote`.
1243
1335
  */
1244
- export function buildAgentInvocation(agent, prompt, quote, modelAlias) {
1336
+ export function buildAgentInvocation(agent, prompt, quote, modelAlias, launchArgsOrCursorWorkspace = []) {
1245
1337
  switch (agent.promptArgStyle) {
1246
1338
  case "positional": {
1247
- const [command, ...rest] = buildAgentInvocationArgv(agent, prompt, modelAlias);
1339
+ const [command, ...rest] = buildAgentInvocationArgv(agent, prompt, modelAlias, launchArgsOrCursorWorkspace);
1248
1340
  const quotedRest = rest.map(quote);
1249
1341
  return [command, ...quotedRest].join(" ");
1250
1342
  }
@@ -1255,13 +1347,13 @@ export function buildAgentInvocation(agent, prompt, quote, modelAlias) {
1255
1347
  }
1256
1348
  }
1257
1349
  /** POSIX agent shell command: `cd '<path>' && <agent> [--model '<alias>'] '<prompt>'`. */
1258
- export function buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1259
- const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), (p) => `'${shSquoteInner(p)}'`, modelAlias);
1350
+ export function buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch, launchArgs = []) {
1351
+ const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch, agentName: agent.name }), (p) => `'${shSquoteInner(p)}'`, modelAlias, agent.name === "cursor-agent" && autoApprove ? worktreePath : launchArgs);
1260
1352
  return `cd '${shSquoteInner(worktreePath)}' && ${invocation}`;
1261
1353
  }
1262
1354
  /** PowerShell agent shell command: `Set-Location -LiteralPath '<path>'; <agent> [--model '<alias>'] '<prompt>'`. */
1263
- export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1264
- const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch }), powershellSquote, modelAlias);
1355
+ export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch, launchArgs = []) {
1356
+ const invocation = buildAgentInvocation(agent, buildAgentPrompt(key, { autoApprove, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch, agentName: agent.name }), powershellSquote, modelAlias, agent.name === "cursor-agent" && autoApprove ? worktreePath : launchArgs);
1265
1357
  return `Set-Location -LiteralPath ${powershellSquote(worktreePath)}; ${invocation}`;
1266
1358
  }
1267
1359
  /**
@@ -1272,10 +1364,10 @@ export function buildPowerShellAgentShellCommand(agent, key, worktreePath, autoA
1272
1364
  * injected as `--model` at the spawn boundary. `conductorEnabled` appends the
1273
1365
  * BAPI-586 PR-base contract instruction to the prompt (opt-in via `--conductor`).
1274
1366
  */
1275
- export function buildAgentShellCommand(agent, key, worktreePath, platform = "darwin", autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch) {
1367
+ export function buildAgentShellCommand(agent, key, worktreePath, platform = "darwin", autoApprove = false, modelAlias, conductorEnabled = false, resumeMode = false, workflow = "implement", reviewRounds, baseBranch, launchArgs = []) {
1276
1368
  if (platform === "win32")
1277
- return buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch);
1278
- return buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch);
1369
+ return buildPowerShellAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch, launchArgs);
1370
+ return buildPosixAgentShellCommand(agent, key, worktreePath, autoApprove, modelAlias, conductorEnabled, resumeMode, workflow, reviewRounds, baseBranch, launchArgs);
1279
1371
  }
1280
1372
  /**
1281
1373
  * Build the shell command run inside a spawned tab/session for an ARBITRARY
@@ -1857,7 +1949,7 @@ export async function spawnTabsForCreatedWorktrees(deps, rows, terminal, buildSh
1857
1949
  out.push(row);
1858
1950
  continue;
1859
1951
  }
1860
- const baseShellCommand = buildShellCommand(row.key, row.path, row.modelAlias ?? null);
1952
+ const baseShellCommand = buildShellCommand(row.key, row.path, row.modelAlias ?? null, row.codexLaunchArgs);
1861
1953
  // Scope the row's conductor identity env to this terminal/tab/session only
1862
1954
  // (BAPI-394). No-op when the row carries no conductorEnv (dry-run, non-Claude
1863
1955
  // agents, or conductor disabled). Never mutates process/global env.
@@ -1912,9 +2004,11 @@ export function getDryRunPlatformDetails(agent, platform = process.platform, env
1912
2004
  // and (BAPI-801/BAPI-847) the PR base as a BAPI_BASE_BRANCH prefix through the
1913
2005
  // SAME resolvePrBaseBranchEnvValue rule — including the ordinary `main` case —
1914
2006
  // and the same outermost ordering the real spawn builder uses; dry-run shares
1915
- // both environment renderings with real spawning. Reuses the same buildAgentShellCommand/buildAgentPrompt path as a
1916
- // real spawn — dry-run is never special-cased — so the preview is exact.
1917
- buildAgentShellCommand: (key, worktreePath, modelAlias) => prependBaseBranchEnvAssignment(prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, false, workflow, reviewRounds, baseBranch), repoName, platform), resolvePrBaseBranchEnvValue(conductorEnabled, baseBranch), platform),
2007
+ // both environment renderings with real spawning. Reuses the same
2008
+ // buildAgentShellCommand/buildAgentPrompt path as a real spawn. A Codex
2009
+ // dry-run has no created worktree to bind, so its preview omits the
2010
+ // process-local -C/-c MCP scope that the real spawn resolves later.
2011
+ buildAgentShellCommand: (key, worktreePath, modelAlias, launchArgs) => prependBaseBranchEnvAssignment(prependRepoNameEnvAssignment(buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove, modelAlias, conductorEnabled, false, workflow, reviewRounds, baseBranch, launchArgs), repoName, platform), resolvePrBaseBranchEnvValue(conductorEnabled, baseBranch), platform),
1918
2012
  };
1919
2013
  }
1920
2014
  /**
@@ -2044,9 +2138,9 @@ export function formatSummaryReport(rows) {
2044
2138
  */
2045
2139
  export function buildMcpProvisioningDeps(deps) {
2046
2140
  return {
2047
- readFile: (filePath) => readFile(filePath, "utf-8"),
2048
- writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
2049
- mkdir: (dirPath, options) => mkdir(dirPath, options),
2141
+ readFile: deps.readFile ?? ((filePath) => readFile(filePath, "utf-8")),
2142
+ writeFile: deps.writeFile ?? ((filePath, data) => writeFile(filePath, data, "utf-8")),
2143
+ mkdir: deps.mkdir ?? ((dirPath, options) => mkdir(dirPath, options)),
2050
2144
  platform: deps.platform,
2051
2145
  cwd: deps.cwd,
2052
2146
  // Resolve how the worker MCP shim launches at dispatch time from the running
@@ -2127,7 +2221,9 @@ export function formatModelRoutingDiagnosticLine(d) {
2127
2221
  const repo = d.repoName ?? "<unknown>";
2128
2222
  const target = d.storeTarget ?? (d.repoName ? `bapi:${d.repoName}` : "bapi:<repo>");
2129
2223
  const storePath = d.storePath ?? "~/.config/bridge/credentials.json";
2130
- const failOpen = "Assuming hard ticket; defaulting to premium/Opus when available.";
2224
+ const failOpen = d.agentName === "codex"
2225
+ ? "Codex continues with its configured default model."
2226
+ : "Assuming hard ticket; defaulting to premium/Opus when available.";
2131
2227
  const ticketSuffix = d.ticketKey ? ` for ${d.ticketKey}` : "";
2132
2228
  const statusSuffix = typeof d.status === "number" ? ` (HTTP ${d.status})` : "";
2133
2229
  switch (d.kind) {
@@ -2165,6 +2261,12 @@ export function formatModelRoutingDiagnosticLine(d) {
2165
2261
  case "no-tier":
2166
2262
  return (`model routing: Bridge API returned no usable tier${ticketSuffix}. Ticket spawning ` +
2167
2263
  `continues fail-open. ${failOpen}`);
2264
+ case "catalog-unavailable":
2265
+ return `model routing degraded: Codex model catalog unavailable. ${failOpen}`;
2266
+ case "catalog-invalid":
2267
+ return `model routing degraded: Codex model catalog failed validation. ${failOpen}`;
2268
+ case "codex-entitlement-unverified":
2269
+ return "model routing: Bridge permits the selected model; local Codex account entitlement cannot be verified non-interactively.";
2168
2270
  default:
2169
2271
  return `model routing degraded: ${d.message} ${failOpen}`;
2170
2272
  }
@@ -2176,6 +2278,7 @@ export function formatModelRoutingDiagnosticLine(d) {
2176
2278
  export const START_TICKETS_DEFAULT_BASE_URL = "https://bridgegpt-api.com";
2177
2279
  const TICKET_MODEL_TIER_FETCH_TIMEOUT_MS = 75_000;
2178
2280
  const CONFIG_FIELD_FETCH_TIMEOUT_MS = 30_000;
2281
+ const CODEX_CATALOG_FETCH_TIMEOUT_MS = 15_000;
2179
2282
  const CURSOR_MODEL_LIST_TIMEOUT_MS = 15_000;
2180
2283
  /** GET auth headers for the CLI's Bridge API calls. */
2181
2284
  function startTicketsGetHeaders(access) {
@@ -2388,10 +2491,12 @@ export async function fetchStartTicketsConfigField(access, fieldName) {
2388
2491
  * Any fetch failure returns a structured failure (with a classified diagnostic)
2389
2492
  * so the caller fails open to premium/Opus.
2390
2493
  */
2391
- export async function fetchDifficultyModelRoutingConfig(access) {
2494
+ export async function fetchDifficultyModelRoutingConfig(access, includeOverrides = true) {
2392
2495
  try {
2393
2496
  const enabledValue = await fetchStartTicketsConfigField(access, "difficulty_model_routing_enabled");
2394
- const overridesValue = await fetchStartTicketsConfigField(access, "difficulty_model_tier_overrides");
2497
+ const overridesValue = includeOverrides
2498
+ ? await fetchStartTicketsConfigField(access, "difficulty_model_tier_overrides")
2499
+ : undefined;
2395
2500
  const enabled = enabledValue === false ? false : true;
2396
2501
  return { ok: true, config: { enabled, overrides: normalizeDifficultyModelTierOverrides(overridesValue) } };
2397
2502
  }
@@ -2407,6 +2512,57 @@ export async function fetchDifficultyModelRoutingConfig(access) {
2407
2512
  };
2408
2513
  }
2409
2514
  }
2515
+ /** One bounded authenticated fetch; reject partial, unsafe, or stale wire data. */
2516
+ export async function fetchCodexModelCatalog(access) {
2517
+ let body;
2518
+ try {
2519
+ const url = buildStartTicketsJiraUrl(access.baseUrl, "/codex-model-catalog", {
2520
+ repo_name: access.repoName,
2521
+ });
2522
+ body = await fetchJsonWithTimeout(url, startTicketsGetHeaders(access), CODEX_CATALOG_FETCH_TIMEOUT_MS);
2523
+ }
2524
+ catch (err) {
2525
+ return {
2526
+ ok: false,
2527
+ warning: "model routing: Codex catalog unavailable; using configured default",
2528
+ diagnostic: {
2529
+ ...diagnosticFromFetchError(err, {
2530
+ repoName: access.repoName,
2531
+ fallbackKind: "catalog-unavailable",
2532
+ message: "Codex model catalog unavailable",
2533
+ }),
2534
+ agentName: "codex",
2535
+ },
2536
+ };
2537
+ }
2538
+ const invalid = () => ({
2539
+ ok: false,
2540
+ warning: "model routing: Codex catalog invalid; using configured default",
2541
+ diagnostic: makeModelRoutingDiagnostic({
2542
+ kind: "catalog-invalid",
2543
+ repoName: access.repoName,
2544
+ agentName: "codex",
2545
+ message: "Codex model catalog failed validation",
2546
+ }),
2547
+ });
2548
+ if (!body || typeof body !== "object" || Array.isArray(body))
2549
+ return invalid();
2550
+ const obj = body;
2551
+ if (!obj.models || typeof obj.models !== "object" || Array.isArray(obj.models))
2552
+ return invalid();
2553
+ const models = obj.models;
2554
+ const tiers = ["cheap", "basic", "premium"];
2555
+ if (Object.keys(models).length !== tiers.length)
2556
+ return invalid();
2557
+ if (!tiers.every((tier) => typeof models[tier] === "string" && isValidModelAlias(models[tier]))) {
2558
+ return invalid();
2559
+ }
2560
+ const canonical = tiers.map((tier) => `${tier}=${models[tier]}`).join("\n");
2561
+ const revision = `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
2562
+ if (obj.revision !== revision)
2563
+ return invalid();
2564
+ return { ok: true, catalog: { models: models, revision } };
2565
+ }
2410
2566
  /**
2411
2567
  * Fetch the coarse tier for one ticket with a bounded timeout. Never throws to
2412
2568
  * the batch orchestrator: failures/timeouts return a structured warning plus a
@@ -2532,6 +2688,24 @@ export function applyDefaultModelRoutingMetadata(row, reason, diagnostic) {
2532
2688
  ...(diagnostic ? { modelRoutingDiagnostic: diagnostic } : {}),
2533
2689
  };
2534
2690
  }
2691
+ function resolveRoutingAlias(agent, tier, overrides, catalog) {
2692
+ return agent.name === "codex"
2693
+ ? catalog?.models[tier] ?? null
2694
+ : resolveModelAlias(agent, tier, overrides);
2695
+ }
2696
+ function codexEntitlementDiagnostic(repoName) {
2697
+ return makeModelRoutingDiagnostic({
2698
+ kind: "codex-entitlement-unverified",
2699
+ agentName: "codex",
2700
+ repoName,
2701
+ message: "Local Codex account entitlement cannot be verified non-interactively",
2702
+ });
2703
+ }
2704
+ function codexDefaultRows(rows, isEligible, warning, diagnostic) {
2705
+ return rows.map((row) => isEligible(row)
2706
+ ? appendSummaryRowWarning(applyDefaultModelRoutingMetadata(row, warning, diagnostic ? { ...diagnostic, agentName: "codex" } : undefined), warning)
2707
+ : row);
2708
+ }
2535
2709
  /**
2536
2710
  * Resolve + validate the premium (Opus) alias ONCE and return a reusable applier.
2537
2711
  *
@@ -2547,12 +2721,14 @@ export function applyDefaultModelRoutingMetadata(row, reason, diagnostic) {
2547
2721
  * (e.g. it doesn't support --model, or verification fails) the applier falls back
2548
2722
  * to the true no-routing default so we never emit an invalid --model.
2549
2723
  */
2550
- async function makePremiumFallbackApplier(deps, agent, overrides) {
2551
- const alias = resolveModelAlias(agent, "premium", overrides);
2724
+ async function makePremiumFallbackApplier(deps, agent, overrides, catalog = null, repoName) {
2725
+ const alias = resolveRoutingAlias(agent, "premium", overrides, catalog);
2552
2726
  const validation = alias ? await validateResolvedModelAliasForAgent(deps, agent, alias) : null;
2553
2727
  return (row, warning, diagnostic) => {
2554
2728
  if (validation && validation.ok) {
2555
- const reason = `${warning}; defaulting to premium/Opus (model=${validation.alias})`;
2729
+ const reason = agent.name === "codex"
2730
+ ? `${warning}; defaulting to premium (model=${validation.alias})`
2731
+ : `${warning}; defaulting to premium/Opus (model=${validation.alias})`;
2556
2732
  return appendSummaryRowWarning({
2557
2733
  ...row,
2558
2734
  difficulty: row.difficulty ?? null,
@@ -2560,7 +2736,11 @@ async function makePremiumFallbackApplier(deps, agent, overrides) {
2560
2736
  modelAlias: validation.alias,
2561
2737
  modelRoutingSource: "fallback",
2562
2738
  modelRoutingReason: reason,
2563
- ...(diagnostic ? { modelRoutingDiagnostic: diagnostic } : {}),
2739
+ ...(diagnostic
2740
+ ? { modelRoutingDiagnostic: agent.name === "codex" ? { ...diagnostic, agentName: "codex" } : diagnostic }
2741
+ : agent.name === "codex" && repoName
2742
+ ? { modelRoutingDiagnostic: codexEntitlementDiagnostic(repoName) }
2743
+ : {}),
2564
2744
  }, warning);
2565
2745
  }
2566
2746
  // Premium alias unavailable for this agent — fall back to agent default.
@@ -2591,14 +2771,14 @@ const isDryRunRoutingEligible = (r) => r.status === "dry-run";
2591
2771
  * validated ONCE (identical for every row) so a cursor-agent batch fires at most
2592
2772
  * one advertised-model probe.
2593
2773
  */
2594
- async function applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, tier) {
2774
+ async function applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, tier, catalog = null, repoName) {
2595
2775
  const stampInjected = (row) => ({
2596
2776
  ...row,
2597
2777
  difficulty: null,
2598
2778
  modelTier: tier,
2599
2779
  modelRoutingSource: "injected",
2600
2780
  });
2601
- const alias = resolveModelAlias(agent, tier, overrides);
2781
+ const alias = resolveRoutingAlias(agent, tier, overrides, catalog);
2602
2782
  if (!alias) {
2603
2783
  const warning = `model routing: no valid alias for injected tier=${tier}; using agent default`;
2604
2784
  return rows.map((r) => isEligible(r)
@@ -2616,15 +2796,17 @@ async function applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, ag
2616
2796
  ...stampInjected(r),
2617
2797
  modelAlias: validation.alias,
2618
2798
  modelRoutingReason: `injected tier=${tier} model=${validation.alias}`,
2799
+ ...(agent.name === "codex" && repoName
2800
+ ? { modelRoutingDiagnostic: codexEntitlementDiagnostic(repoName) }
2801
+ : {}),
2619
2802
  }
2620
2803
  : r);
2621
2804
  }
2622
2805
  /**
2623
2806
  * Shared fail-open routing resolution. `isEligible` selects which rows get a
2624
- * resolved tier/alias; every failure mode (unsupported agent, credential/config/
2625
- * tier fetch failure, backend fallback, invalid/unavailable alias) is converted
2626
- * to `modelAlias: null` plus a row warning, and never throws. Non-eligible rows
2627
- * pass through untouched.
2807
+ * resolved tier/alias. Codex fails open to its configured default with no
2808
+ * `--model`; Claude/Cursor retain their premium fallback. Non-eligible rows
2809
+ * pass through untouched, and routing failures never abort worktree creation.
2628
2810
  */
2629
2811
  async function resolveModelRoutingForEligible(deps, rows, options, agent, isEligible) {
2630
2812
  const eligible = rows.filter(isEligible);
@@ -2636,13 +2818,19 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2636
2818
  }
2637
2819
  const accessResult = await resolveStartTicketsBridgeApiAccess(deps);
2638
2820
  if (!accessResult.ok) {
2821
+ if (agent.name === "codex") {
2822
+ return codexDefaultRows(rows, isEligible, accessResult.warning, accessResult.diagnostic);
2823
+ }
2639
2824
  // Genuine error reaching the backend — default unknown difficulty to premium/Opus,
2640
2825
  // stamping the access diagnostic onto every eligible row.
2641
2826
  return applyPremiumFallbackToEligibleRows(deps, rows, isEligible, agent, null, accessResult.warning, accessResult.diagnostic);
2642
2827
  }
2643
2828
  const access = accessResult.access;
2644
- const configResult = await fetchDifficultyModelRoutingConfig(access);
2829
+ const configResult = await fetchDifficultyModelRoutingConfig(access, agent.name !== "codex");
2645
2830
  if (!configResult.ok) {
2831
+ if (agent.name === "codex") {
2832
+ return codexDefaultRows(rows, isEligible, configResult.warning, configResult.diagnostic);
2833
+ }
2646
2834
  // Can't read routing config — treat as error and default to premium/Opus.
2647
2835
  return applyPremiumFallbackToEligibleRows(deps, rows, isEligible, agent, null, configResult.warning, configResult.diagnostic);
2648
2836
  }
@@ -2651,6 +2839,14 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2651
2839
  const reason = "model routing: disabled for this repo; using agent default";
2652
2840
  return rows.map((r) => (isEligible(r) ? applyDefaultModelRoutingMetadata(r, reason) : r));
2653
2841
  }
2842
+ let catalog = null;
2843
+ if (agent.name === "codex") {
2844
+ const result = await fetchCodexModelCatalog(access);
2845
+ if (!result.ok) {
2846
+ return codexDefaultRows(rows, isEligible, result.warning, result.diagnostic);
2847
+ }
2848
+ catalog = result.catalog;
2849
+ }
2654
2850
  // BAPI-642: an explicitly injected `--tier` short-circuits the per-ticket
2655
2851
  // difficulty/tier lookup — `fetchTicketModelTiersForRows` is NEVER called for
2656
2852
  // any injected value. A recognized tier is applied to every eligible row; a
@@ -2660,11 +2856,11 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2660
2856
  const injected = options.injectedTier;
2661
2857
  if (injected === INJECTED_TIER_UNRESOLVED) {
2662
2858
  const warning = "model routing: --tier was not one of cheap|basic|premium; defaulting to premium routing";
2663
- const apply = await makePremiumFallbackApplier(deps, agent, overrides);
2859
+ const apply = await makePremiumFallbackApplier(deps, agent, overrides, catalog, access.repoName);
2664
2860
  return rows.map((r) => (isEligible(r) ? apply(r, warning) : r));
2665
2861
  }
2666
2862
  if (injected) {
2667
- return applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, injected);
2863
+ return applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, injected, catalog, access.repoName);
2668
2864
  }
2669
2865
  const tierMap = await fetchTicketModelTiersForRows(access, eligible, options.maxParallel, isEligible);
2670
2866
  // Lazily resolve+validate the premium fallback alias at most once for this run
@@ -2672,7 +2868,7 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2672
2868
  let premiumApplier = null;
2673
2869
  const applyPremiumFallback = async (row, warning, diagnostic) => {
2674
2870
  if (!premiumApplier)
2675
- premiumApplier = await makePremiumFallbackApplier(deps, agent, overrides);
2871
+ premiumApplier = await makePremiumFallbackApplier(deps, agent, overrides, catalog, access.repoName);
2676
2872
  return premiumApplier(row, warning, diagnostic);
2677
2873
  };
2678
2874
  const out = [];
@@ -2697,6 +2893,10 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2697
2893
  ticketKey: row.key,
2698
2894
  message: `no tier resolved for ${row.key}`,
2699
2895
  });
2896
+ if (agent.name === "codex") {
2897
+ out.push(codexDefaultRows([row], () => true, warning, diagnostic)[0]);
2898
+ continue;
2899
+ }
2700
2900
  out.push(await applyPremiumFallback(row, warning, diagnostic));
2701
2901
  continue;
2702
2902
  }
@@ -2718,10 +2918,14 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2718
2918
  ticketKey: row.key,
2719
2919
  message: `${row.key} resolved no tier (source=${source})`,
2720
2920
  });
2921
+ if (agent.name === "codex") {
2922
+ out.push(codexDefaultRows([baseRow], () => true, warning, diagnostic)[0]);
2923
+ continue;
2924
+ }
2721
2925
  out.push(await applyPremiumFallback(baseRow, warning, diagnostic));
2722
2926
  continue;
2723
2927
  }
2724
- const alias = resolveModelAlias(agent, tier, overrides);
2928
+ const alias = resolveRoutingAlias(agent, tier, overrides, catalog);
2725
2929
  if (!alias) {
2726
2930
  const warning = `model routing: no valid alias for tier=${tier}; using agent default`;
2727
2931
  out.push(appendSummaryRowWarning({ ...baseRow, modelAlias: null, modelRoutingReason: warning }, warning));
@@ -2736,6 +2940,9 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
2736
2940
  ...baseRow,
2737
2941
  modelAlias: validation.alias,
2738
2942
  modelRoutingReason: `tier=${tier} model=${validation.alias} (source=${source})`,
2943
+ ...(agent.name === "codex"
2944
+ ? { modelRoutingDiagnostic: codexEntitlementDiagnostic(access.repoName) }
2945
+ : {}),
2739
2946
  });
2740
2947
  }
2741
2948
  return out;
@@ -2978,7 +3185,7 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
2978
3185
  if (!agent) {
2979
3186
  return {
2980
3187
  ok: false,
2981
- error: `Unknown agent: '${options.agentName}'. Valid agents: ${formatValidAgentNames()}.`,
3188
+ error: `Unknown agent: '${options.agentName}'. Launchable agents: ${formatStartTicketsAgentNames()}.`,
2982
3189
  };
2983
3190
  }
2984
3191
  const preflight = await runPreflight(deps, options, overrides.liveSourceWarningLog);
@@ -3052,7 +3259,7 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
3052
3259
  await emitClaudeReviewWorkflowDriftAdvisory(deps, options.baseBranch, overrides);
3053
3260
  const createWorktreesFn = overrides.createWorktrees ?? createWorktrees;
3054
3261
  const provisionCommandsFn = overrides.provisionCommands ??
3055
- ((rows, d) => provisionCommandsForCreatedWorktrees(rows, buildCommandProvisioningDeps(d)));
3262
+ ((rows, d) => provisionCommandsForCreatedWorktrees(rows, buildCommandProvisioningDeps(d), agent.name === "codex"));
3056
3263
  const provisionFn = overrides.provisionMcpRegistrations ??
3057
3264
  ((rows, d) => provisionMcpRegistrationsForCreatedWorktrees(rows, buildMcpProvisioningDeps(d)));
3058
3265
  const materializeFn = overrides.materializeFileCredentials ?? materializeFileCredentialsForCreatedWorktrees;
@@ -3166,7 +3373,18 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
3166
3373
  // BAPI-844: stamp the validated scope on the spawnable rows immediately before
3167
3374
  // the spawn, so the value the tab is launched with is the one this run
3168
3375
  // declared. A run with no scope gets the identical `routed` array back.
3169
- const scoped = stampIndexScopeOnRows(routed, indexScope);
3376
+ let scoped = stampIndexScopeOnRows(routed, indexScope);
3377
+ if (agent.name === "codex") {
3378
+ const mcpDeps = buildMcpProvisioningDeps(deps);
3379
+ scoped = await Promise.all(scoped.map(async (row) => {
3380
+ if (row.status !== "created" || !row.path)
3381
+ return row;
3382
+ const scope = await (overrides.resolveCodexScope ?? resolveCodexLaunchScope)(row.path, mcpDeps);
3383
+ return scope.ok
3384
+ ? { ...row, codexLaunchArgs: scope.argv }
3385
+ : { ...row, status: "spawn-failed", error: scope.error };
3386
+ }));
3387
+ }
3170
3388
  const rows = await spawnTabsFn(deps, scoped, terminal, platformConfig.config.buildAgentShellCommand);
3171
3389
  return { ok: true, rows };
3172
3390
  }
@@ -3215,7 +3433,7 @@ export async function runStartTicketsCli(argv, overrides = {}) {
3215
3433
  // is defensive only.
3216
3434
  const agent = resolveAgentSpec(options.agentName);
3217
3435
  if (!agent) {
3218
- errorLog(`Error: Unknown agent: '${options.agentName}'. Valid agents: ${formatValidAgentNames()}.`);
3436
+ errorLog(`Error: Unknown agent: '${options.agentName}'. Launchable agents: ${formatStartTicketsAgentNames()}.`);
3219
3437
  return 1;
3220
3438
  }
3221
3439
  // BAPI-642: a malformed `--tier` value is fail-open — emit ONE concise,
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.54";
3
- export const BUILD_COMMIT = "7b8210791648";
4
- export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.54", "serve"];
2
+ export const VERSION = "0.2.56";
3
+ export const BUILD_COMMIT = "ba95aad09dd4";
4
+ export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.56", "serve"];