UNPKG

@blundergoat/goat-flow

Version:

AI coding agent harness and local dashboard for Claude Code, OpenAI Codex, Google Antigravity, and GitHub Copilot - setup audits, guardrails, structured skills, deny hooks, and persistent learning loops.

639 lines (638 loc) 24.3 kB
"use strict"; /** * Browser-side payload readers for the dashboard. * This is loaded as a classic script before app.js, so helpers intentionally * live in the shared browser global scope rather than using module imports. */ const DASHBOARD_TOKEN_PARAM = "token"; const DASHBOARD_TOKEN_HEADER = "X-Goat-Flow-Dashboard-Token"; /** Return the process-local dashboard authorization token injected at boot. */ function dashboardAuthToken() { return typeof window.__GOAT_FLOW_DASHBOARD_TOKEN__ === "string" ? window.__GOAT_FLOW_DASHBOARD_TOKEN__ : ""; } /** Fetch a dashboard API route with the current process-local token. */ function dashboardFetch(input, init = {}) { const headers = new Headers(init.headers); const token = dashboardAuthToken(); if (token) headers.set(DASHBOARD_TOKEN_HEADER, token); return fetch(input, { ...init, headers }); } /** Read a browser File object and return its raw bytes as a base64 string. * Used by the terminal image drop handler so the upload endpoint receives a * JSON payload it can decode without multipart parsing. */ function dashboardFileToBase64(file) { return new Promise((resolveBase64, rejectBase64) => { const reader = new FileReader(); reader.onerror = () => { rejectBase64(reader.error ?? new Error("File read failed")); }; reader.onload = () => { const result = reader.result; if (typeof result !== "string") { rejectBase64(new Error("Unexpected file read result")); return; } const comma = result.indexOf(","); resolveBase64(comma === -1 ? result : result.slice(comma + 1)); }; reader.readAsDataURL(file); }); } /** Append the dashboard token to a terminal WebSocket path. */ function dashboardTerminalWsPath(wsPath) { const token = dashboardAuthToken(); if (!token) return wsPath; const url = new URL(wsPath, window.location.origin); url.searchParams.set(DASHBOARD_TOKEN_PARAM, token); return `${url.pathname}${url.search}`; } /** Remove the launch token from the visible URL after the boot payload is loaded. */ function dashboardClearLaunchToken() { const url = new URL(window.location.href); if (!url.searchParams.has(DASHBOARD_TOKEN_PARAM)) return; url.searchParams.delete(DASHBOARD_TOKEN_PARAM); const next = url.pathname + (url.searchParams.toString() ? `?${url.searchParams.toString()}` : "") + url.hash; window.history.replaceState(null, "", next); } dashboardClearLaunchToken(); /** Treat arrays as invalid records because dashboard API payloads use named fields. */ function isRecord(candidate) { return (typeof candidate === "object" && candidate !== null && !Array.isArray(candidate)); } /** Read a required object record; throws when a top-level API payload is malformed. */ function readRecord(rawPayload, context) { if (!isRecord(rawPayload)) { throw new Error(`${context} returned an invalid payload`); } return rawPayload; } /** Read a string value with a safe fallback for invalid payload fields. */ function readString(rawValue, fallback = "") { return typeof rawValue === "string" ? rawValue : fallback; } /** Read a string array from raw payload data. */ function readStringArray(rawValue) { return Array.isArray(rawValue) ? rawValue.filter((entry) => typeof entry === "string") : []; } /** Read an array with one row decoder, dropping invalid rows. */ function readArray(rawValue, reader) { return Array.isArray(rawValue) ? rawValue.map(reader).filter((entry) => entry !== null) : []; } /** Read a `{ [key: string]: string }` map, silently dropping invalid entries. */ function readStringMap(rawValue) { if (!isRecord(rawValue)) return {}; const result = {}; for (const [k, v] of Object.entries(rawValue)) { if (typeof v === "string" && v.length > 0) result[k] = v; } return result; } /** Read an audit status from raw payload data. */ function readAuditStatus(rawValue) { return rawValue === "pass" || rawValue === "fail" || rawValue === "skipped" ? rawValue : null; } /** Read a dashboard display status from raw payload data. */ function readAuditDisplayStatus(rawValue) { return rawValue === "pass" || rawValue === "fail" || rawValue === "warn" || rawValue === "info" || rawValue === "skipped" ? rawValue : null; } /** Read a check impact label from raw payload data. */ function readAuditCheckImpact(rawValue) { return rawValue === "none" || rawValue === "scope-fail" || rawValue === "score-only" ? rawValue : null; } /** Compute a backward-compatible display status when an older server omits it. */ function defaultDisplayStatus(status, type, acknowledged = false) { if (status === "skipped") return "skipped"; if (status === "pass") return type === "metric" ? "info" : "pass"; return type === "metric" || acknowledged ? "warn" : "fail"; } /** Compute backward-compatible impact when an older server omits it. */ function defaultCheckImpact(status, type, acknowledged = false) { if (status !== "fail") return "none"; return type === "metric" || acknowledged ? "score-only" : "scope-fail"; } /** Read the runner IDs injected into the dashboard shell. */ function readInjectedRunnerIds() { return Array.isArray(window.__GOAT_FLOW_RUNNER_IDS__) ? window.__GOAT_FLOW_RUNNER_IDS__.filter((id) => typeof id === "string") : []; } /** Read a runner ID from raw payload data. Unknown values narrow to null so * the server's wire contract isn't silently widened to arbitrary strings. */ function readRunnerId(rawValue) { const runner = readString(rawValue).trim(); return readInjectedRunnerIds().includes(runner) ? runner : null; } /** Read prompt invocation style from server-provided runner metadata. */ function readPromptInvocationStyle(rawValue) { return rawValue === "slash" || rawValue === "dollar" ? rawValue : null; } /** Read the source bucket for installed or mirrored runner skills. */ function readSkillSource(rawValue) { return rawValue === "installed" || rawValue === "agent-mirror" || rawValue === "github-mirror" ? rawValue : null; } /** Build the default setup-agent selection from the injected support list. */ function buildDefaultSetupAgents(supportedAgents, defaultRunner) { if (supportedAgents.length === 0) { return { [defaultRunner]: true }; } return Object.fromEntries(supportedAgents.map((agent) => [agent.id, agent.id === defaultRunner])); } /** Read a terminal-session status from raw payload data. */ function readSessionStatus(rawValue) { return rawValue === "starting" || rawValue === "active" || rawValue === "terminated" ? rawValue : null; } /** Read an error message from a payload record. */ function readErrorMessage(payload) { return typeof payload.error === "string" ? payload.error : null; } /** Collapse a project path down to the display name shown in the UI. */ function getProjectDisplayName(path) { return path.split("/").filter(Boolean).pop() || path; } /** Read one audit failure record from raw payload data. */ function readAuditFailure(rawFailure) { if (!isRecord(rawFailure)) return null; const check = readString(rawFailure.check); const message = readString(rawFailure.message); if (!check || !message) return null; const failure = { check, message }; const evidence = readString(rawFailure.evidence); const howToFix = readString(rawFailure.howToFix); if (evidence) failure.evidence = evidence; if (howToFix) failure.howToFix = howToFix; return failure; } const AUDIT_PROVENANCE_SOURCE_TYPES = "|spec|vendor_docs|paper|incident|community|unknown|"; const AUDIT_PROVENANCE_NORMATIVE_LEVELS = "|MUST|SHOULD|BEST_PRACTICE|"; const AUDIT_CHECK_TYPES = "|integrity|advisory|metric|"; const AUDIT_EVIDENCE_KINDS = "|semantic|structural|"; const AUDIT_ASSURANCE_LEVELS = "|full|limited|"; const AUDIT_EVIDENCE_PATH_KEYS = [ "evidence_paths", "framework_evidence_paths", "target_evidence_paths", ]; /** Attach optional provenance evidence paths when the API sends the field as an array. */ function assignEvidencePaths(provenance, key, value) { if (Array.isArray(value)) provenance[key] = readStringArray(value); } /** Read one audit-check provenance block and reject unknown contract discriminants. */ function readAuditCheckProvenance(rawProvenance) { if (!isRecord(rawProvenance)) return null; const sourceType = readString(rawProvenance.source_type); const verifiedOn = readString(rawProvenance.verified_on); const normativeLevel = readString(rawProvenance.normative_level); if (!AUDIT_PROVENANCE_SOURCE_TYPES.includes(`|${sourceType}|`) || !verifiedOn || !AUDIT_PROVENANCE_NORMATIVE_LEVELS.includes(`|${normativeLevel}|`)) { return null; } const provenance = { source_type: sourceType, source_urls: readStringArray(rawProvenance.source_urls), verified_on: verifiedOn, normative_level: normativeLevel, }; for (const key of AUDIT_EVIDENCE_PATH_KEYS) { assignEvidencePaths(provenance, key, rawProvenance[key]); } if (typeof rawProvenance.reason === "string") provenance.reason = rawProvenance.reason; return provenance; } /** Read one optional string discriminator only when it is in the allowed API vocabulary. */ function readEnumValue(value, allowed) { return typeof value === "string" && allowed.includes(`|${value}|`) ? value : undefined; } /** Apply optional audit-check fields because scoring defaults depend on decoded type and acknowledgement. */ function applyAuditCheckOptionalFields(check, rawCheck, status) { const type = readEnumValue(rawCheck.type, AUDIT_CHECK_TYPES); if (type) check.type = type; if (rawCheck.acknowledged === true) check.acknowledged = true; const acknowledged = check.acknowledged === true; check.displayStatus = readAuditDisplayStatus(rawCheck.displayStatus) ?? defaultDisplayStatus(status, check.type, acknowledged); check.impact = readAuditCheckImpact(rawCheck.impact) ?? defaultCheckImpact(status, check.type, acknowledged); const evidenceKind = readEnumValue(rawCheck.evidenceKind, AUDIT_EVIDENCE_KINDS); if (evidenceKind) check.evidenceKind = evidenceKind; const assurance = readEnumValue(rawCheck.assurance, AUDIT_ASSURANCE_LEVELS); if (assurance) check.assurance = assurance; const failure = readAuditFailure(rawCheck.failure); if (failure) check.failure = failure; if (isRecord(rawCheck.details)) check.details = rawCheck.details; } /** * Read one audit check while preserving score-critical discriminants. * * This stays explicit because dashboard scoring branches on `type`, `impact`, * `displayStatus`, and acknowledgement fields after decoding the API payload. */ function readAuditCheck(rawCheck) { if (!isRecord(rawCheck)) return null; const id = readString(rawCheck.id); const name = readString(rawCheck.name); const status = readAuditStatus(rawCheck.status); if (!id || !name || !status) return null; const provenance = readAuditCheckProvenance(rawCheck.provenance); if (!provenance) return null; const check = { id, name, status, displayStatus: "pass", impact: "none", provenance, }; applyAuditCheckOptionalFields(check, rawCheck, status); return check; } /** Read a string-to-string map from raw payload data. */ function readStringRecord(rawValue) { if (!isRecord(rawValue)) return {}; const entries = Object.entries(rawValue).filter((entry) => typeof entry[1] === "string"); return Object.fromEntries(entries); } /** Read one audit scope; throws when required scope status is missing or invalid. */ function readAuditScope(rawScope, context) { const payload = readRecord(rawScope, context); const status = readAuditStatus(payload.status); if (!status) { throw new Error(`${context} returned an invalid audit status`); } return { status, checks: readArray(payload.checks, readAuditCheck), failures: readArray(payload.failures, readAuditFailure), summary: readStringRecord(payload.summary), }; } /** Read one harness concern from raw payload data. */ function readAuditConcern(rawConcern) { if (!isRecord(rawConcern)) return null; const status = readAuditStatus(rawConcern.status); if (!status || typeof rawConcern.score !== "number") return null; /** Read a numeric counter from raw payload data. */ const readCount = (v) => (typeof v === "number" ? v : 0); return { status, score: rawConcern.score, findings: readStringArray(rawConcern.findings), limits: readStringArray(rawConcern.limits), recommendations: readStringArray(rawConcern.recommendations), howToFix: readStringArray(rawConcern.howToFix), integrityPass: readCount(rawConcern.integrityPass), integrityFail: readCount(rawConcern.integrityFail), advisoryPass: readCount(rawConcern.advisoryPass), advisoryFail: readCount(rawConcern.advisoryFail), advisoryAcknowledged: readCount(rawConcern.advisoryAcknowledged), metrics: readCount(rawConcern.metrics), }; } /** Read an enforcement capability status from raw payload data. */ function readEnforcementStatus(rawValue) { return rawValue === "hard" || rawValue === "limited" || rawValue === "soft" || rawValue === "missing" || rawValue === "unknown" ? rawValue : null; } /** Read only the known enforcement status counters from raw payload data. */ function readEnforcementSummary(rawSummary) { const summary = { hard: 0, limited: 0, soft: 0, missing: 0, unknown: 0, }; if (!isRecord(rawSummary)) return summary; for (const [key, count] of Object.entries(rawSummary)) { const status = readEnforcementStatus(key); if (status && typeof count === "number") summary[status] = count; } return summary; } /** Read one enforcement source label from raw payload data. */ function readEnforcementSource(rawValue) { return rawValue === "local-settings" || rawValue === "local-hook" || rawValue === "runtime-self-test" || rawValue === "manifest" || rawValue === "provider-docs" || rawValue === "not-observed" ? rawValue : null; } /** Read one advisory enforcement capability row. */ function readEnforcementCapability(rawCapability) { if (!isRecord(rawCapability)) return null; const id = readString(rawCapability.id); const label = readString(rawCapability.label); const status = readEnforcementStatus(rawCapability.status); const summary = readString(rawCapability.summary); if (!id || !label || !status || !summary) return null; return { id, label, status, sources: readArray(rawCapability.sources, readEnforcementSource), summary, evidence: readStringArray(rawCapability.evidence), }; } /** Read the advisory enforcement matrix for one agent. */ function readAgentEnforcementCapability(rawEnforcement) { if (!isRecord(rawEnforcement)) return null; const agent = readRunnerId(rawEnforcement.agent); const name = readString(rawEnforcement.name); if (!agent || !name || rawEnforcement.advisory !== true) return null; const capabilities = readArray(rawEnforcement.capabilities, readEnforcementCapability); return { agent, name, advisory: true, capabilities, summary: readEnforcementSummary(rawEnforcement.summary), }; } /** Read one per-agent score from raw payload data. */ function readAgentScore(rawScore) { if (!isRecord(rawScore)) return null; const id = readRunnerId(rawScore.id); if (!id) return null; const harness = rawScore.harness === null ? null : rawScore.harness === undefined ? null : readAuditScope(rawScore.harness, "Audit response harness scope"); const concerns = rawScore.concerns === null ? null : isRecord(rawScore.concerns) ? Object.fromEntries(Object.entries(rawScore.concerns) .map(([key, concern]) => [key, readAuditConcern(concern)]) .filter((entry) => entry[1] !== null)) : null; return { id, name: readString(rawScore.name, id), agent: readAuditScope(rawScore.agent, "Audit response agent scope"), harness, concerns, enforcement: readAgentEnforcementCapability(rawScore.enforcement), }; } /** Read one learning-loop action bucket from the audit payload. */ function readLearningLoopBucketAction(rawAction) { if (!isRecord(rawAction)) return null; const path = readString(rawAction.path); const reason = readString(rawAction.reason); if (!path || !reason) return null; return { path, reason }; } /** Read one generated learning-loop index freshness row from the audit payload. */ function readLearningLoopIndexFreshness(rawIndex) { if (!isRecord(rawIndex)) return null; const bucket = readString(rawIndex.bucket); const dirPath = readString(rawIndex.dirPath); const indexPath = readString(rawIndex.indexPath); const state = readString(rawIndex.state); if (!bucket || !dirPath || !indexPath || !["fresh", "stale", "missing", "no-bucket"].includes(state)) { return null; } return { bucket, dirPath, indexPath, state: state, entryCount: readFiniteNumber(rawIndex.entryCount), }; } /** Read compact learning-loop health from the audit payload. */ function readLearningLoopSummary(rawSummary) { if (!isRecord(rawSummary)) return null; const status = readString(rawSummary.status); if (!["fresh", "needs-review", "unavailable"].includes(status) || typeof rawSummary.recordCount !== "number" || typeof rawSummary.footgunCount !== "number" || typeof rawSummary.lessonCount !== "number" || typeof rawSummary.staleCount !== "number" || typeof rawSummary.invalidLineRefCount !== "number" || typeof rawSummary.oversizedCount !== "number") { return null; } return { recordCount: rawSummary.recordCount, footgunCount: rawSummary.footgunCount, lessonCount: rawSummary.lessonCount, staleCount: rawSummary.staleCount, invalidLineRefCount: rawSummary.invalidLineRefCount, oversizedCount: rawSummary.oversizedCount, indexes: readArray(rawSummary.indexes, readLearningLoopIndexFreshness), indexStaleCount: readFiniteNumber(rawSummary.indexStaleCount), indexMissingCount: readFiniteNumber(rawSummary.indexMissingCount), oldestLastReviewed: typeof rawSummary.oldestLastReviewed === "string" ? rawSummary.oldestLastReviewed : null, topBucketsNeedingAction: readArray(rawSummary.topBucketsNeedingAction, readLearningLoopBucketAction), status: status, }; } /** Read one recent lesson row from the audit payload. */ function readRecentLesson(rawLesson) { if (!isRecord(rawLesson)) return null; const id = readString(rawLesson.id); const title = readString(rawLesson.title); const path = readString(rawLesson.path); if (!id || !title || !path) return null; return { id, title, path, created: readString(rawLesson.created) || null, }; } /** Read a finite numeric payload field with a safe fallback. */ function readFiniteNumber(rawValue, fallback = 0) { return typeof rawValue === "number" && Number.isFinite(rawValue) ? rawValue : fallback; } /** Read one top-level plan directory summary from `/api/plans`. */ function readTaskPlanSummary(rawPlan) { if (!isRecord(rawPlan)) return null; const name = readString(rawPlan.name); const path = readString(rawPlan.path); if (!name || !path) return null; return { name, path, modifiedAt: readString(rawPlan.modifiedAt), milestoneCount: readFiniteNumber(rawPlan.milestoneCount), active: rawPlan.active === true, }; } /** Read one milestone summary from `/api/plans`. */ function readTaskMilestoneSummary(rawMilestone) { if (!isRecord(rawMilestone)) return null; const filename = readString(rawMilestone.filename); const path = readString(rawMilestone.path); const title = readString(rawMilestone.title); if (!filename || !path || !title) return null; return { filename, path, title, status: readString(rawMilestone.status, "unknown"), objective: readString(rawMilestone.objective), totalTasks: readFiniteNumber(rawMilestone.totalTasks), completedTasks: readFiniteNumber(rawMilestone.completedTasks), modifiedAt: readString(rawMilestone.modifiedAt), }; } /** Read the selected project's `.goat-flow/plans/` state. */ function readTaskState(rawState) { const payload = readRecord(rawState, "Tasks response"); const planRoot = readString(payload.planRoot, readString(payload.taskRoot)); return { planRoot, taskRoot: planRoot, exists: payload.exists === true, active: readString(payload.active) || null, activeExists: payload.activeExists === true, selectedPlan: readString(payload.selectedPlan) || null, plans: readArray(payload.plans, readTaskPlanSummary), milestones: readArray(payload.milestones, readTaskMilestoneSummary), }; } /** Read the full dashboard report; throws when required audit status fields drift. */ function readDashboardReport(rawReport) { const payload = readRecord(rawReport, "Audit response"); const status = readAuditStatus(payload.status); if (!status) { throw new Error("Audit response returned an invalid status"); } const scopesPayload = readRecord(payload.scopes, "Audit response scopes"); const overallPayload = readRecord(payload.overall, "Audit response overall"); const overallStatus = readAuditStatus(overallPayload.status); if (!overallStatus) { throw new Error("Audit response returned an invalid overall status"); } return { agentScores: readArray(payload.agentScores, readAgentScore), status, scopes: { setup: readAuditScope(scopesPayload.setup, "Audit response setup scope"), agent: readAuditScope(scopesPayload.agent, "Audit response agent scope"), ...(scopesPayload.harness ? { harness: readAuditScope(scopesPayload.harness, "Audit response harness scope"), } : {}), }, overall: { status: overallStatus }, learningLoop: readLearningLoopSummary(payload.learningLoop), recentLessons: readArray(payload.recentLessons, readRecentLesson), target: readString(payload.target), }; } /** Read injected boot report; swallows stale shell payloads so the app can refetch. */ function readInjectedReport() { if (window.__GOAT_FLOW_REPORT__ == null) return null; try { return readDashboardReport(window.__GOAT_FLOW_REPORT__); } catch { return null; } } /** * Read one supported-agent record from dashboard shell injection. * * This stays explicit because the injected metadata controls prompt routing, * terminal launch labels, and setup-surface hints before any API refetch occurs. */