{"version":3,"file":"daemon-9AL6Pwh-.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/daemon/daemon/scheduler.ts","../src/notifications/providers/ntfy.ts","../src/notifications/providers/whatsapp.ts","../src/notifications/providers/macos.ts","../src/notifications/providers/cli.ts","../src/notifications/router.ts","../src/observations/store.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.ts","../src/daemon/daemon/server.ts","../src/daemon/daemon.ts"],"sourcesContent":["/**\n * config.ts — Notification config persistence helpers\n *\n * Reads and writes the `notifications` section of ~/.config/pai/config.json.\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 {\n  existsSync,\n  readFileSync,\n  writeFileSync,\n  mkdirSync,\n} from \"node:fs\";\nimport {\n  CONFIG_FILE,\n  CONFIG_DIR,\n  expandHome,\n} 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  if (!existsSync(CONFIG_FILE)) {\n    return { ...DEFAULT_NOTIFICATION_CONFIG };\n  }\n\n  let raw: string;\n  try {\n    raw = readFileSync(CONFIG_FILE, \"utf-8\");\n  } catch {\n    return { ...DEFAULT_NOTIFICATION_CONFIG };\n  }\n\n  let parsed: Record<string, unknown>;\n  try {\n    parsed = JSON.parse(raw) as Record<string, unknown>;\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/pai/config.json. Creates the file if it does not exist.\n */\nexport function saveNotificationConfig(config: NotificationConfig): void {\n  // Ensure the config dir exists\n  if (!existsSync(CONFIG_DIR)) {\n    mkdirSync(CONFIG_DIR, { recursive: true });\n  }\n\n  // Read current full config\n  let full: Record<string, unknown> = {};\n  if (existsSync(CONFIG_FILE)) {\n    try {\n      full = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<\n        string,\n        unknown\n      >;\n    } catch {\n      // Start fresh if the file is unreadable\n    }\n  }\n\n  // Replace the notifications section\n  full[\"notifications\"] = config;\n\n  writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2) + \"\\n\", \"utf-8\");\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 * 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 type { 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} 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  setTimeout(() => {\n    runIndex()\n      .then(() => runVaultIndex())\n      .catch((e) => {\n        process.stderr.write(`[pai-daemon] Startup index error: ${e}\\n`);\n      });\n  }, 2_000);\n\n  const timer = setInterval(() => {\n    runIndex()\n      .then(() => runVaultIndex())\n      .catch((e) => {\n        process.stderr.write(`[pai-daemon] Scheduled index error: ${e}\\n`);\n      });\n  }, intervalMs);\n\n  if (timer.unref) timer.unref();\n  setIndexSchedulerTimer(timer);\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    const count = await embedChunksWithBackend(storageBackend, () => shutdownRequested, projectNames);\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        );\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  setTimeout(() => {\n    runEmbed().catch((e) => {\n      process.stderr.write(`[pai-daemon] Startup embed error: ${e}\\n`);\n    });\n  }, 60_000);\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 * ntfy.ts — ntfy.sh notification provider\n *\n * Sends notifications to a configured ntfy.sh topic via HTTP.\n */\n\nimport type {\n  NotificationProvider,\n  NotificationPayload,\n  NotificationConfig,\n} from \"../types.js\";\n\nexport class NtfyProvider implements NotificationProvider {\n  readonly channelId = \"ntfy\" as const;\n\n  async send(\n    payload: NotificationPayload,\n    config: NotificationConfig\n  ): Promise<boolean> {\n    const cfg = config.channels.ntfy;\n    if (!cfg.enabled || !cfg.url) return false;\n\n    try {\n      const headers: Record<string, string> = {\n        \"Content-Type\": \"text/plain; charset=utf-8\",\n      };\n\n      if (payload.title) {\n        headers[\"Title\"] = payload.title;\n      }\n\n      if (cfg.priority && cfg.priority !== \"default\") {\n        headers[\"Priority\"] = cfg.priority;\n      }\n\n      const response = await fetch(cfg.url, {\n        method: \"POST\",\n        headers,\n        body: payload.message,\n      });\n\n      return response.ok;\n    } catch {\n      return false;\n    }\n  }\n}\n","/**\n * whatsapp.ts — WhatsApp notification provider (via Whazaa MCP)\n *\n * Sends notifications via the Whazaa Unix Domain Socket IPC protocol.\n * Falls back gracefully if Whazaa is not running.\n *\n * Whazaa IPC socket: /tmp/whazaa.sock (standard Whazaa path)\n *\n * We use the same connect-per-call pattern as PaiClient to avoid\n * requiring any persistent connection state.\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n  NotificationProvider,\n  NotificationPayload,\n  NotificationConfig,\n} from \"../types.js\";\n\nconst WHAZAA_SOCKET = \"/tmp/whazaa.sock\";\nconst WHAZAA_TIMEOUT_MS = 10_000;\n\n/**\n * Send a single IPC call to the Whazaa socket.\n * Returns true on success, false if Whazaa is not available or errors.\n */\nfunction callWhazaa(\n  method: string,\n  params: Record<string, unknown>\n): Promise<boolean> {\n  return new Promise((resolve) => {\n    let done = false;\n    let buffer = \"\";\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    function finish(ok: boolean): void {\n      if (done) return;\n      done = true;\n      if (timer) { clearTimeout(timer); timer = null; }\n      try { socket?.destroy(); } catch { /* ignore */ }\n      resolve(ok);\n    }\n\n    const socket = connect(WHAZAA_SOCKET, () => {\n      const request = {\n        jsonrpc: \"2.0\",\n        id: randomUUID(),\n        method,\n        params,\n      };\n      socket.write(JSON.stringify(request) + \"\\n\");\n    });\n\n    socket.on(\"data\", (chunk: Buffer) => {\n      buffer += chunk.toString();\n      const nl = buffer.indexOf(\"\\n\");\n      if (nl === -1) return;\n      try {\n        const resp = JSON.parse(buffer.slice(0, nl)) as { error?: unknown };\n        finish(!resp.error);\n      } catch {\n        finish(false);\n      }\n    });\n\n    socket.on(\"error\", () => finish(false));\n    socket.on(\"end\", () => finish(false));\n\n    timer = setTimeout(() => finish(false), WHAZAA_TIMEOUT_MS);\n  });\n}\n\nexport class WhatsAppProvider implements NotificationProvider {\n  readonly channelId = \"whatsapp\" as const;\n\n  async send(\n    payload: NotificationPayload,\n    config: NotificationConfig\n  ): Promise<boolean> {\n    const cfg = config.channels.whatsapp;\n    if (!cfg.enabled) return false;\n\n    const isVoiceMode = config.mode === \"voice\" || config.channels.voice.enabled;\n\n    const params: Record<string, unknown> = {\n      message: payload.message,\n    };\n\n    if (cfg.recipient) {\n      params.recipient = cfg.recipient;\n    }\n\n    if (isVoiceMode && config.mode === \"voice\") {\n      const voiceName = config.channels.voice.voiceName ?? \"bm_george\";\n      params.voice = voiceName;\n    }\n\n    return callWhazaa(\"whatsapp_send\", params);\n  }\n}\n","/**\n * macos.ts — macOS notification provider\n *\n * Uses the `osascript` command to display a macOS system notification.\n * Non-blocking: spawns the process and returns success without waiting.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type {\n  NotificationProvider,\n  NotificationPayload,\n  NotificationConfig,\n} from \"../types.js\";\n\nexport class MacOsProvider implements NotificationProvider {\n  readonly channelId = \"macos\" as const;\n\n  async send(\n    payload: NotificationPayload,\n    config: NotificationConfig\n  ): Promise<boolean> {\n    const cfg = config.channels.macos;\n    if (!cfg.enabled) return false;\n\n    try {\n      const title = payload.title ?? \"PAI\";\n      // Escape single quotes in title and message for AppleScript\n      const safeTitle = title.replace(/'/g, \"\\\\'\");\n      const safeMessage = payload.message.replace(/'/g, \"\\\\'\");\n\n      const script = `display notification \"${safeMessage}\" with title \"${safeTitle}\"`;\n\n      return new Promise((resolve) => {\n        const child = spawn(\"osascript\", [\"-e\", script], {\n          detached: true,\n          stdio: \"ignore\",\n        });\n        child.unref();\n\n        // Give the process a moment to start, then assume success.\n        // osascript is always present on macOS.\n        child.on(\"error\", () => resolve(false));\n\n        // Resolve after a short timeout — osascript exits quickly\n        setTimeout(() => resolve(true), 200);\n      });\n    } catch {\n      return false;\n    }\n  }\n}\n","/**\n * cli.ts — CLI notification provider\n *\n * Writes notifications to the PAI daemon log (stderr).\n * Always succeeds — it's the fallback channel.\n */\n\nimport type {\n  NotificationProvider,\n  NotificationPayload,\n  NotificationConfig,\n} from \"../types.js\";\n\nexport class CliProvider implements NotificationProvider {\n  readonly channelId = \"cli\" as const;\n\n  async send(\n    payload: NotificationPayload,\n    _config: NotificationConfig\n  ): Promise<boolean> {\n    const prefix = `[pai-notify:${payload.event}]`;\n    const title = payload.title ? ` ${payload.title}:` : \"\";\n    process.stderr.write(`${prefix}${title} ${payload.message}\\n`);\n    return true;\n  }\n}\n","/**\n * router.ts — Notification router\n *\n * Routes notification events to the appropriate channels based on the\n * current mode and per-event routing config.\n *\n * Channel providers are instantiated lazily and cached.\n */\n\nimport type {\n  NotificationPayload,\n  NotificationConfig,\n  NotificationProvider,\n  ChannelId,\n  SendResult,\n  NotificationMode,\n} from \"./types.js\";\nimport { NtfyProvider } from \"./providers/ntfy.js\";\nimport { WhatsAppProvider } from \"./providers/whatsapp.js\";\nimport { MacOsProvider } from \"./providers/macos.js\";\nimport { CliProvider } from \"./providers/cli.js\";\n\n// ---------------------------------------------------------------------------\n// Provider registry (singletons — stateless, safe to reuse)\n// ---------------------------------------------------------------------------\n\nconst PROVIDERS: Record<ChannelId, NotificationProvider> = {\n  ntfy:      new NtfyProvider(),\n  whatsapp:  new WhatsAppProvider(),\n  macos:     new MacOsProvider(),\n  voice:     new WhatsAppProvider(), // Voice uses WhatsApp TTS; handled in WhatsAppProvider\n  cli:       new CliProvider(),\n};\n\n// ---------------------------------------------------------------------------\n// Channel resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Given the current config, resolve which channels should receive a\n * notification for the given event type.\n *\n * Mode overrides:\n *   \"off\"       → no channels\n *   \"auto\"      → use routing table, filtered by enabled channels\n *   \"voice\"     → whatsapp (TTS enabled in provider)\n *   \"whatsapp\"  → whatsapp\n *   \"ntfy\"      → ntfy\n *   \"macos\"     → macos\n *   \"cli\"       → cli\n */\nfunction resolveChannels(\n  config: NotificationConfig,\n  event: NotificationPayload[\"event\"]\n): ChannelId[] {\n  const { mode, channels, routing } = config;\n\n  if (mode === \"off\") return [];\n\n  // Non-auto modes: force a single channel\n  const modeToChannel: Partial<Record<NotificationMode, ChannelId>> = {\n    voice:     \"whatsapp\",  // WhatsAppProvider checks mode === \"voice\" for TTS\n    whatsapp:  \"whatsapp\",\n    ntfy:      \"ntfy\",\n    macos:     \"macos\",\n    cli:       \"cli\",\n  };\n\n  if (mode !== \"auto\") {\n    const ch = modeToChannel[mode];\n    if (!ch) return [];\n    // Check the channel is enabled\n    const cfg = channels[ch];\n    if (cfg && !cfg.enabled) return [ch]; // Still send — mode override bypasses enabled check\n    return [ch];\n  }\n\n  // Auto mode: use routing table, filter to enabled channels\n  const candidates = routing[event] ?? [];\n  return candidates.filter((ch) => {\n    const cfg = channels[ch];\n    // \"voice\" channel is virtual — it overlaps with whatsapp.\n    // Skip \"voice\" as an independent channel; voice is handled by checking config.mode.\n    if (ch === \"voice\") return false;\n    return cfg?.enabled === true;\n  });\n}\n\n// ---------------------------------------------------------------------------\n// Router\n// ---------------------------------------------------------------------------\n\n/**\n * Route a notification to the appropriate channels.\n *\n * Sends to all resolved channels in parallel.\n * Individual channel failures are non-fatal and logged to stderr.\n *\n * @param payload  The notification to send\n * @param config   The current notification config (from daemon state)\n */\nexport async function routeNotification(\n  payload: NotificationPayload,\n  config: NotificationConfig\n): Promise<SendResult> {\n  const channels = resolveChannels(config, payload.event);\n\n  if (channels.length === 0) {\n    return {\n      channelsAttempted: [],\n      channelsSucceeded: [],\n      channelsFailed: [],\n      mode: config.mode,\n    };\n  }\n\n  const results = await Promise.allSettled(\n    channels.map(async (ch) => {\n      const provider = PROVIDERS[ch];\n      const ok = await provider.send(payload, config);\n      if (!ok) {\n        process.stderr.write(\n          `[pai-notify] Channel ${ch} failed for event ${payload.event}\\n`\n        );\n      }\n      return { ch, ok };\n    })\n  );\n\n  const succeeded: ChannelId[] = [];\n  const failed: ChannelId[] = [];\n\n  for (const r of results) {\n    if (r.status === \"fulfilled\") {\n      if (r.value.ok) {\n        succeeded.push(r.value.ch);\n      } else {\n        failed.push(r.value.ch);\n      }\n    } else {\n      // Provider threw — treat as failure\n      failed.push(channels[results.indexOf(r)]);\n    }\n  }\n\n  return {\n    channelsAttempted: channels,\n    channelsSucceeded: succeeded,\n    channelsFailed: failed,\n    mode: config.mode,\n  };\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// 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 * 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  storeObservation,\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\";\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      },\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      let project_id: number | null = null;\n      let project_slug: string | null = null;\n      if (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          project_id = row.id;\n          project_slug = row.slug;\n        }\n      }\n\n      await ensureObservationTables(pool);\n      const insertedId = await storeObservation(pool, {\n        session_id: p.session_id,\n        project_id,\n        project_slug,\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 ?? null,\n        files_read: p.files_read ?? [],\n        files_modified: p.files_modified ?? [],\n        concepts: p.concepts ?? [],\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  storageBackend,\n} from \"./state.js\";\nimport { startIndexScheduler, startEmbedScheduler, startRegistryScanScheduler } 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  // 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\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","/**\n * Shim — re-exports serve() from daemon/ directory so existing importers\n * continue to work without modification. See daemon/index.ts.\n */\nexport { serve } from \"./daemon/index.js\";\n"],"mappings":"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"}