@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.
314 lines (313 loc) • 11.4 kB
JavaScript
;
/**
* Dashboard model readers for injected shell data and API payloads that feed
* presets, projects, sessions, quality views, and task state.
*/
function readSupportedAgent(rawAgent) {
if (!isRecord(rawAgent))
return null;
const id = readRunnerId(rawAgent.id);
const name = readString(rawAgent.name);
const terminalBinary = readString(rawAgent.terminalBinary).trim();
const setupSurfaces = readStringArray(rawAgent.setupSurfaces).filter((surface) => surface.trim().length > 0);
const promptInvocationStyle = readPromptInvocationStyle(rawAgent.promptInvocationStyle);
const skillSource = readSkillSource(rawAgent.skillSource);
const supportsPostTurnHook = rawAgent.supportsPostTurnHook;
if (!id ||
!name ||
!terminalBinary ||
setupSurfaces.length === 0 ||
!promptInvocationStyle ||
!skillSource ||
typeof supportsPostTurnHook !== "boolean") {
return null;
}
return {
id,
name,
terminalBinary,
setupSurfaces,
promptInvocationStyle,
skillSource,
supportsPostTurnHook,
};
}
/** Read the supported agent list injected into the dashboard shell. */
function readInjectedSupportedAgents() {
return Array.isArray(window.__GOAT_FLOW_AGENTS__)
? window.__GOAT_FLOW_AGENTS__
.map((agent) => readSupportedAgent(agent))
.filter((agent) => agent !== null)
: [];
}
/** Read an optional boolean flag from a decoded record without accepting truthy strings. */
function readOptionalBoolean(record, key) {
return typeof record[key] === "boolean" ? record[key] : undefined;
}
/** Read the optional preset cost tier, rejecting unknown strings from shell injection. */
function readPresetCostTier(value) {
return value === "low" || value === "medium" || value === "high"
? value
: undefined;
}
/**
* Read one preset definition from dashboard shell injection.
*
* This stays field-by-field because preset safety flags gate UI affordances and
* fallback copy independently; dropping one flag changes launch behavior.
*/
function readPreset(rawPreset) {
if (!isRecord(rawPreset))
return null;
const id = readString(rawPreset.id);
const name = readString(rawPreset.name);
const desc = readString(rawPreset.desc);
const prompt = readString(rawPreset.prompt);
const cat = readString(rawPreset.cat);
if (!id || !name || !desc || !prompt || !cat)
return null;
return {
id,
name,
desc,
prompt,
cat,
route: readString(rawPreset.route) || undefined,
source: readString(rawPreset.source) || undefined,
globalSafe: readOptionalBoolean(rawPreset, "globalSafe"),
internalOnly: readOptionalBoolean(rawPreset, "internalOnly"),
qualityMode: readOptionalBoolean(rawPreset, "qualityMode"),
requiresGh: readOptionalBoolean(rawPreset, "requiresGh"),
requiresPrOrIssue: readOptionalBoolean(rawPreset, "requiresPrOrIssue"),
requiresLocalDiff: readOptionalBoolean(rawPreset, "requiresLocalDiff"),
requiresUiApp: readOptionalBoolean(rawPreset, "requiresUiApp"),
requiresDependencyFiles: readOptionalBoolean(rawPreset, "requiresDependencyFiles"),
requiresGoatFlowInstall: readOptionalBoolean(rawPreset, "requiresGoatFlowInstall"),
mayCheckoutBranch: readOptionalBoolean(rawPreset, "mayCheckoutBranch"),
requiresCleanWorktree: readOptionalBoolean(rawPreset, "requiresCleanWorktree"),
mayWriteFiles: readOptionalBoolean(rawPreset, "mayWriteFiles"),
artifactRequired: readOptionalBoolean(rawPreset, "artifactRequired"),
bestTargetSurfaces: readStringArray(rawPreset.bestTargetSurfaces),
fallbackPrompt: readString(rawPreset.fallbackPrompt) || undefined,
costTier: readPresetCostTier(rawPreset.costTier),
};
}
/** Read the preset list injected into the dashboard shell. */
function readInjectedPresets() {
return Array.isArray(window.__GOAT_FLOW_PRESETS__)
? window.__GOAT_FLOW_PRESETS__
.map((preset) => readPreset(preset))
.filter((preset) => preset !== null)
: [];
}
/** Read one installed-agent record from raw payload data. */
function readAgentInfo(rawAgent) {
if (!isRecord(rawAgent))
return null;
const agent = readSupportedAgent(rawAgent);
if (!agent || typeof rawAgent.installed !== "boolean")
return null;
return {
...agent,
installed: rawAgent.installed,
version: typeof rawAgent.version === "string" ? rawAgent.version : null,
};
}
/** Read one directory entry from the project browser payload. */
function readBrowseDir(rawEntry) {
if (!isRecord(rawEntry))
return null;
const name = readString(rawEntry.name);
const path = readString(rawEntry.path);
if (!name || !path || typeof rawEntry.isProject !== "boolean")
return null;
return { name, path, isProject: rawEntry.isProject };
}
/**
* Read one saved project entry from persisted state.
*
* This stays explicit because identity fields preserve alias grouping while
* keeping private remote URLs out of browser-local state.
*/
function readProjectEntry(rawProject) {
if (!isRecord(rawProject))
return null;
const path = readString(rawProject.path);
if (!path)
return null;
const identity = readString(rawProject.identity);
const identitySource = rawProject.identitySource === "git-remote" ||
rawProject.identitySource === "goat-marker" ||
rawProject.identitySource === "path"
? rawProject.identitySource
: null;
const entry = {
path,
paths: readStringArray(rawProject.paths),
state: readString(rawProject.state),
action: readString(rawProject.action),
details: readString(rawProject.details),
};
if (identity)
entry.identity = identity;
if (identitySource)
entry.identitySource = identitySource;
const remoteUrlHash = readString(rawProject.remoteUrlHash);
if (remoteUrlHash)
entry.remoteUrlHash = remoteUrlHash;
const markerId = readString(rawProject.markerId);
if (markerId)
entry.markerId = markerId;
return entry;
}
/**
* Read one backend terminal-session record.
*
* This stays explicit because old session payloads may omit cwd/targetPath, and
* the dashboard must default them to projectPath without marking the session bad.
*/
function readServerSessionInfo(rawSession) {
if (!isRecord(rawSession))
return null;
const id = readString(rawSession.id);
const status = readSessionStatus(rawSession.status);
const runner = readRunnerId(rawSession.runner);
const createdAt = readString(rawSession.createdAt);
const projectPath = readString(rawSession.projectPath);
const cwd = readString(rawSession.cwd);
const targetPath = readString(rawSession.targetPath);
if (!id ||
!status ||
!runner ||
!createdAt ||
!projectPath ||
typeof rawSession.lastInputAt !== "number") {
return null;
}
return {
id,
status,
createdAt,
projectPath,
cwd: cwd || projectPath,
targetPath: targetPath || projectPath,
runner,
lastInputAt: rawSession.lastInputAt,
age: typeof rawSession.age === "number" ? rawSession.age : undefined,
idleDuration: typeof rawSession.idleDuration === "number"
? rawSession.idleDuration
: undefined,
projectName: readString(rawSession.projectName) || undefined,
};
}
/** Read a quality-command response; throws when route identity or status fields drift. */
function readQualityResult(rawResult) {
const payload = readRecord(rawResult, "Quality response");
const agent = readRunnerId(payload.agent);
const auditStatus = readAuditStatus(payload.auditStatus);
const auditCacheStatus = readString(payload.auditCacheStatus);
const command = readString(payload.command);
if (!agent ||
(!auditStatus && payload.auditStatus !== "unavailable") ||
!["hit", "miss", "bypass"].includes(auditCacheStatus) ||
command !== "quality") {
throw new Error("Quality response returned an invalid payload");
}
return {
command: "quality",
agent,
auditStatus: auditStatus ?? "unavailable",
auditCacheStatus: auditCacheStatus,
auditSummary: readString(payload.auditSummary),
prompt: readString(payload.prompt),
};
}
/**
* Read one quality-history table row.
*
* This stays explicit because the dashboard compares setup/system totals and
* nullable setup deltas separately when rendering trend chips.
*/
function readQualityHistoryRow(rawRow) {
if (!isRecord(rawRow))
return null;
const id = readString(rawRow.id);
const date = readString(rawRow.date);
const agent = readRunnerId(rawRow.agent);
if (!id ||
!date ||
!agent ||
typeof rawRow.setupTotal !== "number" ||
typeof rawRow.systemTotal !== "number" ||
(rawRow.setupDelta !== null && typeof rawRow.setupDelta !== "number") ||
typeof rawRow.blockerCount !== "number" ||
typeof rawRow.majorCount !== "number" ||
typeof rawRow.minorCount !== "number") {
return null;
}
return {
id,
date,
agent,
setupTotal: rawRow.setupTotal,
systemTotal: rawRow.systemTotal,
setupDelta: rawRow.setupDelta,
blockerCount: rawRow.blockerCount,
majorCount: rawRow.majorCount,
minorCount: rawRow.minorCount,
};
}
/**
* Read the latest quality-history summary.
*
* This stays explicit because the latest card omits setupDelta but still needs
* the same totals and severity counters as row history.
*/
function readQualityHistoryLatest(rawLatest) {
if (!isRecord(rawLatest))
return null;
const id = readString(rawLatest.id);
const date = readString(rawLatest.date);
const time = readString(rawLatest.time);
const agent = readRunnerId(rawLatest.agent);
if (!id ||
!date ||
!time ||
!agent ||
typeof rawLatest.setupTotal !== "number" ||
typeof rawLatest.systemTotal !== "number" ||
typeof rawLatest.blockerCount !== "number" ||
typeof rawLatest.majorCount !== "number" ||
typeof rawLatest.minorCount !== "number") {
return null;
}
return {
id,
date,
time,
agent,
setupTotal: rawLatest.setupTotal,
systemTotal: rawLatest.systemTotal,
blockerCount: rawLatest.blockerCount,
majorCount: rawLatest.majorCount,
minorCount: rawLatest.minorCount,
};
}
/** Read a persisted string array from localStorage; swallows corrupt JSON. */
function readStoredStringArray(key) {
try {
return readStringArray(JSON.parse(localStorage.getItem(key) || "[]"));
}
catch {
return [];
}
}
/** Read a persisted string map from localStorage; swallows corrupt JSON. */
function readStoredStringMap(key) {
try {
return readStringMap(JSON.parse(localStorage.getItem(key) || "{}"));
}
catch {
return {};
}
}