diff --git a/.gitattributes b/.gitattributes index 8bec391cf43..1418fb12d94 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,8 +9,6 @@ pkg/workflow/js/*.cjs linguist-generated=true pkg/workflow/sh/*.sh linguist-generated=true actions/*/index.js linguist-generated=true actions/setup-cli/install.sh linguist-generated=true -.github/extensions/agentic-workflows-dashboard/web/app.js linguist-generated=true merge=ours -.github/extensions/agentic-workflows-dashboard/*.js linguist-generated=true merge=ours specs/artifacts.md linguist-generated=true merge=ours docs/adr/*.md merge=theirs # Use bd merge for beads JSONL files diff --git a/.github/extensions/agentic-workflows-dashboard/.gitignore b/.github/extensions/agentic-workflows-dashboard/.gitignore deleted file mode 100644 index a3925b730fe..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -dashboard-cli.js -dashboard-config.js -dashboard-data.js -dashboard-logs.js -models.js -pagination.js -usage-forecast.js diff --git a/.github/extensions/agentic-workflows-dashboard/.impeccable/config.json b/.github/extensions/agentic-workflows-dashboard/.impeccable/config.json deleted file mode 100644 index e5d5bf3641b..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/.impeccable/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "detector": { - "designSystem": { - "enabled": true - } - } -} diff --git a/.github/extensions/agentic-workflows-dashboard/AGENTS.md b/.github/extensions/agentic-workflows-dashboard/AGENTS.md deleted file mode 100644 index 5fc5e309d00..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/AGENTS.md +++ /dev/null @@ -1,181 +0,0 @@ -# Agentic Workflows Dashboard — Agent Guide - -## Overview - -This is a GitHub Copilot canvas extension that renders a live dashboard for agentic workflow definitions, runs, usage, and experiments. It is a loopback Node.js extension: the extension process spawns a small HTTP server for each canvas panel, and the iframe served by that server drives the UI. - ---- - -## Directory Structure - -``` -.github/extensions/agentic-workflows-dashboard/ -│ -│ ── TypeScript sources (canonical) ────────────────────────── -├── src/ -│ ├── dashboard-cli.ts CLI runner (spawn, binary detection, getStatus) -│ ├── dashboard-config.ts Shared constants (cache TTL, window presets, etc.) -│ ├── dashboard-data.ts Data-access layer (getDefinitions, getRuns, getUsage, …) -│ ├── dashboard-logs.ts gh aw logs argument/option helpers -│ ├── usage-forecast.ts Usage summary and forecast aggregation -│ ├── models.ts Shared TypeScript types -│ ├── pagination.ts Pagination utility -│ ├── app.ts Web frontend entry point (bundled → web/app.js) -│ └── alpinejs.d.ts Alpine.js type declarations -│ -│ ── Compiled JS outputs (DO NOT edit manually) ─────────────── -├── dashboard-cli.js compiled from src/dashboard-cli.ts -├── dashboard-config.js compiled from src/dashboard-config.ts -├── dashboard-data.js compiled from src/dashboard-data.ts -├── dashboard-logs.js compiled from src/dashboard-logs.ts -├── usage-forecast.js compiled from src/usage-forecast.ts -│ -│ ── Hand-authored files ────────────────────────────────────── -├── extension.mjs Extension entry point (not compiled — edit directly) -│ -│ ── Web frontend (auto-generated) ──────────────────────────── -├── web/ -│ ├── app.js bundled by esbuild from src/app.ts -│ ├── models.js (empty re-export, unused at runtime) -│ ├── index.html UI template -│ └── styles.css Stylesheet -│ -│ ── Tests ──────────────────────────────────────────────────── -├── test/ -│ ├── dashboard-cli.test.ts -│ ├── dashboard-data.test.ts -│ ├── dashboard-logs.test.ts -│ ├── pagination.test.ts -│ └── usage-forecast.test.ts -│ -├── package.json -├── tsconfig.json Used for type-checking (--noEmit); outDir=./web -└── vitest.config.ts -``` - ---- - -## TypeScript / JavaScript Relationship - -| File | Source | How to regenerate | -|---|---|---| -| `dashboard-cli.js` | `src/dashboard-cli.ts` | `npm run build:ts` | -| `dashboard-config.js` | `src/dashboard-config.ts` | `npm run build:ts` | -| `dashboard-data.js` | `src/dashboard-data.ts` | `npm run build:ts` | -| `dashboard-logs.js` | `src/dashboard-logs.ts` | `npm run build:ts` | -| `usage-forecast.js` | `src/usage-forecast.ts` | `npm run build:ts` | -| `web/app.js` | `src/app.ts` | `npm run build:web` | -| `extension.mjs` | — hand-authored — | edit directly | - -**Rule:** Always edit `src/*.ts` files, then run `npm run build:ts` to regenerate the root-level `.js` files. Never edit the root-level `.js` files by hand. - -> Note: `tsconfig.json` uses `outDir: "./web"` and `--noEmit` for type-checking only. -> `build:ts` uses a separate `tsconfig.emit.json` with `outDir: "."` and `rootDir: "src"`. - ---- - -## Build - -```sh -# Install dependencies (first time or after package.json changes) -npm ci - -# Type-check all TypeScript sources -npm run typecheck - -# Compile src/*.ts → root *.js (after any change to src/ backend files) -npm run build:ts - -# Bundle src/app.ts → web/app.js (after any change to the web frontend) -npm run build:web - -# Full build (typecheck + build:web) -npm run build -``` - ---- - -## Format - -```sh -npm run fmt # format all files (TS, HTML, CSS, MJS, JSON) -npm run fmt:ts # TypeScript only (src/**/*.ts, test/**/*.ts) -npm run fmt:js # extension.mjs only -npm run fmt:html # web/index.html -npm run fmt:css # web/styles.css -npm run fmt:json # copilot-extension.json, package.json -``` - ---- - -## Lint - -```sh -npm run lint # typecheck + test (full lint gate) -npm run typecheck # type-check only (no emit) -``` - ---- - -## Test - -```sh -npm test # run all unit tests via vitest -``` - -Tests live in `test/` and are written in TypeScript. Vitest runs them directly (no separate compile step needed). - ---- - -## Makefile targets (from repo root) - -```sh -make test-canvas-extension # npm ci + build + test -make fmt-canvas-extension # npm ci + fmt -make lint-canvas-extension # npm ci + lint -``` - ---- - -## Debugging - -All runtime log output goes to the extension's log file. The log path is printed by the Copilot CLI when the extension starts, and can be retrieved with: - -```sh -# From the Copilot CLI agent: -extensions_manage inspect --name agentic-workflows-dashboard -``` - -### Logging strategy - -All `console.error` calls use a **`[filename]` category prefix** so log lines are greppable by source file: - -| Prefix | File | -|---|---| -| `[dashboard-cli]` | `src/dashboard-cli.ts` | -| `[dashboard-data]` | `src/dashboard-data.ts` | -| `[extension]` | `extension.mjs` | - -**What is logged:** -- `[dashboard-cli]`: every `spawn` call (file, args, cwd), close events when stderr is non-empty or exit code ≠ 0, spawn errors, binary detection result, `getStatus` outcome -- `[dashboard-data]`: cache hits/misses for every data function, CLI fetch start/finish with counts, JSON parse failures with output snippet, per-batch log fetch progress, errors from every async function -- `[extension]`: startup (`__dirname`, `workspacePath`), HTTP server port, per-API-request timing, request errors, canvas open/close lifecycle - -To tail the log live: -```powershell -Get-Content -Wait -Tail 50 "" -``` - -To filter by category: -```powershell -Get-Content "" | Select-String "[dashboard-cli]" -SimpleMatch -``` - ---- - -## Known Pitfalls - -- **`session.workspacePath` and `process.cwd()` are NOT the git repo root.** `session.workspacePath` points to the session-state folder (`~/.copilot/session-state/`). `process.cwd()` resolves to the Copilot runtime directory (`~/.copilot`). The git repo root must be derived from `__dirname`: for a project-scoped extension at `.github/extensions//`, use `resolve(__dirname, "../../..")`. -- **Logs are stored in a shared user-level cache directory** (`~/.cache/gh-aw/logs///`), not inside the workspace. This ensures that when a coding agent starts a fresh session with a new workspace checkout, previously downloaded run artifacts are reused without re-downloading. The shared dir is resolved at startup from `git remote get-url origin`; it falls back to `/.github/aw/logs` when the remote cannot be determined. -- **`detached: true` on Windows** causes spawned processes to allocate a new console, which can redirect their stdout to that console instead of the pipe. Use `windowsHide: true` without `detached: true` for subprocess spawning. -- **Root `.js` files in git** — they are committed alongside the TS sources. After editing `src/`, run `npm run build:ts` and commit both. diff --git a/.github/extensions/agentic-workflows-dashboard/PRODUCT.md b/.github/extensions/agentic-workflows-dashboard/PRODUCT.md deleted file mode 100644 index e3353a04fdd..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/PRODUCT.md +++ /dev/null @@ -1,24 +0,0 @@ -# Agentic Workflows Dashboard - -## Surface - -Product UI (GitHub Copilot Canvas dashboard). - -## Audience - -Repository maintainers operating `gh aw` workflows in `github/gh-aw`. - -## Product intent - -Provide a GitHub.com-like control surface for: - -- exploring workflow definitions, -- monitoring workflow runs, -- reviewing safe run summaries, -- dispatching and auditing workflows from one place. - -## Design lane - -- Match GitHub visual language (Primer components, muted hierarchy, compact controls). -- Keep information dense and scannable. -- Favor predictable tabs and explicit command affordances over decorative UI. diff --git a/.github/extensions/agentic-workflows-dashboard/app.js b/.github/extensions/agentic-workflows-dashboard/app.js deleted file mode 100644 index d55f0766476..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/app.js +++ /dev/null @@ -1,783 +0,0 @@ -// src/dashboard-cli.ts -import { spawn } from "node:child_process"; -import { constants as fsConstants } from "node:fs"; -import { access } from "node:fs/promises"; -import { join } from "node:path"; -var INSTALL_COMMAND = "gh extension install github/gh-aw"; -var GH_INSTALL_URL = "https://cli.github.com"; -var LOG = "[dashboard-cli]"; -function combineOutput(stdout, stderr) { - return [stdout, stderr].filter(Boolean).join("\n").trim(); -} -function spawnExecFile(file, args, options, callback) { - const { env, cwd, maxBuffer = 10 * 1024 * 1024 } = options ?? {}; - const spawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: true }; - console.error(`${LOG} spawn file=${file} args=${JSON.stringify(args)} cwd=${cwd}`); - const proc = spawn(file, args, spawnOptions); - const stdoutChunks = []; - const stderrChunks = []; - let stdoutLen = 0; - let stderrLen = 0; - let overflowed = false; - proc.stdout?.on("data", (chunk) => { - stdoutLen += chunk.length; - if (stdoutLen > maxBuffer) { - overflowed = true; - return; - } - stdoutChunks.push(chunk); - }); - proc.stderr?.on("data", (chunk) => { - stderrLen += chunk.length; - if (stderrLen > maxBuffer) { - overflowed = true; - return; - } - stderrChunks.push(chunk); - }); - proc.on("error", (err) => { - console.error(`${LOG} spawn error file=${file} args=${JSON.stringify(args)}: ${err.message}`); - callback(err, "", ""); - }); - proc.on("close", (code) => { - const stdout = Buffer.concat(stdoutChunks).toString("utf8"); - const stderr = Buffer.concat(stderrChunks).toString("utf8"); - if (code !== 0 || stderr) { - console.error(`${LOG} spawn close file=${file} code=${code} stdout=${stdout.length}B stderr=${stderr.length}B${stderr ? ` stderr: ${stderr.slice(0, 300)}` : ""}`); - } - if (overflowed) { - const err = new Error("stdout/stderr maxBuffer exceeded"); - err.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"; - callback(err, stdout, stderr); - } else if (code !== 0) { - const err = new Error(`Command failed with exit code ${code}`); - err.code = code ?? 1; - callback(err, stdout, stderr); - } else { - callback(null, stdout, stderr); - } - }); -} -function execp(bin, args, cwd, { combineIO = false, execFileFn = spawnExecFile, env = process.env } = {}) { - return new Promise((resolve, reject) => { - execFileFn( - bin, - args, - { - cwd, - env: { ...env, CI: "1", NO_COLOR: "1", GH_NO_UPDATE_NOTIFIER: "1" }, - maxBuffer: 10 * 1024 * 1024 - }, - (err, stdout, stderr) => { - const output = combineOutput(stdout ?? "", stderr ?? ""); - if (err) { - reject(Object.assign(err, { stderr: stderr ?? "", stdout: stdout ?? "", output })); - return; - } - resolve(combineIO ? output : stdout); - } - ); - }); -} -function parseVersionFromOutput(output) { - const trimmed = String(output ?? "").trim(); - if (!trimmed) return ""; - const match = trimmed.match(/gh(?:-aw| aw) version ([^\r\n]+)/i); - return match?.[1]?.trim() ?? ""; -} -function isMissingGh(error) { - const e = error; - return e?.code === "ENOENT" && e?.syscall === "spawn" && e?.path === "gh"; -} -function isMissingGhAwExtension(error) { - const e = error; - const output = String(e?.output ?? e?.stderr ?? e?.message ?? ""); - return /extension not found:\s*aw/i.test(output) || /unknown command ["']aw["'] for ["']gh["']/i.test(output); -} -async function findDevBinary(cwd, accessFn = access, platform = process.platform) { - const devBin = join(cwd, platform === "win32" ? "gh-aw.exe" : "gh-aw"); - try { - await accessFn(devBin, fsConstants.X_OK); - console.error(`${LOG} findDevBinary found: ${devBin}`); - return devBin; - } catch { - console.error(`${LOG} findDevBinary not found at ${devBin}, falling back to gh extension`); - return null; - } -} -function createGhAwRunner({ getWorkspacePath, accessFn = access, execFileFn = spawnExecFile, platform = process.platform, env = process.env, resolveBin }) { - const binCache = /* @__PURE__ */ new Map(); - const _resolveBin = resolveBin ?? (() => { - const cwd = getWorkspacePath(); - if (!binCache.has(cwd)) { - binCache.set(cwd, findDevBinary(cwd, accessFn, platform)); - } - return binCache.get(cwd); - }); - function runExec(bin, args, cwd, options) { - return execp(bin, args, cwd, { ...options, execFileFn, env }); - } - return async function runGhAw(args) { - const cwd = getWorkspacePath(); - const devBin = await _resolveBin(); - if (devBin) { - console.error(`${LOG} runGhAw using dev-binary: ${devBin} args=${JSON.stringify(args)} cwd=${cwd}`); - return runExec(devBin, args, cwd); - } - console.error(`${LOG} runGhAw using gh extension args=${JSON.stringify(args)} cwd=${cwd}`); - return runExec("gh", ["aw", ...args], cwd); - }; -} -function createGhAwRunnerWithStatus(options) { - const binCache = /* @__PURE__ */ new Map(); - const resolveBin = () => { - const cwd = options.getWorkspacePath(); - if (!binCache.has(cwd)) { - binCache.set(cwd, findDevBinary(cwd, options.accessFn ?? access, options.platform ?? process.platform)); - } - return binCache.get(cwd); - }; - const runGhAw = createGhAwRunner({ ...options, resolveBin }); - const getStatus = async () => { - const cwd = options.getWorkspacePath(); - const devBin = await resolveBin(); - if (devBin) { - const output = await execp(devBin, ["version"], cwd, { - combineIO: true, - execFileFn: options.execFileFn ?? spawnExecFile, - env: options.env ?? process.env - }); - const status = { - available: true, - source: "dev-binary", - version: parseVersionFromOutput(output) || "unknown", - command: `${devBin} version`, - installCommand: INSTALL_COMMAND - }; - console.error(`${LOG} getStatus: available=${status.available} source=${status.source} version=${status.version} cwd=${cwd}`); - return status; - } - try { - const output = await execp("gh", ["aw", "version"], cwd, { - combineIO: true, - execFileFn: options.execFileFn ?? spawnExecFile, - env: options.env ?? process.env - }); - const status = { - available: true, - source: "gh-extension", - version: parseVersionFromOutput(output) || "unknown", - command: "gh aw version", - installCommand: INSTALL_COMMAND - }; - console.error(`${LOG} getStatus: available=${status.available} source=${status.source} version=${status.version} cwd=${cwd}`); - return status; - } catch (error) { - if (isMissingGh(error)) { - console.error(`${LOG} getStatus error: gh not found in PATH cwd=${cwd}`); - return { - available: false, - source: "gh-not-found", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - installUrl: GH_INSTALL_URL, - message: "Install the GitHub CLI to use this dashboard." - }; - } - if (isMissingGhAwExtension(error)) { - console.error(`${LOG} getStatus error: gh aw extension not installed cwd=${cwd}`); - return { - available: false, - source: "missing", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - message: "gh aw is not installed. Install the GitHub CLI extension to use the dashboard outside a local dev build." - }; - } - const e = error; - const message = String(e?.output ?? e?.stderr ?? e?.message ?? "Failed to detect gh aw."); - console.error(`${LOG} getStatus error: ${message} cwd=${cwd}`); - return { - available: false, - source: "error", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - message - }; - } - }; - runGhAw.getStatus = getStatus; - return runGhAw; -} - -// src/dashboard-config.ts -var CACHE_TTL_MS = 6e4; -var DEFAULT_LOG_TIMEOUT_MINUTES = 1; -var DEFAULT_REPORT_WINDOW_ID = "7d"; -var DEFAULT_RUN_COUNT = 100; -var MAX_LOG_CONTINUATIONS = 6; -var REPORT_WINDOWS = { - "3d": { id: "3d", label: "3 days", startDate: "-3d", days: 3 }, - "7d": { id: "7d", label: "7 days", startDate: "-1w", days: 7 }, - "1mo": { id: "1mo", label: "1 month", startDate: "-1mo", days: 30 } -}; -function getReportWindow(windowId) { - if (windowId && windowId in REPORT_WINDOWS) { - return REPORT_WINDOWS[windowId]; - } - return REPORT_WINDOWS[DEFAULT_REPORT_WINDOW_ID]; -} - -// src/dashboard-logs.ts -function parsePositiveInt(value, fallback) { - const numeric = Number.parseInt(String(value ?? fallback), 10); - return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback; -} -function readFlagValue(args, index, arg) { - const equalsIndex = arg.indexOf("="); - if (equalsIndex >= 0) { - return { value: arg.slice(equalsIndex + 1), nextIndex: index }; - } - return { value: args[index + 1] ?? "", nextIndex: index + 1 }; -} -function normalizeLogsOptions(options = {}) { - const windowId = typeof options.window === "string" ? options.window : options.window?.id; - const window = getReportWindow(windowId); - const artifacts = Array.isArray(options.artifacts) && options.artifacts.length > 0 ? options.artifacts : ["usage"]; - return { - window, - count: parsePositiveInt(options.count, DEFAULT_RUN_COUNT), - timeout: parsePositiveInt(options.timeout, DEFAULT_LOG_TIMEOUT_MINUTES), - startDate: typeof options.startDate === "string" && options.startDate.trim() ? options.startDate.trim() : window.startDate, - endDate: typeof options.endDate === "string" && options.endDate.trim() ? options.endDate.trim() : "", - beforeRunID: Number.isFinite(Number(options.beforeRunID)) && Number(options.beforeRunID) > 0 ? Number(options.beforeRunID) : 0, - afterRunID: Number.isFinite(Number(options.afterRunID)) && Number(options.afterRunID) > 0 ? Number(options.afterRunID) : 0, - workflowName: typeof options.workflowName === "string" ? options.workflowName.trim() : "", - engine: typeof options.engine === "string" ? options.engine.trim() : "", - branch: typeof options.branch === "string" ? options.branch.trim() : "", - artifacts - }; -} -function buildLogsArgs(options) { - const args = ["logs", "--json", "-c", String(options.count), "--timeout", String(options.timeout)]; - if (options.workflowName) args.push(options.workflowName); - if (options.startDate) args.push("--start-date", options.startDate); - if (options.endDate) args.push("--end-date", options.endDate); - if (options.engine) args.push("--engine", options.engine); - if (options.branch) args.push("--ref", options.branch); - if (options.beforeRunID > 0) args.push("--before-run-id", String(options.beforeRunID)); - if (options.afterRunID > 0) args.push("--after-run-id", String(options.afterRunID)); - if (options.artifacts.length > 0) args.push("--artifacts", options.artifacts.join(",")); - return args; -} -function continuationToLogsOptions(continuation, fallback) { - if (!continuation) return null; - return normalizeLogsOptions({ - window: fallback.window.id, - workflowName: continuation.workflow_name || fallback.workflowName, - count: continuation.count || fallback.count, - startDate: continuation.start_date || fallback.startDate, - endDate: continuation.end_date || fallback.endDate, - engine: continuation.engine || fallback.engine, - branch: continuation.branch || fallback.branch, - afterRunID: continuation.after_run_id || fallback.afterRunID, - beforeRunID: continuation.before_run_id || fallback.beforeRunID, - timeout: continuation.timeout || fallback.timeout, - artifacts: fallback.artifacts - }); -} -function mergeRuns(existingRuns, nextRuns) { - const merged = new Map(existingRuns.map((run) => [run.run_id, run])); - for (const run of nextRuns) { - if (run?.run_id != null) { - merged.set(run.run_id, run); - } - } - return Array.from(merged.values()).sort((a, b) => Number(b.run_id ?? 0) - Number(a.run_id ?? 0)); -} -function parseGhAwArgs(raw) { - const match = raw.trim().match(/^(?:gh\s+aw\s+)(.+)$/); - return match?.[1] ? match[1].trim().split(/\s+/) : null; -} -function hasFlag(args, longFlag, shortFlag = "") { - return args.some((arg) => { - if (arg.startsWith(`${longFlag}=`)) return true; - if (shortFlag && arg.startsWith(`${shortFlag}=`)) return true; - return arg === longFlag || shortFlag !== "" && arg === shortFlag; - }); -} -function logsCommandUsesJSON(args) { - return hasFlag(args, "--json", "-j"); -} -function normalizeLogsCommandArgs(args, windowId, timeoutMinutes) { - const nextArgs = [...args]; - if (!hasFlag(nextArgs, "--start-date") && !hasFlag(nextArgs, "--end-date") && !hasFlag(nextArgs, "--after-run-id") && !hasFlag(nextArgs, "--before-run-id")) { - nextArgs.push("--start-date", getReportWindow(windowId).startDate); - } - if (!hasFlag(nextArgs, "--timeout")) { - nextArgs.push("--timeout", String(timeoutMinutes)); - } - if (!hasFlag(nextArgs, "--artifacts")) { - nextArgs.push("--artifacts", "usage"); - } - return nextArgs; -} -function logsArgsToOptions(args, fallback = {}) { - const options = { - window: typeof fallback.window === "string" ? fallback.window : fallback.window?.id, - count: fallback.count, - timeout: fallback.timeout, - startDate: fallback.startDate, - endDate: fallback.endDate, - beforeRunID: fallback.beforeRunID, - afterRunID: fallback.afterRunID, - workflowName: fallback.workflowName, - engine: fallback.engine, - branch: fallback.branch, - artifacts: fallback.artifacts - }; - for (let index = 1; index < args.length; index += 1) { - const arg = args[index] ?? ""; - if (!arg.startsWith("-")) { - if (!options.workflowName) { - options.workflowName = arg; - } - continue; - } - if (arg === "--json" || arg === "-j") { - continue; - } - if (arg === "-c" || arg.startsWith("-c=") || arg === "--count" || arg.startsWith("--count=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.count = value; - index = nextIndex; - continue; - } - if (arg === "--timeout" || arg.startsWith("--timeout=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.timeout = value; - index = nextIndex; - continue; - } - if (arg === "--start-date" || arg.startsWith("--start-date=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.startDate = value; - index = nextIndex; - continue; - } - if (arg === "--end-date" || arg.startsWith("--end-date=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.endDate = value; - index = nextIndex; - continue; - } - if (arg === "--before-run-id" || arg.startsWith("--before-run-id=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.beforeRunID = value; - index = nextIndex; - continue; - } - if (arg === "--after-run-id" || arg.startsWith("--after-run-id=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.afterRunID = value; - index = nextIndex; - continue; - } - if (arg === "--engine" || arg.startsWith("--engine=") || arg === "-e" || arg.startsWith("-e=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.engine = value; - index = nextIndex; - continue; - } - if (arg === "--ref" || arg.startsWith("--ref=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.branch = value; - index = nextIndex; - continue; - } - if (arg === "--artifacts" || arg.startsWith("--artifacts=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.artifacts = value.split(",").map((item) => item.trim()).filter(Boolean); - index = nextIndex; - } - } - return normalizeLogsOptions(options); -} - -// src/usage-forecast.ts -import { basename } from "node:path"; -function toNumber(value) { - const numeric = Number(value ?? 0); - return Number.isFinite(numeric) ? numeric : 0; -} -function normalizeWorkflowID(value) { - const raw = String(value ?? "").trim(); - if (!raw) return ""; - let name = basename(raw); - const lowerName = name.toLowerCase(); - for (const suffix of [".lock.yml", ".yml", ".yaml", ".md"]) { - if (lowerName.endsWith(suffix)) { - name = name.slice(0, -suffix.length); - break; - } - } - return name.trim(); -} -function forecastDaysForWindow(window) { - return window?.id === "1mo" ? 30 : 7; -} -function getForecastMonthlyAIC(forecast) { - if (!forecast || typeof forecast !== "object") return 0; - const monteCarloP50 = toNumber(forecast.monthly_monte_carlo?.p50_projected_aic); - if (monteCarloP50 > 0) return monteCarloP50; - return toNumber(forecast.monthly_projected_aic); -} -function applyForecastToUsageSummary(items, forecastWorkflows = []) { - const forecastEntries = forecastWorkflows.map((forecast) => [normalizeWorkflowID(forecast?.workflow_id || forecast?.workflow_path), getForecastMonthlyAIC(forecast)]).filter(([workflowID]) => Boolean(workflowID)); - const forecastByWorkflow = new Map(forecastEntries); - return items.map((item) => ({ - ...item, - monthly_forecast_aic: forecastByWorkflow.get(item.workflow_id) ?? 0 - })); -} -function buildUsageSummary(runs, window, forecastWorkflows = []) { - const usageByWorkflow = /* @__PURE__ */ new Map(); - const effectiveDays = Number(window?.days ?? 0); - if (!Number.isFinite(effectiveDays) || effectiveDays <= 0) { - throw new Error(`report window '${window?.id ?? "unknown"}' is missing a valid positive day count.`); - } - for (const run of runs) { - const workflowPath = typeof run?.workflow_path === "string" ? run.workflow_path.trim() : ""; - const workflowID = normalizeWorkflowID(workflowPath || run?.workflow_name); - if (!workflowID) continue; - const workflowName = String(run?.workflow_name ?? workflowID).trim() || workflowID; - const aic = toNumber(run?.aic); - const entry = usageByWorkflow.get(workflowID) ?? { - workflow_id: workflowID, - workflow_name: workflowName, - workflow_path: workflowPath, - run_count: 0, - total_aic: 0, - cost_per_run: 0, - daily_aic: 0, - monthly_forecast_aic: 0, - last_run_at: "" - }; - entry.run_count += 1; - entry.total_aic += aic; - if (!entry.workflow_path && workflowPath) { - entry.workflow_path = workflowPath; - } - if (!entry.workflow_name && workflowName) { - entry.workflow_name = workflowName; - } - const createdAt = typeof run?.created_at === "string" ? run.created_at : ""; - if (createdAt && (!entry.last_run_at || createdAt > entry.last_run_at)) { - entry.last_run_at = createdAt; - } - usageByWorkflow.set(workflowID, entry); - } - const items = Array.from(usageByWorkflow.values()).map((entry) => { - const costPerRun = entry.run_count > 0 ? entry.total_aic / entry.run_count : 0; - const dailyAIC = entry.total_aic / effectiveDays; - return { - ...entry, - cost_per_run: costPerRun, - daily_aic: dailyAIC, - monthly_forecast_aic: 0 - }; - }).sort((a, b) => { - const dailyDelta = b.daily_aic - a.daily_aic; - if (dailyDelta !== 0) return dailyDelta; - return b.cost_per_run - a.cost_per_run; - }); - return applyForecastToUsageSummary(items, forecastWorkflows); -} - -// src/dashboard-data.ts -var LOG2 = "[dashboard-data]"; -function asError(value) { - if (value instanceof Error) { - return value; - } - return new Error(String(value)); -} -function parseJsonOutput(raw, context) { - const trimmed = (raw ?? "").trim(); - if (!trimmed) { - console.error(`${LOG2} parseJsonOutput: no output for context="${context}"`); - throw new Error(`${context}: command produced no output`); - } - try { - return JSON.parse(trimmed); - } catch { - const jsonStart = trimmed.search(/[{[]/); - if (jsonStart > 0) { - try { - return JSON.parse(trimmed.slice(jsonStart)); - } catch { - } - } - const snippet = trimmed.replace(/\s+/g, " ").slice(0, 200); - console.error(`${LOG2} parseJsonOutput: JSON parse failed context="${context}" snippet=${snippet}`); - throw new Error(`${context}: failed to parse JSON (output: ${snippet})`); - } -} -function createDashboardDataAccess({ runGhAw, cacheTTL = CACHE_TTL_MS, logsOutputDir }) { - const cache = /* @__PURE__ */ new Map(); - function getCached(key) { - const entry = cache.get(key); - return entry && Date.now() < entry.expiresAt ? entry.data : null; - } - function setCached(key, data) { - cache.set(key, { data, expiresAt: Date.now() + cacheTTL }); - } - async function getDefinitions() { - const hit = getCached("definitions"); - if (hit) { - console.error(`${LOG2} getDefinitions: cache hit count=${hit.length}`); - return hit; - } - console.error(`${LOG2} getDefinitions: fetching from CLI`); - try { - const raw = await runGhAw(["status", "--json"]); - const parsed = parseJsonOutput(raw, "gh aw status --json"); - const data = Array.isArray(parsed) ? parsed : []; - setCached("definitions", data); - console.error(`${LOG2} getDefinitions: fetched count=${data.length}`); - return data; - } catch (err) { - console.error(`${LOG2} getDefinitions error: ${asError(err).message}`); - throw err; - } - } - async function getExperiments() { - const hit = getCached("experiments"); - if (hit) { - console.error(`${LOG2} getExperiments: cache hit count=${hit.length}`); - return hit; - } - console.error(`${LOG2} getExperiments: fetching from CLI`); - try { - const raw = await runGhAw(["experiments", "list", "--json"]); - const parsed = parseJsonOutput(raw, "gh aw experiments list --json"); - const experiments = Array.isArray(parsed) ? parsed : []; - setCached("experiments", experiments); - console.error(`${LOG2} getExperiments: fetched count=${experiments.length}`); - return experiments; - } catch (err) { - console.error(`${LOG2} getExperiments error: ${asError(err).message}`); - throw err; - } - } - async function fetchLogsBatches(initialOptions, initialArgs = null) { - let current = initialOptions; - let logsFetches = 0; - let runs = []; - let continuation = null; - let summary = null; - let firstBatch = null; - while (current && logsFetches < MAX_LOG_CONTINUATIONS) { - let batchArgs = logsFetches === 0 && initialArgs ? initialArgs : buildLogsArgs(current); - if (logsOutputDir && !hasFlag(batchArgs, "--output", "-o")) { - batchArgs = [...batchArgs, "--output", logsOutputDir]; - } - console.error(`${LOG2} fetchLogsBatches: batch=${logsFetches + 1} args=${JSON.stringify(batchArgs)}`); - const raw = await runGhAw(batchArgs); - let data; - try { - data = parseJsonOutput(raw, `logs batch ${logsFetches + 1}`); - } catch (error) { - console.error(`${LOG2} fetchLogsBatches: parse error on batch ${logsFetches + 1}: ${asError(error).message}`); - throw asError(error); - } - if (!firstBatch) { - firstBatch = data; - } - const newRuns = Array.isArray(data.runs) ? data.runs : []; - runs = mergeRuns(runs, newRuns); - continuation = data.continuation ?? null; - summary = data.summary ?? summary; - logsFetches += 1; - console.error(`${LOG2} fetchLogsBatches: batch=${logsFetches} newRuns=${newRuns.length} totalRuns=${runs.length} hasContinuation=${Boolean(continuation)}`); - if (!continuation) { - break; - } - current = continuationToLogsOptions(continuation, current); - } - return { - firstBatch, - runs, - summary, - logsFetches, - partial: Boolean(continuation), - continuation - }; - } - async function getLogsData(options = {}) { - const normalized = normalizeLogsOptions(options); - const key = `logs:${JSON.stringify({ - window: normalized.window.id, - count: normalized.count, - timeout: normalized.timeout, - startDate: normalized.startDate, - endDate: normalized.endDate, - beforeRunID: normalized.beforeRunID, - afterRunID: normalized.afterRunID, - workflowName: normalized.workflowName, - engine: normalized.engine, - branch: normalized.branch, - artifacts: normalized.artifacts - })}`; - const hit = getCached(key); - if (hit) { - console.error(`${LOG2} getLogsData: cache hit runs=${hit.runs.length} window=${hit.window.id}`); - return hit; - } - console.error(`${LOG2} getLogsData: fetching window=${normalized.window.id} count=${normalized.count} timeout=${normalized.timeout}`); - const logsResult = await fetchLogsBatches(normalized); - console.error(`${LOG2} getLogsData: fetched runs=${logsResult.runs.length} fetches=${logsResult.logsFetches} partial=${logsResult.partial}`); - const result = { - runs: logsResult.runs, - summary: logsResult.summary, - window: normalized.window, - timeout: normalized.timeout, - logsFetches: logsResult.logsFetches, - partial: logsResult.partial, - continuation: logsResult.continuation - }; - setCached(key, result); - return result; - } - async function getForecastData(workflowIDs, window, timeout) { - if (workflowIDs.length === 0) { - return []; - } - const args = ["forecast", "--json", "--period", "month", "--days", String(forecastDaysForWindow(window)), "--timeout", String(timeout), ...workflowIDs]; - console.error(`${LOG2} getForecastData: workflowIDs=${workflowIDs.length} window=${window.id} days=${forecastDaysForWindow(window)}`); - try { - const raw = await runGhAw(args); - const data = parseJsonOutput(raw, "gh aw forecast --json"); - const workflows = Array.isArray(data.workflows) ? data.workflows : []; - console.error(`${LOG2} getForecastData: fetched workflows=${workflows.length}`); - return workflows; - } catch (err) { - console.error(`${LOG2} getForecastData error: ${asError(err).message}`); - throw err; - } - } - async function getRuns(options = {}) { - return getLogsData(options); - } - async function getUsage(options = {}) { - const normalized = normalizeLogsOptions(options); - const key = `usage:${JSON.stringify({ - window: normalized.window.id, - count: normalized.count, - timeout: normalized.timeout - })}`; - const hit = getCached(key); - if (hit) return hit; - const logsData = await getLogsData(normalized); - const usageItems = buildUsageSummary(logsData.runs, logsData.window); - const workflowIDs = usageItems.map((item) => item.workflow_id).filter(Boolean); - const forecastWorkflows = await getForecastData(workflowIDs, logsData.window, logsData.timeout); - const result = { - items: applyForecastToUsageSummary(usageItems, forecastWorkflows), - window: logsData.window, - timeout: logsData.timeout, - logsFetches: logsData.logsFetches, - partial: logsData.partial, - continuation: logsData.continuation, - total_runs: logsData.runs.length, - forecast_history_days: forecastDaysForWindow(logsData.window) - }; - setCached(key, result); - return result; - } - async function execCommand(rawCmd, options = {}) { - console.error(`${LOG2} execCommand: cmd="${rawCmd}"`); - const args = parseGhAwArgs(rawCmd); - if (!args) { - console.error(`${LOG2} execCommand: rejected unsupported command "${rawCmd}"`); - return { command: rawCmd, output: "Only 'gh aw ' commands are supported.", error: true }; - } - try { - if (args[0] === "logs" && logsCommandUsesJSON(args)) { - const commandArgs = normalizeLogsCommandArgs(args, options.window, options.timeout ?? DEFAULT_LOG_TIMEOUT_MINUTES); - const fallback = {}; - if (options.window) { - fallback.window = options.window; - } - if (options.timeout != null) { - fallback.timeout = options.timeout; - } - const logsOptions = logsArgsToOptions(commandArgs, fallback); - const logsResult = await fetchLogsBatches(logsOptions, commandArgs); - return { - command: `gh aw ${commandArgs.join(" ")}`, - output: JSON.stringify( - { - ...logsResult.firstBatch ?? {}, - runs: logsResult.runs, - partial: logsResult.partial, - logs_fetches: logsResult.logsFetches, - continuation: logsResult.continuation - }, - null, - 2 - ) - }; - } - const output = await runGhAw(args); - return { command: rawCmd, output }; - } catch (err) { - const e = asError(err); - const msg = e.stderr || e.message || "Unknown error"; - console.error(`${LOG2} execCommand error cmd="${rawCmd}": ${msg}`); - return { command: rawCmd, output: msg, error: true }; - } - } - async function getAudit(runId) { - if (!runId) return null; - const key = `audit:${runId}`; - const hit = getCached(key); - if (hit) { - console.error(`${LOG2} getAudit: cache hit runId=${runId}`); - return hit; - } - console.error(`${LOG2} getAudit: fetching runId=${runId}`); - try { - const auditArgs = ["audit", String(runId), "--json"]; - if (logsOutputDir) { - auditArgs.push("--output", logsOutputDir); - } - const raw = await runGhAw(auditArgs); - const data = parseJsonOutput(raw, `gh aw audit ${runId} --json`); - setCached(key, data); - console.error(`${LOG2} getAudit: fetched runId=${runId}`); - return data; - } catch (err) { - console.error(`${LOG2} getAudit error runId=${runId}: ${asError(err).message}`); - throw err; - } - } - return { - clearCache: () => cache.clear(), - execCommand, - getAudit, - getDefinitions, - getExperiments, - getRuns, - getUsage - }; -} -export { - DEFAULT_LOG_TIMEOUT_MINUTES, - DEFAULT_RUN_COUNT, - createDashboardDataAccess, - createGhAwRunnerWithStatus -}; diff --git a/.github/extensions/agentic-workflows-dashboard/copilot-extension.json b/.github/extensions/agentic-workflows-dashboard/copilot-extension.json deleted file mode 100644 index 02c1357e1e3..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/copilot-extension.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "agentic-workflows-dashboard", - "version": 1 -} diff --git a/.github/extensions/agentic-workflows-dashboard/extension.mjs b/.github/extensions/agentic-workflows-dashboard/extension.mjs deleted file mode 100644 index 819fb4636d5..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/extension.mjs +++ /dev/null @@ -1,360 +0,0 @@ -import { createServer } from "node:http"; -import { readFile } from "node:fs/promises"; -import { execFile as execFileCb } from "node:child_process"; -import { promisify } from "node:util"; -import { dirname, join, resolve } from "node:path"; -import { homedir } from "node:os"; -import { fileURLToPath } from "node:url"; - -const execFile = promisify(execFileCb); - -import { createCanvas, joinSession } from "@github/copilot-sdk/extension"; - -import { createGhAwRunnerWithStatus, DEFAULT_LOG_TIMEOUT_MINUTES, DEFAULT_RUN_COUNT, createDashboardDataAccess } from "./app.js"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const LOG = "[extension]"; -// For project-scoped extensions the file lives at .github/extensions//extension.mjs, -// so three levels up is the git repo root. process.cwd() and session.workspacePath both -// resolve to unrelated Copilot runtime directories and must not be used as CLI cwd. -const workspacePath = resolve(__dirname, "../../.."); -const runGhAw = createGhAwRunnerWithStatus({ getWorkspacePath: () => workspacePath }); - -/** - * Resolve a shared logs directory keyed by the GitHub repo identity (owner/repo), - * stored in the user's home cache directory. This ensures all sessions that - * target the same repo—even when each session runs in a fresh workspace - * checkout—share a single on-disk artifact cache and never re-download runs - * that are already present. - * - * Falls back to `/.github/aw/logs` when the remote URL cannot - * be determined (e.g. no git remote, or git is unavailable). - */ -async function resolveSharedLogsDir(workspace) { - try { - const { stdout } = await execFile("git", ["-C", workspace, "remote", "get-url", "origin"], { encoding: "utf8" }); - const remoteUrl = stdout.trim(); - // Match both HTTPS (https://github.com/owner/repo) and SSH (git@github.com:owner/repo) formats. - const match = remoteUrl.match(/github\.com[/:]([^/]+)\/([^/\s]+?)(?:\.git)?(?:\s|$)/); - if (match) { - const [, owner, repo] = match; - const dir = join(homedir(), ".cache", "gh-aw", "logs", owner, repo); - console.error(`${LOG} resolveSharedLogsDir: remote="${remoteUrl}" -> ${dir}`); - return dir; - } - console.error(`${LOG} resolveSharedLogsDir: could not parse owner/repo from remote="${remoteUrl}", using workspace default`); - } catch (err) { - console.error(`${LOG} resolveSharedLogsDir: git remote lookup failed: ${err.message}`); - } - const fallback = join(workspace, ".github", "aw", "logs"); - console.error(`${LOG} resolveSharedLogsDir: using fallback=${fallback}`); - return fallback; -} - -const sharedLogsDir = await resolveSharedLogsDir(workspacePath); -const servers = new Map(); -const dataAccess = createDashboardDataAccess({ runGhAw, logsOutputDir: sharedLogsDir }); -console.error(`${LOG} startup __dirname=${__dirname} workspacePath=${workspacePath} sharedLogsDir=${sharedLogsDir}`); - -// --------------------------------------------------------------------------- -// Pagination utility -// --------------------------------------------------------------------------- - -function paginate(items, page = 1, pageSize = 20) { - const totalItems = items.length; - const totalPages = Math.max(1, Math.ceil(totalItems / pageSize)); - const safePage = Math.min(Math.max(1, page), totalPages); - const start = (safePage - 1) * pageSize; - const end = start + pageSize; - return { - items: items.slice(start, end), - page: safePage, - pageSize, - totalItems, - totalPages, - hasNextPage: safePage < totalPages, - hasPreviousPage: safePage > 1, - }; -} - -// --------------------------------------------------------------------------- -// Loopback HTTP server per canvas instance -// --------------------------------------------------------------------------- - -async function startServer() { - const server = createServer(async (req, res) => { - const reqUrl = new URL(req.url ?? "/", "http://localhost"); - const pathname = reqUrl.pathname; - const t0 = Date.now(); - - const sendJson = (payload, status = 200) => { - res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" }); - res.end(JSON.stringify(payload)); - }; - - try { - if (pathname === "/" || pathname === "/index.html") { - const [html, css] = await Promise.all([readFile(join(__dirname, "web", "index.html"), "utf8"), readFile(join(__dirname, "web", "styles.css"), "utf8")]); - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end(html.replace("/*__APP_CSS__*/", css)); - } else if (pathname === "/app.js") { - res.setHeader("Content-Type", "application/javascript; charset=utf-8"); - res.end(await readFile(join(__dirname, "web", "app.js"), "utf8")); - } else if (pathname === "/api/status") { - sendJson(await dataAccess.getDefinitions()); - } else if (pathname === "/api/cli-status") { - sendJson(await runGhAw.getStatus()); - } else if (pathname === "/api/experiments") { - sendJson(await dataAccess.getExperiments()); - } else if (pathname === "/api/runs") { - sendJson( - await dataAccess.getRuns({ - count: parseInt(reqUrl.searchParams.get("count") ?? String(DEFAULT_RUN_COUNT), 10), - window: reqUrl.searchParams.get("window") ?? "7d", - timeout: parseInt(reqUrl.searchParams.get("timeout") ?? String(DEFAULT_LOG_TIMEOUT_MINUTES), 10), - workflowName: reqUrl.searchParams.get("workflow_name") ?? "", - }) - ); - } else if (pathname === "/api/usage") { - sendJson( - await dataAccess.getUsage({ - count: parseInt(reqUrl.searchParams.get("count") ?? String(DEFAULT_RUN_COUNT), 10), - window: reqUrl.searchParams.get("window") ?? "7d", - timeout: parseInt(reqUrl.searchParams.get("timeout") ?? String(DEFAULT_LOG_TIMEOUT_MINUTES), 10), - }) - ); - } else if (pathname === "/api/audit") { - const runId = reqUrl.searchParams.get("run_id") ?? ""; - if (!runId) { - sendJson({ error: "run_id is required" }, 400); - } else { - sendJson(await dataAccess.getAudit(runId)); - } - } else if (pathname === "/api/run-command") { - const cmd = reqUrl.searchParams.get("cmd") ?? ""; - sendJson( - await dataAccess.execCommand(cmd, { - window: reqUrl.searchParams.get("window") ?? "7d", - timeout: parseInt(reqUrl.searchParams.get("timeout") ?? String(DEFAULT_LOG_TIMEOUT_MINUTES), 10), - }) - ); - } else if (pathname === "/api/refresh") { - dataAccess.clearCache(); - sendJson({ ok: true }); - } else { - res.writeHead(404); - res.end("Not found"); - } - if (pathname.startsWith("/api/")) { - console.error(`${LOG} request ${req.method} ${pathname} ${Date.now() - t0}ms`); - } - } catch (err) { - console.error(`${LOG} request error ${req.method} ${pathname} ${Date.now() - t0}ms: ${err.message}`); - sendJson({ error: err.message }, 500); - } - }); - await new Promise(r => server.listen(0, "127.0.0.1", r)); - const { port } = server.address(); - console.error(`${LOG} server listening on port ${port}`); - return { server, url: `http://127.0.0.1:${port}/` }; -} - -// --------------------------------------------------------------------------- -// Session -// --------------------------------------------------------------------------- - -const session = await joinSession({ - systemMessage: { - mode: "append", - content: `## Agentic Workflows Dashboard - -This canvas shows live data from the current repository using the gh-aw CLI. -It never calls Go code directly — all data is fetched by running CLI subcommands. - -**CLI commands used by this canvas:** -- \`gh aw status --json\` — list agentic workflow definitions (workflow, engine_id, compiled, labels, status, time_remaining) -- \`gh aw logs --json -c --start-date --timeout \` — list recent workflow runs and follow continuation batches progressively -- \`gh aw experiments list --json\` — list experiment workflow branches (workflow_id, branch, experiments, total_runs, last_run) - -**Dev build** (when gh-aw is not installed as a gh extension): -1. Run \`make build\` in the repository root to compile \`./gh-aw\` (or \`./gh-aw.exe\` on Windows) -2. The canvas auto-detects the dev binary and uses it before falling back to \`gh aw\` - -**Canvas actions available to the agent:** -- \`listDefinitions\` — calls \`gh aw status --json\`, returns paged results -- \`listRuns\` — calls \`gh aw logs --json\` with a selected report window, timeout, and continuation handling -- \`listUsage\` — aggregates workflow AIC usage from logs and fills monthly forecast via \`gh aw forecast --json\` -- \`listExperiments\` — calls \`gh aw experiments list --json\`, returns paged results -- \`getRun\` — looks up a single run by \`run_id\` -- \`auditRun\` — calls \`gh aw audit --json\` and returns structured audit data (overview, metrics, key_findings, recommendations, jobs, tool_usage, errors, warnings, firewall_analysis) -- \`runCommand\` — executes any \`gh aw \` and returns stdout -- \`refresh\` — clears the 60-second cache so the next call fetches fresh data -`, - }, - canvases: [ - createCanvas({ - id: "agentic-workflows-dashboard", - displayName: "Agentic Workflows Dashboard", - description: "Live dashboard for agentic workflow definitions and runs, powered by gh aw status and gh aw logs.", - actions: [ - { - name: "listDefinitions", - description: "List workflow definitions via gh aw status --json, with paging.", - inputSchema: { - type: "object", - properties: { - page: { type: "number", minimum: 1 }, - pageSize: { type: "number", minimum: 1, maximum: 100 }, - }, - additionalProperties: false, - }, - handler: async ctx => { - const defs = await dataAccess.getDefinitions(); - return paginate(defs, Number(ctx.input?.page ?? 1), Number(ctx.input?.pageSize ?? 20)); - }, - }, - { - name: "listRuns", - description: "List recent workflow runs via gh aw logs --json, with paging and continuation handling.", - inputSchema: { - type: "object", - properties: { - page: { type: "number", minimum: 1 }, - pageSize: { type: "number", minimum: 1, maximum: 100 }, - count: { type: "number", minimum: 1, maximum: 200, description: "Max runs to fetch from the CLI." }, - window: { type: "string", enum: ["3d", "7d", "1mo"], description: "Report window preset for gh aw logs." }, - timeout: { type: "number", minimum: 1, maximum: 10, description: "Per-request timeout in minutes for progressive logs retrieval." }, - }, - additionalProperties: false, - }, - handler: async ctx => { - const logsData = await dataAccess.getRuns({ - count: Number(ctx.input?.count ?? DEFAULT_RUN_COUNT), - window: String(ctx.input?.window ?? "7d"), - timeout: Number(ctx.input?.timeout ?? DEFAULT_LOG_TIMEOUT_MINUTES), - }); - return { - ...paginate(logsData.runs, Number(ctx.input?.page ?? 1), Number(ctx.input?.pageSize ?? 20)), - partial: logsData.partial, - logsFetches: logsData.logsFetches, - window: logsData.window, - }; - }, - }, - { - name: "listUsage", - description: "Aggregate workflow AIC usage from gh aw logs and monthly forecast costs from gh aw forecast.", - inputSchema: { - type: "object", - properties: { - page: { type: "number", minimum: 1 }, - pageSize: { type: "number", minimum: 1, maximum: 100 }, - count: { type: "number", minimum: 1, maximum: 200, description: "Max runs to fetch from the CLI." }, - window: { type: "string", enum: ["3d", "7d", "1mo"], description: "Report window preset for gh aw logs." }, - timeout: { type: "number", minimum: 1, maximum: 10, description: "Per-request timeout in minutes for progressive logs retrieval." }, - }, - additionalProperties: false, - }, - handler: async ctx => { - const usage = await dataAccess.getUsage({ - count: Number(ctx.input?.count ?? DEFAULT_RUN_COUNT), - window: String(ctx.input?.window ?? "7d"), - timeout: Number(ctx.input?.timeout ?? DEFAULT_LOG_TIMEOUT_MINUTES), - }); - return { - ...paginate(usage.items, Number(ctx.input?.page ?? 1), Number(ctx.input?.pageSize ?? 20)), - partial: usage.partial, - logsFetches: usage.logsFetches, - totalRuns: usage.total_runs, - window: usage.window, - }; - }, - }, - { - name: "listExperiments", - description: "List experiment workflow branches via gh aw experiments list --json, with paging.", - inputSchema: { - type: "object", - properties: { - page: { type: "number", minimum: 1 }, - pageSize: { type: "number", minimum: 1, maximum: 100 }, - }, - additionalProperties: false, - }, - handler: async ctx => { - const experiments = await dataAccess.getExperiments(); - return paginate(experiments, Number(ctx.input?.page ?? 1), Number(ctx.input?.pageSize ?? 20)); - }, - }, - { - name: "getRun", - description: "Get a single workflow run by its run_id.", - inputSchema: { - type: "object", - required: ["run_id"], - properties: { run_id: { type: "number" } }, - additionalProperties: false, - }, - handler: async ctx => { - const logsData = await dataAccess.getRuns({ count: 200, window: "1mo", timeout: DEFAULT_LOG_TIMEOUT_MINUTES }); - return { run: logsData.runs.find(r => r.run_id === Number(ctx.input?.run_id)) ?? null }; - }, - }, - { - name: "auditRun", - description: "Run gh aw audit for a specific workflow run by run_id, returning structured audit data.", - inputSchema: { - type: "object", - required: ["run_id"], - properties: { run_id: { type: "string", description: "The workflow run ID to audit (numeric string)." } }, - additionalProperties: false, - }, - handler: async ctx => { - const runId = String(ctx.input?.run_id ?? "").trim(); - if (!runId || !/^\d+$/.test(runId)) { - throw new Error("run_id must be a non-empty numeric string"); - } - return dataAccess.getAudit(runId); - }, - }, - { - name: "runCommand", - description: "Execute a gh aw subcommand (e.g. 'gh aw status', 'gh aw logs -c 5') and return its stdout.", - inputSchema: { - type: "object", - required: ["command"], - properties: { command: { type: "string", description: "Full command string starting with 'gh aw'." } }, - additionalProperties: false, - }, - handler: async ctx => dataAccess.execCommand(String(ctx.input?.command ?? ""), { window: "7d", timeout: DEFAULT_LOG_TIMEOUT_MINUTES }), - }, - { - name: "refresh", - description: "Clear the data cache so the next listDefinitions/listRuns fetches fresh data from the CLI.", - inputSchema: { type: "object", additionalProperties: false }, - handler: () => { - dataAccess.clearCache(); - return { ok: true }; - }, - }, - ], - open: async ctx => { - console.error(`${LOG} canvas open instanceId=${ctx.instanceId}`); - let entry = servers.get(ctx.instanceId); - if (!entry) { - entry = await startServer(); - servers.set(ctx.instanceId, entry); - } - return { title: "Agentic Workflows Dashboard", status: "Live · gh aw", url: entry.url }; - }, - onClose: async ctx => { - console.error(`${LOG} canvas close instanceId=${ctx.instanceId}`); - const entry = servers.get(ctx.instanceId); - if (entry) { - servers.delete(ctx.instanceId); - await new Promise(r => entry.server.close(r)); - } - }, - }), - ], -}); diff --git a/.github/extensions/agentic-workflows-dashboard/package-lock.json b/.github/extensions/agentic-workflows-dashboard/package-lock.json deleted file mode 100644 index a352b2d879c..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/package-lock.json +++ /dev/null @@ -1,1809 +0,0 @@ -{ - "name": "agentic-workflows-dashboard", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "agentic-workflows-dashboard", - "version": "1.0.0", - "dependencies": { - "alpinejs": "^3.15.0" - }, - "devDependencies": { - "@types/node": "^26.0.1", - "esbuild": "^0.28.1", - "typescript": "6.0.3", - "vitest": "4.1.9" - } - }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", - "integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.9", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.9", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", - "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.1.5" - } - }, - "node_modules/@vue/shared": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", - "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==", - "license": "MIT" - }, - "node_modules/alpinejs": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.0.tgz", - "integrity": "sha512-lpokA5okCF1BKh10LG8YjqhfpxyHBk4gE7boIgVHltJzYoM7O9nK3M7VlntLEJGsVmu7U/RzUWajmHREGT38Eg==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "~3.1.1" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.137.0", - "@rolldown/pluginutils": "^1.0.0" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", - "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "~1.1.2", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - } - } -} diff --git a/.github/extensions/agentic-workflows-dashboard/package.json b/.github/extensions/agentic-workflows-dashboard/package.json deleted file mode 100644 index c0a986f45c5..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "agentic-workflows-dashboard", - "version": "1.0.0", - "type": "module", - "main": "extension.mjs", - "description": "GitHub Copilot canvas extension for agentic workflows dashboard.", - "dependencies": { - "alpinejs": "^3.15.0" - }, - "devDependencies": { - "@types/node": "^26.0.1", - "esbuild": "^0.28.1", - "typescript": "6.0.3", - "vitest": "4.1.9" - }, - "scripts": { - "build": "npm run typecheck && npm run build:web", - "build:ts": "esbuild src/index.ts --bundle --format=esm --target=es2022 --platform=node --outfile=app.js", - "build:web": "esbuild src/app.ts --bundle --format=esm --target=es2022 --outfile=web/app.js", - "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "vitest run", - "lint": "npm run typecheck && npm test", - "fmt": "npm run fmt:ts && npm run fmt:html && npm run fmt:css && npm run fmt:js && npm run fmt:json", - "fmt:ts": "npx prettier --write --parser typescript \"src/**/*.ts\" \"test/**/*.ts\" \"vitest.config.ts\"", - "fmt:html": "npx prettier --write --parser html \"web/index.html\"", - "fmt:css": "npx prettier --write --parser css \"web/styles.css\"", - "fmt:js": "npx prettier --write --parser babel \"*.mjs\"", - "fmt:json": "npx prettier --write --parser json \"copilot-extension.json\" \"package.json\"", - "impeccable:detect": "npx impeccable detect web/index.html web/styles.css" - } -} diff --git a/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-full.png b/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-full.png deleted file mode 100644 index 0bb002114cb..00000000000 Binary files a/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-full.png and /dev/null differ diff --git a/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-viewport.png b/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-viewport.png deleted file mode 100644 index 5f90c3ff659..00000000000 Binary files a/.github/extensions/agentic-workflows-dashboard/screenshots/dashboard-viewport.png and /dev/null differ diff --git a/.github/extensions/agentic-workflows-dashboard/src/alpinejs.d.ts b/.github/extensions/agentic-workflows-dashboard/src/alpinejs.d.ts deleted file mode 100644 index 682c31f23ad..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/alpinejs.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare module "alpinejs" { - interface AlpineStatic { - data(name: string, factory: () => T): void; - start(): void; - } - const Alpine: AlpineStatic; - export default Alpine; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/app.ts b/.github/extensions/agentic-workflows-dashboard/src/app.ts deleted file mode 100644 index 81772676b8b..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/app.ts +++ /dev/null @@ -1,587 +0,0 @@ -import Alpine from "alpinejs"; - -import type { AuditFinding, AuditReport, CLIStatus, ExperimentInfo, PagedResult, UsageSummaryItem, WorkflowDefinition, WorkflowRun } from "./models.js"; -import { paginate } from "./pagination.js"; -import type { ReportWindow } from "./dashboard-config.js"; - -type FlashKind = "success" | "warn" | "error"; -type DashboardTabId = "definitions" | "runs" | "details" | "usage" | "experiments" | "maintenance" | "commands"; -type DashboardTab = { id: DashboardTabId; label: string; counter?: "definitions" | "runs" | "usage" | "experiments" }; -type MaintenanceAction = "check-update" | "run-update" | "check-upgrade" | "run-upgrade"; -type ReportMeta = { - window?: { id?: string; label?: string }; - logsFetches?: number; - partial?: boolean; - total_runs?: number; -}; -type RunsResponse = ReportMeta & { runs?: WorkflowRun[] }; -type UsageResponse = ReportMeta & { items?: UsageSummaryItem[] }; - -interface DashboardState { - tabs: DashboardTab[]; - reportWindows: ReportWindow[]; - activeTab: DashboardTabId; - selectedWindow: ReportWindow["id"]; - selectedWorkflowFilter: string; - logsTimeout: number; - pageSize: number; - cliStatus: CLIStatus | null; - definitions: WorkflowDefinition[]; - runs: WorkflowRun[]; - usage: UsageSummaryItem[]; - experiments: ExperimentInfo[]; - definitionsPaged: PagedResult; - runsPaged: PagedResult; - usagePaged: PagedResult; - experimentsPaged: PagedResult; - selectedRun: WorkflowRun | null; - auditData: AuditReport | null; - includePreReleases: boolean; - maintenanceOutput: string; - maintenanceLastAction: string; - commandInput: string; - commandOutput: string; - flashMessage: string; - flashKind: FlashKind; - loadingCliStatus: boolean; - loadingDefinitions: boolean; - loadingRuns: boolean; - loadingUsage: boolean; - loadingExperiments: boolean; - loadingAudit: boolean; - loadingMaintenance: boolean; - errorCliStatus: string; - errorDefinitions: string; - errorRuns: string; - errorUsage: string; - errorExperiments: string; - errorAudit: string; - runsMeta: ReportMeta | null; - usageMeta: ReportMeta | null; - init(): Promise; - currentWindow(): ReportWindow; - reportWindowClass(windowId: ReportWindow["id"]): string; - selectReportWindow(windowId: ReportWindow["id"]): Promise; - fetchCliStatus(): Promise; - fetchDefinitions(): Promise; - fetchRuns(): Promise; - fetchUsage(): Promise; - fetchExperiments(): Promise; - refresh(): Promise; - setActiveTab(tab: DashboardTabId): void; - isActiveTab(tab: DashboardTabId): boolean; - tabCount(tab: DashboardTab): number; - loadDefinitionPage(page: number): void; - loadRunPage(page: number): void; - loadUsagePage(page: number): void; - loadExperimentPage(page: number): void; - selectRun(runId: number): void; - viewRunDetails(runId: number): void; - selectWorkflowFilter(workflowName: string): Promise; - loadAudit(): Promise; - clearAudit(): void; - buildLogsCommand(count?: number): string; - buildMaintenanceCommand(action: MaintenanceAction): string; - maintenanceActionLabel(action: MaintenanceAction): string; - runMaintenanceAction(action: MaintenanceAction): Promise; - auditHasFindings(): boolean; - auditSeverityClass(severity?: string): string; - auditPriorityClass(priority?: string): string; - buildReportSummaryMessage(meta: ReportMeta | null): string; - runCommand(): Promise; - commandQuickFill(value: string): void; - runStatusClass(run: WorkflowRun): string; - runStatusLabel(run: WorkflowRun): string; - definitionStatusClass(definition: WorkflowDefinition): string; - definitionStatusLabel(definition: WorkflowDefinition): string; - formatDuration(ms?: number | null): string; - formatDate(iso?: string | null): string; - formatAIC(value?: number | null): string; - formatNumber(value?: number | null, options?: Intl.NumberFormatOptions): string; - cliSourceLabel(cliStatus: CLIStatus | null): string; - cliUnavailableMessage(): string; -} - -const dashboardTabs: DashboardTab[] = [ - { id: "definitions", label: "Workflows", counter: "definitions" }, - { id: "runs", label: "Runs", counter: "runs" }, - { id: "details", label: "Run details" }, - { id: "usage", label: "Usage", counter: "usage" }, - { id: "experiments", label: "Experiments", counter: "experiments" }, - { id: "maintenance", label: "Maintenance" }, - { id: "commands", label: "Commands" }, -]; - -const reportWindows: ReportWindow[] = [ - { id: "3d", label: "3 days", startDate: "-3d", days: 3 }, - { id: "7d", label: "7 days", startDate: "-1w", days: 7 }, - { id: "1mo", label: "1 month", startDate: "-1mo", days: 30 }, -]; - -const DEFAULT_LOGS_COMMAND_COUNT = 25; - -function cliSourceLabel(cliStatus: CLIStatus | null): string { - if (!cliStatus?.available) return "not installed"; - if (cliStatus.source === "dev-binary") return "local build"; - if (cliStatus.source === "gh-extension") return "gh extension"; - return "available"; -} - -function runStatusClass(run: WorkflowRun): string { - const status = run.status ?? ""; - const conclusion = run.conclusion ?? ""; - if (status === "completed" || status === "success") { - return conclusion && conclusion !== "success" ? "Label Label--danger" : "Label Label--success"; - } - if (status === "failure" || status === "failed") return "Label Label--danger"; - if (status === "in_progress" || status === "running") return "Label Label--attention"; - return "Label Label--secondary"; -} - -function runStatusLabel(run: WorkflowRun): string { - if (run.status === "completed" && run.conclusion) return run.conclusion; - return run.status ?? "unknown"; -} - -function definitionStatusClass(definition: WorkflowDefinition): string { - if (definition.status === "disabled") return "Label Label--secondary"; - return definition.compiled === "yes" ? "Label Label--success" : "Label Label--attention"; -} - -function definitionStatusLabel(definition: WorkflowDefinition): string { - if (definition.status === "disabled") return "disabled"; - return definition.compiled === "yes" ? "enabled" : "not compiled"; -} - -function formatDuration(ms?: number | null): string { - if (ms == null) return "—"; - const secs = Math.round(ms / 1000); - if (secs < 60) return `${secs}s`; - return `${Math.floor(secs / 60)}m ${secs % 60}s`; -} - -function formatDate(iso?: string | null): string { - if (!iso) return "—"; - const date = new Date(iso); - return Number.isNaN(date.getTime()) ? "—" : date.toLocaleString(); -} - -function formatNumber(value?: number | null, options: Intl.NumberFormatOptions = {}): string { - const numeric = Number(value ?? 0); - if (!Number.isFinite(numeric)) return "0"; - return new Intl.NumberFormat(undefined, options).format(numeric); -} - -function formatAIC(value?: number | null): string { - const numeric = Number(value ?? 0); - if (!Number.isFinite(numeric) || numeric <= 0) return "0"; - return formatNumber(Math.ceil(numeric)); -} - -function reportWindowById(windowId: ReportWindow["id"]): ReportWindow { - return reportWindows.find(window => window.id === windowId) ?? reportWindows[1]!; -} - -function buildReportMessage(meta: ReportMeta | null, emptyLabel: string): string { - if (!meta?.window) return emptyLabel ?? ""; - - const windowLabel = meta.window.label ?? meta.window.id; - const fragments = windowLabel ? [`Window: ${windowLabel}`] : []; - if (meta.logsFetches) { - fragments.push(`${meta.logsFetches} log request${meta.logsFetches === 1 ? "" : "s"}`); - } - if (meta.partial) { - fragments.push("continuation still available"); - } - if (meta.total_runs != null) { - fragments.push(`${meta.total_runs} runs analyzed`); - } - - return fragments.length > 0 ? fragments.join(" · ") : emptyLabel; -} - -async function fetchJson(url: string): Promise { - const response = await fetch(url); - const data = (await response.json()) as T & { error?: string }; - if (!response.ok) { - throw new Error(data.error ?? `HTTP ${response.status}`); - } - return data; -} - -Alpine.data("dashboardApp", (): DashboardState => ({ - tabs: dashboardTabs, - reportWindows, - activeTab: "definitions", - selectedWindow: "7d", - selectedWorkflowFilter: "", - logsTimeout: 1, - pageSize: 20, - cliStatus: null, - definitions: [], - runs: [], - usage: [], - experiments: [], - definitionsPaged: paginate([], 1, 20), - runsPaged: paginate([], 1, 20), - usagePaged: paginate([], 1, 20), - experimentsPaged: paginate([], 1, 20), - selectedRun: null, - auditData: null, - includePreReleases: false, - maintenanceOutput: "Use this view to check dashboard maintenance commands before applying updates or upgrades.", - maintenanceLastAction: "", - commandInput: "", - commandOutput: "", - flashMessage: "", - flashKind: "success", - loadingCliStatus: true, - loadingDefinitions: false, - loadingRuns: false, - loadingUsage: false, - loadingExperiments: false, - loadingAudit: false, - loadingMaintenance: false, - errorCliStatus: "", - errorDefinitions: "", - errorRuns: "", - errorUsage: "", - errorExperiments: "", - errorAudit: "", - runsMeta: null, - usageMeta: null, - - async init() { - this.commandInput = this.buildLogsCommand(); - await this.fetchCliStatus(); - if (this.cliStatus?.available) { - await Promise.all([this.fetchDefinitions(), this.fetchUsage(), this.fetchExperiments()]); - this.commandOutput = `$ ${this.cliStatus.command}\ngh aw version ${this.cliStatus.version}`; - return; - } - - const unavailableMessage = this.cliUnavailableMessage(); - this.commandInput = this.cliStatus?.command ?? "gh aw version"; - this.commandOutput = unavailableMessage; - }, - - currentWindow() { - return reportWindowById(this.selectedWindow); - }, - - reportWindowClass(windowId) { - return this.selectedWindow === windowId ? "BtnGroup-item btn btn-sm btn-primary" : "BtnGroup-item btn btn-sm"; - }, - - async selectReportWindow(windowId) { - if (this.selectedWindow === windowId) return; - this.selectedWindow = windowId; - this.commandInput = this.buildLogsCommand(); - if (!this.cliStatus?.available) return; - await Promise.all([this.fetchRuns(), this.fetchUsage()]); - }, - - async selectWorkflowFilter(workflowName) { - this.selectedWorkflowFilter = workflowName; - this.commandInput = this.buildLogsCommand(); - if (!this.cliStatus?.available) return; - await this.fetchRuns(); - }, - - async fetchCliStatus() { - this.loadingCliStatus = true; - this.errorCliStatus = ""; - try { - this.cliStatus = await fetchJson("/api/cli-status"); - } catch (error) { - this.cliStatus = null; - this.errorCliStatus = `Failed to detect gh aw: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingCliStatus = false; - } - }, - - async fetchDefinitions() { - this.loadingDefinitions = true; - this.errorDefinitions = ""; - try { - this.definitions = await fetchJson("/api/status"); - this.loadDefinitionPage(1); - } catch (error) { - this.errorDefinitions = `Failed to load workflows: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingDefinitions = false; - } - }, - - async fetchRuns() { - if (!this.selectedWorkflowFilter) { - this.runs = []; - this.runsMeta = null; - this.selectedRun = null; - this.loadRunPage(1); - return; - } - this.loadingRuns = true; - this.errorRuns = ""; - try { - const previousRunId = this.selectedRun?.run_id ?? null; - const params = new URLSearchParams({ - count: "100", - window: this.selectedWindow, - timeout: String(this.logsTimeout), - workflow_name: this.selectedWorkflowFilter, - }); - const data = await fetchJson(`/api/runs?${params.toString()}`); - this.runsMeta = data; - this.runs = Array.isArray(data.runs) ? data.runs : []; - this.loadRunPage(1); - this.selectedRun = this.runs.find(run => run.run_id === previousRunId) ?? this.runs[0] ?? null; - } catch (error) { - this.runsMeta = null; - this.errorRuns = `Failed to load runs: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingRuns = false; - } - }, - - async fetchUsage() { - this.loadingUsage = true; - this.errorUsage = ""; - try { - const params = new URLSearchParams({ - count: "100", - window: this.selectedWindow, - timeout: String(this.logsTimeout), - }); - const data = await fetchJson(`/api/usage?${params.toString()}`); - this.usageMeta = data; - this.usage = Array.isArray(data.items) ? data.items : []; - this.loadUsagePage(1); - } catch (error) { - this.usageMeta = null; - this.errorUsage = `Failed to load usage summary: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingUsage = false; - } - }, - - async fetchExperiments() { - this.loadingExperiments = true; - this.errorExperiments = ""; - try { - this.experiments = await fetchJson("/api/experiments"); - this.loadExperimentPage(1); - } catch (error) { - this.errorExperiments = `Failed to load experiments: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingExperiments = false; - } - }, - - async refresh() { - await fetch("/api/refresh"); - this.flashMessage = "Refreshing…"; - this.flashKind = "success"; - await this.fetchCliStatus(); - if (this.cliStatus?.available) { - await Promise.all([this.fetchDefinitions(), this.fetchRuns(), this.fetchUsage(), this.fetchExperiments()]); - this.commandOutput = `$ ${this.cliStatus.command}\ngh aw version ${this.cliStatus.version}`; - } else { - this.definitions = []; - this.runs = []; - this.usage = []; - this.experiments = []; - this.selectedRun = null; - this.runsMeta = null; - this.usageMeta = null; - this.loadDefinitionPage(1); - this.loadRunPage(1); - this.loadUsagePage(1); - this.loadExperimentPage(1); - const unavailableMessage = this.cliUnavailableMessage(); - this.commandInput = this.cliStatus?.command ?? "gh aw version"; - this.commandOutput = unavailableMessage; - } - this.flashMessage = "Refreshed."; - setTimeout(() => { - this.flashMessage = ""; - }, 3000); - }, - - setActiveTab(tab) { - if (this.tabs.some(item => item.id === tab)) this.activeTab = tab; - }, - - isActiveTab(tab) { - return this.activeTab === tab; - }, - - tabCount(tab) { - if (tab.counter === "definitions") return this.definitions.length; - if (tab.counter === "runs") return this.runs.length; - if (tab.counter === "usage") return this.usage.length; - if (tab.counter === "experiments") return this.experiments.length; - return 0; - }, - - loadDefinitionPage(page) { - this.definitionsPaged = paginate(this.definitions, page, this.pageSize); - }, - - loadRunPage(page) { - this.runsPaged = paginate(this.runs, page, this.pageSize); - }, - - loadUsagePage(page) { - this.usagePaged = paginate(this.usage, page, this.pageSize); - }, - - loadExperimentPage(page) { - this.experimentsPaged = paginate(this.experiments, page, this.pageSize); - }, - - selectRun(runId) { - const previousRunId = this.selectedRun?.run_id; - this.selectedRun = this.runs.find(run => run.run_id === runId) ?? null; - if (this.selectedRun?.run_id !== previousRunId) { - this.clearAudit(); - } - }, - - viewRunDetails(runId) { - this.selectRun(runId); - this.setActiveTab("details"); - }, - - async loadAudit() { - if (!this.selectedRun?.run_id) return; - this.loadingAudit = true; - this.errorAudit = ""; - try { - const params = new URLSearchParams({ run_id: String(this.selectedRun.run_id) }); - this.auditData = await fetchJson(`/api/audit?${params.toString()}`); - } catch (error) { - this.auditData = null; - this.errorAudit = `Failed to load audit: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingAudit = false; - } - }, - - clearAudit() { - this.auditData = null; - this.errorAudit = ""; - this.loadingAudit = false; - }, - - buildLogsCommand(count = DEFAULT_LOGS_COMMAND_COUNT) { - const window = this.currentWindow(); - const workflowPart = this.selectedWorkflowFilter ? ` ${this.selectedWorkflowFilter}` : ""; - return `gh aw logs --json -c ${count}${workflowPart} --start-date ${window.startDate} --timeout ${this.logsTimeout}`; - }, - - buildMaintenanceCommand(action) { - if (action === "check-update") return "gh aw status --json"; - if (action === "run-update") return "gh aw update"; - if (action === "check-upgrade") return `gh aw upgrade --audit${this.includePreReleases ? " --pre-releases" : ""}`; - return `gh aw upgrade${this.includePreReleases ? " --pre-releases" : ""}`; - }, - - maintenanceActionLabel(action) { - if (action === "check-update") return "Check updates"; - if (action === "run-update") return "Run update"; - if (action === "check-upgrade") return "Check upgrades"; - return "Run upgrade"; - }, - - async runMaintenanceAction(action) { - const cmd = this.buildMaintenanceCommand(action); - this.loadingMaintenance = true; - this.maintenanceLastAction = this.maintenanceActionLabel(action); - this.maintenanceOutput = `$ ${cmd}\n(running…)`; - this.commandInput = cmd; - try { - const params = new URLSearchParams({ - cmd, - window: this.selectedWindow, - timeout: String(this.logsTimeout), - }); - const result = await fetchJson<{ command?: string; output?: string }>(`/api/run-command?${params.toString()}`); - this.maintenanceOutput = `$ ${result.command ?? cmd}\n${result.output ?? ""}`; - if (action === "run-update" || action === "run-upgrade") { - await fetch("/api/refresh"); - await this.fetchCliStatus(); - if (this.cliStatus?.available) { - await Promise.all([this.fetchDefinitions(), this.fetchRuns(), this.fetchUsage(), this.fetchExperiments()]); - } - } - } catch (error) { - this.maintenanceOutput = `$ ${cmd}\nError: ${error instanceof Error ? error.message : String(error)}`; - } finally { - this.loadingMaintenance = false; - } - }, - - cliUnavailableMessage() { - return (this.cliStatus?.message ?? this.errorCliStatus) || "gh aw is not installed."; - }, - - auditHasFindings() { - return (this.auditData?.key_findings ?? []).length > 0; - }, - - auditSeverityClass(severity) { - const normalized = (severity ?? "").toLowerCase(); - if (normalized === "critical" || normalized === "high" || normalized === "error") return "Label Label--danger"; - if (normalized === "medium" || normalized === "warning") return "Label Label--attention"; - if (normalized === "low") return "Label Label--accent"; - return "Label Label--secondary"; - }, - - auditPriorityClass(priority) { - const normalized = (priority ?? "").toLowerCase(); - if (normalized === "high" || normalized === "p0" || normalized === "p1") return "Label Label--danger"; - if (normalized === "medium" || normalized === "p2") return "Label Label--attention"; - return "Label Label--secondary"; - }, - - buildReportSummaryMessage(meta) { - return buildReportMessage(meta, "No logs metadata available."); - }, - - async runCommand() { - const cmd = this.commandInput.trim(); - this.commandOutput = `$ ${cmd}\n(running…)`; - try { - const params = new URLSearchParams({ - cmd, - window: this.selectedWindow, - timeout: String(this.logsTimeout), - }); - const result = await fetchJson<{ command?: string; output?: string }>(`/api/run-command?${params.toString()}`); - this.commandOutput = `$ ${result.command ?? cmd}\n${result.output ?? ""}`; - } catch (error) { - this.commandOutput = `$ ${cmd}\nError: ${error instanceof Error ? error.message : String(error)}`; - } - }, - - commandQuickFill(value) { - this.commandInput = value; - this.runCommand().catch(error => { - this.commandOutput = `$ ${this.commandInput}\nError: ${error instanceof Error ? error.message : String(error)}`; - }); - }, - - runStatusClass, - runStatusLabel, - definitionStatusClass, - definitionStatusLabel, - formatDuration, - formatDate, - formatAIC, - formatNumber, - cliSourceLabel, -})); - -Alpine.start(); diff --git a/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts b/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts deleted file mode 100644 index e5f949bdc5b..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/dashboard-cli.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { spawn, type SpawnOptions } from "node:child_process"; -import { constants as fsConstants } from "node:fs"; -import { access } from "node:fs/promises"; -import { join } from "node:path"; - -const INSTALL_COMMAND = "gh extension install github/gh-aw"; -const GH_INSTALL_URL = "https://cli.github.com"; -const LOG = "[dashboard-cli]"; - -type ExecError = Error & { - code?: string | number; - syscall?: string; - path?: string; - stderr?: string; - stdout?: string; - output?: string; -}; - -type ExecCallback = (err: ExecError | null, stdout: string, stderr: string) => void; - -type ExecFileLike = (file: string, args: string[], options: ExecOptions, callback: ExecCallback) => void; - -type AccessLike = typeof access; - -interface ExecOptions { - env?: NodeJS.ProcessEnv; - cwd?: string; - maxBuffer?: number; -} - -interface RunExecOptions { - combineIO?: boolean; - execFileFn?: ExecFileLike; - env?: NodeJS.ProcessEnv; -} - -interface RunnerOptions { - getWorkspacePath: () => string; - accessFn?: AccessLike; - execFileFn?: ExecFileLike; - platform?: NodeJS.Platform; - env?: NodeJS.ProcessEnv; - /** Pre-built memoized resolver; when provided, `findDevBinary` is never called directly. */ - resolveBin?: () => Promise; -} - -export interface GhAwStatus { - available: boolean; - source: "dev-binary" | "gh-extension" | "gh-not-found" | "missing" | "error"; - version: string; - command: string; - installCommand: string; - installUrl?: string; - message?: string; -} - -export type GhAwRunner = ((args: string[]) => Promise) & { - getStatus: () => Promise; -}; - -function combineOutput(stdout: string, stderr: string): string { - return [stdout, stderr].filter(Boolean).join("\n").trim(); -} - -function spawnExecFile(file: string, args: string[], options: ExecOptions, callback: ExecCallback): void { - const { env, cwd, maxBuffer = 10 * 1024 * 1024 } = options ?? {}; - const spawnOptions: SpawnOptions = { env, cwd, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: true }; - console.error(`${LOG} spawn file=${file} args=${JSON.stringify(args)} cwd=${cwd}`); - const proc = spawn(file, args, spawnOptions); - const stdoutChunks: Buffer[] = []; - const stderrChunks: Buffer[] = []; - let stdoutLen = 0; - let stderrLen = 0; - let overflowed = false; - - proc.stdout?.on("data", (chunk: Buffer) => { - stdoutLen += chunk.length; - if (stdoutLen > maxBuffer) { - overflowed = true; - return; - } - stdoutChunks.push(chunk); - }); - - proc.stderr?.on("data", (chunk: Buffer) => { - stderrLen += chunk.length; - if (stderrLen > maxBuffer) { - overflowed = true; - return; - } - stderrChunks.push(chunk); - }); - - proc.on("error", err => { - console.error(`${LOG} spawn error file=${file} args=${JSON.stringify(args)}: ${(err as ExecError).message}`); - callback(err as ExecError, "", ""); - }); - proc.on("close", code => { - const stdout = Buffer.concat(stdoutChunks).toString("utf8"); - const stderr = Buffer.concat(stderrChunks).toString("utf8"); - if (code !== 0 || stderr) { - console.error(`${LOG} spawn close file=${file} code=${code} stdout=${stdout.length}B stderr=${stderr.length}B${stderr ? ` stderr: ${stderr.slice(0, 300)}` : ""}`); - } - if (overflowed) { - const err: ExecError = new Error("stdout/stderr maxBuffer exceeded"); - err.code = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"; - callback(err, stdout, stderr); - } else if (code !== 0) { - const err: ExecError = new Error(`Command failed with exit code ${code}`); - err.code = code ?? 1; - callback(err, stdout, stderr); - } else { - callback(null, stdout, stderr); - } - }); -} - -function execp(bin: string, args: string[], cwd: string, { combineIO = false, execFileFn = spawnExecFile, env = process.env }: RunExecOptions = {}): Promise { - return new Promise((resolve, reject) => { - execFileFn( - bin, - args, - { - cwd, - env: { ...env, CI: "1", NO_COLOR: "1", GH_NO_UPDATE_NOTIFIER: "1" }, - maxBuffer: 10 * 1024 * 1024, - }, - (err, stdout, stderr) => { - const output = combineOutput(stdout ?? "", stderr ?? ""); - if (err) { - reject(Object.assign(err, { stderr: stderr ?? "", stdout: stdout ?? "", output })); - return; - } - resolve(combineIO ? output : stdout); - } - ); - }); -} - -function parseVersionFromOutput(output: string): string { - const trimmed = String(output ?? "").trim(); - if (!trimmed) return ""; - const match = trimmed.match(/gh(?:-aw| aw) version ([^\r\n]+)/i); - return match?.[1]?.trim() ?? ""; -} - -function isMissingGh(error: unknown): boolean { - const e = error as ExecError | undefined; - return e?.code === "ENOENT" && e?.syscall === "spawn" && e?.path === "gh"; -} - -function isMissingGhAwExtension(error: unknown): boolean { - const e = error as ExecError | undefined; - const output = String(e?.output ?? e?.stderr ?? e?.message ?? ""); - return /extension not found:\s*aw/i.test(output) || /unknown command ["']aw["'] for ["']gh["']/i.test(output); -} - -async function findDevBinary(cwd: string, accessFn: AccessLike = access, platform: NodeJS.Platform = process.platform): Promise { - const devBin = join(cwd, platform === "win32" ? "gh-aw.exe" : "gh-aw"); - try { - await accessFn(devBin, fsConstants.X_OK); - console.error(`${LOG} findDevBinary found: ${devBin}`); - return devBin; - } catch { - console.error(`${LOG} findDevBinary not found at ${devBin}, falling back to gh extension`); - return null; - } -} - -export function createGhAwRunner({ getWorkspacePath, accessFn = access, execFileFn = spawnExecFile, platform = process.platform, env = process.env, resolveBin }: RunnerOptions): (args: string[]) => Promise { - // Memoize per cwd so findDevBinary is called at most once per workspace path. - const binCache = new Map>(); - const _resolveBin = - resolveBin ?? - (() => { - const cwd = getWorkspacePath(); - if (!binCache.has(cwd)) { - binCache.set(cwd, findDevBinary(cwd, accessFn, platform)); - } - return binCache.get(cwd)!; - }); - - function runExec(bin: string, args: string[], cwd: string, options?: RunExecOptions): Promise { - return execp(bin, args, cwd, { ...options, execFileFn, env }); - } - - return async function runGhAw(args: string[]): Promise { - const cwd = getWorkspacePath(); - const devBin = await _resolveBin(); - if (devBin) { - console.error(`${LOG} runGhAw using dev-binary: ${devBin} args=${JSON.stringify(args)} cwd=${cwd}`); - return runExec(devBin, args, cwd); - } - console.error(`${LOG} runGhAw using gh extension args=${JSON.stringify(args)} cwd=${cwd}`); - return runExec("gh", ["aw", ...args], cwd); - }; -} - -export function createGhAwRunnerWithStatus(options: RunnerOptions): GhAwRunner { - // One shared per-cwd memoized resolver so findDevBinary is called at most once, - // even across concurrent runGhAw() calls and getStatus(). - const binCache = new Map>(); - const resolveBin = (): Promise => { - const cwd = options.getWorkspacePath(); - if (!binCache.has(cwd)) { - binCache.set(cwd, findDevBinary(cwd, options.accessFn ?? access, options.platform ?? process.platform)); - } - return binCache.get(cwd)!; - }; - - const runGhAw = createGhAwRunner({ ...options, resolveBin }) as GhAwRunner; - const getStatus = async (): Promise => { - const cwd = options.getWorkspacePath(); - const devBin = await resolveBin(); - - if (devBin) { - const output = await execp(devBin, ["version"], cwd, { - combineIO: true, - execFileFn: options.execFileFn ?? spawnExecFile, - env: options.env ?? process.env, - }); - const status: GhAwStatus = { - available: true, - source: "dev-binary", - version: parseVersionFromOutput(output) || "unknown", - command: `${devBin} version`, - installCommand: INSTALL_COMMAND, - }; - console.error(`${LOG} getStatus: available=${status.available} source=${status.source} version=${status.version} cwd=${cwd}`); - return status; - } - - try { - const output = await execp("gh", ["aw", "version"], cwd, { - combineIO: true, - execFileFn: options.execFileFn ?? spawnExecFile, - env: options.env ?? process.env, - }); - const status: GhAwStatus = { - available: true, - source: "gh-extension", - version: parseVersionFromOutput(output) || "unknown", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - }; - console.error(`${LOG} getStatus: available=${status.available} source=${status.source} version=${status.version} cwd=${cwd}`); - return status; - } catch (error) { - if (isMissingGh(error)) { - console.error(`${LOG} getStatus error: gh not found in PATH cwd=${cwd}`); - return { - available: false, - source: "gh-not-found", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - installUrl: GH_INSTALL_URL, - message: "Install the GitHub CLI to use this dashboard.", - }; - } - - if (isMissingGhAwExtension(error)) { - console.error(`${LOG} getStatus error: gh aw extension not installed cwd=${cwd}`); - return { - available: false, - source: "missing", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - message: "gh aw is not installed. Install the GitHub CLI extension to use the dashboard outside a local dev build.", - }; - } - - const e = error as ExecError | undefined; - const message = String(e?.output ?? e?.stderr ?? e?.message ?? "Failed to detect gh aw."); - console.error(`${LOG} getStatus error: ${message} cwd=${cwd}`); - return { - available: false, - source: "error", - version: "", - command: "gh aw version", - installCommand: INSTALL_COMMAND, - message, - }; - } - }; - - runGhAw.getStatus = getStatus; - return runGhAw; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/dashboard-config.ts b/.github/extensions/agentic-workflows-dashboard/src/dashboard-config.ts deleted file mode 100644 index ece9cb523af..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/dashboard-config.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const CACHE_TTL_MS = 60_000; -export const DEFAULT_LOG_TIMEOUT_MINUTES = 1; -export const DEFAULT_REPORT_WINDOW_ID = "7d" as const; -export const DEFAULT_RUN_COUNT = 100; -export const MAX_LOG_CONTINUATIONS = 6; - -export type ReportWindowID = "3d" | "7d" | "1mo"; - -export interface ReportWindow { - id: ReportWindowID; - label: string; - startDate: string; - days: number; -} - -export const REPORT_WINDOWS: Record = { - "3d": { id: "3d", label: "3 days", startDate: "-3d", days: 3 }, - "7d": { id: "7d", label: "7 days", startDate: "-1w", days: 7 }, - "1mo": { id: "1mo", label: "1 month", startDate: "-1mo", days: 30 }, -}; - -export function getReportWindow(windowId?: string | null): ReportWindow { - if (windowId && windowId in REPORT_WINDOWS) { - return REPORT_WINDOWS[windowId as ReportWindowID]; - } - return REPORT_WINDOWS[DEFAULT_REPORT_WINDOW_ID]; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/dashboard-data.ts b/.github/extensions/agentic-workflows-dashboard/src/dashboard-data.ts deleted file mode 100644 index 5074864d285..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/dashboard-data.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { CACHE_TTL_MS, DEFAULT_LOG_TIMEOUT_MINUTES, MAX_LOG_CONTINUATIONS, type ReportWindow } from "./dashboard-config.js"; -import { - buildLogsArgs, - continuationToLogsOptions, - hasFlag, - logsArgsToOptions, - logsCommandUsesJSON, - mergeRuns, - normalizeLogsCommandArgs, - normalizeLogsOptions, - parseGhAwArgs, - type LogsContinuation, - type LogsOptions, - type LogsOptionsInput, - type RunLike, -} from "./dashboard-logs.js"; -import type { AuditReport, ExperimentInfo, WorkflowDefinition } from "./models.js"; -import { applyForecastToUsageSummary, buildUsageSummary, forecastDaysForWindow, type ForecastWorkflow, type UsageRun, type UsageSummaryItem } from "./usage-forecast.js"; - -const LOG = "[dashboard-data]"; - -interface CacheEntry { - data: T; - expiresAt: number; -} - -interface LogsBatchResponse { - runs?: RunLike[]; - continuation?: LogsContinuation; - summary?: Record; -} - -interface ForecastResponse { - workflows?: ForecastWorkflow[]; -} - -interface LogsBatchResult { - firstBatch: LogsBatchResponse | null; - runs: RunLike[]; - summary: Record | null; - logsFetches: number; - partial: boolean; - continuation: LogsContinuation | null; -} - -interface LogsDataResult { - runs: RunLike[]; - summary: Record | null; - window: ReportWindow; - timeout: number; - logsFetches: number; - partial: boolean; - continuation: LogsContinuation | null; -} - -interface UsageResult { - items: UsageSummaryItem[]; - window: ReportWindow; - timeout: number; - logsFetches: number; - partial: boolean; - continuation: LogsContinuation | null; - total_runs: number; - forecast_history_days: number; -} - -interface ExecCommandOptions { - window?: string; - timeout?: number; -} - -interface ExecCommandResult { - command: string; - output: string; - error?: boolean; -} - -interface CommandError extends Error { - stderr?: string; -} - -type RunGhAw = (args: string[]) => Promise; - -function asError(value: unknown): Error { - if (value instanceof Error) { - return value; - } - return new Error(String(value)); -} - -/** - * Parse JSON from CLI output robustly. - * - * Some gh-aw commands emit a status line (e.g. "✓ Fetched 36 workflows") to - * stdout before the JSON payload. This helper tries a direct parse first and, - * on failure, locates the first `[` or `{` character and retries from there. - * A descriptive error with a raw-output snippet is thrown when parsing still - * fails, making silent "Unexpected end of JSON input" errors actionable. - */ -function parseJsonOutput(raw: string, context: string): unknown { - const trimmed = (raw ?? "").trim(); - if (!trimmed) { - console.error(`${LOG} parseJsonOutput: no output for context="${context}"`); - throw new Error(`${context}: command produced no output`); - } - try { - return JSON.parse(trimmed); - } catch { - const jsonStart = trimmed.search(/[{[]/); - if (jsonStart > 0) { - try { - return JSON.parse(trimmed.slice(jsonStart)); - } catch { - // fall through to the descriptive throw below - } - } - const snippet = trimmed.replace(/\s+/g, " ").slice(0, 200); - console.error(`${LOG} parseJsonOutput: JSON parse failed context="${context}" snippet=${snippet}`); - throw new Error(`${context}: failed to parse JSON (output: ${snippet})`); - } -} - -export function createDashboardDataAccess({ runGhAw, cacheTTL = CACHE_TTL_MS, logsOutputDir }: { runGhAw: RunGhAw; cacheTTL?: number; logsOutputDir?: string }) { - const cache = new Map>(); - - function getCached(key: string): T | null { - const entry = cache.get(key); - return entry && Date.now() < entry.expiresAt ? (entry.data as T) : null; - } - - function setCached(key: string, data: T): void { - cache.set(key, { data, expiresAt: Date.now() + cacheTTL }); - } - - async function getDefinitions(): Promise { - const hit = getCached("definitions"); - if (hit) { - console.error(`${LOG} getDefinitions: cache hit count=${hit.length}`); - return hit; - } - console.error(`${LOG} getDefinitions: fetching from CLI`); - try { - const raw = await runGhAw(["status", "--json"]); - const parsed = parseJsonOutput(raw, "gh aw status --json"); - const data = (Array.isArray(parsed) ? parsed : []) as WorkflowDefinition[]; - setCached("definitions", data); - console.error(`${LOG} getDefinitions: fetched count=${data.length}`); - return data; - } catch (err) { - console.error(`${LOG} getDefinitions error: ${asError(err).message}`); - throw err; - } - } - - async function getExperiments(): Promise { - const hit = getCached("experiments"); - if (hit) { - console.error(`${LOG} getExperiments: cache hit count=${hit.length}`); - return hit; - } - console.error(`${LOG} getExperiments: fetching from CLI`); - try { - const raw = await runGhAw(["experiments", "list", "--json"]); - const parsed = parseJsonOutput(raw, "gh aw experiments list --json"); - const experiments = (Array.isArray(parsed) ? parsed : []) as ExperimentInfo[]; - setCached("experiments", experiments); - console.error(`${LOG} getExperiments: fetched count=${experiments.length}`); - return experiments; - } catch (err) { - console.error(`${LOG} getExperiments error: ${asError(err).message}`); - throw err; - } - } - - async function fetchLogsBatches(initialOptions: LogsOptions, initialArgs: string[] | null = null): Promise { - let current: LogsOptions | null = initialOptions; - let logsFetches = 0; - let runs: RunLike[] = []; - let continuation: LogsContinuation | null = null; - let summary: Record | null = null; - let firstBatch: LogsBatchResponse | null = null; - - while (current && logsFetches < MAX_LOG_CONTINUATIONS) { - let batchArgs = logsFetches === 0 && initialArgs ? initialArgs : buildLogsArgs(current); - // Inject --output so all sessions for the same repo share one disk cache. - if (logsOutputDir && !hasFlag(batchArgs, "--output", "-o")) { - batchArgs = [...batchArgs, "--output", logsOutputDir]; - } - console.error(`${LOG} fetchLogsBatches: batch=${logsFetches + 1} args=${JSON.stringify(batchArgs)}`); - const raw = await runGhAw(batchArgs); - let data: LogsBatchResponse; - try { - data = parseJsonOutput(raw, `logs batch ${logsFetches + 1}`) as LogsBatchResponse; - } catch (error) { - console.error(`${LOG} fetchLogsBatches: parse error on batch ${logsFetches + 1}: ${asError(error).message}`); - throw asError(error); - } - - if (!firstBatch) { - firstBatch = data; - } - - const newRuns = Array.isArray(data.runs) ? data.runs : []; - runs = mergeRuns(runs, newRuns); - continuation = data.continuation ?? null; - summary = data.summary ?? summary; - logsFetches += 1; - console.error(`${LOG} fetchLogsBatches: batch=${logsFetches} newRuns=${newRuns.length} totalRuns=${runs.length} hasContinuation=${Boolean(continuation)}`); - - if (!continuation) { - break; - } - - current = continuationToLogsOptions(continuation, current); - } - - return { - firstBatch, - runs, - summary, - logsFetches, - partial: Boolean(continuation), - continuation, - }; - } - - async function getLogsData(options: LogsOptionsInput = {}): Promise { - const normalized = normalizeLogsOptions(options); - const key = `logs:${JSON.stringify({ - window: normalized.window.id, - count: normalized.count, - timeout: normalized.timeout, - startDate: normalized.startDate, - endDate: normalized.endDate, - beforeRunID: normalized.beforeRunID, - afterRunID: normalized.afterRunID, - workflowName: normalized.workflowName, - engine: normalized.engine, - branch: normalized.branch, - artifacts: normalized.artifacts, - })}`; - const hit = getCached(key); - if (hit) { - console.error(`${LOG} getLogsData: cache hit runs=${hit.runs.length} window=${hit.window.id}`); - return hit; - } - - console.error(`${LOG} getLogsData: fetching window=${normalized.window.id} count=${normalized.count} timeout=${normalized.timeout}`); - const logsResult = await fetchLogsBatches(normalized); - console.error(`${LOG} getLogsData: fetched runs=${logsResult.runs.length} fetches=${logsResult.logsFetches} partial=${logsResult.partial}`); - - const result: LogsDataResult = { - runs: logsResult.runs, - summary: logsResult.summary, - window: normalized.window, - timeout: normalized.timeout, - logsFetches: logsResult.logsFetches, - partial: logsResult.partial, - continuation: logsResult.continuation, - }; - setCached(key, result); - return result; - } - - async function getForecastData(workflowIDs: string[], window: ReportWindow, timeout: number): Promise { - if (workflowIDs.length === 0) { - return []; - } - - const args = ["forecast", "--json", "--period", "month", "--days", String(forecastDaysForWindow(window)), "--timeout", String(timeout), ...workflowIDs]; - console.error(`${LOG} getForecastData: workflowIDs=${workflowIDs.length} window=${window.id} days=${forecastDaysForWindow(window)}`); - try { - const raw = await runGhAw(args); - const data = parseJsonOutput(raw, "gh aw forecast --json") as ForecastResponse; - const workflows = Array.isArray(data.workflows) ? data.workflows : []; - console.error(`${LOG} getForecastData: fetched workflows=${workflows.length}`); - return workflows; - } catch (err) { - console.error(`${LOG} getForecastData error: ${asError(err).message}`); - throw err; - } - } - - async function getRuns(options: LogsOptionsInput = {}): Promise { - return getLogsData(options); - } - - async function getUsage(options: LogsOptionsInput = {}): Promise { - const normalized = normalizeLogsOptions(options); - const key = `usage:${JSON.stringify({ - window: normalized.window.id, - count: normalized.count, - timeout: normalized.timeout, - })}`; - const hit = getCached(key); - if (hit) return hit; - - const logsData = await getLogsData(normalized); - const usageItems = buildUsageSummary(logsData.runs as UsageRun[], logsData.window); - const workflowIDs = usageItems.map(item => item.workflow_id).filter(Boolean); - const forecastWorkflows = await getForecastData(workflowIDs, logsData.window, logsData.timeout); - const result: UsageResult = { - items: applyForecastToUsageSummary(usageItems, forecastWorkflows), - window: logsData.window, - timeout: logsData.timeout, - logsFetches: logsData.logsFetches, - partial: logsData.partial, - continuation: logsData.continuation, - total_runs: logsData.runs.length, - forecast_history_days: forecastDaysForWindow(logsData.window), - }; - setCached(key, result); - return result; - } - - async function execCommand(rawCmd: string, options: ExecCommandOptions = {}): Promise { - console.error(`${LOG} execCommand: cmd="${rawCmd}"`); - const args = parseGhAwArgs(rawCmd); - if (!args) { - console.error(`${LOG} execCommand: rejected unsupported command "${rawCmd}"`); - return { command: rawCmd, output: "Only 'gh aw ' commands are supported.", error: true }; - } - - try { - if (args[0] === "logs" && logsCommandUsesJSON(args)) { - const commandArgs = normalizeLogsCommandArgs(args, options.window, options.timeout ?? DEFAULT_LOG_TIMEOUT_MINUTES); - const fallback: LogsOptionsInput = {}; - if (options.window) { - fallback.window = options.window; - } - if (options.timeout != null) { - fallback.timeout = options.timeout; - } - const logsOptions = logsArgsToOptions(commandArgs, fallback); - const logsResult = await fetchLogsBatches(logsOptions, commandArgs); - - return { - command: `gh aw ${commandArgs.join(" ")}`, - output: JSON.stringify( - { - ...(logsResult.firstBatch ?? {}), - runs: logsResult.runs, - partial: logsResult.partial, - logs_fetches: logsResult.logsFetches, - continuation: logsResult.continuation, - }, - null, - 2 - ), - }; - } - - const output = await runGhAw(args); - return { command: rawCmd, output }; - } catch (err) { - const e = asError(err) as CommandError; - const msg = e.stderr || e.message || "Unknown error"; - console.error(`${LOG} execCommand error cmd="${rawCmd}": ${msg}`); - return { command: rawCmd, output: msg, error: true }; - } - } - - async function getAudit(runId: string | number): Promise { - if (!runId) return null; - const key = `audit:${runId}`; - const hit = getCached(key); - if (hit) { - console.error(`${LOG} getAudit: cache hit runId=${runId}`); - return hit; - } - console.error(`${LOG} getAudit: fetching runId=${runId}`); - try { - const auditArgs: string[] = ["audit", String(runId), "--json"]; - if (logsOutputDir) { - auditArgs.push("--output", logsOutputDir); - } - const raw = await runGhAw(auditArgs); - const data = parseJsonOutput(raw, `gh aw audit ${runId} --json`) as AuditReport; - setCached(key, data); - console.error(`${LOG} getAudit: fetched runId=${runId}`); - return data; - } catch (err) { - console.error(`${LOG} getAudit error runId=${runId}: ${asError(err).message}`); - throw err; - } - } - - return { - clearCache: () => cache.clear(), - execCommand, - getAudit, - getDefinitions, - getExperiments, - getRuns, - getUsage, - }; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/dashboard-logs.ts b/.github/extensions/agentic-workflows-dashboard/src/dashboard-logs.ts deleted file mode 100644 index 0b10226dc20..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/dashboard-logs.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { DEFAULT_LOG_TIMEOUT_MINUTES, DEFAULT_RUN_COUNT, getReportWindow, type ReportWindow, type ReportWindowID } from "./dashboard-config.js"; - -export interface RunLike { - run_id?: number | string | null; - [key: string]: unknown; -} - -export interface LogsOptionsInput { - window?: ReportWindow | ReportWindowID | string | undefined; - count?: number | string | undefined; - timeout?: number | string | undefined; - startDate?: string | undefined; - endDate?: string | undefined; - beforeRunID?: number | string | undefined; - afterRunID?: number | string | undefined; - workflowName?: string | undefined; - engine?: string | undefined; - branch?: string | undefined; - artifacts?: string[] | undefined; -} - -export interface LogsOptions { - window: ReportWindow; - count: number; - timeout: number; - startDate: string; - endDate: string; - beforeRunID: number; - afterRunID: number; - workflowName: string; - engine: string; - branch: string; - artifacts: string[]; -} - -export interface LogsContinuation { - workflow_name?: string; - count?: number | string; - start_date?: string; - end_date?: string; - engine?: string; - branch?: string; - after_run_id?: number | string; - before_run_id?: number | string; - timeout?: number | string; -} - -function parsePositiveInt(value: unknown, fallback: number): number { - const numeric = Number.parseInt(String(value ?? fallback), 10); - return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback; -} - -function readFlagValue(args: string[], index: number, arg: string): { value: string; nextIndex: number } { - const equalsIndex = arg.indexOf("="); - if (equalsIndex >= 0) { - return { value: arg.slice(equalsIndex + 1), nextIndex: index }; - } - return { value: args[index + 1] ?? "", nextIndex: index + 1 }; -} - -export function normalizeLogsOptions(options: LogsOptionsInput = {}): LogsOptions { - const windowId = typeof options.window === "string" ? options.window : options.window?.id; - const window = getReportWindow(windowId); - const artifacts = Array.isArray(options.artifacts) && options.artifacts.length > 0 ? options.artifacts : ["usage"]; - - return { - window, - count: parsePositiveInt(options.count, DEFAULT_RUN_COUNT), - timeout: parsePositiveInt(options.timeout, DEFAULT_LOG_TIMEOUT_MINUTES), - startDate: typeof options.startDate === "string" && options.startDate.trim() ? options.startDate.trim() : window.startDate, - endDate: typeof options.endDate === "string" && options.endDate.trim() ? options.endDate.trim() : "", - beforeRunID: Number.isFinite(Number(options.beforeRunID)) && Number(options.beforeRunID) > 0 ? Number(options.beforeRunID) : 0, - afterRunID: Number.isFinite(Number(options.afterRunID)) && Number(options.afterRunID) > 0 ? Number(options.afterRunID) : 0, - workflowName: typeof options.workflowName === "string" ? options.workflowName.trim() : "", - engine: typeof options.engine === "string" ? options.engine.trim() : "", - branch: typeof options.branch === "string" ? options.branch.trim() : "", - artifacts, - }; -} - -export function buildLogsArgs(options: LogsOptions): string[] { - const args = ["logs", "--json", "-c", String(options.count), "--timeout", String(options.timeout)]; - - if (options.workflowName) args.push(options.workflowName); - if (options.startDate) args.push("--start-date", options.startDate); - if (options.endDate) args.push("--end-date", options.endDate); - if (options.engine) args.push("--engine", options.engine); - if (options.branch) args.push("--ref", options.branch); - if (options.beforeRunID > 0) args.push("--before-run-id", String(options.beforeRunID)); - if (options.afterRunID > 0) args.push("--after-run-id", String(options.afterRunID)); - if (options.artifacts.length > 0) args.push("--artifacts", options.artifacts.join(",")); - - return args; -} - -export function continuationToLogsOptions(continuation: LogsContinuation | null | undefined, fallback: LogsOptions): LogsOptions | null { - if (!continuation) return null; - - return normalizeLogsOptions({ - window: fallback.window.id, - workflowName: continuation.workflow_name || fallback.workflowName, - count: continuation.count || fallback.count, - startDate: continuation.start_date || fallback.startDate, - endDate: continuation.end_date || fallback.endDate, - engine: continuation.engine || fallback.engine, - branch: continuation.branch || fallback.branch, - afterRunID: continuation.after_run_id || fallback.afterRunID, - beforeRunID: continuation.before_run_id || fallback.beforeRunID, - timeout: continuation.timeout || fallback.timeout, - artifacts: fallback.artifacts, - }); -} - -export function mergeRuns(existingRuns: RunLike[], nextRuns: RunLike[]): RunLike[] { - const merged = new Map(existingRuns.map(run => [run.run_id, run])); - for (const run of nextRuns) { - if (run?.run_id != null) { - merged.set(run.run_id, run); - } - } - - return Array.from(merged.values()).sort((a, b) => Number(b.run_id ?? 0) - Number(a.run_id ?? 0)); -} - -export function parseGhAwArgs(raw: string): string[] | null { - const match = raw.trim().match(/^(?:gh\s+aw\s+)(.+)$/); - return match?.[1] ? match[1].trim().split(/\s+/) : null; -} - -export function hasFlag(args: string[], longFlag: string, shortFlag = ""): boolean { - return args.some(arg => { - if (arg.startsWith(`${longFlag}=`)) return true; - if (shortFlag && arg.startsWith(`${shortFlag}=`)) return true; - return arg === longFlag || (shortFlag !== "" && arg === shortFlag); - }); -} - -export function logsCommandUsesJSON(args: string[]): boolean { - return hasFlag(args, "--json", "-j"); -} - -export function normalizeLogsCommandArgs(args: string[], windowId: string | undefined, timeoutMinutes: number): string[] { - const nextArgs = [...args]; - if (!hasFlag(nextArgs, "--start-date") && !hasFlag(nextArgs, "--end-date") && !hasFlag(nextArgs, "--after-run-id") && !hasFlag(nextArgs, "--before-run-id")) { - nextArgs.push("--start-date", getReportWindow(windowId).startDate); - } - if (!hasFlag(nextArgs, "--timeout")) { - nextArgs.push("--timeout", String(timeoutMinutes)); - } - if (!hasFlag(nextArgs, "--artifacts")) { - nextArgs.push("--artifacts", "usage"); - } - return nextArgs; -} - -export function logsArgsToOptions(args: string[], fallback: LogsOptionsInput = {}): LogsOptions { - const options: LogsOptionsInput = { - window: typeof fallback.window === "string" ? fallback.window : fallback.window?.id, - count: fallback.count, - timeout: fallback.timeout, - startDate: fallback.startDate, - endDate: fallback.endDate, - beforeRunID: fallback.beforeRunID, - afterRunID: fallback.afterRunID, - workflowName: fallback.workflowName, - engine: fallback.engine, - branch: fallback.branch, - artifacts: fallback.artifacts, - }; - - for (let index = 1; index < args.length; index += 1) { - const arg = args[index] ?? ""; - - if (!arg.startsWith("-")) { - if (!options.workflowName) { - options.workflowName = arg; - } - continue; - } - - if (arg === "--json" || arg === "-j") { - continue; - } - - if (arg === "-c" || arg.startsWith("-c=") || arg === "--count" || arg.startsWith("--count=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.count = value; - index = nextIndex; - continue; - } - - if (arg === "--timeout" || arg.startsWith("--timeout=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.timeout = value; - index = nextIndex; - continue; - } - - if (arg === "--start-date" || arg.startsWith("--start-date=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.startDate = value; - index = nextIndex; - continue; - } - - if (arg === "--end-date" || arg.startsWith("--end-date=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.endDate = value; - index = nextIndex; - continue; - } - - if (arg === "--before-run-id" || arg.startsWith("--before-run-id=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.beforeRunID = value; - index = nextIndex; - continue; - } - - if (arg === "--after-run-id" || arg.startsWith("--after-run-id=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.afterRunID = value; - index = nextIndex; - continue; - } - - if (arg === "--engine" || arg.startsWith("--engine=") || arg === "-e" || arg.startsWith("-e=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.engine = value; - index = nextIndex; - continue; - } - - if (arg === "--ref" || arg.startsWith("--ref=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.branch = value; - index = nextIndex; - continue; - } - - if (arg === "--artifacts" || arg.startsWith("--artifacts=")) { - const { value, nextIndex } = readFlagValue(args, index, arg); - options.artifacts = value - .split(",") - .map(item => item.trim()) - .filter(Boolean); - index = nextIndex; - } - } - - return normalizeLogsOptions(options); -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/index.ts b/.github/extensions/agentic-workflows-dashboard/src/index.ts deleted file mode 100644 index e8c514177a5..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { createGhAwRunnerWithStatus } from "./dashboard-cli.js"; -export { DEFAULT_LOG_TIMEOUT_MINUTES, DEFAULT_RUN_COUNT } from "./dashboard-config.js"; -export { createDashboardDataAccess } from "./dashboard-data.js"; diff --git a/.github/extensions/agentic-workflows-dashboard/src/models.ts b/.github/extensions/agentic-workflows-dashboard/src/models.ts deleted file mode 100644 index 4eeb170f6f7..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/models.ts +++ /dev/null @@ -1,74 +0,0 @@ -export interface WorkflowDefinition { - workflow: string; - engine_id?: string; - compiled?: string; - labels?: string[]; - status?: string; - time_remaining?: string; -} - -export interface WorkflowRun { - run_id: number; - workflow_name: string; - status?: string; - conclusion?: string; - duration?: number; - aic?: number; - token_usage?: number; - turns?: number; - error_count?: number; - warning_count?: number; - missing_tool_count?: number; -} - -export interface PagedResult { - items: T[]; - page: number; - pageSize: number; - totalItems: number; - totalPages: number; - hasNextPage: boolean; - hasPreviousPage: boolean; -} - -export interface ExperimentInfo { - workflow_id: string; - branch: string; - experiments: number; - total_runs: number; - last_run: string; -} - -export interface UsageSummaryItem { - workflow_id: string; - workflow_name: string; - run_count: number; - total_aic: number; - cost_per_run: number; - daily_aic: number; - monthly_forecast_aic: number; - last_run_at?: string; -} - -export interface CLIStatus { - available: boolean; - source: string; - version: string; - command: string; - installCommand: string; - installUrl?: string; - message?: string; -} - -export interface AuditFinding { - severity?: string; - priority?: string; - title?: string; - description?: string; -} - -export interface AuditReport { - key_findings?: AuditFinding[]; - overview?: Record; - metrics?: Record; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/pagination.ts b/.github/extensions/agentic-workflows-dashboard/src/pagination.ts deleted file mode 100644 index b6a1827fc15..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/pagination.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { PagedResult } from "./models.js"; - -export function paginate(items: T[], page: number, pageSize: number): PagedResult { - const totalItems = items.length; - const totalPages = Math.max(1, Math.ceil(totalItems / pageSize)); - const safePage = Math.min(Math.max(1, page), totalPages); - const start = (safePage - 1) * pageSize; - const end = start + pageSize; - - return { - items: items.slice(start, end), - page: safePage, - pageSize, - totalItems, - totalPages, - hasNextPage: safePage < totalPages, - hasPreviousPage: safePage > 1, - }; -} diff --git a/.github/extensions/agentic-workflows-dashboard/src/usage-forecast.ts b/.github/extensions/agentic-workflows-dashboard/src/usage-forecast.ts deleted file mode 100644 index 537cdea8b4b..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/src/usage-forecast.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { basename } from "node:path"; - -export interface ForecastMonthlyMonteCarlo { - p50_projected_aic?: number; -} - -export interface ForecastWorkflow { - workflow_id?: string; - workflow_path?: string; - monthly_monte_carlo?: ForecastMonthlyMonteCarlo; - monthly_projected_aic?: number; -} - -export interface UsageSummaryItem { - workflow_id: string; - workflow_name: string; - workflow_path: string; - run_count: number; - total_aic: number; - cost_per_run: number; - daily_aic: number; - monthly_forecast_aic: number; - last_run_at: string; -} - -export interface UsageWindow { - id?: string; - days?: number; -} - -export interface UsageRun { - workflow_name?: string; - workflow_path?: string; - aic?: number; - created_at?: string; -} - -export function toNumber(value: unknown): number { - const numeric = Number(value ?? 0); - return Number.isFinite(numeric) ? numeric : 0; -} - -export function normalizeWorkflowID(value: unknown): string { - const raw = String(value ?? "").trim(); - if (!raw) return ""; - - let name = basename(raw); - const lowerName = name.toLowerCase(); - for (const suffix of [".lock.yml", ".yml", ".yaml", ".md"]) { - if (lowerName.endsWith(suffix)) { - name = name.slice(0, -suffix.length); - break; - } - } - return name.trim(); -} - -export function forecastDaysForWindow(window?: UsageWindow | null): number { - return window?.id === "1mo" ? 30 : 7; -} - -export function getForecastMonthlyAIC(forecast?: ForecastWorkflow | null): number { - if (!forecast || typeof forecast !== "object") return 0; - const monteCarloP50 = toNumber(forecast.monthly_monte_carlo?.p50_projected_aic); - if (monteCarloP50 > 0) return monteCarloP50; - return toNumber(forecast.monthly_projected_aic); -} - -export function applyForecastToUsageSummary(items: UsageSummaryItem[], forecastWorkflows: ForecastWorkflow[] = []): UsageSummaryItem[] { - const forecastEntries = forecastWorkflows.map(forecast => [normalizeWorkflowID(forecast?.workflow_id || forecast?.workflow_path), getForecastMonthlyAIC(forecast)] as const).filter(([workflowID]) => Boolean(workflowID)); - const forecastByWorkflow = new Map(forecastEntries); - - return items.map(item => ({ - ...item, - monthly_forecast_aic: forecastByWorkflow.get(item.workflow_id) ?? 0, - })); -} - -export function buildUsageSummary(runs: UsageRun[], window: UsageWindow, forecastWorkflows: ForecastWorkflow[] = []): UsageSummaryItem[] { - const usageByWorkflow = new Map(); - const effectiveDays = Number(window?.days ?? 0); - if (!Number.isFinite(effectiveDays) || effectiveDays <= 0) { - throw new Error(`report window '${window?.id ?? "unknown"}' is missing a valid positive day count.`); - } - - for (const run of runs) { - const workflowPath = typeof run?.workflow_path === "string" ? run.workflow_path.trim() : ""; - const workflowID = normalizeWorkflowID(workflowPath || run?.workflow_name); - if (!workflowID) continue; - - const workflowName = String(run?.workflow_name ?? workflowID).trim() || workflowID; - const aic = toNumber(run?.aic); - const entry: UsageSummaryItem = usageByWorkflow.get(workflowID) ?? { - workflow_id: workflowID, - workflow_name: workflowName, - workflow_path: workflowPath, - run_count: 0, - total_aic: 0, - cost_per_run: 0, - daily_aic: 0, - monthly_forecast_aic: 0, - last_run_at: "", - }; - - entry.run_count += 1; - entry.total_aic += aic; - if (!entry.workflow_path && workflowPath) { - entry.workflow_path = workflowPath; - } - if (!entry.workflow_name && workflowName) { - entry.workflow_name = workflowName; - } - - const createdAt = typeof run?.created_at === "string" ? run.created_at : ""; - if (createdAt && (!entry.last_run_at || createdAt > entry.last_run_at)) { - entry.last_run_at = createdAt; - } - - usageByWorkflow.set(workflowID, entry); - } - - const items = Array.from(usageByWorkflow.values()) - .map(entry => { - const costPerRun = entry.run_count > 0 ? entry.total_aic / entry.run_count : 0; - const dailyAIC = entry.total_aic / effectiveDays; - return { - ...entry, - cost_per_run: costPerRun, - daily_aic: dailyAIC, - monthly_forecast_aic: 0, - }; - }) - .sort((a, b) => { - const dailyDelta = b.daily_aic - a.daily_aic; - if (dailyDelta !== 0) return dailyDelta; - return b.cost_per_run - a.cost_per_run; - }); - - return applyForecastToUsageSummary(items, forecastWorkflows); -} diff --git a/.github/extensions/agentic-workflows-dashboard/test/dashboard-cli.test.ts b/.github/extensions/agentic-workflows-dashboard/test/dashboard-cli.test.ts deleted file mode 100644 index 6577a0dcaed..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/test/dashboard-cli.test.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; - -import { createGhAwRunner, createGhAwRunnerWithStatus } from "../src/dashboard-cli.js"; - -describe("dashboard cli runner", () => { - it("detects gh aw version from the extension and sets CI=1", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - expect(bin).toBe("gh"); - expect(args).toEqual(["aw", "version"]); - expect(options.env.CI).toBe("1"); - callback(null, "", "gh aw version v1.2.3\n"); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - env: { PATH: "/bin" }, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: true, - source: "gh-extension", - version: "v1.2.3", - command: "gh aw version", - }); - }); - - it("returns install instructions when the gh aw extension is missing", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(Object.assign(new Error("missing"), { code: 1 }), "", "extension not found: aw"); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: false, - source: "missing", - command: "gh aw version", - installCommand: "gh extension install github/gh-aw", - }); - }); - - it("returns gh install instructions when gh itself is not found", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(Object.assign(new Error("spawn gh ENOENT"), { code: "ENOENT", syscall: "spawn", path: "gh" }), "", ""); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: false, - source: "gh-not-found", - command: "gh aw version", - message: "Install the GitHub CLI to use this dashboard.", - installUrl: "https://cli.github.com", - installCommand: "gh extension install github/gh-aw", - }); - }); - - it("calls findDevBinary only once across multiple runGhAw() invocations", async () => { - const accessFn = vi.fn(async () => { - throw new Error("missing"); - }); - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(null, `["result"]`, ""); - }); - - const runGhAw = createGhAwRunner({ - getWorkspacePath: () => "/workspace", - accessFn, - execFileFn, - }); - - await runGhAw(["status", "--json"]); - await runGhAw(["status", "--json"]); - await runGhAw(["experiments", "list", "--json"]); - - // accessFn (i.e. findDevBinary) should be called exactly once regardless of how many runGhAw calls are made - expect(accessFn).toHaveBeenCalledTimes(1); - }); - - it("shares the binary resolution between runGhAw() and getStatus()", async () => { - const accessFn = vi.fn(async () => { - throw new Error("missing"); - }); - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(null, "", "gh aw version v2.0.0"); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn, - execFileFn, - }); - - // Trigger both paths that previously called findDevBinary independently - await runGhAw.getStatus(); - await runGhAw.getStatus(); - - // accessFn should be called exactly once total - expect(accessFn).toHaveBeenCalledTimes(1); - }); -}); - -describe("dashboard cli runner", () => { - it("detects gh aw version from the extension and sets CI=1", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - expect(bin).toBe("gh"); - expect(args).toEqual(["aw", "version"]); - expect(options.env.CI).toBe("1"); - callback(null, "", "gh aw version v1.2.3\n"); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - env: { PATH: "/bin" }, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: true, - source: "gh-extension", - version: "v1.2.3", - command: "gh aw version", - }); - }); - - it("returns install instructions when the gh aw extension is missing", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(Object.assign(new Error("missing"), { code: 1 }), "", "extension not found: aw"); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: false, - source: "missing", - command: "gh aw version", - installCommand: "gh extension install github/gh-aw", - }); - }); - - it("returns gh install instructions when gh itself is not found", async () => { - const execFileFn = vi.fn((bin, args, options, callback) => { - callback(Object.assign(new Error("spawn gh ENOENT"), { code: "ENOENT", syscall: "spawn", path: "gh" }), "", ""); - }); - - const runGhAw = createGhAwRunnerWithStatus({ - getWorkspacePath: () => "/workspace", - accessFn: vi.fn(async () => { - throw new Error("missing"); - }), - execFileFn, - }); - - await expect(runGhAw.getStatus()).resolves.toMatchObject({ - available: false, - source: "gh-not-found", - command: "gh aw version", - message: "Install the GitHub CLI to use this dashboard.", - installUrl: "https://cli.github.com", - installCommand: "gh extension install github/gh-aw", - }); - }); -}); diff --git a/.github/extensions/agentic-workflows-dashboard/test/dashboard-data.test.ts b/.github/extensions/agentic-workflows-dashboard/test/dashboard-data.test.ts deleted file mode 100644 index d1892b1e1d9..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/test/dashboard-data.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { createDashboardDataAccess } from "../src/dashboard-data.js"; - -describe("dashboard data access", () => { - it("keeps logs command filters across continuation batches", async () => { - const calls: string[][] = []; - const dataAccess = createDashboardDataAccess({ - runGhAw: async args => { - calls.push(args); - if (calls.length === 1) { - return JSON.stringify({ - runs: [{ run_id: 100, workflow_name: "CI Doctor" }], - continuation: { before_run_id: 99 }, - }); - } - - return JSON.stringify({ - runs: [{ run_id: 99, workflow_name: "CI Doctor" }], - }); - }, - }); - - const result = await dataAccess.execCommand("gh aw logs ci-doctor --json -c 5 --engine claude", { window: "3d", timeout: 2 }); - const payload = JSON.parse(result.output); - - expect(calls).toEqual([ - ["logs", "ci-doctor", "--json", "-c", "5", "--engine", "claude", "--start-date", "-3d", "--timeout", "2", "--artifacts", "usage"], - ["logs", "--json", "-c", "5", "--timeout", "2", "ci-doctor", "--start-date", "-3d", "--engine", "claude", "--before-run-id", "99", "--artifacts", "usage"], - ]); - expect(payload.runs.map((run: { run_id: number }) => run.run_id)).toEqual([100, 99]); - expect(payload.logs_fetches).toBe(2); - expect(payload.partial).toBe(false); - }); - - it("passes timeout minutes through to forecast calls", async () => { - const calls: string[][] = []; - const dataAccess = createDashboardDataAccess({ - runGhAw: async args => { - calls.push(args); - if (args[0] === "logs") { - return JSON.stringify({ - runs: [{ run_id: 100, workflow_name: "CI Doctor", workflow_path: ".github/workflows/ci-doctor.lock.yml", aic: 12, created_at: "2026-06-29T12:00:00Z" }], - }); - } - - return JSON.stringify({ - workflows: [{ workflow_id: "ci-doctor", monthly_projected_aic: 44 }], - }); - }, - }); - - const usage = await dataAccess.getUsage({ window: "7d", timeout: 3 }); - - expect(calls[1]).toEqual(["forecast", "--json", "--period", "month", "--days", "7", "--timeout", "3", "ci-doctor"]); - expect(usage.items[0]?.monthly_forecast_aic).toBe(44); - }); - - it("passes --output to logs and audit commands when logsOutputDir is configured", async () => { - const calls: string[][] = []; - const dataAccess = createDashboardDataAccess({ - runGhAw: async args => { - calls.push(args); - if (args[0] === "logs") return JSON.stringify({ runs: [{ run_id: 100, workflow_name: "CI Doctor" }] }); - if (args[0] === "audit") return JSON.stringify({ overview: {}, metrics: {} }); - return "[]"; - }, - logsOutputDir: "/shared/logs/owner/repo", - }); - - await dataAccess.getRuns({ window: "7d", count: 5, timeout: 1 }); - await dataAccess.getAudit("100"); - - const logsCall = calls.find(a => a[0] === "logs"); - const auditCall = calls.find(a => a[0] === "audit"); - - expect(logsCall).toEqual(expect.arrayContaining(["--output", "/shared/logs/owner/repo"])); - expect(auditCall).toEqual(expect.arrayContaining(["--output", "/shared/logs/owner/repo"])); - }); - - it("does not inject --output when logsOutputDir is not configured", async () => { - const calls: string[][] = []; - const dataAccess = createDashboardDataAccess({ - runGhAw: async args => { - calls.push(args); - if (args[0] === "logs") return JSON.stringify({ runs: [{ run_id: 100 }] }); - return "[]"; - }, - }); - - await dataAccess.getRuns({ window: "7d", count: 5, timeout: 1 }); - - const logsCall = calls.find(a => a[0] === "logs"); - expect(logsCall).not.toEqual(expect.arrayContaining(["--output"])); - }); - - it("does not inject duplicate --output when one is already present in execCommand args", async () => { - const calls: string[][] = []; - const dataAccess = createDashboardDataAccess({ - logsOutputDir: "/shared/logs/owner/repo", - runGhAw: async args => { - calls.push(args); - if (args[0] === "logs") return JSON.stringify({ runs: [{ run_id: 200 }] }); - return "[]"; - }, - }); - - // When the caller already has --output in execCommand we should not add a second one. - // Simulate this by calling getRuns normally — the injected --output appears exactly once. - await dataAccess.getRuns({ window: "7d", count: 5, timeout: 1 }); - - const logsCall = calls.find(a => a[0] === "logs"); - const outputOccurrences = logsCall?.filter(a => a === "--output").length ?? 0; - expect(outputOccurrences).toBe(1); - }); - - it("parses gh aw status output that has a status line prefix before the JSON", async () => { - const dataAccess = createDashboardDataAccess({ - runGhAw: async args => { - if (args[0] === "status") { - // Simulate gh aw writing a status message to stdout before the JSON array - return `✓ Fetched 2 workflows\n[{"workflow":"ci-doctor"},{"workflow":"ab-advisor"}]`; - } - return "[]"; - }, - }); - - const defs = await dataAccess.getDefinitions(); - expect(defs).toHaveLength(2); - expect((defs[0] as { workflow: string }).workflow).toBe("ci-doctor"); - }); - - it("throws a descriptive error when gh aw status produces no output", async () => { - const dataAccess = createDashboardDataAccess({ - runGhAw: async () => "", - }); - - await expect(dataAccess.getDefinitions()).rejects.toThrow("command produced no output"); - }); - - it("throws a descriptive error including an output snippet when JSON is unparseable", async () => { - const dataAccess = createDashboardDataAccess({ - runGhAw: async () => "error: authentication required", - }); - - await expect(dataAccess.getDefinitions()).rejects.toThrow("failed to parse JSON"); - }); -}); diff --git a/.github/extensions/agentic-workflows-dashboard/test/dashboard-logs.test.ts b/.github/extensions/agentic-workflows-dashboard/test/dashboard-logs.test.ts deleted file mode 100644 index 49948013662..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/test/dashboard-logs.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { DEFAULT_LOG_TIMEOUT_MINUTES, REPORT_WINDOWS } from "../src/dashboard-config.js"; -import { buildLogsArgs, continuationToLogsOptions, logsArgsToOptions, normalizeLogsCommandArgs, normalizeLogsOptions } from "../src/dashboard-logs.js"; - -describe("dashboard logs helpers", () => { - it("defaults logs timeouts in minutes", () => { - const options = normalizeLogsOptions({}); - - expect(options.timeout).toBe(DEFAULT_LOG_TIMEOUT_MINUTES); - expect(buildLogsArgs(options)).toEqual(expect.arrayContaining(["--timeout", String(DEFAULT_LOG_TIMEOUT_MINUTES)])); - }); - - it("accepts a window object in normalizeLogsOptions without falling back to default", () => { - const windowObj = REPORT_WINDOWS["3d"]; - const options = normalizeLogsOptions({ window: windowObj }); - - expect(options.window.id).toBe("3d"); - expect(options.startDate).toBe(windowObj.startDate); - }); - - it("preserves fallback filters when continuing logs pages", () => { - const initial = normalizeLogsOptions({ window: "3d", count: 5, timeout: 2, engine: "claude", workflowName: "ci-doctor" }); - const next = continuationToLogsOptions({ before_run_id: 90 }, initial); - - expect(next).toMatchObject({ - count: 5, - timeout: 2, - engine: "claude", - workflowName: "ci-doctor", - beforeRunID: 90, - startDate: "-3d", - }); - }); - - it("parses logs command args into continuation-ready options", () => { - const options = logsArgsToOptions(["logs", "ci-doctor", "--json", "-c", "5", "--engine", "claude", "--timeout", "3", "--before-run-id", "99"], { window: "7d" }); - - expect(options).toMatchObject({ - workflowName: "ci-doctor", - count: 5, - engine: "claude", - timeout: 3, - beforeRunID: 99, - startDate: "-1w", - }); - }); - - it("injects the selected report window and minute timeout when missing", () => { - expect(normalizeLogsCommandArgs(["logs", "--json"], "3d", 4)).toEqual(["logs", "--json", "--start-date", "-3d", "--timeout", "4", "--artifacts", "usage"]); - }); -}); diff --git a/.github/extensions/agentic-workflows-dashboard/test/pagination.test.ts b/.github/extensions/agentic-workflows-dashboard/test/pagination.test.ts deleted file mode 100644 index 99bf5686866..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/test/pagination.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { paginate } from "../src/pagination.js"; - -describe("paginate", () => { - it("returns expected page metadata and items", () => { - const result = paginate([1, 2, 3, 4, 5], 2, 2); - expect(result.items).toEqual([3, 4]); - expect(result.page).toBe(2); - expect(result.totalPages).toBe(3); - expect(result.hasNextPage).toBe(true); - expect(result.hasPreviousPage).toBe(true); - }); - - it("clamps page to valid bounds", () => { - const result = paginate([1, 2, 3], 999, 2); - expect(result.page).toBe(2); - expect(result.items).toEqual([3]); - expect(result.hasNextPage).toBe(false); - }); -}); diff --git a/.github/extensions/agentic-workflows-dashboard/test/usage-forecast.test.ts b/.github/extensions/agentic-workflows-dashboard/test/usage-forecast.test.ts deleted file mode 100644 index 475f0fd33a3..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/test/usage-forecast.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, expect, it } from "vitest"; - -import { applyForecastToUsageSummary, buildUsageSummary, forecastDaysForWindow, getForecastMonthlyAIC, normalizeWorkflowID } from "../src/usage-forecast.js"; - -describe("usage forecast helpers", () => { - it("normalizes workflow ids from workflow paths", () => { - expect(normalizeWorkflowID(".github/workflows/ci-doctor.lock.yml")).toBe("ci-doctor"); - expect(normalizeWorkflowID("daily-planner.md")).toBe("daily-planner"); - }); - - it("maps dashboard windows onto supported forecast history windows", () => { - expect(forecastDaysForWindow({ id: "3d" })).toBe(7); - expect(forecastDaysForWindow({ id: "7d" })).toBe(7); - expect(forecastDaysForWindow({ id: "1mo" })).toBe(30); - }); - - it("prefers monte carlo p50 monthly forecast when available", () => { - expect(getForecastMonthlyAIC({ monthly_monte_carlo: { p50_projected_aic: 123.4 }, monthly_projected_aic: 100 })).toBe(123.4); - expect(getForecastMonthlyAIC({ monthly_projected_aic: 98.2 })).toBe(98.2); - }); - - it("merges logs usage with forecast command output by workflow id", () => { - const summary = applyForecastToUsageSummary( - buildUsageSummary( - [ - { - workflow_name: "CI Doctor", - workflow_path: ".github/workflows/ci-doctor.lock.yml", - aic: 10, - created_at: "2026-06-28T12:00:00Z", - }, - { - workflow_name: "CI Doctor", - workflow_path: ".github/workflows/ci-doctor.lock.yml", - aic: 5, - created_at: "2026-06-29T12:00:00Z", - }, - ], - { id: "7d", days: 7 } - ), - [ - { - workflow_id: "ci-doctor", - monthly_monte_carlo: { p50_projected_aic: 77.7 }, - }, - ] - ); - - expect(summary).toHaveLength(1); - expect(summary[0]).toMatchObject({ - workflow_id: "ci-doctor", - workflow_name: "CI Doctor", - run_count: 2, - total_aic: 15, - cost_per_run: 7.5, - daily_aic: 15 / 7, - monthly_forecast_aic: 77.7, - last_run_at: "2026-06-29T12:00:00Z", - }); - }); - - it("can build summary with forecast data in a single call", () => { - const summary = buildUsageSummary( - [ - { - workflow_name: "CI Doctor", - workflow_path: ".github/workflows/ci-doctor.lock.yml", - aic: 10, - created_at: "2026-06-28T12:00:00Z", - }, - { - workflow_name: "CI Doctor", - workflow_path: ".github/workflows/ci-doctor.lock.yml", - aic: 5, - created_at: "2026-06-29T12:00:00Z", - }, - ], - { id: "7d", days: 7 }, - [ - { - workflow_id: "ci-doctor", - monthly_monte_carlo: { p50_projected_aic: 77.7 }, - }, - ] - ); - - expect(summary).toHaveLength(1); - expect(summary[0]).toMatchObject({ - workflow_id: "ci-doctor", - workflow_name: "CI Doctor", - run_count: 2, - total_aic: 15, - cost_per_run: 7.5, - daily_aic: 15 / 7, - monthly_forecast_aic: 77.7, - last_run_at: "2026-06-29T12:00:00Z", - }); - }); -}); diff --git a/.github/extensions/agentic-workflows-dashboard/tsconfig.emit.json b/.github/extensions/agentic-workflows-dashboard/tsconfig.emit.json deleted file mode 100644 index 11e2a65ce95..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/tsconfig.emit.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": ".", - "rootDir": "src", - "noEmit": false, - "declaration": false - }, - "include": ["src/index.ts"], - "exclude": ["node_modules"] -} diff --git a/.github/extensions/agentic-workflows-dashboard/tsconfig.json b/.github/extensions/agentic-workflows-dashboard/tsconfig.json deleted file mode 100644 index 18e8198ca54..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "moduleResolution": "bundler", - "lib": ["ES2022", "DOM"], - "types": ["node"], - "strict": true, - "noImplicitAny": true, - "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, - "outDir": "./web", - "rootDir": ".", - "skipLibCheck": true - }, - "include": ["src/**/*.ts"] -} diff --git a/.github/extensions/agentic-workflows-dashboard/vitest.config.ts b/.github/extensions/agentic-workflows-dashboard/vitest.config.ts deleted file mode 100644 index 8b5840acac7..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/vitest.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - include: ["test/**/*.test.ts"], - }, -}); diff --git a/.github/extensions/agentic-workflows-dashboard/web/app.js b/.github/extensions/agentic-workflows-dashboard/web/app.js deleted file mode 100644 index 0a59ddfcc7b..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/web/app.js +++ /dev/null @@ -1,3740 +0,0 @@ -// node_modules/alpinejs/dist/module.esm.js -var flushPending = false; -var flushing = false; -var queue = []; -var lastFlushedIndex = -1; -function scheduler(callback) { - queueJob(callback); -} -function queueJob(job) { - if (!queue.includes(job)) - queue.push(job); - queueFlush(); -} -function dequeueJob(job) { - let index = queue.indexOf(job); - if (index !== -1 && index > lastFlushedIndex) - queue.splice(index, 1); -} -function queueFlush() { - if (!flushing && !flushPending) { - flushPending = true; - queueMicrotask(flushJobs); - } -} -function flushJobs() { - flushPending = false; - flushing = true; - for (let i = 0; i < queue.length; i++) { - queue[i](); - lastFlushedIndex = i; - } - queue.length = 0; - lastFlushedIndex = -1; - flushing = false; -} -var reactive; -var effect; -var release; -var raw; -var shouldSchedule = true; -function disableEffectScheduling(callback) { - shouldSchedule = false; - callback(); - shouldSchedule = true; -} -function setReactivityEngine(engine) { - reactive = engine.reactive; - release = engine.release; - effect = (callback) => engine.effect(callback, { scheduler: (task) => { - if (shouldSchedule) { - scheduler(task); - } else { - task(); - } - } }); - raw = engine.raw; -} -function overrideEffect(override) { - effect = override; -} -function elementBoundEffect(el) { - let cleanup2 = () => { - }; - let wrappedEffect = (callback) => { - let effectReference = effect(callback); - if (!el._x_effects) { - el._x_effects = /* @__PURE__ */ new Set(); - el._x_runEffects = () => { - el._x_effects.forEach((i) => i()); - }; - } - el._x_effects.add(effectReference); - cleanup2 = () => { - if (effectReference === void 0) - return; - el._x_effects.delete(effectReference); - release(effectReference); - }; - return effectReference; - }; - return [wrappedEffect, () => { - cleanup2(); - }]; -} -function watch(getter, callback) { - let firstTime = true; - let oldValue; - let effectReference = effect(() => { - let value = getter(); - JSON.stringify(value); - if (!firstTime) { - queueMicrotask(() => { - callback(value, oldValue); - oldValue = value; - }); - } else { - oldValue = value; - } - firstTime = false; - }); - return () => release(effectReference); -} -var onAttributeAddeds = []; -var onElRemoveds = []; -var onElAddeds = []; -function onElAdded(callback) { - onElAddeds.push(callback); -} -function onElRemoved(el, callback) { - if (typeof callback === "function") { - if (!el._x_cleanups) - el._x_cleanups = []; - el._x_cleanups.push(callback); - } else { - callback = el; - onElRemoveds.push(callback); - } -} -function onAttributesAdded(callback) { - onAttributeAddeds.push(callback); -} -function onAttributeRemoved(el, name, callback) { - if (!el._x_attributeCleanups) - el._x_attributeCleanups = {}; - if (!el._x_attributeCleanups[name]) - el._x_attributeCleanups[name] = []; - el._x_attributeCleanups[name].push(callback); -} -function cleanupAttributes(el, names) { - if (!el._x_attributeCleanups) - return; - Object.entries(el._x_attributeCleanups).forEach(([name, value]) => { - if (names === void 0 || names.includes(name)) { - value.forEach((i) => i()); - delete el._x_attributeCleanups[name]; - } - }); -} -function cleanupElement(el) { - el._x_effects?.forEach(dequeueJob); - while (el._x_cleanups?.length) - el._x_cleanups.pop()(); -} -var observer = new MutationObserver(onMutate); -var currentlyObserving = false; -function startObservingMutations() { - observer.observe(document, { subtree: true, childList: true, attributes: true, attributeOldValue: true }); - currentlyObserving = true; -} -function stopObservingMutations() { - flushObserver(); - observer.disconnect(); - currentlyObserving = false; -} -var queuedMutations = []; -function flushObserver() { - let records = observer.takeRecords(); - queuedMutations.push(() => records.length > 0 && onMutate(records)); - let queueLengthWhenTriggered = queuedMutations.length; - queueMicrotask(() => { - if (queuedMutations.length === queueLengthWhenTriggered) { - while (queuedMutations.length > 0) - queuedMutations.shift()(); - } - }); -} -function mutateDom(callback) { - if (!currentlyObserving) - return callback(); - stopObservingMutations(); - let result = callback(); - startObservingMutations(); - return result; -} -var isCollecting = false; -var deferredMutations = []; -function deferMutations() { - isCollecting = true; -} -function flushAndStopDeferringMutations() { - isCollecting = false; - onMutate(deferredMutations); - deferredMutations = []; -} -function onMutate(mutations) { - if (isCollecting) { - deferredMutations = deferredMutations.concat(mutations); - return; - } - let addedNodes = []; - let removedNodes = /* @__PURE__ */ new Set(); - let addedAttributes = /* @__PURE__ */ new Map(); - let removedAttributes = /* @__PURE__ */ new Map(); - for (let i = 0; i < mutations.length; i++) { - if (mutations[i].target._x_ignoreMutationObserver) - continue; - if (mutations[i].type === "childList") { - mutations[i].removedNodes.forEach((node) => { - if (node.nodeType !== 1) - return; - if (!node._x_marker) - return; - removedNodes.add(node); - }); - mutations[i].addedNodes.forEach((node) => { - if (node.nodeType !== 1) - return; - if (removedNodes.has(node)) { - removedNodes.delete(node); - return; - } - if (node._x_marker) - return; - addedNodes.push(node); - }); - } - if (mutations[i].type === "attributes") { - let el = mutations[i].target; - let name = mutations[i].attributeName; - let oldValue = mutations[i].oldValue; - let add2 = () => { - if (!addedAttributes.has(el)) - addedAttributes.set(el, []); - addedAttributes.get(el).push({ name, value: el.getAttribute(name) }); - }; - let remove = () => { - if (!removedAttributes.has(el)) - removedAttributes.set(el, []); - removedAttributes.get(el).push(name); - }; - if (el.hasAttribute(name) && oldValue === null) { - add2(); - } else if (el.hasAttribute(name)) { - remove(); - add2(); - } else { - remove(); - } - } - } - removedAttributes.forEach((attrs, el) => { - cleanupAttributes(el, attrs); - }); - addedAttributes.forEach((attrs, el) => { - onAttributeAddeds.forEach((i) => i(el, attrs)); - }); - for (let node of removedNodes) { - if (addedNodes.some((i) => i.contains(node))) - continue; - onElRemoveds.forEach((i) => i(node)); - } - for (let node of addedNodes) { - if (!node.isConnected) - continue; - onElAddeds.forEach((i) => i(node)); - } - addedNodes = null; - removedNodes = null; - addedAttributes = null; - removedAttributes = null; -} -function scope(node) { - return mergeProxies(closestDataStack(node)); -} -function addScopeToNode(node, data2, referenceNode) { - node._x_dataStack = [data2, ...closestDataStack(referenceNode || node)]; - return () => { - node._x_dataStack = node._x_dataStack.filter((i) => i !== data2); - }; -} -function closestDataStack(node) { - if (node._x_dataStack) - return node._x_dataStack; - if (typeof ShadowRoot === "function" && node instanceof ShadowRoot) { - return closestDataStack(node.host); - } - if (!node.parentNode) { - return []; - } - return closestDataStack(node.parentNode); -} -function mergeProxies(objects) { - return new Proxy({ objects }, mergeProxyTrap); -} -var mergeProxyTrap = { - ownKeys({ objects }) { - return Array.from( - new Set(objects.flatMap((i) => Object.keys(i))) - ); - }, - has({ objects }, name) { - if (name == Symbol.unscopables) - return false; - return objects.some( - (obj) => Object.prototype.hasOwnProperty.call(obj, name) || Reflect.has(obj, name) - ); - }, - get({ objects }, name, thisProxy) { - if (name == "toJSON") - return collapseProxies; - return Reflect.get( - objects.find( - (obj) => Reflect.has(obj, name) - ) || {}, - name, - thisProxy - ); - }, - set({ objects }, name, value, thisProxy) { - const target = objects.find( - (obj) => Object.prototype.hasOwnProperty.call(obj, name) - ) || objects[objects.length - 1]; - const descriptor = Object.getOwnPropertyDescriptor(target, name); - if (descriptor?.set && descriptor?.get) - return descriptor.set.call(thisProxy, value) || true; - return Reflect.set(target, name, value); - } -}; -function collapseProxies() { - let keys = Reflect.ownKeys(this); - return keys.reduce((acc, key) => { - acc[key] = Reflect.get(this, key); - return acc; - }, {}); -} -function initInterceptors(data2) { - let isObject2 = (val) => typeof val === "object" && !Array.isArray(val) && val !== null; - let recurse = (obj, basePath = "") => { - Object.entries(Object.getOwnPropertyDescriptors(obj)).forEach(([key, { value, enumerable }]) => { - if (enumerable === false || value === void 0) - return; - if (typeof value === "object" && value !== null && value.__v_skip) - return; - let path = basePath === "" ? key : `${basePath}.${key}`; - if (typeof value === "object" && value !== null && value._x_interceptor) { - obj[key] = value.initialize(data2, path, key); - } else { - if (isObject2(value) && value !== obj && !(value instanceof Element)) { - recurse(value, path); - } - } - }); - }; - return recurse(data2); -} -function interceptor(callback, mutateObj = () => { -}) { - let obj = { - initialValue: void 0, - _x_interceptor: true, - initialize(data2, path, key) { - return callback(this.initialValue, () => get(data2, path), (value) => set(data2, path, value), path, key); - } - }; - mutateObj(obj); - return (initialValue) => { - if (typeof initialValue === "object" && initialValue !== null && initialValue._x_interceptor) { - let initialize = obj.initialize.bind(obj); - obj.initialize = (data2, path, key) => { - let innerValue = initialValue.initialize(data2, path, key); - obj.initialValue = innerValue; - return initialize(data2, path, key); - }; - } else { - obj.initialValue = initialValue; - } - return obj; - }; -} -function get(obj, path) { - return path.split(".").reduce((carry, segment) => carry[segment], obj); -} -function set(obj, path, value) { - if (typeof path === "string") - path = path.split("."); - if (path.length === 1) - obj[path[0]] = value; - else if (path.length === 0) - throw error; - else { - if (obj[path[0]]) - return set(obj[path[0]], path.slice(1), value); - else { - obj[path[0]] = {}; - return set(obj[path[0]], path.slice(1), value); - } - } -} -var magics = {}; -function magic(name, callback) { - magics[name] = callback; -} -function injectMagics(obj, el) { - let memoizedUtilities = getUtilities(el); - Object.entries(magics).forEach(([name, callback]) => { - Object.defineProperty(obj, `$${name}`, { - get() { - return callback(el, memoizedUtilities); - }, - enumerable: false - }); - }); - return obj; -} -function getUtilities(el) { - let [utilities, cleanup2] = getElementBoundUtilities(el); - let utils = { interceptor, ...utilities }; - onElRemoved(el, cleanup2); - return utils; -} -function tryCatch(el, expression, callback, ...args) { - try { - return callback(...args); - } catch (e) { - handleError(e, el, expression); - } -} -function handleError(error2, el, expression = void 0) { - error2 = Object.assign( - error2 ?? { message: "No error message given." }, - { el, expression } - ); - console.warn(`Alpine Expression Error: ${error2.message} - -${expression ? 'Expression: "' + expression + '"\n\n' : ""}`, el); - setTimeout(() => { - throw error2; - }, 0); -} -var shouldAutoEvaluateFunctions = true; -function dontAutoEvaluateFunctions(callback) { - let cache = shouldAutoEvaluateFunctions; - shouldAutoEvaluateFunctions = false; - let result = callback(); - shouldAutoEvaluateFunctions = cache; - return result; -} -function evaluate(el, expression, extras = {}) { - let result; - evaluateLater(el, expression)((value) => result = value, extras); - return result; -} -function evaluateLater(...args) { - return theEvaluatorFunction(...args); -} -var theEvaluatorFunction = normalEvaluator; -function setEvaluator(newEvaluator) { - theEvaluatorFunction = newEvaluator; -} -function normalEvaluator(el, expression) { - let overriddenMagics = {}; - injectMagics(overriddenMagics, el); - let dataStack = [overriddenMagics, ...closestDataStack(el)]; - let evaluator = typeof expression === "function" ? generateEvaluatorFromFunction(dataStack, expression) : generateEvaluatorFromString(dataStack, expression, el); - return tryCatch.bind(null, el, expression, evaluator); -} -function generateEvaluatorFromFunction(dataStack, func) { - return (receiver = () => { - }, { scope: scope2 = {}, params = [], context } = {}) => { - let result = func.apply(mergeProxies([scope2, ...dataStack]), params); - runIfTypeOfFunction(receiver, result); - }; -} -var evaluatorMemo = {}; -function generateFunctionFromString(expression, el) { - if (evaluatorMemo[expression]) { - return evaluatorMemo[expression]; - } - let AsyncFunction = Object.getPrototypeOf(async function() { - }).constructor; - let rightSideSafeExpression = /^[\n\s]*if.*\(.*\)/.test(expression.trim()) || /^(let|const)\s/.test(expression.trim()) ? `(async()=>{ ${expression} })()` : expression; - const safeAsyncFunction = () => { - try { - let func2 = new AsyncFunction( - ["__self", "scope"], - `with (scope) { __self.result = ${rightSideSafeExpression} }; __self.finished = true; return __self.result;` - ); - Object.defineProperty(func2, "name", { - value: `[Alpine] ${expression}` - }); - return func2; - } catch (error2) { - handleError(error2, el, expression); - return Promise.resolve(); - } - }; - let func = safeAsyncFunction(); - evaluatorMemo[expression] = func; - return func; -} -function generateEvaluatorFromString(dataStack, expression, el) { - let func = generateFunctionFromString(expression, el); - return (receiver = () => { - }, { scope: scope2 = {}, params = [], context } = {}) => { - func.result = void 0; - func.finished = false; - let completeScope = mergeProxies([scope2, ...dataStack]); - if (typeof func === "function") { - let promise = func.call(context, func, completeScope).catch((error2) => handleError(error2, el, expression)); - if (func.finished) { - runIfTypeOfFunction(receiver, func.result, completeScope, params, el); - func.result = void 0; - } else { - promise.then((result) => { - runIfTypeOfFunction(receiver, result, completeScope, params, el); - }).catch((error2) => handleError(error2, el, expression)).finally(() => func.result = void 0); - } - } - }; -} -function runIfTypeOfFunction(receiver, value, scope2, params, el) { - if (shouldAutoEvaluateFunctions && typeof value === "function") { - let result = value.apply(scope2, params); - if (result instanceof Promise) { - result.then((i) => runIfTypeOfFunction(receiver, i, scope2, params)).catch((error2) => handleError(error2, el, value)); - } else { - receiver(result); - } - } else if (typeof value === "object" && value instanceof Promise) { - value.then((i) => receiver(i)); - } else { - receiver(value); - } -} -var prefixAsString = "x-"; -function prefix(subject = "") { - return prefixAsString + subject; -} -function setPrefix(newPrefix) { - prefixAsString = newPrefix; -} -var directiveHandlers = {}; -function directive(name, callback) { - directiveHandlers[name] = callback; - return { - before(directive2) { - if (!directiveHandlers[directive2]) { - console.warn(String.raw`Cannot find directive \`${directive2}\`. \`${name}\` will use the default order of execution`); - return; - } - const pos = directiveOrder.indexOf(directive2); - directiveOrder.splice(pos >= 0 ? pos : directiveOrder.indexOf("DEFAULT"), 0, name); - } - }; -} -function directiveExists(name) { - return Object.keys(directiveHandlers).includes(name); -} -function directives(el, attributes, originalAttributeOverride) { - attributes = Array.from(attributes); - if (el._x_virtualDirectives) { - let vAttributes = Object.entries(el._x_virtualDirectives).map(([name, value]) => ({ name, value })); - let staticAttributes = attributesOnly(vAttributes); - vAttributes = vAttributes.map((attribute) => { - if (staticAttributes.find((attr) => attr.name === attribute.name)) { - return { - name: `x-bind:${attribute.name}`, - value: `"${attribute.value}"` - }; - } - return attribute; - }); - attributes = attributes.concat(vAttributes); - } - let transformedAttributeMap = {}; - let directives2 = attributes.map(toTransformedAttributes((newName, oldName) => transformedAttributeMap[newName] = oldName)).filter(outNonAlpineAttributes).map(toParsedDirectives(transformedAttributeMap, originalAttributeOverride)).sort(byPriority); - return directives2.map((directive2) => { - return getDirectiveHandler(el, directive2); - }); -} -function attributesOnly(attributes) { - return Array.from(attributes).map(toTransformedAttributes()).filter((attr) => !outNonAlpineAttributes(attr)); -} -var isDeferringHandlers = false; -var directiveHandlerStacks = /* @__PURE__ */ new Map(); -var currentHandlerStackKey = /* @__PURE__ */ Symbol(); -function deferHandlingDirectives(callback) { - isDeferringHandlers = true; - let key = /* @__PURE__ */ Symbol(); - currentHandlerStackKey = key; - directiveHandlerStacks.set(key, []); - let flushHandlers = () => { - while (directiveHandlerStacks.get(key).length) - directiveHandlerStacks.get(key).shift()(); - directiveHandlerStacks.delete(key); - }; - let stopDeferring = () => { - isDeferringHandlers = false; - flushHandlers(); - }; - callback(flushHandlers); - stopDeferring(); -} -function getElementBoundUtilities(el) { - let cleanups = []; - let cleanup2 = (callback) => cleanups.push(callback); - let [effect3, cleanupEffect] = elementBoundEffect(el); - cleanups.push(cleanupEffect); - let utilities = { - Alpine: alpine_default, - effect: effect3, - cleanup: cleanup2, - evaluateLater: evaluateLater.bind(evaluateLater, el), - evaluate: evaluate.bind(evaluate, el) - }; - let doCleanup = () => cleanups.forEach((i) => i()); - return [utilities, doCleanup]; -} -function getDirectiveHandler(el, directive2) { - let noop = () => { - }; - let handler4 = directiveHandlers[directive2.type] || noop; - let [utilities, cleanup2] = getElementBoundUtilities(el); - onAttributeRemoved(el, directive2.original, cleanup2); - let fullHandler = () => { - if (el._x_ignore || el._x_ignoreSelf) - return; - handler4.inline && handler4.inline(el, directive2, utilities); - handler4 = handler4.bind(handler4, el, directive2, utilities); - isDeferringHandlers ? directiveHandlerStacks.get(currentHandlerStackKey).push(handler4) : handler4(); - }; - fullHandler.runCleanups = cleanup2; - return fullHandler; -} -var startingWith = (subject, replacement) => ({ name, value }) => { - if (name.startsWith(subject)) - name = name.replace(subject, replacement); - return { name, value }; -}; -var into = (i) => i; -function toTransformedAttributes(callback = () => { -}) { - return ({ name, value }) => { - let { name: newName, value: newValue } = attributeTransformers.reduce((carry, transform) => { - return transform(carry); - }, { name, value }); - if (newName !== name) - callback(newName, name); - return { name: newName, value: newValue }; - }; -} -var attributeTransformers = []; -function mapAttributes(callback) { - attributeTransformers.push(callback); -} -function outNonAlpineAttributes({ name }) { - return alpineAttributeRegex().test(name); -} -var alpineAttributeRegex = () => new RegExp(`^${prefixAsString}([^:^.]+)\\b`); -function toParsedDirectives(transformedAttributeMap, originalAttributeOverride) { - return ({ name, value }) => { - let typeMatch = name.match(alpineAttributeRegex()); - let valueMatch = name.match(/:([a-zA-Z0-9\-_:]+)/); - let modifiers = name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []; - let original = originalAttributeOverride || transformedAttributeMap[name] || name; - return { - type: typeMatch ? typeMatch[1] : null, - value: valueMatch ? valueMatch[1] : null, - modifiers: modifiers.map((i) => i.replace(".", "")), - expression: value, - original - }; - }; -} -var DEFAULT = "DEFAULT"; -var directiveOrder = [ - "ignore", - "ref", - "data", - "id", - "anchor", - "bind", - "init", - "for", - "model", - "modelable", - "transition", - "show", - "if", - DEFAULT, - "teleport" -]; -function byPriority(a, b) { - let typeA = directiveOrder.indexOf(a.type) === -1 ? DEFAULT : a.type; - let typeB = directiveOrder.indexOf(b.type) === -1 ? DEFAULT : b.type; - return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB); -} -function dispatch(el, name, detail = {}) { - el.dispatchEvent( - new CustomEvent(name, { - detail, - bubbles: true, - // Allows events to pass the shadow DOM barrier. - composed: true, - cancelable: true - }) - ); -} -function walk(el, callback) { - if (typeof ShadowRoot === "function" && el instanceof ShadowRoot) { - Array.from(el.children).forEach((el2) => walk(el2, callback)); - return; - } - let skip = false; - callback(el, () => skip = true); - if (skip) - return; - let node = el.firstElementChild; - while (node) { - walk(node, callback, false); - node = node.nextElementSibling; - } -} -function warn(message, ...args) { - console.warn(`Alpine Warning: ${message}`, ...args); -} -var started = false; -function start() { - if (started) - warn("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."); - started = true; - if (!document.body) - warn("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` - - diff --git a/.github/extensions/agentic-workflows-dashboard/web/models.js b/.github/extensions/agentic-workflows-dashboard/web/models.js deleted file mode 100644 index cb0ff5c3b54..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/web/models.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/.github/extensions/agentic-workflows-dashboard/web/styles.css b/.github/extensions/agentic-workflows-dashboard/web/styles.css deleted file mode 100644 index 5c818c81769..00000000000 --- a/.github/extensions/agentic-workflows-dashboard/web/styles.css +++ /dev/null @@ -1,183 +0,0 @@ -/* Hide Alpine.js x-cloak elements until Alpine initializes to prevent FOUC */ -[x-cloak] { - display: none !important; -} - -.awd-shell { - min-height: 100vh; - background: var(--bgColor-default, #ffffff); - padding: var(--base-size-16, 1rem); -} - -.awd-scroll { - max-height: 55vh; - overflow: auto; -} - -.awd-install-banner code { - white-space: nowrap; -} - -.awd-run-row { - cursor: pointer; -} - -.awd-run-row.is-selected { - background: var(--bgColor-accent-muted, #ddf4ff); -} - -.awd-command-output { - white-space: pre-wrap; - max-height: 260px; - overflow: auto; -} - -.awd-maintenance-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 16px; -} - -.awd-usage-header, -.awd-usage-row { - display: grid; - grid-template-columns: minmax(220px, 2fr) repeat(5, minmax(88px, 1fr)) minmax(140px, 1.2fr); - gap: 12px; - align-items: center; -} - -.awd-usage-header { - font-size: 12px; - font-weight: 600; - color: var(--fgColor-muted, #656d76); - background: var(--bgColor-muted, #f6f8fa); - position: sticky; - top: 0; - z-index: 1; -} - -.awd-tool-usage-header, -.awd-tool-usage-row { - display: grid; - grid-template-columns: minmax(180px, 2fr) repeat(3, minmax(80px, 1fr)); - gap: 12px; - align-items: center; -} - -.awd-tool-usage-header { - font-size: 12px; - font-weight: 600; - color: var(--fgColor-muted, #656d76); - background: var(--bgColor-muted, #f6f8fa); - position: sticky; - top: 0; - z-index: 1; -} - -details > summary { - list-style: none; -} - -details > summary::marker, -details > summary::-webkit-details-marker { - display: none; -} - -/* Give flash alert boxes inside Box components breathing room */ -.Box-body.flash { - margin: 0.75rem; - border-radius: 6px; -} - -/* Thin, themed scrollbars for content scroll areas */ -.awd-scroll, -.awd-command-output { - scrollbar-width: thin; - scrollbar-color: var(--borderColor-default, #d0d7de) transparent; -} - -.awd-scroll::-webkit-scrollbar, -.awd-command-output::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -.awd-scroll::-webkit-scrollbar-track, -.awd-command-output::-webkit-scrollbar-track { - background: transparent; -} - -.awd-scroll::-webkit-scrollbar-thumb, -.awd-command-output::-webkit-scrollbar-thumb { - background: var(--borderColor-default, #d0d7de); - border-radius: 4px; -} - -.awd-scroll::-webkit-scrollbar-thumb:hover, -.awd-command-output::-webkit-scrollbar-thumb:hover { - background: var(--fgColor-muted, #656d76); -} - -/* Thin scrollbar for the tab navigation overflow area */ -.UnderlineNav-body { - scrollbar-width: thin; - scrollbar-color: var(--borderColor-default, #d0d7de) transparent; -} - -.UnderlineNav-body::-webkit-scrollbar { - height: 4px; -} - -.UnderlineNav-body::-webkit-scrollbar-track { - background: transparent; -} - -.UnderlineNav-body::-webkit-scrollbar-thumb { - background: var(--borderColor-default, #d0d7de); - border-radius: 2px; -} - -.UnderlineNav-body::-webkit-scrollbar-thumb:hover { - background: var(--fgColor-muted, #656d76); -} - -/* Spinner */ -@keyframes awd-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -.awd-spinner { - display: inline-block; - width: 16px; - height: 16px; - border: 2px solid var(--borderColor-default, #d0d7de); - border-top-color: var(--fgColor-accent, #0969da); - border-radius: 50%; - animation: awd-spin 0.75s linear infinite; - flex-shrink: 0; - vertical-align: middle; -} - -/* Skeleton shimmer */ -@keyframes awd-shimmer { - 0% { - background-position: -400% 0; - } - 100% { - background-position: 400% 0; - } -} - -.awd-skeleton-line { - display: block; - height: 12px; - border-radius: 4px; - background: linear-gradient(90deg, var(--bgColor-muted, #f6f8fa) 25%, var(--bgColor-neutral, #eaeef2) 50%, var(--bgColor-muted, #f6f8fa) 75%); - background-size: 400% 100%; - animation: awd-shimmer 1.5s ease-in-out infinite; -} diff --git a/Makefile b/Makefile index 48a79b87368..6052f928908 100644 --- a/Makefile +++ b/Makefile @@ -235,19 +235,6 @@ security-govulncheck-sarif: test-js: build-js cd actions/setup/js && npm run test:js -- --no-file-parallelism -# Build and test the Copilot canvas extension for agentic workflows -.PHONY: test-canvas-extension -test-canvas-extension: - cd .github/extensions/agentic-workflows-dashboard && npm ci && npm run build && npm test - -.PHONY: fmt-canvas-extension -fmt-canvas-extension: - cd .github/extensions/agentic-workflows-dashboard && npm ci && npm run fmt - -.PHONY: lint-canvas-extension -lint-canvas-extension: - cd .github/extensions/agentic-workflows-dashboard && npm ci && npm run lint - # Test impacted JavaScript unit tests only (excluding integration tests) .PHONY: test-impacted-js test-impacted-js: build-js @@ -779,7 +766,7 @@ lint-lock: build # Format code .PHONY: fmt -fmt: fmt-go fmt-cjs fmt-json fmt-canvas-extension +fmt: fmt-go fmt-cjs fmt-json @echo "✓ Code formatted successfully" .PHONY: fmt-go @@ -902,7 +889,7 @@ lint-action-sh: # Validate all project files .PHONY: lint -lint: fmt-check fmt-check-json lint-cjs golint validate-model-alias-chains lint-action-sh lint-canvas-extension +lint: fmt-check fmt-check-json lint-cjs golint validate-model-alias-chains lint-action-sh @echo "✓ All validations passed" # Install the binary locally @@ -1098,11 +1085,8 @@ help: @echo " test-unit - Run Go unit tests only (faster)" @echo " test-security - Run security regression tests" @echo " test-js - Run JavaScript tests" - @echo " test-canvas-extension - Build and test the Copilot canvas extension" @echo " test-impacted-js - Run impacted JavaScript unit tests for current branch changes" @echo " test-impacted-go - Run impacted Go unit tests for current branch changes" - @echo " fmt-canvas-extension - Format the Copilot canvas extension sources" - @echo " lint-canvas-extension - Lint/typecheck/test the Copilot canvas extension" @echo " test-impacted - Run impacted JavaScript and Go unit tests for current branch changes" @echo " test-all - Run all tests (Go, JavaScript, and wasm golden)" @echo " test-wasm-golden - Run wasm golden tests (Go string API path)"