Fix boot state sharing across Next.js module boundaries
All checks were successful
CI / lint-and-test (push) Successful in 29s
Deploy Production / deploy (push) Successful in 47s
CI / build (push) Successful in 1m16s

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.
This commit is contained in:
Julia McGhee
2026-03-21 19:59:34 +00:00
parent a079225367
commit a60754d5a2
5 changed files with 22 additions and 12 deletions

View File

@@ -58,7 +58,10 @@ export interface AgentConfig {
env?: Record<string, string>; // additional env vars for the agent process env?: Record<string, string>; // additional env vars for the agent process
} }
const configs: Map<string, AgentConfig> = new Map(); // Shared via globalThis to survive Next.js module re-bundling.
const g = globalThis as unknown as { __harnessAgentConfigs?: Map<string, AgentConfig> };
g.__harnessAgentConfigs ??= new Map();
const configs = g.__harnessAgentConfigs;
export function getAllAgentConfigs(): AgentConfig[] { export function getAllAgentConfigs(): AgentConfig[] {
return Array.from(configs.values()); return Array.from(configs.values());

View File

@@ -7,7 +7,8 @@ import { upsertCuratedModel, getCuratedModels } from "./model-store";
import { upsertAgentConfig, getAllAgentConfigs, type AgentRuntime } from "./agents"; import { upsertAgentConfig, getAllAgentConfigs, type AgentRuntime } from "./agents";
import { fetchAllModels } from "./model-providers"; 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 // Well-known models with pricing (used as fallback when API discovery returns
// models without pricing info, and to enable cost tracking from the start). // models without pricing info, and to enable cost tracking from the start).
@@ -179,8 +180,8 @@ async function discoverModelsAndAgents() {
// ─── BOOT ─────────────────────────────────────────────────── // ─── BOOT ───────────────────────────────────────────────────
export async function boot() { export async function boot() {
if (booted) return; if (gb.__harnessBooted) return;
booted = true; gb.__harnessBooted = true;
// 1. Load credentials from mounted secrets (files take priority) // 1. Load credentials from mounted secrets (files take priority)
loadClaudeCredentials(); loadClaudeCredentials();

View File

@@ -13,8 +13,10 @@ export interface Credential {
baseUrl?: string; // for self-hosted GitLab or custom endpoints baseUrl?: string; // for self-hosted GitLab or custom endpoints
} }
// In-memory store. Will be replaced with encrypted persistent storage. // In-memory store shared via globalThis to survive Next.js module re-bundling.
const credentials: Map<string, Credential> = new Map(); const g = globalThis as unknown as { __harnessCredentials?: Map<string, Credential> };
g.__harnessCredentials ??= new Map();
const credentials = g.__harnessCredentials;
export function getAllCredentials(): Credential[] { export function getAllCredentials(): Credential[] {
return Array.from(credentials.values()).map(c => ({ return Array.from(credentials.values()).map(c => ({

View File

@@ -32,9 +32,12 @@ export interface ModelUsageSummary {
totalDurationMs: number; totalDurationMs: number;
} }
// In-memory stores // In-memory stores shared via globalThis to survive Next.js module re-bundling.
const curatedModels: Map<string, CuratedModel> = new Map(); const g = globalThis as unknown as { __harnessCuratedModels?: Map<string, CuratedModel>; __harnessUsageLog?: ModelUsageEntry[] };
const usageLog: ModelUsageEntry[] = []; g.__harnessCuratedModels ??= new Map();
g.__harnessUsageLog ??= [];
const curatedModels = g.__harnessCuratedModels;
const usageLog = g.__harnessUsageLog;
// ─── CURATED MODELS ───────────────────────────────────────── // ─── CURATED MODELS ─────────────────────────────────────────

View File

@@ -1,8 +1,9 @@
import { Task } from "./types"; import { Task } from "./types";
// In-memory task store. Will be replaced with persistent storage (CloudNativePG) // In-memory task store shared via globalThis to survive Next.js module re-bundling.
// once the orchestrator loop is wired up. const g = globalThis as unknown as { __harnessTasks?: Map<string, Task> };
const tasks: Map<string, Task> = new Map(); g.__harnessTasks ??= new Map();
const tasks = g.__harnessTasks;
export function getAllTasks(): Task[] { export function getAllTasks(): Task[] {
return Array.from(tasks.values()); return Array.from(tasks.values());