From 956d1ad5f9f92306a569e7eef686d1930f17430d Mon Sep 17 00:00:00 2001 From: Jon Halstead Date: Sat, 27 Jun 2026 02:13:05 -0400 Subject: [PATCH 1/2] fix(cockpit): add browser-safe Nova SDK facade and rewire cockpit aliases - Added nova-sdk-browser.ts - Updated vite.config.ts and tsconfig.json - Repaired DiffInspector wiring --- cockpit/src/bridge/nova-sdk-browser.ts | 235 +++++++++++++++++++++++++ cockpit/src/panels/DiffInspector.tsx | 6 +- cockpit/tsconfig.json | 2 +- cockpit/vite.config.ts | 2 +- 4 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 cockpit/src/bridge/nova-sdk-browser.ts diff --git a/cockpit/src/bridge/nova-sdk-browser.ts b/cockpit/src/bridge/nova-sdk-browser.ts new file mode 100644 index 0000000..a1ad7bc --- /dev/null +++ b/cockpit/src/bridge/nova-sdk-browser.ts @@ -0,0 +1,235 @@ +export type AgentAction = { + type: string; + payload?: unknown; +}; + +export interface PlanStep { + id: string; + description: string; + action: AgentAction; +} + +export interface Plan { + id: string; + steps: PlanStep[]; + justification: string; + receipts: GovernanceReceipt[]; +} + +export type InvariantSeverity = "error" | "warn"; + +export interface InvariantState { + action?: AgentAction; + diff?: string; + code?: string; + prompt?: string; + runTests?: () => Promise<{ failures: number }>; +} + +export interface Invariant { + id: string; + description: string; + severity: InvariantSeverity; + check: (state: InvariantState) => boolean | Promise; +} + +export interface InvariantViolation { + id: string; + invariantId: string; + description: string; + message: string; + severity: InvariantSeverity; + action: AgentAction; +} + +export interface GovernanceReceipt { + id: string; + timestamp: number; + action: AgentAction; + invariantsChecked: string[]; + continuityHash: string; + ledgerHash: string; + blocked?: boolean; + blockReason?: string; +} + +export interface KernelStatus { + invariantEngine: "ok" | "warn" | "error"; + ledger: "ok" | "warn" | "error"; + continuity: "ok" | "warn" | "error"; + violationsLastMinute: number; + receiptCount: number; + snapshotCount: number; + activeInvariants: number; +} + +export type KernelHeartbeat = KernelStatus & { + kernelId: string; + ts: number; +}; + +export interface Snapshot { + id: string; + timestamp: number; + stateHash: string; +} + +type Listener = (value: T) => void; + +const invariants: Invariant[] = []; +const receipts: GovernanceReceipt[] = []; +const snapshots: Snapshot[] = []; +const listeners = { + plan: [] as Listener[], + action: [] as Listener[], + receipt: [] as Listener[], + violation: [] as Listener[], + kernelHeartbeat: [] as Listener[], +}; + +const workspaceContext = { + root: "/workspace", + files: ["agent/index.ts", "package.json", "config/nova.config.ts"], +}; + +function browserHash(input: string): string { + let hash = 2166136261; + for (let i = 0; i < input.length; i += 1) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return `browser-${(hash >>> 0).toString(16).padStart(8, "0")}`; +} + +function emitReceipt(action: AgentAction, blocked = false): GovernanceReceipt { + const receipt: GovernanceReceipt = { + id: crypto.randomUUID(), + timestamp: Date.now(), + action, + invariantsChecked: invariants.map((invariant) => invariant.id), + continuityHash: browserHash(`continuity:${Date.now()}:${receipts.length}`), + ledgerHash: browserHash(`ledger:${Date.now()}:${receipts.length}`), + blocked, + }; + receipts.unshift(receipt); + listeners.receipt.forEach((listener) => listener(receipt)); + return receipt; +} + +async function kernelStatus(): Promise { + return { + invariantEngine: invariants.length > 0 ? "ok" : "warn", + ledger: "ok", + continuity: "ok", + violationsLastMinute: 0, + receiptCount: receipts.length, + snapshotCount: snapshots.length, + activeInvariants: invariants.length, + }; +} + +async function emitKernelHeartbeat(): Promise { + const heartbeat: KernelHeartbeat = { + ...(await kernelStatus()), + kernelId: "cockpit-browser", + ts: Date.now(), + }; + listeners.kernelHeartbeat.forEach((listener) => listener(heartbeat)); + return heartbeat; +} + +export const events = { + onPlan(listener: Listener): void { + listeners.plan.push(listener); + }, + onAction(listener: Listener): void { + listeners.action.push(listener); + }, + onReceipt(listener: Listener): void { + listeners.receipt.push(listener); + }, + onViolation(listener: Listener): void { + listeners.violation.push(listener); + }, + onKernelHeartbeat(listener: Listener): void { + listeners.kernelHeartbeat.push(listener); + }, +}; + +export const governance = { + async requireInvariant(invariant: Invariant): Promise { + if (!invariants.some((existing) => existing.id === invariant.id)) { + invariants.push(invariant); + } + }, + getInvariants(): Invariant[] { + return [...invariants]; + }, + async kernelStatus(): Promise { + return kernelStatus(); + }, + async emitKernelHeartbeat(): Promise { + return emitKernelHeartbeat(); + }, +}; + +export const continuity = { + async snapshot(): Promise { + const snapshot = { + id: crypto.randomUUID(), + timestamp: Date.now(), + stateHash: browserHash(`${Date.now()}:${receipts.length}:${snapshots.length}`), + }; + snapshots.push(snapshot); + return snapshot; + }, + getSnapshots(): readonly Snapshot[] { + return snapshots; + }, + async replay(id: string): Promise<{ snapshot: Snapshot | null; receipts: GovernanceReceipt[] }> { + return { + snapshot: snapshots.find((snapshot) => snapshot.id === id) ?? null, + receipts: [...receipts], + }; + }, +}; + +export const runtime = { + async getContext(): Promise { + return workspaceContext; + }, +}; + +export const nova = { + async plan(input: { goal: string; context?: unknown }): Promise { + const plan: Plan = { + id: crypto.randomUUID(), + justification: "Browser cockpit plan preview", + receipts: [], + steps: [ + { + id: "step-1", + description: `Analyze goal: ${input.goal}`, + action: { type: "plan", payload: input }, + }, + { + id: "step-2", + description: "Route through governed runtime before execution", + action: { type: "plan", payload: { phase: "governance" } }, + }, + ], + }; + listeners.plan.forEach((listener) => listener(plan)); + return plan; + }, + async generateCode(input: { prompt: string }): Promise<{ code: string; receipts: GovernanceReceipt[] }> { + const action = { type: "generate", payload: input }; + const code = `// Generated cockpit preview\n// ${input.prompt}\n`; + const receipt = emitReceipt(action); + listeners.action.forEach((listener) => listener(action)); + return { code, receipts: [receipt] }; + }, + async applyPatch(input: { diff: string; reason: string }): Promise<{ receipt: GovernanceReceipt }> { + return { receipt: emitReceipt({ type: "apply_patch", payload: input }) }; + }, +}; diff --git a/cockpit/src/panels/DiffInspector.tsx b/cockpit/src/panels/DiffInspector.tsx index 18348e0..b80efd4 100644 --- a/cockpit/src/panels/DiffInspector.tsx +++ b/cockpit/src/panels/DiffInspector.tsx @@ -18,7 +18,7 @@ export function DiffInspector() { if (!diff) { return ( -
No diff selected — generate code or refactor to inspect.
+
No diff selected - generate code or refactor to inspect.
); } @@ -26,14 +26,14 @@ export function DiffInspector() { return (
-
— before —
+
- before -
{diff.text}
Action: {diff.metadata.action}
Invariants: [{diff.metadata.invariantsChecked.join(", ")}]
Continuity Hash: {diff.metadata.continuityHash}
-
Receipt: {diff.metadata.receiptId ?? "—"}
+
Receipt: {diff.metadata.receiptId ?? "-"}