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.
This commit is contained in:
@@ -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());
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 => ({
|
||||||
|
|||||||
@@ -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 ─────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
Reference in New Issue
Block a user