{"version":3,"file":"daemon-DArRKBlk.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/workers/keepalive.ts","../src/workers/supervision.ts","../src/daemon/daemon/scheduler.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.ts","../src/storage/postgres/fts-migration.ts","../src/daemon/daemon/server.ts"],"sourcesContent":["/**\n * config.ts — Notification config persistence helpers\n *\n * Reads and writes the `notifications` section of CONFIG_FILE (see\n * src/daemon/config.ts's paiConfigFilePath).\n * Deep-merges with defaults so partial configs work fine.\n *\n * This module is intentionally separate from the daemon's config loader\n * so it can be used standalone (e.g. from CLI commands).\n */\n\nimport { expandHome, readMainConfigRaw, writeMainConfigRaw } from \"../daemon/config.js\";\nimport type {\n  NotificationConfig,\n  ChannelConfigs,\n  RoutingTable,\n  NotificationMode,\n} from \"./types.js\";\nimport {\n  DEFAULT_NOTIFICATION_CONFIG,\n  DEFAULT_CHANNELS,\n  DEFAULT_ROUTING,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Deep merge helper (same approach as daemon/config.ts)\n// ---------------------------------------------------------------------------\n\nfunction deepMerge<T extends object>(\n  target: T,\n  source: Record<string, unknown>\n): T {\n  const result = { ...target };\n  for (const key of Object.keys(source)) {\n    const srcVal = source[key];\n    if (srcVal === undefined || srcVal === null) continue;\n    const tgtVal = (target as Record<string, unknown>)[key];\n    if (\n      typeof srcVal === \"object\" &&\n      !Array.isArray(srcVal) &&\n      typeof tgtVal === \"object\" &&\n      tgtVal !== null &&\n      !Array.isArray(tgtVal)\n    ) {\n      (result as Record<string, unknown>)[key] = deepMerge(\n        tgtVal as object,\n        srcVal as Record<string, unknown>\n      );\n    } else {\n      (result as Record<string, unknown>)[key] = srcVal;\n    }\n  }\n  return result;\n}\n\n// ---------------------------------------------------------------------------\n// Load\n// ---------------------------------------------------------------------------\n\n/**\n * Load the notification config from the PAI config file.\n * Returns defaults merged with any stored values.\n */\nexport function loadNotificationConfig(): NotificationConfig {\n  let parsed: Record<string, unknown>;\n  try {\n    parsed = readMainConfigRaw();\n  } catch {\n    return { ...DEFAULT_NOTIFICATION_CONFIG };\n  }\n\n  const stored = parsed[\"notifications\"];\n  if (!stored || typeof stored !== \"object\") {\n    return { ...DEFAULT_NOTIFICATION_CONFIG };\n  }\n\n  return deepMerge(\n    DEFAULT_NOTIFICATION_CONFIG,\n    stored as Record<string, unknown>\n  );\n}\n\n// ---------------------------------------------------------------------------\n// Save\n// ---------------------------------------------------------------------------\n\n/**\n * Persist the notification config by merging it into the existing\n * CONFIG_FILE. Creates the file if it does not exist.\n */\nexport function saveNotificationConfig(config: NotificationConfig): void {\n  let full: Record<string, unknown> = {};\n  try {\n    full = readMainConfigRaw();\n  } catch {\n    // Start fresh if the file is unreadable\n  }\n\n  // Replace the notifications section\n  full[\"notifications\"] = config;\n\n  writeMainConfigRaw(full);\n}\n\n// ---------------------------------------------------------------------------\n// Patch helpers (used by the set command)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a partial update to the current notification config and persist it.\n * Returns the new merged config.\n */\nexport function patchNotificationConfig(patch: {\n  mode?: NotificationMode;\n  channels?: Partial<Partial<ChannelConfigs>>;\n  routing?: Partial<RoutingTable>;\n}): NotificationConfig {\n  const current = loadNotificationConfig();\n\n  if (patch.mode !== undefined) {\n    current.mode = patch.mode;\n  }\n\n  if (patch.channels) {\n    current.channels = deepMerge(\n      current.channels,\n      patch.channels as Record<string, unknown>\n    );\n  }\n\n  if (patch.routing) {\n    current.routing = deepMerge(\n      current.routing,\n      patch.routing as Record<string, unknown>\n    );\n  }\n\n  saveNotificationConfig(current);\n  return current;\n}\n\n// Re-export defaults for convenience\nexport { DEFAULT_NOTIFICATION_CONFIG, DEFAULT_CHANNELS, DEFAULT_ROUTING };\nexport { expandHome };\n","/**\n * keepalive.ts — one cache-keepalive beat, shared by the daemon scheduler and\n * the probe script.\n *\n * The provider (GLM anthropic-compat endpoint) caches the worker prefix\n * implicitly — no cache_control blocks — so a trivial single-turn worker spawn\n * through the very same runWorker path re-arms that cache for the next real\n * worker: identical env, identical tool grants, identical WORKER_CONTRACT_PROMPT\n * by construction, because it IS a worker spawn.\n *\n * Measured 2026-09-18 (docs/cache-keepalive.md): the implicit cache only\n * serves spawns within seconds of the previous identical request and never\n * survived a 60 s beat cadence in the armed proof — hence the knob defaults\n * to off and arming it is the operator's explicit call.\n *\n * Deliberately NOT a hand-built claude invocation: duplicated prefix\n * construction is how this repo has shipped real breakage before (probeResume\n * x3, archiver x2). Everything flows through runWorker.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { runWorker, type StreamEvent } from \"./run.js\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { eventsPath, ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatus } from \"./status.js\";\n\n/**\n * The trivial prompt every cache beat (and probe) carries. Kept in one place\n * so the heartbeat arms exactly the prefix a verifying probe measures.\n */\nexport const HEARTBEAT_PROMPT = \"Reply with exactly one word: pong.\";\n\n/** The worker class a beat runs as — the cheapest spawn the config defines. */\nexport const KEEPALIVE_CLASS = \"simple\";\n\n/** Worker label of every beat, so `pai worker ps` can tell them apart. */\nexport const KEEPALIVE_LABEL = \"cache-keepalive\";\n\n/** Cadence a keepalive runs at for this config; 0 keeps it off. */\nexport function keepaliveSecs(workers: { enabled: boolean; cacheKeepaliveSecs: number }): number {\n  return workers.enabled ? workers.cacheKeepaliveSecs : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Transcript parsing — the only real usage on this endpoint is the final\n// result event (per-turn assistant usage is zeroed), so a beat and a probe\n// both read exactly that line.\n// ---------------------------------------------------------------------------\n\n/**\n * The LAST `type:\"result\"` event of a worker transcript, or null when the\n * file is missing, unreadable, or carries no result line (a failed run can\n * end without one).\n */\nexport function parseLastResultEvent(path: string): StreamEvent | null {\n  let text: string;\n  try {\n    text = readFileSync(path, \"utf8\");\n  } catch {\n    return null;\n  }\n  let found: StreamEvent | null = null;\n  for (const line of text.split(\"\\n\")) {\n    if (!line.startsWith(\"{\")) continue;\n    let e: StreamEvent;\n    try {\n      e = JSON.parse(line) as StreamEvent;\n    } catch {\n      continue;\n    }\n    if (e.type === \"result\") found = e;\n  }\n  return found;\n}\n\n/** One worker's cache metrics, as reported by its final result event. */\nexport interface BeatMetrics {\n  id: string;\n  ok: boolean;\n  rc: number | null;\n  provider: string | null;\n  model: string | null;\n  input_tokens: number | null;\n  output_tokens: number | null;\n  cache_read_input_tokens: number | null;\n  cache_creation_input_tokens: number | null;\n  duration_api_ms: number | null;\n  duration_ms: number | null;\n  num_turns: number | null;\n}\n\n/**\n * Metrics of one run from its final result event. Single-turn runs make the\n * cumulative result usage identical to first-turn usage, so a warm cache\n * shows up directly as cache_read_input_tokens > 0.\n */\nexport function extractBeatMetrics(\n  m: Omit<BeatMetrics, \"input_tokens\" | \"output_tokens\" | \"cache_read_input_tokens\" | \"cache_creation_input_tokens\" | \"duration_api_ms\" | \"duration_ms\" | \"num_turns\">,\n  ev: StreamEvent | null\n): BeatMetrics {\n  const u = ev?.usage;\n  return {\n    ...m,\n    input_tokens: u?.input_tokens ?? null,\n    output_tokens: u?.output_tokens ?? null,\n    cache_read_input_tokens: u?.cache_read_input_tokens ?? null,\n    cache_creation_input_tokens: u?.cache_creation_input_tokens ?? null,\n    duration_api_ms: ev?.duration_api_ms ?? null,\n    duration_ms: ev?.duration_ms ?? null,\n    num_turns: ev?.num_turns ?? null,\n  };\n}\n\n// ---------------------------------------------------------------------------\n// The beat\n// ---------------------------------------------------------------------------\n\nexport interface BeatOptions {\n  /** Config path override (tests never touch the live config). */\n  configPath?: string;\n  /** Preset worker id (tests); a fresh stamp when omitted. */\n  id?: string;\n}\n\n/** True while a beat's worker is still running — the overlap guard reads it. */\nlet beatInFlight = false;\n\nexport function beatBusy(): boolean {\n  return beatInFlight;\n}\n\n/** `keepalive-<YYYYMMDD-HHMMSS>` — beats are minutes apart, seconds suffice. */\nexport function beatId(d: Date = new Date()): string {\n  const p = (n: number) => String(n).padStart(2, \"0\");\n  return (\n    `keepalive-${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}` +\n    `-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`\n  );\n}\n\n/**\n * One heartbeat: a single-turn trivial worker through the exact worker spawn\n * path, its result metrics parsed from the transcript, one WORKER-KEEPALIVE\n * ledger line. Returns null when another beat is still running (overlap\n * guard) — a skipped beat is free, a queue of them is not.\n *\n * The beat runs in the workers logDir: no session renders its status line\n * there, so the spawn adopts no spawnerSession and no iTerm identity, and\n * worker supervision has no owner to notify — heartbeats are infrastructure,\n * not somebody's workers.\n */\nexport async function runKeepaliveBeat(opts: BeatOptions = {}): Promise<BeatMetrics | null> {\n  if (beatInFlight) return null;\n  beatInFlight = true;\n  try {\n    const { workers: config } = readWorkersSection(opts.configPath);\n    const logDir = workersLogDir(config);\n    const id = opts.id ?? beatId();\n    const rc = await runWorker({\n      claudeArgs: [\"-p\", HEARTBEAT_PROMPT],\n      className: KEEPALIVE_CLASS,\n      label: KEEPALIVE_LABEL,\n      noPane: true,\n      quiet: true,\n      worktreeFlag: false,\n      id,\n      cwd: logDir,\n    });\n    const status = loadStatus(logDir, id);\n    const metrics = extractBeatMetrics(\n      {\n        id,\n        ok: rc === 0,\n        rc,\n        provider: status?.provider ?? null,\n        model: status?.model ?? null,\n      },\n      parseLastResultEvent(eventsPath(logDir, id))\n    );\n    appendLedger(ledgerPath(logDir), \"WORKER-KEEPALIVE\", {\n      id,\n      provider: metrics.provider,\n      model: metrics.model,\n      input_tokens: metrics.input_tokens,\n      cache_read_input_tokens: metrics.cache_read_input_tokens,\n      cache_creation_input_tokens: metrics.cache_creation_input_tokens,\n      duration_api_ms: metrics.duration_api_ms,\n    });\n    return metrics;\n  } finally {\n    beatInFlight = false;\n  }\n}\n","/**\n * supervision.ts — daemon-side worker supervision, event push not polling.\n *\n * An orchestrating session must keep an eye on its workers without sleeping\n * (the Worker skill forbids busy-waiting), the same way the AIBroker daemon\n * keeps an eye on sessions. The PAI daemon already runs all day; this module\n * gives it a tick that watches every worker's status file and pushes one-line\n * events to the owning session when something happens:\n *\n *   finished — the run ended clean (state done, rc 0)\n *   failed   — the run ended badly (state failed/killed/lost, or rc != 0,\n *              or the runner pid vanished while state was still running)\n *   stalled  — state running, runner alive, but no new turn for longer than\n *              the stall threshold (PAI_WORKER_STALL_MINUTES, default 10)\n *   thrashing — state running, runner alive, turning, but the last N tool\n *              executions all failed (PAI_WORKER_THRASH_FAILS, default 5) —\n *              a worker that keeps failing every command never goes quiet,\n *              so the stall detector alone cannot see it\n *   sleeping — state running, one Bash command sleeping N >= 60s with nothing\n *              to wait on — under the stall threshold for runs like\n *              `sleep 500` (8.3m), which would otherwise be invisible until\n *              they cross it. The bounded-wait pattern (background a build or\n *              test, then `sleep 60; ps` cycles) has live children and stays\n *              quiet; stalled and thrashing remain the backstop for a child\n *              that never finishes or dies silently\n *\n * The interactive chat pane (origin \"chat\") is never supervised: it is the\n * operator's own session, not a task worker. Its status file flips to done\n * when the session ends, and reporting that as \"worker finished\" to whoever\n * next opens the same tab is noise, not an outcome.\n *\n * Delivery, in order of preference, never both counted as required:\n *   1. AIBroker `send_to_session` into the orchestrator's terminal — the\n *      one-line Agentish-style notification, arriving as a user turn without\n *      the orchestrator asking for it. Needs the iTerm identity of the owner,\n *      which the status line bridges into the session map (see scope.ts).\n *   2. An append-only event file, <logDir>/supervision/<owner>.events, one\n *      JSON line per event. The UserPromptSubmit hook\n *      (src/hooks/ts/user-prompt/worker-supervision.ts) surfaces undelivered\n *      lines into the session that owns them, so the events reach the model\n *      on its next turn even with AIBroker absent.\n *\n * Exactly-once: every event carries a deterministic id and the set of\n * delivered ids per worker is persisted (<logDir>/supervision/state.json), so\n * a daemon restart replays nothing. A first run with no state adopts every\n * already-terminal worker as delivered — supervision reports transitions it\n * observes, not history. Chain stages are separate workers with their own\n * status files and are supervised individually.\n *\n * No model is ever called from here — this is daemon code, and it must stay\n * that way: supervision that bills tokens to watch tokens is a bug.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { alive, isChatPane, loadStatuses, nowStamp, type WorkerStatus } from \"./status.js\";\nimport { itermForClaudeSession } from \"./scope.js\";\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type SupervisionKind = \"finished\" | \"failed\" | \"stalled\" | \"thrashing\" | \"sleeping\";\n\n/** The kinds that fire mid-run and re-arm on turn progress (see eventId). */\nconst MID_RUN_KINDS: readonly SupervisionKind[] = [\"stalled\", \"thrashing\", \"sleeping\"];\n\nexport interface SupervisionEvent {\n  /** Deterministic dedup key — see eventId(). */\n  id: string;\n  /** nowStamp() at detection. */\n  ts: string;\n  kind: SupervisionKind;\n  worker: string;\n  label: string;\n  /** Owning session key: the spawner's claude session id, or the iTerm uuid. */\n  session: string;\n  /** Exit code when the run reported one, else null. */\n  rc: number | null;\n  /** Minutes without a turn, for stalled events; else null. */\n  stalledMin: number | null;\n  /** Consecutive failed commands, for thrashing events; else null. */\n  consecFails?: number | null;\n  /** Seconds of the one sleeping command, for sleeping events; else null. */\n  sleepSec?: number | null;\n  /** The one-liner that reaches the orchestrator. */\n  text: string;\n}\n\n/**\n * The owning session of a worker, or null when nobody claims it (and so\n * nothing to notify): the spawner's claude session id first — that is what\n * the hook knows a session by — else the AIBroker iTerm uuid.\n */\nexport function ownershipKey(s: WorkerStatus): string | null {\n  if (s.spawnerSession) return s.spawnerSession;\n  if (s.session?.id) return s.session.id;\n  return null;\n}\n\nconst TERMINAL: readonly WorkerStatus[\"state\"][] = [\"done\", \"failed\", \"killed\", \"lost\"];\n\nfunction isTerminal(s: WorkerStatus): boolean {\n  return TERMINAL.includes(s.state);\n}\n\n/** Age of a \"YYYY-MM-DD HH:MM:SS\" stamp in ms, NaN-safe (unparsable → 0). */\nfunction ageMs(ts: string, now: Date): number {\n  const t = Date.parse(ts.replace(\" \", \"T\"));\n  if (Number.isNaN(t)) return 0;\n  return Math.max(0, now.getTime() - t);\n}\n\n/**\n * The stall threshold in minutes from PAI_WORKER_STALL_MINUTES (default 10).\n * Anything unparsible or non-positive falls back to the default — a broken\n * value must not turn supervision off or make it fire every tick.\n */\nexport const DEFAULT_STALL_MINUTES = 10;\n\nexport function stallMinutesFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n  const raw = env.PAI_WORKER_STALL_MINUTES;\n  if (raw === undefined || raw === \"\") return DEFAULT_STALL_MINUTES;\n  const n = Number(raw);\n  return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALL_MINUTES;\n}\n\n/**\n * The consecutive-failure threshold from PAI_WORKER_THRASH_FAILS (default 5),\n * with the same broken-value tolerance as stallMinutesFromEnv.\n */\nexport const DEFAULT_THRASH_FAILS = 5;\n\nexport function thrashFailsFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n  const raw = env.PAI_WORKER_THRASH_FAILS;\n  if (raw === undefined || raw === \"\") return DEFAULT_THRASH_FAILS;\n  const n = Number(raw);\n  return Number.isFinite(n) && n > 0 ? n : DEFAULT_THRASH_FAILS;\n}\n\n/**\n * Event id: `<worker>#<kind>` for the once-per-run terminal kinds, and\n * `<worker>#<kind>@<turns>` for the mid-run kinds (stalled, thrashing,\n * sleeping) — the turn count re-arms the id, so a worker that resumes and\n * hits the same condition again gets a second event while a restart cannot\n * replay the first.\n */\nexport function eventId(worker: string, kind: SupervisionKind, turns = 0): string {\n  return MID_RUN_KINDS.includes(kind) ? `${worker}#${kind}@${turns}` : `${worker}#${kind}`;\n}\n\n/**\n * Live child pids of a runner via `pgrep -P` (its exit 1 means \"no\n * children\"). Any failure — here or in an injected stub — is caught at the\n * one call site in detectSupervisionEvents and reads as an empty list, so a\n * broken check degrades to firing the sleeping event, never to silencing it.\n */\nfunction pgrepChildren(pid: number): number[] {\n  return execFileSync(\"pgrep\", [\"-P\", String(pid)], { encoding: \"utf8\" })\n    .split(\"\\n\")\n    .flatMap((l) => {\n      const n = Number(l.trim());\n      return Number.isInteger(n) && n > 0 ? [n] : [];\n    });\n}\n\nexport interface DetectOptions {\n  /** How long a running worker may go without a turn before \"stalled\". */\n  stallMs: number;\n  now: Date;\n  /** Consecutive failed tool executions before \"thrashing\" (default 5). */\n  thrashFails?: number;\n  /** pid liveness — injected so tests need no real processes. */\n  isAlive?: (pid: number) => boolean;\n  /**\n   * Live child pids of a runner — a sleeping worker with children is a\n   * bounded wait on real work, not a stuck one. Injected so tests need no\n   * pgrep; the default is pgrepChildren.\n   */\n  childPids?: (pid: number) => number[];\n  /**\n   * How stale `updated` must be before a dead pid counts as killed rather\n   * than as a status write racing the tick.\n   */\n  deadGraceMs?: number;\n}\n\n/** One event's one-liner — the exact line the orchestrator reads. */\nfunction eventText(kind: SupervisionKind, s: WorkerStatus, rc: number | null, stalledMin: number | null): string {\n  const tail = `- see pai worker replay ${s.id}`;\n  if (kind === \"finished\") return `worker ${s.id} ${s.label} finished rc=0 ${tail}`;\n  if (kind === \"stalled\") return `worker ${s.id} ${s.label} stalled ${stalledMin}m no turns ${tail}`;\n  if (kind === \"thrashing\")\n    return `worker ${s.id} ${s.label} thrashing: ${s.consecFails} consecutive failed commands ${tail}`;\n  if (kind === \"sleeping\") return `worker ${s.id} ${s.label} sleeping ${s.sleepSec}s in one command ${tail}`;\n  const why = rc === null ? \"runner gone\" : `rc=${rc}`;\n  return `worker ${s.id} ${s.label} failed ${why} ${tail}`;\n}\n\n/**\n * Detect conditions across a ledger snapshot. Pure: statuses in, events out;\n * dedup against delivered ids is the caller's job (see filterUndelivered).\n * Workers without an owning session are skipped — nobody to notify.\n */\nexport function detectSupervisionEvents(\n  statuses: WorkerStatus[],\n  opts: DetectOptions\n): SupervisionEvent[] {\n  const isAlive = opts.isAlive ?? alive;\n  const deadGraceMs = opts.deadGraceMs ?? 15_000;\n  const out: SupervisionEvent[] = [];\n  for (const s of statuses) {\n    const session = ownershipKey(s);\n    if (!session) continue;\n    // the operator's own pane: ending it is not a worker outcome, idling is\n    // not a stall — nothing here is worth a push into anybody's terminal\n    if (isChatPane(s)) continue;\n    let kind: SupervisionKind | null = null;\n    let rc: number | null = null;\n    let stalledMin: number | null = null;\n    if (isTerminal(s)) {\n      // a finished run is state done with rc 0; every other terminal shape\n      // (failed, killed, lost, done with a non-zero rc) is a failure\n      if (s.state === \"done\" && (s.rc ?? 0) === 0) {\n        kind = \"finished\";\n        rc = 0;\n      } else {\n        kind = \"failed\";\n        rc = s.state === \"lost\" ? null : s.rc;\n      }\n    } else if (!isAlive(s.pid) && ageMs(s.updated, opts.now) > deadGraceMs) {\n      // state says running but the runner is gone (SIGKILL, crash) — the\n      // status will never get its rc, so supervision is the only witness\n      kind = \"failed\";\n      rc = null;\n    } else if (ageMs(s.updated, opts.now) > opts.stallMs) {\n      kind = \"stalled\";\n      stalledMin = Math.floor(ageMs(s.updated, opts.now) / 60_000);\n    } else if ((s.consecFails ?? 0) >= (opts.thrashFails ?? DEFAULT_THRASH_FAILS)) {\n      kind = \"thrashing\";\n    } else if (s.sleepSec != null) {\n      // bounded wait on real work: a sleep while a spawned child still runs\n      // is healthy, so flag only a sleep with nothing to wait on. The check\n      // runs once per sleeping candidate, only for a live pid; a failed\n      // check reads as no children and fires. stalled (no turns) and\n      // thrashing stay the backstop and never pass through here.\n      let hasChildren = false;\n      if (isAlive(s.pid)) {\n        try {\n          hasChildren = (opts.childPids ?? pgrepChildren)(s.pid).length > 0;\n        } catch {\n          hasChildren = false; // pgrep gone: degrade to today's behavior\n        }\n      }\n      if (!hasChildren) kind = \"sleeping\";\n    }\n    if (!kind) continue;\n    out.push({\n      id: eventId(s.id, kind, s.turns),\n      ts: nowStamp(opts.now),\n      kind,\n      worker: s.id,\n      label: s.label,\n      session,\n      rc,\n      stalledMin,\n      ...(kind === \"thrashing\" ? { consecFails: s.consecFails ?? 0 } : {}),\n      ...(kind === \"sleeping\" ? { sleepSec: s.sleepSec ?? 0 } : {}),\n      text: eventText(kind, s, rc, stalledMin),\n    });\n  }\n  return out;\n}\n\n// ---------------------------------------------------------------------------\n// Delivered-id persistence — the restart guard\n// ---------------------------------------------------------------------------\n\nexport interface SupervisionState {\n  /** worker id → event ids already written to that owner's event file. */\n  delivered: Record<string, string[]>;\n}\n\n/** Cap on remembered workers: terminal and delivered is history, not state. */\nconst MAX_REMEMBERED_WORKERS = 500;\n\nexport function supervisionDirPath(logDir: string): string {\n  return join(logDir, \"supervision\");\n}\n\nexport function supervisionStatePath(logDir: string): string {\n  return join(supervisionDirPath(logDir), \"state.json\");\n}\n\nexport function supervisionEventsPath(logDir: string, session: string): string {\n  return join(supervisionDirPath(logDir), `${session}.events`);\n}\n\n/** Receipts of events already pushed into a terminal, one event id per line. */\nexport function supervisionPushedPath(logDir: string, session: string): string {\n  return join(supervisionDirPath(logDir), `${session}.pushed`);\n}\n\nexport function loadSupervisionState(path: string): { state: SupervisionState; fresh: boolean } {\n  if (!existsSync(path)) return { state: { delivered: {} }, fresh: true };\n  try {\n    const parsed = JSON.parse(readFileSync(path, \"utf8\")) as SupervisionState;\n    if (parsed && typeof parsed === \"object\" && typeof parsed.delivered === \"object\") {\n      return { state: parsed, fresh: false };\n    }\n  } catch {\n    // a damaged state file replays at most one round of terminal events;\n    // rewriting it is the recovery, not the problem\n  }\n  return { state: { delivered: {} }, fresh: true };\n}\n\nexport function saveSupervisionState(path: string, state: SupervisionState): void {\n  const dir = dirname(path);\n  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n  const tmp = `${path}.tmp`;\n  writeFileSync(tmp, JSON.stringify(state), \"utf8\");\n  renameSync(tmp, path);\n}\n\n/**\n * Drop the delivered ids of workers the ledger no longer lists (absent means\n * history: their status files are gone) and cap only that history — newest\n * ids last, worker ids sort chronologically — so the persisted state stays\n * bounded. A worker the ledger still lists must never lose its receipts:\n * pruning one un-marks its terminal events as delivered, and every save\n * re-arms them into an unbounded re-delivery loop (the 2026-09-25 incident:\n * one old chain stage re-pushed 7,393 times, because the cap of 500 bit into\n * a ledger of ~1,700 live statuses).\n */\nexport function pruneState(\n  state: SupervisionState,\n  statuses: WorkerStatus[],\n  maxAbsent = MAX_REMEMBERED_WORKERS\n): SupervisionState {\n  const present = new Set(statuses.map((s) => s.id));\n  const entries = Object.entries(state.delivered);\n  const byId = ([a]: [string, string[]], [b]: [string, string[]]) => (a < b ? -1 : a > b ? 1 : 0);\n  const keptPresent = entries.filter(([id]) => present.has(id)).sort(byId);\n  const absent = entries.filter(([id]) => !present.has(id)).sort(byId);\n  // not slice(-maxAbsent): with maxAbsent 0 that returns the whole array\n  const keptAbsent = absent.slice(Math.max(0, absent.length - maxAbsent));\n  return { delivered: Object.fromEntries([...keptAbsent, ...keptPresent]) };\n}\n\n/**\n * First run: adopt every already-terminal worker as delivered. Supervision\n * reports the transitions it observes from now on, never a replay of every\n * worker that ever finished before it started.\n */\nexport function baselineState(statuses: WorkerStatus[]): SupervisionState {\n  const delivered: Record<string, string[]> = {};\n  for (const s of statuses) {\n    if (!isTerminal(s) || !ownershipKey(s)) continue;\n    delivered[s.id] = [eventId(s.id, \"finished\"), eventId(s.id, \"failed\")];\n  }\n  return { delivered };\n}\n\n/** The events whose ids the state has not seen yet. */\nexport function filterUndelivered(events: SupervisionEvent[], state: SupervisionState): SupervisionEvent[] {\n  return events.filter((e) => !(state.delivered[e.worker] ?? []).includes(e.id));\n}\n\nexport function markDelivered(state: SupervisionState, events: SupervisionEvent[]): void {\n  for (const e of events) {\n    const ids = state.delivered[e.worker] ?? [];\n    if (!ids.includes(e.id)) ids.push(e.id);\n    state.delivered[e.worker] = ids;\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery\n// ---------------------------------------------------------------------------\n\n/** Where an event's push goes, resolved from the worker's own identities. */\nexport interface PushTarget {\n  claudeSession: string | null;\n  iterm: string | null;\n}\n\nexport type PushFn = (logDir: string, target: PushTarget, ev: SupervisionEvent) => Promise<boolean>;\n\n/**\n * Default push: AIBroker `send_to_session` into the orchestrator's terminal,\n * using the iTerm uuid the worker recorded (`session.id`) or the one the\n * session map bridges for the spawner's claude session. A timeout still\n * delivered (see aibroker-client) counts as delivered; every failure is\n * reported as not-delivered so only the event file carries the event.\n */\nexport const aibrokerPush: PushFn = async (logDir, target, ev) => {\n  const iterm =\n    target.iterm ?? (target.claudeSession ? itermForClaudeSession(logDir, target.claudeSession) : null);\n  if (!iterm) return false;\n  try {\n    const { sendToSession } = await import(\"../cli/lib/aibroker-client.js\");\n    const r = await sendToSession(iterm, ev.text);\n    return r.ok || r.timedOut === true;\n  } catch {\n    return false;\n  }\n};\n\n/**\n * Whether an event should be relayed into the owner's terminal. The mid-run\n * kinds (stalled, thrashing, sleeping) ALWAYS relay: a json/stream-json\n * caller knows the run's result at exit but has no other way to hear\n * mid-run trouble. Only the terminal kinds (finished/failed) skip the relay\n * for those callers — they read the run's result itself, and the harness\n * already notifies them on process exit, so the one-liner would cost a full\n * extra turn for information they already have. Interactive/pane launches\n * and the default text format are never skipped.\n */\nexport function shouldRelay(\n  kind: SupervisionKind,\n  outputFormat: WorkerStatus[\"outputFormat\"] | undefined\n): boolean {\n  if (MID_RUN_KINDS.includes(kind)) return true;\n  return outputFormat !== \"json\" && outputFormat !== \"stream-json\";\n}\n\nfunction appendEventFile(logDir: string, ev: SupervisionEvent): void {\n  const path = supervisionEventsPath(logDir, ev.session);\n  const dir = supervisionDirPath(logDir);\n  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n  appendFileSync(path, JSON.stringify(ev) + \"\\n\", \"utf8\");\n}\n\n/** Record that an event reached its owner's terminal — the hook then skips it. */\nfunction appendPushReceipt(logDir: string, ev: SupervisionEvent): void {\n  try {\n    appendFileSync(supervisionPushedPath(logDir, ev.session), `${ev.id}\\n`, \"utf8\");\n  } catch {\n    // a missing receipt only means the hook may repeat the line\n  }\n}\n\nexport interface TickOptions {\n  now?: Date;\n  stallMs?: number;\n  /** Consecutive-failure threshold for thrashing (default: PAI_WORKER_THRASH_FAILS). */\n  thrashFails?: number;\n  isAlive?: (pid: number) => boolean;\n  /** Injectable for tests; production pushes through AIBroker. */\n  push?: PushFn;\n  /** Preloaded map, so a test (or a batch of ticks) can share one read. */\n  statuses?: WorkerStatus[];\n}\n\nexport interface TickResult {\n  events: SupervisionEvent[];\n  /** Events that also reached a terminal through the push channel. */\n  pushed: SupervisionEvent[];\n}\n\n/**\n * One supervision pass over the ledger: detect, append each new event to its\n * owner's event file, push it best effort, persist the delivered ids. The\n * event file is the durable record — an event is marked delivered only after\n * its line landed there, so a failed tick retries, a repeated one replays\n * nothing.\n */\nexport async function runSupervisionTick(logDir: string, opts: TickOptions = {}): Promise<TickResult> {\n  const now = opts.now ?? new Date();\n  const statuses = opts.statuses ?? loadStatuses(logDir);\n  const statePath = supervisionStatePath(logDir);\n  const loaded = loadSupervisionState(statePath);\n  let state = loaded.state;\n  if (loaded.fresh) state = baselineState(statuses);\n\n  const pending = filterUndelivered(\n    detectSupervisionEvents(statuses, {\n      stallMs: opts.stallMs ?? DEFAULT_STALL_MINUTES * 60_000,\n      thrashFails: opts.thrashFails ?? thrashFailsFromEnv(),\n      now,\n      ...(opts.isAlive ? { isAlive: opts.isAlive } : {}),\n    }),\n    state\n  );\n\n  const written: SupervisionEvent[] = [];\n  for (const ev of pending) {\n    appendEventFile(logDir, ev);\n    written.push(ev);\n  }\n\n  const push = opts.push ?? aibrokerPush;\n  const pushed: SupervisionEvent[] = [];\n  for (const ev of written) {\n    // the owner's iTerm identity is in its worker status, not the event\n    const owner = statuses.find((s) => s.id === ev.worker);\n    if (!shouldRelay(ev.kind, owner?.outputFormat)) continue; // caller reads its own JSON result\n    const target: PushTarget = {\n      claudeSession: owner?.spawnerSession ?? null,\n      iterm: owner?.session?.id ?? null,\n    };\n    let ok = false;\n    try {\n      ok = await push(logDir, target, ev);\n    } catch {\n      ok = false;\n    }\n    if (ok) {\n      pushed.push(ev);\n      appendPushReceipt(logDir, ev);\n    }\n  }\n\n  markDelivered(state, written);\n  saveSupervisionState(statePath, pruneState(state, statuses));\n  return { events: written, pushed };\n}\n","/**\n * Index, embed, vault index, and registry scan schedulers for the PAI daemon.\n * Exports run* functions (also called on-demand by the IPC handler)\n * and the start* functions invoked once at daemon startup.\n */\n\nimport { readWorkersSection } from \"../../workers/config.js\";\nimport { keepaliveSecs, runKeepaliveBeat, type BeatMetrics } from \"../../workers/keepalive.js\";\nimport { workersLogDir } from \"../../workers/paths.js\";\nimport { runSupervisionTick, stallMinutesFromEnv, thrashFailsFromEnv } from \"../../workers/supervision.js\";\nimport { runSessionKeepaliveTick } from \"../session-keepalive.js\";\nimport {\n  registryBackend,\n  storageBackend,\n  daemonConfig,\n  indexInProgress,\n  embedInProgress,\n  vaultIndexInProgress,\n  shutdownRequested,\n  setIndexInProgress,\n  setLastIndexTime,\n  setIndexSchedulerTimer,\n  setEmbedInProgress,\n  setLastEmbedTime,\n  setEmbedSchedulerTimer,\n  setVaultIndexInProgress,\n  setLastVaultIndexTime,\n  setCacheKeepaliveTimer,\n} from \"./state.js\";\n\n// ---------------------------------------------------------------------------\n// Index scheduler\n// ---------------------------------------------------------------------------\n\n/** Minimum interval between vault index runs (30 minutes). */\nconst VAULT_INDEX_MIN_INTERVAL_MS = 30 * 60 * 1000;\n\n/**\n * Run a full index pass. Guards against overlapping runs with indexInProgress.\n * Called both by the scheduler and by the index_now IPC method.\n */\nexport async function runIndex(): Promise<void> {\n  if (indexInProgress) {\n    process.stderr.write(\"[pai-daemon] Index already in progress, skipping.\\n\");\n    return;\n  }\n\n  if (embedInProgress) {\n    process.stderr.write(\"[pai-daemon] Embed in progress, deferring index run.\\n\");\n    return;\n  }\n\n  setIndexInProgress(true);\n  const t0 = Date.now();\n\n  try {\n    process.stderr.write(\"[pai-daemon] Starting scheduled index run...\\n\");\n\n    const { projects, result } = await storageBackend.indexAll(registryBackend);\n    const elapsed = Date.now() - t0;\n    setLastIndexTime(Date.now());\n    process.stderr.write(\n      `[pai-daemon] Index complete: ${projects} projects, ` +\n        `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n        `(${elapsed}ms)\\n`\n    );\n  } catch (e) {\n    const msg = e instanceof Error ? e.message : String(e);\n    process.stderr.write(`[pai-daemon] Index error: ${msg}\\n`);\n  } finally {\n    setIndexInProgress(false);\n  }\n}\n\n/**\n * Run a vault index pass. Guards against overlapping runs with vaultIndexInProgress.\n * Skips if no vaultPath is configured, or if project index/embed is in progress.\n */\nexport async function runVaultIndex(): Promise<void> {\n  if (!daemonConfig.vaultPath) return;\n\n  if (vaultIndexInProgress) {\n    process.stderr.write(\"[pai-daemon] Vault index already in progress, skipping.\\n\");\n    return;\n  }\n\n  if (indexInProgress || embedInProgress) {\n    process.stderr.write(\"[pai-daemon] Index/embed in progress, deferring vault index.\\n\");\n    return;\n  }\n\n  // Import lastVaultIndexTime from state (re-read each call since it may change)\n  const { lastVaultIndexTime } = await import(\"./state.js\");\n  if (lastVaultIndexTime > 0 && Date.now() - lastVaultIndexTime < VAULT_INDEX_MIN_INTERVAL_MS) {\n    return;\n  }\n\n  let vaultProjectId = daemonConfig.vaultProjectId;\n  if (!vaultProjectId) {\n    const row = await registryBackend.getProjectByRootPath(daemonConfig.vaultPath!);\n    vaultProjectId = row?.id ?? 999;\n    if (!row) {\n      process.stderr.write(\"[pai-daemon] Vault not in project registry — using synthetic project ID 999.\\n\");\n    }\n  }\n\n  setVaultIndexInProgress(true);\n  const t0 = Date.now();\n\n  process.stderr.write(\"[pai-daemon] Starting vault index run...\\n\");\n\n  try {\n    const { indexVault } = await import(\"../../memory/vault-indexer.js\");\n    const r = await indexVault(storageBackend, vaultProjectId, daemonConfig.vaultPath!);\n    const elapsed = Date.now() - t0;\n    setLastVaultIndexTime(Date.now());\n    process.stderr.write(\n      `[pai-daemon] Vault index complete: ${r.filesIndexed} files, ` +\n      `${r.linksExtracted} links, ${r.deadLinksFound} dead, ` +\n      `${r.orphansFound} orphans (${elapsed}ms)\\n`\n    );\n  } catch (e) {\n    const msg = e instanceof Error ? e.message : String(e);\n    process.stderr.write(`[pai-daemon] Vault index error: ${msg}\\n`);\n  } finally {\n    setVaultIndexInProgress(false);\n  }\n}\n\n/**\n * Start the periodic index scheduler. Runs an initial pass 2 seconds after startup.\n */\nexport function startIndexScheduler(): void {\n  const intervalMs = daemonConfig.indexIntervalSecs * 1_000;\n\n  process.stderr.write(\n    `[pai-daemon] Index scheduler: every ${daemonConfig.indexIntervalSecs}s\\n`\n  );\n\n  // Indexing and embedding are mutually exclusive (each defers to the other),\n  // and an index pass regularly outlasts the index interval. Left to two\n  // independent timers, the embed tick therefore lands while indexing is in\n  // progress and is dropped — observed live on 2026-08-01 as six consecutive\n  // index passes with no embed pass at all, and a 143k-chunk embedding backlog.\n  // Chaining embed onto the end of every index pass makes the alternation\n  // guaranteed rather than a race: index, then vault, then embed, every cycle.\n  const cycle = (label: string, withEmbed: boolean) =>\n    runIndex()\n      .then(() => runVaultIndex())\n      .then(() => (withEmbed ? runEmbed() : undefined))\n      .catch((e) => {\n        process.stderr.write(`[pai-daemon] ${label} index error: ${e}\\n`);\n      });\n\n  // The startup pass indexes but must NOT embed unless embedOnStartup says so.\n  // startEmbedScheduler already refuses a startup embed for exactly that reason;\n  // chaining runEmbed onto this pass reinstated the same CPU storm through the\n  // back door. Seen live after a reboot: \"Startup embed pass skipped\n  // (embedOnStartup=false)\" in the log, and minutes later the same boot running\n  // a 5000-chunk pass at ~450% CPU. A guard one scheduler honours and another\n  // walks around is not a guard.\n  // With a maintenance hour configured there is no startup pass at all. Every\n  // restart otherwise buys itself a full index run, so on a machine that is\n  // rebooted or has the daemon reloaded during the day the \"maintenance runs at\n  // night\" guarantee is worth nothing: the heavy work simply follows the\n  // restarts around. Indexing waits for the window like everything else.\n  if (daemonConfig.maintenanceHour === undefined) {\n    setTimeout(() => void cycle(\"Startup\", daemonConfig.embedOnStartup), 2_000);\n  } else {\n    process.stderr.write(\n      \"[pai-daemon] Startup index pass skipped (maintenanceHour is set).\\n\"\n    );\n  }\n\n  // Anchor the recurring cycle to a wall-clock hour when one is configured.\n  // setInterval alone counts from daemon start, so a machine rebooted at noon\n  // gets its \"daily\" maintenance at noon every day thereafter — the interval\n  // says how often, never when. Without an anchor there is no such thing as a\n  // night-only pass, however long the interval.\n  const firstDelayMs = msUntilNextAnchor(daemonConfig.maintenanceHour, intervalMs);\n  if (daemonConfig.maintenanceHour !== undefined) {\n    process.stderr.write(\n      `[pai-daemon] Index scheduler anchored to ${String(daemonConfig.maintenanceHour).padStart(2, \"0\")}:00 local ` +\n        `(next pass in ${Math.round(firstDelayMs / 60_000)} min)\\n`\n    );\n  }\n\n  let timer: ReturnType<typeof setInterval>;\n  const startTimer = () => {\n    timer = setInterval(() => void cycle(\"Scheduled\", true), intervalMs);\n    if (timer.unref) timer.unref();\n    setIndexSchedulerTimer(timer);\n  };\n\n  const first = setTimeout(() => {\n    void cycle(\"Scheduled\", true);\n    startTimer();\n  }, firstDelayMs);\n  if (first.unref) first.unref();\n}\n\n/**\n * Milliseconds until the next occurrence of `hour`:00 local time.\n * Falls back to the plain interval when no anchor hour is configured.\n */\nfunction msUntilNextAnchor(hour: number | undefined, intervalMs: number): number {\n  if (hour === undefined || !Number.isInteger(hour) || hour < 0 || hour > 23) {\n    return intervalMs;\n  }\n  const now = new Date();\n  const next = new Date(now);\n  next.setHours(hour, 0, 0, 0);\n  if (next <= now) next.setDate(next.getDate() + 1);\n  return next.getTime() - now.getTime();\n}\n\n// ---------------------------------------------------------------------------\n// Embed scheduler\n// ---------------------------------------------------------------------------\n\n/**\n * Run an embedding pass for all unembedded chunks (Postgres backend only).\n */\nexport async function runEmbed(): Promise<void> {\n  if (embedInProgress) {\n    process.stderr.write(\"[pai-daemon] Embed already in progress, skipping.\\n\");\n    return;\n  }\n\n  if (indexInProgress) {\n    process.stderr.write(\"[pai-daemon] Index in progress, deferring embed pass.\\n\");\n    return;\n  }\n\n  if (!storageBackend.supportsPostgresFeatures) {\n    return;\n  }\n\n  setEmbedInProgress(true);\n  const t0 = Date.now();\n\n  try {\n    process.stderr.write(\"[pai-daemon] Starting scheduled embed pass...\\n\");\n\n    const projectNames = new Map<number, string>();\n    try {\n      const rows = await registryBackend.listProjects({ status: \"active\" });\n      for (const r of rows) projectNames.set(r.id, r.slug);\n    } catch { /* registry unavailable — IDs will be used instead */ }\n\n    const { embedChunksWithBackend } = await import(\"../../memory/indexer-backend.js\");\n    // Explicit budgets so one embed phase can never outlast an index cycle.\n    // Backlogs drain across passes, not within one.\n    const count = await embedChunksWithBackend(\n      storageBackend,\n      () => shutdownRequested,\n      projectNames,\n      // Indexing settles to a few chunks per pass once caught up, so the daemon\n      // is idle most of the cycle and the embed budget is what actually sets\n      // drain rate. Measured 450 chunks per 90s against real (long) chunks, so\n      // a backlog only clears at roughly the budget's share of the cycle. What\n      // broke the starvation was bounding the pass at all, not this number —\n      // it can be raised freely as long as it stays finite.\n      { maxMillis: 240_000 },\n    );\n\n    // Vault chunks (project 999) are indexed through the same storageBackend\n    // as everything else (indexVault() writes via backend.insertChunks()), so\n    // the embedChunksWithBackend() call above already embeds them. A vault-only\n    // SQLite pass here would open a stray federation database even under Postgres.\n\n    const elapsed = Date.now() - t0;\n    setLastEmbedTime(Date.now());\n    process.stderr.write(\n      `[pai-daemon] Embed pass complete: ${count} chunks embedded (${elapsed}ms)\\n`\n    );\n  } catch (e) {\n    const msg = e instanceof Error ? e.message : String(e);\n    process.stderr.write(`[pai-daemon] Embed error: ${msg}\\n`);\n  } finally {\n    setEmbedInProgress(false);\n  }\n}\n\n/**\n * Start the periodic embed scheduler. Initial run is 60 seconds after startup.\n */\nexport function startEmbedScheduler(): void {\n  const intervalMs = daemonConfig.embedIntervalSecs * 1_000;\n\n  process.stderr.write(\n    `[pai-daemon] Embed scheduler: every ${daemonConfig.embedIntervalSecs}s\\n`\n  );\n\n  // Startup embed pass — OFF unless explicitly enabled.\n  //\n  // This used to run unconditionally 60s after every start, ignoring\n  // embedIntervalSecs entirely. With a large backlog that makes every restart a\n  // CPU storm: the interval was set to a day to stop the machine being bogged\n  // down, the daemon was restarted, and it immediately began a 240-second pass\n  // anyway. Throttling the recurring timer while leaving this armed means the\n  // throttle silently does nothing for anyone who restarts.\n  //\n  // Measured backlog when this was found: 1,868,098 unembedded chunks at ~5.7\n  // chunks/s — about 91 days of work that a restart would resume unbidden.\n  if (daemonConfig.embedOnStartup) {\n    setTimeout(() => {\n      runEmbed().catch((e) => {\n        process.stderr.write(`[pai-daemon] Startup embed error: ${e}\\n`);\n      });\n    }, 60_000);\n  } else {\n    process.stderr.write(\n      \"[pai-daemon] Startup embed pass skipped (embedOnStartup=false).\\n\"\n    );\n  }\n\n  // With a maintenance hour configured the index cycle already chains an embed\n  // pass onto every anchored run, so this timer would only add a second pass\n  // that counts from daemon start — observed as \"every 86400s\" armed at a\n  // 20:08 boot, i.e. a daily daytime pass on a machine configured for 03:00.\n  // The interval says how often, never when; without an anchor it cannot\n  // deliver a night-only pass, so it is not armed at all.\n  if (daemonConfig.maintenanceHour !== undefined) {\n    process.stderr.write(\n      \"[pai-daemon] Standalone embed timer not armed (maintenanceHour is set; \" +\n        \"embed runs at the end of each anchored index cycle).\\n\"\n    );\n    return;\n  }\n\n  const timer = setInterval(() => {\n    runEmbed().catch((e) => {\n      process.stderr.write(`[pai-daemon] Scheduled embed error: ${e}\\n`);\n    });\n  }, intervalMs);\n\n  if (timer.unref) timer.unref();\n  setEmbedSchedulerTimer(timer);\n}\n\n// ---------------------------------------------------------------------------\n// Registry scan scheduler\n// ---------------------------------------------------------------------------\n\nconst REGISTRY_SCAN_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes\nconst REGISTRY_SCAN_STARTUP_DELAY_MS = 10_000;     // 10 seconds after daemon start\n\n/**\n * Start the periodic registry scan scheduler.\n * First scan runs 10 seconds after startup (enough time for IPC to stabilise).\n * Subsequent scans run every 30 minutes.\n * Uses the work-queue so the scan runs on the worker thread without blocking IPC.\n */\nexport function startRegistryScanScheduler(): void {\n  process.stderr.write(\n    \"[pai-daemon] Registry scan scheduler: every 30min (first in 10s)\\n\"\n  );\n\n  setTimeout(() => {\n    import(\"../../daemon/work-queue-worker.js\")\n      .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n      .catch((e) => {\n        process.stderr.write(`[pai-daemon] Startup registry scan error: ${e}\\n`);\n      });\n  }, REGISTRY_SCAN_STARTUP_DELAY_MS);\n\n  const timer = setInterval(() => {\n    import(\"../../daemon/work-queue-worker.js\")\n      .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n      .catch((e) => {\n        process.stderr.write(`[pai-daemon] Scheduled registry scan error: ${e}\\n`);\n      });\n  }, REGISTRY_SCAN_INTERVAL_MS);\n\n  if (timer.unref) timer.unref();\n}\n\n// ---------------------------------------------------------------------------\n// Worker supervision scheduler\n// ---------------------------------------------------------------------------\n\n/** How often the daemon looks at the worker ledger for supervision events. */\nconst SUPERVISION_TICK_MS = 30_000;\n\n/** First supervision pass waits for the first statuses to exist at all. */\nconst SUPERVISION_STARTUP_DELAY_MS = 15_000;\n\n/**\n * Start the worker supervisor: one tick every 30 s over the workers ledger\n * (src/workers/supervision.ts) that pushes finished/failed/stalled events to\n * the owning session — the daemon-side replacement for orchestrator polling.\n * Daemon code only: no model call has any business being here, and none is\n * made. Disabled workers disable the supervisor with it.\n */\nexport function startWorkerSupervisor(): void {\n  let enabled = false;\n  try {\n    enabled = readWorkersSection().workers.enabled;\n  } catch (e) {\n    process.stderr.write(\n      `[pai-daemon] Worker supervisor: not started (${e instanceof Error ? e.message : String(e)})\\n`\n    );\n    return;\n  }\n  if (!enabled) {\n    process.stderr.write(\"[pai-daemon] Worker supervisor: disabled (workers are off)\\n\");\n    return;\n  }\n\n  const tick = () => {\n    runSupervisionTick(workersLogDir(readWorkersSection().workers), {\n      stallMs: stallMinutesFromEnv() * 60_000,\n      thrashFails: thrashFailsFromEnv(),\n    })\n      .then((r) => {\n        for (const ev of r.events) {\n          process.stderr.write(`[pai-daemon] Supervision: ${ev.text}\\n`);\n        }\n      })\n      .catch((e) => {\n        // a broken tick must never take the daemon down with it\n        process.stderr.write(\n          `[pai-daemon] Supervision tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n        );\n      });\n  };\n\n  const first = setTimeout(tick, SUPERVISION_STARTUP_DELAY_MS);\n  if (first.unref) first.unref();\n  const timer = setInterval(tick, SUPERVISION_TICK_MS);\n  if (timer.unref) timer.unref();\n  process.stderr.write(\n    `[pai-daemon] Worker supervisor: every ${Math.round(SUPERVISION_TICK_MS / 1000)}s\\n`\n  );\n}\n\n// ---------------------------------------------------------------------------\n// Worker prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** First beat waits a while after boot; the daemon start is itself a warm request. */\nconst KEEPALIVE_STARTUP_DELAY_MS = 60_000;\n\n/**\n * Start the cache keepalive: every workers.cacheKeepaliveSecs one trivial\n * single-turn worker (src/workers/keepalive.ts) re-arms the provider's\n * implicit prompt cache so real worker spawns start warm. Needs no storage\n * backend — it starts with the IPC server, not after the federation connect.\n * 0 (or workers off) keeps it off. A failed beat is logged and swallowed.\n */\nexport function startCacheKeepalive(opts: {\n  configPath?: string;\n  beat?: () => Promise<BeatMetrics | null>;\n} = {}): void {\n  let workers: ReturnType<typeof readWorkersSection>[\"workers\"];\n  try {\n    workers = readWorkersSection(opts.configPath).workers;\n  } catch (e) {\n    process.stderr.write(\n      `[pai-daemon] Cache keepalive: not started (${e instanceof Error ? e.message : String(e)})\\n`\n    );\n    return;\n  }\n  if (!workers.enabled) {\n    process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (workers are off)\\n\");\n    return;\n  }\n  const secs = keepaliveSecs(workers);\n  if (!secs) {\n    process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (cacheKeepaliveSecs is 0)\\n\");\n    return;\n  }\n\n  let notedNoPostgres = false;\n  const supportsObservations = () => {\n    if (storageBackend?.supportsPostgresFeatures) return true;\n    if (!notedNoPostgres) {\n      notedNoPostgres = true;\n      process.stderr.write(\n        \"[pai-daemon] Cache keepalive: observation rows need Postgres — ledger line only\\n\"\n      );\n    }\n    return false;\n  };\n\n  const beat = async (): Promise<void> => {\n    const m = await (opts.beat ?? runKeepaliveBeat)();\n    if (!m) return; // overlap guard: previous beat still running\n    if (!supportsObservations()) return; // ledger line already written by the beat itself\n    try {\n      await storageBackend.storeObservationWithProject(registryBackend, {\n        session_id: m.id,\n        type: \"change\",\n        title: `cache keepalive ${m.ok ? \"ok\" : \"failed\"} (cache_read=${m.cache_read_input_tokens}, api_ms=${m.duration_api_ms})`,\n        tool_name: \"cache-keepalive\",\n        narrative: `beat ${m.id}: input=${m.input_tokens} cache_read=${m.cache_read_input_tokens} cache_creation=${m.cache_creation_input_tokens} duration_api_ms=${m.duration_api_ms}`,\n        tool_input_summary: undefined,\n        files_read: [],\n        files_modified: [],\n        concepts: [],\n      });\n    } catch (e) {\n      // a failed observation must never fail the beat\n      process.stderr.write(\n        `[pai-daemon] Cache keepalive observation error: ${e instanceof Error ? e.message : String(e)}\\n`\n      );\n    }\n  };\n\n  const first = setTimeout(() => {\n    beat().catch((e) => {\n      process.stderr.write(\n        `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n      );\n    });\n  }, KEEPALIVE_STARTUP_DELAY_MS);\n  if (first.unref) first.unref();\n\n  const timer = setInterval(() => {\n    beat().catch((e) => {\n      process.stderr.write(\n        `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n      );\n    });\n  }, secs * 1000);\n  if (timer.unref) timer.unref();\n  setCacheKeepaliveTimer(timer);\n  process.stderr.write(`[pai-daemon] Cache keepalive: every ${secs}s\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Interactive-session prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** Tick cadence: coarse enough to be cheap, fine enough that a session\n *  crossing idleMinutes gets beaten within a minute of becoming eligible. */\nconst SESSION_KEEPALIVE_TICK_MS = 60_000;\n\n/**\n * Start the interactive-session cache keepalive: every 60s, beat every live\n * session that has gone idle past `sessions.cacheKeepalive.idleMinutes`\n * (src/daemon/session-keepalive.ts). Off unless\n * `sessions.cacheKeepalive.enabled` is true — no hot-reload, matching\n * startCacheKeepalive above: config changes need a daemon restart.\n */\nexport function startSessionKeepalive(): void {\n  const config = daemonConfig.sessions.cacheKeepalive;\n  if (!config.enabled) {\n    process.stderr.write(\"[pai-daemon] Session keepalive: disabled (sessions.cacheKeepalive.enabled is false)\\n\");\n    return;\n  }\n\n  const tick = () => {\n    runSessionKeepaliveTick(config).catch((e) => {\n      process.stderr.write(\n        `[pai-daemon] Session keepalive tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n      );\n    });\n  };\n\n  const timer = setInterval(tick, SESSION_KEEPALIVE_TICK_MS);\n  if (timer.unref) timer.unref();\n  process.stderr.write(\n    `[pai-daemon] Session keepalive: every ${SESSION_KEEPALIVE_TICK_MS / 1000}s ` +\n      `(idle >= ${config.idleMinutes}min, hours ${config.activeHours}, max ${config.maxBeats} beats/stretch)\\n`\n  );\n}\n","/**\n * Tool dispatcher — maps IPC method names to PAI tool functions.\n */\n\nimport {\n  toolMemorySearch,\n  toolMemoryGet,\n  toolProjectInfo,\n  toolProjectList,\n  toolSessionList,\n  toolRegistrySearch,\n  toolProjectDetect,\n  toolProjectHealth,\n  toolProjectTodo,\n  toolSessionRoute,\n  toolMemoryWakeup,\n  toolMemoryTaxonomy,\n  toolMemoryFeedback,\n  toolMemoryKgSearch,\n} from \"../../mcp/tools.js\";\nimport { detectTopicShift } from \"../../topics/detector.js\";\nimport { registryBackend, storageBackend, daemonConfig } from \"./state.js\";\n\n/**\n * Dispatch an IPC tool call to the appropriate tool function.\n * Returns the tool result or throws on unknown/failed methods.\n */\nexport async function dispatchTool(\n  method: string,\n  params: Record<string, unknown>\n): Promise<unknown> {\n  // Cast through unknown to satisfy TypeScript's strict overlap check on\n  // Record<string, unknown> → specific param types. Runtime validation is\n  // the responsibility of each tool function (they surface errors gracefully).\n  const p = params as unknown;\n\n  switch (method) {\n    case \"memory_search\":\n      return toolMemorySearch(registryBackend, storageBackend, p as Parameters<typeof toolMemorySearch>[2]);\n\n    case \"memory_get\":\n      return toolMemoryGet(registryBackend, p as Parameters<typeof toolMemoryGet>[1]);\n\n    case \"project_info\":\n      return toolProjectInfo(registryBackend, p as Parameters<typeof toolProjectInfo>[1]);\n\n    case \"project_list\":\n      return toolProjectList(registryBackend, p as Parameters<typeof toolProjectList>[1]);\n\n    case \"session_list\":\n      return toolSessionList(registryBackend, p as Parameters<typeof toolSessionList>[1]);\n\n    case \"registry_search\":\n      return toolRegistrySearch(registryBackend, p as Parameters<typeof toolRegistrySearch>[1]);\n\n    case \"project_detect\":\n      return toolProjectDetect(p as Parameters<typeof toolProjectDetect>[0]);\n\n    case \"project_health\":\n      return toolProjectHealth(registryBackend, p as Parameters<typeof toolProjectHealth>[1]);\n\n    case \"project_todo\":\n      return toolProjectTodo(registryBackend, p as Parameters<typeof toolProjectTodo>[1]);\n\n    case \"memory_wakeup\":\n      return toolMemoryWakeup(registryBackend, p as Parameters<typeof toolMemoryWakeup>[1]);\n\n    case \"memory_taxonomy\":\n      return toolMemoryTaxonomy(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTaxonomy>[2]);\n\n    case \"topic_check\":\n      return detectTopicShift(\n        registryBackend,\n        storageBackend,\n        p as Parameters<typeof detectTopicShift>[2]\n      );\n\n    case \"session_auto_route\":\n      return toolSessionRoute(\n        registryBackend,\n        storageBackend,\n        p as Parameters<typeof toolSessionRoute>[2]\n      );\n\n    case \"zettel_explore\":\n    case \"zettel_health\":\n    case \"zettel_surprise\":\n    case \"zettel_suggest\":\n    case \"zettel_converse\":\n    case \"zettel_themes\":\n    case \"zettel_god_notes\":\n    case \"zettel_communities\": {\n      const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import(\"../../mcp/tools.js\");\n\n      switch (method) {\n        case \"zettel_explore\": return toolZettelExplore(storageBackend, p as Parameters<typeof toolZettelExplore>[1]);\n        case \"zettel_health\": return toolZettelHealth(storageBackend, p as Parameters<typeof toolZettelHealth>[1]);\n        case \"zettel_surprise\": return toolZettelSurprise(storageBackend, p as Parameters<typeof toolZettelSurprise>[1]);\n        case \"zettel_suggest\": return toolZettelSuggest(storageBackend, p as Parameters<typeof toolZettelSuggest>[1]);\n        case \"zettel_converse\": return toolZettelConverse(storageBackend, p as Parameters<typeof toolZettelConverse>[1]);\n        case \"zettel_themes\": return toolZettelThemes(storageBackend, p as Parameters<typeof toolZettelThemes>[1]);\n        case \"zettel_god_notes\": return toolZettelGodNotes(storageBackend, p as Parameters<typeof toolZettelGodNotes>[1]);\n        case \"zettel_communities\": return toolZettelCommunities(storageBackend, p as Parameters<typeof toolZettelCommunities>[1]);\n      }\n      break;\n    }\n\n    case \"graph_clusters\": {\n      const { handleGraphClusters } = await import(\"../../graph/clusters.js\");\n      return handleGraphClusters(storageBackend, p as Parameters<typeof handleGraphClusters>[1]);\n    }\n\n    case \"graph_neighborhood\": {\n      const { handleGraphNeighborhood } = await import(\"../../graph/neighborhood.js\");\n      return handleGraphNeighborhood(storageBackend, p as Parameters<typeof handleGraphNeighborhood>[1]);\n    }\n\n    case \"graph_note_context\": {\n      const { handleGraphNoteContext } = await import(\"../../graph/note-context.js\");\n      return handleGraphNoteContext(storageBackend, p as Parameters<typeof handleGraphNoteContext>[1]);\n    }\n\n    case \"graph_trace\": {\n      const { handleGraphTrace } = await import(\"../../graph/trace.js\");\n      return handleGraphTrace(storageBackend, p as Parameters<typeof handleGraphTrace>[1]);\n    }\n\n    case \"graph_latent_ideas\": {\n      const { handleGraphLatentIdeas } = await import(\"../../graph/latent-ideas.js\");\n      return handleGraphLatentIdeas(storageBackend, p as Parameters<typeof handleGraphLatentIdeas>[1]);\n    }\n\n    case \"idea_materialize\": {\n      const { handleIdeaMaterialize } = await import(\"../../graph/latent-ideas.js\");\n      if (!daemonConfig.vaultPath) {\n        throw new Error(\"idea_materialize requires vaultPath to be configured in the daemon config\");\n      }\n      return handleIdeaMaterialize(\n        p as Parameters<typeof handleIdeaMaterialize>[0],\n        daemonConfig.vaultPath\n      );\n    }\n\n    case \"kg_add\":\n    case \"kg_query\":\n    case \"kg_invalidate\":\n    case \"kg_contradictions\": {\n      const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import(\"../../mcp/tools.js\");\n      if (!storageBackend.supportsPostgresFeatures) {\n        throw new Error(`${method} requires a Postgres storage backend`);\n      }\n      switch (method) {\n        case \"kg_add\":           return toolKgAdd(storageBackend, p as Parameters<typeof toolKgAdd>[1]);\n        case \"kg_query\":         return toolKgQuery(storageBackend, p as Parameters<typeof toolKgQuery>[1]);\n        case \"kg_invalidate\":    return toolKgInvalidate(storageBackend, p as Parameters<typeof toolKgInvalidate>[1]);\n        case \"kg_contradictions\": return toolKgContradictions(storageBackend, p as Parameters<typeof toolKgContradictions>[1]);\n      }\n      break;\n    }\n\n    case \"memory_tunnels\": {\n      const { toolMemoryTunnels } = await import(\"../../mcp/tools.js\");\n      return toolMemoryTunnels(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTunnels>[2]);\n    }\n\n    case \"memory_feedback\": {\n      // MR2: feedback weight loop — relevance_score/feedback_weight go through\n      // the StorageBackend, so this works on either backend.\n      return await toolMemoryFeedback(storageBackend, p as Parameters<typeof toolMemoryFeedback>[1]);\n    }\n\n    case \"memory_kg_search\": {\n      // MR1: graph-completion retrieval — active storage backend for KG triple expansion\n      if (!storageBackend.supportsPostgresFeatures) {\n        throw new Error(\"memory_kg_search requires a Postgres storage backend for KG triple expansion\");\n      }\n      return await toolMemoryKgSearch(storageBackend, p as Parameters<typeof toolMemoryKgSearch>[1]);\n    }\n\n    default:\n      throw new Error(`Unknown method: ${method}`);\n  }\n}\n","/**\n * IPC request handler — processes all inbound IPC methods and sends responses.\n */\n\nimport type { Socket } from \"node:net\";\nimport type { IpcRequest, IpcResponse } from \"./types.js\";\nimport type { NotificationMode } from \"../../notifications/types.js\";\nimport {\n  patchNotificationConfig,\n} from \"../../notifications/config.js\";\nimport { routeNotification } from \"../../notifications/router.js\";\nimport {\n  registryBackend,\n  storageBackend,\n  daemonConfig,\n  startTime,\n  indexInProgress,\n  lastIndexTime,\n  embedInProgress,\n  lastEmbedTime,\n  vaultIndexInProgress,\n  lastVaultIndexTime,\n  notificationConfig,\n  setNotificationConfig,\n} from \"./state.js\";\nimport { runIndex, runVaultIndex } from \"./scheduler.js\";\nimport { dispatchTool } from \"./dispatcher.js\";\nimport { enqueue, getStats as getQueueStats } from \"../../daemon/work-queue.js\";\nimport { notifyNewWork } from \"../../daemon/work-queue-worker.js\";\nimport { getBackendOutage } from \"../../storage/outage.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nexport function sendResponse(socket: Socket, response: IpcResponse): void {\n  try {\n    socket.write(JSON.stringify(response) + \"\\n\");\n  } catch {\n    // Socket may already be closed\n  }\n}\n\n// ---------------------------------------------------------------------------\n// Main handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single IPC request.\n */\nexport async function handleRequest(\n  request: IpcRequest,\n  socket: Socket\n): Promise<void> {\n  const { id, method, params } = request;\n\n  // Special: status\n  if (method === \"status\") {\n    const dbStats = await (async () => {\n      try {\n        if (!storageBackend) return null;\n        const fedStats = await storageBackend.getStats();\n        const projects = await registryBackend.countProjects();\n        return { files: fedStats.files, chunks: fedStats.chunks, projects };\n      } catch {\n        return null;\n      }\n    })();\n\n    sendResponse(socket, {\n      id,\n      ok: true,\n      result: {\n        uptime: Math.floor((Date.now() - startTime) / 1000),\n        indexInProgress,\n        lastIndexTime: lastIndexTime ? new Date(lastIndexTime).toISOString() : null,\n        indexIntervalSecs: daemonConfig.indexIntervalSecs,\n        embedInProgress,\n        lastEmbedTime: lastEmbedTime ? new Date(lastEmbedTime).toISOString() : null,\n        embedIntervalSecs: daemonConfig.embedIntervalSecs,\n        socketPath: daemonConfig.socketPath,\n        storageBackend: storageBackend?.backendType ?? \"initializing\",\n        db: dbStats,\n        vaultIndexInProgress,\n        lastVaultIndexTime: lastVaultIndexTime ? new Date(lastVaultIndexTime).toISOString() : null,\n        vaultPath: daemonConfig.vaultPath ?? null,\n        workQueue: getQueueStats(),\n        // Null when the backend is answering. Present, this is the reason\n        // everything else in this payload is stalled — and the reason status\n        // used to report \"idle\" for two days while nothing progressed.\n        backendOutage: getBackendOutage(),\n      },\n    });\n    socket.end();\n    return;\n  }\n\n  // Special: index_now — trigger immediate index (non-blocking response)\n  if (method === \"index_now\") {\n    runIndex().catch((e) => {\n      process.stderr.write(`[pai-daemon] index_now error: ${e}\\n`);\n    });\n    sendResponse(socket, { id, ok: true, result: { triggered: true } });\n    socket.end();\n    return;\n  }\n\n  // Special: vault_index_now — trigger immediate vault index (non-blocking response)\n  if (method === \"vault_index_now\") {\n    runVaultIndex().catch((e) => {\n      process.stderr.write(`[pai-daemon] vault_index_now error: ${e}\\n`);\n    });\n    sendResponse(socket, { id, ok: true, result: { triggered: true } });\n    socket.end();\n    return;\n  }\n\n  // Special: notification_get_config\n  if (method === \"notification_get_config\") {\n    sendResponse(socket, {\n      id,\n      ok: true,\n      result: {\n        config: notificationConfig,\n        activeChannels: Object.entries(notificationConfig.channels)\n          .filter(([ch, cfg]) => ch !== \"voice\" && (cfg as { enabled: boolean }).enabled)\n          .map(([ch]) => ch),\n      },\n    });\n    socket.end();\n    return;\n  }\n\n  // Special: notification_set_config\n  if (method === \"notification_set_config\") {\n    try {\n      const p = params as {\n        mode?: NotificationMode;\n        channels?: Record<string, unknown>;\n        routing?: Record<string, unknown>;\n      };\n      const updated = patchNotificationConfig({\n        mode: p.mode,\n        channels: p.channels as Parameters<typeof patchNotificationConfig>[0][\"channels\"],\n        routing: p.routing as Parameters<typeof patchNotificationConfig>[0][\"routing\"],\n      });\n      setNotificationConfig(updated);\n      sendResponse(socket, { id, ok: true, result: { config: updated } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // Special: notification_send\n  if (method === \"notification_send\") {\n    const p = params as {\n      event?: string;\n      message?: string;\n      title?: string;\n    };\n\n    if (!p.message) {\n      sendResponse(socket, { id, ok: false, error: \"notification_send: message is required\" });\n      socket.end();\n      return;\n    }\n\n    const event = (p.event as Parameters<typeof routeNotification>[0][\"event\"]) ?? \"info\";\n\n    routeNotification(\n      { event, message: p.message, title: p.title },\n      notificationConfig\n    ).then((result) => {\n      sendResponse(socket, { id, ok: true, result });\n      socket.end();\n    }).catch((e) => {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n      socket.end();\n    });\n    return;\n  }\n\n  // ---- Observation methods (Postgres only) --------------------------------\n\n  if (method === \"observation_store\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as {\n        session_id: string;\n        type: string;\n        title: string;\n        narrative?: string;\n        tool_name: string;\n        tool_input_summary?: string;\n        files_read?: string[];\n        files_modified?: string[];\n        concepts?: string[];\n        content_hash?: string;\n        cwd?: string;\n      };\n\n      const insertedId = await storageBackend.storeObservationWithProject(registryBackend, {\n        session_id: p.session_id,\n        type: p.type as \"decision\" | \"bugfix\" | \"feature\" | \"refactor\" | \"discovery\" | \"change\",\n        title: p.title,\n        narrative: p.narrative ?? null,\n        tool_name: p.tool_name,\n        tool_input_summary: p.tool_input_summary ?? undefined,\n        files_read: p.files_read ?? [],\n        files_modified: p.files_modified ?? [],\n        concepts: p.concepts ?? [],\n        cwd: p.cwd,\n      });\n\n      sendResponse(socket, { id, ok: true, result: { ok: true, id: insertedId } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // ---- Skill telemetry (Postgres only) ------------------------------------\n\n  if (method === \"skill_telemetry_record\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as {\n        skill_name: string;\n        source?: string;\n        scope?: string;\n        cwd?: string;\n      };\n\n      let project_slug: string | null = null;\n      if (p.cwd) {\n        const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n        if (row) project_slug = row.slug;\n      }\n\n      await storageBackend.recordSkillInvocation({\n        skill_name: p.skill_name,\n        source: p.source,\n        scope: p.scope,\n        project_slug,\n      });\n\n      sendResponse(socket, { id, ok: true, result: { ok: true } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  if (method === \"skill_telemetry_query\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as { scope?: string; status?: string; limit?: number };\n      const rows = await storageBackend.querySkillTelemetry({\n        scope: p.scope,\n        status: p.status,\n        limit: p.limit,\n      });\n      sendResponse(socket, { id, ok: true, result: rows });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  if (method === \"observation_query\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as {\n        project_id?: number;\n        session_id?: string;\n        type?: string;\n        limit?: number;\n        offset?: number;\n      };\n\n      const rows = await storageBackend.queryObservations({\n        projectId: p.project_id,\n        sessionId: p.session_id,\n        type: p.type,\n        limit: p.limit,\n        offset: p.offset,\n      });\n      sendResponse(socket, { id, ok: true, result: rows });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  if (method === \"observation_recent\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as { project_id?: number; cwd?: string; limit?: number };\n      const limit = p.limit ?? 20;\n\n      let resolvedProjectId = p.project_id;\n      let resolvedProjectSlug: string | undefined;\n      if (resolvedProjectId === undefined && p.cwd) {\n        const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n        if (row) {\n          resolvedProjectId = row.id;\n          resolvedProjectSlug = row.slug;\n        }\n      }\n\n      let rows;\n      if (resolvedProjectId !== undefined) {\n        rows = await storageBackend.queryRecentObservations(resolvedProjectId, limit);\n      } else {\n        rows = await storageBackend.queryObservations({ limit });\n      }\n      sendResponse(socket, { id, ok: true, result: { rows, project_slug: resolvedProjectSlug } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // observation_list — alias for observation_query with project slug resolution\n  if (method === \"observation_list\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as {\n        project_slug?: string;\n        session_id?: string;\n        type?: string;\n        limit?: number;\n        offset?: number;\n      };\n\n      let projectId: number | undefined;\n      if (p.project_slug) {\n        const row = await registryBackend.getProjectBySlug(p.project_slug);\n        projectId = row?.id;\n      }\n\n      const rows = await storageBackend.queryObservations({\n        projectId,\n        sessionId: p.session_id,\n        type: p.type,\n        limit: p.limit ?? 20,\n        offset: p.offset ?? 0,\n      });\n      sendResponse(socket, { id, ok: true, result: rows });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // observation_stats — aggregate statistics\n  if (method === \"observation_stats\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const stats = await storageBackend.getObservationStats();\n      sendResponse(socket, { id, ok: true, result: stats });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  if (method === \"session_summary_store\") {\n    if (!storageBackend?.supportsPostgresFeatures) {\n      sendResponse(socket, { id, ok: false, error: \"Session summaries require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      const p = params as {\n        session_id: string;\n        project_id?: number;\n        project_slug?: string;\n        cwd?: string;\n        request?: string;\n        investigated?: string;\n        learned?: string;\n        completed?: string;\n        next_steps?: string;\n        observation_count?: number;\n      };\n\n      let resolvedProjectId = p.project_id ?? null;\n      let resolvedProjectSlug = p.project_slug ?? null;\n      if (resolvedProjectId === null && p.cwd) {\n        const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n        if (row) {\n          resolvedProjectId = row.id;\n          resolvedProjectSlug = row.slug;\n        }\n      }\n\n      await storageBackend.storeSessionSummary({\n        session_id: p.session_id,\n        project_id: resolvedProjectId,\n        project_slug: resolvedProjectSlug,\n        request: p.request ?? null,\n        investigated: p.investigated ?? null,\n        learned: p.learned ?? null,\n        completed: p.completed ?? null,\n        next_steps: p.next_steps ?? null,\n        observation_count: p.observation_count ?? 0,\n      });\n\n      sendResponse(socket, { id, ok: true, result: { ok: true } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // work_queue_enqueue — hooks push work items here instead of doing heavy work inline\n  if (method === \"work_queue_enqueue\") {\n    try {\n      const p = params as {\n        type?: string;\n        priority?: number;\n        payload?: Record<string, unknown>;\n        maxAttempts?: number;\n      };\n\n      if (!p.type) {\n        sendResponse(socket, { id, ok: false, error: \"work_queue_enqueue: type is required\" });\n        socket.end();\n        return;\n      }\n\n      const item = enqueue({\n        type: p.type as import(\"../../daemon/work-queue.js\").WorkItemType,\n        priority: p.priority,\n        payload: p.payload ?? {},\n        maxAttempts: p.maxAttempts,\n      });\n\n      notifyNewWork();\n\n      sendResponse(socket, { id, ok: true, result: { id: item.id, status: item.status } });\n    } catch (e) {\n      const msg = e instanceof Error ? e.message : String(e);\n      sendResponse(socket, { id, ok: false, error: msg });\n    }\n    socket.end();\n    return;\n  }\n\n  // work_queue_stats — return current queue statistics\n  if (method === \"work_queue_stats\") {\n    sendResponse(socket, { id, ok: true, result: getQueueStats() });\n    socket.end();\n    return;\n  }\n\n  // All other methods: PAI tool dispatch\n  try {\n    const result = await dispatchTool(method, params);\n    sendResponse(socket, { id, ok: true, result });\n  } catch (e) {\n    const msg = e instanceof Error ? e.message : String(e);\n    sendResponse(socket, { id, ok: false, error: msg });\n  }\n  socket.end();\n}\n","/**\n * One-time repair for chunks written while the `pai_chunks_fts_update`\n * trigger used the 'english' text-search config instead of 'simple'.\n *\n * The trigger stemmed and stripped stop-words from every stored fts_vector,\n * but every query (searchKeyword, findTunnels) and the direct-insert path\n * (insertChunks) always used 'simple' — so a BEFORE trigger silently\n * overwrote each insert with a vector that keyword search could never match.\n * Measured on a real database: 0 rows for `fts_vector @@ to_tsquery('simple',\n * 'running')` against 142,559 chunks containing \"running\".\n *\n * Completion is marked by a comment on pai_chunks.fts_vector so this can run\n * on every daemon boot for free once migrated, and resume cleanly if a batch\n * run was interrupted (each batch is its own transaction, keyed on the PK).\n */\n\nimport type { StorageBackend } from \"../interface.js\";\nimport type { PostgresBackend } from \"../postgres.js\";\n\nconst DONE_MARKER = \"fts:simple\";\nconst BATCH_SIZE = 5000;\n\nexport async function migrateFtsConfig(backend: StorageBackend): Promise<void> {\n  // Obtained inside the async function body, not by the caller: a throw here\n  // (e.g. a bad cast) becomes a rejection of this function's own promise,\n  // caught by the caller's `.catch`, instead of a synchronous throw inside\n  // the daemon's storage-backend `.then()` that would abort loadQueue()/\n  // startWorker() and hit the outer fatal-exit `.catch`.\n  const pool = (backend as unknown as PostgresBackend).getPool();\n  const tableCheck = await pool.query(\n    `SELECT 1 FROM information_schema.tables WHERE table_name = 'pai_chunks'`\n  );\n  if (tableCheck.rowCount === 0) return;\n\n  const commentCheck = await pool.query<{ comment: string | null }>(\n    `SELECT col_description('pai_chunks'::regclass, ordinal_position) AS comment\n     FROM information_schema.columns\n     WHERE table_name = 'pai_chunks' AND column_name = 'fts_vector'`\n  );\n  if (commentCheck.rows[0]?.comment === DONE_MARKER) return;\n\n  process.stderr.write(\n    \"[pai-postgres] FTS config migration: starting (english -> simple)\\n\"\n  );\n\n  const fnCheck = await pool.query(\n    `SELECT 1 FROM pg_proc WHERE proname = 'pai_chunks_fts_update'`\n  );\n  if (fnCheck.rowCount !== 0) {\n    await pool.query(`\n      CREATE OR REPLACE FUNCTION pai_chunks_fts_update()\n      RETURNS TRIGGER AS $$\n      BEGIN\n        NEW.fts_vector := to_tsvector('simple', COALESCE(NEW.text, ''));\n        RETURN NEW;\n      END;\n      $$ LANGUAGE plpgsql;\n    `);\n    process.stderr.write(\n      \"[pai-postgres] FTS config migration: trigger function now uses 'simple'\\n\"\n    );\n  }\n\n  let lastId: string | null = null;\n  let totalUpdated = 0;\n  let batchN = 0;\n  while (true) {\n    const sql = lastId === null\n      ? `SELECT id FROM pai_chunks ORDER BY id LIMIT $1`\n      : `SELECT id FROM pai_chunks WHERE id > $2 ORDER BY id LIMIT $1`;\n    const params: Array<number | string> = lastId === null ? [BATCH_SIZE] : [BATCH_SIZE, lastId];\n    const idRows = await pool.query<{ id: string }>(sql, params);\n    if (idRows.rows.length === 0) break;\n\n    const ids: string[] = idRows.rows.map((r) => r.id);\n    await pool.query(\n      `UPDATE pai_chunks SET fts_vector = to_tsvector('simple', COALESCE(text, ''))\n       WHERE id = ANY($1::text[])`,\n      [ids]\n    );\n    totalUpdated += ids.length;\n    lastId = ids[ids.length - 1];\n    batchN++;\n    if (batchN % 20 === 0) {\n      process.stderr.write(\n        `[pai-postgres] FTS config migration: ${totalUpdated} chunks rebuilt so far...\\n`\n      );\n    }\n    if (idRows.rows.length < BATCH_SIZE) break;\n  }\n\n  await pool.query(\n    `COMMENT ON COLUMN pai_chunks.fts_vector IS '${DONE_MARKER}'`\n  );\n\n  process.stderr.write(\n    `[pai-postgres] FTS config migration: done, ${totalUpdated} chunks rebuilt\\n`\n  );\n}\n","/**\n * IPC server and daemon entry point.\n * Owns: isSocketLive, startIpcServer, serve (exported).\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { createServer, connect, Socket, Server } from \"node:net\";\nimport { setPriority } from \"node:os\";\nimport { createStorageBackend, createRegistryBackend } from \"../../storage/factory.js\";\nimport { configureEmbeddingModel } from \"../../memory/embeddings.js\";\nimport { loadNotificationConfig } from \"../../notifications/config.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\nimport type { IpcRequest } from \"./types.js\";\nimport {\n  setRegistryBackend,\n  setStorageBackend,\n  setDaemonConfig,\n  setStartTime,\n  setNotificationConfig,\n  setShutdownRequested,\n  indexInProgress,\n  embedInProgress,\n  indexSchedulerTimer,\n  embedSchedulerTimer,\n  cacheKeepaliveTimer,\n  storageBackend,\n} from \"./state.js\";\nimport { startIndexScheduler, startEmbedScheduler, startRegistryScanScheduler, startWorkerSupervisor, startCacheKeepalive, startSessionKeepalive } from \"./scheduler.js\";\nimport { handleRequest, sendResponse } from \"./handler.js\";\nimport { loadQueue } from \"../../daemon/work-queue.js\";\nimport { startWorker, stopWorker } from \"../../daemon/work-queue-worker.js\";\nimport { migrateFtsConfig } from \"../../storage/postgres/fts-migration.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an existing socket file is actually being served by a live process.\n */\nfunction isSocketLive(path: string): Promise<boolean> {\n  return new Promise((resolve) => {\n    const client = connect(path);\n    const timer = setTimeout(() => { client.destroy(); resolve(false); }, 500);\n    client.on(\"connect\", () => { clearTimeout(timer); client.end(); resolve(true); });\n    client.on(\"error\", () => { clearTimeout(timer); resolve(false); });\n  });\n}\n\n/**\n * Start the Unix Domain Socket IPC server.\n */\nasync function startIpcServer(socketPath: string): Promise<Server> {\n  if (existsSync(socketPath)) {\n    const live = await isSocketLive(socketPath);\n    if (live) {\n      throw new Error(\"Another daemon is already running — socket is live. Aborting startup.\");\n    }\n    try {\n      unlinkSync(socketPath);\n      process.stderr.write(\"[pai-daemon] Removed stale socket file.\\n\");\n    } catch {\n      // If we can't remove it, bind will fail with a clear error\n    }\n  }\n\n  const server = createServer((socket: Socket) => {\n    let buffer = \"\";\n\n    socket.on(\"data\", (chunk: Buffer) => {\n      buffer += chunk.toString();\n      let nl: number;\n      while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n        const line = buffer.slice(0, nl);\n        buffer = buffer.slice(nl + 1);\n\n        if (line.trim() === \"\") continue;\n\n        let request: IpcRequest;\n        try {\n          request = JSON.parse(line) as IpcRequest;\n        } catch {\n          sendResponse(socket, { id: \"?\", ok: false, error: \"Invalid JSON\" });\n          socket.destroy();\n          return;\n        }\n\n        handleRequest(request, socket).catch((e: unknown) => {\n          const msg = e instanceof Error ? e.message : String(e);\n          sendResponse(socket, { id: request.id, ok: false, error: msg });\n          socket.destroy();\n        });\n      }\n    });\n\n    socket.on(\"error\", () => {\n      // Client disconnected — nothing to do\n    });\n  });\n\n  server.on(\"error\", (e) => {\n    process.stderr.write(`[pai-daemon] IPC server error: ${e}\\n`);\n  });\n\n  server.listen(socketPath, () => {\n    process.stderr.write(\n      `[pai-daemon] IPC server listening on ${socketPath}\\n`\n    );\n  });\n\n  return server;\n}\n\n// ---------------------------------------------------------------------------\n// Main daemon entry point\n// ---------------------------------------------------------------------------\n\nexport async function serve(config: PaiDaemonConfig): Promise<void> {\n  setDaemonConfig(config);\n  setStartTime(Date.now());\n\n  setNotificationConfig(loadNotificationConfig());\n\n  process.stderr.write(\"[pai-daemon] Starting daemon...\\n\");\n  process.stderr.write(`[pai-daemon] Socket: ${config.socketPath}\\n`);\n  process.stderr.write(`[pai-daemon] Storage backend: ${config.storageBackend}\\n`);\n  const { notificationConfig } = await import(\"./state.js\");\n  process.stderr.write(\n    `[pai-daemon] Notification mode: ${notificationConfig.mode}\\n`\n  );\n\n  // Lower scheduling priority so the daemon yields CPU to interactive sessions\n  try { setPriority(process.pid, 10); } catch { /* non-fatal */ }\n\n  configureEmbeddingModel(config.embeddingModel);\n\n  // Connect the registry backend in the background — non-blocking, since it\n  // may take a few retries under Postgres (waitForPostgres: true never\n  // silently falls back to SQLite, matching the federation backend below).\n  // Callers that need it before it resolves (status, observation IPC methods)\n  // already tolerate an unready backend via their own try/catch.\n  void createRegistryBackend(config, { waitForPostgres: true })\n    .then((backend) => {\n      setRegistryBackend(backend);\n      process.stderr.write(`[pai-daemon] Registry backend: ${backend.backendType}\\n`);\n    })\n    .catch((e) => {\n      const msg = e instanceof Error ? e.message : String(e);\n      process.stderr.write(`[pai-daemon] Fatal: Could not open registry: ${msg}\\n`);\n      process.exit(1);\n    });\n\n  // Start the IPC server immediately so `pai daemon status` answers even while\n  // we are still waiting for the federation backend (e.g. Postgres coming up\n  // after a reboot). The status handler tolerates a not-yet-ready backend.\n  const server = await startIpcServer(config.socketPath);\n\n  // Registry scan scheduler only needs the registry DB — safe to start now.\n  startRegistryScanScheduler();\n\n  // Worker supervision reads the workers ledger off disk and pushes events to\n  // owning sessions — no backend dependency, so it starts with the IPC server.\n  startWorkerSupervisor();\n\n  // Cache keepalive spawns trivial workers on the workers config — also no\n  // backend dependency (observations store opportunistically when Postgres\n  // is up), so it starts with the IPC server too.\n  startCacheKeepalive();\n\n  // Interactive-session cache keepalive: also no backend dependency (reads\n  // AIBroker + transcript files, writes the workers ledger + its own state\n  // file), so it starts with the IPC server too.\n  startSessionKeepalive();\n\n  // Connect the federation backend in the background. When storageBackend is\n  // \"postgres\" this retries forever (never silently falls back to SQLite), so\n  // the boot race with Docker/Postgres resolves itself once Postgres is up.\n  // Only then do we start the federation-dependent schedulers and work queue —\n  // this also keeps the first index run from firing against an unready backend.\n  void createStorageBackend(config, { waitForPostgres: true })\n    .then((backend) => {\n      setStorageBackend(backend);\n      process.stderr.write(\n        `[pai-daemon] Federation backend: ${backend.backendType}\\n`\n      );\n\n      startIndexScheduler();\n\n      if (backend.supportsPostgresFeatures) {\n        startEmbedScheduler();\n\n        // Repair fts_vector rows written under the old ('english') trigger.\n        // Async, non-blocking: real tables hold millions of rows, and a\n        // rebuild must never delay daemon startup. Idempotent — safe to\n        // resume from scratch if interrupted (see fts-migration.ts).\n        void migrateFtsConfig(backend).catch((e) => {\n          const msg = e instanceof Error ? e.message : String(e);\n          process.stderr.write(`[pai-daemon] FTS config migration failed: ${msg}\\n`);\n        });\n      } else {\n        process.stderr.write(\n          \"[pai-daemon] Embed scheduler: disabled (SQLite backend)\\n\"\n        );\n      }\n\n      // Work queue — load persisted items and start worker loop\n      loadQueue();\n      startWorker();\n    })\n    .catch((e) => {\n      const msg = e instanceof Error ? e.message : String(e);\n      process.stderr.write(\n        `[pai-daemon] Fatal: Could not open federation storage: ${msg}\\n`\n      );\n      process.exit(1);\n    });\n\n  const shutdown = async (signal: string): Promise<void> => {\n    process.stderr.write(`\\n[pai-daemon] ${signal} received. Stopping.\\n`);\n\n    setShutdownRequested(true);\n\n    if (indexSchedulerTimer) clearInterval(indexSchedulerTimer);\n    if (embedSchedulerTimer) clearInterval(embedSchedulerTimer);\n    if (cacheKeepaliveTimer) clearInterval(cacheKeepaliveTimer);\n\n    stopWorker();\n\n    server.close();\n\n    const SHUTDOWN_TIMEOUT_MS = 10_000;\n    const POLL_INTERVAL_MS = 100;\n    const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS;\n\n    if (indexInProgress || embedInProgress) {\n      process.stderr.write(\n        `[pai-daemon] Waiting for in-progress operations to finish ` +\n          `(index=${indexInProgress}, embed=${embedInProgress})...\\n`\n      );\n\n      while ((indexInProgress || embedInProgress) && Date.now() < deadline) {\n        await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n      }\n\n      if (indexInProgress || embedInProgress) {\n        process.stderr.write(\"[pai-daemon] Shutdown timeout reached — forcing exit.\\n\");\n      } else {\n        process.stderr.write(\"[pai-daemon] In-progress operations finished.\\n\");\n      }\n    }\n\n    try {\n      await storageBackend.close();\n    } catch {\n      // ignore\n    }\n\n    try {\n      unlinkSync(config.socketPath);\n    } catch {\n      // ignore\n    }\n\n    process.exit(0);\n  };\n\n  process.on(\"SIGINT\", () => { shutdown(\"SIGINT\").catch(() => process.exit(0)); });\n  process.on(\"SIGTERM\", () => { shutdown(\"SIGTERM\").catch(() => process.exit(0)); });\n\n  // Keep process alive\n  await new Promise(() => {});\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,SAAS,UACP,QACA,QACG;CACH,MAAM,SAAS,EAAE,GAAG,QAAQ;AAC5B,MAAK,MAAM,OAAO,OAAO,KAAK,OAAO,EAAE;EACrC,MAAM,SAAS,OAAO;AACtB,MAAI,WAAW,UAAa,WAAW,KAAM;EAC7C,MAAM,SAAU,OAAmC;AACnD,MACE,OAAO,WAAW,YAClB,CAAC,MAAM,QAAQ,OAAO,IACtB,OAAO,WAAW,YAClB,WAAW,QACX,CAAC,MAAM,QAAQ,OAAO,CAEtB,CAAC,OAAmC,OAAO,UACzC,QACA,OACD;MAED,CAAC,OAAmC,OAAO;;AAG/C,QAAO;;;;;;AAWT,SAAgB,yBAA6C;CAC3D,IAAI;AACJ,KAAI;AACF,WAAS,mBAAmB;SACtB;AACN,SAAO,EAAE,GAAG,6BAA6B;;CAG3C,MAAM,SAAS,OAAO;AACtB,KAAI,CAAC,UAAU,OAAO,WAAW,SAC/B,QAAO,EAAE,GAAG,6BAA6B;AAG3C,QAAO,UACL,6BACA,OACD;;;;;;AAWH,SAAgB,uBAAuB,QAAkC;CACvE,IAAI,OAAgC,EAAE;AACtC,KAAI;AACF,SAAO,mBAAmB;SACpB;AAKR,MAAK,mBAAmB;AAExB,oBAAmB,KAAK;;;;;;AAW1B,SAAgB,wBAAwB,OAIjB;CACrB,MAAM,UAAU,wBAAwB;AAExC,KAAI,MAAM,SAAS,OACjB,SAAQ,OAAO,MAAM;AAGvB,KAAI,MAAM,SACR,SAAQ,WAAW,UACjB,QAAQ,UACR,MAAM,SACP;AAGH,KAAI,MAAM,QACR,SAAQ,UAAU,UAChB,QAAQ,SACR,MAAM,QACP;AAGH,wBAAuB,QAAQ;AAC/B,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3GT,MAAa,mBAAmB;;AAGhC,MAAa,kBAAkB;;AAG/B,MAAa,kBAAkB;;AAG/B,SAAgB,cAAc,SAAmE;AAC/F,QAAO,QAAQ,UAAU,QAAQ,qBAAqB;;;;;;;AAcxD,SAAgB,qBAAqB,MAAkC;CACrE,IAAI;AACJ,KAAI;AACF,SAAO,aAAa,MAAM,OAAO;SAC3B;AACN,SAAO;;CAET,IAAI,QAA4B;AAChC,MAAK,MAAM,QAAQ,KAAK,MAAM,KAAK,EAAE;AACnC,MAAI,CAAC,KAAK,WAAW,IAAI,CAAE;EAC3B,IAAI;AACJ,MAAI;AACF,OAAI,KAAK,MAAM,KAAK;UACd;AACN;;AAEF,MAAI,EAAE,SAAS,SAAU,SAAQ;;AAEnC,QAAO;;;;;;;AAwBT,SAAgB,mBACd,GACA,IACa;CACb,MAAM,IAAI,IAAI;AACd,QAAO;EACL,GAAG;EACH,cAAc,GAAG,gBAAgB;EACjC,eAAe,GAAG,iBAAiB;EACnC,yBAAyB,GAAG,2BAA2B;EACvD,6BAA6B,GAAG,+BAA+B;EAC/D,iBAAiB,IAAI,mBAAmB;EACxC,aAAa,IAAI,eAAe;EAChC,WAAW,IAAI,aAAa;EAC7B;;;AAeH,IAAI,eAAe;;AAOnB,SAAgB,OAAO,oBAAU,IAAI,MAAM,EAAU;CACnD,MAAM,KAAK,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACnD,QACE,aAAa,EAAE,aAAa,GAAG,EAAE,EAAE,UAAU,GAAG,EAAE,GAAG,EAAE,EAAE,SAAS,CAAC,IAC/D,EAAE,EAAE,UAAU,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;;;;;;;;;;;;;AAe/D,eAAsB,iBAAiB,OAAoB,EAAE,EAA+B;AAC1F,KAAI,aAAc,QAAO;AACzB,gBAAe;AACf,KAAI;EACF,MAAM,EAAE,SAAS,WAAW,mBAAmB,KAAK,WAAW;EAC/D,MAAM,SAAS,cAAc,OAAO;EACpC,MAAM,KAAK,KAAK,MAAM,QAAQ;EAC9B,MAAM,KAAK,MAAM,UAAU;GACzB,YAAY,CAAC,MAAM,iBAAiB;GACpC,WAAW;GACX,OAAO;GACP,QAAQ;GACR,OAAO;GACP,cAAc;GACd;GACA,KAAK;GACN,CAAC;EACF,MAAM,SAAS,WAAW,QAAQ,GAAG;EACrC,MAAM,UAAU,mBACd;GACE;GACA,IAAI,OAAO;GACX;GACA,UAAU,QAAQ,YAAY;GAC9B,OAAO,QAAQ,SAAS;GACzB,EACD,qBAAqB,WAAW,QAAQ,GAAG,CAAC,CAC7C;AACD,eAAa,WAAW,OAAO,EAAE,oBAAoB;GACnD;GACA,UAAU,QAAQ;GAClB,OAAO,QAAQ;GACf,cAAc,QAAQ;GACtB,yBAAyB,QAAQ;GACjC,6BAA6B,QAAQ;GACrC,iBAAiB,QAAQ;GAC1B,CAAC;AACF,SAAO;WACC;AACR,iBAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7HnB,MAAM,gBAA4C;CAAC;CAAW;CAAa;CAAW;;;;;;AA6BtF,SAAgB,aAAa,GAAgC;AAC3D,KAAI,EAAE,eAAgB,QAAO,EAAE;AAC/B,KAAI,EAAE,SAAS,GAAI,QAAO,EAAE,QAAQ;AACpC,QAAO;;AAGT,MAAM,WAA6C;CAAC;CAAQ;CAAU;CAAU;CAAO;AAEvF,SAAS,WAAW,GAA0B;AAC5C,QAAO,SAAS,SAAS,EAAE,MAAM;;;AAInC,SAAS,MAAM,IAAY,KAAmB;CAC5C,MAAM,IAAI,KAAK,MAAM,GAAG,QAAQ,KAAK,IAAI,CAAC;AAC1C,KAAI,OAAO,MAAM,EAAE,CAAE,QAAO;AAC5B,QAAO,KAAK,IAAI,GAAG,IAAI,SAAS,GAAG,EAAE;;;;;;;AAQvC,MAAa,wBAAwB;AAErC,SAAgB,oBAAoB,MAAyB,QAAQ,KAAa;CAChF,MAAM,MAAM,IAAI;AAChB,KAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;CAC5C,MAAM,IAAI,OAAO,IAAI;AACrB,QAAO,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,IAAI;;;;;;AAO3C,MAAa,uBAAuB;AAEpC,SAAgB,mBAAmB,MAAyB,QAAQ,KAAa;CAC/E,MAAM,MAAM,IAAI;AAChB,KAAI,QAAQ,UAAa,QAAQ,GAAI,QAAO;CAC5C,MAAM,IAAI,OAAO,IAAI;AACrB,QAAO,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,IAAI;;;;;;;;;AAU3C,SAAgB,QAAQ,QAAgB,MAAuB,QAAQ,GAAW;AAChF,QAAO,cAAc,SAAS,KAAK,GAAG,GAAG,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,GAAG;;;;;;;;AASpF,SAAS,cAAc,KAAuB;AAC5C,QAAO,aAAa,SAAS,CAAC,MAAM,OAAO,IAAI,CAAC,EAAE,EAAE,UAAU,QAAQ,CAAC,CACpE,MAAM,KAAK,CACX,SAAS,MAAM;EACd,MAAM,IAAI,OAAO,EAAE,MAAM,CAAC;AAC1B,SAAO,OAAO,UAAU,EAAE,IAAI,IAAI,IAAI,CAAC,EAAE,GAAG,EAAE;GAC9C;;;AAyBN,SAAS,UAAU,MAAuB,GAAiB,IAAmB,YAAmC;CAC/G,MAAM,OAAO,2BAA2B,EAAE;AAC1C,KAAI,SAAS,WAAY,QAAO,UAAU,EAAE,GAAG,GAAG,EAAE,MAAM,iBAAiB;AAC3E,KAAI,SAAS,UAAW,QAAO,UAAU,EAAE,GAAG,GAAG,EAAE,MAAM,WAAW,WAAW,aAAa;AAC5F,KAAI,SAAS,YACX,QAAO,UAAU,EAAE,GAAG,GAAG,EAAE,MAAM,cAAc,EAAE,YAAY,+BAA+B;AAC9F,KAAI,SAAS,WAAY,QAAO,UAAU,EAAE,GAAG,GAAG,EAAE,MAAM,YAAY,EAAE,SAAS,mBAAmB;CACpG,MAAM,MAAM,OAAO,OAAO,gBAAgB,MAAM;AAChD,QAAO,UAAU,EAAE,GAAG,GAAG,EAAE,MAAM,UAAU,IAAI,GAAG;;;;;;;AAQpD,SAAgB,wBACd,UACA,MACoB;CACpB,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,MAA0B,EAAE;AAClC,MAAK,MAAM,KAAK,UAAU;EACxB,MAAM,UAAU,aAAa,EAAE;AAC/B,MAAI,CAAC,QAAS;AAGd,MAAI,WAAW,EAAE,CAAE;EACnB,IAAI,OAA+B;EACnC,IAAI,KAAoB;EACxB,IAAI,aAA4B;AAChC,MAAI,WAAW,EAAE,CAGf,KAAI,EAAE,UAAU,WAAW,EAAE,MAAM,OAAO,GAAG;AAC3C,UAAO;AACP,QAAK;SACA;AACL,UAAO;AACP,QAAK,EAAE,UAAU,SAAS,OAAO,EAAE;;WAE5B,CAAC,QAAQ,EAAE,IAAI,IAAI,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,aAAa;AAGtE,UAAO;AACP,QAAK;aACI,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,KAAK,SAAS;AACpD,UAAO;AACP,gBAAa,KAAK,MAAM,MAAM,EAAE,SAAS,KAAK,IAAI,GAAG,IAAO;cAClD,EAAE,eAAe,OAAO,KAAK,eAAe,sBACtD,QAAO;WACE,EAAE,YAAY,MAAM;GAM7B,IAAI,cAAc;AAClB,OAAI,QAAQ,EAAE,IAAI,CAChB,KAAI;AACF,mBAAe,KAAK,aAAa,eAAe,EAAE,IAAI,CAAC,SAAS;WAC1D;AACN,kBAAc;;AAGlB,OAAI,CAAC,YAAa,QAAO;;AAE3B,MAAI,CAAC,KAAM;AACX,MAAI,KAAK;GACP,IAAI,QAAQ,EAAE,IAAI,MAAM,EAAE,MAAM;GAChC,IAAI,SAAS,KAAK,IAAI;GACtB;GACA,QAAQ,EAAE;GACV,OAAO,EAAE;GACT;GACA;GACA;GACA,GAAI,SAAS,cAAc,EAAE,aAAa,EAAE,eAAe,GAAG,GAAG,EAAE;GACnE,GAAI,SAAS,aAAa,EAAE,UAAU,EAAE,YAAY,GAAG,GAAG,EAAE;GAC5D,MAAM,UAAU,MAAM,GAAG,IAAI,WAAW;GACzC,CAAC;;AAEJ,QAAO;;;AAaT,MAAM,yBAAyB;AAE/B,SAAgB,mBAAmB,QAAwB;AACzD,QAAO,KAAK,QAAQ,cAAc;;AAGpC,SAAgB,qBAAqB,QAAwB;AAC3D,QAAO,KAAK,mBAAmB,OAAO,EAAE,aAAa;;AAGvD,SAAgB,sBAAsB,QAAgB,SAAyB;AAC7E,QAAO,KAAK,mBAAmB,OAAO,EAAE,GAAG,QAAQ,SAAS;;;AAI9D,SAAgB,sBAAsB,QAAgB,SAAyB;AAC7E,QAAO,KAAK,mBAAmB,OAAO,EAAE,GAAG,QAAQ,SAAS;;AAG9D,SAAgB,qBAAqB,MAA2D;AAC9F,KAAI,CAAC,WAAW,KAAK,CAAE,QAAO;EAAE,OAAO,EAAE,WAAW,EAAE,EAAE;EAAE,OAAO;EAAM;AACvE,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,aAAa,MAAM,OAAO,CAAC;AACrD,MAAI,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,cAAc,SACtE,QAAO;GAAE,OAAO;GAAQ,OAAO;GAAO;SAElC;AAIR,QAAO;EAAE,OAAO,EAAE,WAAW,EAAE,EAAE;EAAE,OAAO;EAAM;;AAGlD,SAAgB,qBAAqB,MAAc,OAA+B;CAChF,MAAM,MAAM,QAAQ,KAAK;AACzB,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACzD,MAAM,MAAM,GAAG,KAAK;AACpB,eAAc,KAAK,KAAK,UAAU,MAAM,EAAE,OAAO;AACjD,YAAW,KAAK,KAAK;;;;;;;;;;;;AAavB,SAAgB,WACd,OACA,UACA,YAAY,wBACM;CAClB,MAAM,UAAU,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,GAAG,CAAC;CAClD,MAAM,UAAU,OAAO,QAAQ,MAAM,UAAU;CAC/C,MAAM,QAAQ,CAAC,IAAwB,CAAC,OAA4B,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;CAC7F,MAAM,cAAc,QAAQ,QAAQ,CAAC,QAAQ,QAAQ,IAAI,GAAG,CAAC,CAAC,KAAK,KAAK;CACxE,MAAM,SAAS,QAAQ,QAAQ,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC,KAAK,KAAK;CAEpE,MAAM,aAAa,OAAO,MAAM,KAAK,IAAI,GAAG,OAAO,SAAS,UAAU,CAAC;AACvE,QAAO,EAAE,WAAW,OAAO,YAAY,CAAC,GAAG,YAAY,GAAG,YAAY,CAAC,EAAE;;;;;;;AAQ3E,SAAgB,cAAc,UAA4C;CACxE,MAAM,YAAsC,EAAE;AAC9C,MAAK,MAAM,KAAK,UAAU;AACxB,MAAI,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,CAAE;AACxC,YAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,WAAW,EAAE,QAAQ,EAAE,IAAI,SAAS,CAAC;;AAExE,QAAO,EAAE,WAAW;;;AAItB,SAAgB,kBAAkB,QAA4B,OAA6C;AACzG,QAAO,OAAO,QAAQ,MAAM,EAAE,MAAM,UAAU,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,GAAG,CAAC;;AAGhF,SAAgB,cAAc,OAAyB,QAAkC;AACvF,MAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,MAAM,MAAM,UAAU,EAAE,WAAW,EAAE;AAC3C,MAAI,CAAC,IAAI,SAAS,EAAE,GAAG,CAAE,KAAI,KAAK,EAAE,GAAG;AACvC,QAAM,UAAU,EAAE,UAAU;;;;;;;;;;AAuBhC,MAAa,eAAuB,OAAO,QAAQ,QAAQ,OAAO;CAChE,MAAM,QACJ,OAAO,UAAU,OAAO,gBAAgB,sBAAsB,QAAQ,OAAO,cAAc,GAAG;AAChG,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI;EACF,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,IAAI,MAAM,cAAc,OAAO,GAAG,KAAK;AAC7C,SAAO,EAAE,MAAM,EAAE,aAAa;SACxB;AACN,SAAO;;;;;;;;;;;;;AAcX,SAAgB,YACd,MACA,cACS;AACT,KAAI,cAAc,SAAS,KAAK,CAAE,QAAO;AACzC,QAAO,iBAAiB,UAAU,iBAAiB;;AAGrD,SAAS,gBAAgB,QAAgB,IAA4B;CACnE,MAAM,OAAO,sBAAsB,QAAQ,GAAG,QAAQ;CACtD,MAAM,MAAM,mBAAmB,OAAO;AACtC,KAAI,CAAC,WAAW,IAAI,CAAE,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;AACzD,gBAAe,MAAM,KAAK,UAAU,GAAG,GAAG,MAAM,OAAO;;;AAIzD,SAAS,kBAAkB,QAAgB,IAA4B;AACrE,KAAI;AACF,iBAAe,sBAAsB,QAAQ,GAAG,QAAQ,EAAE,GAAG,GAAG,GAAG,KAAK,OAAO;SACzE;;;;;;;;;AA8BV,eAAsB,mBAAmB,QAAgB,OAAoB,EAAE,EAAuB;CACpG,MAAM,MAAM,KAAK,uBAAO,IAAI,MAAM;CAClC,MAAM,WAAW,KAAK,YAAY,aAAa,OAAO;CACtD,MAAM,YAAY,qBAAqB,OAAO;CAC9C,MAAM,SAAS,qBAAqB,UAAU;CAC9C,IAAI,QAAQ,OAAO;AACnB,KAAI,OAAO,MAAO,SAAQ,cAAc,SAAS;CAEjD,MAAM,UAAU,kBACd,wBAAwB,UAAU;EAChC,SAAS,KAAK,WAAW,wBAAwB;EACjD,aAAa,KAAK,eAAe,oBAAoB;EACrD;EACA,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,SAAS,GAAG,EAAE;EAClD,CAAC,EACF,MACD;CAED,MAAM,UAA8B,EAAE;AACtC,MAAK,MAAM,MAAM,SAAS;AACxB,kBAAgB,QAAQ,GAAG;AAC3B,UAAQ,KAAK,GAAG;;CAGlB,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,SAA6B,EAAE;AACrC,MAAK,MAAM,MAAM,SAAS;EAExB,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,OAAO,GAAG,OAAO;AACtD,MAAI,CAAC,YAAY,GAAG,MAAM,OAAO,aAAa,CAAE;EAChD,MAAM,SAAqB;GACzB,eAAe,OAAO,kBAAkB;GACxC,OAAO,OAAO,SAAS,MAAM;GAC9B;EACD,IAAI,KAAK;AACT,MAAI;AACF,QAAK,MAAM,KAAK,QAAQ,QAAQ,GAAG;UAC7B;AACN,QAAK;;AAEP,MAAI,IAAI;AACN,UAAO,KAAK,GAAG;AACf,qBAAkB,QAAQ,GAAG;;;AAIjC,eAAc,OAAO,QAAQ;AAC7B,sBAAqB,WAAW,WAAW,OAAO,SAAS,CAAC;AAC5D,QAAO;EAAE,QAAQ;EAAS;EAAQ;;;;;;;;;;;AClepC,MAAM,8BAA8B,OAAU;;;;;AAM9C,eAAsB,WAA0B;AAC9C,KAAI,iBAAiB;AACnB,UAAQ,OAAO,MAAM,sDAAsD;AAC3E;;AAGF,KAAI,iBAAiB;AACnB,UAAQ,OAAO,MAAM,yDAAyD;AAC9E;;AAGF,oBAAmB,KAAK;CACxB,MAAM,KAAK,KAAK,KAAK;AAErB,KAAI;AACF,UAAQ,OAAO,MAAM,iDAAiD;EAEtE,MAAM,EAAE,UAAU,WAAW,MAAM,eAAe,SAAS,gBAAgB;EAC3E,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,mBAAiB,KAAK,KAAK,CAAC;AAC5B,UAAQ,OAAO,MACb,gCAAgC,SAAS,aACpC,OAAO,eAAe,UAAU,OAAO,cAAc,WACpD,QAAQ,OACf;UACM,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MAAM,6BAA6B,IAAI,IAAI;WAClD;AACR,qBAAmB,MAAM;;;;;;;AAQ7B,eAAsB,gBAA+B;AACnD,KAAI,CAAC,aAAa,UAAW;AAE7B,KAAI,sBAAsB;AACxB,UAAQ,OAAO,MAAM,4DAA4D;AACjF;;AAGF,KAAI,mBAAmB,iBAAiB;AACtC,UAAQ,OAAO,MAAM,iEAAiE;AACtF;;CAIF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,KAAI,qBAAqB,KAAK,KAAK,KAAK,GAAG,qBAAqB,4BAC9D;CAGF,IAAI,iBAAiB,aAAa;AAClC,KAAI,CAAC,gBAAgB;EACnB,MAAM,MAAM,MAAM,gBAAgB,qBAAqB,aAAa,UAAW;AAC/E,mBAAiB,KAAK,MAAM;AAC5B,MAAI,CAAC,IACH,SAAQ,OAAO,MAAM,iFAAiF;;AAI1G,yBAAwB,KAAK;CAC7B,MAAM,KAAK,KAAK,KAAK;AAErB,SAAQ,OAAO,MAAM,6CAA6C;AAElE,KAAI;EACF,MAAM,EAAE,eAAe,MAAM,OAAO;EACpC,MAAM,IAAI,MAAM,WAAW,gBAAgB,gBAAgB,aAAa,UAAW;EACnF,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,wBAAsB,KAAK,KAAK,CAAC;AACjC,UAAQ,OAAO,MACb,sCAAsC,EAAE,aAAa,UAClD,EAAE,eAAe,UAAU,EAAE,eAAe,SAC5C,EAAE,aAAa,YAAY,QAAQ,OACvC;UACM,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MAAM,mCAAmC,IAAI,IAAI;WACxD;AACR,0BAAwB,MAAM;;;;;;AAOlC,SAAgB,sBAA4B;CAC1C,MAAM,aAAa,aAAa,oBAAoB;AAEpD,SAAQ,OAAO,MACb,uCAAuC,aAAa,kBAAkB,KACvE;CASD,MAAM,SAAS,OAAe,cAC5B,UAAU,CACP,WAAW,eAAe,CAAC,CAC3B,WAAY,YAAY,UAAU,GAAG,OAAW,CAChD,OAAO,MAAM;AACZ,UAAQ,OAAO,MAAM,gBAAgB,MAAM,gBAAgB,EAAE,IAAI;GACjE;AAcN,KAAI,aAAa,oBAAoB,OACnC,kBAAiB,KAAK,MAAM,WAAW,aAAa,eAAe,EAAE,IAAM;KAE3E,SAAQ,OAAO,MACb,sEACD;CAQH,MAAM,eAAe,kBAAkB,aAAa,iBAAiB,WAAW;AAChF,KAAI,aAAa,oBAAoB,OACnC,SAAQ,OAAO,MACb,4CAA4C,OAAO,aAAa,gBAAgB,CAAC,SAAS,GAAG,IAAI,CAAC,0BAC/E,KAAK,MAAM,eAAe,IAAO,CAAC,SACtD;CAGH,IAAI;CACJ,MAAM,mBAAmB;AACvB,UAAQ,kBAAkB,KAAK,MAAM,aAAa,KAAK,EAAE,WAAW;AACpE,MAAI,MAAM,MAAO,OAAM,OAAO;AAC9B,yBAAuB,MAAM;;CAG/B,MAAM,QAAQ,iBAAiB;AAC7B,EAAK,MAAM,aAAa,KAAK;AAC7B,cAAY;IACX,aAAa;AAChB,KAAI,MAAM,MAAO,OAAM,OAAO;;;;;;AAOhC,SAAS,kBAAkB,MAA0B,YAA4B;AAC/E,KAAI,SAAS,UAAa,CAAC,OAAO,UAAU,KAAK,IAAI,OAAO,KAAK,OAAO,GACtE,QAAO;CAET,MAAM,sBAAM,IAAI,MAAM;CACtB,MAAM,OAAO,IAAI,KAAK,IAAI;AAC1B,MAAK,SAAS,MAAM,GAAG,GAAG,EAAE;AAC5B,KAAI,QAAQ,IAAK,MAAK,QAAQ,KAAK,SAAS,GAAG,EAAE;AACjD,QAAO,KAAK,SAAS,GAAG,IAAI,SAAS;;;;;AAUvC,eAAsB,WAA0B;AAC9C,KAAI,iBAAiB;AACnB,UAAQ,OAAO,MAAM,sDAAsD;AAC3E;;AAGF,KAAI,iBAAiB;AACnB,UAAQ,OAAO,MAAM,0DAA0D;AAC/E;;AAGF,KAAI,CAAC,eAAe,yBAClB;AAGF,oBAAmB,KAAK;CACxB,MAAM,KAAK,KAAK,KAAK;AAErB,KAAI;AACF,UAAQ,OAAO,MAAM,kDAAkD;EAEvE,MAAM,+BAAe,IAAI,KAAqB;AAC9C,MAAI;GACF,MAAM,OAAO,MAAM,gBAAgB,aAAa,EAAE,QAAQ,UAAU,CAAC;AACrE,QAAK,MAAM,KAAK,KAAM,cAAa,IAAI,EAAE,IAAI,EAAE,KAAK;UAC9C;EAER,MAAM,EAAE,2BAA2B,MAAM,OAAO;EAGhD,MAAM,QAAQ,MAAM,uBAClB,sBACM,mBACN,cAOA,EAAE,WAAW,MAAS,CACvB;EAOD,MAAM,UAAU,KAAK,KAAK,GAAG;AAC7B,mBAAiB,KAAK,KAAK,CAAC;AAC5B,UAAQ,OAAO,MACb,qCAAqC,MAAM,oBAAoB,QAAQ,OACxE;UACM,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MAAM,6BAA6B,IAAI,IAAI;WAClD;AACR,qBAAmB,MAAM;;;;;;AAO7B,SAAgB,sBAA4B;CAC1C,MAAM,aAAa,aAAa,oBAAoB;AAEpD,SAAQ,OAAO,MACb,uCAAuC,aAAa,kBAAkB,KACvE;AAaD,KAAI,aAAa,eACf,kBAAiB;AACf,YAAU,CAAC,OAAO,MAAM;AACtB,WAAQ,OAAO,MAAM,qCAAqC,EAAE,IAAI;IAChE;IACD,IAAO;KAEV,SAAQ,OAAO,MACb,oEACD;AASH,KAAI,aAAa,oBAAoB,QAAW;AAC9C,UAAQ,OAAO,MACb,gIAED;AACD;;CAGF,MAAM,QAAQ,kBAAkB;AAC9B,YAAU,CAAC,OAAO,MAAM;AACtB,WAAQ,OAAO,MAAM,uCAAuC,EAAE,IAAI;IAClE;IACD,WAAW;AAEd,KAAI,MAAM,MAAO,OAAM,OAAO;AAC9B,wBAAuB,MAAM;;AAO/B,MAAM,4BAA4B,OAAU;AAC5C,MAAM,iCAAiC;;;;;;;AAQvC,SAAgB,6BAAmC;AACjD,SAAQ,OAAO,MACb,qEACD;AAED,kBAAiB;AACf,SAAO,oCACJ,MAAM,EAAE,0BAA0B,qBAAqB,CAAC,CACxD,OAAO,MAAM;AACZ,WAAQ,OAAO,MAAM,6CAA6C,EAAE,IAAI;IACxE;IACH,+BAA+B;CAElC,MAAM,QAAQ,kBAAkB;AAC9B,SAAO,oCACJ,MAAM,EAAE,0BAA0B,qBAAqB,CAAC,CACxD,OAAO,MAAM;AACZ,WAAQ,OAAO,MAAM,+CAA+C,EAAE,IAAI;IAC1E;IACH,0BAA0B;AAE7B,KAAI,MAAM,MAAO,OAAM,OAAO;;;AAQhC,MAAM,sBAAsB;;AAG5B,MAAM,+BAA+B;;;;;;;;AASrC,SAAgB,wBAA8B;CAC5C,IAAI,UAAU;AACd,KAAI;AACF,YAAU,oBAAoB,CAAC,QAAQ;UAChC,GAAG;AACV,UAAQ,OAAO,MACb,gDAAgD,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,KAC5F;AACD;;AAEF,KAAI,CAAC,SAAS;AACZ,UAAQ,OAAO,MAAM,+DAA+D;AACpF;;CAGF,MAAM,aAAa;AACjB,qBAAmB,cAAc,oBAAoB,CAAC,QAAQ,EAAE;GAC9D,SAAS,qBAAqB,GAAG;GACjC,aAAa,oBAAoB;GAClC,CAAC,CACC,MAAM,MAAM;AACX,QAAK,MAAM,MAAM,EAAE,OACjB,SAAQ,OAAO,MAAM,6BAA6B,GAAG,KAAK,IAAI;IAEhE,CACD,OAAO,MAAM;AAEZ,WAAQ,OAAO,MACb,wCAAwC,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,IACpF;IACD;;CAGN,MAAM,QAAQ,WAAW,MAAM,6BAA6B;AAC5D,KAAI,MAAM,MAAO,OAAM,OAAO;CAC9B,MAAM,QAAQ,YAAY,MAAM,oBAAoB;AACpD,KAAI,MAAM,MAAO,OAAM,OAAO;AAC9B,SAAQ,OAAO,MACb,yCAAyC,KAAK,MAAM,sBAAsB,IAAK,CAAC,KACjF;;;AAQH,MAAM,6BAA6B;;;;;;;;AASnC,SAAgB,oBAAoB,OAGhC,EAAE,EAAQ;CACZ,IAAI;AACJ,KAAI;AACF,YAAU,mBAAmB,KAAK,WAAW,CAAC;UACvC,GAAG;AACV,UAAQ,OAAO,MACb,8CAA8C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,KAC1F;AACD;;AAEF,KAAI,CAAC,QAAQ,SAAS;AACpB,UAAQ,OAAO,MAAM,6DAA6D;AAClF;;CAEF,MAAM,OAAO,cAAc,QAAQ;AACnC,KAAI,CAAC,MAAM;AACT,UAAQ,OAAO,MAAM,qEAAqE;AAC1F;;CAGF,IAAI,kBAAkB;CACtB,MAAM,6BAA6B;AACjC,MAAI,gBAAgB,yBAA0B,QAAO;AACrD,MAAI,CAAC,iBAAiB;AACpB,qBAAkB;AAClB,WAAQ,OAAO,MACb,oFACD;;AAEH,SAAO;;CAGT,MAAM,OAAO,YAA2B;EACtC,MAAM,IAAI,OAAO,KAAK,QAAQ,mBAAmB;AACjD,MAAI,CAAC,EAAG;AACR,MAAI,CAAC,sBAAsB,CAAE;AAC7B,MAAI;AACF,SAAM,eAAe,4BAA4B,iBAAiB;IAChE,YAAY,EAAE;IACd,MAAM;IACN,OAAO,mBAAmB,EAAE,KAAK,OAAO,SAAS,eAAe,EAAE,wBAAwB,WAAW,EAAE,gBAAgB;IACvH,WAAW;IACX,WAAW,QAAQ,EAAE,GAAG,UAAU,EAAE,aAAa,cAAc,EAAE,wBAAwB,kBAAkB,EAAE,4BAA4B,mBAAmB,EAAE;IAC9J,oBAAoB;IACpB,YAAY,EAAE;IACd,gBAAgB,EAAE;IAClB,UAAU,EAAE;IACb,CAAC;WACK,GAAG;AAEV,WAAQ,OAAO,MACb,mDAAmD,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,IAC/F;;;CAIL,MAAM,QAAQ,iBAAiB;AAC7B,QAAM,CAAC,OAAO,MAAM;AAClB,WAAQ,OAAO,MACb,4CAA4C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,IACxF;IACD;IACD,2BAA2B;AAC9B,KAAI,MAAM,MAAO,OAAM,OAAO;CAE9B,MAAM,QAAQ,kBAAkB;AAC9B,QAAM,CAAC,OAAO,MAAM;AAClB,WAAQ,OAAO,MACb,4CAA4C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,IACxF;IACD;IACD,OAAO,IAAK;AACf,KAAI,MAAM,MAAO,OAAM,OAAO;AAC9B,wBAAuB,MAAM;AAC7B,SAAQ,OAAO,MAAM,uCAAuC,KAAK,KAAK;;;;AASxE,MAAM,4BAA4B;;;;;;;;AASlC,SAAgB,wBAA8B;CAC5C,MAAM,SAAS,aAAa,SAAS;AACrC,KAAI,CAAC,OAAO,SAAS;AACnB,UAAQ,OAAO,MAAM,wFAAwF;AAC7G;;CAGF,MAAM,aAAa;AACjB,0BAAwB,OAAO,CAAC,OAAO,MAAM;AAC3C,WAAQ,OAAO,MACb,8CAA8C,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE,CAAC,IAC1F;IACD;;CAGJ,MAAM,QAAQ,YAAY,MAAM,0BAA0B;AAC1D,KAAI,MAAM,MAAO,OAAM,OAAO;AAC9B,SAAQ,OAAO,MACb,yCAAyC,4BAA4B,IAAK,aAC5D,OAAO,YAAY,aAAa,OAAO,YAAY,QAAQ,OAAO,SAAS,mBAC1F;;;;;;;;;;;;AC3hBH,eAAsB,aACpB,QACA,QACkB;CAIlB,MAAM,IAAI;AAEV,SAAQ,QAAR;EACE,KAAK,gBACH,QAAO,iBAAiB,iBAAiB,gBAAgB,EAA4C;EAEvG,KAAK,aACH,QAAO,cAAc,iBAAiB,EAAyC;EAEjF,KAAK,eACH,QAAO,gBAAgB,iBAAiB,EAA2C;EAErF,KAAK,eACH,QAAO,gBAAgB,iBAAiB,EAA2C;EAErF,KAAK,eACH,QAAO,gBAAgB,iBAAiB,EAA2C;EAErF,KAAK,kBACH,QAAO,mBAAmB,iBAAiB,EAA8C;EAE3F,KAAK,iBACH,QAAO,kBAAkB,EAA6C;EAExE,KAAK,iBACH,QAAO,kBAAkB,iBAAiB,EAA6C;EAEzF,KAAK,eACH,QAAO,gBAAgB,iBAAiB,EAA2C;EAErF,KAAK,gBACH,QAAO,iBAAiB,iBAAiB,EAA4C;EAEvF,KAAK,kBACH,QAAO,mBAAmB,iBAAiB,gBAAgB,EAA8C;EAE3G,KAAK,cACH,QAAO,iBACL,iBACA,gBACA,EACD;EAEH,KAAK,qBACH,QAAO,iBACL,iBACA,gBACA,EACD;EAEH,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,sBAAsB;GACzB,MAAM,EAAE,mBAAmB,kBAAkB,oBAAoB,mBAAmB,oBAAoB,kBAAkB,oBAAoB,0BAA0B,MAAM,OAAO;AAErL,WAAQ,QAAR;IACE,KAAK,iBAAkB,QAAO,kBAAkB,gBAAgB,EAA6C;IAC7G,KAAK,gBAAiB,QAAO,iBAAiB,gBAAgB,EAA4C;IAC1G,KAAK,kBAAmB,QAAO,mBAAmB,gBAAgB,EAA8C;IAChH,KAAK,iBAAkB,QAAO,kBAAkB,gBAAgB,EAA6C;IAC7G,KAAK,kBAAmB,QAAO,mBAAmB,gBAAgB,EAA8C;IAChH,KAAK,gBAAiB,QAAO,iBAAiB,gBAAgB,EAA4C;IAC1G,KAAK,mBAAoB,QAAO,mBAAmB,gBAAgB,EAA8C;IACjH,KAAK,qBAAsB,QAAO,sBAAsB,gBAAgB,EAAiD;;AAE3H;;EAGF,KAAK,kBAAkB;GACrB,MAAM,EAAE,wBAAwB,MAAM,OAAO;AAC7C,UAAO,oBAAoB,gBAAgB,EAA+C;;EAG5F,KAAK,sBAAsB;GACzB,MAAM,EAAE,4BAA4B,MAAM,OAAO;AACjD,UAAO,wBAAwB,gBAAgB,EAAmD;;EAGpG,KAAK,sBAAsB;GACzB,MAAM,EAAE,2BAA2B,MAAM,OAAO;AAChD,UAAO,uBAAuB,gBAAgB,EAAkD;;EAGlG,KAAK,eAAe;GAClB,MAAM,EAAE,qBAAqB,MAAM,OAAO;AAC1C,UAAO,iBAAiB,gBAAgB,EAA4C;;EAGtF,KAAK,sBAAsB;GACzB,MAAM,EAAE,2BAA2B,MAAM,OAAO;AAChD,UAAO,uBAAuB,gBAAgB,EAAkD;;EAGlG,KAAK,oBAAoB;GACvB,MAAM,EAAE,0BAA0B,MAAM,OAAO;AAC/C,OAAI,CAAC,aAAa,UAChB,OAAM,IAAI,MAAM,4EAA4E;AAE9F,UAAO,sBACL,GACA,aAAa,UACd;;EAGH,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,qBAAqB;GACxB,MAAM,EAAE,WAAW,aAAa,kBAAkB,yBAAyB,MAAM,OAAO;AACxF,OAAI,CAAC,eAAe,yBAClB,OAAM,IAAI,MAAM,GAAG,OAAO,sCAAsC;AAElE,WAAQ,QAAR;IACE,KAAK,SAAoB,QAAO,UAAU,gBAAgB,EAAqC;IAC/F,KAAK,WAAoB,QAAO,YAAY,gBAAgB,EAAuC;IACnG,KAAK,gBAAoB,QAAO,iBAAiB,gBAAgB,EAA4C;IAC7G,KAAK,oBAAqB,QAAO,qBAAqB,gBAAgB,EAAgD;;AAExH;;EAGF,KAAK,kBAAkB;GACrB,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAC3C,UAAO,kBAAkB,iBAAiB,gBAAgB,EAA6C;;EAGzG,KAAK,kBAGH,QAAO,MAAM,mBAAmB,gBAAgB,EAA8C;EAGhG,KAAK;AAEH,OAAI,CAAC,eAAe,yBAClB,OAAM,IAAI,MAAM,+EAA+E;AAEjG,UAAO,MAAM,mBAAmB,gBAAgB,EAA8C;EAGhG,QACE,OAAM,IAAI,MAAM,mBAAmB,SAAS;;;;;;ACjJlD,SAAgB,aAAa,QAAgB,UAA6B;AACxE,KAAI;AACF,SAAO,MAAM,KAAK,UAAU,SAAS,GAAG,KAAK;SACvC;;;;;AAYV,eAAsB,cACpB,SACA,QACe;CACf,MAAM,EAAE,IAAI,QAAQ,WAAW;AAG/B,KAAI,WAAW,UAAU;EACvB,MAAM,UAAU,OAAO,YAAY;AACjC,OAAI;AACF,QAAI,CAAC,eAAgB,QAAO;IAC5B,MAAM,WAAW,MAAM,eAAe,UAAU;IAChD,MAAM,WAAW,MAAM,gBAAgB,eAAe;AACtD,WAAO;KAAE,OAAO,SAAS;KAAO,QAAQ,SAAS;KAAQ;KAAU;WAC7D;AACN,WAAO;;MAEP;AAEJ,eAAa,QAAQ;GACnB;GACA,IAAI;GACJ,QAAQ;IACN,QAAQ,KAAK,OAAO,KAAK,KAAK,GAAG,aAAa,IAAK;IACnD;IACA,eAAe,gBAAgB,IAAI,KAAK,cAAc,CAAC,aAAa,GAAG;IACvE,mBAAmB,aAAa;IAChC;IACA,eAAe,gBAAgB,IAAI,KAAK,cAAc,CAAC,aAAa,GAAG;IACvE,mBAAmB,aAAa;IAChC,YAAY,aAAa;IACzB,gBAAgB,gBAAgB,eAAe;IAC/C,IAAI;IACJ;IACA,oBAAoB,qBAAqB,IAAI,KAAK,mBAAmB,CAAC,aAAa,GAAG;IACtF,WAAW,aAAa,aAAa;IACrC,WAAWA,UAAe;IAI1B,eAAe,kBAAkB;IAClC;GACF,CAAC;AACF,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,aAAa;AAC1B,YAAU,CAAC,OAAO,MAAM;AACtB,WAAQ,OAAO,MAAM,iCAAiC,EAAE,IAAI;IAC5D;AACF,eAAa,QAAQ;GAAE;GAAI,IAAI;GAAM,QAAQ,EAAE,WAAW,MAAM;GAAE,CAAC;AACnE,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,mBAAmB;AAChC,iBAAe,CAAC,OAAO,MAAM;AAC3B,WAAQ,OAAO,MAAM,uCAAuC,EAAE,IAAI;IAClE;AACF,eAAa,QAAQ;GAAE;GAAI,IAAI;GAAM,QAAQ,EAAE,WAAW,MAAM;GAAE,CAAC;AACnE,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,2BAA2B;AACxC,eAAa,QAAQ;GACnB;GACA,IAAI;GACJ,QAAQ;IACN,QAAQ;IACR,gBAAgB,OAAO,QAAQ,mBAAmB,SAAS,CACxD,QAAQ,CAAC,IAAI,SAAS,OAAO,WAAY,IAA6B,QAAQ,CAC9E,KAAK,CAAC,QAAQ,GAAG;IACrB;GACF,CAAC;AACF,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,2BAA2B;AACxC,MAAI;GACF,MAAM,IAAI;GAKV,MAAM,UAAU,wBAAwB;IACtC,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,SAAS,EAAE;IACZ,CAAC;AACF,yBAAsB,QAAQ;AAC9B,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ,EAAE,QAAQ,SAAS;IAAE,CAAC;WAC5D,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,qBAAqB;EAClC,MAAM,IAAI;AAMV,MAAI,CAAC,EAAE,SAAS;AACd,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAA0C,CAAC;AACxF,UAAO,KAAK;AACZ;;AAKF,oBACE;GAAE,OAHW,EAAE,SAA8D;GAGpE,SAAS,EAAE;GAAS,OAAO,EAAE;GAAO,EAC7C,mBACD,CAAC,MAAM,WAAW;AACjB,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM;IAAQ,CAAC;AAC9C,UAAO,KAAK;IACZ,CAAC,OAAO,MAAM;AAEd,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;AACnD,UAAO,KAAK;IACZ;AACF;;AAKF,KAAI,WAAW,qBAAqB;AAClC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAAyC,CAAC;AACvF,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;AA2BV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ;KAAE,IAAI;KAAM,IAbtC,MAAM,eAAe,4BAA4B,iBAAiB;MACnF,YAAY,EAAE;MACd,MAAM,EAAE;MACR,OAAO,EAAE;MACT,WAAW,EAAE,aAAa;MAC1B,WAAW,EAAE;MACb,oBAAoB,EAAE,sBAAsB;MAC5C,YAAY,EAAE,cAAc,EAAE;MAC9B,gBAAgB,EAAE,kBAAkB,EAAE;MACtC,UAAU,EAAE,YAAY,EAAE;MAC1B,KAAK,EAAE;MACR,CAAC;KAEuE;IAAE,CAAC;WACrE,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAKF,KAAI,WAAW,0BAA0B;AACvC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAA6C,CAAC;AAC3F,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;GAOV,IAAI,eAA8B;AAClC,OAAI,EAAE,KAAK;IACT,MAAM,MAAM,MAAM,gBAAgB,uBAAuB,EAAE,IAAI;AAC/D,QAAI,IAAK,gBAAe,IAAI;;AAG9B,SAAM,eAAe,sBAAsB;IACzC,YAAY,EAAE;IACd,QAAQ,EAAE;IACV,OAAO,EAAE;IACT;IACD,CAAC;AAEF,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ,EAAE,IAAI,MAAM;IAAE,CAAC;WACrD,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAGF,KAAI,WAAW,yBAAyB;AACtC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAA6C,CAAC;AAC3F,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;AAMV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QALxB,MAAM,eAAe,oBAAoB;KACpD,OAAO,EAAE;KACT,QAAQ,EAAE;KACV,OAAO,EAAE;KACV,CAAC;IACiD,CAAC;WAC7C,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAGF,KAAI,WAAW,qBAAqB;AAClC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAAyC,CAAC;AACvF,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;AAeV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAPxB,MAAM,eAAe,kBAAkB;KAClD,WAAW,EAAE;KACb,WAAW,EAAE;KACb,MAAM,EAAE;KACR,OAAO,EAAE;KACT,QAAQ,EAAE;KACX,CAAC;IACiD,CAAC;WAC7C,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAGF,KAAI,WAAW,sBAAsB;AACnC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAAyC,CAAC;AACvF,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;GACV,MAAM,QAAQ,EAAE,SAAS;GAEzB,IAAI,oBAAoB,EAAE;GAC1B,IAAI;AACJ,OAAI,sBAAsB,UAAa,EAAE,KAAK;IAC5C,MAAM,MAAM,MAAM,gBAAgB,uBAAuB,EAAE,IAAI;AAC/D,QAAI,KAAK;AACP,yBAAoB,IAAI;AACxB,2BAAsB,IAAI;;;GAI9B,IAAI;AACJ,OAAI,sBAAsB,OACxB,QAAO,MAAM,eAAe,wBAAwB,mBAAmB,MAAM;OAE7E,QAAO,MAAM,eAAe,kBAAkB,EAAE,OAAO,CAAC;AAE1D,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ;KAAE;KAAM,cAAc;KAAqB;IAAE,CAAC;WACpF,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,oBAAoB;AACjC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAAyC,CAAC;AACvF,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;GAQV,IAAI;AACJ,OAAI,EAAE,aAEJ,cADY,MAAM,gBAAgB,iBAAiB,EAAE,aAAa,GACjD;AAUnB,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAPxB,MAAM,eAAe,kBAAkB;KAClD;KACA,WAAW,EAAE;KACb,MAAM,EAAE;KACR,OAAO,EAAE,SAAS;KAClB,QAAQ,EAAE,UAAU;KACrB,CAAC;IACiD,CAAC;WAC7C,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,qBAAqB;AAClC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAAyC,CAAC;AACvF,UAAO,KAAK;AACZ;;AAEF,MAAI;AAEF,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QADvB,MAAM,eAAe,qBAAqB;IACJ,CAAC;WAC9C,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAGF,KAAI,WAAW,yBAAyB;AACtC,MAAI,CAAC,gBAAgB,0BAA0B;AAC7C,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAAO;IAA8C,CAAC;AAC5F,UAAO,KAAK;AACZ;;AAEF,MAAI;GACF,MAAM,IAAI;GAaV,IAAI,oBAAoB,EAAE,cAAc;GACxC,IAAI,sBAAsB,EAAE,gBAAgB;AAC5C,OAAI,sBAAsB,QAAQ,EAAE,KAAK;IACvC,MAAM,MAAM,MAAM,gBAAgB,uBAAuB,EAAE,IAAI;AAC/D,QAAI,KAAK;AACP,yBAAoB,IAAI;AACxB,2BAAsB,IAAI;;;AAI9B,SAAM,eAAe,oBAAoB;IACvC,YAAY,EAAE;IACd,YAAY;IACZ,cAAc;IACd,SAAS,EAAE,WAAW;IACtB,cAAc,EAAE,gBAAgB;IAChC,SAAS,EAAE,WAAW;IACtB,WAAW,EAAE,aAAa;IAC1B,YAAY,EAAE,cAAc;IAC5B,mBAAmB,EAAE,qBAAqB;IAC3C,CAAC;AAEF,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ,EAAE,IAAI,MAAM;IAAE,CAAC;WACrD,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,sBAAsB;AACnC,MAAI;GACF,MAAM,IAAI;AAOV,OAAI,CAAC,EAAE,MAAM;AACX,iBAAa,QAAQ;KAAE;KAAI,IAAI;KAAO,OAAO;KAAwC,CAAC;AACtF,WAAO,KAAK;AACZ;;GAGF,MAAM,OAAO,QAAQ;IACnB,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,SAAS,EAAE,WAAW,EAAE;IACxB,aAAa,EAAE;IAChB,CAAC;AAEF,kCAAe;AAEf,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAM,QAAQ;KAAE,IAAI,KAAK;KAAI,QAAQ,KAAK;KAAQ;IAAE,CAAC;WAC7E,GAAG;AAEV,gBAAa,QAAQ;IAAE;IAAI,IAAI;IAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;IACJ,CAAC;;AAErD,SAAO,KAAK;AACZ;;AAIF,KAAI,WAAW,oBAAoB;AACjC,eAAa,QAAQ;GAAE;GAAI,IAAI;GAAM,QAAQA,UAAe;GAAE,CAAC;AAC/D,SAAO,KAAK;AACZ;;AAIF,KAAI;AAEF,eAAa,QAAQ;GAAE;GAAI,IAAI;GAAM,QADtB,MAAM,aAAa,QAAQ,OAAO;GACJ,CAAC;UACvC,GAAG;AAEV,eAAa,QAAQ;GAAE;GAAI,IAAI;GAAO,OAD1B,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;GACJ,CAAC;;AAErD,QAAO,KAAK;;;;;AC9ed,MAAM,cAAc;AACpB,MAAM,aAAa;AAEnB,eAAsB,iBAAiB,SAAwC;CAM7E,MAAM,OAAQ,QAAuC,SAAS;AAI9D,MAHmB,MAAM,KAAK,MAC5B,0EACD,EACc,aAAa,EAAG;AAO/B,MALqB,MAAM,KAAK,MAC9B;;qEAGD,EACgB,KAAK,IAAI,YAAY,YAAa;AAEnD,SAAQ,OAAO,MACb,sEACD;AAKD,MAHgB,MAAM,KAAK,MACzB,gEACD,EACW,aAAa,GAAG;AAC1B,QAAM,KAAK,MAAM;;;;;;;;MAQf;AACF,UAAQ,OAAO,MACb,4EACD;;CAGH,IAAI,SAAwB;CAC5B,IAAI,eAAe;CACnB,IAAI,SAAS;AACb,QAAO,MAAM;EACX,MAAM,MAAM,WAAW,OACnB,mDACA;EACJ,MAAM,SAAiC,WAAW,OAAO,CAAC,WAAW,GAAG,CAAC,YAAY,OAAO;EAC5F,MAAM,SAAS,MAAM,KAAK,MAAsB,KAAK,OAAO;AAC5D,MAAI,OAAO,KAAK,WAAW,EAAG;EAE9B,MAAM,MAAgB,OAAO,KAAK,KAAK,MAAM,EAAE,GAAG;AAClD,QAAM,KAAK,MACT;oCAEA,CAAC,IAAI,CACN;AACD,kBAAgB,IAAI;AACpB,WAAS,IAAI,IAAI,SAAS;AAC1B;AACA,MAAI,SAAS,OAAO,EAClB,SAAQ,OAAO,MACb,wCAAwC,aAAa,6BACtD;AAEH,MAAI,OAAO,KAAK,SAAS,WAAY;;AAGvC,OAAM,KAAK,MACT,+CAA+C,YAAY,GAC5D;AAED,SAAQ,OAAO,MACb,8CAA8C,aAAa,mBAC5D;;;;;;;;;;;;ACzDH,SAAS,aAAa,MAAgC;AACpD,QAAO,IAAI,SAAS,YAAY;EAC9B,MAAM,SAAS,QAAQ,KAAK;EAC5B,MAAM,QAAQ,iBAAiB;AAAE,UAAO,SAAS;AAAE,WAAQ,MAAM;KAAK,IAAI;AAC1E,SAAO,GAAG,iBAAiB;AAAE,gBAAa,MAAM;AAAE,UAAO,KAAK;AAAE,WAAQ,KAAK;IAAI;AACjF,SAAO,GAAG,eAAe;AAAE,gBAAa,MAAM;AAAE,WAAQ,MAAM;IAAI;GAClE;;;;;AAMJ,eAAe,eAAe,YAAqC;AACjE,KAAI,WAAW,WAAW,EAAE;AAE1B,MADa,MAAM,aAAa,WAAW,CAEzC,OAAM,IAAI,MAAM,wEAAwE;AAE1F,MAAI;AACF,cAAW,WAAW;AACtB,WAAQ,OAAO,MAAM,4CAA4C;UAC3D;;CAKV,MAAM,SAAS,cAAc,WAAmB;EAC9C,IAAI,SAAS;AAEb,SAAO,GAAG,SAAS,UAAkB;AACnC,aAAU,MAAM,UAAU;GAC1B,IAAI;AACJ,WAAQ,KAAK,OAAO,QAAQ,KAAK,MAAM,IAAI;IACzC,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;AAChC,aAAS,OAAO,MAAM,KAAK,EAAE;AAE7B,QAAI,KAAK,MAAM,KAAK,GAAI;IAExB,IAAI;AACJ,QAAI;AACF,eAAU,KAAK,MAAM,KAAK;YACpB;AACN,kBAAa,QAAQ;MAAE,IAAI;MAAK,IAAI;MAAO,OAAO;MAAgB,CAAC;AACnE,YAAO,SAAS;AAChB;;AAGF,kBAAc,SAAS,OAAO,CAAC,OAAO,MAAe;KACnD,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,kBAAa,QAAQ;MAAE,IAAI,QAAQ;MAAI,IAAI;MAAO,OAAO;MAAK,CAAC;AAC/D,YAAO,SAAS;MAChB;;IAEJ;AAEF,SAAO,GAAG,eAAe,GAEvB;GACF;AAEF,QAAO,GAAG,UAAU,MAAM;AACxB,UAAQ,OAAO,MAAM,kCAAkC,EAAE,IAAI;GAC7D;AAEF,QAAO,OAAO,kBAAkB;AAC9B,UAAQ,OAAO,MACb,wCAAwC,WAAW,IACpD;GACD;AAEF,QAAO;;AAOT,eAAsB,MAAM,QAAwC;AAClE,iBAAgB,OAAO;AACvB,cAAa,KAAK,KAAK,CAAC;AAExB,uBAAsB,wBAAwB,CAAC;AAE/C,SAAQ,OAAO,MAAM,oCAAoC;AACzD,SAAQ,OAAO,MAAM,wBAAwB,OAAO,WAAW,IAAI;AACnE,SAAQ,OAAO,MAAM,iCAAiC,OAAO,eAAe,IAAI;CAChF,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,SAAQ,OAAO,MACb,mCAAmC,mBAAmB,KAAK,IAC5D;AAGD,KAAI;AAAE,cAAY,QAAQ,KAAK,GAAG;SAAU;AAE5C,yBAAwB,OAAO,eAAe;AAO9C,CAAK,sBAAsB,QAAQ,EAAE,iBAAiB,MAAM,CAAC,CAC1D,MAAM,YAAY;AACjB,qBAAmB,QAAQ;AAC3B,UAAQ,OAAO,MAAM,kCAAkC,QAAQ,YAAY,IAAI;GAC/E,CACD,OAAO,MAAM;EACZ,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MAAM,gDAAgD,IAAI,IAAI;AAC7E,UAAQ,KAAK,EAAE;GACf;CAKJ,MAAM,SAAS,MAAM,eAAe,OAAO,WAAW;AAGtD,6BAA4B;AAI5B,wBAAuB;AAKvB,sBAAqB;AAKrB,wBAAuB;AAOvB,CAAK,qBAAqB,QAAQ,EAAE,iBAAiB,MAAM,CAAC,CACzD,MAAM,YAAY;AACjB,oBAAkB,QAAQ;AAC1B,UAAQ,OAAO,MACb,oCAAoC,QAAQ,YAAY,IACzD;AAED,uBAAqB;AAErB,MAAI,QAAQ,0BAA0B;AACpC,wBAAqB;AAMrB,GAAK,iBAAiB,QAAQ,CAAC,OAAO,MAAM;IAC1C,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,YAAQ,OAAO,MAAM,6CAA6C,IAAI,IAAI;KAC1E;QAEF,SAAQ,OAAO,MACb,4DACD;AAIH,aAAW;AACX,eAAa;GACb,CACD,OAAO,MAAM;EACZ,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MACb,0DAA0D,IAAI,IAC/D;AACD,UAAQ,KAAK,EAAE;GACf;CAEJ,MAAM,WAAW,OAAO,WAAkC;AACxD,UAAQ,OAAO,MAAM,kBAAkB,OAAO,wBAAwB;AAEtE,uBAAqB,KAAK;AAE1B,MAAI,oBAAqB,eAAc,oBAAoB;AAC3D,MAAI,oBAAqB,eAAc,oBAAoB;AAC3D,MAAI,oBAAqB,eAAc,oBAAoB;AAE3D,cAAY;AAEZ,SAAO,OAAO;EAEd,MAAM,sBAAsB;EAC5B,MAAM,mBAAmB;EACzB,MAAM,WAAW,KAAK,KAAK,GAAG;AAE9B,MAAI,mBAAmB,iBAAiB;AACtC,WAAQ,OAAO,MACb,oEACY,gBAAgB,UAAU,gBAAgB,QACvD;AAED,WAAQ,mBAAmB,oBAAoB,KAAK,KAAK,GAAG,SAC1D,OAAM,IAAI,SAAS,YAAY,WAAW,SAAS,iBAAiB,CAAC;AAGvE,OAAI,mBAAmB,gBACrB,SAAQ,OAAO,MAAM,0DAA0D;OAE/E,SAAQ,OAAO,MAAM,kDAAkD;;AAI3E,MAAI;AACF,SAAM,eAAe,OAAO;UACtB;AAIR,MAAI;AACF,cAAW,OAAO,WAAW;UACvB;AAIR,UAAQ,KAAK,EAAE;;AAGjB,SAAQ,GAAG,gBAAgB;AAAE,WAAS,SAAS,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC;GAAI;AAChF,SAAQ,GAAG,iBAAiB;AAAE,WAAS,UAAU,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC;GAAI;AAGlF,OAAM,IAAI,cAAc,GAAG"}