import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; export interface OrchestrationConfig { enabled?: boolean; /** Wake the settled main session after this idle interval. Defaults to five minutes. */ idleWakeMs?: number; /** Send the full review task every time instead of the shorter follow-up task. */ freshAdversaryEachReview?: boolean; /** Agent name used for the independent verifier. */ adversaryAgent?: string; /** * How long to wait for pi-subagents to acknowledge a review request before * treating it as unavailable. Defaults to 10 seconds. */ delegationAckTimeoutMs?: number; } export interface ResolvedOrchestrationConfig { enabled: boolean; idleWakeMs: number; freshAdversaryEachReview: boolean; adversaryAgent: string; delegationAckTimeoutMs: number; } export const DEFAULT_IDLE_WAKE_MS = 5 * 60 * 1000; export const DEFAULT_DELEGATION_ACK_TIMEOUT_MS = 10_000; export const MAX_TIMER_DELAY_MS = 2_147_483_647; export const ORCHESTRATION_ADVERSARY_AGENT = "orchestration-adversary"; function timerDelay(value: unknown, fallback: number): number { return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= MAX_TIMER_DELAY_MS ? value : fallback; } export function resolveOrchestrationConfig(config: OrchestrationConfig | undefined): ResolvedOrchestrationConfig { const agent = typeof config?.adversaryAgent === "string" && config.adversaryAgent.trim() ? config.adversaryAgent.trim() : ORCHESTRATION_ADVERSARY_AGENT; return { // Enabled unless explicitly turned off: installing the extension is the opt-in. enabled: config?.enabled !== false, idleWakeMs: timerDelay(config?.idleWakeMs, DEFAULT_IDLE_WAKE_MS), freshAdversaryEachReview: config?.freshAdversaryEachReview === true, adversaryAgent: agent, delegationAckTimeoutMs: timerDelay( config?.delegationAckTimeoutMs, DEFAULT_DELEGATION_ACK_TIMEOUT_MS, ), }; } function agentDir(): string { return process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent"); } function warning(file: string, message: string): void { process.stderr.write(`continuous-orchestration: ${message} at ${file}\n`); } function validatedConfig(value: unknown, file: string): OrchestrationConfig { if (!value || typeof value !== "object" || Array.isArray(value)) { warning(file, "configuration must be a JSON object; orchestration disabled"); return { enabled: false }; } const source = value as Record; const result: OrchestrationConfig = {}; if (source.enabled !== undefined) { if (typeof source.enabled === "boolean") result.enabled = source.enabled; else { warning(file, '"enabled" must be a boolean; orchestration disabled'); result.enabled = false; } } if (source.idleWakeMs !== undefined) { if (typeof source.idleWakeMs === "number") result.idleWakeMs = source.idleWakeMs; else warning(file, '"idleWakeMs" must be a number; using the default'); } if (source.freshAdversaryEachReview !== undefined) { if (typeof source.freshAdversaryEachReview === "boolean") { result.freshAdversaryEachReview = source.freshAdversaryEachReview; } else { warning(file, '"freshAdversaryEachReview" must be a boolean; using the default'); } } if (source.adversaryAgent !== undefined) { if (typeof source.adversaryAgent === "string") result.adversaryAgent = source.adversaryAgent; else warning(file, '"adversaryAgent" must be a string; using the default'); } if (source.delegationAckTimeoutMs !== undefined) { if (typeof source.delegationAckTimeoutMs === "number") { result.delegationAckTimeoutMs = source.delegationAckTimeoutMs; } else { warning(file, '"delegationAckTimeoutMs" must be a number; using the default'); } } return result; } /** * Read `/extensions/continuous-orchestration/config.json` when present. * Missing config uses defaults. Malformed or unreadable existing config fails * closed so a broken `enabled: false` file cannot silently re-enable the loop. */ export function loadOrchestrationConfig(): OrchestrationConfig | undefined { const file = path.join(agentDir(), "extensions", "continuous-orchestration", "config.json"); let raw: string; try { raw = fs.readFileSync(file, "utf8"); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; warning(file, `cannot read configuration (${error instanceof Error ? error.message : String(error)}); orchestration disabled`); return { enabled: false }; } let parsed: unknown; try { parsed = JSON.parse(raw); } catch { warning(file, "malformed JSON; orchestration disabled"); return { enabled: false }; } if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return validatedConfig(parsed, file); } const root = parsed as Record; return validatedConfig(root.orchestration ?? root, file); }