@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.
- package/README.md +45 -59
- package/build/agent-capabilities/types.js +4 -0
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +25 -5
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/codex-launch-scope.js +49 -0
- package/build/codex-skill-adapter.js +58 -0
- package/build/command-host.js +9 -0
- package/build/command-provisioning.js +35 -4
- package/build/commands.generated.js +9 -8
- package/build/conductor/bridge-api-client.js +199 -6
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +1 -1
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +37 -6
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +81 -18
- package/build/drive-epic.js +541 -115
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +274 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +617 -168
- package/build/init.js +74 -9
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +425 -79
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +19 -32
- package/build/mcp-provisioning.js +1 -1
- package/build/mcp-registration-doctor.js +1 -1
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +7 -7
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +36 -5
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +4 -4
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +50 -35
- package/build/run-unit-tests-launcher.js +1 -1
- package/build/setup-epic.js +69 -31
- package/build/start-tickets-conductor.js +8 -7
- package/build/start-tickets.js +266 -48
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/package.json +5 -3
package/build/start-tickets.js
CHANGED
|
@@ -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,
|
|
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
|
-
" --
|
|
130
|
-
"
|
|
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
|
|
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}' (
|
|
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
|
|
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
|
|
1916
|
-
//
|
|
1917
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
2625
|
-
*
|
|
2626
|
-
*
|
|
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 =
|
|
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}'.
|
|
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
|
-
|
|
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}'.
|
|
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.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
4
|
-
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
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"];
|