feat(coder): v2.6 Phase 3 — lifecycle hardening (idle evict, crash recovery, worktree reaper)
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>
This commit is contained in:
@@ -32,10 +32,12 @@ import { registerStatsRoutes } from './routes/stats.js';
|
||||
import { registerArenaRoutes } from './routes/arena.js';
|
||||
import { registerProviderRoutes } from './routes/providers.js';
|
||||
import { registerWorktreeSafetyRoutes } from './routes/worktree-safety.js';
|
||||
import { registerLifecycleRoutes } from './routes/lifecycle.js';
|
||||
import { registerWebSocket } from './routes/ws.js';
|
||||
// Phase 4: dispatcher + agent probe
|
||||
import { createDispatcher } from './services/dispatcher.js';
|
||||
import { agentPool } from './services/agent-pool.js';
|
||||
import { createOrphanWorktreeReaper } from './services/orphan-worktree-reaper.js';
|
||||
import { probeAgents } from './services/agent-probe.js';
|
||||
import { getProviderSnapshot, persistProbedModels } from './services/provider-snapshot.js';
|
||||
import { setPermissionHooks } from './services/permission-waiter.js';
|
||||
@@ -181,10 +183,30 @@ async function main() {
|
||||
// Phase 4: dispatcher — polls tasks table and runs inference
|
||||
const dispatcher = createDispatcher({ sql, inference: inferenceApi, broker, log: app.log, config });
|
||||
dispatcher.start();
|
||||
|
||||
// v2.6 Phase 3: configure + start the agent-pool lifecycle sweep (idle-TTL +
|
||||
// LRU-cap eviction of warm backends, plus each backend's proactive health probe)
|
||||
// and the orphan-worktree reaper. Both run on the same periodic timer.
|
||||
agentPool.configure({
|
||||
idleTtlMs: config.AGENT_POOL_IDLE_TTL_MS,
|
||||
maxLive: config.AGENT_POOL_MAX_LIVE,
|
||||
sweepIntervalMs: config.LIFECYCLE_SWEEP_INTERVAL_MS,
|
||||
log: app.log,
|
||||
});
|
||||
agentPool.startReaper(app.log);
|
||||
const orphanReaper = createOrphanWorktreeReaper({
|
||||
sql,
|
||||
log: app.log,
|
||||
intervalMs: config.LIFECYCLE_SWEEP_INTERVAL_MS,
|
||||
graceMs: config.ORPHAN_WORKTREE_GRACE_MS,
|
||||
});
|
||||
orphanReaper.start();
|
||||
|
||||
app.addHook('onClose', async () => {
|
||||
// stop() first so in-flight dispatcher turns settle, then drain the pool.
|
||||
// Pool is empty in Phase 0 (nothing spawns yet) — dispose() is inert.
|
||||
// stop() first so in-flight dispatcher turns settle, then stop the reapers and
|
||||
// drain the pool (kills opencode server + warm ACP children).
|
||||
await dispatcher.stop();
|
||||
orphanReaper.stop();
|
||||
await agentPool.dispose();
|
||||
});
|
||||
|
||||
@@ -199,6 +221,7 @@ async function main() {
|
||||
registerArenaRoutes(app, sql);
|
||||
registerProviderRoutes(app, sql, config);
|
||||
registerWorktreeSafetyRoutes(app, sql);
|
||||
registerLifecycleRoutes(app, sql);
|
||||
registerWebSocket(app, sql, broker);
|
||||
|
||||
// Serve static frontend (built web app). In production, the dist/ is
|
||||
|
||||
Reference in New Issue
Block a user