Idle TTL eviction per (chat,agent) + LRU cap (never a busy backend); pure lifecycle-decisions.ts (TDD). Crash recovery lifts openchamber's health-monitor + busy-aware-restart + stale-grace state machine into opencode-server.ts (+ port reclaim) and warm-acp.ts; opencode crash -> fresh sessions, ACP -> re-session/new. F.1 turn-guard + U.6 usage preserved (their tests pass). Orphan worktree reaper (1h grace, superset-style dirty/unpushed preflight, Paseo soft-delete) + close hooks + diff re-baseline after apply_pending. 35 new tests + DB-opt-in reconnect test; 215 coder tests pass; tsc + build clean. Completes v2.6. Follow-ups out of scope: apps/server close-hook caller, 3.7 DiffPanel staging hint, live smokes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
3.6 KiB
TypeScript
70 lines
3.6 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
// BooCoder's config is a superset of the server's Config type so it can be
|
|
// passed directly into the inference runner's InferenceContext. Fields the
|
|
// inference loop reads: LLAMA_SWAP_URL, PROJECT_ROOT_WHITELIST. The rest
|
|
// default to values that satisfy the server's Zod schema without BooCoder
|
|
// needing to supply them in its environment.
|
|
const ConfigSchema = z.object({
|
|
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
|
PORT: z.coerce.number().int().positive().default(3000),
|
|
HOST: z.string().default('0.0.0.0'),
|
|
DATABASE_URL: z.string().url(),
|
|
LLAMA_SWAP_URL: z.string().url(),
|
|
PROJECT_ROOT_WHITELIST: z.string().default('/opt'),
|
|
BOOTSTRAP_ROOT: z.string().default('/opt/projects'),
|
|
DEFAULT_MODEL: z.string().default('qwen3.6-35b-a3b-mxfp4'),
|
|
LOG_LEVEL: z.string().default('info'),
|
|
CONTAINER_GUIDANCE_FILE: z.string().optional(),
|
|
// Fields needed to satisfy the server's Config type but unused by BooCoder:
|
|
SEARXNG_URL: z.string().url().default('http://100.114.205.53:8888'),
|
|
GITEA_BASE_URL: z.string().url().default('https://git.indifferentketchup.com'),
|
|
GITEA_USER: z.string().default('indifferentketchup'),
|
|
GITEA_TOKEN: z.string().optional(),
|
|
GITEA_SSH_HOST: z.string().default('100.114.205.53:2222'),
|
|
MCP_CONFIG_PATH: z.string().optional(),
|
|
// v2.3: config-backed provider overrides/custom-ACP entries merged over the
|
|
// hardcoded built-ins. Missing file = built-ins only (see provider-config.ts).
|
|
CODER_PROVIDERS_PATH: z.string().default('/data/coder-providers.json'),
|
|
// v2.3 phase 2: tier-2 (cold ACP probe) is skipped when available_agents was
|
|
// probed more recently than this. 24h default — stale model lists self-heal
|
|
// on the next snapshot; an explicit /refresh always re-probes.
|
|
PROVIDER_PROBE_TTL_MS: z.coerce.number().int().positive().default(86_400_000),
|
|
// v2.0.5: cheaper model for titles, summaries, labeling.
|
|
FAST_MODEL: z.string().optional(),
|
|
// SSH access to the host for external agent dispatch (Phase 5)
|
|
BOOCODER_SSH_HOST: z.string().default('100.114.205.53'),
|
|
BOOCODER_SSH_USER: z.string().default('samkintop'),
|
|
// v2.6 Phase 3 (lifecycle hardening). Idle TTL: evict a non-busy warm backend
|
|
// (opencode server / warm-ACP child) after this long with no turn — its worktree
|
|
// + agent_sessions row persist, so the next turn re-spawns + reattaches. 30 min
|
|
// default (design §6).
|
|
AGENT_POOL_IDLE_TTL_MS: z.coerce.number().int().positive().default(1_800_000),
|
|
// LRU cap: max live warm backends before the least-recently-used (non-busy) ones
|
|
// are evicted. Bounds the long-lived-daemon's per-(chat,agent) Map growth.
|
|
AGENT_POOL_MAX_LIVE: z.coerce.number().int().positive().default(10),
|
|
// Periodic sweep cadence (idle/LRU pool eviction + orphan-worktree reap). 60s
|
|
// mirrors the apps/server truncation/stale-streaming sweeper.
|
|
LIFECYCLE_SWEEP_INTERVAL_MS: z.coerce.number().int().positive().default(60_000),
|
|
// Orphan-worktree grace: an on-disk worktree dir with no live `worktrees` row is
|
|
// only reaped after it's been untouched this long (avoids sweeping a dir mid
|
|
// ensureSessionWorktree create). 1h default.
|
|
ORPHAN_WORKTREE_GRACE_MS: z.coerce.number().int().positive().default(3_600_000),
|
|
});
|
|
|
|
export type Config = z.infer<typeof ConfigSchema>;
|
|
|
|
let cached: Config | null = null;
|
|
|
|
export function loadConfig(): Config {
|
|
if (cached) return cached;
|
|
const parsed = ConfigSchema.safeParse(process.env);
|
|
if (!parsed.success) {
|
|
console.error('Invalid environment configuration:');
|
|
console.error(parsed.error.flatten().fieldErrors);
|
|
process.exit(1);
|
|
}
|
|
cached = parsed.data;
|
|
return cached;
|
|
}
|