diff --git a/.github/Agent/packages/CopilotPortal/assets/flowChartMermaid.js b/.github/Agent/packages/CopilotPortal/assets/flowChartMermaid.js index aa86d913..6ca69268 100644 --- a/.github/Agent/packages/CopilotPortal/assets/flowChartMermaid.js +++ b/.github/Agent/packages/CopilotPortal/assets/flowChartMermaid.js @@ -187,7 +187,7 @@ async function renderFlowChartMermaid(chart, container, onInspect) { // Track currently selected TaskNode/CondNode let currentSelectedGroup = null; - let currentSelectedOriginalStrokeWidth = null; + let currentSelectedOriginalStyle = null; let currentSelectedWorkId = null; // Map workId -> DOM group for status updates @@ -196,7 +196,8 @@ async function renderFlowChartMermaid(chart, container, onInspect) { // Add click handlers for TaskNode/CondNode elements for (const nodeId of taskNodeIds) { - const nodeEl = container.querySelector(`[id^="flowchart-${nodeId}-"]`); + // Mermaid 10: id="flowchart-N0-123"; Mermaid 11: id="mermaid-chart-flowchart-N0-0" + const nodeEl = container.querySelector(`[id*="flowchart-${nodeId}-"]`); if (!nodeEl) continue; const group = nodeEl.closest("g.node") || nodeEl; group.style.cursor = "pointer"; @@ -214,25 +215,26 @@ async function renderFlowChartMermaid(chart, container, onInspect) { if (currentSelectedGroup === group) { // Unselect - if (shapeEl && currentSelectedOriginalStrokeWidth !== null) { - shapeEl.style.strokeWidth = currentSelectedOriginalStrokeWidth; + if (shapeEl && currentSelectedOriginalStyle !== null) { + shapeEl.setAttribute("style", currentSelectedOriginalStyle); } currentSelectedGroup = null; - currentSelectedOriginalStrokeWidth = null; + currentSelectedOriginalStyle = null; currentSelectedWorkId = null; if (onInspect) onInspect(null); } else { // Unselect previous if (currentSelectedGroup) { const prevShape = currentSelectedGroup.querySelector("rect, polygon, circle, ellipse, path"); - if (prevShape && currentSelectedOriginalStrokeWidth !== null) { - prevShape.style.strokeWidth = currentSelectedOriginalStrokeWidth; + if (prevShape && currentSelectedOriginalStyle !== null) { + prevShape.setAttribute("style", currentSelectedOriginalStyle); } } - // Select new - currentSelectedOriginalStrokeWidth = shapeEl ? (shapeEl.style.strokeWidth || shapeEl.getAttribute("style")?.match(/stroke-width:\s*([^;]+)/)?.[1] || getComputedStyle(shapeEl).strokeWidth) : null; + // Select new — save full style string so !important flags are preserved on restore + currentSelectedOriginalStyle = shapeEl ? (shapeEl.getAttribute("style") ?? "") : null; if (shapeEl) { - shapeEl.style.strokeWidth = "5px"; + // Use setProperty with "important" to override Mermaid 11's !important inline styles + shapeEl.style.setProperty("stroke-width", "5px", "important"); } currentSelectedGroup = group; currentSelectedWorkId = wid; diff --git a/.github/Agent/packages/CopilotPortal/src/index.ts b/.github/Agent/packages/CopilotPortal/src/index.ts index b1a14fa4..b1a22bd4 100644 --- a/.github/Agent/packages/CopilotPortal/src/index.ts +++ b/.github/Agent/packages/CopilotPortal/src/index.ts @@ -99,15 +99,16 @@ async function installJobsEntry(entryValue: Entry): Promise { } const models = await helperGetModels(); const validModelIds = new Set(models.map(m => m.id)); + const modelListText = models.map(m => ` ${m.name} (id: ${m.id}, multiplier: ${m.multiplier})`).join("\n"); for (const [category, modelId] of Object.entries(entryValue.models)) { if (!validModelIds.has(modelId)) { - throw new Error(`entry.models["${category}"] refers to model "${modelId}" which is not a valid model.`); + throw new Error(`entry.models["${category}"] refers to model "${modelId}" which is not a valid model.\nAvailable models:\n${modelListText}`); } } for (let i = 0; i < entryValue.drivingSessionRetries.length; i++) { const modelId = entryValue.drivingSessionRetries[i].modelId; if (!validModelIds.has(modelId)) { - throw new Error(`entry.drivingSessionRetries[${i}].modelId refers to model "${modelId}" which is not a valid model.`); + throw new Error(`entry.drivingSessionRetries[${i}].modelId refers to model "${modelId}" which is not a valid model.\nAvailable models:\n${modelListText}`); } } installedEntry = entryValue; diff --git a/.github/Agent/packages/CopilotPortal/src/jobsData.ts b/.github/Agent/packages/CopilotPortal/src/jobsData.ts index 8111a5a2..b7948294 100644 --- a/.github/Agent/packages/CopilotPortal/src/jobsData.ts +++ b/.github/Agent/packages/CopilotPortal/src/jobsData.ts @@ -50,7 +50,7 @@ const entryInput: Entry = { models: { driving: "gpt-5-mini", planning: "gpt-5.2", - coding: "gpt-5.2-codex", + coding: "gpt-5.4", reviewers1: "gpt-5.3-codex", reviewers2: "claude-opus-4.6", reviewers3: "claude-sonnet-4.6" @@ -58,7 +58,7 @@ const entryInput: Entry = { drivingSessionRetries: [ { modelId: "gpt-5-mini", retries: 5 }, { modelId: "gpt-4.1", retries: 5 }, - { modelId: "gpt-5.1-codex-mini", retries: 3 }, + { modelId: "gpt-5.4-mini", retries: 3 }, { modelId: "claude-haiku-4.5", retries: 3 }, ], promptVariables: { diff --git a/.github/Agent/packages/CopilotPortal/test/startServer.mjs b/.github/Agent/packages/CopilotPortal/test/startServer.mjs index ea81c1da..9459fa92 100644 --- a/.github/Agent/packages/CopilotPortal/test/startServer.mjs +++ b/.github/Agent/packages/CopilotPortal/test/startServer.mjs @@ -6,6 +6,25 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const serverScript = path.resolve(__dirname, "..", "dist", "index.js"); const windowsHidePatch = path.resolve(__dirname, "windowsHide.cjs"); +// Stop any previously running server so the new one starts with clean state. +// Without this, a leftover server with an installed entry would cause false failures. +try { + await fetch("http://localhost:8888/api/stop"); +} catch { + // No server running, that's fine +} +// Wait until the old server is gone (port free) +for (let i = 0; i < 20; i++) { + try { + await fetch("http://localhost:8888/api/test"); + // Still responding — wait a bit more + await new Promise((r) => setTimeout(r, 200)); + } catch { + // Connection refused: old server is gone + break; + } +} + // Spawn server as detached process in test mode so it runs independently of this script. // On Windows, the Copilot SDK internally spawns node.exe to run its bundled CLI server. // Without windowsHide, each spawn creates a visible console window that steals keyboard focus. diff --git a/.github/Agent/prompts/snapshot/CopilotPortal/API.md b/.github/Agent/prompts/snapshot/CopilotPortal/API.md index b8ee54f4..217c8917 100644 --- a/.github/Agent/prompts/snapshot/CopilotPortal/API.md +++ b/.github/Agent/prompts/snapshot/CopilotPortal/API.md @@ -47,6 +47,7 @@ Prints the following URL for shortcut: `async installJobsEntry(entry: Entry): Promise;` - Verify if all `entry.model[name]` is a valid model with `helperGetModels`. - Verify if all `entry.drivingSessionRetries[index].modelId` is a valid model with `helperGetModels`. +- If a model is not valid, in the error messages a list of all model names, ids and premium multipliers should be listed as well. - Use the entry. It could be `entry` from `jobsData.ts` or whatever. - This function can only be called when no session is running, otherwise throws. diff --git a/.github/Agent/prompts/spec/CopilotPortal/API.md b/.github/Agent/prompts/spec/CopilotPortal/API.md index b8ee54f4..217c8917 100644 --- a/.github/Agent/prompts/spec/CopilotPortal/API.md +++ b/.github/Agent/prompts/spec/CopilotPortal/API.md @@ -47,6 +47,7 @@ Prints the following URL for shortcut: `async installJobsEntry(entry: Entry): Promise;` - Verify if all `entry.model[name]` is a valid model with `helperGetModels`. - Verify if all `entry.drivingSessionRetries[index].modelId` is a valid model with `helperGetModels`. +- If a model is not valid, in the error messages a list of all model names, ids and premium multipliers should be listed as well. - Use the entry. It could be `entry` from `jobsData.ts` or whatever. - This function can only be called when no session is running, otherwise throws.