feat: one Headroom process per Pi process - #1
Conversation
Replace global supervisor pattern with one Headroom process per Pi process. Architecture: - Each Pi spawns exactly one Headroom child process - Headroom starts from current working directory with Code Graph - Supports openai-codex, opencode-go, and all existing providers - Dynamic port allocation on 127.0.0.1 - Health polling with retry logic - Windows process tree termination via taskkill /T - Orphan cleanup on startup - Singleton runtime via globalThis survives /reload - Single-flight start prevents race conditions - Restart limiter (max 3 per 10 minutes) New modules: - types.ts: core types and interfaces - session-runtime.ts: state machine and lifecycle - headroom-process.ts: spawn and health check - windows-process.ts: taskkill-based termination - port-allocation.ts: dynamic port allocation - proxy-options.ts: CLI args and environment - request-identity.ts: per-request headers - headroom-retrieve.ts: CCR retrieval tool - headroom-commands.ts: /headroom-status, restart, stop, log - runtime-state.ts: globalThis singleton - provider-registry.ts: updated with openai-codex and opencode-go Tests: - Provider routing (Codex, OpenCode Go, existing providers) - Port allocation - Session runtime lifecycle - Request identity headers - CCR retrieval with fake server - Windows path handling - Process lifecycle
Fixes from code review:
1. opencode-go routing: use computed baseUrl in registerProvider()
instead of always registering root URL.
2. Limit managed providers to openai-codex and opencode-go only.
Old providers (openai, anthropic, etc.) are not routed through
Headroom in this per-Pi mode.
3. Startup error kills child process: when health check fails,
terminate the Headroom process tree before setting state to failed.
4. Write metadata immediately after spawn (before health check)
so orphan scanner can find processes that failed to start.
5. Wire restart limiter: check canAutomaticallyRestart() in
startInternal() and record successful starts.
6. model_select uses event.model instead of ctx.model for routing.
7. /headroom-status shows runtime.ownerPid as Pi PID and
info.pid as Headroom PID (was showing same PID twice).
8. opencode-go throws error for unknown model.api instead of
silently falling back to root URL.
9. headroom_retrieve uses extensionCtx.cwd instead of
hardcoded process.cwd().
10. Session ID uses monotonic counter to differentiate
session switches within same Pi process.
11. Type definitions narrowed to match actual managed providers.
Обновлённое review PR #1ВердиктСтатус остаётся Оценка текущей реализации: 6/10. Основная архитектура «один Headroom-процесс на один процесс Pi» остаётся правильной. Однако требования к Code Graph изменились: по умолчанию Headroom не должен владеть графом, индексом или filesystem watcher. В рекомендуемой конфигурации: Headroom не должен запускать второй watcher и повторно инициировать индексацию того же worktree. Что уже сделано правильноPR уже содержит полезную основу:
Эти части следует сохранить. Новый архитектурный блокер: безусловный
|
Дальнейшие инструкции агентуВнеси следующий fix-коммит в текущую ветку PR. 1. Добавить Code Graph modeВ export type CodeGraphMode =
| "external"
| "headroom"
| "off";Добавить: export function resolveCodeGraphMode(
env: NodeJS.ProcessEnv = process.env,
): CodeGraphMode {
const raw =
env.PI_HEADROOM_CODE_GRAPH_MODE
?.trim()
.toLowerCase() ?? "external";
switch (raw) {
case "external":
case "headroom":
case "off":
return raw;
default:
throw new Error(
`Invalid PI_HEADROOM_CODE_GRAPH_MODE: ${raw}. ` +
`Expected external, headroom, or off.`,
);
}
}2. Переделать
|
Summary
Replace the global Headroom supervisor pattern with a per-Pi session runtime. Each Pi process now automatically spawns exactly one child Headroom process that:
Architecture
Changes
New Commands
/headroom-status— show process status/headroom-restart— restart Headroom/headroom-stop— stop Headroom/headroom-log— show log pathTesting
All 47 tests pass. TypeScript compiles without errors.