@bridge_gpt/mcp-server 0.2.65 → 0.2.66
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 +25 -13
- package/build/agent-launchers/claude-executor-adapter.js +12 -18
- package/build/agent-launchers/executor-adapter.js +27 -0
- package/build/bridge-config.js +104 -0
- package/build/codex-bridge-profile.js +10 -1
- package/build/commands.generated.js +4 -4
- package/build/conductor/bridge-api-client.js +230 -10
- package/build/conductor/epic-child-continuation.js +61 -34
- package/build/conductor/epic-implementer-cli.js +12 -0
- package/build/conductor/guided-repair.js +323 -0
- package/build/conductor/readiness-cli.js +30 -9
- package/build/conductor/readiness.js +23 -1
- package/build/conductor/recovery-cli.js +507 -16
- package/build/conductor/recovery-operations.js +325 -2
- package/build/conductor/repair-contract.js +61 -3
- package/build/credential-store.js +5 -4
- package/build/diagnostic-text.js +36 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +70 -10
- package/build/drive-epic.js +395 -78
- package/build/epic-implementer-bin.js +24 -22
- package/build/epic-launch-diagnostic.js +31 -0
- package/build/epic-launch-profile.js +44 -14
- package/build/executor/completion-supervision.js +5 -0
- package/build/executor/correction-dispatch.js +60 -0
- package/build/executor/env.js +6 -0
- package/build/executor/http-client.js +15 -0
- package/build/executor/integration-job.js +6 -1
- package/build/executor/job-errors.js +13 -4
- package/build/executor/job-log-registry.js +340 -73
- package/build/executor/job-runner.js +260 -33
- package/build/executor/job-types.js +8 -13
- package/build/executor/preflight.js +91 -2
- package/build/executor/prompt-spec.js +113 -9
- package/build/executor/runner.js +23 -0
- package/build/executor/stale-artifacts.js +5 -4
- package/build/executor/types.js +1 -0
- package/build/executor/watch-cli.js +35 -8
- package/build/executor/worker-command.js +43 -43
- package/build/executor/worker-finalization.js +80 -15
- package/build/executor/worker-guard-hook.js +46 -10
- package/build/executor/worker-log.js +137 -70
- package/build/executor/worktree-gc.js +30 -6
- package/build/index.js +177 -329
- package/build/init.js +107 -4
- package/build/install-bridge.js +284 -20
- package/build/install-doctor.js +172 -30
- package/build/install-reexec.js +69 -7
- package/build/macos-terminal-permission-guidance.js +16 -0
- package/build/model-thresholds-tool.js +185 -0
- package/build/pipelines.generated.js +1 -1
- package/build/plane/status.js +4 -0
- package/build/plane/supervisor.js +4 -0
- package/build/plane/worker-exit-code.js +2 -0
- package/build/project-root.js +99 -0
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +156 -13
- package/build/setup-epic.js +180 -108
- package/build/sfcc/setup-status.js +2 -0
- package/build/sfcc/tool-wrapper.js +2 -0
- package/build/sfcc/tools-configured.js +96 -0
- package/build/start-tickets.js +163 -58
- package/build/upgrade-cli.js +109 -24
- package/build/version.generated.js +3 -3
- package/build/windows-npm-entry.js +103 -0
- package/build/worker-guard-hook-bin.js +4 -3
- package/build/worktree-core.js +6 -5
- package/docs/install/mcp-tool-integrations.md +1 -0
- package/docs/install/sfcc-integration.md +12 -7
- package/package.json +1 -1
- package/pipelines/implement-ticket.json +1 -1
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* prevent the others from running.
|
|
7
7
|
*/
|
|
8
8
|
import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
|
|
9
|
+
import { publishSfccVersion } from "./tools-configured.js";
|
|
9
10
|
import { getAmToken } from "./client.js";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Implementation
|
|
@@ -38,6 +39,7 @@ export async function sfccSetupStatusTool(deps) {
|
|
|
38
39
|
if (apiKeyOk && repoOk) {
|
|
39
40
|
try {
|
|
40
41
|
resolvedVersion = await getSfccVersionConfig(deps.buildGetUrl, deps.getGetHeaders, deps.repoName);
|
|
42
|
+
publishSfccVersion(resolvedVersion);
|
|
41
43
|
if (resolvedVersion === null) {
|
|
42
44
|
versionStatus = "✗ Not set (configure the 'version' field in Bridge API project settings)";
|
|
43
45
|
}
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
* no per-tool opt-in and no exemption flag.
|
|
63
63
|
*/
|
|
64
64
|
import { SFCC_VERSIONS, getSfccVersionConfig } from "./config.js";
|
|
65
|
+
import { publishSfccVersion } from "./tools-configured.js";
|
|
65
66
|
import { resolveSfccCredentials } from "./credentials.js";
|
|
66
67
|
import { formatSfccFailure } from "./sfcc-result.js";
|
|
67
68
|
// ---------------------------------------------------------------------------
|
|
@@ -385,6 +386,7 @@ export function withSfccGate(deps, handler) {
|
|
|
385
386
|
}
|
|
386
387
|
// (a) Read /config-field/version
|
|
387
388
|
const version = await getSfccVersionConfig(deps.buildGetUrl, deps.getGetHeaders, deps.repoName);
|
|
389
|
+
publishSfccVersion(version);
|
|
388
390
|
if (version === null) {
|
|
389
391
|
return notConfigured("bridge-auth", "Could not read the SFCC version from Bridge API (/config-field/version). " +
|
|
390
392
|
"Ensure your Bridge API key is set and the repo is configured. " +
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Process-local, secret-free SFCC setup verdict for authenticated Bridge requests. */
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import { constants } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { resolveSfccCredentials } from "./credentials.js";
|
|
6
|
+
import { getSfccVersionConfig, SFCC_VERSIONS } from "./config.js";
|
|
7
|
+
const DIAGNOSTIC = "SFCC tools header omitted: version lookup did not establish SFCC setup; check setup status.";
|
|
8
|
+
/** Existence and readability are enough to establish the local gate. */
|
|
9
|
+
export async function hasRootDwJson() {
|
|
10
|
+
try {
|
|
11
|
+
await access(path.join(process.cwd(), "dw.json"), constants.R_OK);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Isolated state is injectable for offline tests; the MCP uses one instance. */
|
|
19
|
+
export function createSfccToolsHeaderState(deps) {
|
|
20
|
+
let credentialVerdict;
|
|
21
|
+
let knownVersion;
|
|
22
|
+
let lookupStarted = false;
|
|
23
|
+
let diagnosticPrinted = false;
|
|
24
|
+
function getCredentialVerdict() {
|
|
25
|
+
credentialVerdict ??= Promise.resolve().then(() => deps.resolveCredentials(deps.env, {
|
|
26
|
+
...deps.credentialDeps,
|
|
27
|
+
writeFile: async () => { },
|
|
28
|
+
mkdir: async () => { },
|
|
29
|
+
})).then((result) => result.ok ? "yes" : "no")
|
|
30
|
+
.catch(() => "no");
|
|
31
|
+
return credentialVerdict;
|
|
32
|
+
}
|
|
33
|
+
function publishVersion(version) {
|
|
34
|
+
if (version !== null && knownVersion === undefined)
|
|
35
|
+
knownVersion = version;
|
|
36
|
+
if (version !== null && SFCC_VERSIONS.includes(version))
|
|
37
|
+
knownVersion = version;
|
|
38
|
+
}
|
|
39
|
+
function reportUnresolved() {
|
|
40
|
+
if (diagnosticPrinted)
|
|
41
|
+
return;
|
|
42
|
+
diagnosticPrinted = true;
|
|
43
|
+
deps.diagnostic(DIAGNOSTIC);
|
|
44
|
+
}
|
|
45
|
+
async function withHeader(headers, options) {
|
|
46
|
+
if (options.skipSfccToolsHeader)
|
|
47
|
+
return headers;
|
|
48
|
+
try {
|
|
49
|
+
const localFile = await deps.hasDwJson();
|
|
50
|
+
if (knownVersion === undefined && !lookupStarted && options.repoName) {
|
|
51
|
+
lookupStarted = true;
|
|
52
|
+
void deps.getVersion(options.buildGetUrl, options.getLookupHeaders, options.repoName)
|
|
53
|
+
.then((version) => {
|
|
54
|
+
if (version !== null)
|
|
55
|
+
publishVersion(version);
|
|
56
|
+
if (knownVersion === undefined) {
|
|
57
|
+
knownVersion = null;
|
|
58
|
+
if (!localFile)
|
|
59
|
+
reportUnresolved();
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
.catch(() => {
|
|
63
|
+
if (knownVersion === undefined) {
|
|
64
|
+
knownVersion = null;
|
|
65
|
+
if (!localFile)
|
|
66
|
+
reportUnresolved();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const isSfcc = knownVersion !== undefined && knownVersion !== null
|
|
71
|
+
&& SFCC_VERSIONS.includes(knownVersion);
|
|
72
|
+
if (!localFile && !isSfcc) {
|
|
73
|
+
if (!options.repoName)
|
|
74
|
+
reportUnresolved();
|
|
75
|
+
return headers;
|
|
76
|
+
}
|
|
77
|
+
headers["X-Bridge-SFCC-Tools"] = await getCredentialVerdict();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Optional enrichment cannot interrupt authenticated requests.
|
|
81
|
+
}
|
|
82
|
+
return headers;
|
|
83
|
+
}
|
|
84
|
+
return { getCredentialVerdict, publishVersion, withHeader };
|
|
85
|
+
}
|
|
86
|
+
const processState = createSfccToolsHeaderState({
|
|
87
|
+
hasDwJson: hasRootDwJson,
|
|
88
|
+
resolveCredentials: (env, credentialDeps) => resolveSfccCredentials(undefined, env, credentialDeps),
|
|
89
|
+
credentialDeps: {},
|
|
90
|
+
env: process.env,
|
|
91
|
+
getVersion: getSfccVersionConfig,
|
|
92
|
+
diagnostic: (message) => console.error(message),
|
|
93
|
+
});
|
|
94
|
+
export const getSfccCredentialVerdict = processState.getCredentialVerdict;
|
|
95
|
+
export const publishSfccVersion = processState.publishVersion;
|
|
96
|
+
export const withSfccToolsHeader = processState.withHeader;
|
package/build/start-tickets.js
CHANGED
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
* unit-testable on Linux CI without spawning real commands or terminals.
|
|
55
55
|
*/
|
|
56
56
|
import { execFile } from "child_process";
|
|
57
|
+
import { macosTerminalPermissionGuidance } from "./macos-terminal-permission-guidance.js";
|
|
57
58
|
import { readFile, writeFile, mkdir, mkdtemp, stat, lstat, symlink, unlink, copyFile, readdir, rm, } from "fs/promises";
|
|
58
59
|
import os from "node:os";
|
|
59
60
|
import { createHash } from "node:crypto";
|
|
@@ -136,7 +137,7 @@ export function getStartTicketsUsage() {
|
|
|
136
137
|
" Cursor with --auto launches headlessly after a bounded readiness check; without --auto it stays interactive for the review gate.",
|
|
137
138
|
" --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.",
|
|
138
139
|
" --tier cheap|basic|premium Strict manual model-routing override: bypasses the per-ticket difficulty/tier lookup and applies this tier to every ticket exactly as supplied, with no per-session posture adjustment. 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. Mutually exclusive with --auto-tier.",
|
|
139
|
-
" --auto-tier cheap|basic|premium Internal/advanced machine-carried raw backend tier for chained automation handoffs (e.g. /review-and-implement): bypasses the per-ticket lookup like --tier, but remains subject to per-session optimization-posture adjustment (may be downshifted under maximize_subscription). Not the strict-override mechanism — use --tier for a human override. Mutually exclusive with --tier.",
|
|
140
|
+
" --auto-tier cheap|basic|premium Internal/advanced machine-carried raw backend tier for chained automation handoffs (e.g. /review-and-implement): bypasses the per-ticket lookup like --tier, but remains subject to per-session optimization-posture adjustment (may be downshifted under maximize_subscription, unless the server reports configured thresholds). Not the strict-override mechanism — use --tier for a human override. Mutually exclusive with --tier.",
|
|
140
141
|
" --rounds 1|2 Review round count forwarded to the review phase; review-only, valid only with --workflow review-and-implement",
|
|
141
142
|
" --terminal terminal|iterm Override the macOS terminal app (default: auto-detect via $TERM_PROGRAM); honored on macOS only",
|
|
142
143
|
" --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",
|
|
@@ -2749,14 +2750,32 @@ export async function fetchCodexModelCatalog(access) {
|
|
|
2749
2750
|
return invalid();
|
|
2750
2751
|
return { ok: true, catalog: { models: models, revision } };
|
|
2751
2752
|
}
|
|
2753
|
+
/**
|
|
2754
|
+
* True only when stored thresholds decided the tier (`configured` or
|
|
2755
|
+
* `anomaly`). The merged resolver maps every `anomaly` tier to `premium` as a
|
|
2756
|
+
* fail-safe, so a threshold-decided tier is final and must never be downshifted.
|
|
2757
|
+
*
|
|
2758
|
+
* A `source: "fallback"` response is never threshold-decided: the server could
|
|
2759
|
+
* not obtain a difficulty and assumed premium, even though it still reports the
|
|
2760
|
+
* stored thresholds' mode. Treating it as decided would give review tabs a
|
|
2761
|
+
* premium fallback and skip the maximize downshift for a guess.
|
|
2762
|
+
*/
|
|
2763
|
+
export function isThresholdDecidedTierResponse(value) {
|
|
2764
|
+
if (value.source === "fallback")
|
|
2765
|
+
return false;
|
|
2766
|
+
return value.thresholdMode === "configured" || value.thresholdMode === "anomaly";
|
|
2767
|
+
}
|
|
2752
2768
|
/**
|
|
2753
2769
|
* Fetch the coarse tier for one ticket with a bounded timeout. Never throws to
|
|
2754
2770
|
* the batch orchestrator: failures/timeouts return a structured warning plus a
|
|
2755
2771
|
* classified diagnostic that preserves the ticket key in metadata.
|
|
2756
2772
|
*/
|
|
2757
|
-
export async function fetchTicketModelTierForStartTickets(access, ticket) {
|
|
2773
|
+
export async function fetchTicketModelTierForStartTickets(access, ticket, fetchOptions = {}) {
|
|
2758
2774
|
try {
|
|
2759
|
-
const
|
|
2775
|
+
const params = { repo_name: access.repoName };
|
|
2776
|
+
if (fetchOptions.consumer)
|
|
2777
|
+
params.consumer = fetchOptions.consumer;
|
|
2778
|
+
const url = buildStartTicketsJiraUrl(access.baseUrl, `/tickets/${encodeURIComponent(ticket)}/model-tier`, params);
|
|
2760
2779
|
const body = await fetchJsonWithTimeout(url, startTicketsGetHeaders(access), TICKET_MODEL_TIER_FETCH_TIMEOUT_MS);
|
|
2761
2780
|
const obj = (body ?? {});
|
|
2762
2781
|
const tier = isModelTier(obj.tier) ? obj.tier : null;
|
|
@@ -2764,7 +2783,17 @@ export async function fetchTicketModelTierForStartTickets(access, ticket) {
|
|
|
2764
2783
|
const source = obj.source === "cached" || obj.source === "computed" || obj.source === "fallback"
|
|
2765
2784
|
? obj.source
|
|
2766
2785
|
: "fallback";
|
|
2767
|
-
|
|
2786
|
+
const value = { difficulty, tier, source };
|
|
2787
|
+
// BAPI-1343: additive fields are attached only when the server sent them,
|
|
2788
|
+
// so an older server's response parses to exactly the pre-feature shape.
|
|
2789
|
+
if (obj.threshold_mode !== undefined && obj.threshold_mode !== null) {
|
|
2790
|
+
value.thresholdMode =
|
|
2791
|
+
obj.threshold_mode === "configured" || obj.threshold_mode === "anomaly" ? obj.threshold_mode : "legacy";
|
|
2792
|
+
}
|
|
2793
|
+
if (obj.consumer === "review" || obj.consumer === "implementation") {
|
|
2794
|
+
value.consumer = obj.consumer;
|
|
2795
|
+
}
|
|
2796
|
+
return { ok: true, value };
|
|
2768
2797
|
}
|
|
2769
2798
|
catch (err) {
|
|
2770
2799
|
return {
|
|
@@ -2779,15 +2808,23 @@ export async function fetchTicketModelTierForStartTickets(access, ticket) {
|
|
|
2779
2808
|
};
|
|
2780
2809
|
}
|
|
2781
2810
|
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Fetch tiers for ticket keys with bounded parallelism (capped at 3). One
|
|
2813
|
+
* ticket's failure never aborts the others; results are keyed by ticket. The
|
|
2814
|
+
* shared bounded fetch behind both `start-tickets` and `/review-tickets`.
|
|
2815
|
+
*/
|
|
2816
|
+
export async function fetchTicketModelTiersForKeys(access, keys, maxParallel, fetchOptions = {}) {
|
|
2817
|
+
const limit = Math.min(maxParallel, 3);
|
|
2818
|
+
const results = await runWithConcurrency(keys, limit, (key) => fetchTicketModelTierForStartTickets(access, key, fetchOptions).then((res) => [key, res]));
|
|
2819
|
+
return new Map(results);
|
|
2820
|
+
}
|
|
2782
2821
|
/**
|
|
2783
2822
|
* Fetch tiers for the still-spawnable rows with bounded parallelism (capped at
|
|
2784
2823
|
* 3). One ticket's failure never aborts the others; results are keyed by ticket.
|
|
2785
2824
|
*/
|
|
2786
|
-
export async function fetchTicketModelTiersForRows(access, rows, maxParallel, isEligible = (r) => r.status === "created" && !!r.path) {
|
|
2825
|
+
export async function fetchTicketModelTiersForRows(access, rows, maxParallel, isEligible = (r) => r.status === "created" && !!r.path, fetchOptions = {}) {
|
|
2787
2826
|
const eligible = rows.filter(isEligible);
|
|
2788
|
-
|
|
2789
|
-
const results = await runWithConcurrency(eligible, limit, (row) => fetchTicketModelTierForStartTickets(access, row.key).then((res) => [row.key, res]));
|
|
2790
|
-
return new Map(results);
|
|
2827
|
+
return fetchTicketModelTiersForKeys(access, eligible.map((row) => row.key), maxParallel, fetchOptions);
|
|
2791
2828
|
}
|
|
2792
2829
|
/**
|
|
2793
2830
|
* Parse a model-list command's stdout into a set of advertised model aliases.
|
|
@@ -2874,7 +2911,15 @@ export function applyDefaultModelRoutingMetadata(row, reason, diagnostic) {
|
|
|
2874
2911
|
...(diagnostic ? { modelRoutingDiagnostic: diagnostic } : {}),
|
|
2875
2912
|
};
|
|
2876
2913
|
}
|
|
2877
|
-
|
|
2914
|
+
/**
|
|
2915
|
+
* Map a coarse tier to the agent's alias: the Codex authenticated catalog for
|
|
2916
|
+
* `codex`, otherwise the agent registry plus per-repo
|
|
2917
|
+
* `difficulty_model_tier_overrides`. Exported (BAPI-1343) so `/review-tickets`
|
|
2918
|
+
* maps review-threshold tiers exactly as `start-tickets` does, without copying
|
|
2919
|
+
* the registry or catalog rules. The result still needs
|
|
2920
|
+
* {@link validateResolvedModelAliasForAgent} before it reaches a shell.
|
|
2921
|
+
*/
|
|
2922
|
+
export function resolveRoutingAlias(agent, tier, overrides, catalog) {
|
|
2878
2923
|
return agent.name === "codex"
|
|
2879
2924
|
? catalog?.models[tier] ?? null
|
|
2880
2925
|
: resolveModelAlias(agent, tier, overrides);
|
|
@@ -2951,8 +2996,12 @@ const isDryRunRoutingEligible = (r) => r.status === "dry-run";
|
|
|
2951
2996
|
* routing (posture `not_evaluated_manual_override`) and for `quality`/`speed`;
|
|
2952
2997
|
* the exact transition token — including the `cheap` floor — under
|
|
2953
2998
|
* `maximize_subscription`. Total and exhaustive over {@link ModelTier}.
|
|
2999
|
+
* BAPI-1343: `none` whenever a `threshold` marker is supplied, because a
|
|
3000
|
+
* threshold-decided tier is final and is never downshifted.
|
|
2954
3001
|
*/
|
|
2955
|
-
function resolveModelRoutingDecisionAdjustment(posture, baseline) {
|
|
3002
|
+
function resolveModelRoutingDecisionAdjustment(posture, baseline, threshold) {
|
|
3003
|
+
if (threshold)
|
|
3004
|
+
return "none";
|
|
2956
3005
|
if (posture !== "maximize_subscription")
|
|
2957
3006
|
return "none";
|
|
2958
3007
|
switch (baseline) {
|
|
@@ -2973,7 +3022,24 @@ function resolveModelRoutingDecisionAdjustment(posture, baseline) {
|
|
|
2973
3022
|
export function formatModelRoutingDecisionDiagnosticLine(input) {
|
|
2974
3023
|
return (`[model-routing] source=${input.source} baseline=${input.baseline} ` +
|
|
2975
3024
|
`posture=${input.posture} adjustment=${input.adjustment} effective=${input.effective} ` +
|
|
2976
|
-
`alias=${input.alias ?? "unresolved"}`
|
|
3025
|
+
`alias=${input.alias ?? "unresolved"}` +
|
|
3026
|
+
(input.threshold ? ` threshold=${input.threshold}` : ""));
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* BAPI-1343 R-6: the threshold marker for a successfully fetched `/model-tier`
|
|
3030
|
+
* response when — and only when — the maximize downshift must be skipped:
|
|
3031
|
+
* posture is `maximize_subscription` AND the response is threshold-decided
|
|
3032
|
+
* (`configured` or `anomaly`). Every other case returns `undefined`, so the
|
|
3033
|
+
* caller keeps today's exact downshift and text.
|
|
3034
|
+
*/
|
|
3035
|
+
function thresholdSkipMarker(posture, tierResult) {
|
|
3036
|
+
if (posture !== "maximize_subscription")
|
|
3037
|
+
return undefined;
|
|
3038
|
+
if (!tierResult || !tierResult.ok)
|
|
3039
|
+
return undefined;
|
|
3040
|
+
if (!isThresholdDecidedTierResponse(tierResult.value))
|
|
3041
|
+
return undefined;
|
|
3042
|
+
return tierResult.value.thresholdMode;
|
|
2977
3043
|
}
|
|
2978
3044
|
/**
|
|
2979
3045
|
* Best-effort emitter: calls the supplied stderr sink once and swallows any
|
|
@@ -3160,36 +3226,63 @@ errorLog = (message) => console.error(message)) {
|
|
|
3160
3226
|
// chained automation handoff (e.g. `/review-and-implement`'s `--auto-tier`).
|
|
3161
3227
|
// Routed through the CURRENT session's posture EXACTLY ONCE — unlike the
|
|
3162
3228
|
// manual override above, this path IS subject to `downshiftModelTier()`
|
|
3163
|
-
// under `maximize_subscription` — while
|
|
3164
|
-
// per-ticket
|
|
3165
|
-
//
|
|
3166
|
-
//
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3229
|
+
// under `maximize_subscription` — while never re-deriving the tier from a
|
|
3230
|
+
// per-ticket lookup. The alias is resolved+validated at most ONCE per
|
|
3231
|
+
// distinct effective tier, matching the premium fallback / injected-tier
|
|
3232
|
+
// hoisting pattern above.
|
|
3233
|
+
//
|
|
3234
|
+
// BAPI-1343 R-6: under `maximize_subscription` ONLY, each eligible row's
|
|
3235
|
+
// `/model-tier` is fetched once (the shared bounded fetch, no `consumer`
|
|
3236
|
+
// parameter) purely to read `threshold_mode`. A threshold-decided
|
|
3237
|
+
// (`configured`/`anomaly`) response keeps the carried tier — stored
|
|
3238
|
+
// thresholds made it final. A fetch failure, `legacy`, or anything else
|
|
3239
|
+
// downshifts exactly as before. `quality`/`speed` never downshift, so they
|
|
3240
|
+
// make no extra request.
|
|
3241
|
+
const thresholdTierMap = posture === "maximize_subscription"
|
|
3242
|
+
? await fetchTicketModelTiersForRows(access, eligible, options.maxParallel, isEligible)
|
|
3243
|
+
: null;
|
|
3244
|
+
const aliasByEffectiveTier = new Map();
|
|
3245
|
+
const resolveAutoTierAlias = async (effectiveTier) => {
|
|
3246
|
+
const cached = aliasByEffectiveTier.get(effectiveTier);
|
|
3247
|
+
if (cached)
|
|
3248
|
+
return cached;
|
|
3249
|
+
const alias = resolveRoutingAlias(agent, effectiveTier, overrides, catalog);
|
|
3250
|
+
let resolved;
|
|
3251
|
+
if (!alias) {
|
|
3252
|
+
resolved = {
|
|
3253
|
+
resolvedAlias: null,
|
|
3254
|
+
aliasWarning: `model routing: no valid alias for tier=${effectiveTier}; using agent default`,
|
|
3255
|
+
};
|
|
3182
3256
|
}
|
|
3183
3257
|
else {
|
|
3184
|
-
|
|
3258
|
+
const validation = await validateResolvedModelAliasForAgent(deps, agent, alias);
|
|
3259
|
+
resolved = validation.ok
|
|
3260
|
+
? { resolvedAlias: validation.alias, aliasWarning: null }
|
|
3261
|
+
: { resolvedAlias: null, aliasWarning: validation.warning };
|
|
3185
3262
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
if (!isEligible(row))
|
|
3192
|
-
|
|
3263
|
+
aliasByEffectiveTier.set(effectiveTier, resolved);
|
|
3264
|
+
return resolved;
|
|
3265
|
+
};
|
|
3266
|
+
const autoOut = [];
|
|
3267
|
+
for (const row of rows) {
|
|
3268
|
+
if (!isEligible(row)) {
|
|
3269
|
+
autoOut.push(row);
|
|
3270
|
+
continue;
|
|
3271
|
+
}
|
|
3272
|
+
const threshold = thresholdSkipMarker(posture, thresholdTierMap?.get(row.key));
|
|
3273
|
+
const effectiveTier = posture === "maximize_subscription" && !threshold
|
|
3274
|
+
? downshiftModelTier(autoTier)
|
|
3275
|
+
: autoTier;
|
|
3276
|
+
const adjustment = resolveModelRoutingDecisionAdjustment(posture, autoTier, threshold);
|
|
3277
|
+
const lineageSuffix = threshold
|
|
3278
|
+
? `; threshold:${threshold}`
|
|
3279
|
+
: posture === "maximize_subscription"
|
|
3280
|
+
? `; auto-tier:${autoTier}->maximize:${effectiveTier}`
|
|
3281
|
+
: "";
|
|
3282
|
+
const { resolvedAlias, aliasWarning } = await resolveAutoTierAlias(effectiveTier);
|
|
3283
|
+
const reason = resolvedAlias
|
|
3284
|
+
? `auto-tier=${autoTier} model=${resolvedAlias} (posture=${posture})${lineageSuffix}`
|
|
3285
|
+
: `${aliasWarning}${lineageSuffix}`;
|
|
3193
3286
|
const baseRow = {
|
|
3194
3287
|
...row,
|
|
3195
3288
|
difficulty: null,
|
|
@@ -3201,14 +3294,10 @@ errorLog = (message) => console.error(message)) {
|
|
|
3201
3294
|
? { modelRoutingDiagnostic: codexEntitlementDiagnostic(access.repoName) }
|
|
3202
3295
|
: {}),
|
|
3203
3296
|
};
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
// the calculated effective tier, and the validated alias (or `unresolved`).
|
|
3209
|
-
for (const row of rows) {
|
|
3210
|
-
if (!isEligible(row))
|
|
3211
|
-
continue;
|
|
3297
|
+
autoOut.push(aliasWarning ? appendSummaryRowWarning(baseRow, aliasWarning) : baseRow);
|
|
3298
|
+
// BAPI-1236: one `automatic_snapshot` diagnostic per eligible row — the
|
|
3299
|
+
// carried baseline, the newly resolved posture, the exact adjustment token,
|
|
3300
|
+
// the calculated effective tier, and the validated alias (or `unresolved`).
|
|
3212
3301
|
emitModelRoutingDecisionDiagnostic(errorLog, {
|
|
3213
3302
|
source: "automatic_snapshot",
|
|
3214
3303
|
baseline: autoTier,
|
|
@@ -3216,8 +3305,10 @@ errorLog = (message) => console.error(message)) {
|
|
|
3216
3305
|
adjustment,
|
|
3217
3306
|
effective: effectiveTier,
|
|
3218
3307
|
alias: resolvedAlias,
|
|
3308
|
+
...(threshold ? { threshold } : {}),
|
|
3219
3309
|
});
|
|
3220
3310
|
}
|
|
3311
|
+
out = autoOut;
|
|
3221
3312
|
}
|
|
3222
3313
|
else {
|
|
3223
3314
|
// Ordinary per-ticket difficulty-lookup path.
|
|
@@ -3298,12 +3389,24 @@ errorLog = (message) => console.error(message)) {
|
|
|
3298
3389
|
// alias table. Copilot never reaches this loop at all: it has no
|
|
3299
3390
|
// `AGENT_REGISTRY` entry and `isRunnableHostTarget()` rejects hosts
|
|
3300
3391
|
// without a launch agent before an `AgentSpec` is ever resolved.
|
|
3392
|
+
//
|
|
3393
|
+
// BAPI-1343 R-6: the response is already in hand — when it is
|
|
3394
|
+
// threshold-decided (`configured`/`anomaly`), stored thresholds made the
|
|
3395
|
+
// tier final, so the maximize downshift is skipped and the lineage and
|
|
3396
|
+
// diagnostic carry a distinct `threshold` marker instead. The harness-
|
|
3397
|
+
// local premium fallback above is untouched.
|
|
3301
3398
|
const difficultyTier = tier;
|
|
3302
|
-
const
|
|
3303
|
-
const
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3399
|
+
const threshold = thresholdSkipMarker(posture, tierResult);
|
|
3400
|
+
const effectiveTier = posture === "maximize_subscription" && !threshold
|
|
3401
|
+
? downshiftModelTier(difficultyTier)
|
|
3402
|
+
: difficultyTier;
|
|
3403
|
+
const adjustment = resolveModelRoutingDecisionAdjustment(posture, difficultyTier, threshold);
|
|
3404
|
+
const lineageSuffix = threshold
|
|
3405
|
+
? `; threshold:${threshold}`
|
|
3406
|
+
: posture === "maximize_subscription"
|
|
3407
|
+
? `; difficulty:${difficultyTier}->maximize:${effectiveTier}`
|
|
3408
|
+
: "";
|
|
3409
|
+
const thresholdField = threshold ? { threshold } : {};
|
|
3307
3410
|
// BAPI-1236: stamp `difficulty_lookup` rather than copying the backend
|
|
3308
3411
|
// cache/computation source (`cached`/`computed`/`fallback`) into the
|
|
3309
3412
|
// summary's own routing-provenance field — that backend source is
|
|
@@ -3325,6 +3428,7 @@ errorLog = (message) => console.error(message)) {
|
|
|
3325
3428
|
adjustment,
|
|
3326
3429
|
effective: effectiveTier,
|
|
3327
3430
|
alias: null,
|
|
3431
|
+
...thresholdField,
|
|
3328
3432
|
});
|
|
3329
3433
|
continue;
|
|
3330
3434
|
}
|
|
@@ -3338,6 +3442,7 @@ errorLog = (message) => console.error(message)) {
|
|
|
3338
3442
|
adjustment,
|
|
3339
3443
|
effective: effectiveTier,
|
|
3340
3444
|
alias: null,
|
|
3445
|
+
...thresholdField,
|
|
3341
3446
|
});
|
|
3342
3447
|
continue;
|
|
3343
3448
|
}
|
|
@@ -3356,6 +3461,7 @@ errorLog = (message) => console.error(message)) {
|
|
|
3356
3461
|
adjustment,
|
|
3357
3462
|
effective: effectiveTier,
|
|
3358
3463
|
alias: validation.alias,
|
|
3464
|
+
...thresholdField,
|
|
3359
3465
|
});
|
|
3360
3466
|
}
|
|
3361
3467
|
out = loopOut;
|
|
@@ -3835,14 +3941,13 @@ export async function orchestrateStartTickets(deps, options, overrides = {}) {
|
|
|
3835
3941
|
const rows = await spawnTabsFn(deps, scoped, terminal, platformConfig.config.buildAgentShellCommand);
|
|
3836
3942
|
return { ok: true, rows };
|
|
3837
3943
|
}
|
|
3838
|
-
/**
|
|
3839
|
-
|
|
3944
|
+
/**
|
|
3945
|
+
* Platform-specific guidance printed when one or more tabs fail to spawn.
|
|
3946
|
+
* Exported for the BAPI-1319 byte-identity pin on the darwin sentence.
|
|
3947
|
+
*/
|
|
3948
|
+
export function spawnFailureHintForPlatform(platform) {
|
|
3840
3949
|
if (platform === "darwin") {
|
|
3841
|
-
return
|
|
3842
|
-
"required macOS permissions under System Settings -> Privacy & Security, then re-run: " +
|
|
3843
|
-
"Automation (to let the terminal app be controlled) and, for the Terminal.app path, " +
|
|
3844
|
-
"Accessibility (the new-tab step sends Cmd-T via System Events keystrokes, which Automation " +
|
|
3845
|
-
"alone does not cover).");
|
|
3950
|
+
return "One or more tabs failed to spawn. " + macosTerminalPermissionGuidance();
|
|
3846
3951
|
}
|
|
3847
3952
|
if (platform === "win32") {
|
|
3848
3953
|
return ("One or more tabs failed to spawn. Ensure Windows Terminal (wt.exe) or PowerShell is " +
|
package/build/upgrade-cli.js
CHANGED
|
@@ -42,26 +42,85 @@ import { fetchLatestVersion } from "./cli-release.js";
|
|
|
42
42
|
import { CANONICAL_UPGRADE_COMMAND } from "./upgrade-advice.js";
|
|
43
43
|
import { describeLauncherReason, inspectLauncherConfigs, isApplicableStandardLauncher, isPrerequisiteFailure, LAUNCHER_CONFIG_TARGETS, } from "./launcher-config-inspection.js";
|
|
44
44
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
45
|
+
import { defaultNpmEntryFileExists, resolveNpmEntry } from "./windows-npm-entry.js";
|
|
45
46
|
/** The sentinel this CLI passes to the child it re-execs from `@latest`. */
|
|
46
47
|
const INTERNAL_REEXEC_FLAG = "--internal-reexec";
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The fixed, secret-free message printed when, on Windows, the latest CLI could
|
|
50
|
+
* not be STARTED. Upgrade stops here rather than continuing with the local copy.
|
|
51
|
+
*/
|
|
52
|
+
export const UPGRADE_REEXEC_START_FAILURE_MESSAGE = "Bridge API upgrade failed: could not start the latest version of the CLI. " +
|
|
53
|
+
`Nothing was changed. Run ${CANONICAL_UPGRADE_COMMAND} to run the latest copy directly.`;
|
|
54
|
+
export function defaultReexec(cwd, oldVersion, options = {}) {
|
|
55
|
+
const platform = options.platform ?? process.platform;
|
|
56
|
+
const doSpawn = options.spawn ?? spawn;
|
|
57
|
+
const errorLog = options.errorLog ?? ((message) => console.error(message));
|
|
58
|
+
const reexecArgs = [
|
|
59
|
+
"-y",
|
|
60
|
+
`${MCP_PACKAGE_NAME}@latest`,
|
|
61
|
+
"upgrade",
|
|
62
|
+
INTERNAL_REEXEC_FLAG,
|
|
63
|
+
"--old-version",
|
|
64
|
+
oldVersion,
|
|
65
|
+
];
|
|
66
|
+
if (platform !== "win32") {
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const child = doSpawn("npx", reexecArgs, { stdio: "inherit", cwd });
|
|
69
|
+
child.on("close", (code) => resolve(code ?? 0));
|
|
70
|
+
child.on("error", (err) => {
|
|
71
|
+
errorLog(`Bridge API upgrade failed: could not re-exec npx: ${err.message}`);
|
|
72
|
+
resolve(1);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// WINDOWS (BAPI-1319): run npm's `npx-cli.js` through Node, never `npx.cmd`
|
|
77
|
+
// (EINVAL without a shell since CVE-2024-27980). A launch that fails before the
|
|
78
|
+
// child starts STOPS the upgrade with a fixed message — it never continues with
|
|
79
|
+
// the local copy, and a sync throw can no longer escape as an unhandled rejection.
|
|
80
|
+
const resolved = resolveNpmEntry("npx", reexecArgs, {
|
|
81
|
+
platform,
|
|
82
|
+
env: options.env ?? process.env,
|
|
83
|
+
execPath: options.execPath ?? process.execPath,
|
|
84
|
+
fileExists: options.fileExists ?? defaultNpmEntryFileExists,
|
|
85
|
+
});
|
|
86
|
+
if (resolved.status !== "resolved") {
|
|
87
|
+
errorLog(UPGRADE_REEXEC_START_FAILURE_MESSAGE);
|
|
88
|
+
return Promise.resolve(1);
|
|
89
|
+
}
|
|
52
90
|
return new Promise((resolve) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
91
|
+
let settled = false;
|
|
92
|
+
let started = false;
|
|
93
|
+
const settle = (code) => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
settled = true;
|
|
97
|
+
resolve(code);
|
|
98
|
+
};
|
|
99
|
+
const failToStart = () => {
|
|
100
|
+
if (settled)
|
|
101
|
+
return;
|
|
102
|
+
errorLog(UPGRADE_REEXEC_START_FAILURE_MESSAGE);
|
|
103
|
+
settle(1);
|
|
104
|
+
};
|
|
105
|
+
let child;
|
|
106
|
+
try {
|
|
107
|
+
child = doSpawn(resolved.command, resolved.args, { shell: false, stdio: "inherit", cwd });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
failToStart();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
child.on("spawn", () => {
|
|
114
|
+
started = true;
|
|
115
|
+
});
|
|
116
|
+
child.on("close", (code) => settle(code ?? 0));
|
|
62
117
|
child.on("error", (err) => {
|
|
63
|
-
|
|
64
|
-
|
|
118
|
+
if (!started) {
|
|
119
|
+
failToStart();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
errorLog(`Bridge API upgrade failed: could not re-exec npx: ${err.message}`);
|
|
123
|
+
settle(1);
|
|
65
124
|
});
|
|
66
125
|
});
|
|
67
126
|
}
|
|
@@ -77,13 +136,39 @@ async function defaultHasCompetingLocalInstall(cwd) {
|
|
|
77
136
|
return false;
|
|
78
137
|
}
|
|
79
138
|
}
|
|
80
|
-
function defaultRemoveCompetingLocalInstall(cwd) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
139
|
+
export function defaultRemoveCompetingLocalInstall(cwd, options = {}) {
|
|
140
|
+
const platform = options.platform ?? process.platform;
|
|
141
|
+
const doSpawn = options.spawn ?? spawn;
|
|
142
|
+
const removeArgs = ["uninstall", MCP_PACKAGE_NAME];
|
|
143
|
+
if (platform !== "win32") {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const child = doSpawn("npm", removeArgs, {
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
cwd,
|
|
148
|
+
});
|
|
149
|
+
child.on("close", (code) => resolve(code === 0));
|
|
150
|
+
child.on("error", () => resolve(false));
|
|
86
151
|
});
|
|
152
|
+
}
|
|
153
|
+
// WINDOWS (BAPI-1319): `npm-cli.js` through Node. Every launch failure resolves
|
|
154
|
+
// false, which reaches the caller's existing "could not remove" message.
|
|
155
|
+
const resolved = resolveNpmEntry("npm", removeArgs, {
|
|
156
|
+
platform,
|
|
157
|
+
env: options.env ?? process.env,
|
|
158
|
+
execPath: options.execPath ?? process.execPath,
|
|
159
|
+
fileExists: options.fileExists ?? defaultNpmEntryFileExists,
|
|
160
|
+
});
|
|
161
|
+
if (resolved.status !== "resolved")
|
|
162
|
+
return Promise.resolve(false);
|
|
163
|
+
return new Promise((resolve) => {
|
|
164
|
+
let child;
|
|
165
|
+
try {
|
|
166
|
+
child = doSpawn(resolved.command, resolved.args, { shell: false, stdio: "inherit", cwd });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
resolve(false);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
87
172
|
child.on("close", (code) => resolve(code === 0));
|
|
88
173
|
child.on("error", () => resolve(false));
|
|
89
174
|
});
|
|
@@ -95,9 +180,9 @@ export function createDefaultUpgradeCliDeps(cwd) {
|
|
|
95
180
|
fetchLatestVersion,
|
|
96
181
|
readFile: (filePath) => readFile(filePath, "utf-8"),
|
|
97
182
|
hasCompetingLocalInstall: defaultHasCompetingLocalInstall,
|
|
98
|
-
removeCompetingLocalInstall: defaultRemoveCompetingLocalInstall,
|
|
183
|
+
removeCompetingLocalInstall: (installCwd) => defaultRemoveCompetingLocalInstall(installCwd),
|
|
99
184
|
runInit,
|
|
100
|
-
reexecFromLatest: defaultReexec,
|
|
185
|
+
reexecFromLatest: (reexecCwd, oldVersion) => defaultReexec(reexecCwd, oldVersion),
|
|
101
186
|
stdout: (m) => console.log(m),
|
|
102
187
|
stderr: (m) => console.error(m),
|
|
103
188
|
env: process.env,
|
|
@@ -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.66";
|
|
3
|
+
export const BUILD_COMMIT = "b90190f51abc";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.66", "serve"];
|