@bridge_gpt/mcp-server 0.2.26 → 0.2.28
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 +32 -24
- package/build/commands.generated.js +2 -2
- package/build/conductor/bridge-api-client.js +98 -1
- package/build/conductor/epic-reconcile.js +28 -0
- package/build/conductor/epic-runtime.js +28 -1
- package/build/conductor-bin.js +68 -9267
- package/build/conductor-claude-hook-bin.js +2 -172
- package/build/connect-github-api.js +9 -0
- package/build/connect-github.js +10 -0
- package/build/decision-page-template.js +2 -31
- package/build/doctor.js +2 -2
- package/build/env-flags.js +23 -0
- package/build/index.js +4699 -37043
- package/build/init.js +80 -56
- package/build/install-bridge.js +451 -153
- package/build/mcp-host-targets.js +12 -0
- package/build/readme.generated.js +1 -1
- package/build/setup-epic.js +198 -4
- package/build/start-tickets.js +25 -4
- package/build/tool-surface-gating.js +13 -2
- package/build/version.generated.js +1 -1
- package/package.json +3 -5
- package/design-assets/favicon/android-chrome-192x192.png +0 -0
- package/design-assets/favicon/android-chrome-512x512.png +0 -0
- package/design-assets/favicon/apple-touch-icon.png +0 -0
- package/design-assets/favicon/favicon-16x16.png +0 -0
- package/design-assets/favicon/favicon.ico +0 -0
- package/design-assets/favicon/site.webmanifest +0 -1
- package/public/css/main.min.css +0 -4971
- package/public/css/main.min.css.map +0 -1
- package/public/fonts/OFL.txt +0 -93
- package/public/fonts/SourceSansPro-Black.ttf +0 -0
- package/public/fonts/SourceSansPro-BlackItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Bold.ttf +0 -0
- package/public/fonts/SourceSansPro-BoldItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-ExtraLight.ttf +0 -0
- package/public/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Italic.ttf +0 -0
- package/public/fonts/SourceSansPro-Light.ttf +0 -0
- package/public/fonts/SourceSansPro-LightItalic.ttf +0 -0
- package/public/fonts/SourceSansPro-Regular.ttf +0 -0
- package/public/fonts/SourceSansPro-SemiBold.ttf +0 -0
- package/public/fonts/SourceSansPro-SemiBoldItalic.ttf +0 -0
- package/public/img/bridge-logo-160x51.webp +0 -0
- package/public/img/bridge-logo-300x92.webp +0 -0
- package/public/img/favicon/android-chrome-192x192.png +0 -0
- package/public/img/favicon/android-chrome-512x512.png +0 -0
- package/public/img/favicon/apple-touch-icon.png +0 -0
- package/public/img/favicon/favicon-16x16.png +0 -0
- package/public/img/favicon/favicon-32x32.png +0 -0
- package/public/img/favicon/favicon.ico +0 -0
- package/public/img/favicon/site.webmanifest +0 -1
- package/public/img/installation/bitbucket/app-password-1.png +0 -0
- package/public/img/installation/bitbucket/app-password-2.png +0 -0
- package/public/img/installation/bitbucket/create-token-1.png +0 -0
- package/public/img/installation/bitbucket/create-token-2.png +0 -0
- package/public/img/installation/bitbucket/webhook-1.png +0 -0
- package/public/img/installation/github/github-review-webhook.png +0 -0
- package/public/img/installation/jira/credentials/api-key.png +0 -0
- package/public/img/installation/jira/webhook/create-rule.png +0 -0
- package/public/img/installation/jira/webhook/project-settings.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-1.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-2.png +0 -0
- package/public/img/installation/jira/webhook/rule-create-3.png +0 -0
- package/public/img/installation/pinecone/pinecone-api-key.png +0 -0
- package/public/img/installation/pinecone/pinecone-index.png +0 -0
- package/public/js/main.min.js +0 -10409
- package/public/js/main.min.js.map +0 -1
- package/smoke-test/SMOKE-TEST.md +0 -560
- package/smoke-test/smoke-test-mcp.md +0 -23
|
@@ -1,173 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
|
-
var CONDUCTOR_HOOK_EMIT_FAILED_WARNING = "Warning: conductor hook emit failed.";
|
|
7
|
-
function resolveClaudeHookEventName(payload) {
|
|
8
|
-
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
const record = payload;
|
|
12
|
-
const candidates = ["hook_event_name", "hookEventName", "event_name", "event"];
|
|
13
|
-
for (const key of candidates) {
|
|
14
|
-
const value = record[key];
|
|
15
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
16
|
-
return value.trim();
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
function mapClaudeHookEventToSemanticType(eventName) {
|
|
22
|
-
switch (eventName) {
|
|
23
|
-
case "SessionStart":
|
|
24
|
-
return "run.started";
|
|
25
|
-
case "SessionEnd":
|
|
26
|
-
return "run.stopped";
|
|
27
|
-
case "Stop":
|
|
28
|
-
return null;
|
|
29
|
-
case "SubagentStop":
|
|
30
|
-
return null;
|
|
31
|
-
case "Notification":
|
|
32
|
-
return "agent.notification";
|
|
33
|
-
case "PreToolUse":
|
|
34
|
-
return "tool.intent";
|
|
35
|
-
default:
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
var REQUIRED_IDENTITY_ENV = [
|
|
40
|
-
"BAPI_CONDUCTOR_RUN_ID",
|
|
41
|
-
"BAPI_CONDUCTOR_WORKER_ID",
|
|
42
|
-
"BAPI_CONDUCTOR_TICKET_KEY",
|
|
43
|
-
"BAPI_CONDUCTOR_WORKTREE_PATH"
|
|
44
|
-
];
|
|
45
|
-
function nonEmpty(value) {
|
|
46
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
47
|
-
}
|
|
48
|
-
function buildClaudeHookConductorEvent(payload, env) {
|
|
49
|
-
const eventName = resolveClaudeHookEventName(payload);
|
|
50
|
-
const type = mapClaudeHookEventToSemanticType(eventName);
|
|
51
|
-
if (type === null) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
if (env.BAPI_CONDUCTOR_ENABLED !== "1") {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
for (const key of REQUIRED_IDENTITY_ENV) {
|
|
58
|
-
if (!nonEmpty(env[key])) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const ticketKey = env.BAPI_CONDUCTOR_TICKET_KEY.trim();
|
|
63
|
-
const worktreePath = env.BAPI_CONDUCTOR_WORKTREE_PATH.trim();
|
|
64
|
-
const details = {
|
|
65
|
-
ticket_key: ticketKey,
|
|
66
|
-
worktree_path: worktreePath
|
|
67
|
-
};
|
|
68
|
-
if (nonEmpty(env.BAPI_CONDUCTOR_REPO_NAME)) {
|
|
69
|
-
details.repo = env.BAPI_CONDUCTOR_REPO_NAME.trim();
|
|
70
|
-
}
|
|
71
|
-
const raw = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : { payload };
|
|
72
|
-
return {
|
|
73
|
-
source: "claude-code",
|
|
74
|
-
type,
|
|
75
|
-
subject: ticketKey,
|
|
76
|
-
run_id: env.BAPI_CONDUCTOR_RUN_ID.trim(),
|
|
77
|
-
worker_id: env.BAPI_CONDUCTOR_WORKER_ID.trim(),
|
|
78
|
-
producer: "claude-code-hook",
|
|
79
|
-
observed_via: "claude-code-hook",
|
|
80
|
-
data: {
|
|
81
|
-
details,
|
|
82
|
-
raw
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
function buildConductorEmitEventArgs(event) {
|
|
87
|
-
const args = ["--type", event.type, "--source", event.source];
|
|
88
|
-
if (nonEmpty(event.subject ?? void 0)) args.push("--subject", event.subject);
|
|
89
|
-
if (nonEmpty(event.run_id ?? void 0)) args.push("--run-id", event.run_id);
|
|
90
|
-
if (nonEmpty(event.worker_id ?? void 0)) args.push("--worker-id", event.worker_id);
|
|
91
|
-
if (nonEmpty(event.producer ?? void 0)) args.push("--producer", event.producer);
|
|
92
|
-
if (nonEmpty(event.observed_via ?? void 0)) {
|
|
93
|
-
args.push("--observed-via", event.observed_via);
|
|
94
|
-
}
|
|
95
|
-
args.push("--data-json-stdin", "--json");
|
|
96
|
-
return args;
|
|
97
|
-
}
|
|
98
|
-
function resolveConductorEmitCommand(env, emitArgs) {
|
|
99
|
-
const cliFile = env.BAPI_CONDUCTOR_CLI_FILE;
|
|
100
|
-
if (nonEmpty(cliFile)) {
|
|
101
|
-
return {
|
|
102
|
-
command: process.execPath,
|
|
103
|
-
args: [cliFile.trim(), "emit-event", ...emitArgs]
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
const bin = nonEmpty(env.BAPI_CONDUCTOR_BIN) ? env.BAPI_CONDUCTOR_BIN.trim() : "conductor";
|
|
107
|
-
return { command: bin, args: ["emit-event", ...emitArgs] };
|
|
108
|
-
}
|
|
109
|
-
var defaultConductorSpawn = (command, args, input) => {
|
|
110
|
-
const result = spawnSync(command, args, {
|
|
111
|
-
input,
|
|
112
|
-
encoding: "utf-8",
|
|
113
|
-
// Never use a shell: list args + stdin keep raw payloads/secrets out of any
|
|
114
|
-
// shell-interpreted command string.
|
|
115
|
-
shell: false
|
|
116
|
-
});
|
|
117
|
-
return { status: result.status, error: result.error };
|
|
118
|
-
};
|
|
119
|
-
function emitClaudeHookEventWithConductorCli(event, deps = {}) {
|
|
120
|
-
const env = deps.env ?? process.env;
|
|
121
|
-
const spawn = deps.spawn ?? defaultConductorSpawn;
|
|
122
|
-
const emitArgs = buildConductorEmitEventArgs(event);
|
|
123
|
-
const { command, args } = resolveConductorEmitCommand(env, emitArgs);
|
|
124
|
-
const stdinPayload = JSON.stringify(event.data ?? {});
|
|
125
|
-
try {
|
|
126
|
-
const result = spawn(command, args, stdinPayload);
|
|
127
|
-
if (result.error) return false;
|
|
128
|
-
return result.status === 0;
|
|
129
|
-
} catch {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function runClaudeConductorHookCli(deps = {}) {
|
|
134
|
-
const env = deps.env ?? process.env;
|
|
135
|
-
const readStdin = deps.readStdin ?? (() => readStdinSync());
|
|
136
|
-
const warn = deps.warn ?? ((m) => process.stderr.write(`${m}
|
|
137
|
-
`));
|
|
138
|
-
let payload;
|
|
139
|
-
try {
|
|
140
|
-
const raw = readStdin();
|
|
141
|
-
payload = JSON.parse(raw);
|
|
142
|
-
} catch {
|
|
143
|
-
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
144
|
-
return 0;
|
|
145
|
-
}
|
|
146
|
-
let event;
|
|
147
|
-
try {
|
|
148
|
-
event = buildClaudeHookConductorEvent(payload, env);
|
|
149
|
-
} catch {
|
|
150
|
-
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
151
|
-
return 0;
|
|
152
|
-
}
|
|
153
|
-
if (event === null) {
|
|
154
|
-
return 0;
|
|
155
|
-
}
|
|
156
|
-
const ok = emitClaudeHookEventWithConductorCli(event, { env, spawn: deps.spawn });
|
|
157
|
-
if (!ok) {
|
|
158
|
-
warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING);
|
|
159
|
-
}
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
function readStdinSync() {
|
|
163
|
-
return readFileSync(0, "utf-8");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// src/conductor-claude-hook-bin.ts
|
|
167
|
-
var exitCode = 0;
|
|
168
|
-
try {
|
|
169
|
-
exitCode = runClaudeConductorHookCli();
|
|
170
|
-
} catch {
|
|
171
|
-
exitCode = 0;
|
|
172
|
-
}
|
|
173
|
-
process.exit(exitCode);
|
|
2
|
+
import{spawnSync}from"node:child_process";import{readFileSync}from"node:fs";var CONDUCTOR_HOOK_EMIT_FAILED_WARNING="Warning: conductor hook emit failed.";function resolveClaudeHookEventName(payload){if(payload===null||typeof payload!="object"||Array.isArray(payload))return null;let record=payload,candidates=["hook_event_name","hookEventName","event_name","event"];for(let key of candidates){let value=record[key];if(typeof value=="string"&&value.trim().length>0)return value.trim()}return null}function mapClaudeHookEventToSemanticType(eventName){switch(eventName){case"SessionStart":return"run.started";case"SessionEnd":return"run.stopped";case"Stop":return null;case"SubagentStop":return null;case"Notification":return"agent.notification";case"PreToolUse":return"tool.intent";default:return null}}var REQUIRED_IDENTITY_ENV=["BAPI_CONDUCTOR_RUN_ID","BAPI_CONDUCTOR_WORKER_ID","BAPI_CONDUCTOR_TICKET_KEY","BAPI_CONDUCTOR_WORKTREE_PATH"];function nonEmpty(value){return typeof value=="string"&&value.trim().length>0}function buildClaudeHookConductorEvent(payload,env){let eventName=resolveClaudeHookEventName(payload),type=mapClaudeHookEventToSemanticType(eventName);if(type===null||env.BAPI_CONDUCTOR_ENABLED!=="1")return null;for(let key of REQUIRED_IDENTITY_ENV)if(!nonEmpty(env[key]))return null;let ticketKey=env.BAPI_CONDUCTOR_TICKET_KEY.trim(),worktreePath=env.BAPI_CONDUCTOR_WORKTREE_PATH.trim(),details={ticket_key:ticketKey,worktree_path:worktreePath};nonEmpty(env.BAPI_CONDUCTOR_REPO_NAME)&&(details.repo=env.BAPI_CONDUCTOR_REPO_NAME.trim());let raw=payload!==null&&typeof payload=="object"&&!Array.isArray(payload)?payload:{payload};return{source:"claude-code",type,subject:ticketKey,run_id:env.BAPI_CONDUCTOR_RUN_ID.trim(),worker_id:env.BAPI_CONDUCTOR_WORKER_ID.trim(),producer:"claude-code-hook",observed_via:"claude-code-hook",data:{details,raw}}}function buildConductorEmitEventArgs(event){let args=["--type",event.type,"--source",event.source];return nonEmpty(event.subject??void 0)&&args.push("--subject",event.subject),nonEmpty(event.run_id??void 0)&&args.push("--run-id",event.run_id),nonEmpty(event.worker_id??void 0)&&args.push("--worker-id",event.worker_id),nonEmpty(event.producer??void 0)&&args.push("--producer",event.producer),nonEmpty(event.observed_via??void 0)&&args.push("--observed-via",event.observed_via),args.push("--data-json-stdin","--json"),args}function resolveConductorEmitCommand(env,emitArgs){let cliFile=env.BAPI_CONDUCTOR_CLI_FILE;return nonEmpty(cliFile)?{command:process.execPath,args:[cliFile.trim(),"emit-event",...emitArgs]}:{command:nonEmpty(env.BAPI_CONDUCTOR_BIN)?env.BAPI_CONDUCTOR_BIN.trim():"conductor",args:["emit-event",...emitArgs]}}var defaultConductorSpawn=(command,args,input)=>{let result=spawnSync(command,args,{input,encoding:"utf-8",shell:!1});return{status:result.status,error:result.error}};function emitClaudeHookEventWithConductorCli(event,deps={}){let env=deps.env??process.env,spawn=deps.spawn??defaultConductorSpawn,emitArgs=buildConductorEmitEventArgs(event),{command,args}=resolveConductorEmitCommand(env,emitArgs),stdinPayload=JSON.stringify(event.data??{});try{let result=spawn(command,args,stdinPayload);return result.error?!1:result.status===0}catch{return!1}}function runClaudeConductorHookCli(deps={}){let env=deps.env??process.env,readStdin=deps.readStdin??(()=>readStdinSync()),warn=deps.warn??(m=>process.stderr.write(`${m}
|
|
3
|
+
`)),payload;try{let raw=readStdin();payload=JSON.parse(raw)}catch{return warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING),0}let event;try{event=buildClaudeHookConductorEvent(payload,env)}catch{return warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING),0}return event===null||emitClaudeHookEventWithConductorCli(event,{env,spawn:deps.spawn})||warn(CONDUCTOR_HOOK_EMIT_FAILED_WARNING),0}function readStdinSync(){return readFileSync(0,"utf-8")}var exitCode=0;try{exitCode=runClaudeConductorHookCli()}catch{exitCode=0}process.exit(exitCode);
|
|
@@ -31,6 +31,10 @@ const ALL_STATUSES = new Set([
|
|
|
31
31
|
"waiting",
|
|
32
32
|
...TERMINAL_STATUSES,
|
|
33
33
|
]);
|
|
34
|
+
const KNOWN_INDEXING_STATUSES = new Set([
|
|
35
|
+
"started",
|
|
36
|
+
"waiting-for-setup",
|
|
37
|
+
]);
|
|
34
38
|
/** Per-request timeout. Generous enough for a cold server, short enough to retry. */
|
|
35
39
|
const REQUEST_TIMEOUT_MS = 15_000;
|
|
36
40
|
/**
|
|
@@ -211,11 +215,16 @@ export async function confirmGithubConnection(deps, repoName, state, githubRepos
|
|
|
211
215
|
if (!body || body.status !== "connected" || !name) {
|
|
212
216
|
return { ok: false, kind: "malformed" };
|
|
213
217
|
}
|
|
218
|
+
const rawIndexingStatus = body.indexing_status;
|
|
219
|
+
const indexingStatus = typeof rawIndexingStatus === "string" && KNOWN_INDEXING_STATUSES.has(rawIndexingStatus)
|
|
220
|
+
? rawIndexingStatus
|
|
221
|
+
: null;
|
|
214
222
|
return {
|
|
215
223
|
ok: true,
|
|
216
224
|
value: {
|
|
217
225
|
githubRepoName: name,
|
|
218
226
|
githubRepoFullName: asNullableString(body.github_repo_full_name),
|
|
227
|
+
indexingStatus,
|
|
219
228
|
},
|
|
220
229
|
};
|
|
221
230
|
}
|
package/build/connect-github.js
CHANGED
|
@@ -352,6 +352,16 @@ export async function runGithubConnectionFlow(deps, api, repoName) {
|
|
|
352
352
|
return reportNoConnection(deps, FAILURE_MESSAGES[confirmed.kind]);
|
|
353
353
|
}
|
|
354
354
|
deps.stdout(`Connected ${confirmed.value.githubRepoFullName ?? confirmed.value.githubRepoName}.`);
|
|
355
|
+
// BAPI-658: truthful, secondary to connection success. Only these two recognized
|
|
356
|
+
// statuses ever print anything — an absent/unknown backend value (already-indexed,
|
|
357
|
+
// already-active, skipped, error, or a future value this build doesn't know about)
|
|
358
|
+
// stays silent rather than guessing at indexing state.
|
|
359
|
+
if (confirmed.value.indexingStatus === "started") {
|
|
360
|
+
deps.stdout("Indexing started automatically.");
|
|
361
|
+
}
|
|
362
|
+
else if (confirmed.value.indexingStatus === "waiting-for-setup") {
|
|
363
|
+
deps.stdout("Indexing will start automatically once setup completes.");
|
|
364
|
+
}
|
|
355
365
|
return 0;
|
|
356
366
|
}
|
|
357
367
|
// ---------------------------------------------------------------------------
|
|
@@ -59,7 +59,7 @@ function hasDisplayText(value) {
|
|
|
59
59
|
// ---------------------------------------------------------------------------
|
|
60
60
|
// Template
|
|
61
61
|
// ---------------------------------------------------------------------------
|
|
62
|
-
const DEFAULT_ASSETS = { faviconBase64: "", logoBase64: ""
|
|
62
|
+
const DEFAULT_ASSETS = { faviconBase64: "", logoBase64: "" };
|
|
63
63
|
export function generateDecisionPageHtml(data, assets = DEFAULT_ASSETS) {
|
|
64
64
|
const { ticket_key, actionable_items, clear_improvements } = data;
|
|
65
65
|
const hasDecisions = actionable_items.length > 0;
|
|
@@ -73,7 +73,6 @@ export function generateDecisionPageHtml(data, assets = DEFAULT_ASSETS) {
|
|
|
73
73
|
const faviconLink = assets.faviconBase64
|
|
74
74
|
? `<link rel="icon" type="image/png" sizes="32x32" href="data:image/png;base64,${assets.faviconBase64}">`
|
|
75
75
|
: "";
|
|
76
|
-
const fontFaces = assets.fontsRelPath ? renderFontFaces(assets.fontsRelPath) : "";
|
|
77
76
|
// The has-decisions intro is an overridable label; the no-decisions copy is a
|
|
78
77
|
// fixed constant. For review_decisions pages the MCP handler short-circuits
|
|
79
78
|
// before rendering when there are no decisions, so that branch is reachable
|
|
@@ -93,7 +92,6 @@ export function generateDecisionPageHtml(data, assets = DEFAULT_ASSETS) {
|
|
|
93
92
|
<title>${escapeHtml(effectiveLabels.title)}: ${escapeHtml(ticket_key)}</title>
|
|
94
93
|
${faviconLink}
|
|
95
94
|
<style>
|
|
96
|
-
${fontFaces}
|
|
97
95
|
:root {
|
|
98
96
|
--primary-color: #E2624B;
|
|
99
97
|
--primary-dark: #C51616;
|
|
@@ -112,7 +110,7 @@ ${fontFaces}
|
|
|
112
110
|
}
|
|
113
111
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
114
112
|
body {
|
|
115
|
-
font-family:
|
|
113
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
|
116
114
|
background-color: var(--background-color);
|
|
117
115
|
color: var(--text-color);
|
|
118
116
|
line-height: 1.5;
|
|
@@ -541,33 +539,6 @@ ${needsForm ? renderScript(data, isPlanning) : ""}
|
|
|
541
539
|
</html>`;
|
|
542
540
|
}
|
|
543
541
|
// ---------------------------------------------------------------------------
|
|
544
|
-
// Font faces
|
|
545
|
-
// ---------------------------------------------------------------------------
|
|
546
|
-
// fontsRelPath comes from server-controlled env vars, so this is not an
|
|
547
|
-
// injection vector — but a deploy path containing a single quote or backslash
|
|
548
|
-
// would silently break CSS parsing and stop fonts from loading. Escape both
|
|
549
|
-
// so the url('...') stays well-formed regardless of deploy layout.
|
|
550
|
-
function escapeCssUrl(value) {
|
|
551
|
-
return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
552
|
-
}
|
|
553
|
-
function renderFontFaces(fontsRelPath) {
|
|
554
|
-
const weights = [
|
|
555
|
-
{ weight: "400", style: "normal", file: "SourceSansPro-Regular.ttf" },
|
|
556
|
-
{ weight: "400", style: "italic", file: "SourceSansPro-Italic.ttf" },
|
|
557
|
-
{ weight: "600", style: "normal", file: "SourceSansPro-SemiBold.ttf" },
|
|
558
|
-
{ weight: "600", style: "italic", file: "SourceSansPro-SemiBoldItalic.ttf" },
|
|
559
|
-
{ weight: "700", style: "normal", file: "SourceSansPro-Bold.ttf" },
|
|
560
|
-
{ weight: "700", style: "italic", file: "SourceSansPro-BoldItalic.ttf" },
|
|
561
|
-
];
|
|
562
|
-
const safePath = escapeCssUrl(fontsRelPath);
|
|
563
|
-
return weights.map(({ weight, style, file }) => ` @font-face {
|
|
564
|
-
font-family: 'Source Sans Pro';
|
|
565
|
-
src: url('${safePath}/${escapeCssUrl(file)}') format('truetype');
|
|
566
|
-
font-weight: ${weight};
|
|
567
|
-
font-style: ${style};
|
|
568
|
-
}`).join("\n");
|
|
569
|
-
}
|
|
570
|
-
// ---------------------------------------------------------------------------
|
|
571
542
|
// Section renderers
|
|
572
543
|
// ---------------------------------------------------------------------------
|
|
573
544
|
function renderHeader(assets) {
|
package/build/doctor.js
CHANGED
|
@@ -378,7 +378,7 @@ export function formatLauncherCacheReport(inspections) {
|
|
|
378
378
|
/**
|
|
379
379
|
* Collect the read-only tool-surface diagnostic. Evaluates the kill switch first
|
|
380
380
|
* and returns without any network request when disabled. For an enabled flag and
|
|
381
|
-
* a resolved repo/credential, performs ONE
|
|
381
|
+
* a resolved repo/credential, performs ONE `probeToolSurface()` GET to the
|
|
382
382
|
* deployed route. Credential values live ONLY in the request headers and never in
|
|
383
383
|
* the returned result. Never throws.
|
|
384
384
|
*/
|
|
@@ -465,7 +465,7 @@ export function formatToolSurfaceDiagnosticReport(diag) {
|
|
|
465
465
|
break;
|
|
466
466
|
}
|
|
467
467
|
case "timeout":
|
|
468
|
-
lines.push("Reason: timeout — the
|
|
468
|
+
lines.push("Reason: timeout — the probe deadline elapsed; fail-open to full surface.");
|
|
469
469
|
break;
|
|
470
470
|
case "malformed":
|
|
471
471
|
lines.push(`Reason: malformed (${diag.subtype ?? "unknown"}) — fail-open to full surface.`);
|
package/build/env-flags.js
CHANGED
|
@@ -15,6 +15,14 @@ const OFF_TOKENS = new Set([
|
|
|
15
15
|
"off",
|
|
16
16
|
"disabled",
|
|
17
17
|
]);
|
|
18
|
+
/** The recognized, case-insensitive on-tokens for a default-off flag. */
|
|
19
|
+
const ON_TOKENS = new Set([
|
|
20
|
+
"true",
|
|
21
|
+
"1",
|
|
22
|
+
"yes",
|
|
23
|
+
"on",
|
|
24
|
+
"enabled",
|
|
25
|
+
]);
|
|
18
26
|
/**
|
|
19
27
|
* Parse a default-ON boolean env flag. `undefined` / blank → true; the
|
|
20
28
|
* normalized off-tokens (`false`, `0`, `no`, `off`, `disabled`) → false; any
|
|
@@ -29,3 +37,18 @@ export function parseDefaultOnEnvFlag(value) {
|
|
|
29
37
|
return true;
|
|
30
38
|
return !OFF_TOKENS.has(normalized);
|
|
31
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse a default-OFF boolean env flag. `undefined` / blank → false; only the
|
|
42
|
+
* normalized on-tokens (`true`, `1`, `yes`, `on`, `enabled`) → true; any other
|
|
43
|
+
* value → false (preserving default-off behavior). Matching is case-insensitive
|
|
44
|
+
* and trims surrounding whitespace. Used for opt-IN features that stay dormant
|
|
45
|
+
* unless an operator explicitly turns them on.
|
|
46
|
+
*/
|
|
47
|
+
export function parseDefaultOffEnvFlag(value) {
|
|
48
|
+
if (value === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
const normalized = value.trim().toLowerCase();
|
|
51
|
+
if (normalized === "")
|
|
52
|
+
return false;
|
|
53
|
+
return ON_TOKENS.has(normalized);
|
|
54
|
+
}
|