titan-agent 5.5.30 → 5.5.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,17 +85,29 @@ async function ensureDrivers() {
85
85
  async function resumeDriversAfterRestart() {
86
86
  let resumed = 0;
87
87
  let cancelled = 0;
88
+ let sweptStale = 0;
88
89
  try {
90
+ try {
91
+ const { sweepStaleDriverStates } = await import("./goalDriver.js");
92
+ const swept = sweepStaleDriverStates();
93
+ sweptStale = swept.removed;
94
+ if (swept.removed > 0) {
95
+ logger.info(COMPONENT, `Reaped ${swept.removed} stale driver state(s) (>24h since lastTick): ${swept.ids.join(", ")}`);
96
+ }
97
+ } catch (err) {
98
+ logger.warn(COMPONENT, `stale-driver sweep: ${err.message}`);
99
+ }
89
100
  const drivers = listActiveDrivers();
90
101
  const { listGoals } = await import("./goals.js");
91
102
  const active = new Set(listGoals("active").map((g) => g.id));
92
103
  for (const d of drivers) {
93
104
  if (!active.has(d.goalId)) {
94
105
  try {
95
- const { cancelDriver } = await import("./goalDriver.js");
96
- cancelDriver(d.goalId);
97
- cancelled++;
98
- logger.info(COMPONENT, `Cancelled driver for inactive goal ${d.goalId}`);
106
+ const { deleteDriverState } = await import("./goalDriver.js");
107
+ if (deleteDriverState(d.goalId)) {
108
+ cancelled++;
109
+ logger.info(COMPONENT, `Removed driver state for inactive goal ${d.goalId}`);
110
+ }
99
111
  } catch {
100
112
  }
101
113
  } else {
@@ -106,7 +118,7 @@ async function resumeDriversAfterRestart() {
106
118
  } catch (err) {
107
119
  logger.warn(COMPONENT, `resumeDriversAfterRestart: ${err.message}`);
108
120
  }
109
- return { resumed, cancelled };
121
+ return { resumed, cancelled, sweptStale };
110
122
  }
111
123
  function startDriverScheduler(intervalMs = 1e4, maxConcurrent = 5) {
112
124
  if (schedulerInterval) return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/driverScheduler.ts"],"sourcesContent":["/**\n * TITAN — Driver Scheduler (v4.10.0-local, Phase A)\n *\n * Replaces autopilot's `checkInitiative` path. Every 10 seconds (default),\n * scans active goals and ensures there's exactly one driver running for\n * each. Drivers that have reached terminal phases (done/failed/cancelled)\n * are skipped.\n *\n * Concurrency:\n * - `runningDrivers` set tracks which goalIds currently have a tick\n * in flight. Re-entry is prevented: if a goal is already ticking,\n * the scheduler waits for it to return before firing another tick.\n * - `maxConcurrent` caps how many drivers run at once (default 5).\n * Higher-priority goals jump the queue.\n *\n * Restart resume:\n * - On server bootstrap, `resumeDriversAfterRestart()` walks the\n * state directory and re-activates any driver in a non-terminal\n * phase. The scheduler picks them up on its first tick.\n */\nimport logger from '../utils/logger.js';\nimport {\n tickDriver, listAllDrivers, listActiveDrivers,\n} from './goalDriver.js';\n\nconst COMPONENT = 'DriverScheduler';\n\n// ── State ───────────────────────────────────────────────────────\n\nlet schedulerInterval: NodeJS.Timeout | null = null;\nconst runningDrivers = new Set<string>();\nlet MAX_CONCURRENT = 5;\n\n// ── Core scheduling logic ───────────────────────────────────────\n\n/**\n * One scheduler tick: ensures a tickDriver() is running for every\n * active goal (up to maxConcurrent). Non-blocking: drivers run in\n * parallel via floating promises tracked in `runningDrivers`.\n */\nexport async function ensureDrivers(): Promise<{ started: number; active: number; total: number; reconciled: number }> {\n let started = 0;\n let reconciled = 0;\n try {\n const { listGoals, updateGoal } = await import('./goals.js');\n const activeGoals = listGoals('active');\n const driverStates = listAllDrivers();\n const driverByGoal = new Map(driverStates.map(d => [d.goalId, d]));\n\n // Build the candidate list: skip goals whose driver is already in a\n // terminal phase. If the driver is `done`/`failed`/`cancelled` but\n // the goal is still `active`, reconcile by updating the goal status\n // — this prevents the scheduler from tick-spamming zombie goals\n // whose status never transitioned (historical bug pre-v4.10.0).\n const candidates: Array<{ goal: typeof activeGoals[number]; driverPriority: number }> = [];\n for (const g of activeGoals) {\n const ds = driverByGoal.get(g.id);\n if (ds && (ds.phase === 'done' || ds.phase === 'failed' || ds.phase === 'cancelled')) {\n // Driver is terminal but goal still active → reconcile.\n try {\n const newStatus = ds.phase === 'done' ? 'completed' : 'failed';\n updateGoal(g.id, { status: newStatus });\n reconciled++;\n logger.info(\n COMPONENT,\n `Reconciled zombie goal ${g.id} (\"${g.title.slice(0, 50)}\") — ` +\n `driver=${ds.phase} → goal status=${newStatus}`,\n );\n } catch (err) {\n logger.warn(COMPONENT, `Failed to reconcile goal ${g.id}: ${(err as Error).message}`);\n }\n continue; // skip scheduling\n }\n\n // Pre-driver / legacy zombie: no driver state but all subtasks done +\n // completedAt already set. These pre-date the Goal Driver and can\n // never transition on their own. Close them out.\n const subs = g.subtasks || [];\n const allSubsDone = subs.length > 0 && subs.every(s => s.status === 'done');\n if (!ds && allSubsDone && g.completedAt) {\n try {\n updateGoal(g.id, { status: 'completed' });\n reconciled++;\n logger.info(\n COMPONENT,\n `Reconciled legacy zombie goal ${g.id} (\"${g.title.slice(0, 50)}\") — ` +\n `no driver + all ${subs.length} subtasks done → status=completed`,\n );\n } catch (err) {\n logger.warn(COMPONENT, `Failed to reconcile legacy goal ${g.id}: ${(err as Error).message}`);\n }\n continue;\n }\n\n const driverPriority = ds?.userControls.priority ?? g.priority ?? 3;\n candidates.push({ goal: g, driverPriority });\n }\n\n // Sort by driver priority (user-controlled) ascending = higher priority first\n candidates.sort((a, b) => a.driverPriority - b.driverPriority);\n\n for (const { goal } of candidates) {\n if (runningDrivers.size >= MAX_CONCURRENT) break;\n if (runningDrivers.has(goal.id)) continue;\n runningDrivers.add(goal.id);\n started++;\n // Fire a single tick; don't await — drivers run concurrently\n // up to the cap.\n (async () => {\n try {\n const phase = await tickDriver(goal.id);\n // Only log the FIRST time a driver reaches terminal — the\n // scheduler skips terminal drivers next tick, so this\n // effectively logs the transition, not every re-entry.\n if (phase === 'done' || phase === 'failed' || phase === 'cancelled') {\n logger.info(COMPONENT, `Driver for ${goal.id} reached terminal phase: ${phase}`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Tick for ${goal.id} threw: ${(err as Error).message}`);\n } finally {\n runningDrivers.delete(goal.id);\n }\n })();\n }\n\n return {\n started,\n active: listActiveDrivers().length,\n total: activeGoals.length,\n reconciled,\n };\n } catch (err) {\n logger.warn(COMPONENT, `ensureDrivers failed: ${(err as Error).message}`);\n return { started, active: 0, total: 0, reconciled };\n }\n}\n\n// ── Restart resume ───────────────────────────────────────────────\n\n/**\n * Called on server bootstrap. Scans driver-state/ for non-terminal\n * drivers and makes sure they get re-ticked on the next scheduler\n * pass. No-op besides that — the scheduler handles actual resume.\n *\n * We also sanity-check the goal still exists + is active; if not,\n * the driver state is marked `cancelled` so it doesn't keep ticking\n * a zombie goal.\n */\nexport async function resumeDriversAfterRestart(): Promise<{ resumed: number; cancelled: number }> {\n let resumed = 0;\n let cancelled = 0;\n try {\n const drivers = listActiveDrivers();\n const { listGoals } = await import('./goals.js');\n const active = new Set(listGoals('active').map(g => g.id));\n for (const d of drivers) {\n if (!active.has(d.goalId)) {\n try {\n const { cancelDriver } = await import('./goalDriver.js');\n cancelDriver(d.goalId);\n cancelled++;\n logger.info(COMPONENT, `Cancelled driver for inactive goal ${d.goalId}`);\n } catch { /* ok */ }\n } else {\n resumed++;\n logger.info(COMPONENT, `Resuming driver for ${d.goalId} (phase=${d.phase}, attempts=${d.budget.totalRetries})`);\n }\n }\n } catch (err) {\n logger.warn(COMPONENT, `resumeDriversAfterRestart: ${(err as Error).message}`);\n }\n return { resumed, cancelled };\n}\n\n// ── Lifecycle ────────────────────────────────────────────────────\n\nexport function startDriverScheduler(intervalMs = 10_000, maxConcurrent = 5): void {\n if (schedulerInterval) return;\n MAX_CONCURRENT = maxConcurrent;\n schedulerInterval = setInterval(() => {\n void ensureDrivers();\n }, intervalMs);\n // Don't block process exit\n schedulerInterval.unref?.();\n logger.info(COMPONENT, `Driver scheduler started (interval=${intervalMs}ms, maxConcurrent=${maxConcurrent})`);\n}\n\nexport function stopDriverScheduler(): void {\n if (schedulerInterval) {\n clearInterval(schedulerInterval);\n schedulerInterval = null;\n }\n logger.info(COMPONENT, 'Driver scheduler stopped');\n}\n\nexport function getSchedulerStats(): {\n running: string[];\n maxConcurrent: number;\n intervalActive: boolean;\n} {\n return {\n running: Array.from(runningDrivers),\n maxConcurrent: MAX_CONCURRENT,\n intervalActive: schedulerInterval !== null,\n };\n}\n\n/** Test-only */\nexport function _resetSchedulerForTests(): void {\n stopDriverScheduler();\n runningDrivers.clear();\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/agent/driverScheduler.ts"],"sourcesContent":["/**\n * TITAN — Driver Scheduler (v4.10.0-local, Phase A)\n *\n * Replaces autopilot's `checkInitiative` path. Every 10 seconds (default),\n * scans active goals and ensures there's exactly one driver running for\n * each. Drivers that have reached terminal phases (done/failed/cancelled)\n * are skipped.\n *\n * Concurrency:\n * - `runningDrivers` set tracks which goalIds currently have a tick\n * in flight. Re-entry is prevented: if a goal is already ticking,\n * the scheduler waits for it to return before firing another tick.\n * - `maxConcurrent` caps how many drivers run at once (default 5).\n * Higher-priority goals jump the queue.\n *\n * Restart resume:\n * - On server bootstrap, `resumeDriversAfterRestart()` walks the\n * state directory and re-activates any driver in a non-terminal\n * phase. The scheduler picks them up on its first tick.\n */\nimport logger from '../utils/logger.js';\nimport {\n tickDriver, listAllDrivers, listActiveDrivers,\n} from './goalDriver.js';\n\nconst COMPONENT = 'DriverScheduler';\n\n// ── State ───────────────────────────────────────────────────────\n\nlet schedulerInterval: NodeJS.Timeout | null = null;\nconst runningDrivers = new Set<string>();\nlet MAX_CONCURRENT = 5;\n\n// ── Core scheduling logic ───────────────────────────────────────\n\n/**\n * One scheduler tick: ensures a tickDriver() is running for every\n * active goal (up to maxConcurrent). Non-blocking: drivers run in\n * parallel via floating promises tracked in `runningDrivers`.\n */\nexport async function ensureDrivers(): Promise<{ started: number; active: number; total: number; reconciled: number }> {\n let started = 0;\n let reconciled = 0;\n try {\n const { listGoals, updateGoal } = await import('./goals.js');\n const activeGoals = listGoals('active');\n const driverStates = listAllDrivers();\n const driverByGoal = new Map(driverStates.map(d => [d.goalId, d]));\n\n // Build the candidate list: skip goals whose driver is already in a\n // terminal phase. If the driver is `done`/`failed`/`cancelled` but\n // the goal is still `active`, reconcile by updating the goal status\n // — this prevents the scheduler from tick-spamming zombie goals\n // whose status never transitioned (historical bug pre-v4.10.0).\n const candidates: Array<{ goal: typeof activeGoals[number]; driverPriority: number }> = [];\n for (const g of activeGoals) {\n const ds = driverByGoal.get(g.id);\n if (ds && (ds.phase === 'done' || ds.phase === 'failed' || ds.phase === 'cancelled')) {\n // Driver is terminal but goal still active → reconcile.\n try {\n const newStatus = ds.phase === 'done' ? 'completed' : 'failed';\n updateGoal(g.id, { status: newStatus });\n reconciled++;\n logger.info(\n COMPONENT,\n `Reconciled zombie goal ${g.id} (\"${g.title.slice(0, 50)}\") — ` +\n `driver=${ds.phase} → goal status=${newStatus}`,\n );\n } catch (err) {\n logger.warn(COMPONENT, `Failed to reconcile goal ${g.id}: ${(err as Error).message}`);\n }\n continue; // skip scheduling\n }\n\n // Pre-driver / legacy zombie: no driver state but all subtasks done +\n // completedAt already set. These pre-date the Goal Driver and can\n // never transition on their own. Close them out.\n const subs = g.subtasks || [];\n const allSubsDone = subs.length > 0 && subs.every(s => s.status === 'done');\n if (!ds && allSubsDone && g.completedAt) {\n try {\n updateGoal(g.id, { status: 'completed' });\n reconciled++;\n logger.info(\n COMPONENT,\n `Reconciled legacy zombie goal ${g.id} (\"${g.title.slice(0, 50)}\") — ` +\n `no driver + all ${subs.length} subtasks done → status=completed`,\n );\n } catch (err) {\n logger.warn(COMPONENT, `Failed to reconcile legacy goal ${g.id}: ${(err as Error).message}`);\n }\n continue;\n }\n\n const driverPriority = ds?.userControls.priority ?? g.priority ?? 3;\n candidates.push({ goal: g, driverPriority });\n }\n\n // Sort by driver priority (user-controlled) ascending = higher priority first\n candidates.sort((a, b) => a.driverPriority - b.driverPriority);\n\n for (const { goal } of candidates) {\n if (runningDrivers.size >= MAX_CONCURRENT) break;\n if (runningDrivers.has(goal.id)) continue;\n runningDrivers.add(goal.id);\n started++;\n // Fire a single tick; don't await — drivers run concurrently\n // up to the cap.\n (async () => {\n try {\n const phase = await tickDriver(goal.id);\n // Only log the FIRST time a driver reaches terminal — the\n // scheduler skips terminal drivers next tick, so this\n // effectively logs the transition, not every re-entry.\n if (phase === 'done' || phase === 'failed' || phase === 'cancelled') {\n logger.info(COMPONENT, `Driver for ${goal.id} reached terminal phase: ${phase}`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Tick for ${goal.id} threw: ${(err as Error).message}`);\n } finally {\n runningDrivers.delete(goal.id);\n }\n })();\n }\n\n return {\n started,\n active: listActiveDrivers().length,\n total: activeGoals.length,\n reconciled,\n };\n } catch (err) {\n logger.warn(COMPONENT, `ensureDrivers failed: ${(err as Error).message}`);\n return { started, active: 0, total: 0, reconciled };\n }\n}\n\n// ── Restart resume ───────────────────────────────────────────────\n\n/**\n * Called on server bootstrap. Scans driver-state/ for non-terminal\n * drivers and makes sure they get re-ticked on the next scheduler\n * pass. No-op besides that — the scheduler handles actual resume.\n *\n * We also sanity-check the goal still exists + is active; if not,\n * the driver state is marked `cancelled` so it doesn't keep ticking\n * a zombie goal.\n */\nexport async function resumeDriversAfterRestart(): Promise<{ resumed: number; cancelled: number; sweptStale: number }> {\n let resumed = 0;\n let cancelled = 0;\n let sweptStale = 0;\n try {\n // v5.5.31: first reap files older than 24h with no recent tick.\n // Audit 2026-05-08 found 5 driver-state files at 395+ hours old\n // that previous resume calls had only marked `cancelRequested`\n // without deleting — they accumulated forever.\n try {\n const { sweepStaleDriverStates } = await import('./goalDriver.js');\n const swept = sweepStaleDriverStates();\n sweptStale = swept.removed;\n if (swept.removed > 0) {\n logger.info(COMPONENT, `Reaped ${swept.removed} stale driver state(s) (>24h since lastTick): ${swept.ids.join(', ')}`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `stale-driver sweep: ${(err as Error).message}`);\n }\n\n const drivers = listActiveDrivers();\n const { listGoals } = await import('./goals.js');\n const active = new Set(listGoals('active').map(g => g.id));\n for (const d of drivers) {\n if (!active.has(d.goalId)) {\n try {\n // v5.5.31: actually delete the state file. Previously\n // cancelDriver only flipped `cancelRequested` and the\n // scheduler kept ticking the same zombie. Now the file\n // goes away on this orphan-goal path.\n const { deleteDriverState } = await import('./goalDriver.js');\n if (deleteDriverState(d.goalId)) {\n cancelled++;\n logger.info(COMPONENT, `Removed driver state for inactive goal ${d.goalId}`);\n }\n } catch { /* ok */ }\n } else {\n resumed++;\n logger.info(COMPONENT, `Resuming driver for ${d.goalId} (phase=${d.phase}, attempts=${d.budget.totalRetries})`);\n }\n }\n } catch (err) {\n logger.warn(COMPONENT, `resumeDriversAfterRestart: ${(err as Error).message}`);\n }\n return { resumed, cancelled, sweptStale };\n}\n\n// ── Lifecycle ────────────────────────────────────────────────────\n\nexport function startDriverScheduler(intervalMs = 10_000, maxConcurrent = 5): void {\n if (schedulerInterval) return;\n MAX_CONCURRENT = maxConcurrent;\n schedulerInterval = setInterval(() => {\n void ensureDrivers();\n }, intervalMs);\n // Don't block process exit\n schedulerInterval.unref?.();\n logger.info(COMPONENT, `Driver scheduler started (interval=${intervalMs}ms, maxConcurrent=${maxConcurrent})`);\n}\n\nexport function stopDriverScheduler(): void {\n if (schedulerInterval) {\n clearInterval(schedulerInterval);\n schedulerInterval = null;\n }\n logger.info(COMPONENT, 'Driver scheduler stopped');\n}\n\nexport function getSchedulerStats(): {\n running: string[];\n maxConcurrent: number;\n intervalActive: boolean;\n} {\n return {\n running: Array.from(runningDrivers),\n maxConcurrent: MAX_CONCURRENT,\n intervalActive: schedulerInterval !== null,\n };\n}\n\n/** Test-only */\nexport function _resetSchedulerForTests(): void {\n stopDriverScheduler();\n runningDrivers.clear();\n}\n"],"mappings":"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","names":[]}
@@ -825,6 +825,38 @@ function cancelDriver(goalId) {
825
825
  saveState(s);
826
826
  return true;
827
827
  }
828
+ function deleteDriverState(goalId) {
829
+ const path = statePath(goalId);
830
+ if (!existsSync(path)) return false;
831
+ try {
832
+ rmSync(path, { force: true });
833
+ return true;
834
+ } catch (err) {
835
+ logger.warn(COMPONENT, `deleteDriverState ${goalId}: ${err.message}`);
836
+ return false;
837
+ }
838
+ }
839
+ function sweepStaleDriverStates(maxAgeMs = 24 * 60 * 60 * 1e3) {
840
+ ensureStateDir();
841
+ const removed = [];
842
+ const cutoff = Date.now() - maxAgeMs;
843
+ try {
844
+ const entries = readdirSync(STATE_DIR);
845
+ for (const name of entries) {
846
+ if (!name.endsWith(".json")) continue;
847
+ const goalId = name.replace(/\.json$/, "");
848
+ const s = loadState(goalId);
849
+ if (!s) continue;
850
+ const lastTickMs = new Date(s.lastTickAt || s.startedAt).getTime();
851
+ if (Number.isFinite(lastTickMs) && lastTickMs < cutoff) {
852
+ if (deleteDriverState(goalId)) removed.push(goalId);
853
+ }
854
+ }
855
+ } catch (err) {
856
+ logger.warn(COMPONENT, `sweepStaleDriverStates: ${err.message}`);
857
+ }
858
+ return { removed: removed.length, ids: removed };
859
+ }
828
860
  function reprioritizeDriver(goalId, priority) {
829
861
  const s = loadState(goalId);
830
862
  if (!s) return false;
@@ -848,6 +880,7 @@ function _resetDriverStateForTests(goalId) {
848
880
  export {
849
881
  _resetDriverStateForTests,
850
882
  cancelDriver,
883
+ deleteDriverState,
851
884
  driveGoal,
852
885
  forceUnblockDriver,
853
886
  getDriverState,
@@ -856,6 +889,7 @@ export {
856
889
  pauseDriver,
857
890
  reprioritizeDriver,
858
891
  resumeDriverControl,
892
+ sweepStaleDriverStates,
859
893
  tickDriver
860
894
  };
861
895
  //# sourceMappingURL=goalDriver.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/goalDriver.ts"],"sourcesContent":["/**\n * TITAN — Goal Driver (v4.10.0-local, Phase A)\n *\n * Owns a goal from \"active\" to terminal (done | failed | cancelled).\n * Replaces the passive \"initiative picks one subtask per 5-min tick\"\n * model with an active phase state machine that drives subtasks through\n * specialists, verifies each one, retries on failure via fallbackChain,\n * and reports outcomes back to SOMA.\n *\n * Design principles:\n * - State is persisted to ~/.titan/driver-state/<goalId>.json after\n * every phase transition. Restart-safe.\n * - One tick = one phase transition. Scheduler loops ticks until the\n * driver reaches a terminal phase OR requires waiting (observing a\n * spawned specialist, or blocked on human).\n * - Every phase's state transition is logged to driver state history\n * for UI replay + debugging.\n * - Kill-switch / scope-lock / staging are handled by toolRunner —\n * the driver inherits that protection automatically.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync, renameSync } from 'fs';\nimport { join, dirname } from 'path';\nimport logger from '../utils/logger.js';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport type {\n DriverPhase, DriverState, DriverSubtaskState, DriverHistoryEvent,\n} from './goalDriverTypes.js';\nimport type { SubtaskKind } from './subtaskTaxonomy.js';\nimport { classifyAll } from './subtaskTaxonomy.js';\nimport { routeForKind, pickAttempt } from './specialistRouter.js';\nimport { nextFallback } from './fallbackChain.js';\nimport { DEFAULT_BUDGET_CAPS, checkBudget, suggestDegradation, recordSpend } from './budgetEnforcer.js';\nimport { structuredSpawn } from './structuredSpawn.js';\nimport { verifyByKind } from './verifier.js';\nimport { onGoalCompleted, onGoalFailed, onGoalBlocked } from './somaFeedback.js';\nimport type { Goal, Subtask } from './goals.js';\n\nconst COMPONENT = 'GoalDriver';\nconst STATE_DIR = join(TITAN_HOME, 'driver-state');\n\n// ── Storage ──────────────────────────────────────────────────────\n\nfunction ensureStateDir(): void {\n try { mkdirSync(STATE_DIR, { recursive: true }); } catch { /* ok */ }\n}\n\nfunction statePath(goalId: string): string {\n return join(STATE_DIR, `${goalId}.json`);\n}\n\nfunction loadState(goalId: string): DriverState | null {\n const path = statePath(goalId);\n if (!existsSync(path)) return null;\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8')) as DriverState;\n if (parsed.schemaVersion !== 1) {\n logger.warn(COMPONENT, `State for ${goalId} has unknown schemaVersion=${parsed.schemaVersion} — ignoring`);\n return null;\n }\n return parsed;\n } catch (err) {\n logger.warn(COMPONENT, `Could not parse state for ${goalId}: ${(err as Error).message}`);\n return null;\n }\n}\n\nfunction saveState(state: DriverState): void {\n ensureStateDir();\n state.lastTickAt = new Date().toISOString();\n const path = statePath(state.goalId);\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path + '.tmp', JSON.stringify(state, null, 2));\n // Atomic rename so partial writes never surface\n // (Windows rename-over-existing is OK on Node 22+)\n renameSync(path + '.tmp', path);\n } catch (err) {\n logger.warn(COMPONENT, `Could not persist state for ${state.goalId}: ${(err as Error).message}`);\n }\n}\n\nfunction appendHistory(state: DriverState, phase: DriverPhase, note: string): void {\n const event: DriverHistoryEvent = { at: new Date().toISOString(), phase, note };\n state.history.push(event);\n if (state.history.length > 200) state.history = state.history.slice(-200);\n}\n\n// ── Infrastructure failure detection ──────────────────────────────\n\n/**\n * Detect systematic infrastructure failures that warrant human escalation.\n * Returns true if the error indicates all specialists are failing to produce\n * structured JSON output (thinking patterns, parse errors).\n */\nfunction isInfrastructureFailure(error: string | undefined): boolean {\n if (!error) return false;\n const e = error.toLowerCase();\n // JSON parse failures from structuredSpawn\n const parseFailurePatterns = [\n 'parser could not extract json',\n 'no json block found',\n 'json.parse failure',\n 'prose-fallback:thinking',\n 'thinking prose instead of structured json',\n ];\n return parseFailurePatterns.some(p => e.includes(p));\n}\n\n// ── Init / creation ──────────────────────────────────────────────\n\nfunction freshDriverState(goal: Goal): DriverState {\n const subtaskStates: Record<string, DriverSubtaskState> = {};\n const kinds = classifyAll(goal.subtasks || []);\n for (const sub of goal.subtasks || []) {\n subtaskStates[sub.id] = {\n kind: kinds[sub.id] ?? 'analysis',\n attempts: 0,\n // v4.10.0-local (post-deploy): 5 = full ladder depth\n // (primary + 4 fallbacks). Ensures the claude-code MAX-plan\n // tier is reachable before the subtask gives up. Goal-level\n // maxRetries: 10 remains as the cross-subtask backstop.\n maxAttempts: 5,\n artifacts: [],\n };\n }\n const now = new Date().toISOString();\n return {\n schemaVersion: 1,\n goalId: goal.id,\n phase: 'planning',\n startedAt: now,\n lastTickAt: now,\n budget: { tokensUsed: 0, costUsd: 0, elapsedMs: 0, totalRetries: 0 },\n budgetCaps: { ...DEFAULT_BUDGET_CAPS },\n userControls: {\n paused: false,\n cancelRequested: false,\n priority: (goal.priority as 1 | 2 | 3 | 4 | 5) ?? 3,\n },\n subtaskStates,\n history: [{ at: now, phase: 'planning', note: `Driver started for \"${goal.title}\"` }],\n };\n}\n\n// ── Phase transitions (one tick = one transition) ───────────────\n\nasync function tickPlanning(goal: Goal, state: DriverState): Promise<void> {\n // Ensure subtasks exist (the proposer usually creates them; if a goal\n // came without any, we classify based on title and create a single\n // `analysis` subtask as a placeholder).\n if (!goal.subtasks || goal.subtasks.length === 0) {\n appendHistory(state, 'planning', 'No subtasks — creating single analysis subtask from title');\n try {\n const { addSubtask } = await import('./goals.js');\n addSubtask(goal.id, goal.title, goal.description);\n } catch (err) {\n logger.warn(COMPONENT, `Could not add default subtask to ${goal.id}: ${(err as Error).message}`);\n }\n }\n\n // v4.10.0-local (post-deploy, Fix 9): classify lazily and persist\n // once. Previously this called classifyAll unconditionally on every\n // planning pass, but only persisted new entries — if taxonomy rules\n // changed between boots, restored drivers kept their stale kinds\n // silently. Only classify when we actually need to create a new\n // subtask state entry.\n let kinds: Record<string, ReturnType<typeof classifyAll>[string]> | null = null;\n for (const sub of goal.subtasks || []) {\n if (!state.subtaskStates[sub.id]) {\n if (!kinds) kinds = classifyAll(goal.subtasks || []);\n state.subtaskStates[sub.id] = {\n kind: kinds[sub.id] ?? 'analysis',\n attempts: 0,\n maxAttempts: 5, // per Fix B — matches ladder depth\n artifacts: [],\n };\n }\n }\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Planned: ${Object.keys(state.subtaskStates).length} subtasks classified`);\n}\n\nasync function tickDelegating(goal: Goal, state: DriverState): Promise<void> {\n // Find the next ready subtask (dependencies satisfied, not already done/failed)\n const next = await pickNextReadySubtask(goal, state);\n if (!next) {\n // Re-fetch goal so the allResolved check sees any subtasks that\n // pickNextReadySubtask just marked as failed (durable deadlock\n // recovery from Fix C). Falls back to the stale reference on\n // import failure — worst case we deadlock once more and recover\n // next tick.\n let freshGoal: Goal = goal;\n try {\n const { getGoal } = await import('./goals.js');\n freshGoal = getGoal(goal.id) || goal;\n } catch { /* ok */ }\n // No more ready subtasks — either all done or all blocked on deps.\n // Check if everything's done or failed:\n const allResolved = (freshGoal.subtasks || []).every(\n s => s.status === 'done' || s.status === 'failed' || s.status === 'skipped',\n );\n if (allResolved) {\n // v4.10.0-local (post-deploy): clear currentSubtaskId so\n // tickVerifying takes the whole-goal-verify branch\n // (line 316). Otherwise it keeps trying to verify the\n // last-touched subtask (which may be stale or terminal)\n // and oscillates verifying → iterating → delegating.\n state.currentSubtaskId = undefined;\n state.phase = 'verifying';\n appendHistory(state, 'verifying', 'All subtasks resolved — running final verification');\n } else {\n // Dependencies blocking — pause-block for human\n state.phase = 'blocked';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" is deadlocked. All pending subtasks have unresolved dependencies and none are ready to run. Please review the subtask order and dependencies.`,\n approvalId: '', // filled below if we file an approval\n sinceAt: new Date().toISOString(),\n kind: 'dep_deadlock',\n };\n appendHistory(state, 'blocked', 'Dependency deadlock');\n }\n return;\n }\n\n state.currentSubtaskId = next.id;\n const subState = state.subtaskStates[next.id];\n subState.attempts += 1;\n\n // v4.10.0-local (post-deploy, Fix B): per-subtask attempt cap. A single\n // unlucky subtask can no longer consume the whole goal's retry budget.\n // We fail fast on THIS subtask and let the driver move to the next one.\n const cap = subState.maxAttempts ?? 5;\n if (subState.attempts > cap) {\n subState.verificationResult = {\n passed: false,\n reason: `Per-subtask cap exceeded (${cap})`,\n verifier: 'budget',\n };\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, next.id, subState.lastError || `per-subtask cap exceeded (${cap} attempts)`);\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating'; // next tick picks up the next ready subtask\n appendHistory(state, 'delegating', `Subtask ${next.id} exceeded per-subtask cap (${cap}) — moved on`);\n return;\n }\n\n // Pass the per-subtask cap — NOT goal-level maxRetries — to\n // nextFallback. The ladder has 5 tiers; matching means we can reach\n // the final (claude-code MAX) tier before declaring exhaustion.\n const strategy = nextFallback(subState.kind, subState.attempts - 1, subState.lastError, cap);\n if (!strategy) {\n // v4.10.0-local fix: Check if exhaustion is due to infrastructure failure\n // (systematic JSON parse errors). If so, escalate to human instead of\n // silently failing the subtask.\n if (isInfrastructureFailure(subState.lastError)) {\n state.phase = 'escalated';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" — all specialists failed to produce valid output after ${goal.subtasks?.length ?? 0} subtask(s). This usually means the model is misconfigured, the task is too vague, or the specialist doesn't have the right tools. Please review the goal description or switch the model tier.`,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'infrastructure_failure',\n };\n appendHistory(state, 'escalated', `Subtask ${next.id}: infrastructure failure — all specialists failed JSON output`);\n await fileBlockedApproval(state, goal, [state.blockedReason.question]);\n return;\n }\n\n // Exhausted retries for this subtask (normal failure)\n subState.verificationResult = {\n passed: false,\n reason: 'Max retries exhausted',\n verifier: 'budget',\n };\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, next.id, 'max-retries-exhausted');\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating'; // try next subtask\n appendHistory(state, 'delegating', `Subtask ${next.id} exhausted retries — moved on`);\n return;\n }\n\n subState.specialist = strategy.specialist;\n subState.pendingSpawn = {\n attemptedAt: new Date().toISOString(),\n specialist: strategy.specialist,\n };\n state.phase = 'observing';\n appendHistory(\n state,\n 'observing',\n `Spawning ${strategy.specialist} for subtask \"${next.title}\" (kind=${subState.kind}, attempt ${subState.attempts})`,\n );\n\n // Actually fire the spawn — runs in-tick, driver waits for the return.\n // (Future: async wakeup path so driver can observe multiple concurrent\n // spawns; for now one at a time per goal.)\n try {\n const startMs = Date.now();\n const result = await structuredSpawn({\n specialistId: strategy.specialist,\n task: `${next.title}\\n\\n${next.description}${strategy.promptAdjustment ?? ''}`,\n modelOverride: strategy.modelOverride,\n toolAllowlist: routeForKind(subState.kind).toolAllowlist,\n maxRounds: strategy.maxRounds,\n });\n const durationMs = Date.now() - startMs;\n recordSpend(state, {\n elapsedMs: durationMs,\n tokens: result.tokensUsed ?? 0,\n costUsd: result.costUsd ?? 0,\n });\n\n // Store artifacts\n subState.artifacts = [...new Set([\n ...subState.artifacts,\n ...result.artifacts.map(a => a.ref),\n ])];\n\n // Decide phase based on spawn status\n if (result.status === 'done') {\n state.phase = 'verifying';\n appendHistory(state, 'verifying', `Spawn returned done with ${result.artifacts.length} artifact(s), confidence ${result.confidence.toFixed(2)}`);\n // Stash the spawn result so verifying can read it\n (subState as DriverSubtaskState & { lastSpawnResult?: unknown }).lastSpawnResult = result;\n } else if (result.status === 'failed') {\n subState.lastError = result.reasoning || 'failed';\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Spawn returned failed: ${subState.lastError.slice(0, 120)}`);\n } else if (result.status === 'needs_info' || result.status === 'blocked') {\n state.phase = 'blocked';\n // v4.14.0: build a rich, contextual blocked reason instead of\n // the generic \"Specialist requires input\" that tells the user\n // nothing about what actually went wrong.\n const subtaskTitle = next.title || 'Unnamed subtask';\n const specialistName = strategy.specialist || subState.specialist || 'specialist';\n const attemptCount = subState.attempts;\n const lastErr = subState.lastError;\n const rawQuestion = result.questions[0] ?? '';\n\n let richQuestion: string;\n if (rawQuestion && rawQuestion.length > 10 && !rawQuestion.toLowerCase().includes('specialist requires')) {\n // The specialist gave a real question — use it but wrap with context\n richQuestion = `Goal \"${goal.title}\" is stuck on subtask \"${subtaskTitle}\" (attempt ${attemptCount}, specialist: ${specialistName}).\\n\\n${rawQuestion}`;\n } else if (lastErr) {\n richQuestion = `Goal \"${goal.title}\" — subtask \"${subtaskTitle}\" failed after ${attemptCount} attempt(s) with specialist ${specialistName}.\\n\\nError: ${lastErr.slice(0, 200)}\\n\\nWhat should the specialist do next?`;\n } else {\n richQuestion = `Goal \"${goal.title}\" — subtask \"${subtaskTitle}\" is blocked after ${attemptCount} attempt(s) with specialist ${specialistName}. The specialist could not complete the task and needs guidance on how to proceed.`;\n }\n\n state.blockedReason = {\n question: richQuestion,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'needs_info',\n };\n appendHistory(state, 'blocked', `Spawn needs info: ${richQuestion.slice(0, 120)}`);\n await fileBlockedApproval(state, goal, [richQuestion, ...result.questions.slice(1)]);\n }\n subState.pendingSpawn = undefined;\n } catch (err) {\n const msg = (err as Error).message;\n subState.lastError = msg;\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Spawn threw: ${msg.slice(0, 120)}`);\n }\n}\n\nasync function tickObserving(goal: Goal, state: DriverState): Promise<void> {\n // In Phase A, spawns are sync (await structuredSpawn completes in tickDelegating).\n // This phase exists for future async-wakeup integration; for now it just\n // advances based on whatever subState said after the spawn.\n //\n // v4.10.0-local (post-deploy, Fix 8): be a no-op unless there's a\n // pending spawn waiting for a wakeup. Previously this unconditionally\n // transitioned to iterating, which tickIterating treated as a failure\n // and burned an attempt on goals that never actually spawned. Now the\n // observing phase only advances when there's a spawn to observe.\n void goal;\n const currentId = state.currentSubtaskId;\n const subState = currentId ? state.subtaskStates[currentId] : undefined;\n if (!subState?.pendingSpawn) {\n // Nothing to observe — bounce back to delegating without counting\n // as a retry.\n state.phase = 'delegating';\n return;\n }\n state.phase = 'iterating';\n appendHistory(state, 'iterating', 'Observe tick with no spawn progress — iterating');\n}\n\nasync function tickIterating(goal: Goal, state: DriverState): Promise<void> {\n const currentId = state.currentSubtaskId;\n if (!currentId) {\n state.phase = 'delegating';\n return;\n }\n const subState = state.subtaskStates[currentId];\n\n // v4.10.0-local (post-deploy, Fix A): forward-progress detector.\n // If the same lastError (first 80 chars) repeats 3 times in a row,\n // retrying isn't producing new information — fail this subtask and\n // move on. Prevents the verifying↔iterating→delegating oscillation\n // observed on stuck drivers. Uses a semantic signal (error text)\n // rather than phase-pattern detection, which false-positives on\n // legitimate multi-phase verify passes.\n const fingerprint = (subState.lastError || '').slice(0, 80).toLowerCase().trim();\n if (fingerprint) {\n if (fingerprint === subState.lastErrorFingerprint) {\n subState.consecutiveIdenticalErrors = (subState.consecutiveIdenticalErrors || 0) + 1;\n } else {\n subState.consecutiveIdenticalErrors = 1;\n subState.lastErrorFingerprint = fingerprint;\n }\n if ((subState.consecutiveIdenticalErrors ?? 0) >= 3) {\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, currentId, `Stuck loop: same error 3× in a row: ${fingerprint}`);\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Broke stall loop on ${currentId} (same error 3×)`);\n return;\n }\n }\n\n // v4.10.0-local (post-deploy, Fix 8): do NOT increment totalRetries\n // here. Per-subtask attempts are counted in tickDelegating. Counting\n // again here caused double-billing: one failed spawn consumed 2 budget\n // units. Retry budget is meant to track subtask-transition retries,\n // not individual spawn retries.\n const check = checkBudget(state);\n if (check.status === 'exceeded') {\n const suggestion = suggestDegradation(state);\n if (suggestion === 'ask_human') {\n state.phase = 'blocked';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" — budget exceeded (${check.message}). The driver has used ${state.budget.costUsd.toFixed(2)} USD so far. Continue with extended budget, de-scope, or cancel?`,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'budget_exceeded',\n };\n appendHistory(state, 'blocked', check.message);\n await fileBlockedApproval(state, goal, [state.blockedReason.question]);\n return;\n }\n }\n\n if (subState.attempts >= state.budgetCaps.maxRetries) {\n // Gap 2 (plan-this-logical-ocean): before giving up on this subtask,\n // consult the bounded continuation counter. If the per-subtask cap\n // hasn't been hit (max 2, persisted to disk so restarts can't\n // bypass), halve attempts and let it try again. This is the\n // \"plan_only\" signal — spawns kept producing plans/output that\n // wouldn't verify. Two extra cycles across a restart boundary is\n // the cheapest escape from the verifying↔iterating oscillation that\n // the existing stuck-loop detector can't catch (different errors\n // each time, but no real progress).\n const { shouldContinue } = await import('./runContinuations.js');\n const continuationKey = `${goal.id}:${currentId}`;\n if (shouldContinue(continuationKey, 'plan_only')) {\n const before = subState.attempts;\n subState.attempts = Math.max(0, Math.floor(subState.attempts / 2));\n subState.consecutiveIdenticalErrors = 0;\n subState.lastErrorFingerprint = undefined;\n appendHistory(state, 'delegating', `Continuation granted on ${currentId}: attempts halved ${before} → ${subState.attempts}`);\n state.phase = 'delegating';\n return;\n }\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, currentId, subState.lastError || 'max retries');\n } catch { /* ok */ }\n appendHistory(state, 'delegating', `Subtask ${currentId} failed after ${subState.attempts} attempts`);\n state.phase = 'delegating';\n return;\n }\n\n // Back to delegating to try the next fallback\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Iterating on subtask ${currentId} — attempt ${subState.attempts + 1}`);\n}\n\nasync function tickVerifying(goal: Goal, state: DriverState): Promise<void> {\n const currentId = state.currentSubtaskId;\n if (!currentId) {\n // Whole-goal verify — check per-subtask results against the goal\n // file's subtask statuses. A subtask passes the whole-goal check\n // iff EITHER (a) its verificationResult.passed === true, OR\n // (b) the goal file reports it as done/skipped (completed through\n // an external path, e.g. human marked it done).\n //\n // v4.10.0-local (post-deploy, Fix 8): previous check was\n // `passed !== false`, which treated subtasks that never got\n // verified at all (verificationResult undefined) as passing\n // vacuously. Now we require an explicit pass signal OR an\n // explicit terminal status on the goal-side subtask.\n const goalSubs = goal.subtasks || [];\n const subById = new Map(goalSubs.map(s => [s.id, s]));\n const allPassed = Object.entries(state.subtaskStates).every(([subId, subState]) => {\n if (subState.verificationResult?.passed === true) return true;\n const goalSub = subById.get(subId);\n if (goalSub?.status === 'done' || goalSub?.status === 'skipped') return true;\n return false;\n });\n state.phase = allPassed ? 'reporting' : 'failed';\n appendHistory(state, state.phase, allPassed ? 'All subtasks verified' : 'Some subtasks failed verification');\n return;\n }\n const subState = state.subtaskStates[currentId];\n const subtask = (goal.subtasks || []).find(s => s.id === currentId);\n if (!subtask) {\n state.phase = 'delegating';\n return;\n }\n const lastSpawn = (subState as DriverSubtaskState & { lastSpawnResult?: unknown }).lastSpawnResult;\n if (!lastSpawn) {\n // No spawn result to verify against — iterate\n state.phase = 'iterating';\n appendHistory(state, 'iterating', 'Verify with no spawn result — iterating');\n return;\n }\n\n const verifyResult = await verifyByKind({\n kind: subState.kind,\n subtask,\n spawnResult: lastSpawn as Parameters<typeof verifyByKind>[0]['spawnResult'],\n });\n subState.verificationResult = verifyResult;\n\n if (verifyResult.passed) {\n try {\n const { completeSubtask } = await import('./goals.js');\n // v4.10.0-local polish: store the SPAWN'S actual output\n // (reasoning + artifacts summary), not the verifier's pass\n // message. Prior behavior stored \"Analysis 171 chars, conf 0.95\"\n // in the subtask.result field, losing the actual content.\n const spawn = lastSpawn as Parameters<typeof verifyByKind>[0]['spawnResult'];\n const artifactSummary = spawn.artifacts.length > 0\n ? `\\n\\nArtifacts:\\n${spawn.artifacts.map(a => ` - [${a.type}] ${a.ref}${a.description ? ` — ${a.description}` : ''}`).join('\\n')}`\n : '';\n const contentToSave = (spawn.reasoning || spawn.rawResponse || verifyResult.reason) + artifactSummary;\n completeSubtask(goal.id, currentId, contentToSave);\n } catch { /* ok */ }\n appendHistory(state, 'delegating', `Subtask ${currentId} verified: ${verifyResult.reason.slice(0, 120)}`);\n state.currentSubtaskId = undefined;\n state.phase = 'delegating';\n } else {\n subState.lastError = `Verification failed: ${verifyResult.reason}`;\n appendHistory(state, 'iterating', `Verification failed: ${verifyResult.reason.slice(0, 120)}`);\n state.phase = 'iterating';\n }\n}\n\nasync function tickReporting(goal: Goal, state: DriverState): Promise<void> {\n const durationMs = Date.now() - new Date(state.startedAt).getTime();\n const specialistsUsed = [...new Set(\n Object.values(state.subtaskStates)\n .map(s => s.specialist)\n .filter((x): x is string => !!x),\n )];\n state.retrospective = {\n success: true,\n durationMs,\n tokensUsed: state.budget.tokensUsed,\n costUsd: state.budget.costUsd,\n lessonsLearned: [\n `Completed ${Object.keys(state.subtaskStates).length} subtasks across ${specialistsUsed.length} specialist(s) in ${Math.round(durationMs / 1000)}s`,\n ],\n specialistsUsed,\n };\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'completed' });\n } catch { /* ok */ }\n state.phase = 'done';\n appendHistory(state, 'done', `Goal completed: ${state.retrospective.lessonsLearned[0]}`);\n try { await onGoalCompleted(goal, state); } catch { /* ok */ }\n // v4.10.0-local (Phase B): record retrospective for future goal learning\n try {\n const { saveRetrospective } = await import('./retrospectives.js');\n await saveRetrospective(goal, state);\n } catch { /* ok */ }\n\n // Fire episode\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: 'goal_completed',\n summary: `Driver completed goal \"${goal.title}\" (${Math.round(durationMs / 1000)}s, ${Object.keys(state.subtaskStates).length} subtasks, specialists: ${specialistsUsed.join(', ')})`,\n detail: state.history.slice(-10).map(h => `${h.at} ${h.phase}: ${h.note}`).join('\\n'),\n tags: ['goal-driver', 'goal_completed', goal.id, ...(goal.tags || [])],\n });\n } catch { /* ok */ }\n}\n\nasync function tickBlocked(goal: Goal, state: DriverState): Promise<void> {\n // v4.10.0-local (post-deploy, Fix F): auto-unblock stale blocked states.\n // The block was either never backed by an approval (approvalId empty —\n // bookkeeping bug) or the approval no longer exists (deleted / TTL'd).\n // After 10 minutes of sitting idle with no live approval, retry the\n // subtask rather than sitting forever waiting for a human who has no\n // way to resolve this. Uses forceUnblockDriver so the recovery is\n // consistent with the manual API path.\n const approvalId = state.blockedReason?.approvalId;\n const sinceAt = state.blockedReason?.sinceAt;\n const sinceMs = sinceAt ? Date.now() - new Date(sinceAt).getTime() : 0;\n const STALE_MS = 10 * 60 * 1000;\n if (sinceMs > STALE_MS) {\n let approval: { status?: string } | null = null;\n if (approvalId) {\n try {\n const { getApproval } = await import('./commandPost.js');\n approval = getApproval(approvalId) as { status?: string } | null;\n } catch { /* ok */ }\n }\n if (!approvalId || !approval) {\n // Unblock in-place on the shared state reference. We can't\n // call forceUnblockDriver here because it does its own\n // loadState/saveState cycle, and tickDriver's outer saveState\n // would then overwrite those changes with our stale local\n // state. Mirror forceUnblockDriver's logic here.\n logger.info(COMPONENT, `Auto-unblocking stale blocked state for ${goal.id} (approvalId=${approvalId || 'empty'}, age=${Math.round(sinceMs / 60000)}min)`);\n const note = `stale block auto-recovered (age ${Math.round(sinceMs / 60000)}min)`;\n const wasBudget = state.blockedReason?.kind === 'budget_exceeded'\n || /budget.*exceed|retries exceed/i.test(state.blockedReason?.question || '');\n if (wasBudget) {\n state.budget.totalRetries = Math.floor(state.budget.totalRetries / 2);\n }\n const currentId = state.currentSubtaskId;\n if (currentId && state.subtaskStates[currentId]) {\n const sub = state.subtaskStates[currentId];\n sub.attempts = Math.floor(sub.attempts / 2);\n sub.consecutiveIdenticalErrors = 0;\n }\n state.blockedReason = undefined;\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Force-unblocked: ${note}`);\n return;\n }\n }\n\n // Check if the blocking approval has been decided\n if (!approvalId) return; // nothing to unblock from\n try {\n const { getApproval } = await import('./commandPost.js');\n const approval = getApproval(approvalId);\n if (!approval) return;\n if (approval.status === 'pending') return; // still waiting\n if (approval.status === 'approved') {\n // Incorporate human answer into the current subtask's context\n const currentId = state.currentSubtaskId;\n if (currentId) {\n const subState = state.subtaskStates[currentId];\n const note = approval.decisionNote || 'Approved';\n subState.lastError = `Previous attempt needed info; human provided: \"${note}\". Try again using this.`;\n }\n state.blockedReason = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Unblocked by human (approval ${approvalId})`);\n return;\n }\n if (approval.status === 'rejected') {\n state.phase = 'failed';\n appendHistory(state, 'failed', `Goal rejected by human via approval ${approvalId}`);\n await onGoalFailed(goal, state, 'rejected by human');\n return;\n }\n } catch { /* ok */ }\n}\n\nasync function tickEscalated(goal: Goal, state: DriverState): Promise<void> {\n // v4.10.0-local: Escalated phase handles systematic infrastructure failures.\n // Similar to blocked, but specifically for JSON parse failures that indicate\n // model tier issues. Requires human intervention to fix infrastructure.\n\n const approvalId = state.blockedReason?.approvalId;\n const sinceAt = state.blockedReason?.sinceAt;\n const sinceMs = sinceAt ? Date.now() - new Date(sinceAt).getTime() : 0;\n\n // Check if the escalation approval has been decided\n if (!approvalId) return;\n try {\n const { getApproval } = await import('./commandPost.js');\n const approval = getApproval(approvalId);\n if (!approval) return;\n if (approval.status === 'pending') return; // still waiting\n\n if (approval.status === 'approved') {\n // Human acknowledged the infrastructure issue and wants to retry\n // Reset the subtask attempts to give it another go with potentially\n // new model configuration\n const currentId = state.currentSubtaskId;\n if (currentId) {\n const subState = state.subtaskStates[currentId];\n subState.attempts = 0; // Reset to allow fresh attempts\n subState.consecutiveIdenticalErrors = 0;\n subState.lastError = undefined;\n subState.lastErrorFingerprint = undefined;\n }\n state.blockedReason = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Escalation resolved by human — retrying subtask ${currentId} with fresh attempts`);\n return;\n }\n\n if (approval.status === 'rejected') {\n // Human decided to fail the goal rather than retry\n state.phase = 'failed';\n appendHistory(state, 'failed', `Infrastructure escalation rejected — goal failed`);\n await onGoalFailed(goal, state, 'infrastructure escalation rejected by human');\n return;\n }\n } catch { /* ok */ }\n}\n\nasync function tickFailed(goal: Goal, state: DriverState): Promise<void> {\n const durationMs = Date.now() - new Date(state.startedAt).getTime();\n state.retrospective = {\n success: false,\n durationMs,\n tokensUsed: state.budget.tokensUsed,\n costUsd: state.budget.costUsd,\n lessonsLearned: [\n `Goal failed after ${Math.round(durationMs / 1000)}s, ${state.budget.totalRetries} retries`,\n ],\n specialistsUsed: [...new Set(Object.values(state.subtaskStates).map(s => s.specialist).filter((x): x is string => !!x))],\n };\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'failed' });\n } catch { /* ok */ }\n try { await onGoalFailed(goal, state, 'driver terminated in failed state'); } catch { /* ok */ }\n // Phase B: failed retrospectives are the most valuable — they teach us what to avoid\n try {\n const { saveRetrospective } = await import('./retrospectives.js');\n await saveRetrospective(goal, state);\n } catch { /* ok */ }\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: 'goal_failed',\n summary: `Driver failed goal \"${goal.title}\" after ${Math.round(durationMs / 1000)}s`,\n detail: state.history.slice(-15).map(h => `${h.at} ${h.phase}: ${h.note}`).join('\\n'),\n tags: ['goal-driver', 'goal_failed', goal.id, ...(goal.tags || [])],\n });\n } catch { /* ok */ }\n}\n\nasync function tickCancelled(goal: Goal, state: DriverState): Promise<void> {\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'failed' });\n } catch { /* ok */ }\n appendHistory(state, 'cancelled', 'Cancelled by user');\n}\n\n// ── Helpers ──────────────────────────────────────────────────────\n\nasync function pickNextReadySubtask(goal: Goal, state: DriverState): Promise<Subtask | null> {\n // v4.10.0-local (post-deploy, Fix C): synchronous deadlock recovery.\n // If a pending subtask has exhausted its attempts with a failed\n // verification, await failSubtask to persist the failure *before*\n // continuing. The previous async mutation was ephemeral — the next\n // tick's getGoal() call saw stale data and re-entered the deadlock\n // branch in tickDelegating. This version is durable.\n const cap = (id: string) => state.subtaskStates[id]?.maxAttempts ?? 5;\n for (const sub of goal.subtasks || []) {\n if (sub.status !== 'pending') continue;\n const subState = state.subtaskStates[sub.id];\n if (!subState) continue;\n const exhaustedAttempts = subState.attempts >= cap(sub.id)\n || subState.attempts >= state.budgetCaps.maxRetries;\n if (subState.verificationResult?.passed === false && exhaustedAttempts) {\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, sub.id, subState.lastError || 'max retries exceeded (deadlock recovery)');\n // failSubtask mutates the cached goalsCache in place, so our\n // `goal` reference (passed by the caller, loaded from the\n // same cache) now reflects status: 'failed'. Belt-and-braces:\n // also update our local object in case the cache was bypassed.\n sub.status = 'failed';\n } catch { /* ok — driver will re-try next tick */ }\n continue;\n }\n // Respect dependsOn: skip subtasks whose prerequisites are not completed\n const depsSatisfied = (sub.dependsOn ?? []).every(depId => {\n const dep = goal.subtasks?.find(s => s.id === depId);\n return dep?.status === 'done';\n });\n if (!depsSatisfied) continue;\n return sub;\n }\n return null;\n}\n\nasync function fileBlockedApproval(\n state: DriverState,\n goal: Goal,\n questions: string[],\n): Promise<void> {\n // v4.10.0-local (Phase B): throttle to 1 per (goalId, driver_blocked) per 5 min\n try {\n const { shouldCreateApproval } = await import('./notificationThrottle.js');\n if (!shouldCreateApproval(goal.id, 'driver_blocked')) {\n logger.debug(COMPONENT, `Throttled duplicate driver_blocked approval for goal ${goal.id}`);\n // Still record SOMA feedback since we DID get blocked\n try { await onGoalBlocked(goal, state); } catch { /* ok */ }\n return;\n }\n } catch { /* if throttle module unavailable, fall through */ }\n try {\n const { createApproval } = await import('./commandPost.js');\n const subState = state.currentSubtaskId ? state.subtaskStates[state.currentSubtaskId] : undefined;\n const subtaskTitle = goal.subtasks?.find(s => s.id === state.currentSubtaskId)?.title;\n const approval = createApproval({\n type: 'custom',\n requestedBy: 'goal-driver',\n payload: {\n kind: 'driver_blocked',\n goalId: goal.id,\n goalTitle: goal.title,\n question: questions[0] ?? state.blockedReason?.question ?? 'Specialist requires input',\n allQuestions: questions,\n blockedPhase: state.phase,\n currentSubtaskId: state.currentSubtaskId,\n subtaskKind: subState?.kind,\n subtaskTitle,\n specialist: subState?.specialist,\n attempts: subState?.attempts,\n lastError: subState?.lastError,\n urgency: 'high',\n },\n linkedIssueIds: [],\n });\n if (approval?.id && state.blockedReason) {\n state.blockedReason.approvalId = approval.id;\n }\n // Broadcast via SSE if broadcaster exists (throttled separately)\n try {\n const { shouldBroadcast } = await import('./notificationThrottle.js');\n if (shouldBroadcast('driver:blocked', goal.id)) {\n const g = globalThis as unknown as { __titan_sse_broadcast?: (topic: string, payload: unknown) => void };\n if (typeof g.__titan_sse_broadcast === 'function') {\n g.__titan_sse_broadcast('driver:blocked', {\n goalId: goal.id,\n goalTitle: goal.title,\n question: questions[0] ?? 'Specialist requires input',\n approvalId: approval?.id,\n });\n }\n }\n } catch { /* ok */ }\n } catch (err) {\n logger.warn(COMPONENT, `Could not file blocked-on-human approval: ${(err as Error).message}`);\n }\n // SOMA feedback: small hunger bump + slight purpose stall (via metricGuard)\n try { await onGoalBlocked(goal, state); } catch { /* ok */ }\n}\n\n// ── Main tick loop ──────────────────────────────────────────────\n\nexport async function tickDriver(goalId: string): Promise<DriverPhase> {\n const { getGoal } = await import('./goals.js');\n const goal = getGoal(goalId);\n if (!goal) return 'failed';\n\n let state = loadState(goalId);\n if (!state) {\n state = freshDriverState(goal);\n saveState(state);\n }\n\n // Respect user controls\n if (state.userControls.cancelRequested) {\n state.phase = 'cancelled';\n await tickCancelled(goal, state);\n saveState(state);\n return 'cancelled';\n }\n if (state.userControls.paused) return state.phase;\n\n // Kill switch inherits automatically — toolRunner gates writes,\n // spawn_agent early-returns. Drivers don't need to re-check.\n\n try {\n switch (state.phase) {\n case 'planning': await tickPlanning(goal, state); break;\n case 'delegating': await tickDelegating(goal, state); break;\n case 'observing': await tickObserving(goal, state); break;\n case 'iterating': await tickIterating(goal, state); break;\n case 'verifying': await tickVerifying(goal, state); break;\n case 'reporting': await tickReporting(goal, state); break;\n case 'blocked': await tickBlocked(goal, state); break;\n case 'escalated': await tickEscalated(goal, state); break;\n case 'failed': await tickFailed(goal, state); break;\n case 'done':\n case 'cancelled':\n break;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Tick for ${goalId} threw in phase=${state.phase}: ${(err as Error).message}`);\n appendHistory(state, state.phase, `Tick threw: ${(err as Error).message.slice(0, 120)}`);\n }\n\n saveState(state);\n return state.phase;\n}\n\n/**\n * Drive a goal synchronously to a terminal or waiting state. Ticks in a\n * loop with a small pause between ticks so observing/blocked states give\n * the event loop time to breathe. Returns when the driver is terminal\n * (done/failed/cancelled) or waiting on a human (blocked).\n */\nexport async function driveGoal(goalId: string, maxTicks = 200): Promise<DriverPhase> {\n let last: DriverPhase = 'planning';\n for (let i = 0; i < maxTicks; i++) {\n last = await tickDriver(goalId);\n if (last === 'done' || last === 'failed' || last === 'cancelled' || last === 'blocked' || last === 'escalated') break;\n // Mini-delay between ticks to let IO + timers run\n await new Promise(res => setTimeout(res, 50));\n }\n return last;\n}\n\n// ── External API ────────────────────────────────────────────────\n\nexport function getDriverState(goalId: string): DriverState | null {\n return loadState(goalId);\n}\n\nexport function listActiveDrivers(): DriverState[] {\n ensureStateDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: DriverState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const goalId = file.slice(0, -5);\n const s = loadState(goalId);\n if (s && s.phase !== 'done' && s.phase !== 'failed' && s.phase !== 'cancelled') out.push(s);\n }\n return out;\n}\n\nexport function listAllDrivers(): DriverState[] {\n ensureStateDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: DriverState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const goalId = file.slice(0, -5);\n const s = loadState(goalId);\n if (s) out.push(s);\n }\n return out;\n}\n\nexport function pauseDriver(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.paused = true;\n saveState(s);\n return true;\n}\n\nexport function resumeDriverControl(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.paused = false;\n saveState(s);\n return true;\n}\n\n/**\n * Force a blocked driver back to iterating. Used when the block was\n * triggered by a machine-level failure (parse error, model glitch)\n * rather than a genuine human-answerable question, and the underlying\n * issue has been fixed (e.g. model swap).\n *\n * Also auto-rejects any pending approval the driver was blocked on so\n * it doesn't clutter the Approvals UI forever.\n */\nexport async function forceUnblockDriver(goalId: string, note = 'auto-unblock'): Promise<boolean> {\n const s = loadState(goalId);\n if (!s) return false;\n if (s.phase !== 'blocked') return false;\n\n // Auto-reject the bogus approval if one exists\n const approvalId = s.blockedReason?.approvalId;\n if (approvalId) {\n try {\n const { rejectApproval } = await import('./commandPost.js');\n rejectApproval(approvalId, 'force-unblock', `Auto-rejected by force-unblock: ${note}`);\n } catch { /* ok — approval might not exist anymore */ }\n }\n\n // If the block was budget-related, halve the retry counter so the\n // driver has headroom to retry with the underlying fix in place.\n // This is preferable to a full reset — it still records that the\n // goal has been difficult, just gives it another shot.\n const wasBudget = s.blockedReason?.kind === 'budget_exceeded'\n || /budget.*exceed|retries exceed/i.test(s.blockedReason?.question || '');\n if (wasBudget) {\n s.budget.totalRetries = Math.floor(s.budget.totalRetries / 2);\n appendHistory(s, 'iterating', `Force-unblock: budget halved (${s.budget.totalRetries} / ${s.budgetCaps.maxRetries} retries)`);\n }\n\n // v4.10.0-local (post-deploy, Fix F): also halve the per-subtask\n // attempt counter for the current subtask. Otherwise the subtask\n // re-hits its per-subtask cap immediately on resume (from Fix B)\n // and we just re-enter the blocked state next tick.\n const currentId = s.currentSubtaskId;\n if (currentId && s.subtaskStates[currentId]) {\n const sub = s.subtaskStates[currentId];\n const before = sub.attempts;\n sub.attempts = Math.floor(sub.attempts / 2);\n sub.consecutiveIdenticalErrors = 0; // reset stall detector too\n appendHistory(s, 'iterating', `Force-unblock: per-subtask attempts halved on ${currentId} (${before} → ${sub.attempts})`);\n }\n\n s.blockedReason = undefined;\n s.phase = 'iterating'; // iterating retries the current subtask with a fresh spawn\n appendHistory(s, 'iterating', `Force-unblocked: ${note}`);\n saveState(s);\n logger.info(COMPONENT, `Force-unblocked driver ${goalId}: ${note}${wasBudget ? ' (budget halved)' : ''}`);\n return true;\n}\n\nexport function cancelDriver(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.cancelRequested = true;\n saveState(s);\n return true;\n}\n\nexport function reprioritizeDriver(goalId: string, priority: 1 | 2 | 3 | 4 | 5): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.priority = priority;\n saveState(s);\n return true;\n}\n\nexport function _resetDriverStateForTests(goalId?: string): void {\n if (goalId) {\n try { rmSync(statePath(goalId), { force: true }); } catch { /* ok */ }\n } else {\n try { rmSync(STATE_DIR, { recursive: true, force: true }); } catch { /* ok */ }\n }\n}\n"],"mappings":";AAoBA,SAAS,YAAY,WAAW,cAAc,eAAe,aAAa,QAAQ,kBAAkB;AACpG,SAAS,MAAM,eAAe;AAC9B,OAAO,YAAY;AACnB,SAAS,kBAAkB;AAK3B,SAAS,mBAAmB;AAC5B,SAAS,oBAAiC;AAC1C,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB,aAAa,oBAAoB,mBAAmB;AAClF,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB,cAAc,qBAAqB;AAG7D,MAAM,YAAY;AAClB,MAAM,YAAY,KAAK,YAAY,cAAc;AAIjD,SAAS,iBAAuB;AAC5B,MAAI;AAAE,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,EAAG,QAAQ;AAAA,EAAW;AACxE;AAEA,SAAS,UAAU,QAAwB;AACvC,SAAO,KAAK,WAAW,GAAG,MAAM,OAAO;AAC3C;AAEA,SAAS,UAAU,QAAoC;AACnD,QAAM,OAAO,UAAU,MAAM;AAC7B,MAAI,CAAC,WAAW,IAAI,EAAG,QAAO;AAC9B,MAAI;AACA,UAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AACrD,QAAI,OAAO,kBAAkB,GAAG;AAC5B,aAAO,KAAK,WAAW,aAAa,MAAM,8BAA8B,OAAO,aAAa,kBAAa;AACzG,aAAO;AAAA,IACX;AACA,WAAO;AAAA,EACX,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,6BAA6B,MAAM,KAAM,IAAc,OAAO,EAAE;AACvF,WAAO;AAAA,EACX;AACJ;AAEA,SAAS,UAAU,OAA0B;AACzC,iBAAe;AACf,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAC1C,QAAM,OAAO,UAAU,MAAM,MAAM;AACnC,MAAI;AACA,cAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,kBAAc,OAAO,QAAQ,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAG3D,eAAW,OAAO,QAAQ,IAAI;AAAA,EAClC,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,+BAA+B,MAAM,MAAM,KAAM,IAAc,OAAO,EAAE;AAAA,EACnG;AACJ;AAEA,SAAS,cAAc,OAAoB,OAAoB,MAAoB;AAC/E,QAAM,QAA4B,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,GAAG,OAAO,KAAK;AAC9E,QAAM,QAAQ,KAAK,KAAK;AACxB,MAAI,MAAM,QAAQ,SAAS,IAAK,OAAM,UAAU,MAAM,QAAQ,MAAM,IAAI;AAC5E;AASA,SAAS,wBAAwB,OAAoC;AACjE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,IAAI,MAAM,YAAY;AAE5B,QAAM,uBAAuB;AAAA,IACzB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACA,SAAO,qBAAqB,KAAK,OAAK,EAAE,SAAS,CAAC,CAAC;AACvD;AAIA,SAAS,iBAAiB,MAAyB;AAC/C,QAAM,gBAAoD,CAAC;AAC3D,QAAM,QAAQ,YAAY,KAAK,YAAY,CAAC,CAAC;AAC7C,aAAW,OAAO,KAAK,YAAY,CAAC,GAAG;AACnC,kBAAc,IAAI,EAAE,IAAI;AAAA,MACpB,MAAM,MAAM,IAAI,EAAE,KAAK;AAAA,MACvB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA,MAKV,aAAa;AAAA,MACb,WAAW,CAAC;AAAA,IAChB;AAAA,EACJ;AACA,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,SAAO;AAAA,IACH,eAAe;AAAA,IACf,QAAQ,KAAK;AAAA,IACb,OAAO;AAAA,IACP,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,QAAQ,EAAE,YAAY,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,EAAE;AAAA,IACnE,YAAY,EAAE,GAAG,oBAAoB;AAAA,IACrC,cAAc;AAAA,MACV,QAAQ;AAAA,MACR,iBAAiB;AAAA,MACjB,UAAW,KAAK,YAAkC;AAAA,IACtD;AAAA,IACA;AAAA,IACA,SAAS,CAAC,EAAE,IAAI,KAAK,OAAO,YAAY,MAAM,uBAAuB,KAAK,KAAK,IAAI,CAAC;AAAA,EACxF;AACJ;AAIA,eAAe,aAAa,MAAY,OAAmC;AAIvE,MAAI,CAAC,KAAK,YAAY,KAAK,SAAS,WAAW,GAAG;AAC9C,kBAAc,OAAO,YAAY,gEAA2D;AAC5F,QAAI;AACA,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,YAAY;AAChD,iBAAW,KAAK,IAAI,KAAK,OAAO,KAAK,WAAW;AAAA,IACpD,SAAS,KAAK;AACV,aAAO,KAAK,WAAW,oCAAoC,KAAK,EAAE,KAAM,IAAc,OAAO,EAAE;AAAA,IACnG;AAAA,EACJ;AAQA,MAAI,QAAuE;AAC3E,aAAW,OAAO,KAAK,YAAY,CAAC,GAAG;AACnC,QAAI,CAAC,MAAM,cAAc,IAAI,EAAE,GAAG;AAC9B,UAAI,CAAC,MAAO,SAAQ,YAAY,KAAK,YAAY,CAAC,CAAC;AACnD,YAAM,cAAc,IAAI,EAAE,IAAI;AAAA,QAC1B,MAAM,MAAM,IAAI,EAAE,KAAK;AAAA,QACvB,UAAU;AAAA,QACV,aAAa;AAAA;AAAA,QACb,WAAW,CAAC;AAAA,MAChB;AAAA,IACJ;AAAA,EACJ;AACA,QAAM,QAAQ;AACd,gBAAc,OAAO,cAAc,YAAY,OAAO,KAAK,MAAM,aAAa,EAAE,MAAM,sBAAsB;AAChH;AAEA,eAAe,eAAe,MAAY,OAAmC;AAEzE,QAAM,OAAO,MAAM,qBAAqB,MAAM,KAAK;AACnD,MAAI,CAAC,MAAM;AAMP,QAAI,YAAkB;AACtB,QAAI;AACA,YAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,YAAY;AAC7C,kBAAY,QAAQ,KAAK,EAAE,KAAK;AAAA,IACpC,QAAQ;AAAA,IAAW;AAGnB,UAAM,eAAe,UAAU,YAAY,CAAC,GAAG;AAAA,MAC3C,OAAK,EAAE,WAAW,UAAU,EAAE,WAAW,YAAY,EAAE,WAAW;AAAA,IACtE;AACA,QAAI,aAAa;AAMb,YAAM,mBAAmB;AACzB,YAAM,QAAQ;AACd,oBAAc,OAAO,aAAa,yDAAoD;AAAA,IAC1F,OAAO;AAEH,YAAM,QAAQ;AACd,YAAM,gBAAgB;AAAA,QAClB,UAAU,SAAS,KAAK,KAAK;AAAA,QAC7B,YAAY;AAAA;AAAA,QACZ,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,QAChC,MAAM;AAAA,MACV;AACA,oBAAc,OAAO,WAAW,qBAAqB;AAAA,IACzD;AACA;AAAA,EACJ;AAEA,QAAM,mBAAmB,KAAK;AAC9B,QAAM,WAAW,MAAM,cAAc,KAAK,EAAE;AAC5C,WAAS,YAAY;AAKrB,QAAM,MAAM,SAAS,eAAe;AACpC,MAAI,SAAS,WAAW,KAAK;AACzB,aAAS,qBAAqB;AAAA,MAC1B,QAAQ;AAAA,MACR,QAAQ,6BAA6B,GAAG;AAAA,MACxC,UAAU;AAAA,IACd;AACA,QAAI;AACA,YAAM,EAAE,YAAY,IAAI,MAAM,OAAO,YAAY;AACjD,kBAAY,KAAK,IAAI,KAAK,IAAI,SAAS,aAAa,6BAA6B,GAAG,YAAY;AAAA,IACpG,QAAQ;AAAA,IAAW;AACnB,UAAM,mBAAmB;AACzB,UAAM,QAAQ;AACd,kBAAc,OAAO,cAAc,WAAW,KAAK,EAAE,8BAA8B,GAAG,mBAAc;AACpG;AAAA,EACJ;AAKA,QAAM,WAAW,aAAa,SAAS,MAAM,SAAS,WAAW,GAAG,SAAS,WAAW,GAAG;AAC3F,MAAI,CAAC,UAAU;AAIX,QAAI,wBAAwB,SAAS,SAAS,GAAG;AAC7C,YAAM,QAAQ;AACd,YAAM,gBAAgB;AAAA,QAClB,UAAU,SAAS,KAAK,KAAK,iEAA4D,KAAK,UAAU,UAAU,CAAC;AAAA,QACnH,YAAY;AAAA,QACZ,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,QAChC,MAAM;AAAA,MACV;AACA,oBAAc,OAAO,aAAa,WAAW,KAAK,EAAE,oEAA+D;AACnH,YAAM,oBAAoB,OAAO,MAAM,CAAC,MAAM,cAAc,QAAQ,CAAC;AACrE;AAAA,IACJ;AAGA,aAAS,qBAAqB;AAAA,MAC1B,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,UAAU;AAAA,IACd;AACA,QAAI;AACA,YAAM,EAAE,YAAY,IAAI,MAAM,OAAO,YAAY;AACjD,kBAAY,KAAK,IAAI,KAAK,IAAI,uBAAuB;AAAA,IACzD,QAAQ;AAAA,IAAW;AACnB,UAAM,mBAAmB;AACzB,UAAM,QAAQ;AACd,kBAAc,OAAO,cAAc,WAAW,KAAK,EAAE,oCAA+B;AACpF;AAAA,EACJ;AAEA,WAAS,aAAa,SAAS;AAC/B,WAAS,eAAe;AAAA,IACpB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,YAAY,SAAS;AAAA,EACzB;AACA,QAAM,QAAQ;AACd;AAAA,IACI;AAAA,IACA;AAAA,IACA,YAAY,SAAS,UAAU,iBAAiB,KAAK,KAAK,WAAW,SAAS,IAAI,aAAa,SAAS,QAAQ;AAAA,EACpH;AAKA,MAAI;AACA,UAAM,UAAU,KAAK,IAAI;AACzB,UAAM,SAAS,MAAM,gBAAgB;AAAA,MACjC,cAAc,SAAS;AAAA,MACvB,MAAM,GAAG,KAAK,KAAK;AAAA;AAAA,EAAO,KAAK,WAAW,GAAG,SAAS,oBAAoB,EAAE;AAAA,MAC5E,eAAe,SAAS;AAAA,MACxB,eAAe,aAAa,SAAS,IAAI,EAAE;AAAA,MAC3C,WAAW,SAAS;AAAA,IACxB,CAAC;AACD,UAAM,aAAa,KAAK,IAAI,IAAI;AAChC,gBAAY,OAAO;AAAA,MACf,WAAW;AAAA,MACX,QAAQ,OAAO,cAAc;AAAA,MAC7B,SAAS,OAAO,WAAW;AAAA,IAC/B,CAAC;AAGD,aAAS,YAAY,CAAC,GAAG,oBAAI,IAAI;AAAA,MAC7B,GAAG,SAAS;AAAA,MACZ,GAAG,OAAO,UAAU,IAAI,OAAK,EAAE,GAAG;AAAA,IACtC,CAAC,CAAC;AAGF,QAAI,OAAO,WAAW,QAAQ;AAC1B,YAAM,QAAQ;AACd,oBAAc,OAAO,aAAa,4BAA4B,OAAO,UAAU,MAAM,4BAA4B,OAAO,WAAW,QAAQ,CAAC,CAAC,EAAE;AAE/I,MAAC,SAAgE,kBAAkB;AAAA,IACvF,WAAW,OAAO,WAAW,UAAU;AACnC,eAAS,YAAY,OAAO,aAAa;AACzC,YAAM,QAAQ;AACd,oBAAc,OAAO,aAAa,0BAA0B,SAAS,UAAU,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,IAClG,WAAW,OAAO,WAAW,gBAAgB,OAAO,WAAW,WAAW;AACtE,YAAM,QAAQ;AAId,YAAM,eAAe,KAAK,SAAS;AACnC,YAAM,iBAAiB,SAAS,cAAc,SAAS,cAAc;AACrE,YAAM,eAAe,SAAS;AAC9B,YAAM,UAAU,SAAS;AACzB,YAAM,cAAc,OAAO,UAAU,CAAC,KAAK;AAE3C,UAAI;AACJ,UAAI,eAAe,YAAY,SAAS,MAAM,CAAC,YAAY,YAAY,EAAE,SAAS,qBAAqB,GAAG;AAEtG,uBAAe,SAAS,KAAK,KAAK,0BAA0B,YAAY,cAAc,YAAY,iBAAiB,cAAc;AAAA;AAAA,EAAS,WAAW;AAAA,MACzJ,WAAW,SAAS;AAChB,uBAAe,SAAS,KAAK,KAAK,qBAAgB,YAAY,kBAAkB,YAAY,+BAA+B,cAAc;AAAA;AAAA,SAAe,QAAQ,MAAM,GAAG,GAAG,CAAC;AAAA;AAAA;AAAA,MACjL,OAAO;AACH,uBAAe,SAAS,KAAK,KAAK,qBAAgB,YAAY,sBAAsB,YAAY,+BAA+B,cAAc;AAAA,MACjJ;AAEA,YAAM,gBAAgB;AAAA,QAClB,UAAU;AAAA,QACV,YAAY;AAAA,QACZ,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,QAChC,MAAM;AAAA,MACV;AACA,oBAAc,OAAO,WAAW,qBAAqB,aAAa,MAAM,GAAG,GAAG,CAAC,EAAE;AACjF,YAAM,oBAAoB,OAAO,MAAM,CAAC,cAAc,GAAG,OAAO,UAAU,MAAM,CAAC,CAAC,CAAC;AAAA,IACvF;AACA,aAAS,eAAe;AAAA,EAC5B,SAAS,KAAK;AACV,UAAM,MAAO,IAAc;AAC3B,aAAS,YAAY;AACrB,UAAM,QAAQ;AACd,kBAAc,OAAO,aAAa,gBAAgB,IAAI,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACzE;AACJ;AAEA,eAAe,cAAc,MAAY,OAAmC;AAUxE,OAAK;AACL,QAAM,YAAY,MAAM;AACxB,QAAM,WAAW,YAAY,MAAM,cAAc,SAAS,IAAI;AAC9D,MAAI,CAAC,UAAU,cAAc;AAGzB,UAAM,QAAQ;AACd;AAAA,EACJ;AACA,QAAM,QAAQ;AACd,gBAAc,OAAO,aAAa,sDAAiD;AACvF;AAEA,eAAe,cAAc,MAAY,OAAmC;AACxE,QAAM,YAAY,MAAM;AACxB,MAAI,CAAC,WAAW;AACZ,UAAM,QAAQ;AACd;AAAA,EACJ;AACA,QAAM,WAAW,MAAM,cAAc,SAAS;AAS9C,QAAM,eAAe,SAAS,aAAa,IAAI,MAAM,GAAG,EAAE,EAAE,YAAY,EAAE,KAAK;AAC/E,MAAI,aAAa;AACb,QAAI,gBAAgB,SAAS,sBAAsB;AAC/C,eAAS,8BAA8B,SAAS,8BAA8B,KAAK;AAAA,IACvF,OAAO;AACH,eAAS,6BAA6B;AACtC,eAAS,uBAAuB;AAAA,IACpC;AACA,SAAK,SAAS,8BAA8B,MAAM,GAAG;AACjD,UAAI;AACA,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,YAAY;AACjD,oBAAY,KAAK,IAAI,WAAW,0CAAuC,WAAW,EAAE;AAAA,MACxF,QAAQ;AAAA,MAAW;AACnB,YAAM,mBAAmB;AACzB,YAAM,QAAQ;AACd,oBAAc,OAAO,cAAc,uBAAuB,SAAS,qBAAkB;AACrF;AAAA,IACJ;AAAA,EACJ;AAOA,QAAM,QAAQ,YAAY,KAAK;AAC/B,MAAI,MAAM,WAAW,YAAY;AAC7B,UAAM,aAAa,mBAAmB,KAAK;AAC3C,QAAI,eAAe,aAAa;AAC5B,YAAM,QAAQ;AACd,YAAM,gBAAgB;AAAA,QAClB,UAAU,SAAS,KAAK,KAAK,6BAAwB,MAAM,OAAO,0BAA0B,MAAM,OAAO,QAAQ,QAAQ,CAAC,CAAC;AAAA,QAC3H,YAAY;AAAA,QACZ,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,QAChC,MAAM;AAAA,MACV;AACA,oBAAc,OAAO,WAAW,MAAM,OAAO;AAC7C,YAAM,oBAAoB,OAAO,MAAM,CAAC,MAAM,cAAc,QAAQ,CAAC;AACrE;AAAA,IACJ;AAAA,EACJ;AAEA,MAAI,SAAS,YAAY,MAAM,WAAW,YAAY;AAUlD,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,uBAAuB;AAC/D,UAAM,kBAAkB,GAAG,KAAK,EAAE,IAAI,SAAS;AAC/C,QAAI,eAAe,iBAAiB,WAAW,GAAG;AAC9C,YAAM,SAAS,SAAS;AACxB,eAAS,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,WAAW,CAAC,CAAC;AACjE,eAAS,6BAA6B;AACtC,eAAS,uBAAuB;AAChC,oBAAc,OAAO,cAAc,2BAA2B,SAAS,qBAAqB,MAAM,WAAM,SAAS,QAAQ,EAAE;AAC3H,YAAM,QAAQ;AACd;AAAA,IACJ;AACA,QAAI;AACA,YAAM,EAAE,YAAY,IAAI,MAAM,OAAO,YAAY;AACjD,kBAAY,KAAK,IAAI,WAAW,SAAS,aAAa,aAAa;AAAA,IACvE,QAAQ;AAAA,IAAW;AACnB,kBAAc,OAAO,cAAc,WAAW,SAAS,iBAAiB,SAAS,QAAQ,WAAW;AACpG,UAAM,QAAQ;AACd;AAAA,EACJ;AAGA,QAAM,QAAQ;AACd,gBAAc,OAAO,cAAc,wBAAwB,SAAS,mBAAc,SAAS,WAAW,CAAC,EAAE;AAC7G;AAEA,eAAe,cAAc,MAAY,OAAmC;AACxE,QAAM,YAAY,MAAM;AACxB,MAAI,CAAC,WAAW;AAYZ,UAAM,WAAW,KAAK,YAAY,CAAC;AACnC,UAAM,UAAU,IAAI,IAAI,SAAS,IAAI,OAAK,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;AACpD,UAAM,YAAY,OAAO,QAAQ,MAAM,aAAa,EAAE,MAAM,CAAC,CAAC,OAAOA,SAAQ,MAAM;AAC/E,UAAIA,UAAS,oBAAoB,WAAW,KAAM,QAAO;AACzD,YAAM,UAAU,QAAQ,IAAI,KAAK;AACjC,UAAI,SAAS,WAAW,UAAU,SAAS,WAAW,UAAW,QAAO;AACxE,aAAO;AAAA,IACX,CAAC;AACD,UAAM,QAAQ,YAAY,cAAc;AACxC,kBAAc,OAAO,MAAM,OAAO,YAAY,0BAA0B,mCAAmC;AAC3G;AAAA,EACJ;AACA,QAAM,WAAW,MAAM,cAAc,SAAS;AAC9C,QAAM,WAAW,KAAK,YAAY,CAAC,GAAG,KAAK,OAAK,EAAE,OAAO,SAAS;AAClE,MAAI,CAAC,SAAS;AACV,UAAM,QAAQ;AACd;AAAA,EACJ;AACA,QAAM,YAAa,SAAgE;AACnF,MAAI,CAAC,WAAW;AAEZ,UAAM,QAAQ;AACd,kBAAc,OAAO,aAAa,8CAAyC;AAC3E;AAAA,EACJ;AAEA,QAAM,eAAe,MAAM,aAAa;AAAA,IACpC,MAAM,SAAS;AAAA,IACf;AAAA,IACA,aAAa;AAAA,EACjB,CAAC;AACD,WAAS,qBAAqB;AAE9B,MAAI,aAAa,QAAQ;AACrB,QAAI;AACA,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,YAAY;AAKrD,YAAM,QAAQ;AACd,YAAM,kBAAkB,MAAM,UAAU,SAAS,IAC3C;AAAA;AAAA;AAAA,EAAmB,MAAM,UAAU,IAAI,OAAK,QAAQ,EAAE,IAAI,KAAK,EAAE,GAAG,GAAG,EAAE,cAAc,WAAM,EAAE,WAAW,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC,KAC/H;AACN,YAAM,iBAAiB,MAAM,aAAa,MAAM,eAAe,aAAa,UAAU;AACtF,sBAAgB,KAAK,IAAI,WAAW,aAAa;AAAA,IACrD,QAAQ;AAAA,IAAW;AACnB,kBAAc,OAAO,cAAc,WAAW,SAAS,cAAc,aAAa,OAAO,MAAM,GAAG,GAAG,CAAC,EAAE;AACxG,UAAM,mBAAmB;AACzB,UAAM,QAAQ;AAAA,EAClB,OAAO;AACH,aAAS,YAAY,wBAAwB,aAAa,MAAM;AAChE,kBAAc,OAAO,aAAa,wBAAwB,aAAa,OAAO,MAAM,GAAG,GAAG,CAAC,EAAE;AAC7F,UAAM,QAAQ;AAAA,EAClB;AACJ;AAEA,eAAe,cAAc,MAAY,OAAmC;AACxE,QAAM,aAAa,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AAClE,QAAM,kBAAkB,CAAC,GAAG,IAAI;AAAA,IAC5B,OAAO,OAAO,MAAM,aAAa,EAC5B,IAAI,OAAK,EAAE,UAAU,EACrB,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,EACvC,CAAC;AACD,QAAM,gBAAgB;AAAA,IAClB,SAAS;AAAA,IACT;AAAA,IACA,YAAY,MAAM,OAAO;AAAA,IACzB,SAAS,MAAM,OAAO;AAAA,IACtB,gBAAgB;AAAA,MACZ,aAAa,OAAO,KAAK,MAAM,aAAa,EAAE,MAAM,oBAAoB,gBAAgB,MAAM,qBAAqB,KAAK,MAAM,aAAa,GAAI,CAAC;AAAA,IACpJ;AAAA,IACA;AAAA,EACJ;AACA,MAAI;AACA,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,YAAY;AAChD,eAAW,KAAK,IAAI,EAAE,QAAQ,YAAY,CAAC;AAAA,EAC/C,QAAQ;AAAA,EAAW;AACnB,QAAM,QAAQ;AACd,gBAAc,OAAO,QAAQ,mBAAmB,MAAM,cAAc,eAAe,CAAC,CAAC,EAAE;AACvF,MAAI;AAAE,UAAM,gBAAgB,MAAM,KAAK;AAAA,EAAG,QAAQ;AAAA,EAAW;AAE7D,MAAI;AACA,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qBAAqB;AAChE,UAAM,kBAAkB,MAAM,KAAK;AAAA,EACvC,QAAQ;AAAA,EAAW;AAGnB,MAAI;AACA,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAuB;AAC9D,kBAAc;AAAA,MACV,MAAM;AAAA,MACN,SAAS,0BAA0B,KAAK,KAAK,MAAM,KAAK,MAAM,aAAa,GAAI,CAAC,MAAM,OAAO,KAAK,MAAM,aAAa,EAAE,MAAM,2BAA2B,gBAAgB,KAAK,IAAI,CAAC;AAAA,MAClL,QAAQ,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAK,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI;AAAA,MACpF,MAAM,CAAC,eAAe,kBAAkB,KAAK,IAAI,GAAI,KAAK,QAAQ,CAAC,CAAE;AAAA,IACzE,CAAC;AAAA,EACL,QAAQ;AAAA,EAAW;AACvB;AAEA,eAAe,YAAY,MAAY,OAAmC;AAQtE,QAAM,aAAa,MAAM,eAAe;AACxC,QAAM,UAAU,MAAM,eAAe;AACrC,QAAM,UAAU,UAAU,KAAK,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,QAAQ,IAAI;AACrE,QAAM,WAAW,KAAK,KAAK;AAC3B,MAAI,UAAU,UAAU;AACpB,QAAI,WAAuC;AAC3C,QAAI,YAAY;AACZ,UAAI;AACA,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,kBAAkB;AACvD,mBAAW,YAAY,UAAU;AAAA,MACrC,QAAQ;AAAA,MAAW;AAAA,IACvB;AACA,QAAI,CAAC,cAAc,CAAC,UAAU;AAM1B,aAAO,KAAK,WAAW,2CAA2C,KAAK,EAAE,gBAAgB,cAAc,OAAO,SAAS,KAAK,MAAM,UAAU,GAAK,CAAC,MAAM;AACxJ,YAAM,OAAO,mCAAmC,KAAK,MAAM,UAAU,GAAK,CAAC;AAC3E,YAAM,YAAY,MAAM,eAAe,SAAS,qBACzC,iCAAiC,KAAK,MAAM,eAAe,YAAY,EAAE;AAChF,UAAI,WAAW;AACX,cAAM,OAAO,eAAe,KAAK,MAAM,MAAM,OAAO,eAAe,CAAC;AAAA,MACxE;AACA,YAAM,YAAY,MAAM;AACxB,UAAI,aAAa,MAAM,cAAc,SAAS,GAAG;AAC7C,cAAM,MAAM,MAAM,cAAc,SAAS;AACzC,YAAI,WAAW,KAAK,MAAM,IAAI,WAAW,CAAC;AAC1C,YAAI,6BAA6B;AAAA,MACrC;AACA,YAAM,gBAAgB;AACtB,YAAM,QAAQ;AACd,oBAAc,OAAO,aAAa,oBAAoB,IAAI,EAAE;AAC5D;AAAA,IACJ;AAAA,EACJ;AAGA,MAAI,CAAC,WAAY;AACjB,MAAI;AACA,UAAM,EAAE,YAAY,IAAI,MAAM,OAAO,kBAAkB;AACvD,UAAM,WAAW,YAAY,UAAU;AACvC,QAAI,CAAC,SAAU;AACf,QAAI,SAAS,WAAW,UAAW;AACnC,QAAI,SAAS,WAAW,YAAY;AAEhC,YAAM,YAAY,MAAM;AACxB,UAAI,WAAW;AACX,cAAM,WAAW,MAAM,cAAc,SAAS;AAC9C,cAAM,OAAO,SAAS,gBAAgB;AACtC,iBAAS,YAAY,kDAAkD,IAAI;AAAA,MAC/E;AACA,YAAM,gBAAgB;AACtB,YAAM,QAAQ;AACd,oBAAc,OAAO,cAAc,gCAAgC,UAAU,GAAG;AAChF;AAAA,IACJ;AACA,QAAI,SAAS,WAAW,YAAY;AAChC,YAAM,QAAQ;AACd,oBAAc,OAAO,UAAU,uCAAuC,UAAU,EAAE;AAClF,YAAM,aAAa,MAAM,OAAO,mBAAmB;AACnD;AAAA,IACJ;AAAA,EACJ,QAAQ;AAAA,EAAW;AACvB;AAEA,eAAe,cAAc,MAAY,OAAmC;AAKxE,QAAM,aAAa,MAAM,eAAe;AACxC,QAAM,UAAU,MAAM,eAAe;AACrC,QAAM,UAAU,UAAU,KAAK,IAAI,IAAI,IAAI,KAAK,OAAO,EAAE,QAAQ,IAAI;AAGrE,MAAI,CAAC,WAAY;AACjB,MAAI;AACA,UAAM,EAAE,YAAY,IAAI,MAAM,OAAO,kBAAkB;AACvD,UAAM,WAAW,YAAY,UAAU;AACvC,QAAI,CAAC,SAAU;AACf,QAAI,SAAS,WAAW,UAAW;AAEnC,QAAI,SAAS,WAAW,YAAY;AAIhC,YAAM,YAAY,MAAM;AACxB,UAAI,WAAW;AACX,cAAM,WAAW,MAAM,cAAc,SAAS;AAC9C,iBAAS,WAAW;AACpB,iBAAS,6BAA6B;AACtC,iBAAS,YAAY;AACrB,iBAAS,uBAAuB;AAAA,MACpC;AACA,YAAM,gBAAgB;AACtB,YAAM,QAAQ;AACd,oBAAc,OAAO,cAAc,wDAAmD,SAAS,sBAAsB;AACrH;AAAA,IACJ;AAEA,QAAI,SAAS,WAAW,YAAY;AAEhC,YAAM,QAAQ;AACd,oBAAc,OAAO,UAAU,uDAAkD;AACjF,YAAM,aAAa,MAAM,OAAO,6CAA6C;AAC7E;AAAA,IACJ;AAAA,EACJ,QAAQ;AAAA,EAAW;AACvB;AAEA,eAAe,WAAW,MAAY,OAAmC;AACrE,QAAM,aAAa,KAAK,IAAI,IAAI,IAAI,KAAK,MAAM,SAAS,EAAE,QAAQ;AAClE,QAAM,gBAAgB;AAAA,IAClB,SAAS;AAAA,IACT;AAAA,IACA,YAAY,MAAM,OAAO;AAAA,IACzB,SAAS,MAAM,OAAO;AAAA,IACtB,gBAAgB;AAAA,MACZ,qBAAqB,KAAK,MAAM,aAAa,GAAI,CAAC,MAAM,MAAM,OAAO,YAAY;AAAA,IACrF;AAAA,IACA,iBAAiB,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,MAAM,aAAa,EAAE,IAAI,OAAK,EAAE,UAAU,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,EAC3H;AACA,MAAI;AACA,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,YAAY;AAChD,eAAW,KAAK,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EAC5C,QAAQ;AAAA,EAAW;AACnB,MAAI;AAAE,UAAM,aAAa,MAAM,OAAO,mCAAmC;AAAA,EAAG,QAAQ;AAAA,EAAW;AAE/F,MAAI;AACA,UAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qBAAqB;AAChE,UAAM,kBAAkB,MAAM,KAAK;AAAA,EACvC,QAAQ;AAAA,EAAW;AACnB,MAAI;AACA,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAuB;AAC9D,kBAAc;AAAA,MACV,MAAM;AAAA,MACN,SAAS,uBAAuB,KAAK,KAAK,WAAW,KAAK,MAAM,aAAa,GAAI,CAAC;AAAA,MAClF,QAAQ,MAAM,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAK,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,IAAI;AAAA,MACpF,MAAM,CAAC,eAAe,eAAe,KAAK,IAAI,GAAI,KAAK,QAAQ,CAAC,CAAE;AAAA,IACtE,CAAC;AAAA,EACL,QAAQ;AAAA,EAAW;AACvB;AAEA,eAAe,cAAc,MAAY,OAAmC;AACxE,MAAI;AACA,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,YAAY;AAChD,eAAW,KAAK,IAAI,EAAE,QAAQ,SAAS,CAAC;AAAA,EAC5C,QAAQ;AAAA,EAAW;AACnB,gBAAc,OAAO,aAAa,mBAAmB;AACzD;AAIA,eAAe,qBAAqB,MAAY,OAA6C;AAOzF,QAAM,MAAM,CAAC,OAAe,MAAM,cAAc,EAAE,GAAG,eAAe;AACpE,aAAW,OAAO,KAAK,YAAY,CAAC,GAAG;AACnC,QAAI,IAAI,WAAW,UAAW;AAC9B,UAAM,WAAW,MAAM,cAAc,IAAI,EAAE;AAC3C,QAAI,CAAC,SAAU;AACf,UAAM,oBAAoB,SAAS,YAAY,IAAI,IAAI,EAAE,KAClD,SAAS,YAAY,MAAM,WAAW;AAC7C,QAAI,SAAS,oBAAoB,WAAW,SAAS,mBAAmB;AACpE,UAAI;AACA,cAAM,EAAE,YAAY,IAAI,MAAM,OAAO,YAAY;AACjD,oBAAY,KAAK,IAAI,IAAI,IAAI,SAAS,aAAa,0CAA0C;AAK7F,YAAI,SAAS;AAAA,MACjB,QAAQ;AAAA,MAA0C;AAClD;AAAA,IACJ;AAEA,UAAM,iBAAiB,IAAI,aAAa,CAAC,GAAG,MAAM,WAAS;AACvD,YAAM,MAAM,KAAK,UAAU,KAAK,OAAK,EAAE,OAAO,KAAK;AACnD,aAAO,KAAK,WAAW;AAAA,IAC3B,CAAC;AACD,QAAI,CAAC,cAAe;AACpB,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAEA,eAAe,oBACX,OACA,MACA,WACa;AAEb,MAAI;AACA,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,2BAA2B;AACzE,QAAI,CAAC,qBAAqB,KAAK,IAAI,gBAAgB,GAAG;AAClD,aAAO,MAAM,WAAW,wDAAwD,KAAK,EAAE,EAAE;AAEzF,UAAI;AAAE,cAAM,cAAc,MAAM,KAAK;AAAA,MAAG,QAAQ;AAAA,MAAW;AAC3D;AAAA,IACJ;AAAA,EACJ,QAAQ;AAAA,EAAqD;AAC7D,MAAI;AACA,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kBAAkB;AAC1D,UAAM,WAAW,MAAM,mBAAmB,MAAM,cAAc,MAAM,gBAAgB,IAAI;AACxF,UAAM,eAAe,KAAK,UAAU,KAAK,OAAK,EAAE,OAAO,MAAM,gBAAgB,GAAG;AAChF,UAAM,WAAW,eAAe;AAAA,MAC5B,MAAM;AAAA,MACN,aAAa;AAAA,MACb,SAAS;AAAA,QACL,MAAM;AAAA,QACN,QAAQ,KAAK;AAAA,QACb,WAAW,KAAK;AAAA,QAChB,UAAU,UAAU,CAAC,KAAK,MAAM,eAAe,YAAY;AAAA,QAC3D,cAAc;AAAA,QACd,cAAc,MAAM;AAAA,QACpB,kBAAkB,MAAM;AAAA,QACxB,aAAa,UAAU;AAAA,QACvB;AAAA,QACA,YAAY,UAAU;AAAA,QACtB,UAAU,UAAU;AAAA,QACpB,WAAW,UAAU;AAAA,QACrB,SAAS;AAAA,MACb;AAAA,MACA,gBAAgB,CAAC;AAAA,IACrB,CAAC;AACD,QAAI,UAAU,MAAM,MAAM,eAAe;AACrC,YAAM,cAAc,aAAa,SAAS;AAAA,IAC9C;AAEA,QAAI;AACA,YAAM,EAAE,gBAAgB,IAAI,MAAM,OAAO,2BAA2B;AACpE,UAAI,gBAAgB,kBAAkB,KAAK,EAAE,GAAG;AAC5C,cAAM,IAAI;AACV,YAAI,OAAO,EAAE,0BAA0B,YAAY;AAC/C,YAAE,sBAAsB,kBAAkB;AAAA,YACtC,QAAQ,KAAK;AAAA,YACb,WAAW,KAAK;AAAA,YAChB,UAAU,UAAU,CAAC,KAAK;AAAA,YAC1B,YAAY,UAAU;AAAA,UAC1B,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,QAAQ;AAAA,IAAW;AAAA,EACvB,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,6CAA8C,IAAc,OAAO,EAAE;AAAA,EAChG;AAEA,MAAI;AAAE,UAAM,cAAc,MAAM,KAAK;AAAA,EAAG,QAAQ;AAAA,EAAW;AAC/D;AAIA,eAAsB,WAAW,QAAsC;AACnE,QAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,YAAY;AAC7C,QAAM,OAAO,QAAQ,MAAM;AAC3B,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,QAAQ,UAAU,MAAM;AAC5B,MAAI,CAAC,OAAO;AACR,YAAQ,iBAAiB,IAAI;AAC7B,cAAU,KAAK;AAAA,EACnB;AAGA,MAAI,MAAM,aAAa,iBAAiB;AACpC,UAAM,QAAQ;AACd,UAAM,cAAc,MAAM,KAAK;AAC/B,cAAU,KAAK;AACf,WAAO;AAAA,EACX;AACA,MAAI,MAAM,aAAa,OAAQ,QAAO,MAAM;AAK5C,MAAI;AACA,YAAQ,MAAM,OAAO;AAAA,MACjB,KAAK;AAAe,cAAM,aAAa,MAAM,KAAK;AAAG;AAAA,MACrD,KAAK;AAAe,cAAM,eAAe,MAAM,KAAK;AAAG;AAAA,MACvD,KAAK;AAAe,cAAM,cAAc,MAAM,KAAK;AAAG;AAAA,MACtD,KAAK;AAAe,cAAM,cAAc,MAAM,KAAK;AAAG;AAAA,MACtD,KAAK;AAAe,cAAM,cAAc,MAAM,KAAK;AAAG;AAAA,MACtD,KAAK;AAAe,cAAM,cAAc,MAAM,KAAK;AAAG;AAAA,MACtD,KAAK;AAAe,cAAM,YAAY,MAAM,KAAK;AAAG;AAAA,MACpD,KAAK;AAAe,cAAM,cAAc,MAAM,KAAK;AAAG;AAAA,MACtD,KAAK;AAAe,cAAM,WAAW,MAAM,KAAK;AAAG;AAAA,MACnD,KAAK;AAAA,MACL,KAAK;AACD;AAAA,IACR;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,YAAY,MAAM,mBAAmB,MAAM,KAAK,KAAM,IAAc,OAAO,EAAE;AACpG,kBAAc,OAAO,MAAM,OAAO,eAAgB,IAAc,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC3F;AAEA,YAAU,KAAK;AACf,SAAO,MAAM;AACjB;AAQA,eAAsB,UAAU,QAAgB,WAAW,KAA2B;AAClF,MAAI,OAAoB;AACxB,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AAC/B,WAAO,MAAM,WAAW,MAAM;AAC9B,QAAI,SAAS,UAAU,SAAS,YAAY,SAAS,eAAe,SAAS,aAAa,SAAS,YAAa;AAEhH,UAAM,IAAI,QAAQ,SAAO,WAAW,KAAK,EAAE,CAAC;AAAA,EAChD;AACA,SAAO;AACX;AAIO,SAAS,eAAe,QAAoC;AAC/D,SAAO,UAAU,MAAM;AAC3B;AAEO,SAAS,oBAAmC;AAC/C,iBAAe;AACf,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO,CAAC;AACpC,QAAM,MAAqB,CAAC;AAC5B,aAAW,QAAQ,YAAY,SAAS,GAAG;AACvC,QAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,UAAM,SAAS,KAAK,MAAM,GAAG,EAAE;AAC/B,UAAM,IAAI,UAAU,MAAM;AAC1B,QAAI,KAAK,EAAE,UAAU,UAAU,EAAE,UAAU,YAAY,EAAE,UAAU,YAAa,KAAI,KAAK,CAAC;AAAA,EAC9F;AACA,SAAO;AACX;AAEO,SAAS,iBAAgC;AAC5C,iBAAe;AACf,MAAI,CAAC,WAAW,SAAS,EAAG,QAAO,CAAC;AACpC,QAAM,MAAqB,CAAC;AAC5B,aAAW,QAAQ,YAAY,SAAS,GAAG;AACvC,QAAI,CAAC,KAAK,SAAS,OAAO,EAAG;AAC7B,UAAM,SAAS,KAAK,MAAM,GAAG,EAAE;AAC/B,UAAM,IAAI,UAAU,MAAM;AAC1B,QAAI,EAAG,KAAI,KAAK,CAAC;AAAA,EACrB;AACA,SAAO;AACX;AAEO,SAAS,YAAY,QAAyB;AACjD,QAAM,IAAI,UAAU,MAAM;AAC1B,MAAI,CAAC,EAAG,QAAO;AACf,IAAE,aAAa,SAAS;AACxB,YAAU,CAAC;AACX,SAAO;AACX;AAEO,SAAS,oBAAoB,QAAyB;AACzD,QAAM,IAAI,UAAU,MAAM;AAC1B,MAAI,CAAC,EAAG,QAAO;AACf,IAAE,aAAa,SAAS;AACxB,YAAU,CAAC;AACX,SAAO;AACX;AAWA,eAAsB,mBAAmB,QAAgB,OAAO,gBAAkC;AAC9F,QAAM,IAAI,UAAU,MAAM;AAC1B,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,EAAE,UAAU,UAAW,QAAO;AAGlC,QAAM,aAAa,EAAE,eAAe;AACpC,MAAI,YAAY;AACZ,QAAI;AACA,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kBAAkB;AAC1D,qBAAe,YAAY,iBAAiB,mCAAmC,IAAI,EAAE;AAAA,IACzF,QAAQ;AAAA,IAA8C;AAAA,EAC1D;AAMA,QAAM,YAAY,EAAE,eAAe,SAAS,qBACrC,iCAAiC,KAAK,EAAE,eAAe,YAAY,EAAE;AAC5E,MAAI,WAAW;AACX,MAAE,OAAO,eAAe,KAAK,MAAM,EAAE,OAAO,eAAe,CAAC;AAC5D,kBAAc,GAAG,aAAa,iCAAiC,EAAE,OAAO,YAAY,MAAM,EAAE,WAAW,UAAU,WAAW;AAAA,EAChI;AAMA,QAAM,YAAY,EAAE;AACpB,MAAI,aAAa,EAAE,cAAc,SAAS,GAAG;AACzC,UAAM,MAAM,EAAE,cAAc,SAAS;AACrC,UAAM,SAAS,IAAI;AACnB,QAAI,WAAW,KAAK,MAAM,IAAI,WAAW,CAAC;AAC1C,QAAI,6BAA6B;AACjC,kBAAc,GAAG,aAAa,iDAAiD,SAAS,KAAK,MAAM,WAAM,IAAI,QAAQ,GAAG;AAAA,EAC5H;AAEA,IAAE,gBAAgB;AAClB,IAAE,QAAQ;AACV,gBAAc,GAAG,aAAa,oBAAoB,IAAI,EAAE;AACxD,YAAU,CAAC;AACX,SAAO,KAAK,WAAW,0BAA0B,MAAM,KAAK,IAAI,GAAG,YAAY,qBAAqB,EAAE,EAAE;AACxG,SAAO;AACX;AAEO,SAAS,aAAa,QAAyB;AAClD,QAAM,IAAI,UAAU,MAAM;AAC1B,MAAI,CAAC,EAAG,QAAO;AACf,IAAE,aAAa,kBAAkB;AACjC,YAAU,CAAC;AACX,SAAO;AACX;AAEO,SAAS,mBAAmB,QAAgB,UAAsC;AACrF,QAAM,IAAI,UAAU,MAAM;AAC1B,MAAI,CAAC,EAAG,QAAO;AACf,IAAE,aAAa,WAAW;AAC1B,YAAU,CAAC;AACX,SAAO;AACX;AAEO,SAAS,0BAA0B,QAAuB;AAC7D,MAAI,QAAQ;AACR,QAAI;AAAE,aAAO,UAAU,MAAM,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAW;AAAA,EACzE,OAAO;AACH,QAAI;AAAE,aAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IAAG,QAAQ;AAAA,IAAW;AAAA,EAClF;AACJ;","names":["subState"]}
1
+ {"version":3,"sources":["../../src/agent/goalDriver.ts"],"sourcesContent":["/**\n * TITAN — Goal Driver (v4.10.0-local, Phase A)\n *\n * Owns a goal from \"active\" to terminal (done | failed | cancelled).\n * Replaces the passive \"initiative picks one subtask per 5-min tick\"\n * model with an active phase state machine that drives subtasks through\n * specialists, verifies each one, retries on failure via fallbackChain,\n * and reports outcomes back to SOMA.\n *\n * Design principles:\n * - State is persisted to ~/.titan/driver-state/<goalId>.json after\n * every phase transition. Restart-safe.\n * - One tick = one phase transition. Scheduler loops ticks until the\n * driver reaches a terminal phase OR requires waiting (observing a\n * spawned specialist, or blocked on human).\n * - Every phase's state transition is logged to driver state history\n * for UI replay + debugging.\n * - Kill-switch / scope-lock / staging are handled by toolRunner —\n * the driver inherits that protection automatically.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, rmSync, renameSync } from 'fs';\nimport { join, dirname } from 'path';\nimport logger from '../utils/logger.js';\nimport { TITAN_HOME } from '../utils/constants.js';\nimport type {\n DriverPhase, DriverState, DriverSubtaskState, DriverHistoryEvent,\n} from './goalDriverTypes.js';\nimport type { SubtaskKind } from './subtaskTaxonomy.js';\nimport { classifyAll } from './subtaskTaxonomy.js';\nimport { routeForKind, pickAttempt } from './specialistRouter.js';\nimport { nextFallback } from './fallbackChain.js';\nimport { DEFAULT_BUDGET_CAPS, checkBudget, suggestDegradation, recordSpend } from './budgetEnforcer.js';\nimport { structuredSpawn } from './structuredSpawn.js';\nimport { verifyByKind } from './verifier.js';\nimport { onGoalCompleted, onGoalFailed, onGoalBlocked } from './somaFeedback.js';\nimport type { Goal, Subtask } from './goals.js';\n\nconst COMPONENT = 'GoalDriver';\nconst STATE_DIR = join(TITAN_HOME, 'driver-state');\n\n// ── Storage ──────────────────────────────────────────────────────\n\nfunction ensureStateDir(): void {\n try { mkdirSync(STATE_DIR, { recursive: true }); } catch { /* ok */ }\n}\n\nfunction statePath(goalId: string): string {\n return join(STATE_DIR, `${goalId}.json`);\n}\n\nfunction loadState(goalId: string): DriverState | null {\n const path = statePath(goalId);\n if (!existsSync(path)) return null;\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8')) as DriverState;\n if (parsed.schemaVersion !== 1) {\n logger.warn(COMPONENT, `State for ${goalId} has unknown schemaVersion=${parsed.schemaVersion} — ignoring`);\n return null;\n }\n return parsed;\n } catch (err) {\n logger.warn(COMPONENT, `Could not parse state for ${goalId}: ${(err as Error).message}`);\n return null;\n }\n}\n\nfunction saveState(state: DriverState): void {\n ensureStateDir();\n state.lastTickAt = new Date().toISOString();\n const path = statePath(state.goalId);\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path + '.tmp', JSON.stringify(state, null, 2));\n // Atomic rename so partial writes never surface\n // (Windows rename-over-existing is OK on Node 22+)\n renameSync(path + '.tmp', path);\n } catch (err) {\n logger.warn(COMPONENT, `Could not persist state for ${state.goalId}: ${(err as Error).message}`);\n }\n}\n\nfunction appendHistory(state: DriverState, phase: DriverPhase, note: string): void {\n const event: DriverHistoryEvent = { at: new Date().toISOString(), phase, note };\n state.history.push(event);\n if (state.history.length > 200) state.history = state.history.slice(-200);\n}\n\n// ── Infrastructure failure detection ──────────────────────────────\n\n/**\n * Detect systematic infrastructure failures that warrant human escalation.\n * Returns true if the error indicates all specialists are failing to produce\n * structured JSON output (thinking patterns, parse errors).\n */\nfunction isInfrastructureFailure(error: string | undefined): boolean {\n if (!error) return false;\n const e = error.toLowerCase();\n // JSON parse failures from structuredSpawn\n const parseFailurePatterns = [\n 'parser could not extract json',\n 'no json block found',\n 'json.parse failure',\n 'prose-fallback:thinking',\n 'thinking prose instead of structured json',\n ];\n return parseFailurePatterns.some(p => e.includes(p));\n}\n\n// ── Init / creation ──────────────────────────────────────────────\n\nfunction freshDriverState(goal: Goal): DriverState {\n const subtaskStates: Record<string, DriverSubtaskState> = {};\n const kinds = classifyAll(goal.subtasks || []);\n for (const sub of goal.subtasks || []) {\n subtaskStates[sub.id] = {\n kind: kinds[sub.id] ?? 'analysis',\n attempts: 0,\n // v4.10.0-local (post-deploy): 5 = full ladder depth\n // (primary + 4 fallbacks). Ensures the claude-code MAX-plan\n // tier is reachable before the subtask gives up. Goal-level\n // maxRetries: 10 remains as the cross-subtask backstop.\n maxAttempts: 5,\n artifacts: [],\n };\n }\n const now = new Date().toISOString();\n return {\n schemaVersion: 1,\n goalId: goal.id,\n phase: 'planning',\n startedAt: now,\n lastTickAt: now,\n budget: { tokensUsed: 0, costUsd: 0, elapsedMs: 0, totalRetries: 0 },\n budgetCaps: { ...DEFAULT_BUDGET_CAPS },\n userControls: {\n paused: false,\n cancelRequested: false,\n priority: (goal.priority as 1 | 2 | 3 | 4 | 5) ?? 3,\n },\n subtaskStates,\n history: [{ at: now, phase: 'planning', note: `Driver started for \"${goal.title}\"` }],\n };\n}\n\n// ── Phase transitions (one tick = one transition) ───────────────\n\nasync function tickPlanning(goal: Goal, state: DriverState): Promise<void> {\n // Ensure subtasks exist (the proposer usually creates them; if a goal\n // came without any, we classify based on title and create a single\n // `analysis` subtask as a placeholder).\n if (!goal.subtasks || goal.subtasks.length === 0) {\n appendHistory(state, 'planning', 'No subtasks — creating single analysis subtask from title');\n try {\n const { addSubtask } = await import('./goals.js');\n addSubtask(goal.id, goal.title, goal.description);\n } catch (err) {\n logger.warn(COMPONENT, `Could not add default subtask to ${goal.id}: ${(err as Error).message}`);\n }\n }\n\n // v4.10.0-local (post-deploy, Fix 9): classify lazily and persist\n // once. Previously this called classifyAll unconditionally on every\n // planning pass, but only persisted new entries — if taxonomy rules\n // changed between boots, restored drivers kept their stale kinds\n // silently. Only classify when we actually need to create a new\n // subtask state entry.\n let kinds: Record<string, ReturnType<typeof classifyAll>[string]> | null = null;\n for (const sub of goal.subtasks || []) {\n if (!state.subtaskStates[sub.id]) {\n if (!kinds) kinds = classifyAll(goal.subtasks || []);\n state.subtaskStates[sub.id] = {\n kind: kinds[sub.id] ?? 'analysis',\n attempts: 0,\n maxAttempts: 5, // per Fix B — matches ladder depth\n artifacts: [],\n };\n }\n }\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Planned: ${Object.keys(state.subtaskStates).length} subtasks classified`);\n}\n\nasync function tickDelegating(goal: Goal, state: DriverState): Promise<void> {\n // Find the next ready subtask (dependencies satisfied, not already done/failed)\n const next = await pickNextReadySubtask(goal, state);\n if (!next) {\n // Re-fetch goal so the allResolved check sees any subtasks that\n // pickNextReadySubtask just marked as failed (durable deadlock\n // recovery from Fix C). Falls back to the stale reference on\n // import failure — worst case we deadlock once more and recover\n // next tick.\n let freshGoal: Goal = goal;\n try {\n const { getGoal } = await import('./goals.js');\n freshGoal = getGoal(goal.id) || goal;\n } catch { /* ok */ }\n // No more ready subtasks — either all done or all blocked on deps.\n // Check if everything's done or failed:\n const allResolved = (freshGoal.subtasks || []).every(\n s => s.status === 'done' || s.status === 'failed' || s.status === 'skipped',\n );\n if (allResolved) {\n // v4.10.0-local (post-deploy): clear currentSubtaskId so\n // tickVerifying takes the whole-goal-verify branch\n // (line 316). Otherwise it keeps trying to verify the\n // last-touched subtask (which may be stale or terminal)\n // and oscillates verifying → iterating → delegating.\n state.currentSubtaskId = undefined;\n state.phase = 'verifying';\n appendHistory(state, 'verifying', 'All subtasks resolved — running final verification');\n } else {\n // Dependencies blocking — pause-block for human\n state.phase = 'blocked';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" is deadlocked. All pending subtasks have unresolved dependencies and none are ready to run. Please review the subtask order and dependencies.`,\n approvalId: '', // filled below if we file an approval\n sinceAt: new Date().toISOString(),\n kind: 'dep_deadlock',\n };\n appendHistory(state, 'blocked', 'Dependency deadlock');\n }\n return;\n }\n\n state.currentSubtaskId = next.id;\n const subState = state.subtaskStates[next.id];\n subState.attempts += 1;\n\n // v4.10.0-local (post-deploy, Fix B): per-subtask attempt cap. A single\n // unlucky subtask can no longer consume the whole goal's retry budget.\n // We fail fast on THIS subtask and let the driver move to the next one.\n const cap = subState.maxAttempts ?? 5;\n if (subState.attempts > cap) {\n subState.verificationResult = {\n passed: false,\n reason: `Per-subtask cap exceeded (${cap})`,\n verifier: 'budget',\n };\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, next.id, subState.lastError || `per-subtask cap exceeded (${cap} attempts)`);\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating'; // next tick picks up the next ready subtask\n appendHistory(state, 'delegating', `Subtask ${next.id} exceeded per-subtask cap (${cap}) — moved on`);\n return;\n }\n\n // Pass the per-subtask cap — NOT goal-level maxRetries — to\n // nextFallback. The ladder has 5 tiers; matching means we can reach\n // the final (claude-code MAX) tier before declaring exhaustion.\n const strategy = nextFallback(subState.kind, subState.attempts - 1, subState.lastError, cap);\n if (!strategy) {\n // v4.10.0-local fix: Check if exhaustion is due to infrastructure failure\n // (systematic JSON parse errors). If so, escalate to human instead of\n // silently failing the subtask.\n if (isInfrastructureFailure(subState.lastError)) {\n state.phase = 'escalated';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" — all specialists failed to produce valid output after ${goal.subtasks?.length ?? 0} subtask(s). This usually means the model is misconfigured, the task is too vague, or the specialist doesn't have the right tools. Please review the goal description or switch the model tier.`,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'infrastructure_failure',\n };\n appendHistory(state, 'escalated', `Subtask ${next.id}: infrastructure failure — all specialists failed JSON output`);\n await fileBlockedApproval(state, goal, [state.blockedReason.question]);\n return;\n }\n\n // Exhausted retries for this subtask (normal failure)\n subState.verificationResult = {\n passed: false,\n reason: 'Max retries exhausted',\n verifier: 'budget',\n };\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, next.id, 'max-retries-exhausted');\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating'; // try next subtask\n appendHistory(state, 'delegating', `Subtask ${next.id} exhausted retries — moved on`);\n return;\n }\n\n subState.specialist = strategy.specialist;\n subState.pendingSpawn = {\n attemptedAt: new Date().toISOString(),\n specialist: strategy.specialist,\n };\n state.phase = 'observing';\n appendHistory(\n state,\n 'observing',\n `Spawning ${strategy.specialist} for subtask \"${next.title}\" (kind=${subState.kind}, attempt ${subState.attempts})`,\n );\n\n // Actually fire the spawn — runs in-tick, driver waits for the return.\n // (Future: async wakeup path so driver can observe multiple concurrent\n // spawns; for now one at a time per goal.)\n try {\n const startMs = Date.now();\n const result = await structuredSpawn({\n specialistId: strategy.specialist,\n task: `${next.title}\\n\\n${next.description}${strategy.promptAdjustment ?? ''}`,\n modelOverride: strategy.modelOverride,\n toolAllowlist: routeForKind(subState.kind).toolAllowlist,\n maxRounds: strategy.maxRounds,\n });\n const durationMs = Date.now() - startMs;\n recordSpend(state, {\n elapsedMs: durationMs,\n tokens: result.tokensUsed ?? 0,\n costUsd: result.costUsd ?? 0,\n });\n\n // Store artifacts\n subState.artifacts = [...new Set([\n ...subState.artifacts,\n ...result.artifacts.map(a => a.ref),\n ])];\n\n // Decide phase based on spawn status\n if (result.status === 'done') {\n state.phase = 'verifying';\n appendHistory(state, 'verifying', `Spawn returned done with ${result.artifacts.length} artifact(s), confidence ${result.confidence.toFixed(2)}`);\n // Stash the spawn result so verifying can read it\n (subState as DriverSubtaskState & { lastSpawnResult?: unknown }).lastSpawnResult = result;\n } else if (result.status === 'failed') {\n subState.lastError = result.reasoning || 'failed';\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Spawn returned failed: ${subState.lastError.slice(0, 120)}`);\n } else if (result.status === 'needs_info' || result.status === 'blocked') {\n state.phase = 'blocked';\n // v4.14.0: build a rich, contextual blocked reason instead of\n // the generic \"Specialist requires input\" that tells the user\n // nothing about what actually went wrong.\n const subtaskTitle = next.title || 'Unnamed subtask';\n const specialistName = strategy.specialist || subState.specialist || 'specialist';\n const attemptCount = subState.attempts;\n const lastErr = subState.lastError;\n const rawQuestion = result.questions[0] ?? '';\n\n let richQuestion: string;\n if (rawQuestion && rawQuestion.length > 10 && !rawQuestion.toLowerCase().includes('specialist requires')) {\n // The specialist gave a real question — use it but wrap with context\n richQuestion = `Goal \"${goal.title}\" is stuck on subtask \"${subtaskTitle}\" (attempt ${attemptCount}, specialist: ${specialistName}).\\n\\n${rawQuestion}`;\n } else if (lastErr) {\n richQuestion = `Goal \"${goal.title}\" — subtask \"${subtaskTitle}\" failed after ${attemptCount} attempt(s) with specialist ${specialistName}.\\n\\nError: ${lastErr.slice(0, 200)}\\n\\nWhat should the specialist do next?`;\n } else {\n richQuestion = `Goal \"${goal.title}\" — subtask \"${subtaskTitle}\" is blocked after ${attemptCount} attempt(s) with specialist ${specialistName}. The specialist could not complete the task and needs guidance on how to proceed.`;\n }\n\n state.blockedReason = {\n question: richQuestion,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'needs_info',\n };\n appendHistory(state, 'blocked', `Spawn needs info: ${richQuestion.slice(0, 120)}`);\n await fileBlockedApproval(state, goal, [richQuestion, ...result.questions.slice(1)]);\n }\n subState.pendingSpawn = undefined;\n } catch (err) {\n const msg = (err as Error).message;\n subState.lastError = msg;\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Spawn threw: ${msg.slice(0, 120)}`);\n }\n}\n\nasync function tickObserving(goal: Goal, state: DriverState): Promise<void> {\n // In Phase A, spawns are sync (await structuredSpawn completes in tickDelegating).\n // This phase exists for future async-wakeup integration; for now it just\n // advances based on whatever subState said after the spawn.\n //\n // v4.10.0-local (post-deploy, Fix 8): be a no-op unless there's a\n // pending spawn waiting for a wakeup. Previously this unconditionally\n // transitioned to iterating, which tickIterating treated as a failure\n // and burned an attempt on goals that never actually spawned. Now the\n // observing phase only advances when there's a spawn to observe.\n void goal;\n const currentId = state.currentSubtaskId;\n const subState = currentId ? state.subtaskStates[currentId] : undefined;\n if (!subState?.pendingSpawn) {\n // Nothing to observe — bounce back to delegating without counting\n // as a retry.\n state.phase = 'delegating';\n return;\n }\n state.phase = 'iterating';\n appendHistory(state, 'iterating', 'Observe tick with no spawn progress — iterating');\n}\n\nasync function tickIterating(goal: Goal, state: DriverState): Promise<void> {\n const currentId = state.currentSubtaskId;\n if (!currentId) {\n state.phase = 'delegating';\n return;\n }\n const subState = state.subtaskStates[currentId];\n\n // v4.10.0-local (post-deploy, Fix A): forward-progress detector.\n // If the same lastError (first 80 chars) repeats 3 times in a row,\n // retrying isn't producing new information — fail this subtask and\n // move on. Prevents the verifying↔iterating→delegating oscillation\n // observed on stuck drivers. Uses a semantic signal (error text)\n // rather than phase-pattern detection, which false-positives on\n // legitimate multi-phase verify passes.\n const fingerprint = (subState.lastError || '').slice(0, 80).toLowerCase().trim();\n if (fingerprint) {\n if (fingerprint === subState.lastErrorFingerprint) {\n subState.consecutiveIdenticalErrors = (subState.consecutiveIdenticalErrors || 0) + 1;\n } else {\n subState.consecutiveIdenticalErrors = 1;\n subState.lastErrorFingerprint = fingerprint;\n }\n if ((subState.consecutiveIdenticalErrors ?? 0) >= 3) {\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, currentId, `Stuck loop: same error 3× in a row: ${fingerprint}`);\n } catch { /* ok */ }\n state.currentSubtaskId = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Broke stall loop on ${currentId} (same error 3×)`);\n return;\n }\n }\n\n // v4.10.0-local (post-deploy, Fix 8): do NOT increment totalRetries\n // here. Per-subtask attempts are counted in tickDelegating. Counting\n // again here caused double-billing: one failed spawn consumed 2 budget\n // units. Retry budget is meant to track subtask-transition retries,\n // not individual spawn retries.\n const check = checkBudget(state);\n if (check.status === 'exceeded') {\n const suggestion = suggestDegradation(state);\n if (suggestion === 'ask_human') {\n state.phase = 'blocked';\n state.blockedReason = {\n question: `Goal \"${goal.title}\" — budget exceeded (${check.message}). The driver has used ${state.budget.costUsd.toFixed(2)} USD so far. Continue with extended budget, de-scope, or cancel?`,\n approvalId: '',\n sinceAt: new Date().toISOString(),\n kind: 'budget_exceeded',\n };\n appendHistory(state, 'blocked', check.message);\n await fileBlockedApproval(state, goal, [state.blockedReason.question]);\n return;\n }\n }\n\n if (subState.attempts >= state.budgetCaps.maxRetries) {\n // Gap 2 (plan-this-logical-ocean): before giving up on this subtask,\n // consult the bounded continuation counter. If the per-subtask cap\n // hasn't been hit (max 2, persisted to disk so restarts can't\n // bypass), halve attempts and let it try again. This is the\n // \"plan_only\" signal — spawns kept producing plans/output that\n // wouldn't verify. Two extra cycles across a restart boundary is\n // the cheapest escape from the verifying↔iterating oscillation that\n // the existing stuck-loop detector can't catch (different errors\n // each time, but no real progress).\n const { shouldContinue } = await import('./runContinuations.js');\n const continuationKey = `${goal.id}:${currentId}`;\n if (shouldContinue(continuationKey, 'plan_only')) {\n const before = subState.attempts;\n subState.attempts = Math.max(0, Math.floor(subState.attempts / 2));\n subState.consecutiveIdenticalErrors = 0;\n subState.lastErrorFingerprint = undefined;\n appendHistory(state, 'delegating', `Continuation granted on ${currentId}: attempts halved ${before} → ${subState.attempts}`);\n state.phase = 'delegating';\n return;\n }\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, currentId, subState.lastError || 'max retries');\n } catch { /* ok */ }\n appendHistory(state, 'delegating', `Subtask ${currentId} failed after ${subState.attempts} attempts`);\n state.phase = 'delegating';\n return;\n }\n\n // Back to delegating to try the next fallback\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Iterating on subtask ${currentId} — attempt ${subState.attempts + 1}`);\n}\n\nasync function tickVerifying(goal: Goal, state: DriverState): Promise<void> {\n const currentId = state.currentSubtaskId;\n if (!currentId) {\n // Whole-goal verify — check per-subtask results against the goal\n // file's subtask statuses. A subtask passes the whole-goal check\n // iff EITHER (a) its verificationResult.passed === true, OR\n // (b) the goal file reports it as done/skipped (completed through\n // an external path, e.g. human marked it done).\n //\n // v4.10.0-local (post-deploy, Fix 8): previous check was\n // `passed !== false`, which treated subtasks that never got\n // verified at all (verificationResult undefined) as passing\n // vacuously. Now we require an explicit pass signal OR an\n // explicit terminal status on the goal-side subtask.\n const goalSubs = goal.subtasks || [];\n const subById = new Map(goalSubs.map(s => [s.id, s]));\n const allPassed = Object.entries(state.subtaskStates).every(([subId, subState]) => {\n if (subState.verificationResult?.passed === true) return true;\n const goalSub = subById.get(subId);\n if (goalSub?.status === 'done' || goalSub?.status === 'skipped') return true;\n return false;\n });\n state.phase = allPassed ? 'reporting' : 'failed';\n appendHistory(state, state.phase, allPassed ? 'All subtasks verified' : 'Some subtasks failed verification');\n return;\n }\n const subState = state.subtaskStates[currentId];\n const subtask = (goal.subtasks || []).find(s => s.id === currentId);\n if (!subtask) {\n state.phase = 'delegating';\n return;\n }\n const lastSpawn = (subState as DriverSubtaskState & { lastSpawnResult?: unknown }).lastSpawnResult;\n if (!lastSpawn) {\n // No spawn result to verify against — iterate\n state.phase = 'iterating';\n appendHistory(state, 'iterating', 'Verify with no spawn result — iterating');\n return;\n }\n\n const verifyResult = await verifyByKind({\n kind: subState.kind,\n subtask,\n spawnResult: lastSpawn as Parameters<typeof verifyByKind>[0]['spawnResult'],\n });\n subState.verificationResult = verifyResult;\n\n if (verifyResult.passed) {\n try {\n const { completeSubtask } = await import('./goals.js');\n // v4.10.0-local polish: store the SPAWN'S actual output\n // (reasoning + artifacts summary), not the verifier's pass\n // message. Prior behavior stored \"Analysis 171 chars, conf 0.95\"\n // in the subtask.result field, losing the actual content.\n const spawn = lastSpawn as Parameters<typeof verifyByKind>[0]['spawnResult'];\n const artifactSummary = spawn.artifacts.length > 0\n ? `\\n\\nArtifacts:\\n${spawn.artifacts.map(a => ` - [${a.type}] ${a.ref}${a.description ? ` — ${a.description}` : ''}`).join('\\n')}`\n : '';\n const contentToSave = (spawn.reasoning || spawn.rawResponse || verifyResult.reason) + artifactSummary;\n completeSubtask(goal.id, currentId, contentToSave);\n } catch { /* ok */ }\n appendHistory(state, 'delegating', `Subtask ${currentId} verified: ${verifyResult.reason.slice(0, 120)}`);\n state.currentSubtaskId = undefined;\n state.phase = 'delegating';\n } else {\n subState.lastError = `Verification failed: ${verifyResult.reason}`;\n appendHistory(state, 'iterating', `Verification failed: ${verifyResult.reason.slice(0, 120)}`);\n state.phase = 'iterating';\n }\n}\n\nasync function tickReporting(goal: Goal, state: DriverState): Promise<void> {\n const durationMs = Date.now() - new Date(state.startedAt).getTime();\n const specialistsUsed = [...new Set(\n Object.values(state.subtaskStates)\n .map(s => s.specialist)\n .filter((x): x is string => !!x),\n )];\n state.retrospective = {\n success: true,\n durationMs,\n tokensUsed: state.budget.tokensUsed,\n costUsd: state.budget.costUsd,\n lessonsLearned: [\n `Completed ${Object.keys(state.subtaskStates).length} subtasks across ${specialistsUsed.length} specialist(s) in ${Math.round(durationMs / 1000)}s`,\n ],\n specialistsUsed,\n };\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'completed' });\n } catch { /* ok */ }\n state.phase = 'done';\n appendHistory(state, 'done', `Goal completed: ${state.retrospective.lessonsLearned[0]}`);\n try { await onGoalCompleted(goal, state); } catch { /* ok */ }\n // v4.10.0-local (Phase B): record retrospective for future goal learning\n try {\n const { saveRetrospective } = await import('./retrospectives.js');\n await saveRetrospective(goal, state);\n } catch { /* ok */ }\n\n // Fire episode\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: 'goal_completed',\n summary: `Driver completed goal \"${goal.title}\" (${Math.round(durationMs / 1000)}s, ${Object.keys(state.subtaskStates).length} subtasks, specialists: ${specialistsUsed.join(', ')})`,\n detail: state.history.slice(-10).map(h => `${h.at} ${h.phase}: ${h.note}`).join('\\n'),\n tags: ['goal-driver', 'goal_completed', goal.id, ...(goal.tags || [])],\n });\n } catch { /* ok */ }\n}\n\nasync function tickBlocked(goal: Goal, state: DriverState): Promise<void> {\n // v4.10.0-local (post-deploy, Fix F): auto-unblock stale blocked states.\n // The block was either never backed by an approval (approvalId empty —\n // bookkeeping bug) or the approval no longer exists (deleted / TTL'd).\n // After 10 minutes of sitting idle with no live approval, retry the\n // subtask rather than sitting forever waiting for a human who has no\n // way to resolve this. Uses forceUnblockDriver so the recovery is\n // consistent with the manual API path.\n const approvalId = state.blockedReason?.approvalId;\n const sinceAt = state.blockedReason?.sinceAt;\n const sinceMs = sinceAt ? Date.now() - new Date(sinceAt).getTime() : 0;\n const STALE_MS = 10 * 60 * 1000;\n if (sinceMs > STALE_MS) {\n let approval: { status?: string } | null = null;\n if (approvalId) {\n try {\n const { getApproval } = await import('./commandPost.js');\n approval = getApproval(approvalId) as { status?: string } | null;\n } catch { /* ok */ }\n }\n if (!approvalId || !approval) {\n // Unblock in-place on the shared state reference. We can't\n // call forceUnblockDriver here because it does its own\n // loadState/saveState cycle, and tickDriver's outer saveState\n // would then overwrite those changes with our stale local\n // state. Mirror forceUnblockDriver's logic here.\n logger.info(COMPONENT, `Auto-unblocking stale blocked state for ${goal.id} (approvalId=${approvalId || 'empty'}, age=${Math.round(sinceMs / 60000)}min)`);\n const note = `stale block auto-recovered (age ${Math.round(sinceMs / 60000)}min)`;\n const wasBudget = state.blockedReason?.kind === 'budget_exceeded'\n || /budget.*exceed|retries exceed/i.test(state.blockedReason?.question || '');\n if (wasBudget) {\n state.budget.totalRetries = Math.floor(state.budget.totalRetries / 2);\n }\n const currentId = state.currentSubtaskId;\n if (currentId && state.subtaskStates[currentId]) {\n const sub = state.subtaskStates[currentId];\n sub.attempts = Math.floor(sub.attempts / 2);\n sub.consecutiveIdenticalErrors = 0;\n }\n state.blockedReason = undefined;\n state.phase = 'iterating';\n appendHistory(state, 'iterating', `Force-unblocked: ${note}`);\n return;\n }\n }\n\n // Check if the blocking approval has been decided\n if (!approvalId) return; // nothing to unblock from\n try {\n const { getApproval } = await import('./commandPost.js');\n const approval = getApproval(approvalId);\n if (!approval) return;\n if (approval.status === 'pending') return; // still waiting\n if (approval.status === 'approved') {\n // Incorporate human answer into the current subtask's context\n const currentId = state.currentSubtaskId;\n if (currentId) {\n const subState = state.subtaskStates[currentId];\n const note = approval.decisionNote || 'Approved';\n subState.lastError = `Previous attempt needed info; human provided: \"${note}\". Try again using this.`;\n }\n state.blockedReason = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Unblocked by human (approval ${approvalId})`);\n return;\n }\n if (approval.status === 'rejected') {\n state.phase = 'failed';\n appendHistory(state, 'failed', `Goal rejected by human via approval ${approvalId}`);\n await onGoalFailed(goal, state, 'rejected by human');\n return;\n }\n } catch { /* ok */ }\n}\n\nasync function tickEscalated(goal: Goal, state: DriverState): Promise<void> {\n // v4.10.0-local: Escalated phase handles systematic infrastructure failures.\n // Similar to blocked, but specifically for JSON parse failures that indicate\n // model tier issues. Requires human intervention to fix infrastructure.\n\n const approvalId = state.blockedReason?.approvalId;\n const sinceAt = state.blockedReason?.sinceAt;\n const sinceMs = sinceAt ? Date.now() - new Date(sinceAt).getTime() : 0;\n\n // Check if the escalation approval has been decided\n if (!approvalId) return;\n try {\n const { getApproval } = await import('./commandPost.js');\n const approval = getApproval(approvalId);\n if (!approval) return;\n if (approval.status === 'pending') return; // still waiting\n\n if (approval.status === 'approved') {\n // Human acknowledged the infrastructure issue and wants to retry\n // Reset the subtask attempts to give it another go with potentially\n // new model configuration\n const currentId = state.currentSubtaskId;\n if (currentId) {\n const subState = state.subtaskStates[currentId];\n subState.attempts = 0; // Reset to allow fresh attempts\n subState.consecutiveIdenticalErrors = 0;\n subState.lastError = undefined;\n subState.lastErrorFingerprint = undefined;\n }\n state.blockedReason = undefined;\n state.phase = 'delegating';\n appendHistory(state, 'delegating', `Escalation resolved by human — retrying subtask ${currentId} with fresh attempts`);\n return;\n }\n\n if (approval.status === 'rejected') {\n // Human decided to fail the goal rather than retry\n state.phase = 'failed';\n appendHistory(state, 'failed', `Infrastructure escalation rejected — goal failed`);\n await onGoalFailed(goal, state, 'infrastructure escalation rejected by human');\n return;\n }\n } catch { /* ok */ }\n}\n\nasync function tickFailed(goal: Goal, state: DriverState): Promise<void> {\n const durationMs = Date.now() - new Date(state.startedAt).getTime();\n state.retrospective = {\n success: false,\n durationMs,\n tokensUsed: state.budget.tokensUsed,\n costUsd: state.budget.costUsd,\n lessonsLearned: [\n `Goal failed after ${Math.round(durationMs / 1000)}s, ${state.budget.totalRetries} retries`,\n ],\n specialistsUsed: [...new Set(Object.values(state.subtaskStates).map(s => s.specialist).filter((x): x is string => !!x))],\n };\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'failed' });\n } catch { /* ok */ }\n try { await onGoalFailed(goal, state, 'driver terminated in failed state'); } catch { /* ok */ }\n // Phase B: failed retrospectives are the most valuable — they teach us what to avoid\n try {\n const { saveRetrospective } = await import('./retrospectives.js');\n await saveRetrospective(goal, state);\n } catch { /* ok */ }\n try {\n const { recordEpisode } = await import('../memory/episodic.js');\n recordEpisode({\n kind: 'goal_failed',\n summary: `Driver failed goal \"${goal.title}\" after ${Math.round(durationMs / 1000)}s`,\n detail: state.history.slice(-15).map(h => `${h.at} ${h.phase}: ${h.note}`).join('\\n'),\n tags: ['goal-driver', 'goal_failed', goal.id, ...(goal.tags || [])],\n });\n } catch { /* ok */ }\n}\n\nasync function tickCancelled(goal: Goal, state: DriverState): Promise<void> {\n try {\n const { updateGoal } = await import('./goals.js');\n updateGoal(goal.id, { status: 'failed' });\n } catch { /* ok */ }\n appendHistory(state, 'cancelled', 'Cancelled by user');\n}\n\n// ── Helpers ──────────────────────────────────────────────────────\n\nasync function pickNextReadySubtask(goal: Goal, state: DriverState): Promise<Subtask | null> {\n // v4.10.0-local (post-deploy, Fix C): synchronous deadlock recovery.\n // If a pending subtask has exhausted its attempts with a failed\n // verification, await failSubtask to persist the failure *before*\n // continuing. The previous async mutation was ephemeral — the next\n // tick's getGoal() call saw stale data and re-entered the deadlock\n // branch in tickDelegating. This version is durable.\n const cap = (id: string) => state.subtaskStates[id]?.maxAttempts ?? 5;\n for (const sub of goal.subtasks || []) {\n if (sub.status !== 'pending') continue;\n const subState = state.subtaskStates[sub.id];\n if (!subState) continue;\n const exhaustedAttempts = subState.attempts >= cap(sub.id)\n || subState.attempts >= state.budgetCaps.maxRetries;\n if (subState.verificationResult?.passed === false && exhaustedAttempts) {\n try {\n const { failSubtask } = await import('./goals.js');\n failSubtask(goal.id, sub.id, subState.lastError || 'max retries exceeded (deadlock recovery)');\n // failSubtask mutates the cached goalsCache in place, so our\n // `goal` reference (passed by the caller, loaded from the\n // same cache) now reflects status: 'failed'. Belt-and-braces:\n // also update our local object in case the cache was bypassed.\n sub.status = 'failed';\n } catch { /* ok — driver will re-try next tick */ }\n continue;\n }\n // Respect dependsOn: skip subtasks whose prerequisites are not completed\n const depsSatisfied = (sub.dependsOn ?? []).every(depId => {\n const dep = goal.subtasks?.find(s => s.id === depId);\n return dep?.status === 'done';\n });\n if (!depsSatisfied) continue;\n return sub;\n }\n return null;\n}\n\nasync function fileBlockedApproval(\n state: DriverState,\n goal: Goal,\n questions: string[],\n): Promise<void> {\n // v4.10.0-local (Phase B): throttle to 1 per (goalId, driver_blocked) per 5 min\n try {\n const { shouldCreateApproval } = await import('./notificationThrottle.js');\n if (!shouldCreateApproval(goal.id, 'driver_blocked')) {\n logger.debug(COMPONENT, `Throttled duplicate driver_blocked approval for goal ${goal.id}`);\n // Still record SOMA feedback since we DID get blocked\n try { await onGoalBlocked(goal, state); } catch { /* ok */ }\n return;\n }\n } catch { /* if throttle module unavailable, fall through */ }\n try {\n const { createApproval } = await import('./commandPost.js');\n const subState = state.currentSubtaskId ? state.subtaskStates[state.currentSubtaskId] : undefined;\n const subtaskTitle = goal.subtasks?.find(s => s.id === state.currentSubtaskId)?.title;\n const approval = createApproval({\n type: 'custom',\n requestedBy: 'goal-driver',\n payload: {\n kind: 'driver_blocked',\n goalId: goal.id,\n goalTitle: goal.title,\n question: questions[0] ?? state.blockedReason?.question ?? 'Specialist requires input',\n allQuestions: questions,\n blockedPhase: state.phase,\n currentSubtaskId: state.currentSubtaskId,\n subtaskKind: subState?.kind,\n subtaskTitle,\n specialist: subState?.specialist,\n attempts: subState?.attempts,\n lastError: subState?.lastError,\n urgency: 'high',\n },\n linkedIssueIds: [],\n });\n if (approval?.id && state.blockedReason) {\n state.blockedReason.approvalId = approval.id;\n }\n // Broadcast via SSE if broadcaster exists (throttled separately)\n try {\n const { shouldBroadcast } = await import('./notificationThrottle.js');\n if (shouldBroadcast('driver:blocked', goal.id)) {\n const g = globalThis as unknown as { __titan_sse_broadcast?: (topic: string, payload: unknown) => void };\n if (typeof g.__titan_sse_broadcast === 'function') {\n g.__titan_sse_broadcast('driver:blocked', {\n goalId: goal.id,\n goalTitle: goal.title,\n question: questions[0] ?? 'Specialist requires input',\n approvalId: approval?.id,\n });\n }\n }\n } catch { /* ok */ }\n } catch (err) {\n logger.warn(COMPONENT, `Could not file blocked-on-human approval: ${(err as Error).message}`);\n }\n // SOMA feedback: small hunger bump + slight purpose stall (via metricGuard)\n try { await onGoalBlocked(goal, state); } catch { /* ok */ }\n}\n\n// ── Main tick loop ──────────────────────────────────────────────\n\nexport async function tickDriver(goalId: string): Promise<DriverPhase> {\n const { getGoal } = await import('./goals.js');\n const goal = getGoal(goalId);\n if (!goal) return 'failed';\n\n let state = loadState(goalId);\n if (!state) {\n state = freshDriverState(goal);\n saveState(state);\n }\n\n // Respect user controls\n if (state.userControls.cancelRequested) {\n state.phase = 'cancelled';\n await tickCancelled(goal, state);\n saveState(state);\n return 'cancelled';\n }\n if (state.userControls.paused) return state.phase;\n\n // Kill switch inherits automatically — toolRunner gates writes,\n // spawn_agent early-returns. Drivers don't need to re-check.\n\n try {\n switch (state.phase) {\n case 'planning': await tickPlanning(goal, state); break;\n case 'delegating': await tickDelegating(goal, state); break;\n case 'observing': await tickObserving(goal, state); break;\n case 'iterating': await tickIterating(goal, state); break;\n case 'verifying': await tickVerifying(goal, state); break;\n case 'reporting': await tickReporting(goal, state); break;\n case 'blocked': await tickBlocked(goal, state); break;\n case 'escalated': await tickEscalated(goal, state); break;\n case 'failed': await tickFailed(goal, state); break;\n case 'done':\n case 'cancelled':\n break;\n }\n } catch (err) {\n logger.warn(COMPONENT, `Tick for ${goalId} threw in phase=${state.phase}: ${(err as Error).message}`);\n appendHistory(state, state.phase, `Tick threw: ${(err as Error).message.slice(0, 120)}`);\n }\n\n saveState(state);\n return state.phase;\n}\n\n/**\n * Drive a goal synchronously to a terminal or waiting state. Ticks in a\n * loop with a small pause between ticks so observing/blocked states give\n * the event loop time to breathe. Returns when the driver is terminal\n * (done/failed/cancelled) or waiting on a human (blocked).\n */\nexport async function driveGoal(goalId: string, maxTicks = 200): Promise<DriverPhase> {\n let last: DriverPhase = 'planning';\n for (let i = 0; i < maxTicks; i++) {\n last = await tickDriver(goalId);\n if (last === 'done' || last === 'failed' || last === 'cancelled' || last === 'blocked' || last === 'escalated') break;\n // Mini-delay between ticks to let IO + timers run\n await new Promise(res => setTimeout(res, 50));\n }\n return last;\n}\n\n// ── External API ────────────────────────────────────────────────\n\nexport function getDriverState(goalId: string): DriverState | null {\n return loadState(goalId);\n}\n\nexport function listActiveDrivers(): DriverState[] {\n ensureStateDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: DriverState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const goalId = file.slice(0, -5);\n const s = loadState(goalId);\n if (s && s.phase !== 'done' && s.phase !== 'failed' && s.phase !== 'cancelled') out.push(s);\n }\n return out;\n}\n\nexport function listAllDrivers(): DriverState[] {\n ensureStateDir();\n if (!existsSync(STATE_DIR)) return [];\n const out: DriverState[] = [];\n for (const file of readdirSync(STATE_DIR)) {\n if (!file.endsWith('.json')) continue;\n const goalId = file.slice(0, -5);\n const s = loadState(goalId);\n if (s) out.push(s);\n }\n return out;\n}\n\nexport function pauseDriver(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.paused = true;\n saveState(s);\n return true;\n}\n\nexport function resumeDriverControl(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.paused = false;\n saveState(s);\n return true;\n}\n\n/**\n * Force a blocked driver back to iterating. Used when the block was\n * triggered by a machine-level failure (parse error, model glitch)\n * rather than a genuine human-answerable question, and the underlying\n * issue has been fixed (e.g. model swap).\n *\n * Also auto-rejects any pending approval the driver was blocked on so\n * it doesn't clutter the Approvals UI forever.\n */\nexport async function forceUnblockDriver(goalId: string, note = 'auto-unblock'): Promise<boolean> {\n const s = loadState(goalId);\n if (!s) return false;\n if (s.phase !== 'blocked') return false;\n\n // Auto-reject the bogus approval if one exists\n const approvalId = s.blockedReason?.approvalId;\n if (approvalId) {\n try {\n const { rejectApproval } = await import('./commandPost.js');\n rejectApproval(approvalId, 'force-unblock', `Auto-rejected by force-unblock: ${note}`);\n } catch { /* ok — approval might not exist anymore */ }\n }\n\n // If the block was budget-related, halve the retry counter so the\n // driver has headroom to retry with the underlying fix in place.\n // This is preferable to a full reset — it still records that the\n // goal has been difficult, just gives it another shot.\n const wasBudget = s.blockedReason?.kind === 'budget_exceeded'\n || /budget.*exceed|retries exceed/i.test(s.blockedReason?.question || '');\n if (wasBudget) {\n s.budget.totalRetries = Math.floor(s.budget.totalRetries / 2);\n appendHistory(s, 'iterating', `Force-unblock: budget halved (${s.budget.totalRetries} / ${s.budgetCaps.maxRetries} retries)`);\n }\n\n // v4.10.0-local (post-deploy, Fix F): also halve the per-subtask\n // attempt counter for the current subtask. Otherwise the subtask\n // re-hits its per-subtask cap immediately on resume (from Fix B)\n // and we just re-enter the blocked state next tick.\n const currentId = s.currentSubtaskId;\n if (currentId && s.subtaskStates[currentId]) {\n const sub = s.subtaskStates[currentId];\n const before = sub.attempts;\n sub.attempts = Math.floor(sub.attempts / 2);\n sub.consecutiveIdenticalErrors = 0; // reset stall detector too\n appendHistory(s, 'iterating', `Force-unblock: per-subtask attempts halved on ${currentId} (${before} → ${sub.attempts})`);\n }\n\n s.blockedReason = undefined;\n s.phase = 'iterating'; // iterating retries the current subtask with a fresh spawn\n appendHistory(s, 'iterating', `Force-unblocked: ${note}`);\n saveState(s);\n logger.info(COMPONENT, `Force-unblocked driver ${goalId}: ${note}${wasBudget ? ' (budget halved)' : ''}`);\n return true;\n}\n\nexport function cancelDriver(goalId: string): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.cancelRequested = true;\n saveState(s);\n return true;\n}\n\n/**\n * Permanently remove a driver state file. Used by `resumeDriversAfterRestart`\n * to reap drivers whose goal no longer exists, and by the boot-time stale\n * sweep to cull drivers that haven't ticked in a long time.\n *\n * v5.5.31+. Before this, `cancelDriver` only flipped a `cancelRequested`\n * flag without deleting — meaning orphan driver files (e.g. tests pointing\n * at deleted goals) accumulated forever and the scheduler could re-tick\n * them. Audit 2026-05-08 found `mtime-cache-test.json` actively ticking\n * for 14h+ after the test that created it had finished.\n */\nexport function deleteDriverState(goalId: string): boolean {\n const path = statePath(goalId);\n if (!existsSync(path)) return false;\n try { rmSync(path, { force: true }); return true; }\n catch (err) { logger.warn(COMPONENT, `deleteDriverState ${goalId}: ${(err as Error).message}`); return false; }\n}\n\n/**\n * Sweep stale driver-state files. Returns count + ids of removed files.\n * A driver is considered stale if its `lastTickAt` is older than `maxAgeMs`\n * (default 24h). Called on gateway boot via `resumeDriversAfterRestart`.\n *\n * The audit found 5 driver-state files at 395+ hours old (16-17 days)\n * that resumeDriversAfterRestart kept \"cancelling\" via the flag but never\n * actually removing.\n */\nexport function sweepStaleDriverStates(maxAgeMs = 24 * 60 * 60 * 1000): { removed: number; ids: string[] } {\n ensureStateDir();\n const removed: string[] = [];\n const cutoff = Date.now() - maxAgeMs;\n try {\n const entries = readdirSync(STATE_DIR);\n for (const name of entries) {\n if (!name.endsWith('.json')) continue;\n const goalId = name.replace(/\\.json$/, '');\n const s = loadState(goalId);\n if (!s) continue;\n const lastTickMs = new Date(s.lastTickAt || s.startedAt).getTime();\n if (Number.isFinite(lastTickMs) && lastTickMs < cutoff) {\n if (deleteDriverState(goalId)) removed.push(goalId);\n }\n }\n } catch (err) {\n logger.warn(COMPONENT, `sweepStaleDriverStates: ${(err as Error).message}`);\n }\n return { removed: removed.length, ids: removed };\n}\n\nexport function reprioritizeDriver(goalId: string, priority: 1 | 2 | 3 | 4 | 5): boolean {\n const s = loadState(goalId);\n if (!s) return false;\n s.userControls.priority = priority;\n saveState(s);\n return true;\n}\n\nexport function _resetDriverStateForTests(goalId?: string): void {\n if (goalId) {\n try { rmSync(statePath(goalId), { force: true }); } catch { /* ok */ }\n } else {\n try { rmSync(STATE_DIR, { recursive: true, force: true }); } catch { /* ok */ }\n }\n}\n"],"mappings":"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","names":["subState"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/channels/irc.ts"],"sourcesContent":["/**\n * TITAN — IRC Channel Adapter\n * Connects to IRC servers and relays messages.\n * Requires an IRC client library (e.g. irc-framework).\n */\nimport { ChannelAdapter, type InboundMessage, type OutboundMessage, type ChannelStatus } from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'IRC';\n\nexport class IRCChannel extends ChannelAdapter {\n readonly name = 'irc';\n readonly displayName = 'IRC';\n private connected = false;\n private client: unknown = null;\n\n async connect(): Promise<void> {\n const config = loadConfig();\n const channelConfig = config.channels.irc;\n\n if (!channelConfig.enabled) {\n logger.info(COMPONENT, 'IRC channel is disabled');\n return;\n }\n\n const token = channelConfig.token; // Used as server address (host:port)\n if (!token) {\n logger.warn(COMPONENT, 'IRC server address not configured (set channels.irc.token to host:port)');\n return;\n }\n\n try {\n // TODO: Install irc-framework: npm install irc-framework\n // @ts-expect-error — irc-framework is an optional dependency\n const { Client } = await import('irc-framework');\n const [host, portStr] = token.split(':');\n const port = parseInt(portStr || '6667', 10);\n const nick = channelConfig.apiKey || 'TitanBot';\n\n const client = new Client();\n client.connect({ host, port, nick });\n\n client.on('registered', () => {\n this.connected = true;\n logger.info(COMPONENT, `Connected to IRC server ${host}:${port} as ${nick}`);\n // Join channels from allowFrom list\n for (const ch of channelConfig.allowFrom) {\n if (ch.startsWith('#')) {\n client.join(ch);\n logger.info(COMPONENT, `Joined ${ch}`);\n }\n }\n });\n\n client.on('privmsg', (event: { nick: string; target: string; message: string; time?: number }) => {\n try {\n const inbound: InboundMessage = {\n id: `${Date.now()}-${event.nick}`,\n channel: 'irc',\n userId: event.nick,\n userName: event.nick,\n content: event.message,\n groupId: event.target.startsWith('#') ? event.target : undefined,\n timestamp: new Date(event.time ?? Date.now()),\n raw: event,\n };\n this.emit('message', inbound);\n } catch (error) {\n logger.error(COMPONENT, `Message handler error: ${(error as Error).message}`);\n }\n });\n\n client.on('close', () => {\n this.connected = false;\n logger.info(COMPONENT, 'Disconnected from IRC');\n });\n\n this.client = client;\n } catch (error) {\n logger.error(COMPONENT, `Failed to connect: ${(error as Error).message}`);\n logger.info(COMPONENT, 'Install irc-framework with: npm install irc-framework');\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n (this.client as { quit(msg: string): void }).quit('TITAN shutting down');\n this.connected = false;\n this.client = null;\n logger.info(COMPONENT, 'Disconnected');\n }\n }\n\n async send(message: OutboundMessage): Promise<void> {\n if (!this.client || !this.connected) return;\n const target = message.groupId || message.userId;\n if (!target) {\n logger.warn(COMPONENT, 'No target provided for IRC message');\n return;\n }\n try {\n (this.client as { say(target: string, text: string): void }).say(target, message.content);\n logger.debug(COMPONENT, `Sent message to ${target}`);\n } catch (error) {\n logger.error(COMPONENT, `Send failed: ${(error as Error).message}`);\n }\n }\n\n getStatus(): ChannelStatus {\n return { name: this.displayName, connected: this.connected };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/channels/irc.ts"],"sourcesContent":["/**\n * TITAN — IRC Channel Adapter\n *\n * Connects to IRC servers and relays messages. **Optional dependency:**\n * `irc-framework` is NOT a required dependency in package.json. The\n * adapter ships in TITAN's distribution but the import is lazy and\n * graceful — if the channel is enabled in config but `irc-framework`\n * isn't installed, `connect()` logs an actionable error and returns\n * without throwing. To enable IRC, run `npm install irc-framework` in\n * the TITAN install directory and set `channels.irc.enabled = true`.\n *\n * v5.5.31 audit clarification: a \"TODO: Install irc-framework\" comment\n * inside the dynamic import block previously made this look broken.\n * It's not broken — it's optional-dep design. The comment is now\n * accurate about the install path.\n */\nimport { ChannelAdapter, type InboundMessage, type OutboundMessage, type ChannelStatus } from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'IRC';\n\nexport class IRCChannel extends ChannelAdapter {\n readonly name = 'irc';\n readonly displayName = 'IRC';\n private connected = false;\n private client: unknown = null;\n\n async connect(): Promise<void> {\n const config = loadConfig();\n const channelConfig = config.channels.irc;\n\n if (!channelConfig.enabled) {\n logger.info(COMPONENT, 'IRC channel is disabled');\n return;\n }\n\n const token = channelConfig.token; // Used as server address (host:port)\n if (!token) {\n logger.warn(COMPONENT, 'IRC server address not configured (set channels.irc.token to host:port)');\n return;\n }\n\n try {\n // irc-framework is an optional dependency — install with\n // `npm install irc-framework` to enable. The dynamic import\n // throws ERR_MODULE_NOT_FOUND when not installed; we catch\n // below and log the actionable install command.\n // @ts-expect-error — irc-framework is an optional dependency, not in package.json\n const { Client } = await import('irc-framework');\n const [host, portStr] = token.split(':');\n const port = parseInt(portStr || '6667', 10);\n const nick = channelConfig.apiKey || 'TitanBot';\n\n const client = new Client();\n client.connect({ host, port, nick });\n\n client.on('registered', () => {\n this.connected = true;\n logger.info(COMPONENT, `Connected to IRC server ${host}:${port} as ${nick}`);\n // Join channels from allowFrom list\n for (const ch of channelConfig.allowFrom) {\n if (ch.startsWith('#')) {\n client.join(ch);\n logger.info(COMPONENT, `Joined ${ch}`);\n }\n }\n });\n\n client.on('privmsg', (event: { nick: string; target: string; message: string; time?: number }) => {\n try {\n const inbound: InboundMessage = {\n id: `${Date.now()}-${event.nick}`,\n channel: 'irc',\n userId: event.nick,\n userName: event.nick,\n content: event.message,\n groupId: event.target.startsWith('#') ? event.target : undefined,\n timestamp: new Date(event.time ?? Date.now()),\n raw: event,\n };\n this.emit('message', inbound);\n } catch (error) {\n logger.error(COMPONENT, `Message handler error: ${(error as Error).message}`);\n }\n });\n\n client.on('close', () => {\n this.connected = false;\n logger.info(COMPONENT, 'Disconnected from IRC');\n });\n\n this.client = client;\n } catch (error) {\n logger.error(COMPONENT, `Failed to connect: ${(error as Error).message}`);\n logger.info(COMPONENT, 'Install irc-framework with: npm install irc-framework');\n }\n }\n\n async disconnect(): Promise<void> {\n if (this.client) {\n (this.client as { quit(msg: string): void }).quit('TITAN shutting down');\n this.connected = false;\n this.client = null;\n logger.info(COMPONENT, 'Disconnected');\n }\n }\n\n async send(message: OutboundMessage): Promise<void> {\n if (!this.client || !this.connected) return;\n const target = message.groupId || message.userId;\n if (!target) {\n logger.warn(COMPONENT, 'No target provided for IRC message');\n return;\n }\n try {\n (this.client as { say(target: string, text: string): void }).say(target, message.content);\n logger.debug(COMPONENT, `Sent message to ${target}`);\n } catch (error) {\n logger.error(COMPONENT, `Send failed: ${(error as Error).message}`);\n }\n }\n\n getStatus(): ChannelStatus {\n return { name: this.displayName, connected: this.connected };\n }\n}\n"],"mappings":"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","names":[]}
@@ -3247,7 +3247,7 @@ data: ${JSON.stringify(structured)}
3247
3247
  registerSomaVerifier();
3248
3248
  const { startDriverScheduler, resumeDriversAfterRestart } = await import("../agent/driverScheduler.js");
3249
3249
  const resumed = await resumeDriversAfterRestart();
3250
- logger.info(COMPONENT, `Goal Driver resume: ${resumed.resumed} drivers re-activated, ${resumed.cancelled} cancelled (goal no longer active)`);
3250
+ logger.info(COMPONENT, `Goal Driver resume: ${resumed.resumed} re-activated, ${resumed.cancelled} cancelled (goal no longer active), ${resumed.sweptStale} stale (>24h since lastTick) reaped`);
3251
3251
  startDriverScheduler(1e4, 5);
3252
3252
  } catch (e) {
3253
3253
  logger.warn(COMPONENT, `Goal Driver scheduler bootstrap skipped: ${e.message}`);