{"version":3,"file":"daemon-Bhs7kk0a.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/workers/keepalive.ts","../src/workers/supervision.ts","../src/observations/store.ts","../src/daemon/daemon/scheduler.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.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 *\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 { 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\";\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  /** 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 * Event id: `<worker>#<kind>` for the once-per-run terminal kinds, and\n * `<worker>#stalled@<turns>` for stalls — the turn count re-arms the id, so a\n * worker that resumes and stalls again gets a second event while a restart\n * cannot replay the first.\n */\nexport function eventId(worker: string, kind: SupervisionKind, turns = 0): string {\n  return kind === \"stalled\" ? `${worker}#stalled@${turns}` : `${worker}#${kind}`;\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  /** pid liveness — injected so tests need no real processes. */\n  isAlive?: (pid: number) => boolean;\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  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    }\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      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 workers the ledger no longer lists and cap the rest (newest ids last —\n * worker ids sort chronologically), so the persisted state stays bounded.\n */\nexport function pruneState(state: SupervisionState, statuses: WorkerStatus[]): SupervisionState {\n  const present = new Set(statuses.map((s) => s.id));\n  const kept = Object.entries(state.delivered)\n    .filter(([id]) => present.has(id))\n    .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n    .slice(-MAX_REMEMBERED_WORKERS);\n  return { delivered: Object.fromEntries(kept) };\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 a finished/failed/stalled event should be relayed into the owner's\n * terminal. A caller launched with --output-format json|stream-json reads\n * the run's result itself, and the harness already notifies it on process\n * exit — pushing the one-liner too would cost that caller a full extra turn\n * for information it already has. Interactive/pane launches and the default\n * text format are unaffected: only json and stream-json ever skip the relay.\n */\nexport function shouldRelay(outputFormat: WorkerStatus[\"outputFormat\"] | undefined): boolean {\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  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      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(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 * store.ts — PostgreSQL persistence for PAI observations.\n *\n * All functions accept a pg.Pool and are safe to call concurrently.\n * Schema is initialized lazily via ensureObservationTables().\n *\n * Content-hash deduplication: observations with the same hash\n * created within a 30-second window are silently dropped to prevent\n * duplicate entries from rapid repeated tool calls.\n */\n\nimport { sha256 } from '../utils/hash.js';\nimport type { Pool } from 'pg';\nimport type { ClassifiedObservation } from './classifier.js';\n\n// ---------------------------------------------------------------------------\n// Row types\n// ---------------------------------------------------------------------------\n\nexport interface ObservationRow {\n  id: number;\n  session_id: string;\n  project_id: number | null;\n  project_slug: string | null;\n  type: string;\n  title: string;\n  narrative: string | null;\n  tool_name: string | null;\n  tool_input_summary: string | null;\n  files_read: string[];\n  files_modified: string[];\n  concepts: string[];\n  content_hash: string | null;\n  created_at: Date;\n}\n\nexport interface SessionSummaryRow {\n  id: number;\n  session_id: string;\n  project_id: number | null;\n  project_slug: string | null;\n  request: string | null;\n  investigated: string | null;\n  learned: string | null;\n  completed: string | null;\n  next_steps: string | null;\n  observation_count: number;\n  created_at: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Input types\n// ---------------------------------------------------------------------------\n\nexport interface StoreObservationInput extends Omit<ClassifiedObservation, 'narrative'> {\n  session_id: string;\n  project_id?: number | null;\n  project_slug?: string | null;\n  narrative?: string | null;\n}\n\nexport interface StoreSessionSummaryInput {\n  session_id: string;\n  project_id?: number | null;\n  project_slug?: string | null;\n  request?: string | null;\n  investigated?: string | null;\n  learned?: string | null;\n  completed?: string | null;\n  next_steps?: string | null;\n  observation_count?: number;\n}\n\nexport interface QueryObservationsOptions {\n  projectId?: number;\n  sessionId?: string;\n  type?: string;\n  limit?: number;\n  offset?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Schema initialisation\n// ---------------------------------------------------------------------------\n\nlet _tablesEnsured = false;\n\n/**\n * Inlined schema DDL — avoids runtime file reads that break in bundled code\n * (the bundler puts this in a shared chunk whose __dirname differs from src/).\n */\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS pai_observations (\n  id SERIAL PRIMARY KEY,\n  session_id TEXT NOT NULL,\n  project_id INTEGER,\n  project_slug TEXT,\n  type TEXT NOT NULL CHECK (type IN ('decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change')),\n  title TEXT NOT NULL,\n  narrative TEXT,\n  tool_name TEXT,\n  tool_input_summary TEXT,\n  files_read JSONB DEFAULT '[]'::jsonb,\n  files_modified JSONB DEFAULT '[]'::jsonb,\n  concepts JSONB DEFAULT '[]'::jsonb,\n  content_hash TEXT,\n  created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\nCREATE INDEX IF NOT EXISTS idx_obs_project ON pai_observations(project_id);\nCREATE INDEX IF NOT EXISTS idx_obs_session ON pai_observations(session_id);\nCREATE INDEX IF NOT EXISTS idx_obs_type ON pai_observations(type);\nCREATE INDEX IF NOT EXISTS idx_obs_created ON pai_observations(created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_obs_hash ON pai_observations(content_hash);\n\nCREATE TABLE IF NOT EXISTS pai_session_summaries (\n  id SERIAL PRIMARY KEY,\n  session_id TEXT NOT NULL UNIQUE,\n  project_id INTEGER,\n  project_slug TEXT,\n  request TEXT,\n  investigated TEXT,\n  learned TEXT,\n  completed TEXT,\n  next_steps TEXT,\n  observation_count INTEGER DEFAULT 0,\n  created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\nCREATE INDEX IF NOT EXISTS idx_ss_project ON pai_session_summaries(project_id);\nCREATE INDEX IF NOT EXISTS idx_ss_session ON pai_session_summaries(session_id);\n`;\n\n/**\n * Run schema DDL idempotently against the given pool.\n * Uses a module-level flag so subsequent calls are no-ops within the same\n * process lifetime (the SQL itself uses IF NOT EXISTS so it is safe to re-run).\n */\nexport async function ensureObservationTables(pool: Pool): Promise<void> {\n  if (_tablesEnsured) return;\n  await pool.query(SCHEMA_SQL);\n  _tablesEnsured = true;\n}\n\n// ---------------------------------------------------------------------------\n// Content-hash deduplication\n// ---------------------------------------------------------------------------\n\n/**\n * Compute a 16-character hex content hash for deduplication.\n * Hash = SHA256(session_id + tool_name + title).slice(0, 16)\n */\nfunction computeContentHash(sessionId: string, toolName: string, title: string): string {\n  return sha256(sessionId + '\\x00' + toolName + '\\x00' + title).slice(0, 16);\n}\n\n// ---------------------------------------------------------------------------\n// Store observation\n// ---------------------------------------------------------------------------\n\n/**\n * Insert an observation, skipping duplicates within a 30-second window.\n * Returns the inserted row's id, or null if the insert was suppressed.\n */\nexport async function storeObservation(\n  pool: Pool,\n  obs: StoreObservationInput\n): Promise<number | null> {\n  await ensureObservationTables(pool);\n\n  const hash = computeContentHash(obs.session_id, obs.tool_name, obs.title);\n\n  // Check for a recent duplicate (30-second window)\n  const dupCheck = await pool.query<{ id: number }>(\n    `SELECT id FROM pai_observations\n     WHERE content_hash = $1\n       AND session_id   = $2\n       AND created_at  >= NOW() - INTERVAL '30 seconds'\n     LIMIT 1`,\n    [hash, obs.session_id]\n  );\n\n  if (dupCheck.rowCount && dupCheck.rowCount > 0) {\n    // Duplicate within dedup window — silently skip\n    return null;\n  }\n\n  const result = await pool.query<{ id: number }>(\n    `INSERT INTO pai_observations\n       (session_id, project_id, project_slug, type, title, narrative,\n        tool_name, tool_input_summary, files_read, files_modified, concepts, content_hash)\n     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10::jsonb, $11::jsonb, $12)\n     RETURNING id`,\n    [\n      obs.session_id,\n      obs.project_id ?? null,\n      obs.project_slug ?? null,\n      obs.type,\n      obs.title,\n      obs.narrative ?? null,\n      obs.tool_name,\n      obs.tool_input_summary ?? null,\n      JSON.stringify(obs.files_read),\n      JSON.stringify(obs.files_modified),\n      JSON.stringify(obs.concepts),\n      hash,\n    ]\n  );\n\n  return result.rows[0]?.id ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Store observation with project attribution\n// ---------------------------------------------------------------------------\n\n/**\n * Minimal registry handle the cwd → project lookup needs (better-sqlite3\n * shaped). Kept structural so callers pass the daemon's registryDb without a\n * cross-module import.\n */\nexport interface ProjectLookupDb {\n  prepare(sql: string): { get(...params: unknown[]): unknown };\n}\n\nexport interface ObservationWithCwd extends StoreObservationInput {\n  /** Working directory the observation happened in; attributed to a project when one actively covers it. */\n  cwd?: string;\n}\n\n/**\n * Attribute an observation to the active project covering `cwd`, then store\n * it. One shared sequence for every daemon-side writer (IPC handler, cache\n * keepalive) so the lookup query exists exactly once.\n */\nexport async function storeObservationWithProject(\n  db: ProjectLookupDb,\n  pool: Pool,\n  obs: ObservationWithCwd\n): Promise<number | null> {\n  let project_id: number | null = null;\n  let project_slug: string | null = null;\n  if (obs.cwd) {\n    const row = db.prepare(\n      \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n    ).get(obs.cwd) as { id: number; slug: string } | undefined;\n    if (row) {\n      project_id = row.id;\n      project_slug = row.slug;\n    }\n  }\n  return storeObservation(pool, { ...obs, project_id, project_slug });\n}\n\n// ---------------------------------------------------------------------------\n// Query observations\n// ---------------------------------------------------------------------------\n\n/**\n * Filtered query for observations with optional projectId, sessionId, type,\n * limit, and offset. Returns results ordered by created_at DESC.\n */\nexport async function queryObservations(\n  pool: Pool,\n  opts: QueryObservationsOptions = {}\n): Promise<ObservationRow[]> {\n  await ensureObservationTables(pool);\n\n  const conditions: string[] = [];\n  const params: unknown[] = [];\n  let idx = 1;\n\n  if (opts.projectId !== undefined) {\n    conditions.push(`project_id = $${idx++}`);\n    params.push(opts.projectId);\n  }\n  if (opts.sessionId !== undefined) {\n    conditions.push(`session_id = $${idx++}`);\n    params.push(opts.sessionId);\n  }\n  if (opts.type !== undefined) {\n    conditions.push(`type = $${idx++}`);\n    params.push(opts.type);\n  }\n\n  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n  const limit = opts.limit ?? 50;\n  const offset = opts.offset ?? 0;\n\n  params.push(limit, offset);\n\n  const result = await pool.query<ObservationRow>(\n    `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n            tool_name, tool_input_summary,\n            files_read, files_modified, concepts,\n            content_hash, created_at\n     FROM pai_observations\n     ${where}\n     ORDER BY created_at DESC\n     LIMIT $${idx++} OFFSET $${idx}`,\n    params\n  );\n\n  return result.rows;\n}\n\n/**\n * Most recent observations for a project, ordered by created_at DESC.\n */\nexport async function queryRecentObservations(\n  pool: Pool,\n  projectId: number,\n  limit: number\n): Promise<ObservationRow[]> {\n  await ensureObservationTables(pool);\n\n  const result = await pool.query<ObservationRow>(\n    `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n            tool_name, tool_input_summary,\n            files_read, files_modified, concepts,\n            content_hash, created_at\n     FROM pai_observations\n     WHERE project_id = $1\n     ORDER BY created_at DESC\n     LIMIT $2`,\n    [projectId, limit]\n  );\n\n  return result.rows;\n}\n\n/**\n * All observations for a specific session, ordered chronologically.\n */\nexport async function querySessionObservations(\n  pool: Pool,\n  sessionId: string\n): Promise<ObservationRow[]> {\n  await ensureObservationTables(pool);\n\n  const result = await pool.query<ObservationRow>(\n    `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n            tool_name, tool_input_summary,\n            files_read, files_modified, concepts,\n            content_hash, created_at\n     FROM pai_observations\n     WHERE session_id = $1\n     ORDER BY created_at ASC`,\n    [sessionId]\n  );\n\n  return result.rows;\n}\n\n// ---------------------------------------------------------------------------\n// Session summaries\n// ---------------------------------------------------------------------------\n\n/**\n * Upsert a session summary.  Uses ON CONFLICT on session_id so calling this\n * multiple times with updated content is safe.\n */\nexport async function storeSessionSummary(\n  pool: Pool,\n  summary: StoreSessionSummaryInput\n): Promise<void> {\n  await ensureObservationTables(pool);\n\n  await pool.query(\n    `INSERT INTO pai_session_summaries\n       (session_id, project_id, project_slug, request, investigated,\n        learned, completed, next_steps, observation_count)\n     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n     ON CONFLICT (session_id) DO UPDATE SET\n       project_id        = EXCLUDED.project_id,\n       project_slug      = EXCLUDED.project_slug,\n       request           = EXCLUDED.request,\n       investigated      = EXCLUDED.investigated,\n       learned           = EXCLUDED.learned,\n       completed         = EXCLUDED.completed,\n       next_steps        = EXCLUDED.next_steps,\n       observation_count = EXCLUDED.observation_count`,\n    [\n      summary.session_id,\n      summary.project_id ?? null,\n      summary.project_slug ?? null,\n      summary.request ?? null,\n      summary.investigated ?? null,\n      summary.learned ?? null,\n      summary.completed ?? null,\n      summary.next_steps ?? null,\n      summary.observation_count ?? 0,\n    ]\n  );\n}\n\n// ---------------------------------------------------------------------------\n// Skill telemetry — self-educating skill system, Phase 1 (capture)\n// ---------------------------------------------------------------------------\n\nexport interface SkillTelemetryRow {\n  id: number;\n  scope: string;\n  skill_name: string;\n  source: string;\n  status: string;\n  trigger_count: number;\n  accept_count: number;\n  first_triggered: Date;\n  last_triggered: Date;\n  context_projects: string[];\n  hash: string | null;\n  audit_status: string | null;\n  last_audited: Date | null;\n}\n\nexport interface RecordSkillInvocationInput {\n  skill_name: string;\n  /** 'local' | 'skills.sh' | repo slug */\n  source?: string;\n  /** governance seam — defaults to 'default' */\n  scope?: string;\n  /** project slug for context_projects rollup */\n  project_slug?: string | null;\n}\n\nexport interface QuerySkillTelemetryOptions {\n  scope?: string;\n  status?: string;\n  limit?: number;\n}\n\nconst SKILL_TELEMETRY_SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS pai_skill_telemetry (\n  id               SERIAL PRIMARY KEY,\n  scope            TEXT NOT NULL DEFAULT 'default',\n  skill_name       TEXT NOT NULL,\n  source           TEXT NOT NULL DEFAULT 'local',\n  status           TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('trial','active','archived')),\n  trigger_count    INTEGER NOT NULL DEFAULT 0,\n  accept_count     INTEGER NOT NULL DEFAULT 0,\n  first_triggered  TIMESTAMPTZ DEFAULT NOW(),\n  last_triggered   TIMESTAMPTZ DEFAULT NOW(),\n  context_projects JSONB DEFAULT '[]'::jsonb,\n  hash             TEXT,\n  audit_status     TEXT,\n  last_audited     TIMESTAMPTZ,\n  UNIQUE(scope, skill_name, source)\n);\n\nCREATE INDEX IF NOT EXISTS idx_skilltel_scope   ON pai_skill_telemetry(scope);\nCREATE INDEX IF NOT EXISTS idx_skilltel_status  ON pai_skill_telemetry(scope, status);\nCREATE INDEX IF NOT EXISTS idx_skilltel_last    ON pai_skill_telemetry(last_triggered DESC);\n`;\n\nlet _skillTelemetryEnsured = false;\n\nexport async function ensureSkillTelemetryTable(pool: Pool): Promise<void> {\n  if (_skillTelemetryEnsured) return;\n  await pool.query(SKILL_TELEMETRY_SCHEMA_SQL);\n  _skillTelemetryEnsured = true;\n}\n\n/**\n * Record a single skill invocation. Upserts on (scope, skill_name, source):\n * increments trigger_count, refreshes last_triggered, and dedup-merges the\n * project slug into context_projects.\n */\nexport async function recordSkillInvocation(\n  pool: Pool,\n  input: RecordSkillInvocationInput\n): Promise<void> {\n  await ensureSkillTelemetryTable(pool);\n\n  const scope = input.scope ?? 'default';\n  const source = input.source ?? 'local';\n  const proj = input.project_slug ?? null;\n  const initialProjects = JSON.stringify(proj ? [proj] : []);\n\n  await pool.query(\n    `INSERT INTO pai_skill_telemetry\n       (scope, skill_name, source, trigger_count, context_projects)\n     VALUES ($1, $2, $3, 1, $4::jsonb)\n     ON CONFLICT (scope, skill_name, source) DO UPDATE SET\n       trigger_count    = pai_skill_telemetry.trigger_count + 1,\n       last_triggered   = NOW(),\n       context_projects = CASE\n         WHEN $5::text IS NULL THEN pai_skill_telemetry.context_projects\n         WHEN pai_skill_telemetry.context_projects @> to_jsonb($5::text)\n           THEN pai_skill_telemetry.context_projects\n         ELSE pai_skill_telemetry.context_projects || to_jsonb($5::text)\n       END`,\n    [scope, input.skill_name, source, initialProjects, proj]\n  );\n}\n\n/**\n * List skill telemetry rows, most-triggered first.\n */\nexport async function querySkillTelemetry(\n  pool: Pool,\n  opts: QuerySkillTelemetryOptions = {}\n): Promise<SkillTelemetryRow[]> {\n  await ensureSkillTelemetryTable(pool);\n\n  const conditions: string[] = [];\n  const params: unknown[] = [];\n  let idx = 1;\n\n  conditions.push(`scope = $${idx++}`);\n  params.push(opts.scope ?? 'default');\n\n  if (opts.status !== undefined) {\n    conditions.push(`status = $${idx++}`);\n    params.push(opts.status);\n  }\n\n  const where = `WHERE ${conditions.join(' AND ')}`;\n  const limit = opts.limit ?? 100;\n  params.push(limit);\n\n  const result = await pool.query<SkillTelemetryRow>(\n    `SELECT id, scope, skill_name, source, status,\n            trigger_count, accept_count,\n            first_triggered, last_triggered,\n            context_projects, hash, audit_status, last_audited\n     FROM pai_skill_telemetry\n     ${where}\n     ORDER BY trigger_count DESC, last_triggered DESC\n     LIMIT $${idx}`,\n    params\n  );\n\n  return result.rows;\n}\n\n/**\n * Most recent session summaries for a project, ordered by created_at DESC.\n */\nexport async function queryRecentSummaries(\n  pool: Pool,\n  projectId: number,\n  limit: number\n): Promise<SessionSummaryRow[]> {\n  await ensureObservationTables(pool);\n\n  const result = await pool.query<SessionSummaryRow>(\n    `SELECT id, session_id, project_id, project_slug,\n            request, investigated, learned, completed, next_steps,\n            observation_count, created_at\n     FROM pai_session_summaries\n     WHERE project_id = $1\n     ORDER BY created_at DESC\n     LIMIT $2`,\n    [projectId, limit]\n  );\n\n  return result.rows;\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 { indexAll } from \"../../memory/indexer.js\";\nimport { readWorkersSection } from \"../../workers/config.js\";\nimport { keepaliveSecs, runKeepaliveBeat, type BeatMetrics } from \"../../workers/keepalive.js\";\nimport { workersLogDir } from \"../../workers/paths.js\";\nimport { runSupervisionTick, stallMinutesFromEnv } from \"../../workers/supervision.js\";\nimport { storeObservationWithProject } from \"../../observations/store.js\";\nimport { runSessionKeepaliveTick } from \"../session-keepalive.js\";\nimport type { PostgresBackendWithPool, SQLiteBackendWithDb } from \"./types.js\";\nimport {\n  registryDb,\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    if (storageBackend.backendType === \"sqlite\") {\n      const { SQLiteBackend } = await import(\"../../storage/sqlite.js\");\n      if (storageBackend instanceof SQLiteBackend) {\n        const db = (storageBackend as SQLiteBackendWithDb).getRawDb();\n        const { projects, result } = await indexAll(db, registryDb);\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      }\n    } else {\n      const { indexAllWithBackend } = await import(\"../../memory/indexer-backend.js\");\n      const { projects, result } = await indexAllWithBackend(storageBackend, registryDb);\n      const elapsed = Date.now() - t0;\n      setLastIndexTime(Date.now());\n      process.stderr.write(\n        `[pai-daemon] Index complete (postgres): ${projects} projects, ` +\n          `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n          `(${elapsed}ms)\\n`\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 = registryDb\n      .prepare(\"SELECT id FROM projects WHERE root_path = ?\")\n      .get(daemonConfig.vaultPath) as { id: number } | undefined;\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.backendType !== \"postgres\") {\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 = registryDb\n        .prepare(\"SELECT id, slug FROM projects WHERE status = 'active'\")\n        .all() as Array<{ id: number; slug: string }>;\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    let vaultEmbedCount = 0;\n    if (daemonConfig.vaultPath) {\n      try {\n        const { SQLiteBackend } = await import(\"../../storage/sqlite.js\");\n        const { openFederation } = await import(\"../../memory/db.js\");\n        const federationDb = openFederation();\n        const vaultSqliteBackend = new SQLiteBackend(federationDb);\n\n        const vaultProjectNames = new Map(projectNames);\n        if (!vaultProjectNames.has(999)) {\n          vaultProjectNames.set(999, \"obsidian-vault\");\n        }\n\n        vaultEmbedCount = await embedChunksWithBackend(\n          vaultSqliteBackend,\n          () => shutdownRequested,\n          vaultProjectNames,\n          { maxMillis: 45_000 },\n        );\n\n        try { federationDb.close(); } catch { /* ignore */ }\n\n        if (vaultEmbedCount > 0) {\n          process.stderr.write(\n            `[pai-daemon] Vault embed pass complete: ${vaultEmbedCount} vault chunks embedded\\n`\n          );\n        }\n      } catch (ve) {\n        const vmsg = ve instanceof Error ? ve.message : String(ve);\n        process.stderr.write(`[pai-daemon] Vault embed error: ${vmsg}\\n`);\n      }\n    }\n\n    const elapsed = Date.now() - t0;\n    setLastEmbedTime(Date.now());\n    process.stderr.write(\n      `[pai-daemon] Embed pass complete: ${count} postgres chunks + ${vaultEmbedCount} vault 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    })\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 sqliteNote = () => {\n    const pool = (storageBackend as PostgresBackendWithPool | undefined)?.getPool?.();\n    if (pool) return pool;\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 null;\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    const pool = sqliteNote();\n    if (!pool) return; // ledger line already written by the beat itself\n    try {\n      await storeObservationWithProject(registryDb, pool, {\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 { registryDb, storageBackend, daemonConfig } from \"./state.js\";\nimport { openFederation } from \"../../memory/db.js\";\nimport type { PostgresBackendWithPool } from \"./types.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(registryDb, storageBackend, p as Parameters<typeof toolMemorySearch>[2]);\n\n    case \"memory_get\":\n      return toolMemoryGet(registryDb, p as Parameters<typeof toolMemoryGet>[1]);\n\n    case \"project_info\":\n      return toolProjectInfo(registryDb, p as Parameters<typeof toolProjectInfo>[1]);\n\n    case \"project_list\":\n      return toolProjectList(registryDb, p as Parameters<typeof toolProjectList>[1]);\n\n    case \"session_list\":\n      return toolSessionList(registryDb, p as Parameters<typeof toolSessionList>[1]);\n\n    case \"registry_search\":\n      return toolRegistrySearch(registryDb, p as Parameters<typeof toolRegistrySearch>[1]);\n\n    case \"project_detect\":\n      return toolProjectDetect(registryDb, p as Parameters<typeof toolProjectDetect>[1]);\n\n    case \"project_health\":\n      return toolProjectHealth(registryDb, p as Parameters<typeof toolProjectHealth>[1]);\n\n    case \"project_todo\":\n      return toolProjectTodo(registryDb, p as Parameters<typeof toolProjectTodo>[1]);\n\n    case \"memory_wakeup\":\n      return toolMemoryWakeup(registryDb, p as Parameters<typeof toolMemoryWakeup>[1]);\n\n    case \"memory_taxonomy\":\n      return toolMemoryTaxonomy(registryDb, storageBackend, p as Parameters<typeof toolMemoryTaxonomy>[2]);\n\n    case \"topic_check\":\n      return detectTopicShift(\n        registryDb,\n        storageBackend,\n        p as Parameters<typeof detectTopicShift>[2]\n      );\n\n    case \"session_auto_route\":\n      return toolSessionRoute(\n        registryDb,\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      const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n      return handleGraphClusters(pgPool, storageBackend, p as Parameters<typeof handleGraphClusters>[2]);\n    }\n\n    case \"graph_neighborhood\": {\n      const { handleGraphNeighborhood } = await import(\"../../graph/neighborhood.js\");\n      const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n      return handleGraphNeighborhood(pgPool, storageBackend, p as Parameters<typeof handleGraphNeighborhood>[2]);\n    }\n\n    case \"graph_note_context\": {\n      const { handleGraphNoteContext } = await import(\"../../graph/note-context.js\");\n      const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n      return handleGraphNoteContext(pgPool, storageBackend, p as Parameters<typeof handleGraphNoteContext>[2]);\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      const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n      if (!pgPool) {\n        throw new Error(`${method} requires a Postgres storage backend`);\n      }\n      switch (method) {\n        case \"kg_add\":           return toolKgAdd(pgPool, p as Parameters<typeof toolKgAdd>[1]);\n        case \"kg_query\":         return toolKgQuery(pgPool, p as Parameters<typeof toolKgQuery>[1]);\n        case \"kg_invalidate\":    return toolKgInvalidate(pgPool, p as Parameters<typeof toolKgInvalidate>[1]);\n        case \"kg_contradictions\": return toolKgContradictions(pgPool, 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(registryDb, storageBackend, p as Parameters<typeof toolMemoryTunnels>[2]);\n    }\n\n    case \"memory_feedback\": {\n      // MR2: feedback weight loop — uses federation.db (SQLite) directly\n      const federationDb = openFederation();\n      try {\n        return await toolMemoryFeedback(federationDb, p as Parameters<typeof toolMemoryFeedback>[1]);\n      } finally {\n        federationDb.close();\n      }\n    }\n\n    case \"memory_kg_search\": {\n      // MR1: graph-completion retrieval — federation.db (SQLite) + Postgres pool\n      const federationDb = openFederation();\n      const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n      if (!pgPool) {\n        federationDb.close();\n        throw new Error(\"memory_kg_search requires a Postgres storage backend for KG triple expansion\");\n      }\n      try {\n        return await toolMemoryKgSearch(federationDb, pgPool, p as Parameters<typeof toolMemoryKgSearch>[2]);\n      } finally {\n        federationDb.close();\n      }\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, PostgresBackendWithPool } 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  ensureObservationTables,\n  storeObservationWithProject,\n  queryObservations,\n  queryRecentObservations,\n  storeSessionSummary,\n  recordSkillInvocation,\n  querySkillTelemetry,\n} from \"../../observations/store.js\";\nimport {\n  registryDb,\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 = (\n          registryDb\n            .prepare(\"SELECT COUNT(*) AS n FROM projects\")\n            .get() as { n: number }\n        ).n;\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 storeObservationWithProject(registryDb, pool, {\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 = registryDb.prepare(\n          \"SELECT slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n        ).get(p.cwd) as { slug: string } | undefined;\n        if (row) project_slug = row.slug;\n      }\n\n      await recordSkillInvocation(pool, {\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 querySkillTelemetry(pool, {\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 queryObservations(pool, {\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 = registryDb.prepare(\n          \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n        ).get(p.cwd) as { id: number; slug: string } | undefined;\n        if (row) {\n          resolvedProjectId = row.id;\n          resolvedProjectSlug = row.slug;\n        }\n      }\n\n      let rows;\n      if (resolvedProjectId !== undefined) {\n        rows = await queryRecentObservations(pool, resolvedProjectId, limit);\n      } else {\n        rows = await queryObservations(pool, { 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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 = registryDb.prepare(\n          \"SELECT id FROM projects WHERE slug = ?\"\n        ).get(p.project_slug) as { id: number } | undefined;\n        projectId = row?.id;\n      }\n\n      const rows = await queryObservations(pool, {\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\n      sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n      socket.end();\n      return;\n    }\n    try {\n      await ensureObservationTables(pool);\n      const [totalRes, byTypeRes, byProjectRes, recentRes] = await Promise.all([\n        pool.query<{ count: string }>(\"SELECT COUNT(*) as count FROM pai_observations\"),\n        pool.query<{ type: string; count: string }>(\n          \"SELECT type, COUNT(*) as count FROM pai_observations GROUP BY type ORDER BY count DESC\"\n        ),\n        pool.query<{ project_slug: string | null; count: string }>(\n          \"SELECT project_slug, COUNT(*) as count FROM pai_observations GROUP BY project_slug ORDER BY count DESC LIMIT 15\"\n        ),\n        pool.query<{ created_at: string }>(\n          \"SELECT created_at FROM pai_observations ORDER BY created_at DESC LIMIT 1\"\n        ),\n      ]);\n\n      sendResponse(socket, {\n        id,\n        ok: true,\n        result: {\n          total: parseInt(totalRes.rows[0]?.count ?? \"0\", 10),\n          by_type: byTypeRes.rows.map(r => ({ type: r.type, count: parseInt(r.count, 10) })),\n          by_project: byProjectRes.rows.map(r => ({ project_slug: r.project_slug, count: parseInt(r.count, 10) })),\n          most_recent: recentRes.rows[0]?.created_at ?? null,\n        },\n      });\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    const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n    if (!pool) {\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 = registryDb.prepare(\n          \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n        ).get(p.cwd) as { id: number; slug: string } | undefined;\n        if (row) {\n          resolvedProjectId = row.id;\n          resolvedProjectSlug = row.slug;\n        }\n      }\n\n      await storeSessionSummary(pool, {\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 * 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 { openRegistry } from \"../../registry/db.js\";\nimport { createStorageBackend } 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  setRegistryDb,\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\";\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  try {\n    setRegistryDb(openRegistry());\n    process.stderr.write(\"[pai-daemon] Registry database opened.\\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.backendType === \"postgres\") {\n        startEmbedScheduler();\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":"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"}