diff --git a/agent/agent-tool-config.mjs b/agent/agent-tool-config.mjs index 888f041c1..cf7966d72 100644 --- a/agent/agent-tool-config.mjs +++ b/agent/agent-tool-config.mjs @@ -28,10 +28,12 @@ * setAgentToolConfig(rootDir, agentId, config) — update config for one agent * getEffectiveTools(rootDir, agentId) — compute final enabled tools list * listAvailableTools(rootDir) — list all available tools (builtin + MCP) + * measureToolDefinitionChars(toolDefs) — char counts for serialized tool defs + * getToolOverheadReport(rootDir, agentId) — persisted/runtime overhead summary */ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"; -import { resolve } from "node:path"; +import { basename, resolve } from "node:path"; import { homedir } from "node:os"; // ── Constants ───────────────────────────────────────────────────────────────── @@ -51,6 +53,36 @@ function getBosunHome() { * Default built-in tools available to all voice agents and executors. * Maps to common capabilities that voice/agent sessions can invoke. */ +function measureToolDefinitionCharsInternal(toolDefs = []) { + const tools = Array.isArray(toolDefs) + ? toolDefs.map((toolDef, index) => { + const serialized = JSON.stringify(toolDef ?? null); + const fallbackId = `tool-${index + 1}`; + return { + id: String(toolDef?.id || toolDef?.name || toolDef?.tool_name || fallbackId), + chars: serialized.length, + }; + }) + : []; + + return { + total: tools.reduce((sum, tool) => sum + tool.chars, 0), + tools, + }; +} + +export function measureToolDefinitionChars(toolDefs = []) { + return measureToolDefinitionCharsInternal(toolDefs); +} + +function normalizeSourceCharMap(bySource = {}) { + return Object.fromEntries( + Object.entries(bySource) + .map(([source, chars]) => [String(source), Math.max(0, Number(chars) || 0)]) + .sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])), + ); +} + export const DEFAULT_BUILTIN_TOOLS = Object.freeze([ { id: "search-files", @@ -177,7 +209,11 @@ export const DEFAULT_BUILTIN_TOOLS = Object.freeze([ // ── Config File I/O ─────────────────────────────────────────────────────────── function getConfigPath(rootDir) { - return resolve(rootDir || getBosunHome(), ".bosun", CONFIG_FILE); + const baseDir = rootDir || getBosunHome(); + const configDir = basename(resolve(baseDir)) === ".bosun" + ? resolve(baseDir) + : resolve(baseDir, ".bosun"); + return resolve(configDir, CONFIG_FILE); } /** @@ -194,6 +230,7 @@ export function loadToolConfig(rootDir) { builtinTools: DEFAULT_BUILTIN_TOOLS.filter((t) => t.default).map((t) => t.id), updatedAt: new Date().toISOString(), }, + toolOverhead: {}, }; } try { @@ -205,6 +242,7 @@ export function loadToolConfig(rootDir) { builtinTools: DEFAULT_BUILTIN_TOOLS.filter((t) => t.default).map((t) => t.id), updatedAt: new Date().toISOString(), }, + toolOverhead: parsed.toolOverhead || {}, }; } catch { return { @@ -213,6 +251,7 @@ export function loadToolConfig(rootDir) { builtinTools: DEFAULT_BUILTIN_TOOLS.filter((t) => t.default).map((t) => t.id), updatedAt: new Date().toISOString(), }, + toolOverhead: {}, }; } } @@ -336,3 +375,66 @@ export async function listAvailableTools(rootDir) { })), }; } + +function persistToolOverheadReport(rootDir, agentId, report) { + if (!rootDir || !agentId || !report) return report; + const cfg = loadToolConfig(rootDir); + cfg.toolOverhead ||= {}; + cfg.toolOverhead[agentId] = { + total: Math.max(0, Number(report.total) || 0), + bySource: normalizeSourceCharMap(report.bySource), + updatedAt: new Date().toISOString(), + }; + saveToolConfig(rootDir, cfg); + return cfg.toolOverhead[agentId]; +} + +export function getToolOverheadReport(rootDir, agentId) { + const cfg = loadToolConfig(rootDir); + const stored = cfg.toolOverhead?.[agentId] || null; + if (!stored) return { total: 0, bySource: {} }; + return { + total: Math.max(0, Number(stored.total) || 0), + bySource: normalizeSourceCharMap(stored.bySource), + }; +} + +export async function refreshToolOverheadReport(rootDir, agentId, options = {}) { + const builtinMeasurement = measureToolDefinitionChars(DEFAULT_BUILTIN_TOOLS); + const report = { + total: builtinMeasurement.total, + bySource: { builtin: builtinMeasurement.total }, + }; + + const agentConfig = agentId ? getAgentToolConfig(rootDir, agentId) : { enabledMcpServers: [] }; + const enabledServerIds = Array.isArray(options.serverIds) + ? options.serverIds + : Array.isArray(agentConfig.enabledMcpServers) + ? agentConfig.enabledMcpServers + : []; + + if (enabledServerIds.length > 0) { + try { + const { resolveMcpServersForAgent } = await import("../workflow/mcp-registry.mjs"); + const servers = await resolveMcpServersForAgent(rootDir, enabledServerIds); + for (const server of servers) { + const serverDefs = Array.isArray(server?.tools) ? server.tools : []; + const measurement = measureToolDefinitionChars(serverDefs); + report.bySource[server.id || server.name || "unknown-mcp"] = measurement.total; + report.total += measurement.total; + } + } catch { + for (const serverId of enabledServerIds) report.bySource[serverId] ||= 0; + } + } + + const persisted = persistToolOverheadReport(rootDir, agentId, report); + if (process.env.BOSUN_LOG_TOOL_OVERHEAD === "1") { + console.log(TAG + " tool definition overhead for " + (agentId || "agent") + ":"); + for (const [source, chars] of Object.entries(persisted.bySource)) { + console.log(TAG + " " + source + ": " + chars + " chars"); + } + console.log(TAG + " total: " + persisted.total + " chars"); + } + return persisted; +} diff --git a/agent/primary-agent.mjs b/agent/primary-agent.mjs index 907fd702c..ebe33fc17 100644 --- a/agent/primary-agent.mjs +++ b/agent/primary-agent.mjs @@ -10,7 +10,7 @@ import { ensureCodexConfig, printConfigSummary } from "../shell/codex-config.mjs import { ensureRepoConfigs, printRepoConfigSummary } from "../config/repo-config.mjs"; import { resolveRepoRoot } from "../config/repo-root.mjs"; import { buildArchitectEditorFrame } from "../lib/repo-map.mjs"; -import { getAgentToolConfig, getEffectiveTools } from "./agent-tool-config.mjs"; +import { getAgentToolConfig, getEffectiveTools, refreshToolOverheadReport } from "./agent-tool-config.mjs"; import { getSessionTracker } from "../infra/session-tracker.mjs"; import { buildContextEnvelope } from "../workspace/context-cache.mjs"; import { getEntry, getEntryContent, resolveAgentProfileLibraryMetadata } from "../infra/library-manager.mjs"; @@ -234,6 +234,12 @@ function buildPrimaryToolCapabilityContract(options = {}) { const enabledMcpServers = Array.isArray(rawCfg?.enabledMcpServers) ? rawCfg.enabledMcpServers.map((id) => String(id || "").trim()).filter(Boolean) : []; + if (agentProfileId) { + void refreshToolOverheadReport(rootDir, agentProfileId, { serverIds: enabledMcpServers }) + .catch((error) => { + console.warn("[primary-agent] failed to refresh tool overhead report:", error?.message || error); + }); + } const manifest = { agentProfileId: agentProfileId || null, enabledBuiltinTools, @@ -1532,4 +1538,3 @@ export async function execSdkCommand(command, args = "", adapterName, options = - diff --git a/cli.mjs b/cli.mjs index 65cb49d67..48be60778 100755 --- a/cli.mjs +++ b/cli.mjs @@ -1469,6 +1469,8 @@ async function main() { console.error(` Error: ${err.message}`); process.exit(1); } + + process.exit(0); } @@ -2176,9 +2178,12 @@ async function main() { // Handle --workspace-status if (args.includes("--workspace-status") || args.includes("workspace-status")) { const { getWorkspaceStateSummary } = await import("./workspace/workspace-manager.mjs"); + const { getToolOverheadReport } = await import("./agent/agent-tool-config.mjs"); const configDirArg = getArgValue("--config-dir"); const configDir = configDirArg || process.env.BOSUN_DIR || resolveConfigDirForCli(); const summary = getWorkspaceStateSummary(configDir); + const toolOverhead = getToolOverheadReport(configDir, "primary"); + const overheadSources = Object.entries(toolOverhead.bySource || {}); if (summary.length === 0) { console.log("\n No workspaces configured.\n"); } else { @@ -2196,8 +2201,16 @@ async function main() { console.log(` enabled workflows: ${ws.enabledWorkflows.join(", ")}`); } } - console.log(""); } + if (toolOverhead.total > 0 || overheadSources.length > 0) { + console.log(" Tool Overhead:"); + console.log(` Total tool chars: ${toolOverhead.total.toLocaleString("en-US")}`); + for (const [source, chars] of overheadSources) { + const warning = Number(chars) > 10000 ? " WARNING: high overhead" : ""; + console.log(` ${source}: ${Number(chars).toLocaleString("en-US")} chars${warning}`); + } + } + console.log(""); process.exit(0); } diff --git a/site/ui/demo-defaults.js b/site/ui/demo-defaults.js index db0167409..307bf5060 100644 --- a/site/ui/demo-defaults.js +++ b/site/ui/demo-defaults.js @@ -2000,7 +2000,7 @@ "label": "Pick Conflict PR", "config": { "key": "targetPrNumber", - "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", + "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const hasBosunCreatedText = (value) => { const text = String(value || ''); const taskIdMatch = text.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); const hasLegacyTaskSignature = Boolean(taskIdMatch && text.toLowerCase().includes(`automated pr for task ${String(taskIdMatch[1] || '').trim().toLowerCase()}`)); return text.includes('') || /Bosun-Origin:\\s*created/i.test(text) || /auto-created by bosun/i.test(text) || hasLegacyTaskSignature; }; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL) || hasBosunCreatedText(pr?.body); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", "isExpression": true }, "position": { @@ -26609,7 +26609,7 @@ "label": "Pick Conflict PR", "config": { "key": "targetPrNumber", - "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", + "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const hasBosunCreatedText = (value) => { const text = String(value || ''); const taskIdMatch = text.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); const hasLegacyTaskSignature = Boolean(taskIdMatch && text.toLowerCase().includes(`automated pr for task ${String(taskIdMatch[1] || '').trim().toLowerCase()}`)); return text.includes('') || /Bosun-Origin:\\s*created/i.test(text) || /auto-created by bosun/i.test(text) || hasLegacyTaskSignature; }; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL) || hasBosunCreatedText(pr?.body); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", "isExpression": true }, "position": { diff --git a/tests/agent-tool-config.test.mjs b/tests/agent-tool-config.test.mjs new file mode 100644 index 000000000..20c62e7e4 --- /dev/null +++ b/tests/agent-tool-config.test.mjs @@ -0,0 +1,67 @@ +import { mkdtempSync, existsSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +import { + DEFAULT_BUILTIN_TOOLS, + getToolOverheadReport, + measureToolDefinitionChars, + saveToolConfig, +} from "../agent/agent-tool-config.mjs"; + +describe("agent tool overhead", () => { + it("measures serialized chars per tool and total", () => { + const toolDefs = [ + { id: "alpha", name: "Alpha", description: "First tool" }, + { id: "beta", schema: { type: "object", properties: { q: { type: "string" } } } }, + ]; + + const report = measureToolDefinitionChars(toolDefs); + + expect(report.total).toBe(toolDefs.reduce((sum, tool) => sum + JSON.stringify(tool).length, 0)); + expect(report.tools).toEqual([ + { id: "alpha", chars: JSON.stringify(toolDefs[0]).length }, + { id: "beta", chars: JSON.stringify(toolDefs[1]).length }, + ]); + }); + + it("supports builtin tool definitions", () => { + const report = measureToolDefinitionChars(DEFAULT_BUILTIN_TOOLS); + + expect(report.total).toBeGreaterThan(0); + expect(report.tools).toHaveLength(DEFAULT_BUILTIN_TOOLS.length); + expect(report.tools[0]).toHaveProperty("chars"); + }); + + it("persists overhead reports when passed an explicit .bosun config dir", () => { + const rootDir = mkdtempSync(resolve(tmpdir(), "bosun-agent-tools-")); + const bosunDir = resolve(rootDir, ".bosun"); + + try { + saveToolConfig(bosunDir, { + agents: {}, + defaults: { + builtinTools: ["search-files"], + updatedAt: "2026-03-25T00:00:00.000Z", + }, + toolOverhead: { + primary: { + total: 23456, + bySource: { builtin: 3456, "huge-server": 20000 }, + updatedAt: "2026-03-25T00:00:00.000Z", + }, + }, + }); + + expect(existsSync(resolve(bosunDir, "agent-tools.json"))).toBe(true); + expect(existsSync(resolve(bosunDir, ".bosun", "agent-tools.json"))).toBe(false); + expect(getToolOverheadReport(bosunDir, "primary")).toEqual({ + total: 23456, + bySource: { builtin: 3456, "huge-server": 20000 }, + }); + } finally { + rmSync(rootDir, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/cli-tool-overhead-status.test.mjs b/tests/cli-tool-overhead-status.test.mjs new file mode 100644 index 000000000..2c3981b7a --- /dev/null +++ b/tests/cli-tool-overhead-status.test.mjs @@ -0,0 +1,51 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; + +describe("cli tool overhead status", () => { + it("shows tool overhead totals and warnings in workspace status", () => { + const rootDir = mkdtempSync(resolve(tmpdir(), "bosun-tool-overhead-")); + const bosunDir = resolve(rootDir, ".bosun"); + mkdirSync(bosunDir, { recursive: true }); + writeFileSync(resolve(bosunDir, "bosun.config.json"), JSON.stringify({ workspaces: [] }), "utf8"); + writeFileSync( + resolve(bosunDir, "agent-tools.json"), + JSON.stringify({ + agents: { + primary: { + enabledMcpServers: ["huge-server"], + updatedAt: "2026-03-25T00:00:00.000Z", + }, + }, + defaults: { builtinTools: ["search-files"], updatedAt: "2026-03-25T00:00:00.000Z" }, + toolOverhead: { + primary: { + total: 23456, + bySource: { + builtin: 3456, + "huge-server": 20000, + }, + }, + }, + }), + "utf8", + ); + + try { + const output = execFileSync(process.execPath, ["cli.mjs", "--workspace-status", "--config-dir", bosunDir], { + cwd: process.cwd(), + encoding: "utf8", + }); + + expect(output).toContain("Tool Overhead:"); + expect(output).toContain("Total tool chars: 23,456"); + expect(output).toContain("builtin: 3,456 chars"); + expect(output).toContain("huge-server: 20,000 chars"); + expect(output).toContain("WARNING"); + } finally { + rmSync(rootDir, { recursive: true, force: true }); + } + }); +}); diff --git a/ui/demo-defaults.js b/ui/demo-defaults.js index db0167409..307bf5060 100644 --- a/ui/demo-defaults.js +++ b/ui/demo-defaults.js @@ -2000,7 +2000,7 @@ "label": "Pick Conflict PR", "config": { "key": "targetPrNumber", - "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", + "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const hasBosunCreatedText = (value) => { const text = String(value || ''); const taskIdMatch = text.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); const hasLegacyTaskSignature = Boolean(taskIdMatch && text.toLowerCase().includes(`automated pr for task ${String(taskIdMatch[1] || '').trim().toLowerCase()}`)); return text.includes('') || /Bosun-Origin:\\s*created/i.test(text) || /auto-created by bosun/i.test(text) || hasLegacyTaskSignature; }; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL) || hasBosunCreatedText(pr?.body); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", "isExpression": true }, "position": { @@ -26609,7 +26609,7 @@ "label": "Pick Conflict PR", "config": { "key": "targetPrNumber", - "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", + "value": "/* */ (() => { const raw = $ctx.getNodeOutput('list-prs')?.output || '[]'; let prs = []; try { prs = typeof raw === 'string' ? JSON.parse(raw) : raw; } catch { return ''; } if (!Array.isArray(prs)) return ''; const CONFLICT = new Set(['CONFLICTING', 'BEHIND', 'DIRTY']); const BOSUN_CREATED_LABEL = 'bosun-pr-bosun-created'; const readLabelNames = (pr) => Array.isArray(pr?.labels) ? pr.labels.map((entry) => typeof entry === 'string' ? entry : entry?.name).filter(Boolean) : []; const hasBosunCreatedText = (value) => { const text = String(value || ''); const taskIdMatch = text.match(/(?:Bosun-Task|VE-Task|Task-ID|task[_-]?id)[:\\s]+([a-zA-Z0-9_-]{4,64})/i); const hasLegacyTaskSignature = Boolean(taskIdMatch && text.toLowerCase().includes(`automated pr for task ${String(taskIdMatch[1] || '').trim().toLowerCase()}`)); return text.includes('') || /Bosun-Origin:\\s*created/i.test(text) || /auto-created by bosun/i.test(text) || hasLegacyTaskSignature; }; const isBosunCreated = (pr) => readLabelNames(pr).includes(BOSUN_CREATED_LABEL) || hasBosunCreatedText(pr?.body); /* Skip PRs already owned by the watchdog fix agent */ const pr = prs.find((p) => isBosunCreated(p) && CONFLICT.has(String(p?.mergeable || '').toUpperCase()) && !(p.labels || []).some((l) => l.name === 'bosun-needs-fix') ); return pr?.number ? String(pr.number) : '';})()", "isExpression": true }, "position": { diff --git a/ui/vendor/es-module-shims.js b/ui/vendor/es-module-shims.js index 8e5261d5d..522f0acd2 100644 --- a/ui/vendor/es-module-shims.js +++ b/ui/vendor/es-module-shims.js @@ -1,1482 +1,16 @@ -/** ES Module Shims @version 2.8.0 */ -(function () { - - const self_ = typeof globalThis !== 'undefined' ? globalThis : self; - - let invalidate; - const hotReload$1 = url => invalidate(new URL(url, baseUrl).href); - const initHotReload = (topLevelLoad, importShim) => { - let _importHook = importHook, - _resolveHook = resolveHook, - _metaHook = metaHook; - - let defaultResolve; - let hotResolveHook = (id, parent, _defaultResolve) => { - if (!defaultResolve) defaultResolve = _defaultResolve; - const originalParent = stripVersion(parent); - const url = stripVersion(defaultResolve(id, originalParent)); - const hotState = getHotState(url); - const parents = hotState.p; - if (!parents.includes(originalParent)) parents.push(originalParent); - return toVersioned(url, hotState); - }; - const hotImportHook = (url, _, __, source, sourceType) => { - const hotState = getHotState(url); - hotState.e = typeof source === 'string' ? source : true; - hotState.t = sourceType; - }; - const hotMetaHook = (metaObj, url) => (metaObj.hot = new Hot(url)); - - const Hot = class Hot { - constructor(url) { - this.data = getHotState((this.url = stripVersion(url))).d; - } - accept(deps, cb) { - if (typeof deps === 'function') { - cb = deps; - deps = null; - } - const hotState = getHotState(this.url); - if (!hotState.A) return; - (hotState.a = hotState.a || []).push([ - typeof deps === 'string' ? defaultResolve(deps, this.url) - : deps ? deps.map(d => defaultResolve(d, this.url)) - : null, - cb - ]); - } - dispose(cb) { - getHotState(this.url).u = cb; - } - invalidate() { - const hotState = getHotState(this.url); - hotState.a = hotState.A = null; - const seen = [this.url]; - hotState.p.forEach(p => invalidate(p, this.url, seen)); - } - }; - - const versionedRegEx = /\?v=\d+$/; - const stripVersion = url => { - const versionMatch = url.match(versionedRegEx); - return versionMatch ? url.slice(0, -versionMatch[0].length) : url; - }; - - const toVersioned = (url, hotState) => { - const { v } = hotState; - return url + (v ? '?v=' + v : ''); - }; - - let hotRegistry = {}, - curInvalidationRoots = new Set(), - curInvalidationInterval; - const getHotState = url => - hotRegistry[url] || - (hotRegistry[url] = { - // version - v: 0, - // accept list ([deps, cb] pairs) - a: null, - // accepting acceptors - A: true, - // unload callback - u: null, - // entry point or inline script source - e: false, - // hot data - d: {}, - // parents - p: [], - // source type - t: undefined - }); - - invalidate = (url, fromUrl, seen = []) => { - const hotState = hotRegistry[url]; - if (!hotState || seen.includes(url)) return false; - seen.push(url); - hotState.A = false; - if ( - fromUrl && - hotState.a && - hotState.a.some(([d]) => d && (typeof d === 'string' ? d === fromUrl : d.includes(fromUrl))) - ) { - curInvalidationRoots.add(fromUrl); - } else { - if (hotState.e || hotState.a) curInvalidationRoots.add(url); - hotState.v++; - if (!hotState.a) hotState.p.forEach(p => invalidate(p, url, seen)); - } - if (!curInvalidationInterval) curInvalidationInterval = setTimeout(handleInvalidations, hotReloadInterval); - return true; - }; - - const handleInvalidations = () => { - curInvalidationInterval = null; - const earlyRoots = new Set(); - for (const root of curInvalidationRoots) { - const hotState = hotRegistry[root]; - topLevelLoad( - toVersioned(root, hotState), - baseUrl, - defaultFetchOpts, - typeof hotState.e === 'string' ? hotState.e : undefined, - false, - undefined, - hotState.t - ).then(m => { - if (hotState.a) { - hotState.a.forEach(([d, c]) => d === null && !earlyRoots.has(c) && c(m)); - // unload should be the latest unload handler from the just loaded module - if (hotState.u) { - hotState.u(hotState.d); - hotState.u = null; - } - } - hotState.p.forEach(p => { - const hotState = hotRegistry[p]; - if (hotState && hotState.a) - hotState.a.forEach( - async ([d, c]) => - d && - !earlyRoots.has(c) && - (typeof d === 'string' ? - d === root && c(m) - : c(await Promise.all(d.map(d => (earlyRoots.push(c), importShim(toVersioned(d, getHotState(d)))))))) - ); - }); - }, throwError); - } - curInvalidationRoots = new Set(); - }; - - setHooks( - _importHook ? chain(_importHook, hotImportHook) : hotImportHook, - _resolveHook ? - (id, parent, defaultResolve) => - hotResolveHook(id, parent, (id, parent) => _resolveHook(id, parent, defaultResolve)) - : hotResolveHook, - _metaHook ? chain(_metaHook, hotMetaHook) : hotMetaHook - ); - }; - - const hasDocument = typeof document !== 'undefined'; - - const noop = () => {}; - - const chain = (a, b) => - function () { - a.apply(this, arguments); - b.apply(this, arguments); - }; - - const dynamicImport = (u, _errUrl) => import(u); - - const defineValue = (obj, prop, value) => - Object.defineProperty(obj, prop, { writable: false, configurable: false, value }); - - const optionsScript = hasDocument ? document.querySelector('script[type=esms-options]') : undefined; - - const esmsInitOptions = optionsScript ? JSON.parse(optionsScript.innerHTML) : {}; - Object.assign(esmsInitOptions, self_.esmsInitOptions || {}); - - const version = "2.8.0"; - - const r$1 = esmsInitOptions.version; - if (self_.importShim || (r$1 && r$1 !== version)) { - return; - } - - // shim mode is determined on initialization, no late shim mode - const shimMode = - esmsInitOptions.shimMode || - (hasDocument ? - document.querySelectorAll('script[type=module-shim],script[type=importmap-shim],link[rel=modulepreload-shim]') - .length > 0 - // Without a document, shim mode is always true as we cannot polyfill - : true); - - let importHook, - resolveHook, - fetchHook = fetch, - sourceHook, - metaHook, - tsTransform = - esmsInitOptions.tsTransform || - (hasDocument && document.currentScript && document.currentScript.src.replace(/(\.\w+)?\.js$/, '-typescript.js')) || - './es-module-shims-typescript.js'; - - const defaultFetchOpts = { credentials: 'same-origin' }; - - const globalHook = name => (typeof name === 'string' ? self_[name] : name); - - if (esmsInitOptions.onimport) importHook = globalHook(esmsInitOptions.onimport); - if (esmsInitOptions.resolve) resolveHook = globalHook(esmsInitOptions.resolve); - if (esmsInitOptions.fetch) fetchHook = globalHook(esmsInitOptions.fetch); - if (esmsInitOptions.source) sourceHook = globalHook(esmsInitOptions.source); - if (esmsInitOptions.meta) metaHook = globalHook(esmsInitOptions.meta); - - const hasCustomizationHooks = importHook || resolveHook || fetchHook !== fetch || sourceHook || metaHook; - - const { - noLoadEventRetriggers, - enforceIntegrity, - hotReload, - hotReloadInterval = 100, - nativePassthrough = !hasCustomizationHooks && !hotReload - } = esmsInitOptions; - - const setHooks = (importHook_, resolveHook_, metaHook_) => ( - (importHook = importHook_), - (resolveHook = resolveHook_), - (metaHook = metaHook_) - ); - - const mapOverrides = esmsInitOptions.mapOverrides; - - let nonce = esmsInitOptions.nonce; - if (!nonce && hasDocument) { - const nonceElement = document.querySelector('script[nonce]'); - if (nonceElement) nonce = nonceElement.nonce || nonceElement.getAttribute('nonce'); - } - - const onerror = globalHook(esmsInitOptions.onerror || console.error.bind(console)); - - const enable = Array.isArray(esmsInitOptions.polyfillEnable) ? esmsInitOptions.polyfillEnable : []; - const disable = Array.isArray(esmsInitOptions.polyfillDisable) ? esmsInitOptions.polyfillDisable : []; - - const enableAll = esmsInitOptions.polyfillEnable === 'all' || enable.includes('all'); - const wasmInstancePhaseEnabled = - enable.includes('wasm-modules') || enable.includes('wasm-module-instances') || enableAll; - const wasmSourcePhaseEnabled = - enable.includes('wasm-modules') || enable.includes('wasm-module-sources') || enableAll; - const deferPhaseEnabled = enable.includes('import-defer') || enableAll; - const cssModulesEnabled = !disable.includes('css-modules'); - const jsonModulesEnabled = !disable.includes('json-modules'); - - const onpolyfill = - esmsInitOptions.onpolyfill ? - globalHook(esmsInitOptions.onpolyfill) - : () => { - console.log(`%c^^ Module error above is polyfilled and can be ignored ^^`, 'font-weight:900;color:#391'); - }; - - const baseUrl = - hasDocument ? document.baseURI - : typeof location !== 'undefined' ? - `${location.protocol}//${location.host}${ - location.pathname.includes('/') ? - location.pathname.slice(0, location.pathname.lastIndexOf('/') + 1) - : location.pathname - }` - : 'about:blank'; - - const createBlob = (source, type = 'text/javascript') => URL.createObjectURL(new Blob([source], { type })); - let { skip } = esmsInitOptions; - if (Array.isArray(skip)) { - const l = skip.map(s => new URL(s, baseUrl).href); - skip = s => l.some(i => (i[i.length - 1] === '/' && s.startsWith(i)) || s === i); - } else if (typeof skip === 'string') { - const r = new RegExp(skip); - skip = s => r.test(s); - } else if (skip instanceof RegExp) { - skip = s => skip.test(s); - } - - const dispatchError = error => self_.dispatchEvent(Object.assign(new Event('error'), { error })); - - const throwError = err => { - (self_.reportError || dispatchError)(err); - onerror(err); - }; - - const fromParent = parent => (parent ? ` imported from ${parent}` : ''); - - const backslashRegEx = /\\/g; - - const asURL = url => { - try { - if (url.indexOf(':') !== -1) return new URL(url).href; - } catch (_) {} - }; - - const resolveUrl = (relUrl, parentUrl) => - resolveIfNotPlainOrUrl(relUrl, parentUrl) || asURL(relUrl) || resolveIfNotPlainOrUrl('./' + relUrl, parentUrl); - - const resolveIfNotPlainOrUrl = (relUrl, parentUrl) => { - const hIdx = parentUrl.indexOf('#'), - qIdx = parentUrl.indexOf('?'); - if (hIdx + qIdx > -2) - parentUrl = parentUrl.slice( - 0, - hIdx === -1 ? qIdx - : qIdx === -1 || qIdx > hIdx ? hIdx - : qIdx - ); - if (relUrl.indexOf('\\') !== -1) relUrl = relUrl.replace(backslashRegEx, '/'); - // protocol-relative - if (relUrl[0] === '/' && relUrl[1] === '/') { - return parentUrl.slice(0, parentUrl.indexOf(':') + 1) + relUrl; - } - // relative-url - else if ( - (relUrl[0] === '.' && - (relUrl[1] === '/' || - (relUrl[1] === '.' && (relUrl[2] === '/' || (relUrl.length === 2 && (relUrl += '/')))) || - (relUrl.length === 1 && (relUrl += '/')))) || - relUrl[0] === '/' - ) { - const parentProtocol = parentUrl.slice(0, parentUrl.indexOf(':') + 1); - if (parentProtocol === 'blob:') { - throw new TypeError( - `Failed to resolve module specifier "${relUrl}". Invalid relative url or base scheme isn't hierarchical.` - ); - } - // Disabled, but these cases will give inconsistent results for deep backtracking - //if (parentUrl[parentProtocol.length] !== '/') - // throw new Error('Cannot resolve'); - // read pathname from parent URL - // pathname taken to be part after leading "/" - let pathname; - if (parentUrl[parentProtocol.length + 1] === '/') { - // resolving to a :// so we need to read out the auth and host - if (parentProtocol !== 'file:') { - pathname = parentUrl.slice(parentProtocol.length + 2); - pathname = pathname.slice(pathname.indexOf('/') + 1); - } else { - pathname = parentUrl.slice(8); - } - } else { - // resolving to :/ so pathname is the /... part - pathname = parentUrl.slice(parentProtocol.length + (parentUrl[parentProtocol.length] === '/')); - } - - if (relUrl[0] === '/') return parentUrl.slice(0, parentUrl.length - pathname.length - 1) + relUrl; - - // join together and split for removal of .. and . segments - // looping the string instead of anything fancy for perf reasons - // '../../../../../z' resolved to 'x/y' is just 'z' - const segmented = pathname.slice(0, pathname.lastIndexOf('/') + 1) + relUrl; - - const output = []; - let segmentIndex = -1; - for (let i = 0; i < segmented.length; i++) { - // busy reading a segment - only terminate on '/' - if (segmentIndex !== -1) { - if (segmented[i] === '/') { - output.push(segmented.slice(segmentIndex, i + 1)); - segmentIndex = -1; - } - continue; - } - // new segment - check if it is relative - else if (segmented[i] === '.') { - // ../ segment - if (segmented[i + 1] === '.' && (segmented[i + 2] === '/' || i + 2 === segmented.length)) { - output.pop(); - i += 2; - continue; - } - // ./ segment - else if (segmented[i + 1] === '/' || i + 1 === segmented.length) { - i += 1; - continue; - } - } - // it is the start of a new segment - while (segmented[i] === '/') i++; - segmentIndex = i; - } - // finish reading out the last segment - if (segmentIndex !== -1) output.push(segmented.slice(segmentIndex)); - return parentUrl.slice(0, parentUrl.length - pathname.length) + output.join(''); - } - }; - - const resolveAndComposeImportMap = (json, baseUrl, parentMap) => { - const outMap = { - imports: { ...parentMap.imports }, - scopes: { ...parentMap.scopes }, - integrity: { ...parentMap.integrity } - }; - - if (json.imports) resolveAndComposePackages(json.imports, outMap.imports, baseUrl, parentMap); - - if (json.scopes) - for (let s in json.scopes) { - const resolvedScope = resolveUrl(s, baseUrl); - resolveAndComposePackages( - json.scopes[s], - outMap.scopes[resolvedScope] || (outMap.scopes[resolvedScope] = {}), - baseUrl, - parentMap - ); - } - - if (json.integrity) resolveAndComposeIntegrity(json.integrity, outMap.integrity, baseUrl); - - return outMap; - }; - - const getMatch = (path, matchObj) => { - if (matchObj[path]) return path; - let sepIndex = path.length; - do { - const segment = path.slice(0, sepIndex + 1); - if (segment in matchObj) return segment; - } while ((sepIndex = path.lastIndexOf('/', sepIndex - 1)) !== -1); - }; - - const applyPackages = (id, packages) => { - const pkgName = getMatch(id, packages); - if (pkgName) { - const pkg = packages[pkgName]; - if (pkg === null) return; - return pkg + id.slice(pkgName.length); - } - }; - - const resolveImportMap = (importMap, resolvedOrPlain, parentUrl) => { - let scopeUrl = parentUrl && getMatch(parentUrl, importMap.scopes); - while (scopeUrl) { - const packageResolution = applyPackages(resolvedOrPlain, importMap.scopes[scopeUrl]); - if (packageResolution) return packageResolution; - scopeUrl = getMatch(scopeUrl.slice(0, scopeUrl.lastIndexOf('/')), importMap.scopes); - } - return applyPackages(resolvedOrPlain, importMap.imports) || (resolvedOrPlain.indexOf(':') !== -1 && resolvedOrPlain); - }; - - const resolveAndComposePackages = (packages, outPackages, baseUrl, parentMap) => { - for (let p in packages) { - const resolvedLhs = resolveIfNotPlainOrUrl(p, baseUrl) || p; - if ( - (!shimMode || !mapOverrides) && - outPackages[resolvedLhs] && - outPackages[resolvedLhs] !== packages[resolvedLhs] - ) { - console.warn( - `es-module-shims: Rejected map override "${resolvedLhs}" from ${outPackages[resolvedLhs]} to ${packages[resolvedLhs]}.` - ); - continue; - } - let target = packages[p]; - if (typeof target !== 'string') continue; - const mapped = resolveImportMap(parentMap, resolveIfNotPlainOrUrl(target, baseUrl) || target, baseUrl); - if (mapped) { - outPackages[resolvedLhs] = mapped; - continue; - } - console.warn(`es-module-shims: Mapping "${p}" -> "${packages[p]}" does not resolve`); - } - }; - - const resolveAndComposeIntegrity = (integrity, outIntegrity, baseUrl) => { - for (let p in integrity) { - const resolvedLhs = resolveIfNotPlainOrUrl(p, baseUrl) || p; - if ( - (!shimMode || !mapOverrides) && - outIntegrity[resolvedLhs] && - outIntegrity[resolvedLhs] !== integrity[resolvedLhs] - ) { - console.warn( - `es-module-shims: Rejected map integrity override "${resolvedLhs}" from ${outIntegrity[resolvedLhs]} to ${integrity[resolvedLhs]}.` - ); - } - outIntegrity[resolvedLhs] = integrity[p]; - } - }; - - let policy; - if (typeof window.trustedTypes !== 'undefined' || typeof window.TrustedTypes !== 'undefined') { - try { - policy = (window.trustedTypes || window.TrustedTypes).createPolicy('es-module-shims', { - createHTML: html => html, - createScript: script => script - }); - } catch {} - } - - function maybeTrustedInnerHTML(html) { - return policy ? policy.createHTML(html) : html; - } - - function maybeTrustedScript(script) { - return policy ? policy.createScript(script) : script; - } - - // support browsers without dynamic import support (eg Firefox 6x) - let supportsJsonType = false; - let supportsCssType = false; - - const supports = hasDocument && HTMLScriptElement.supports; - - let supportsImportMaps = supports && supports.name === 'supports' && supports('importmap'); - let supportsWasmInstancePhase = false; - let supportsWasmSourcePhase = false; - let supportsMultipleImportMaps = false; - - const wasmBytes = [0, 97, 115, 109, 1, 0, 0, 0]; - - let featureDetectionPromise = (async function () { - if (!hasDocument) - return Promise.all([ - import(createBlob(`import"${createBlob('{}', 'text/json')}"with{type:"json"}`)).then( - () => ( - (supportsJsonType = true), - import(createBlob(`import"${createBlob('', 'text/css')}"with{type:"css"}`)).then( - () => (supportsCssType = true), - noop - ) - ), - noop - ), - wasmInstancePhaseEnabled && - import(createBlob(`import"${createBlob(new Uint8Array(wasmBytes), 'application/wasm')}"`)).then( - () => (supportsWasmInstancePhase = true), - noop - ), - wasmSourcePhaseEnabled && - import(createBlob(`import source x from"${createBlob(new Uint8Array(wasmBytes), 'application/wasm')}"`)).then( - () => (supportsWasmSourcePhase = true), - noop - ) - ]); - - const msgTag = `s${version}`; - return new Promise(resolve => { - const iframe = document.createElement('iframe'); - iframe.style.display = 'none'; - iframe.setAttribute('nonce', nonce); - function cb({ data }) { - const isFeatureDetectionMessage = Array.isArray(data) && data[0] === msgTag; - if (!isFeatureDetectionMessage) return; - [ - , - supportsImportMaps, - supportsMultipleImportMaps, - supportsJsonType, - supportsCssType, - supportsWasmSourcePhase, - supportsWasmInstancePhase - ] = data; - resolve(); - document.head.removeChild(iframe); - window.removeEventListener('message', cb, false); - } - window.addEventListener('message', cb, false); - // Feature checking with careful avoidance of unnecessary work - all gated on initial import map supports check. CSS gates on JSON feature check, Wasm instance phase gates on wasm source phase check. - const importMapTest = `