From a60754d5a2ae45964a9f27aa1eec30f399f31927 Mon Sep 17 00:00:00 2001 From: Julia McGhee Date: Sat, 21 Mar 2026 19:59:34 +0000 Subject: [PATCH] Fix boot state sharing across Next.js module boundaries Use globalThis for all in-memory stores (credentials, models, agents, tasks) so the instrumentation hook and API route handlers share the same data. Next.js bundles these as separate chunks with independent module instances, causing boot-populated state to be invisible to API routes. --- apps/harness/src/lib/agents.ts | 5 ++++- apps/harness/src/lib/boot.ts | 7 ++++--- apps/harness/src/lib/credentials.ts | 6 ++++-- apps/harness/src/lib/model-store.ts | 9 ++++++--- apps/harness/src/lib/store.ts | 7 ++++--- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/harness/src/lib/agents.ts b/apps/harness/src/lib/agents.ts index 0ebff72..acd95fc 100644 --- a/apps/harness/src/lib/agents.ts +++ b/apps/harness/src/lib/agents.ts @@ -58,7 +58,10 @@ export interface AgentConfig { env?: Record; // additional env vars for the agent process } -const configs: Map = new Map(); +// Shared via globalThis to survive Next.js module re-bundling. +const g = globalThis as unknown as { __harnessAgentConfigs?: Map }; +g.__harnessAgentConfigs ??= new Map(); +const configs = g.__harnessAgentConfigs; export function getAllAgentConfigs(): AgentConfig[] { return Array.from(configs.values()); diff --git a/apps/harness/src/lib/boot.ts b/apps/harness/src/lib/boot.ts index 3736a7e..84f525a 100644 --- a/apps/harness/src/lib/boot.ts +++ b/apps/harness/src/lib/boot.ts @@ -7,7 +7,8 @@ import { upsertCuratedModel, getCuratedModels } from "./model-store"; import { upsertAgentConfig, getAllAgentConfigs, type AgentRuntime } from "./agents"; import { fetchAllModels } from "./model-providers"; -let booted = false; +const gb = globalThis as unknown as { __harnessBooted?: boolean }; + // Well-known models with pricing (used as fallback when API discovery returns // models without pricing info, and to enable cost tracking from the start). @@ -179,8 +180,8 @@ async function discoverModelsAndAgents() { // ─── BOOT ─────────────────────────────────────────────────── export async function boot() { - if (booted) return; - booted = true; + if (gb.__harnessBooted) return; + gb.__harnessBooted = true; // 1. Load credentials from mounted secrets (files take priority) loadClaudeCredentials(); diff --git a/apps/harness/src/lib/credentials.ts b/apps/harness/src/lib/credentials.ts index 6bb7d02..6a071db 100644 --- a/apps/harness/src/lib/credentials.ts +++ b/apps/harness/src/lib/credentials.ts @@ -13,8 +13,10 @@ export interface Credential { baseUrl?: string; // for self-hosted GitLab or custom endpoints } -// In-memory store. Will be replaced with encrypted persistent storage. -const credentials: Map = new Map(); +// In-memory store shared via globalThis to survive Next.js module re-bundling. +const g = globalThis as unknown as { __harnessCredentials?: Map }; +g.__harnessCredentials ??= new Map(); +const credentials = g.__harnessCredentials; export function getAllCredentials(): Credential[] { return Array.from(credentials.values()).map(c => ({ diff --git a/apps/harness/src/lib/model-store.ts b/apps/harness/src/lib/model-store.ts index 80718e4..52ac317 100644 --- a/apps/harness/src/lib/model-store.ts +++ b/apps/harness/src/lib/model-store.ts @@ -32,9 +32,12 @@ export interface ModelUsageSummary { totalDurationMs: number; } -// In-memory stores -const curatedModels: Map = new Map(); -const usageLog: ModelUsageEntry[] = []; +// In-memory stores shared via globalThis to survive Next.js module re-bundling. +const g = globalThis as unknown as { __harnessCuratedModels?: Map; __harnessUsageLog?: ModelUsageEntry[] }; +g.__harnessCuratedModels ??= new Map(); +g.__harnessUsageLog ??= []; +const curatedModels = g.__harnessCuratedModels; +const usageLog = g.__harnessUsageLog; // ─── CURATED MODELS ───────────────────────────────────────── diff --git a/apps/harness/src/lib/store.ts b/apps/harness/src/lib/store.ts index f00903c..e4951ad 100644 --- a/apps/harness/src/lib/store.ts +++ b/apps/harness/src/lib/store.ts @@ -1,8 +1,9 @@ import { Task } from "./types"; -// In-memory task store. Will be replaced with persistent storage (CloudNativePG) -// once the orchestrator loop is wired up. -const tasks: Map = new Map(); +// In-memory task store shared via globalThis to survive Next.js module re-bundling. +const g = globalThis as unknown as { __harnessTasks?: Map }; +g.__harnessTasks ??= new Map(); +const tasks = g.__harnessTasks; export function getAllTasks(): Task[] { return Array.from(tasks.values());