@bridge_gpt/mcp-server 0.2.24 → 0.2.26
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 +98 -28
- package/build/agents.generated.js +1 -1
- package/build/bridge-api-urls.js +31 -0
- package/build/commands.generated.js +5 -5
- package/build/conductor/epic-reconcile.js +7 -1
- package/build/conductor/epic-runtime.js +5 -0
- package/build/conductor-bundle-artifacts.js +802 -0
- package/build/conductor-bundle-cli.js +256 -0
- package/build/connect-github-api.js +365 -0
- package/build/connect-github.js +415 -0
- package/build/decision-page-schema.js +34 -5
- package/build/decision-page-template.js +117 -35
- package/build/docs.generated.js +2 -1
- package/build/doctor.js +148 -1
- package/build/env-flags.js +31 -0
- package/build/index.js +3467 -498
- package/build/init.js +7 -3
- package/build/install-bridge.js +624 -38
- package/build/install-doctor.js +64 -0
- package/build/mcp-host-config.js +521 -0
- package/build/mcp-host-targets.js +194 -0
- package/build/mcp-install-state.js +175 -0
- package/build/pipelines.generated.js +127 -132
- package/build/readme.generated.js +1 -1
- package/build/start-tickets.js +166 -18
- package/build/tool-surface-gating.js +396 -0
- package/build/version.generated.js +1 -1
- package/docs/install/github-app.md +80 -17
- package/docs/install/mcp-tool-integrations.md +2 -2
- package/package.json +5 -5
- package/pipelines/learn-repository.json +111 -119
- package/public/css/main.min.css +258 -65
- package/public/css/main.min.css.map +1 -1
- package/public/js/main.min.js +188 -92
- package/public/js/main.min.js.map +1 -1
- package/smoke-test/SMOKE-TEST.md +4 -4
package/build/start-tickets.js
CHANGED
|
@@ -87,6 +87,16 @@ export const DEFAULT_MAX_PARALLEL = 3;
|
|
|
87
87
|
export const DEFAULT_TMUX_SESSION_PREFIX = "bridge-start-tickets";
|
|
88
88
|
/** Environment variable overriding the tmux session-name prefix. */
|
|
89
89
|
export const TMUX_SESSION_OVERRIDE_ENV = "BAPI_TMUX_SESSION";
|
|
90
|
+
/**
|
|
91
|
+
* BAPI-642 — internal marker for an explicitly supplied `--tier` value that is
|
|
92
|
+
* NOT one of the three recognized {@link ModelTier} names (a malformed or empty
|
|
93
|
+
* flag). It is deliberately distinct from an OMITTED flag (the option property is
|
|
94
|
+
* absent entirely in that case): an unresolved marker records that the operator
|
|
95
|
+
* asked for a coarse override we could not honor, so the routing layer fails open
|
|
96
|
+
* to the premium fallback rather than silently entering difficulty-based routing.
|
|
97
|
+
* The sentinel string is never a valid `ModelTier`, so `isModelTier` rejects it.
|
|
98
|
+
*/
|
|
99
|
+
export const INJECTED_TIER_UNRESOLVED = "__unresolved__";
|
|
90
100
|
/** Return a copy of `row` with `warning` appended; never mutates the input. */
|
|
91
101
|
export function appendSummaryRowWarning(row, warning) {
|
|
92
102
|
return { ...row, warnings: [...(row.warnings ?? []), warning] };
|
|
@@ -103,6 +113,7 @@ export function getStartTicketsUsage() {
|
|
|
103
113
|
"Flags:",
|
|
104
114
|
" --agent claude|cursor-agent Agent command to launch in each worktree (default: claude)",
|
|
105
115
|
" --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.",
|
|
116
|
+
" --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.",
|
|
106
117
|
" --rounds 1|2 Review round count forwarded to the review phase; review-only, valid only with --workflow review-and-implement",
|
|
107
118
|
" --terminal terminal|iterm Override the macOS terminal app (default: auto-detect via $TERM_PROGRAM); honored on macOS only",
|
|
108
119
|
" --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",
|
|
@@ -156,6 +167,10 @@ export function parseStartTicketsArgs(argv) {
|
|
|
156
167
|
let conductorEnabled = false;
|
|
157
168
|
let workflow = "implement";
|
|
158
169
|
let reviewRoundsRaw;
|
|
170
|
+
// BAPI-642 `--tier` override. `undefined` = flag absent (property omitted from
|
|
171
|
+
// the parsed options); a `ModelTier` = usable override; INJECTED_TIER_UNRESOLVED
|
|
172
|
+
// = supplied-but-malformed (fail open to premium). NEVER an error/throw.
|
|
173
|
+
let injectedTier;
|
|
159
174
|
const branchEntries = [];
|
|
160
175
|
const keys = [];
|
|
161
176
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -231,6 +246,26 @@ export function parseStartTicketsArgs(argv) {
|
|
|
231
246
|
reviewRoundsRaw = value;
|
|
232
247
|
continue;
|
|
233
248
|
}
|
|
249
|
+
if (arg === "--tier" || arg.startsWith("--tier=")) {
|
|
250
|
+
// BAPI-642: fail-open coarse routing override. A malformed/empty/missing
|
|
251
|
+
// value is NOT a parse error — it records INJECTED_TIER_UNRESOLVED so the
|
|
252
|
+
// routing layer can fall back to premium instead of aborting the launch.
|
|
253
|
+
let value;
|
|
254
|
+
if (arg.startsWith("--tier=")) {
|
|
255
|
+
value = arg.slice("--tier=".length);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
// For the space-separated form, refuse to swallow a following option or
|
|
259
|
+
// ticket-key token as the value (leaving it to be parsed normally);
|
|
260
|
+
// an absent value fails open to the unresolved marker.
|
|
261
|
+
const next = i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
262
|
+
if (next !== undefined && !next.startsWith("-") && !TICKET_KEY_PATTERN.test(next)) {
|
|
263
|
+
value = takeValue();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
injectedTier = isModelTier(value) ? value : INJECTED_TIER_UNRESOLVED;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
234
269
|
if (arg === "--terminal" || arg.startsWith("--terminal=")) {
|
|
235
270
|
let value;
|
|
236
271
|
if (arg.startsWith("--terminal=")) {
|
|
@@ -412,6 +447,10 @@ export function parseStartTicketsArgs(argv) {
|
|
|
412
447
|
conductorEnabled,
|
|
413
448
|
workflow,
|
|
414
449
|
reviewRounds,
|
|
450
|
+
// BAPI-642: include the injected-tier property ONLY when `--tier` was
|
|
451
|
+
// supplied, so an omitted flag leaves the legacy option shape untouched
|
|
452
|
+
// (no own `injectedTier` key, not even `undefined`).
|
|
453
|
+
...(injectedTier !== undefined ? { injectedTier } : {}),
|
|
415
454
|
},
|
|
416
455
|
};
|
|
417
456
|
}
|
|
@@ -1469,9 +1508,10 @@ const defaultPruneStaleLaunchScriptsDeps = {
|
|
|
1469
1508
|
* recursively. Newer entries and unrelated files/dirs are left untouched.
|
|
1470
1509
|
*
|
|
1471
1510
|
* Fully fail-open: any error (missing parent dir, unreadable entry, stat/unlink
|
|
1472
|
-
* failure) is swallowed and never blocks or aborts a spawn
|
|
1473
|
-
*
|
|
1474
|
-
* {@link
|
|
1511
|
+
* failure) is swallowed and never blocks or aborts a spawn. (Unconditionally so,
|
|
1512
|
+
* unlike {@link materializeWorkerLaunchCommand}, whose inline fallback is gated on
|
|
1513
|
+
* {@link MAX_TERMINAL_COMMAND_BYTES} — pruning a stale temp dir has no launchable/
|
|
1514
|
+
* unlaunchable axis to gate on.)
|
|
1475
1515
|
*/
|
|
1476
1516
|
export async function pruneStaleLaunchScripts(deps = defaultPruneStaleLaunchScriptsDeps) {
|
|
1477
1517
|
try {
|
|
@@ -1523,27 +1563,54 @@ export const defaultWriteWorkerLaunchScript = async ({ platform, key, content, }
|
|
|
1523
1563
|
await writeFile(file, content, { mode: 0o600 });
|
|
1524
1564
|
return file;
|
|
1525
1565
|
};
|
|
1566
|
+
/**
|
|
1567
|
+
* Canonical maximum UTF-8 byte length of a single command line handed to a
|
|
1568
|
+
* terminal spawner. macOS `osascript`-driven Terminal/iTerm keystroke delivery
|
|
1569
|
+
* silently truncates (or mangles) a longer line, so a command at or below this
|
|
1570
|
+
* bound is "known-launchable" and anything above it is "known-unlaunchable".
|
|
1571
|
+
*
|
|
1572
|
+
* Exported so the launcher and every install-flow guard read the SAME number:
|
|
1573
|
+
* two independently-written literals would silently drift apart, and the whole
|
|
1574
|
+
* point of the bound is that the launcher's fallback decision and the caller's
|
|
1575
|
+
* final pre-spawn check agree.
|
|
1576
|
+
*/
|
|
1577
|
+
export const MAX_TERMINAL_COMMAND_BYTES = 1024;
|
|
1526
1578
|
/**
|
|
1527
1579
|
* Resolve the command actually handed to the terminal spawner for one worker.
|
|
1528
1580
|
* When `deps.writeWorkerLaunchScript` is provided, the full command is persisted
|
|
1529
|
-
* to a script and a short `source <path>` runner is returned
|
|
1530
|
-
* for any reason the original inline command is returned (fail-open — a launch
|
|
1531
|
-
* never aborts because a temp file could not be written). When the seam is
|
|
1581
|
+
* to a script and a short `source <path>` runner is returned. When the seam is
|
|
1532
1582
|
* absent, the inline command is returned unchanged (legacy behaviour).
|
|
1583
|
+
*
|
|
1584
|
+
* The fail-open on a failed write is CONDITIONAL, and the condition is the point.
|
|
1585
|
+
* Falling back to the inline command is only a fallback if the terminal can
|
|
1586
|
+
* actually run it: above {@link MAX_TERMINAL_COMMAND_BYTES} the spawner truncates
|
|
1587
|
+
* the line, so "fail-open" would deliver a corrupted command — a silent, confusing
|
|
1588
|
+
* failure strictly worse than a loud one. So a failed write falls back inline only
|
|
1589
|
+
* at or below the bound, and returns a structured failure above it.
|
|
1533
1590
|
*/
|
|
1534
1591
|
export async function materializeWorkerLaunchCommand(deps, key, fullCommand) {
|
|
1535
1592
|
if (!deps.writeWorkerLaunchScript)
|
|
1536
|
-
return fullCommand;
|
|
1593
|
+
return { ok: true, command: fullCommand };
|
|
1537
1594
|
try {
|
|
1538
1595
|
const scriptPath = await deps.writeWorkerLaunchScript({
|
|
1539
1596
|
platform: deps.platform,
|
|
1540
1597
|
key,
|
|
1541
1598
|
content: buildLaunchScriptContent(deps.platform, fullCommand),
|
|
1542
1599
|
});
|
|
1543
|
-
return buildLaunchScriptRunnerCommand(deps.platform, scriptPath);
|
|
1600
|
+
return { ok: true, command: buildLaunchScriptRunnerCommand(deps.platform, scriptPath) };
|
|
1544
1601
|
}
|
|
1545
1602
|
catch {
|
|
1546
|
-
|
|
1603
|
+
// Bytes, not `.length`: the bound is a byte bound, and a multibyte prompt can
|
|
1604
|
+
// sit under 1024 JS characters while being well over 1024 UTF-8 bytes.
|
|
1605
|
+
if (Buffer.byteLength(fullCommand, "utf8") <= MAX_TERMINAL_COMMAND_BYTES) {
|
|
1606
|
+
return { ok: true, command: fullCommand };
|
|
1607
|
+
}
|
|
1608
|
+
return {
|
|
1609
|
+
ok: false,
|
|
1610
|
+
reason: "launch-script-write-failed-oversized-command",
|
|
1611
|
+
error: "Could not write the temporary launch script, and the full command is too long to send " +
|
|
1612
|
+
"to the terminal directly. Check that the system temporary directory is writable.",
|
|
1613
|
+
};
|
|
1547
1614
|
}
|
|
1548
1615
|
}
|
|
1549
1616
|
// ---------------------------------------------------------------------------
|
|
@@ -1569,10 +1636,17 @@ export async function spawnTabsForCreatedWorktrees(deps, rows, terminal, buildSh
|
|
|
1569
1636
|
// agents, or conductor disabled). Never mutates process/global env.
|
|
1570
1637
|
const shellCommand = injectConductorEnvIntoShellCommand(deps.platform, baseShellCommand, row.conductorEnv);
|
|
1571
1638
|
// Deliver the (potentially multi-KB) command via a launch-script file so the
|
|
1572
|
-
// terminal spawn payload stays tiny and escaping-immune.
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1575
|
-
const
|
|
1639
|
+
// terminal spawn payload stays tiny and escaping-immune. No-op when no writer
|
|
1640
|
+
// seam is configured; a failed write falls back inline only while the command
|
|
1641
|
+
// is still launchable (see materializeWorkerLaunchCommand).
|
|
1642
|
+
const materialized = await materializeWorkerLaunchCommand(deps, row.key, shellCommand);
|
|
1643
|
+
if (!materialized.ok) {
|
|
1644
|
+
// Known-unlaunchable: spawning the oversized inline command would deliver a
|
|
1645
|
+
// truncated line. Fail this row only — siblings still launch.
|
|
1646
|
+
out.push({ ...row, status: "spawn-failed", error: materialized.error });
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
const result = await deps.spawnTerminalTab(deps, terminal, materialized.command, {
|
|
1576
1650
|
key: row.key,
|
|
1577
1651
|
worktreePath: row.path,
|
|
1578
1652
|
});
|
|
@@ -2251,6 +2325,45 @@ async function applyPremiumFallbackToEligibleRows(deps, rows, isEligible, agent,
|
|
|
2251
2325
|
const isCreatedRoutingEligible = (r) => r.status === "created" && !!r.path;
|
|
2252
2326
|
/** Dry-run path: rows have no worktree path yet but are eligible for a preview. */
|
|
2253
2327
|
const isDryRunRoutingEligible = (r) => r.status === "dry-run";
|
|
2328
|
+
/**
|
|
2329
|
+
* BAPI-642 — apply a single explicitly injected tier to every eligible row,
|
|
2330
|
+
* bypassing the per-ticket difficulty/tier HTTP lookup. The tier is still mapped
|
|
2331
|
+
* to a concrete alias through the agent registry + per-repo overrides and, for
|
|
2332
|
+
* cursor-agent, live-validated before injection. Rows are stamped with
|
|
2333
|
+
* `difficulty: null`, the supplied `modelTier`, and `modelRoutingSource:
|
|
2334
|
+
* "injected"`. Fail-open: an unmappable tier or a failed validation degrades to
|
|
2335
|
+
* the agent default (with a row warning), never a throw. The alias is resolved +
|
|
2336
|
+
* validated ONCE (identical for every row) so a cursor-agent batch fires at most
|
|
2337
|
+
* one advertised-model probe.
|
|
2338
|
+
*/
|
|
2339
|
+
async function applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, tier) {
|
|
2340
|
+
const stampInjected = (row) => ({
|
|
2341
|
+
...row,
|
|
2342
|
+
difficulty: null,
|
|
2343
|
+
modelTier: tier,
|
|
2344
|
+
modelRoutingSource: "injected",
|
|
2345
|
+
});
|
|
2346
|
+
const alias = resolveModelAlias(agent, tier, overrides);
|
|
2347
|
+
if (!alias) {
|
|
2348
|
+
const warning = `model routing: no valid alias for injected tier=${tier}; using agent default`;
|
|
2349
|
+
return rows.map((r) => isEligible(r)
|
|
2350
|
+
? appendSummaryRowWarning({ ...stampInjected(r), modelAlias: null, modelRoutingReason: warning }, warning)
|
|
2351
|
+
: r);
|
|
2352
|
+
}
|
|
2353
|
+
const validation = await validateResolvedModelAliasForAgent(deps, agent, alias);
|
|
2354
|
+
if (!validation.ok) {
|
|
2355
|
+
return rows.map((r) => isEligible(r)
|
|
2356
|
+
? appendSummaryRowWarning({ ...stampInjected(r), modelAlias: null, modelRoutingReason: validation.warning }, validation.warning)
|
|
2357
|
+
: r);
|
|
2358
|
+
}
|
|
2359
|
+
return rows.map((r) => isEligible(r)
|
|
2360
|
+
? {
|
|
2361
|
+
...stampInjected(r),
|
|
2362
|
+
modelAlias: validation.alias,
|
|
2363
|
+
modelRoutingReason: `injected tier=${tier} model=${validation.alias}`,
|
|
2364
|
+
}
|
|
2365
|
+
: r);
|
|
2366
|
+
}
|
|
2254
2367
|
/**
|
|
2255
2368
|
* Shared fail-open routing resolution. `isEligible` selects which rows get a
|
|
2256
2369
|
* resolved tier/alias; every failure mode (unsupported agent, credential/config/
|
|
@@ -2283,6 +2396,21 @@ async function resolveModelRoutingForEligible(deps, rows, options, agent, isElig
|
|
|
2283
2396
|
const reason = "model routing: disabled for this repo; using agent default";
|
|
2284
2397
|
return rows.map((r) => (isEligible(r) ? applyDefaultModelRoutingMetadata(r, reason) : r));
|
|
2285
2398
|
}
|
|
2399
|
+
// BAPI-642: an explicitly injected `--tier` short-circuits the per-ticket
|
|
2400
|
+
// difficulty/tier lookup — `fetchTicketModelTiersForRows` is NEVER called for
|
|
2401
|
+
// any injected value. A recognized tier is applied to every eligible row; a
|
|
2402
|
+
// malformed value (INJECTED_TIER_UNRESOLVED) fails open to the premium
|
|
2403
|
+
// fallback. When the flag is absent (`injected === undefined`) the legacy
|
|
2404
|
+
// difficulty-lookup path below runs unchanged.
|
|
2405
|
+
const injected = options.injectedTier;
|
|
2406
|
+
if (injected === INJECTED_TIER_UNRESOLVED) {
|
|
2407
|
+
const warning = "model routing: --tier was not one of cheap|basic|premium; defaulting to premium routing";
|
|
2408
|
+
const apply = await makePremiumFallbackApplier(deps, agent, overrides);
|
|
2409
|
+
return rows.map((r) => (isEligible(r) ? apply(r, warning) : r));
|
|
2410
|
+
}
|
|
2411
|
+
if (injected) {
|
|
2412
|
+
return applyInjectedTierRoutingToEligibleRows(deps, rows, isEligible, agent, overrides, injected);
|
|
2413
|
+
}
|
|
2286
2414
|
const tierMap = await fetchTicketModelTiersForRows(access, eligible, options.maxParallel, isEligible);
|
|
2287
2415
|
// Lazily resolve+validate the premium fallback alias at most once for this run
|
|
2288
2416
|
// (the alias is identical across rows; cursor-agent validation spawns a probe).
|
|
@@ -2379,12 +2507,25 @@ export async function resolveModelRoutingForDryRun(deps, rows, options, agent) {
|
|
|
2379
2507
|
/**
|
|
2380
2508
|
* Format one concise routing decision line per ticket:
|
|
2381
2509
|
* `KEY difficulty=<n|?> tier=<tier|fallback> agent=<name> model=<alias|default>`.
|
|
2382
|
-
|
|
2383
|
-
|
|
2510
|
+
*
|
|
2511
|
+
* BAPI-642: an optional trailing label distinguishes the two non-default launch
|
|
2512
|
+
* shapes so an operator can tell them apart in the diagnostics — `phase=review`
|
|
2513
|
+
* for a `review-and-implement` chain launch and `routing=injected` for a
|
|
2514
|
+
* `--tier`-driven implementation handoff. The ORDINARY implementation launch (a
|
|
2515
|
+
* plain `--workflow implement` with no `--tier`) appends NO label, so its line
|
|
2516
|
+
* stays byte-for-byte identical to the pre-feature format.
|
|
2517
|
+
*/
|
|
2518
|
+
export function formatModelRoutingLine(row, agent, context) {
|
|
2384
2519
|
const difficulty = typeof row.difficulty === "number" ? String(row.difficulty) : "?";
|
|
2385
2520
|
const tier = row.modelTier ?? "fallback";
|
|
2386
2521
|
const model = row.modelAlias ?? "default";
|
|
2387
|
-
|
|
2522
|
+
const base = `${row.key} difficulty=${difficulty} tier=${tier} agent=${agent.name} model=${model}`;
|
|
2523
|
+
const labels = [];
|
|
2524
|
+
if (context?.workflow === "review-and-implement")
|
|
2525
|
+
labels.push("phase=review");
|
|
2526
|
+
if (row.modelRoutingSource === "injected")
|
|
2527
|
+
labels.push("routing=injected");
|
|
2528
|
+
return labels.length > 0 ? `${base} ${labels.join(" ")}` : base;
|
|
2388
2529
|
}
|
|
2389
2530
|
/** Stable de-dup key for an invocation-level routing diagnostic. */
|
|
2390
2531
|
function modelRoutingDiagnosticKey(d) {
|
|
@@ -2718,7 +2859,7 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
|
|
|
2718
2859
|
for (const row of routed) {
|
|
2719
2860
|
if (row.status !== "created" || !row.path)
|
|
2720
2861
|
continue;
|
|
2721
|
-
overrides.modelRoutingLog?.(formatModelRoutingLine(row, agent));
|
|
2862
|
+
overrides.modelRoutingLog?.(formatModelRoutingLine(row, agent, { workflow: options.workflow }));
|
|
2722
2863
|
}
|
|
2723
2864
|
// Single invocation-level routing diagnostic to stderr (de-duplicated across
|
|
2724
2865
|
// rows). Fail-open: emission is best-effort and never blocks spawning.
|
|
@@ -2776,6 +2917,13 @@ export async function runStartTicketsCli(argv, overrides = {}) {
|
|
|
2776
2917
|
errorLog(`Error: Unknown agent: '${options.agentName}'. Valid agents: ${formatValidAgentNames()}.`);
|
|
2777
2918
|
return 1;
|
|
2778
2919
|
}
|
|
2920
|
+
// BAPI-642: a malformed `--tier` value is fail-open — emit ONE concise,
|
|
2921
|
+
// secret-free warning and continue (routing applies the premium fallback). It
|
|
2922
|
+
// never aborts the launch or changes the numeric exit code.
|
|
2923
|
+
if (options.injectedTier === INJECTED_TIER_UNRESOLVED) {
|
|
2924
|
+
errorLog("Warning: --tier value was not one of cheap|basic|premium; ignoring the override and " +
|
|
2925
|
+
"using premium model routing for every ticket.");
|
|
2926
|
+
}
|
|
2779
2927
|
if (options.dryRun) {
|
|
2780
2928
|
// Resolve the repo identity for the preview so the dry-run command matches
|
|
2781
2929
|
// what the real spawn injects (see prependRepoNameEnvAssignment). The real
|
|
@@ -2799,7 +2947,7 @@ export async function runStartTicketsCli(argv, overrides = {}) {
|
|
|
2799
2947
|
for (const line of buildDryRunDetailLines(agent, key, branch, deps.platform, deps.env, options.baseBranch, options.autoApprove, modelAlias, options.conductorEnabled ?? false, dryRunRepoName, dryRunMcpInvocation, options.workflow, options.reviewRounds)) {
|
|
2800
2948
|
log(line);
|
|
2801
2949
|
}
|
|
2802
|
-
log(`DRY-RUN: model routing: ${formatModelRoutingLine(routedRow ?? { key, branch, status: "dry-run" }, agent)}`);
|
|
2950
|
+
log(`DRY-RUN: model routing: ${formatModelRoutingLine(routedRow ?? { key, branch, status: "dry-run" }, agent, { workflow: options.workflow })}`);
|
|
2803
2951
|
if (modelAlias == null && routedRow?.modelRoutingReason) {
|
|
2804
2952
|
log(`DRY-RUN: ${routedRow.modelRoutingReason}`);
|
|
2805
2953
|
}
|