@tekmidian/pai 0.66.0 → 0.66.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chain-D4PuQ2c-.mjs → chain-CZfKIEq9.mjs} +2 -2
- package/dist/{chain-D4PuQ2c-.mjs.map → chain-CZfKIEq9.mjs.map} +1 -1
- package/dist/cli/index.mjs +5 -5
- package/dist/cli/program.mjs +5 -5
- package/dist/daemon/index.mjs +3 -3
- package/dist/{daemon-BEoVgVfJ.mjs → daemon-DXlaVCkw.mjs} +3 -3
- package/dist/{daemon-BEoVgVfJ.mjs.map → daemon-DXlaVCkw.mjs.map} +1 -1
- package/dist/{daemon-DPAYU9hp.mjs → daemon-DyORmxZu.mjs} +3 -3
- package/dist/daemon-mcp/index.mjs +6 -4
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{fallback-B5svk75f.mjs → fallback-CIiq2Raw.mjs} +6 -4
- package/dist/fallback-CIiq2Raw.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +5 -0
- package/dist/hooks/worker-guard.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +1 -0
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/{planner-EJPZnnif.mjs → planner-Dm5Acyo1.mjs} +3 -3
- package/dist/{planner-EJPZnnif.mjs.map → planner-Dm5Acyo1.mjs.map} +1 -1
- package/dist/{program-DSETzrWg.mjs → program-JJ0TwSHI.mjs} +9 -6
- package/dist/{program-DSETzrWg.mjs.map → program-JJ0TwSHI.mjs.map} +1 -1
- package/dist/{run-Z4akoa8G.mjs → run-BCpbxqA5.mjs} +168 -6
- package/dist/run-BCpbxqA5.mjs.map +1 -0
- package/dist/{session-keepalive-BDhytXEo.mjs → session-keepalive-DxjTHGcK.mjs} +2 -2
- package/dist/{session-keepalive-BDhytXEo.mjs.map → session-keepalive-DxjTHGcK.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
- package/src/hooks/ts/lib/worker-guard.ts +9 -0
- package/dist/fallback-B5svk75f.mjs.map +0 -1
- package/dist/run-Z4akoa8G.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon-BEoVgVfJ.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/workers/keepalive.ts","../src/workers/supervision.ts","../src/daemon/daemon/scheduler.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.ts","../src/storage/postgres/fts-migration.ts","../src/daemon/daemon/server.ts"],"sourcesContent":["/**\n * config.ts — Notification config persistence helpers\n *\n * Reads and writes the `notifications` section of CONFIG_FILE (see\n * src/daemon/config.ts's paiConfigFilePath).\n * Deep-merges with defaults so partial configs work fine.\n *\n * This module is intentionally separate from the daemon's config loader\n * so it can be used standalone (e.g. from CLI commands).\n */\n\nimport { expandHome, readMainConfigRaw, writeMainConfigRaw } from \"../daemon/config.js\";\nimport type {\n NotificationConfig,\n ChannelConfigs,\n RoutingTable,\n NotificationMode,\n} from \"./types.js\";\nimport {\n DEFAULT_NOTIFICATION_CONFIG,\n DEFAULT_CHANNELS,\n DEFAULT_ROUTING,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Deep merge helper (same approach as daemon/config.ts)\n// ---------------------------------------------------------------------------\n\nfunction deepMerge<T extends object>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n const srcVal = source[key];\n if (srcVal === undefined || srcVal === null) continue;\n const tgtVal = (target as Record<string, unknown>)[key];\n if (\n typeof srcVal === \"object\" &&\n !Array.isArray(srcVal) &&\n typeof tgtVal === \"object\" &&\n tgtVal !== null &&\n !Array.isArray(tgtVal)\n ) {\n (result as Record<string, unknown>)[key] = deepMerge(\n tgtVal as object,\n srcVal as Record<string, unknown>\n );\n } else {\n (result as Record<string, unknown>)[key] = srcVal;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Load\n// ---------------------------------------------------------------------------\n\n/**\n * Load the notification config from the PAI config file.\n * Returns defaults merged with any stored values.\n */\nexport function loadNotificationConfig(): NotificationConfig {\n let parsed: Record<string, unknown>;\n try {\n parsed = readMainConfigRaw();\n } catch {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n const stored = parsed[\"notifications\"];\n if (!stored || typeof stored !== \"object\") {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n return deepMerge(\n DEFAULT_NOTIFICATION_CONFIG,\n stored as Record<string, unknown>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Save\n// ---------------------------------------------------------------------------\n\n/**\n * Persist the notification config by merging it into the existing\n * CONFIG_FILE. Creates the file if it does not exist.\n */\nexport function saveNotificationConfig(config: NotificationConfig): void {\n let full: Record<string, unknown> = {};\n try {\n full = readMainConfigRaw();\n } catch {\n // Start fresh if the file is unreadable\n }\n\n // Replace the notifications section\n full[\"notifications\"] = config;\n\n writeMainConfigRaw(full);\n}\n\n// ---------------------------------------------------------------------------\n// Patch helpers (used by the set command)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a partial update to the current notification config and persist it.\n * Returns the new merged config.\n */\nexport function patchNotificationConfig(patch: {\n mode?: NotificationMode;\n channels?: Partial<Partial<ChannelConfigs>>;\n routing?: Partial<RoutingTable>;\n}): NotificationConfig {\n const current = loadNotificationConfig();\n\n if (patch.mode !== undefined) {\n current.mode = patch.mode;\n }\n\n if (patch.channels) {\n current.channels = deepMerge(\n current.channels,\n patch.channels as Record<string, unknown>\n );\n }\n\n if (patch.routing) {\n current.routing = deepMerge(\n current.routing,\n patch.routing as Record<string, unknown>\n );\n }\n\n saveNotificationConfig(current);\n return current;\n}\n\n// Re-export defaults for convenience\nexport { DEFAULT_NOTIFICATION_CONFIG, DEFAULT_CHANNELS, DEFAULT_ROUTING };\nexport { expandHome };\n","/**\n * keepalive.ts — one cache-keepalive beat, shared by the daemon scheduler and\n * the probe script.\n *\n * The provider (GLM anthropic-compat endpoint) caches the worker prefix\n * implicitly — no cache_control blocks — so a trivial single-turn worker spawn\n * through the very same runWorker path re-arms that cache for the next real\n * worker: identical env, identical tool grants, identical WORKER_CONTRACT_PROMPT\n * by construction, because it IS a worker spawn.\n *\n * Measured 2026-09-18 (docs/cache-keepalive.md): the implicit cache only\n * serves spawns within seconds of the previous identical request and never\n * survived a 60 s beat cadence in the armed proof — hence the knob defaults\n * to off and arming it is the operator's explicit call.\n *\n * Deliberately NOT a hand-built claude invocation: duplicated prefix\n * construction is how this repo has shipped real breakage before (probeResume\n * x3, archiver x2). Everything flows through runWorker.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { runWorker, type StreamEvent } from \"./run.js\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { eventsPath, ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatus } from \"./status.js\";\n\n/**\n * The trivial prompt every cache beat (and probe) carries. Kept in one place\n * so the heartbeat arms exactly the prefix a verifying probe measures.\n */\nexport const HEARTBEAT_PROMPT = \"Reply with exactly one word: pong.\";\n\n/** The worker class a beat runs as — the cheapest spawn the config defines. */\nexport const KEEPALIVE_CLASS = \"simple\";\n\n/** Worker label of every beat, so `pai worker ps` can tell them apart. */\nexport const KEEPALIVE_LABEL = \"cache-keepalive\";\n\n/** Cadence a keepalive runs at for this config; 0 keeps it off. */\nexport function keepaliveSecs(workers: { enabled: boolean; cacheKeepaliveSecs: number }): number {\n return workers.enabled ? workers.cacheKeepaliveSecs : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Transcript parsing — the only real usage on this endpoint is the final\n// result event (per-turn assistant usage is zeroed), so a beat and a probe\n// both read exactly that line.\n// ---------------------------------------------------------------------------\n\n/**\n * The LAST `type:\"result\"` event of a worker transcript, or null when the\n * file is missing, unreadable, or carries no result line (a failed run can\n * end without one).\n */\nexport function parseLastResultEvent(path: string): StreamEvent | null {\n let text: string;\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n let found: StreamEvent | null = null;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(\"{\")) continue;\n let e: StreamEvent;\n try {\n e = JSON.parse(line) as StreamEvent;\n } catch {\n continue;\n }\n if (e.type === \"result\") found = e;\n }\n return found;\n}\n\n/** One worker's cache metrics, as reported by its final result event. */\nexport interface BeatMetrics {\n id: string;\n ok: boolean;\n rc: number | null;\n provider: string | null;\n model: string | null;\n input_tokens: number | null;\n output_tokens: number | null;\n cache_read_input_tokens: number | null;\n cache_creation_input_tokens: number | null;\n duration_api_ms: number | null;\n duration_ms: number | null;\n num_turns: number | null;\n}\n\n/**\n * Metrics of one run from its final result event. Single-turn runs make the\n * cumulative result usage identical to first-turn usage, so a warm cache\n * shows up directly as cache_read_input_tokens > 0.\n */\nexport function extractBeatMetrics(\n m: Omit<BeatMetrics, \"input_tokens\" | \"output_tokens\" | \"cache_read_input_tokens\" | \"cache_creation_input_tokens\" | \"duration_api_ms\" | \"duration_ms\" | \"num_turns\">,\n ev: StreamEvent | null\n): BeatMetrics {\n const u = ev?.usage;\n return {\n ...m,\n input_tokens: u?.input_tokens ?? null,\n output_tokens: u?.output_tokens ?? null,\n cache_read_input_tokens: u?.cache_read_input_tokens ?? null,\n cache_creation_input_tokens: u?.cache_creation_input_tokens ?? null,\n duration_api_ms: ev?.duration_api_ms ?? null,\n duration_ms: ev?.duration_ms ?? null,\n num_turns: ev?.num_turns ?? null,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The beat\n// ---------------------------------------------------------------------------\n\nexport interface BeatOptions {\n /** Config path override (tests never touch the live config). */\n configPath?: string;\n /** Preset worker id (tests); a fresh stamp when omitted. */\n id?: string;\n}\n\n/** True while a beat's worker is still running — the overlap guard reads it. */\nlet beatInFlight = false;\n\nexport function beatBusy(): boolean {\n return beatInFlight;\n}\n\n/** `keepalive-<YYYYMMDD-HHMMSS>` — beats are minutes apart, seconds suffice. */\nexport function beatId(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `keepalive-${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}` +\n `-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`\n );\n}\n\n/**\n * One heartbeat: a single-turn trivial worker through the exact worker spawn\n * path, its result metrics parsed from the transcript, one WORKER-KEEPALIVE\n * ledger line. Returns null when another beat is still running (overlap\n * guard) — a skipped beat is free, a queue of them is not.\n *\n * The beat runs in the workers logDir: no session renders its status line\n * there, so the spawn adopts no spawnerSession and no iTerm identity, and\n * worker supervision has no owner to notify — heartbeats are infrastructure,\n * not somebody's workers.\n */\nexport async function runKeepaliveBeat(opts: BeatOptions = {}): Promise<BeatMetrics | null> {\n if (beatInFlight) return null;\n beatInFlight = true;\n try {\n const { workers: config } = readWorkersSection(opts.configPath);\n const logDir = workersLogDir(config);\n const id = opts.id ?? beatId();\n const rc = await runWorker({\n claudeArgs: [\"-p\", HEARTBEAT_PROMPT],\n className: KEEPALIVE_CLASS,\n label: KEEPALIVE_LABEL,\n noPane: true,\n quiet: true,\n worktreeFlag: false,\n id,\n cwd: logDir,\n });\n const status = loadStatus(logDir, id);\n const metrics = extractBeatMetrics(\n {\n id,\n ok: rc === 0,\n rc,\n provider: status?.provider ?? null,\n model: status?.model ?? null,\n },\n parseLastResultEvent(eventsPath(logDir, id))\n );\n appendLedger(ledgerPath(logDir), \"WORKER-KEEPALIVE\", {\n id,\n provider: metrics.provider,\n model: metrics.model,\n input_tokens: metrics.input_tokens,\n cache_read_input_tokens: metrics.cache_read_input_tokens,\n cache_creation_input_tokens: metrics.cache_creation_input_tokens,\n duration_api_ms: metrics.duration_api_ms,\n });\n return metrics;\n } finally {\n beatInFlight = false;\n }\n}\n","/**\n * supervision.ts — daemon-side worker supervision, event push not polling.\n *\n * An orchestrating session must keep an eye on its workers without sleeping\n * (the Worker skill forbids busy-waiting), the same way the AIBroker daemon\n * keeps an eye on sessions. The PAI daemon already runs all day; this module\n * gives it a tick that watches every worker's status file and pushes one-line\n * events to the owning session when something happens:\n *\n * finished — the run ended clean (state done, rc 0)\n * failed — the run ended badly (state failed/killed/lost, or rc != 0,\n * or the runner pid vanished while state was still running)\n * stalled — state running, runner alive, but no new turn for longer than\n * the stall threshold (PAI_WORKER_STALL_MINUTES, default 10)\n * thrashing — state running, runner alive, turning, but the last N tool\n * executions all failed (PAI_WORKER_THRASH_FAILS, default 5) —\n * a worker that keeps failing every command never goes quiet,\n * so the stall detector alone cannot see it\n * sleeping — state running, one Bash command sleeping N >= 60s with nothing\n * to wait on — under the stall threshold for runs like\n * `sleep 500` (8.3m), which would otherwise be invisible until\n * they cross it. The bounded-wait pattern (background a build or\n * test, then `sleep 60; ps` cycles) has live children and stays\n * quiet; stalled and thrashing remain the backstop for a child\n * that never finishes or dies silently\n *\n * The interactive chat pane (origin \"chat\") is never supervised: it is the\n * operator's own session, not a task worker. Its status file flips to done\n * when the session ends, and reporting that as \"worker finished\" to whoever\n * next opens the same tab is noise, not an outcome.\n *\n * Delivery, in order of preference, never both counted as required:\n * 1. AIBroker `send_to_session` into the orchestrator's terminal — the\n * one-line Agentish-style notification, arriving as a user turn without\n * the orchestrator asking for it. Needs the iTerm identity of the owner,\n * which the status line bridges into the session map (see scope.ts).\n * 2. An append-only event file, <logDir>/supervision/<owner>.events, one\n * JSON line per event. The UserPromptSubmit hook\n * (src/hooks/ts/user-prompt/worker-supervision.ts) surfaces undelivered\n * lines into the session that owns them, so the events reach the model\n * on its next turn even with AIBroker absent.\n *\n * Exactly-once: every event carries a deterministic id and the set of\n * delivered ids per worker is persisted (<logDir>/supervision/state.json), so\n * a daemon restart replays nothing. A first run with no state adopts every\n * already-terminal worker as delivered — supervision reports transitions it\n * observes, not history. Chain stages are separate workers with their own\n * status files and are supervised individually.\n *\n * No model is ever called from here — this is daemon code, and it must stay\n * that way: supervision that bills tokens to watch tokens is a bug.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { alive, isChatPane, loadStatuses, nowStamp, type WorkerStatus } from \"./status.js\";\nimport { itermForClaudeSession } from \"./scope.js\";\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type SupervisionKind = \"finished\" | \"failed\" | \"stalled\" | \"thrashing\" | \"sleeping\";\n\n/** The kinds that fire mid-run and re-arm on turn progress (see eventId). */\nconst MID_RUN_KINDS: readonly SupervisionKind[] = [\"stalled\", \"thrashing\", \"sleeping\"];\n\nexport interface SupervisionEvent {\n /** Deterministic dedup key — see eventId(). */\n id: string;\n /** nowStamp() at detection. */\n ts: string;\n kind: SupervisionKind;\n worker: string;\n label: string;\n /** Owning session key: the spawner's claude session id, or the iTerm uuid. */\n session: string;\n /** Exit code when the run reported one, else null. */\n rc: number | null;\n /** Minutes without a turn, for stalled events; else null. */\n stalledMin: number | null;\n /** Consecutive failed commands, for thrashing events; else null. */\n consecFails?: number | null;\n /** Seconds of the one sleeping command, for sleeping events; else null. */\n sleepSec?: number | null;\n /** The one-liner that reaches the orchestrator. */\n text: string;\n}\n\n/**\n * The owning session of a worker, or null when nobody claims it (and so\n * nothing to notify): the spawner's claude session id first — that is what\n * the hook knows a session by — else the AIBroker iTerm uuid.\n */\nexport function ownershipKey(s: WorkerStatus): string | null {\n if (s.spawnerSession) return s.spawnerSession;\n if (s.session?.id) return s.session.id;\n return null;\n}\n\nconst TERMINAL: readonly WorkerStatus[\"state\"][] = [\"done\", \"failed\", \"killed\", \"lost\"];\n\nfunction isTerminal(s: WorkerStatus): boolean {\n return TERMINAL.includes(s.state);\n}\n\n/** Age of a \"YYYY-MM-DD HH:MM:SS\" stamp in ms, NaN-safe (unparsable → 0). */\nfunction ageMs(ts: string, now: Date): number {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return 0;\n return Math.max(0, now.getTime() - t);\n}\n\n/**\n * The stall threshold in minutes from PAI_WORKER_STALL_MINUTES (default 10).\n * Anything unparsible or non-positive falls back to the default — a broken\n * value must not turn supervision off or make it fire every tick.\n */\nexport const DEFAULT_STALL_MINUTES = 10;\n\nexport function stallMinutesFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.PAI_WORKER_STALL_MINUTES;\n if (raw === undefined || raw === \"\") return DEFAULT_STALL_MINUTES;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALL_MINUTES;\n}\n\n/**\n * The consecutive-failure threshold from PAI_WORKER_THRASH_FAILS (default 5),\n * with the same broken-value tolerance as stallMinutesFromEnv.\n */\nexport const DEFAULT_THRASH_FAILS = 5;\n\nexport function thrashFailsFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.PAI_WORKER_THRASH_FAILS;\n if (raw === undefined || raw === \"\") return DEFAULT_THRASH_FAILS;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : DEFAULT_THRASH_FAILS;\n}\n\n/**\n * Event id: `<worker>#<kind>` for the once-per-run terminal kinds, and\n * `<worker>#<kind>@<turns>` for the mid-run kinds (stalled, thrashing,\n * sleeping) — the turn count re-arms the id, so a worker that resumes and\n * hits the same condition again gets a second event while a restart cannot\n * replay the first.\n */\nexport function eventId(worker: string, kind: SupervisionKind, turns = 0): string {\n return MID_RUN_KINDS.includes(kind) ? `${worker}#${kind}@${turns}` : `${worker}#${kind}`;\n}\n\n/**\n * Live child pids of a runner via `pgrep -P` (its exit 1 means \"no\n * children\"). Any failure — here or in an injected stub — is caught at the\n * one call site in detectSupervisionEvents and reads as an empty list, so a\n * broken check degrades to firing the sleeping event, never to silencing it.\n */\nfunction pgrepChildren(pid: number): number[] {\n return execFileSync(\"pgrep\", [\"-P\", String(pid)], { encoding: \"utf8\" })\n .split(\"\\n\")\n .flatMap((l) => {\n const n = Number(l.trim());\n return Number.isInteger(n) && n > 0 ? [n] : [];\n });\n}\n\nexport interface DetectOptions {\n /** How long a running worker may go without a turn before \"stalled\". */\n stallMs: number;\n now: Date;\n /** Consecutive failed tool executions before \"thrashing\" (default 5). */\n thrashFails?: number;\n /** pid liveness — injected so tests need no real processes. */\n isAlive?: (pid: number) => boolean;\n /**\n * Live child pids of a runner — a sleeping worker with children is a\n * bounded wait on real work, not a stuck one. Injected so tests need no\n * pgrep; the default is pgrepChildren.\n */\n childPids?: (pid: number) => number[];\n /**\n * How stale `updated` must be before a dead pid counts as killed rather\n * than as a status write racing the tick.\n */\n deadGraceMs?: number;\n}\n\n/** One event's one-liner — the exact line the orchestrator reads. */\nfunction eventText(kind: SupervisionKind, s: WorkerStatus, rc: number | null, stalledMin: number | null): string {\n const tail = `- see pai worker replay ${s.id}`;\n if (kind === \"finished\") return `worker ${s.id} ${s.label} finished rc=0 ${tail}`;\n if (kind === \"stalled\") return `worker ${s.id} ${s.label} stalled ${stalledMin}m no turns ${tail}`;\n if (kind === \"thrashing\")\n return `worker ${s.id} ${s.label} thrashing: ${s.consecFails} consecutive failed commands ${tail}`;\n if (kind === \"sleeping\") return `worker ${s.id} ${s.label} sleeping ${s.sleepSec}s in one command ${tail}`;\n const why = rc === null ? \"runner gone\" : `rc=${rc}`;\n return `worker ${s.id} ${s.label} failed ${why} ${tail}`;\n}\n\n/**\n * Detect conditions across a ledger snapshot. Pure: statuses in, events out;\n * dedup against delivered ids is the caller's job (see filterUndelivered).\n * Workers without an owning session are skipped — nobody to notify.\n */\nexport function detectSupervisionEvents(\n statuses: WorkerStatus[],\n opts: DetectOptions\n): SupervisionEvent[] {\n const isAlive = opts.isAlive ?? alive;\n const deadGraceMs = opts.deadGraceMs ?? 15_000;\n const out: SupervisionEvent[] = [];\n for (const s of statuses) {\n const session = ownershipKey(s);\n if (!session) continue;\n // the operator's own pane: ending it is not a worker outcome, idling is\n // not a stall — nothing here is worth a push into anybody's terminal\n if (isChatPane(s)) continue;\n let kind: SupervisionKind | null = null;\n let rc: number | null = null;\n let stalledMin: number | null = null;\n if (isTerminal(s)) {\n // a finished run is state done with rc 0; every other terminal shape\n // (failed, killed, lost, done with a non-zero rc) is a failure\n if (s.state === \"done\" && (s.rc ?? 0) === 0) {\n kind = \"finished\";\n rc = 0;\n } else {\n kind = \"failed\";\n rc = s.state === \"lost\" ? null : s.rc;\n }\n } else if (!isAlive(s.pid) && ageMs(s.updated, opts.now) > deadGraceMs) {\n // state says running but the runner is gone (SIGKILL, crash) — the\n // status will never get its rc, so supervision is the only witness\n kind = \"failed\";\n rc = null;\n } else if (ageMs(s.updated, opts.now) > opts.stallMs) {\n kind = \"stalled\";\n stalledMin = Math.floor(ageMs(s.updated, opts.now) / 60_000);\n } else if ((s.consecFails ?? 0) >= (opts.thrashFails ?? DEFAULT_THRASH_FAILS)) {\n kind = \"thrashing\";\n } else if (s.sleepSec != null) {\n // bounded wait on real work: a sleep while a spawned child still runs\n // is healthy, so flag only a sleep with nothing to wait on. The check\n // runs once per sleeping candidate, only for a live pid; a failed\n // check reads as no children and fires. stalled (no turns) and\n // thrashing stay the backstop and never pass through here.\n let hasChildren = false;\n if (isAlive(s.pid)) {\n try {\n hasChildren = (opts.childPids ?? pgrepChildren)(s.pid).length > 0;\n } catch {\n hasChildren = false; // pgrep gone: degrade to today's behavior\n }\n }\n if (!hasChildren) kind = \"sleeping\";\n }\n if (!kind) continue;\n out.push({\n id: eventId(s.id, kind, s.turns),\n ts: nowStamp(opts.now),\n kind,\n worker: s.id,\n label: s.label,\n session,\n rc,\n stalledMin,\n ...(kind === \"thrashing\" ? { consecFails: s.consecFails ?? 0 } : {}),\n ...(kind === \"sleeping\" ? { sleepSec: s.sleepSec ?? 0 } : {}),\n text: eventText(kind, s, rc, stalledMin),\n });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Delivered-id persistence — the restart guard\n// ---------------------------------------------------------------------------\n\nexport interface SupervisionState {\n /** worker id → event ids already written to that owner's event file. */\n delivered: Record<string, string[]>;\n}\n\n/** Cap on remembered workers: terminal and delivered is history, not state. */\nconst MAX_REMEMBERED_WORKERS = 500;\n\nexport function supervisionDirPath(logDir: string): string {\n return join(logDir, \"supervision\");\n}\n\nexport function supervisionStatePath(logDir: string): string {\n return join(supervisionDirPath(logDir), \"state.json\");\n}\n\nexport function supervisionEventsPath(logDir: string, session: string): string {\n return join(supervisionDirPath(logDir), `${session}.events`);\n}\n\n/** Receipts of events already pushed into a terminal, one event id per line. */\nexport function supervisionPushedPath(logDir: string, session: string): string {\n return join(supervisionDirPath(logDir), `${session}.pushed`);\n}\n\nexport function loadSupervisionState(path: string): { state: SupervisionState; fresh: boolean } {\n if (!existsSync(path)) return { state: { delivered: {} }, fresh: true };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as SupervisionState;\n if (parsed && typeof parsed === \"object\" && typeof parsed.delivered === \"object\") {\n return { state: parsed, fresh: false };\n }\n } catch {\n // a damaged state file replays at most one round of terminal events;\n // rewriting it is the recovery, not the problem\n }\n return { state: { delivered: {} }, fresh: true };\n}\n\nexport function saveSupervisionState(path: string, state: SupervisionState): void {\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state), \"utf8\");\n renameSync(tmp, path);\n}\n\n/**\n * Drop the delivered ids of workers the ledger no longer lists (absent means\n * history: their status files are gone) and cap only that history — newest\n * ids last, worker ids sort chronologically — so the persisted state stays\n * bounded. A worker the ledger still lists must never lose its receipts:\n * pruning one un-marks its terminal events as delivered, and every save\n * re-arms them into an unbounded re-delivery loop (the 2026-09-25 incident:\n * one old chain stage re-pushed 7,393 times, because the cap of 500 bit into\n * a ledger of ~1,700 live statuses).\n */\nexport function pruneState(\n state: SupervisionState,\n statuses: WorkerStatus[],\n maxAbsent = MAX_REMEMBERED_WORKERS\n): SupervisionState {\n const present = new Set(statuses.map((s) => s.id));\n const entries = Object.entries(state.delivered);\n const byId = ([a]: [string, string[]], [b]: [string, string[]]) => (a < b ? -1 : a > b ? 1 : 0);\n const keptPresent = entries.filter(([id]) => present.has(id)).sort(byId);\n const absent = entries.filter(([id]) => !present.has(id)).sort(byId);\n // not slice(-maxAbsent): with maxAbsent 0 that returns the whole array\n const keptAbsent = absent.slice(Math.max(0, absent.length - maxAbsent));\n return { delivered: Object.fromEntries([...keptAbsent, ...keptPresent]) };\n}\n\n/**\n * First run: adopt every already-terminal worker as delivered. Supervision\n * reports the transitions it observes from now on, never a replay of every\n * worker that ever finished before it started.\n */\nexport function baselineState(statuses: WorkerStatus[]): SupervisionState {\n const delivered: Record<string, string[]> = {};\n for (const s of statuses) {\n if (!isTerminal(s) || !ownershipKey(s)) continue;\n delivered[s.id] = [eventId(s.id, \"finished\"), eventId(s.id, \"failed\")];\n }\n return { delivered };\n}\n\n/** The events whose ids the state has not seen yet. */\nexport function filterUndelivered(events: SupervisionEvent[], state: SupervisionState): SupervisionEvent[] {\n return events.filter((e) => !(state.delivered[e.worker] ?? []).includes(e.id));\n}\n\nexport function markDelivered(state: SupervisionState, events: SupervisionEvent[]): void {\n for (const e of events) {\n const ids = state.delivered[e.worker] ?? [];\n if (!ids.includes(e.id)) ids.push(e.id);\n state.delivered[e.worker] = ids;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery\n// ---------------------------------------------------------------------------\n\n/** Where an event's push goes, resolved from the worker's own identities. */\nexport interface PushTarget {\n claudeSession: string | null;\n iterm: string | null;\n}\n\nexport type PushFn = (logDir: string, target: PushTarget, ev: SupervisionEvent) => Promise<boolean>;\n\n/**\n * Default push: AIBroker `send_to_session` into the orchestrator's terminal,\n * using the iTerm uuid the worker recorded (`session.id`) or the one the\n * session map bridges for the spawner's claude session. A timeout still\n * delivered (see aibroker-client) counts as delivered; every failure is\n * reported as not-delivered so only the event file carries the event.\n */\nexport const aibrokerPush: PushFn = async (logDir, target, ev) => {\n const iterm =\n target.iterm ?? (target.claudeSession ? itermForClaudeSession(logDir, target.claudeSession) : null);\n if (!iterm) return false;\n try {\n const { sendToSession } = await import(\"../cli/lib/aibroker-client.js\");\n const r = await sendToSession(iterm, ev.text);\n return r.ok || r.timedOut === true;\n } catch {\n return false;\n }\n};\n\n/**\n * Whether an event should be relayed into the owner's terminal. The mid-run\n * kinds (stalled, thrashing, sleeping) ALWAYS relay: a json/stream-json\n * caller knows the run's result at exit but has no other way to hear\n * mid-run trouble. Only the terminal kinds (finished/failed) skip the relay\n * for those callers — they read the run's result itself, and the harness\n * already notifies them on process exit, so the one-liner would cost a full\n * extra turn for information they already have. Interactive/pane launches\n * and the default text format are never skipped.\n */\nexport function shouldRelay(\n kind: SupervisionKind,\n outputFormat: WorkerStatus[\"outputFormat\"] | undefined\n): boolean {\n if (MID_RUN_KINDS.includes(kind)) return true;\n return outputFormat !== \"json\" && outputFormat !== \"stream-json\";\n}\n\nfunction appendEventFile(logDir: string, ev: SupervisionEvent): void {\n const path = supervisionEventsPath(logDir, ev.session);\n const dir = supervisionDirPath(logDir);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(path, JSON.stringify(ev) + \"\\n\", \"utf8\");\n}\n\n/** Record that an event reached its owner's terminal — the hook then skips it. */\nfunction appendPushReceipt(logDir: string, ev: SupervisionEvent): void {\n try {\n appendFileSync(supervisionPushedPath(logDir, ev.session), `${ev.id}\\n`, \"utf8\");\n } catch {\n // a missing receipt only means the hook may repeat the line\n }\n}\n\nexport interface TickOptions {\n now?: Date;\n stallMs?: number;\n /** Consecutive-failure threshold for thrashing (default: PAI_WORKER_THRASH_FAILS). */\n thrashFails?: number;\n isAlive?: (pid: number) => boolean;\n /** Injectable for tests; production pushes through AIBroker. */\n push?: PushFn;\n /** Preloaded map, so a test (or a batch of ticks) can share one read. */\n statuses?: WorkerStatus[];\n}\n\nexport interface TickResult {\n events: SupervisionEvent[];\n /** Events that also reached a terminal through the push channel. */\n pushed: SupervisionEvent[];\n}\n\n/**\n * One supervision pass over the ledger: detect, append each new event to its\n * owner's event file, push it best effort, persist the delivered ids. The\n * event file is the durable record — an event is marked delivered only after\n * its line landed there, so a failed tick retries, a repeated one replays\n * nothing.\n */\nexport async function runSupervisionTick(logDir: string, opts: TickOptions = {}): Promise<TickResult> {\n const now = opts.now ?? new Date();\n const statuses = opts.statuses ?? loadStatuses(logDir);\n const statePath = supervisionStatePath(logDir);\n const loaded = loadSupervisionState(statePath);\n let state = loaded.state;\n if (loaded.fresh) state = baselineState(statuses);\n\n const pending = filterUndelivered(\n detectSupervisionEvents(statuses, {\n stallMs: opts.stallMs ?? DEFAULT_STALL_MINUTES * 60_000,\n thrashFails: opts.thrashFails ?? thrashFailsFromEnv(),\n now,\n ...(opts.isAlive ? { isAlive: opts.isAlive } : {}),\n }),\n state\n );\n\n const written: SupervisionEvent[] = [];\n for (const ev of pending) {\n appendEventFile(logDir, ev);\n written.push(ev);\n }\n\n const push = opts.push ?? aibrokerPush;\n const pushed: SupervisionEvent[] = [];\n for (const ev of written) {\n // the owner's iTerm identity is in its worker status, not the event\n const owner = statuses.find((s) => s.id === ev.worker);\n if (!shouldRelay(ev.kind, owner?.outputFormat)) continue; // caller reads its own JSON result\n const target: PushTarget = {\n claudeSession: owner?.spawnerSession ?? null,\n iterm: owner?.session?.id ?? null,\n };\n let ok = false;\n try {\n ok = await push(logDir, target, ev);\n } catch {\n ok = false;\n }\n if (ok) {\n pushed.push(ev);\n appendPushReceipt(logDir, ev);\n }\n }\n\n markDelivered(state, written);\n saveSupervisionState(statePath, pruneState(state, statuses));\n return { events: written, pushed };\n}\n","/**\n * Index, embed, vault index, and registry scan schedulers for the PAI daemon.\n * Exports run* functions (also called on-demand by the IPC handler)\n * and the start* functions invoked once at daemon startup.\n */\n\nimport { readWorkersSection } from \"../../workers/config.js\";\nimport { keepaliveSecs, runKeepaliveBeat, type BeatMetrics } from \"../../workers/keepalive.js\";\nimport { workersLogDir } from \"../../workers/paths.js\";\nimport { runSupervisionTick, stallMinutesFromEnv, thrashFailsFromEnv } from \"../../workers/supervision.js\";\nimport { runSessionKeepaliveTick } from \"../session-keepalive.js\";\nimport {\n registryBackend,\n storageBackend,\n daemonConfig,\n indexInProgress,\n embedInProgress,\n vaultIndexInProgress,\n shutdownRequested,\n setIndexInProgress,\n setLastIndexTime,\n setIndexSchedulerTimer,\n setEmbedInProgress,\n setLastEmbedTime,\n setEmbedSchedulerTimer,\n setVaultIndexInProgress,\n setLastVaultIndexTime,\n setCacheKeepaliveTimer,\n} from \"./state.js\";\n\n// ---------------------------------------------------------------------------\n// Index scheduler\n// ---------------------------------------------------------------------------\n\n/** Minimum interval between vault index runs (30 minutes). */\nconst VAULT_INDEX_MIN_INTERVAL_MS = 30 * 60 * 1000;\n\n/**\n * Run a full index pass. Guards against overlapping runs with indexInProgress.\n * Called both by the scheduler and by the index_now IPC method.\n */\nexport async function runIndex(): Promise<void> {\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index already in progress, skipping.\\n\");\n return;\n }\n\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed in progress, deferring index run.\\n\");\n return;\n }\n\n setIndexInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled index run...\\n\");\n\n const { projects, result } = await storageBackend.indexAll(registryBackend);\n const elapsed = Date.now() - t0;\n setLastIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Index complete: ${projects} projects, ` +\n `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n `(${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Index error: ${msg}\\n`);\n } finally {\n setIndexInProgress(false);\n }\n}\n\n/**\n * Run a vault index pass. Guards against overlapping runs with vaultIndexInProgress.\n * Skips if no vaultPath is configured, or if project index/embed is in progress.\n */\nexport async function runVaultIndex(): Promise<void> {\n if (!daemonConfig.vaultPath) return;\n\n if (vaultIndexInProgress) {\n process.stderr.write(\"[pai-daemon] Vault index already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Index/embed in progress, deferring vault index.\\n\");\n return;\n }\n\n // Import lastVaultIndexTime from state (re-read each call since it may change)\n const { lastVaultIndexTime } = await import(\"./state.js\");\n if (lastVaultIndexTime > 0 && Date.now() - lastVaultIndexTime < VAULT_INDEX_MIN_INTERVAL_MS) {\n return;\n }\n\n let vaultProjectId = daemonConfig.vaultProjectId;\n if (!vaultProjectId) {\n const row = await registryBackend.getProjectByRootPath(daemonConfig.vaultPath!);\n vaultProjectId = row?.id ?? 999;\n if (!row) {\n process.stderr.write(\"[pai-daemon] Vault not in project registry — using synthetic project ID 999.\\n\");\n }\n }\n\n setVaultIndexInProgress(true);\n const t0 = Date.now();\n\n process.stderr.write(\"[pai-daemon] Starting vault index run...\\n\");\n\n try {\n const { indexVault } = await import(\"../../memory/vault-indexer.js\");\n const r = await indexVault(storageBackend, vaultProjectId, daemonConfig.vaultPath!);\n const elapsed = Date.now() - t0;\n setLastVaultIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Vault index complete: ${r.filesIndexed} files, ` +\n `${r.linksExtracted} links, ${r.deadLinksFound} dead, ` +\n `${r.orphansFound} orphans (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Vault index error: ${msg}\\n`);\n } finally {\n setVaultIndexInProgress(false);\n }\n}\n\n/**\n * Start the periodic index scheduler. Runs an initial pass 2 seconds after startup.\n */\nexport function startIndexScheduler(): void {\n const intervalMs = daemonConfig.indexIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Index scheduler: every ${daemonConfig.indexIntervalSecs}s\\n`\n );\n\n // Indexing and embedding are mutually exclusive (each defers to the other),\n // and an index pass regularly outlasts the index interval. Left to two\n // independent timers, the embed tick therefore lands while indexing is in\n // progress and is dropped — observed live on 2026-08-01 as six consecutive\n // index passes with no embed pass at all, and a 143k-chunk embedding backlog.\n // Chaining embed onto the end of every index pass makes the alternation\n // guaranteed rather than a race: index, then vault, then embed, every cycle.\n const cycle = (label: string, withEmbed: boolean) =>\n runIndex()\n .then(() => runVaultIndex())\n .then(() => (withEmbed ? runEmbed() : undefined))\n .catch((e) => {\n process.stderr.write(`[pai-daemon] ${label} index error: ${e}\\n`);\n });\n\n // The startup pass indexes but must NOT embed unless embedOnStartup says so.\n // startEmbedScheduler already refuses a startup embed for exactly that reason;\n // chaining runEmbed onto this pass reinstated the same CPU storm through the\n // back door. Seen live after a reboot: \"Startup embed pass skipped\n // (embedOnStartup=false)\" in the log, and minutes later the same boot running\n // a 5000-chunk pass at ~450% CPU. A guard one scheduler honours and another\n // walks around is not a guard.\n // With a maintenance hour configured there is no startup pass at all. Every\n // restart otherwise buys itself a full index run, so on a machine that is\n // rebooted or has the daemon reloaded during the day the \"maintenance runs at\n // night\" guarantee is worth nothing: the heavy work simply follows the\n // restarts around. Indexing waits for the window like everything else.\n if (daemonConfig.maintenanceHour === undefined) {\n setTimeout(() => void cycle(\"Startup\", daemonConfig.embedOnStartup), 2_000);\n } else {\n process.stderr.write(\n \"[pai-daemon] Startup index pass skipped (maintenanceHour is set).\\n\"\n );\n }\n\n // Anchor the recurring cycle to a wall-clock hour when one is configured.\n // setInterval alone counts from daemon start, so a machine rebooted at noon\n // gets its \"daily\" maintenance at noon every day thereafter — the interval\n // says how often, never when. Without an anchor there is no such thing as a\n // night-only pass, however long the interval.\n const firstDelayMs = msUntilNextAnchor(daemonConfig.maintenanceHour, intervalMs);\n if (daemonConfig.maintenanceHour !== undefined) {\n process.stderr.write(\n `[pai-daemon] Index scheduler anchored to ${String(daemonConfig.maintenanceHour).padStart(2, \"0\")}:00 local ` +\n `(next pass in ${Math.round(firstDelayMs / 60_000)} min)\\n`\n );\n }\n\n let timer: ReturnType<typeof setInterval>;\n const startTimer = () => {\n timer = setInterval(() => void cycle(\"Scheduled\", true), intervalMs);\n if (timer.unref) timer.unref();\n setIndexSchedulerTimer(timer);\n };\n\n const first = setTimeout(() => {\n void cycle(\"Scheduled\", true);\n startTimer();\n }, firstDelayMs);\n if (first.unref) first.unref();\n}\n\n/**\n * Milliseconds until the next occurrence of `hour`:00 local time.\n * Falls back to the plain interval when no anchor hour is configured.\n */\nfunction msUntilNextAnchor(hour: number | undefined, intervalMs: number): number {\n if (hour === undefined || !Number.isInteger(hour) || hour < 0 || hour > 23) {\n return intervalMs;\n }\n const now = new Date();\n const next = new Date(now);\n next.setHours(hour, 0, 0, 0);\n if (next <= now) next.setDate(next.getDate() + 1);\n return next.getTime() - now.getTime();\n}\n\n// ---------------------------------------------------------------------------\n// Embed scheduler\n// ---------------------------------------------------------------------------\n\n/**\n * Run an embedding pass for all unembedded chunks (Postgres backend only).\n */\nexport async function runEmbed(): Promise<void> {\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index in progress, deferring embed pass.\\n\");\n return;\n }\n\n if (!storageBackend.supportsPostgresFeatures) {\n return;\n }\n\n setEmbedInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled embed pass...\\n\");\n\n const projectNames = new Map<number, string>();\n try {\n const rows = await registryBackend.listProjects({ status: \"active\" });\n for (const r of rows) projectNames.set(r.id, r.slug);\n } catch { /* registry unavailable — IDs will be used instead */ }\n\n const { embedChunksWithBackend } = await import(\"../../memory/indexer-backend.js\");\n // Explicit budgets so one embed phase can never outlast an index cycle.\n // Backlogs drain across passes, not within one.\n const count = await embedChunksWithBackend(\n storageBackend,\n () => shutdownRequested,\n projectNames,\n // Indexing settles to a few chunks per pass once caught up, so the daemon\n // is idle most of the cycle and the embed budget is what actually sets\n // drain rate. Measured 450 chunks per 90s against real (long) chunks, so\n // a backlog only clears at roughly the budget's share of the cycle. What\n // broke the starvation was bounding the pass at all, not this number —\n // it can be raised freely as long as it stays finite.\n { maxMillis: 240_000 },\n );\n\n // Vault chunks (project 999) are indexed through the same storageBackend\n // as everything else (indexVault() writes via backend.insertChunks()), so\n // the embedChunksWithBackend() call above already embeds them. A vault-only\n // SQLite pass here would open a stray federation database even under Postgres.\n\n const elapsed = Date.now() - t0;\n setLastEmbedTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Embed pass complete: ${count} chunks embedded (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Embed error: ${msg}\\n`);\n } finally {\n setEmbedInProgress(false);\n }\n}\n\n/**\n * Start the periodic embed scheduler. Initial run is 60 seconds after startup.\n */\nexport function startEmbedScheduler(): void {\n const intervalMs = daemonConfig.embedIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Embed scheduler: every ${daemonConfig.embedIntervalSecs}s\\n`\n );\n\n // Startup embed pass — OFF unless explicitly enabled.\n //\n // This used to run unconditionally 60s after every start, ignoring\n // embedIntervalSecs entirely. With a large backlog that makes every restart a\n // CPU storm: the interval was set to a day to stop the machine being bogged\n // down, the daemon was restarted, and it immediately began a 240-second pass\n // anyway. Throttling the recurring timer while leaving this armed means the\n // throttle silently does nothing for anyone who restarts.\n //\n // Measured backlog when this was found: 1,868,098 unembedded chunks at ~5.7\n // chunks/s — about 91 days of work that a restart would resume unbidden.\n if (daemonConfig.embedOnStartup) {\n setTimeout(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Startup embed error: ${e}\\n`);\n });\n }, 60_000);\n } else {\n process.stderr.write(\n \"[pai-daemon] Startup embed pass skipped (embedOnStartup=false).\\n\"\n );\n }\n\n // With a maintenance hour configured the index cycle already chains an embed\n // pass onto every anchored run, so this timer would only add a second pass\n // that counts from daemon start — observed as \"every 86400s\" armed at a\n // 20:08 boot, i.e. a daily daytime pass on a machine configured for 03:00.\n // The interval says how often, never when; without an anchor it cannot\n // deliver a night-only pass, so it is not armed at all.\n if (daemonConfig.maintenanceHour !== undefined) {\n process.stderr.write(\n \"[pai-daemon] Standalone embed timer not armed (maintenanceHour is set; \" +\n \"embed runs at the end of each anchored index cycle).\\n\"\n );\n return;\n }\n\n const timer = setInterval(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled embed error: ${e}\\n`);\n });\n }, intervalMs);\n\n if (timer.unref) timer.unref();\n setEmbedSchedulerTimer(timer);\n}\n\n// ---------------------------------------------------------------------------\n// Registry scan scheduler\n// ---------------------------------------------------------------------------\n\nconst REGISTRY_SCAN_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes\nconst REGISTRY_SCAN_STARTUP_DELAY_MS = 10_000; // 10 seconds after daemon start\n\n/**\n * Start the periodic registry scan scheduler.\n * First scan runs 10 seconds after startup (enough time for IPC to stabilise).\n * Subsequent scans run every 30 minutes.\n * Uses the work-queue so the scan runs on the worker thread without blocking IPC.\n */\nexport function startRegistryScanScheduler(): void {\n process.stderr.write(\n \"[pai-daemon] Registry scan scheduler: every 30min (first in 10s)\\n\"\n );\n\n setTimeout(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Startup registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_STARTUP_DELAY_MS);\n\n const timer = setInterval(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_INTERVAL_MS);\n\n if (timer.unref) timer.unref();\n}\n\n// ---------------------------------------------------------------------------\n// Worker supervision scheduler\n// ---------------------------------------------------------------------------\n\n/** How often the daemon looks at the worker ledger for supervision events. */\nconst SUPERVISION_TICK_MS = 30_000;\n\n/** First supervision pass waits for the first statuses to exist at all. */\nconst SUPERVISION_STARTUP_DELAY_MS = 15_000;\n\n/**\n * Start the worker supervisor: one tick every 30 s over the workers ledger\n * (src/workers/supervision.ts) that pushes finished/failed/stalled events to\n * the owning session — the daemon-side replacement for orchestrator polling.\n * Daemon code only: no model call has any business being here, and none is\n * made. Disabled workers disable the supervisor with it.\n */\nexport function startWorkerSupervisor(): void {\n let enabled = false;\n try {\n enabled = readWorkersSection().workers.enabled;\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Worker supervisor: not started (${e instanceof Error ? e.message : String(e)})\\n`\n );\n return;\n }\n if (!enabled) {\n process.stderr.write(\"[pai-daemon] Worker supervisor: disabled (workers are off)\\n\");\n return;\n }\n\n const tick = () => {\n runSupervisionTick(workersLogDir(readWorkersSection().workers), {\n stallMs: stallMinutesFromEnv() * 60_000,\n thrashFails: thrashFailsFromEnv(),\n })\n .then((r) => {\n for (const ev of r.events) {\n process.stderr.write(`[pai-daemon] Supervision: ${ev.text}\\n`);\n }\n })\n .catch((e) => {\n // a broken tick must never take the daemon down with it\n process.stderr.write(\n `[pai-daemon] Supervision tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n };\n\n const first = setTimeout(tick, SUPERVISION_STARTUP_DELAY_MS);\n if (first.unref) first.unref();\n const timer = setInterval(tick, SUPERVISION_TICK_MS);\n if (timer.unref) timer.unref();\n process.stderr.write(\n `[pai-daemon] Worker supervisor: every ${Math.round(SUPERVISION_TICK_MS / 1000)}s\\n`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Worker prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** First beat waits a while after boot; the daemon start is itself a warm request. */\nconst KEEPALIVE_STARTUP_DELAY_MS = 60_000;\n\n/**\n * Start the cache keepalive: every workers.cacheKeepaliveSecs one trivial\n * single-turn worker (src/workers/keepalive.ts) re-arms the provider's\n * implicit prompt cache so real worker spawns start warm. Needs no storage\n * backend — it starts with the IPC server, not after the federation connect.\n * 0 (or workers off) keeps it off. A failed beat is logged and swallowed.\n */\nexport function startCacheKeepalive(opts: {\n configPath?: string;\n beat?: () => Promise<BeatMetrics | null>;\n} = {}): void {\n let workers: ReturnType<typeof readWorkersSection>[\"workers\"];\n try {\n workers = readWorkersSection(opts.configPath).workers;\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Cache keepalive: not started (${e instanceof Error ? e.message : String(e)})\\n`\n );\n return;\n }\n if (!workers.enabled) {\n process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (workers are off)\\n\");\n return;\n }\n const secs = keepaliveSecs(workers);\n if (!secs) {\n process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (cacheKeepaliveSecs is 0)\\n\");\n return;\n }\n\n let notedNoPostgres = false;\n const supportsObservations = () => {\n if (storageBackend?.supportsPostgresFeatures) return true;\n if (!notedNoPostgres) {\n notedNoPostgres = true;\n process.stderr.write(\n \"[pai-daemon] Cache keepalive: observation rows need Postgres — ledger line only\\n\"\n );\n }\n return false;\n };\n\n const beat = async (): Promise<void> => {\n const m = await (opts.beat ?? runKeepaliveBeat)();\n if (!m) return; // overlap guard: previous beat still running\n if (!supportsObservations()) return; // ledger line already written by the beat itself\n try {\n await storageBackend.storeObservationWithProject(registryBackend, {\n session_id: m.id,\n type: \"change\",\n title: `cache keepalive ${m.ok ? \"ok\" : \"failed\"} (cache_read=${m.cache_read_input_tokens}, api_ms=${m.duration_api_ms})`,\n tool_name: \"cache-keepalive\",\n narrative: `beat ${m.id}: input=${m.input_tokens} cache_read=${m.cache_read_input_tokens} cache_creation=${m.cache_creation_input_tokens} duration_api_ms=${m.duration_api_ms}`,\n tool_input_summary: undefined,\n files_read: [],\n files_modified: [],\n concepts: [],\n });\n } catch (e) {\n // a failed observation must never fail the beat\n process.stderr.write(\n `[pai-daemon] Cache keepalive observation error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n }\n };\n\n const first = setTimeout(() => {\n beat().catch((e) => {\n process.stderr.write(\n `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n }, KEEPALIVE_STARTUP_DELAY_MS);\n if (first.unref) first.unref();\n\n const timer = setInterval(() => {\n beat().catch((e) => {\n process.stderr.write(\n `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n }, secs * 1000);\n if (timer.unref) timer.unref();\n setCacheKeepaliveTimer(timer);\n process.stderr.write(`[pai-daemon] Cache keepalive: every ${secs}s\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Interactive-session prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** Tick cadence: coarse enough to be cheap, fine enough that a session\n * crossing idleMinutes gets beaten within a minute of becoming eligible. */\nconst SESSION_KEEPALIVE_TICK_MS = 60_000;\n\n/**\n * Start the interactive-session cache keepalive: every 60s, beat every live\n * session that has gone idle past `sessions.cacheKeepalive.idleMinutes`\n * (src/daemon/session-keepalive.ts). Off unless\n * `sessions.cacheKeepalive.enabled` is true — no hot-reload, matching\n * startCacheKeepalive above: config changes need a daemon restart.\n */\nexport function startSessionKeepalive(): void {\n const config = daemonConfig.sessions.cacheKeepalive;\n if (!config.enabled) {\n process.stderr.write(\"[pai-daemon] Session keepalive: disabled (sessions.cacheKeepalive.enabled is false)\\n\");\n return;\n }\n\n const tick = () => {\n runSessionKeepaliveTick(config).catch((e) => {\n process.stderr.write(\n `[pai-daemon] Session keepalive tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n };\n\n const timer = setInterval(tick, SESSION_KEEPALIVE_TICK_MS);\n if (timer.unref) timer.unref();\n process.stderr.write(\n `[pai-daemon] Session keepalive: every ${SESSION_KEEPALIVE_TICK_MS / 1000}s ` +\n `(idle >= ${config.idleMinutes}min, hours ${config.activeHours}, max ${config.maxBeats} beats/stretch)\\n`\n );\n}\n","/**\n * Tool dispatcher — maps IPC method names to PAI tool functions.\n */\n\nimport {\n toolMemorySearch,\n toolMemoryGet,\n toolMemoryOutline,\n toolProjectInfo,\n toolProjectList,\n toolSessionList,\n toolRegistrySearch,\n toolProjectDetect,\n toolProjectHealth,\n toolProjectTodo,\n toolSessionRoute,\n toolMemoryWakeup,\n toolMemoryTaxonomy,\n toolMemoryFeedback,\n toolMemoryKgSearch,\n} from \"../../mcp/tools.js\";\nimport { detectTopicShift } from \"../../topics/detector.js\";\nimport { registryBackend, storageBackend, daemonConfig } from \"./state.js\";\n\n/**\n * Dispatch an IPC tool call to the appropriate tool function.\n * Returns the tool result or throws on unknown/failed methods.\n */\nexport async function dispatchTool(\n method: string,\n params: Record<string, unknown>\n): Promise<unknown> {\n // Cast through unknown to satisfy TypeScript's strict overlap check on\n // Record<string, unknown> → specific param types. Runtime validation is\n // the responsibility of each tool function (they surface errors gracefully).\n const p = params as unknown;\n\n switch (method) {\n case \"memory_search\":\n return toolMemorySearch(registryBackend, storageBackend, p as Parameters<typeof toolMemorySearch>[2]);\n\n case \"memory_get\":\n return toolMemoryGet(registryBackend, p as Parameters<typeof toolMemoryGet>[1]);\n\n case \"memory_outline\":\n return toolMemoryOutline(registryBackend, p as Parameters<typeof toolMemoryOutline>[1]);\n\n case \"project_info\":\n return toolProjectInfo(registryBackend, p as Parameters<typeof toolProjectInfo>[1]);\n\n case \"project_list\":\n return toolProjectList(registryBackend, p as Parameters<typeof toolProjectList>[1]);\n\n case \"session_list\":\n return toolSessionList(registryBackend, p as Parameters<typeof toolSessionList>[1]);\n\n case \"registry_search\":\n return toolRegistrySearch(registryBackend, p as Parameters<typeof toolRegistrySearch>[1]);\n\n case \"project_detect\":\n return toolProjectDetect(p as Parameters<typeof toolProjectDetect>[0]);\n\n case \"project_health\":\n return toolProjectHealth(registryBackend, p as Parameters<typeof toolProjectHealth>[1]);\n\n case \"project_todo\":\n return toolProjectTodo(registryBackend, p as Parameters<typeof toolProjectTodo>[1]);\n\n case \"memory_wakeup\":\n return toolMemoryWakeup(registryBackend, p as Parameters<typeof toolMemoryWakeup>[1]);\n\n case \"memory_taxonomy\":\n return toolMemoryTaxonomy(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTaxonomy>[2]);\n\n case \"topic_check\":\n return detectTopicShift(\n registryBackend,\n storageBackend,\n p as Parameters<typeof detectTopicShift>[2]\n );\n\n case \"session_auto_route\":\n return toolSessionRoute(\n registryBackend,\n storageBackend,\n p as Parameters<typeof toolSessionRoute>[2]\n );\n\n case \"zettel_explore\":\n case \"zettel_health\":\n case \"zettel_surprise\":\n case \"zettel_suggest\":\n case \"zettel_converse\":\n case \"zettel_themes\":\n case \"zettel_god_notes\":\n case \"zettel_communities\": {\n const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import(\"../../mcp/tools.js\");\n\n switch (method) {\n case \"zettel_explore\": return toolZettelExplore(storageBackend, p as Parameters<typeof toolZettelExplore>[1]);\n case \"zettel_health\": return toolZettelHealth(storageBackend, p as Parameters<typeof toolZettelHealth>[1]);\n case \"zettel_surprise\": return toolZettelSurprise(storageBackend, p as Parameters<typeof toolZettelSurprise>[1]);\n case \"zettel_suggest\": return toolZettelSuggest(storageBackend, p as Parameters<typeof toolZettelSuggest>[1]);\n case \"zettel_converse\": return toolZettelConverse(storageBackend, p as Parameters<typeof toolZettelConverse>[1]);\n case \"zettel_themes\": return toolZettelThemes(storageBackend, p as Parameters<typeof toolZettelThemes>[1]);\n case \"zettel_god_notes\": return toolZettelGodNotes(storageBackend, p as Parameters<typeof toolZettelGodNotes>[1]);\n case \"zettel_communities\": return toolZettelCommunities(storageBackend, p as Parameters<typeof toolZettelCommunities>[1]);\n }\n break;\n }\n\n case \"graph_clusters\": {\n const { handleGraphClusters } = await import(\"../../graph/clusters.js\");\n return handleGraphClusters(storageBackend, p as Parameters<typeof handleGraphClusters>[1]);\n }\n\n case \"graph_neighborhood\": {\n const { handleGraphNeighborhood } = await import(\"../../graph/neighborhood.js\");\n return handleGraphNeighborhood(storageBackend, p as Parameters<typeof handleGraphNeighborhood>[1]);\n }\n\n case \"graph_note_context\": {\n const { handleGraphNoteContext } = await import(\"../../graph/note-context.js\");\n return handleGraphNoteContext(storageBackend, p as Parameters<typeof handleGraphNoteContext>[1]);\n }\n\n case \"graph_trace\": {\n const { handleGraphTrace } = await import(\"../../graph/trace.js\");\n return handleGraphTrace(storageBackend, p as Parameters<typeof handleGraphTrace>[1]);\n }\n\n case \"graph_latent_ideas\": {\n const { handleGraphLatentIdeas } = await import(\"../../graph/latent-ideas.js\");\n return handleGraphLatentIdeas(storageBackend, p as Parameters<typeof handleGraphLatentIdeas>[1]);\n }\n\n case \"idea_materialize\": {\n const { handleIdeaMaterialize } = await import(\"../../graph/latent-ideas.js\");\n if (!daemonConfig.vaultPath) {\n throw new Error(\"idea_materialize requires vaultPath to be configured in the daemon config\");\n }\n return handleIdeaMaterialize(\n p as Parameters<typeof handleIdeaMaterialize>[0],\n daemonConfig.vaultPath\n );\n }\n\n case \"kg_add\":\n case \"kg_query\":\n case \"kg_invalidate\":\n case \"kg_contradictions\": {\n const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import(\"../../mcp/tools.js\");\n if (!storageBackend.supportsPostgresFeatures) {\n throw new Error(`${method} requires a Postgres storage backend`);\n }\n switch (method) {\n case \"kg_add\": return toolKgAdd(storageBackend, p as Parameters<typeof toolKgAdd>[1]);\n case \"kg_query\": return toolKgQuery(storageBackend, p as Parameters<typeof toolKgQuery>[1]);\n case \"kg_invalidate\": return toolKgInvalidate(storageBackend, p as Parameters<typeof toolKgInvalidate>[1]);\n case \"kg_contradictions\": return toolKgContradictions(storageBackend, p as Parameters<typeof toolKgContradictions>[1]);\n }\n break;\n }\n\n case \"memory_tunnels\": {\n const { toolMemoryTunnels } = await import(\"../../mcp/tools.js\");\n return toolMemoryTunnels(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTunnels>[2]);\n }\n\n case \"memory_feedback\": {\n // MR2: feedback weight loop — relevance_score/feedback_weight go through\n // the StorageBackend, so this works on either backend.\n return await toolMemoryFeedback(storageBackend, p as Parameters<typeof toolMemoryFeedback>[1]);\n }\n\n case \"memory_kg_search\": {\n // MR1: graph-completion retrieval — active storage backend for KG triple expansion\n if (!storageBackend.supportsPostgresFeatures) {\n throw new Error(\"memory_kg_search requires a Postgres storage backend for KG triple expansion\");\n }\n return await toolMemoryKgSearch(storageBackend, p as Parameters<typeof toolMemoryKgSearch>[1]);\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n}\n","/**\n * IPC request handler — processes all inbound IPC methods and sends responses.\n */\n\nimport type { Socket } from \"node:net\";\nimport type { IpcRequest, IpcResponse } from \"./types.js\";\nimport type { NotificationMode } from \"../../notifications/types.js\";\nimport {\n patchNotificationConfig,\n} from \"../../notifications/config.js\";\nimport { routeNotification } from \"../../notifications/router.js\";\nimport {\n registryBackend,\n storageBackend,\n daemonConfig,\n startTime,\n indexInProgress,\n lastIndexTime,\n embedInProgress,\n lastEmbedTime,\n vaultIndexInProgress,\n lastVaultIndexTime,\n notificationConfig,\n setNotificationConfig,\n} from \"./state.js\";\nimport { runIndex, runVaultIndex } from \"./scheduler.js\";\nimport { dispatchTool } from \"./dispatcher.js\";\nimport { enqueue, getStats as getQueueStats } from \"../../daemon/work-queue.js\";\nimport { notifyNewWork } from \"../../daemon/work-queue-worker.js\";\nimport { getBackendOutage } from \"../../storage/outage.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nexport function sendResponse(socket: Socket, response: IpcResponse): void {\n try {\n socket.write(JSON.stringify(response) + \"\\n\");\n } catch {\n // Socket may already be closed\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single IPC request.\n */\nexport async function handleRequest(\n request: IpcRequest,\n socket: Socket\n): Promise<void> {\n const { id, method, params } = request;\n\n // Special: status\n if (method === \"status\") {\n const dbStats = await (async () => {\n try {\n if (!storageBackend) return null;\n const fedStats = await storageBackend.getStats();\n const projects = await registryBackend.countProjects();\n return { files: fedStats.files, chunks: fedStats.chunks, projects };\n } catch {\n return null;\n }\n })();\n\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n uptime: Math.floor((Date.now() - startTime) / 1000),\n indexInProgress,\n lastIndexTime: lastIndexTime ? new Date(lastIndexTime).toISOString() : null,\n indexIntervalSecs: daemonConfig.indexIntervalSecs,\n embedInProgress,\n lastEmbedTime: lastEmbedTime ? new Date(lastEmbedTime).toISOString() : null,\n embedIntervalSecs: daemonConfig.embedIntervalSecs,\n socketPath: daemonConfig.socketPath,\n storageBackend: storageBackend?.backendType ?? \"initializing\",\n db: dbStats,\n vaultIndexInProgress,\n lastVaultIndexTime: lastVaultIndexTime ? new Date(lastVaultIndexTime).toISOString() : null,\n vaultPath: daemonConfig.vaultPath ?? null,\n workQueue: getQueueStats(),\n // Null when the backend is answering. Present, this is the reason\n // everything else in this payload is stalled — and the reason status\n // used to report \"idle\" for two days while nothing progressed.\n backendOutage: getBackendOutage(),\n },\n });\n socket.end();\n return;\n }\n\n // Special: index_now — trigger immediate index (non-blocking response)\n if (method === \"index_now\") {\n runIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: vault_index_now — trigger immediate vault index (non-blocking response)\n if (method === \"vault_index_now\") {\n runVaultIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] vault_index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: notification_get_config\n if (method === \"notification_get_config\") {\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n config: notificationConfig,\n activeChannels: Object.entries(notificationConfig.channels)\n .filter(([ch, cfg]) => ch !== \"voice\" && (cfg as { enabled: boolean }).enabled)\n .map(([ch]) => ch),\n },\n });\n socket.end();\n return;\n }\n\n // Special: notification_set_config\n if (method === \"notification_set_config\") {\n try {\n const p = params as {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n routing?: Record<string, unknown>;\n };\n const updated = patchNotificationConfig({\n mode: p.mode,\n channels: p.channels as Parameters<typeof patchNotificationConfig>[0][\"channels\"],\n routing: p.routing as Parameters<typeof patchNotificationConfig>[0][\"routing\"],\n });\n setNotificationConfig(updated);\n sendResponse(socket, { id, ok: true, result: { config: updated } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // Special: notification_send\n if (method === \"notification_send\") {\n const p = params as {\n event?: string;\n message?: string;\n title?: string;\n };\n\n if (!p.message) {\n sendResponse(socket, { id, ok: false, error: \"notification_send: message is required\" });\n socket.end();\n return;\n }\n\n const event = (p.event as Parameters<typeof routeNotification>[0][\"event\"]) ?? \"info\";\n\n routeNotification(\n { event, message: p.message, title: p.title },\n notificationConfig\n ).then((result) => {\n sendResponse(socket, { id, ok: true, result });\n socket.end();\n }).catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n socket.end();\n });\n return;\n }\n\n // ---- Observation methods (Postgres only) --------------------------------\n\n if (method === \"observation_store\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n type: string;\n title: string;\n narrative?: string;\n tool_name: string;\n tool_input_summary?: string;\n files_read?: string[];\n files_modified?: string[];\n concepts?: string[];\n content_hash?: string;\n cwd?: string;\n };\n\n const insertedId = await storageBackend.storeObservationWithProject(registryBackend, {\n session_id: p.session_id,\n type: p.type as \"decision\" | \"bugfix\" | \"feature\" | \"refactor\" | \"discovery\" | \"change\",\n title: p.title,\n narrative: p.narrative ?? null,\n tool_name: p.tool_name,\n tool_input_summary: p.tool_input_summary ?? undefined,\n files_read: p.files_read ?? [],\n files_modified: p.files_modified ?? [],\n concepts: p.concepts ?? [],\n cwd: p.cwd,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true, id: insertedId } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // ---- Skill telemetry (Postgres only) ------------------------------------\n\n if (method === \"skill_telemetry_record\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n skill_name: string;\n source?: string;\n scope?: string;\n cwd?: string;\n };\n\n let project_slug: string | null = null;\n if (p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) project_slug = row.slug;\n }\n\n await storageBackend.recordSkillInvocation({\n skill_name: p.skill_name,\n source: p.source,\n scope: p.scope,\n project_slug,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"skill_telemetry_query\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { scope?: string; status?: string; limit?: number };\n const rows = await storageBackend.querySkillTelemetry({\n scope: p.scope,\n status: p.status,\n limit: p.limit,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_query\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_id?: number;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n const rows = await storageBackend.queryObservations({\n projectId: p.project_id,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit,\n offset: p.offset,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_recent\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { project_id?: number; cwd?: string; limit?: number };\n const limit = p.limit ?? 20;\n\n let resolvedProjectId = p.project_id;\n let resolvedProjectSlug: string | undefined;\n if (resolvedProjectId === undefined && p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n let rows;\n if (resolvedProjectId !== undefined) {\n rows = await storageBackend.queryRecentObservations(resolvedProjectId, limit);\n } else {\n rows = await storageBackend.queryObservations({ limit });\n }\n sendResponse(socket, { id, ok: true, result: { rows, project_slug: resolvedProjectSlug } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_list — alias for observation_query with project slug resolution\n if (method === \"observation_list\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_slug?: string;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n let projectId: number | undefined;\n if (p.project_slug) {\n const row = await registryBackend.getProjectBySlug(p.project_slug);\n projectId = row?.id;\n }\n\n const rows = await storageBackend.queryObservations({\n projectId,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit ?? 20,\n offset: p.offset ?? 0,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_stats — aggregate statistics\n if (method === \"observation_stats\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const stats = await storageBackend.getObservationStats();\n sendResponse(socket, { id, ok: true, result: stats });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"session_summary_store\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Session summaries require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n project_id?: number;\n project_slug?: string;\n cwd?: string;\n request?: string;\n investigated?: string;\n learned?: string;\n completed?: string;\n next_steps?: string;\n observation_count?: number;\n };\n\n let resolvedProjectId = p.project_id ?? null;\n let resolvedProjectSlug = p.project_slug ?? null;\n if (resolvedProjectId === null && p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n await storageBackend.storeSessionSummary({\n session_id: p.session_id,\n project_id: resolvedProjectId,\n project_slug: resolvedProjectSlug,\n request: p.request ?? null,\n investigated: p.investigated ?? null,\n learned: p.learned ?? null,\n completed: p.completed ?? null,\n next_steps: p.next_steps ?? null,\n observation_count: p.observation_count ?? 0,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_enqueue — hooks push work items here instead of doing heavy work inline\n if (method === \"work_queue_enqueue\") {\n try {\n const p = params as {\n type?: string;\n priority?: number;\n payload?: Record<string, unknown>;\n maxAttempts?: number;\n };\n\n if (!p.type) {\n sendResponse(socket, { id, ok: false, error: \"work_queue_enqueue: type is required\" });\n socket.end();\n return;\n }\n\n const item = enqueue({\n type: p.type as import(\"../../daemon/work-queue.js\").WorkItemType,\n priority: p.priority,\n payload: p.payload ?? {},\n maxAttempts: p.maxAttempts,\n });\n\n notifyNewWork();\n\n sendResponse(socket, { id, ok: true, result: { id: item.id, status: item.status } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_stats — return current queue statistics\n if (method === \"work_queue_stats\") {\n sendResponse(socket, { id, ok: true, result: getQueueStats() });\n socket.end();\n return;\n }\n\n // All other methods: PAI tool dispatch\n try {\n const result = await dispatchTool(method, params);\n sendResponse(socket, { id, ok: true, result });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n}\n","/**\n * One-time repair for chunks written while the `pai_chunks_fts_update`\n * trigger used the 'english' text-search config instead of 'simple'.\n *\n * The trigger stemmed and stripped stop-words from every stored fts_vector,\n * but every query (searchKeyword, findTunnels) and the direct-insert path\n * (insertChunks) always used 'simple' — so a BEFORE trigger silently\n * overwrote each insert with a vector that keyword search could never match.\n * Measured on a real database: 0 rows for `fts_vector @@ to_tsquery('simple',\n * 'running')` against 142,559 chunks containing \"running\".\n *\n * Completion is marked by a comment on pai_chunks.fts_vector so this can run\n * on every daemon boot for free once migrated, and resume if a batch run\n * was interrupted: every 20 batches the last completed id is stored in the\n * comment as `fts:simple:inprogress:<id>` and the next start continues after it.\n *\n * The rebuild runs in the background while the indexer writes pai_chunks, so\n * each batch UPDATE locks its rows in id order (consistent with other writers)\n * and is retried up to 5 times on deadlock (40P01) or serialization failure\n * (40001) with a 200ms * attempt backoff. Rows whose fts_vector is already\n * correct are skipped, so a retry or restart is cheap.\n */\n\nimport type { StorageBackend } from \"../interface.js\";\nimport type { PostgresBackend } from \"../postgres.js\";\n\nconst DONE_MARKER = \"fts:simple\";\nconst PROGRESS_PREFIX = \"fts:simple:inprogress:\";\nconst BATCH_SIZE = 1000;\nconst MAX_ATTEMPTS = 5;\nconst RETRYABLE_CODES = new Set([\"40P01\", \"40001\"]);\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function migrateFtsConfig(backend: StorageBackend): Promise<void> {\n // Obtained inside the async function body, not by the caller: a throw here\n // (e.g. a bad cast) becomes a rejection of this function's own promise,\n // caught by the caller's `.catch`, instead of a synchronous throw inside\n // the daemon's storage-backend `.then()` that would abort loadQueue()/\n // startWorker() and hit the outer fatal-exit `.catch`.\n const pool = (backend as unknown as PostgresBackend).getPool();\n const tableCheck = await pool.query(\n `SELECT 1 FROM information_schema.tables WHERE table_name = 'pai_chunks'`\n );\n if (tableCheck.rowCount === 0) return;\n\n const commentCheck = await pool.query<{ comment: string | null }>(\n `SELECT col_description('pai_chunks'::regclass, ordinal_position) AS comment\n FROM information_schema.columns\n WHERE table_name = 'pai_chunks' AND column_name = 'fts_vector'`\n );\n const comment = commentCheck.rows[0]?.comment ?? null;\n if (comment === DONE_MARKER) return;\n const resumeId = comment?.startsWith(PROGRESS_PREFIX)\n ? comment.slice(PROGRESS_PREFIX.length)\n : \"\";\n\n process.stderr.write(\n \"[pai-postgres] FTS config migration: starting (english -> simple)\\n\"\n );\n\n const fnCheck = await pool.query(\n `SELECT 1 FROM pg_proc WHERE proname = 'pai_chunks_fts_update'`\n );\n if (fnCheck.rowCount !== 0) {\n await pool.query(`\n CREATE OR REPLACE FUNCTION pai_chunks_fts_update()\n RETURNS TRIGGER AS $$\n BEGIN\n NEW.fts_vector := to_tsvector('simple', COALESCE(NEW.text, ''));\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql;\n `);\n process.stderr.write(\n \"[pai-postgres] FTS config migration: trigger function now uses 'simple'\\n\"\n );\n }\n\n let lastId: string | null = resumeId || null;\n if (lastId !== null) {\n process.stderr.write(`[pai-postgres] FTS config migration: resuming from ${lastId}\\n`);\n }\n let totalUpdated = 0;\n let batchN = 0;\n while (true) {\n const sql = lastId === null\n ? `SELECT id FROM pai_chunks ORDER BY id LIMIT $1`\n : `SELECT id FROM pai_chunks WHERE id > $2 ORDER BY id LIMIT $1`;\n const params: Array<number | string> = lastId === null ? [BATCH_SIZE] : [BATCH_SIZE, lastId];\n const idRows = await pool.query<{ id: string }>(sql, params);\n if (idRows.rows.length === 0) break;\n\n const ids: string[] = idRows.rows.map((r) => r.id);\n for (let attempt = 1; ; attempt++) {\n try {\n const result = await pool.query(\n `UPDATE pai_chunks c SET fts_vector = to_tsvector('simple', COALESCE(c.text, ''))\n FROM (SELECT id FROM pai_chunks WHERE id = ANY($1::text[]) ORDER BY id FOR UPDATE) s\n WHERE c.id = s.id\n AND c.fts_vector IS DISTINCT FROM to_tsvector('simple', COALESCE(c.text, ''))`,\n [ids]\n );\n totalUpdated += result.rowCount ?? 0;\n break;\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (attempt >= MAX_ATTEMPTS || !code || !RETRYABLE_CODES.has(code)) throw err;\n await sleep(200 * attempt);\n }\n }\n lastId = ids[ids.length - 1];\n batchN++;\n if (batchN % 20 === 0) {\n // COMMENT takes no bind params; double quotes to escape the literal.\n await pool.query(\n `COMMENT ON COLUMN pai_chunks.fts_vector IS '${PROGRESS_PREFIX}${lastId.replace(/'/g, \"''\")}'`\n );\n process.stderr.write(\n `[pai-postgres] FTS config migration: ${totalUpdated} chunks rebuilt so far...\\n`\n );\n }\n if (idRows.rows.length < BATCH_SIZE) break;\n }\n\n await pool.query(\n `COMMENT ON COLUMN pai_chunks.fts_vector IS '${DONE_MARKER}'`\n );\n\n process.stderr.write(\n `[pai-postgres] FTS config migration: done, ${totalUpdated} chunks rebuilt\\n`\n );\n}\n","/**\n * IPC server and daemon entry point.\n * Owns: isSocketLive, startIpcServer, serve (exported).\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { createServer, connect, Socket, Server } from \"node:net\";\nimport { setPriority } from \"node:os\";\nimport { createStorageBackend, createRegistryBackend } from \"../../storage/factory.js\";\nimport { configureEmbeddingModel } from \"../../memory/embeddings.js\";\nimport { loadNotificationConfig } from \"../../notifications/config.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\nimport type { IpcRequest } from \"./types.js\";\nimport {\n setRegistryBackend,\n setStorageBackend,\n setDaemonConfig,\n setStartTime,\n setNotificationConfig,\n setShutdownRequested,\n indexInProgress,\n embedInProgress,\n indexSchedulerTimer,\n embedSchedulerTimer,\n cacheKeepaliveTimer,\n storageBackend,\n} from \"./state.js\";\nimport { startIndexScheduler, startEmbedScheduler, startRegistryScanScheduler, startWorkerSupervisor, startCacheKeepalive, startSessionKeepalive } from \"./scheduler.js\";\nimport { handleRequest, sendResponse } from \"./handler.js\";\nimport { loadQueue } from \"../../daemon/work-queue.js\";\nimport { startWorker, stopWorker } from \"../../daemon/work-queue-worker.js\";\nimport { migrateFtsConfig } from \"../../storage/postgres/fts-migration.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an existing socket file is actually being served by a live process.\n */\nfunction isSocketLive(path: string): Promise<boolean> {\n return new Promise((resolve) => {\n const client = connect(path);\n const timer = setTimeout(() => { client.destroy(); resolve(false); }, 500);\n client.on(\"connect\", () => { clearTimeout(timer); client.end(); resolve(true); });\n client.on(\"error\", () => { clearTimeout(timer); resolve(false); });\n });\n}\n\n/**\n * Start the Unix Domain Socket IPC server.\n */\nasync function startIpcServer(socketPath: string): Promise<Server> {\n if (existsSync(socketPath)) {\n const live = await isSocketLive(socketPath);\n if (live) {\n throw new Error(\"Another daemon is already running — socket is live. Aborting startup.\");\n }\n try {\n unlinkSync(socketPath);\n process.stderr.write(\"[pai-daemon] Removed stale socket file.\\n\");\n } catch {\n // If we can't remove it, bind will fail with a clear error\n }\n }\n\n const server = createServer((socket: Socket) => {\n let buffer = \"\";\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n\n if (line.trim() === \"\") continue;\n\n let request: IpcRequest;\n try {\n request = JSON.parse(line) as IpcRequest;\n } catch {\n sendResponse(socket, { id: \"?\", ok: false, error: \"Invalid JSON\" });\n socket.destroy();\n return;\n }\n\n handleRequest(request, socket).catch((e: unknown) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id: request.id, ok: false, error: msg });\n socket.destroy();\n });\n }\n });\n\n socket.on(\"error\", () => {\n // Client disconnected — nothing to do\n });\n });\n\n server.on(\"error\", (e) => {\n process.stderr.write(`[pai-daemon] IPC server error: ${e}\\n`);\n });\n\n server.listen(socketPath, () => {\n process.stderr.write(\n `[pai-daemon] IPC server listening on ${socketPath}\\n`\n );\n });\n\n return server;\n}\n\n// ---------------------------------------------------------------------------\n// Main daemon entry point\n// ---------------------------------------------------------------------------\n\nexport async function serve(config: PaiDaemonConfig): Promise<void> {\n setDaemonConfig(config);\n setStartTime(Date.now());\n\n setNotificationConfig(loadNotificationConfig());\n\n process.stderr.write(\"[pai-daemon] Starting daemon...\\n\");\n process.stderr.write(`[pai-daemon] Socket: ${config.socketPath}\\n`);\n process.stderr.write(`[pai-daemon] Storage backend: ${config.storageBackend}\\n`);\n const { notificationConfig } = await import(\"./state.js\");\n process.stderr.write(\n `[pai-daemon] Notification mode: ${notificationConfig.mode}\\n`\n );\n\n // Lower scheduling priority so the daemon yields CPU to interactive sessions\n try { setPriority(process.pid, 10); } catch { /* non-fatal */ }\n\n configureEmbeddingModel(config.embeddingModel);\n\n // Connect the registry backend in the background — non-blocking, since it\n // may take a few retries under Postgres (waitForPostgres: true never\n // silently falls back to SQLite, matching the federation backend below).\n // Callers that need it before it resolves (status, observation IPC methods)\n // already tolerate an unready backend via their own try/catch.\n void createRegistryBackend(config, { waitForPostgres: true })\n .then((backend) => {\n setRegistryBackend(backend);\n process.stderr.write(`[pai-daemon] Registry backend: ${backend.backendType}\\n`);\n })\n .catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Fatal: Could not open registry: ${msg}\\n`);\n process.exit(1);\n });\n\n // Start the IPC server immediately so `pai daemon status` answers even while\n // we are still waiting for the federation backend (e.g. Postgres coming up\n // after a reboot). The status handler tolerates a not-yet-ready backend.\n const server = await startIpcServer(config.socketPath);\n\n // Registry scan scheduler only needs the registry DB — safe to start now.\n startRegistryScanScheduler();\n\n // Worker supervision reads the workers ledger off disk and pushes events to\n // owning sessions — no backend dependency, so it starts with the IPC server.\n startWorkerSupervisor();\n\n // Cache keepalive spawns trivial workers on the workers config — also no\n // backend dependency (observations store opportunistically when Postgres\n // is up), so it starts with the IPC server too.\n startCacheKeepalive();\n\n // Interactive-session cache keepalive: also no backend dependency (reads\n // AIBroker + transcript files, writes the workers ledger + its own state\n // file), so it starts with the IPC server too.\n startSessionKeepalive();\n\n // Connect the federation backend in the background. When storageBackend is\n // \"postgres\" this retries forever (never silently falls back to SQLite), so\n // the boot race with Docker/Postgres resolves itself once Postgres is up.\n // Only then do we start the federation-dependent schedulers and work queue —\n // this also keeps the first index run from firing against an unready backend.\n void createStorageBackend(config, { waitForPostgres: true })\n .then((backend) => {\n setStorageBackend(backend);\n process.stderr.write(\n `[pai-daemon] Federation backend: ${backend.backendType}\\n`\n );\n\n startIndexScheduler();\n\n if (backend.supportsPostgresFeatures) {\n startEmbedScheduler();\n\n // Repair fts_vector rows written under the old ('english') trigger.\n // Async, non-blocking: real tables hold millions of rows, and a\n // rebuild must never delay daemon startup. Idempotent — safe to\n // resume from scratch if interrupted (see fts-migration.ts).\n void migrateFtsConfig(backend).catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] FTS config migration failed: ${msg}\\n`);\n });\n } else {\n process.stderr.write(\n \"[pai-daemon] Embed scheduler: disabled (SQLite backend)\\n\"\n );\n }\n\n // Work queue — load persisted items and start worker loop\n loadQueue();\n startWorker();\n })\n .catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Fatal: Could not open federation storage: ${msg}\\n`\n );\n process.exit(1);\n });\n\n const shutdown = async (signal: string): Promise<void> => {\n process.stderr.write(`\\n[pai-daemon] ${signal} received. Stopping.\\n`);\n\n setShutdownRequested(true);\n\n if (indexSchedulerTimer) clearInterval(indexSchedulerTimer);\n if (embedSchedulerTimer) clearInterval(embedSchedulerTimer);\n if (cacheKeepaliveTimer) clearInterval(cacheKeepaliveTimer);\n\n stopWorker();\n\n server.close();\n\n const SHUTDOWN_TIMEOUT_MS = 10_000;\n const POLL_INTERVAL_MS = 100;\n const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS;\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\n `[pai-daemon] Waiting for in-progress operations to finish ` +\n `(index=${indexInProgress}, embed=${embedInProgress})...\\n`\n );\n\n while ((indexInProgress || embedInProgress) && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Shutdown timeout reached — forcing exit.\\n\");\n } else {\n process.stderr.write(\"[pai-daemon] In-progress operations finished.\\n\");\n }\n }\n\n try {\n await storageBackend.close();\n } catch {\n // ignore\n }\n\n try {\n unlinkSync(config.socketPath);\n } catch {\n // ignore\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => { shutdown(\"SIGINT\").catch(() => process.exit(0)); });\n process.on(\"SIGTERM\", () => { shutdown(\"SIGTERM\").catch(() => process.exit(0)); });\n\n // Keep process alive\n await new Promise(() => {});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"daemon-DXlaVCkw.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/workers/keepalive.ts","../src/workers/supervision.ts","../src/daemon/daemon/scheduler.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.ts","../src/storage/postgres/fts-migration.ts","../src/daemon/daemon/server.ts"],"sourcesContent":["/**\n * config.ts — Notification config persistence helpers\n *\n * Reads and writes the `notifications` section of CONFIG_FILE (see\n * src/daemon/config.ts's paiConfigFilePath).\n * Deep-merges with defaults so partial configs work fine.\n *\n * This module is intentionally separate from the daemon's config loader\n * so it can be used standalone (e.g. from CLI commands).\n */\n\nimport { expandHome, readMainConfigRaw, writeMainConfigRaw } from \"../daemon/config.js\";\nimport type {\n NotificationConfig,\n ChannelConfigs,\n RoutingTable,\n NotificationMode,\n} from \"./types.js\";\nimport {\n DEFAULT_NOTIFICATION_CONFIG,\n DEFAULT_CHANNELS,\n DEFAULT_ROUTING,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Deep merge helper (same approach as daemon/config.ts)\n// ---------------------------------------------------------------------------\n\nfunction deepMerge<T extends object>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n const srcVal = source[key];\n if (srcVal === undefined || srcVal === null) continue;\n const tgtVal = (target as Record<string, unknown>)[key];\n if (\n typeof srcVal === \"object\" &&\n !Array.isArray(srcVal) &&\n typeof tgtVal === \"object\" &&\n tgtVal !== null &&\n !Array.isArray(tgtVal)\n ) {\n (result as Record<string, unknown>)[key] = deepMerge(\n tgtVal as object,\n srcVal as Record<string, unknown>\n );\n } else {\n (result as Record<string, unknown>)[key] = srcVal;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Load\n// ---------------------------------------------------------------------------\n\n/**\n * Load the notification config from the PAI config file.\n * Returns defaults merged with any stored values.\n */\nexport function loadNotificationConfig(): NotificationConfig {\n let parsed: Record<string, unknown>;\n try {\n parsed = readMainConfigRaw();\n } catch {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n const stored = parsed[\"notifications\"];\n if (!stored || typeof stored !== \"object\") {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n return deepMerge(\n DEFAULT_NOTIFICATION_CONFIG,\n stored as Record<string, unknown>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Save\n// ---------------------------------------------------------------------------\n\n/**\n * Persist the notification config by merging it into the existing\n * CONFIG_FILE. Creates the file if it does not exist.\n */\nexport function saveNotificationConfig(config: NotificationConfig): void {\n let full: Record<string, unknown> = {};\n try {\n full = readMainConfigRaw();\n } catch {\n // Start fresh if the file is unreadable\n }\n\n // Replace the notifications section\n full[\"notifications\"] = config;\n\n writeMainConfigRaw(full);\n}\n\n// ---------------------------------------------------------------------------\n// Patch helpers (used by the set command)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a partial update to the current notification config and persist it.\n * Returns the new merged config.\n */\nexport function patchNotificationConfig(patch: {\n mode?: NotificationMode;\n channels?: Partial<Partial<ChannelConfigs>>;\n routing?: Partial<RoutingTable>;\n}): NotificationConfig {\n const current = loadNotificationConfig();\n\n if (patch.mode !== undefined) {\n current.mode = patch.mode;\n }\n\n if (patch.channels) {\n current.channels = deepMerge(\n current.channels,\n patch.channels as Record<string, unknown>\n );\n }\n\n if (patch.routing) {\n current.routing = deepMerge(\n current.routing,\n patch.routing as Record<string, unknown>\n );\n }\n\n saveNotificationConfig(current);\n return current;\n}\n\n// Re-export defaults for convenience\nexport { DEFAULT_NOTIFICATION_CONFIG, DEFAULT_CHANNELS, DEFAULT_ROUTING };\nexport { expandHome };\n","/**\n * keepalive.ts — one cache-keepalive beat, shared by the daemon scheduler and\n * the probe script.\n *\n * The provider (GLM anthropic-compat endpoint) caches the worker prefix\n * implicitly — no cache_control blocks — so a trivial single-turn worker spawn\n * through the very same runWorker path re-arms that cache for the next real\n * worker: identical env, identical tool grants, identical WORKER_CONTRACT_PROMPT\n * by construction, because it IS a worker spawn.\n *\n * Measured 2026-09-18 (docs/cache-keepalive.md): the implicit cache only\n * serves spawns within seconds of the previous identical request and never\n * survived a 60 s beat cadence in the armed proof — hence the knob defaults\n * to off and arming it is the operator's explicit call.\n *\n * Deliberately NOT a hand-built claude invocation: duplicated prefix\n * construction is how this repo has shipped real breakage before (probeResume\n * x3, archiver x2). Everything flows through runWorker.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { runWorker, type StreamEvent } from \"./run.js\";\nimport { readWorkersSection } from \"./config.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { eventsPath, ledgerPath, workersLogDir } from \"./paths.js\";\nimport { loadStatus } from \"./status.js\";\n\n/**\n * The trivial prompt every cache beat (and probe) carries. Kept in one place\n * so the heartbeat arms exactly the prefix a verifying probe measures.\n */\nexport const HEARTBEAT_PROMPT = \"Reply with exactly one word: pong.\";\n\n/** The worker class a beat runs as — the cheapest spawn the config defines. */\nexport const KEEPALIVE_CLASS = \"simple\";\n\n/** Worker label of every beat, so `pai worker ps` can tell them apart. */\nexport const KEEPALIVE_LABEL = \"cache-keepalive\";\n\n/** Cadence a keepalive runs at for this config; 0 keeps it off. */\nexport function keepaliveSecs(workers: { enabled: boolean; cacheKeepaliveSecs: number }): number {\n return workers.enabled ? workers.cacheKeepaliveSecs : 0;\n}\n\n// ---------------------------------------------------------------------------\n// Transcript parsing — the only real usage on this endpoint is the final\n// result event (per-turn assistant usage is zeroed), so a beat and a probe\n// both read exactly that line.\n// ---------------------------------------------------------------------------\n\n/**\n * The LAST `type:\"result\"` event of a worker transcript, or null when the\n * file is missing, unreadable, or carries no result line (a failed run can\n * end without one).\n */\nexport function parseLastResultEvent(path: string): StreamEvent | null {\n let text: string;\n try {\n text = readFileSync(path, \"utf8\");\n } catch {\n return null;\n }\n let found: StreamEvent | null = null;\n for (const line of text.split(\"\\n\")) {\n if (!line.startsWith(\"{\")) continue;\n let e: StreamEvent;\n try {\n e = JSON.parse(line) as StreamEvent;\n } catch {\n continue;\n }\n if (e.type === \"result\") found = e;\n }\n return found;\n}\n\n/** One worker's cache metrics, as reported by its final result event. */\nexport interface BeatMetrics {\n id: string;\n ok: boolean;\n rc: number | null;\n provider: string | null;\n model: string | null;\n input_tokens: number | null;\n output_tokens: number | null;\n cache_read_input_tokens: number | null;\n cache_creation_input_tokens: number | null;\n duration_api_ms: number | null;\n duration_ms: number | null;\n num_turns: number | null;\n}\n\n/**\n * Metrics of one run from its final result event. Single-turn runs make the\n * cumulative result usage identical to first-turn usage, so a warm cache\n * shows up directly as cache_read_input_tokens > 0.\n */\nexport function extractBeatMetrics(\n m: Omit<BeatMetrics, \"input_tokens\" | \"output_tokens\" | \"cache_read_input_tokens\" | \"cache_creation_input_tokens\" | \"duration_api_ms\" | \"duration_ms\" | \"num_turns\">,\n ev: StreamEvent | null\n): BeatMetrics {\n const u = ev?.usage;\n return {\n ...m,\n input_tokens: u?.input_tokens ?? null,\n output_tokens: u?.output_tokens ?? null,\n cache_read_input_tokens: u?.cache_read_input_tokens ?? null,\n cache_creation_input_tokens: u?.cache_creation_input_tokens ?? null,\n duration_api_ms: ev?.duration_api_ms ?? null,\n duration_ms: ev?.duration_ms ?? null,\n num_turns: ev?.num_turns ?? null,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The beat\n// ---------------------------------------------------------------------------\n\nexport interface BeatOptions {\n /** Config path override (tests never touch the live config). */\n configPath?: string;\n /** Preset worker id (tests); a fresh stamp when omitted. */\n id?: string;\n}\n\n/** True while a beat's worker is still running — the overlap guard reads it. */\nlet beatInFlight = false;\n\nexport function beatBusy(): boolean {\n return beatInFlight;\n}\n\n/** `keepalive-<YYYYMMDD-HHMMSS>` — beats are minutes apart, seconds suffice. */\nexport function beatId(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `keepalive-${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}` +\n `-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`\n );\n}\n\n/**\n * One heartbeat: a single-turn trivial worker through the exact worker spawn\n * path, its result metrics parsed from the transcript, one WORKER-KEEPALIVE\n * ledger line. Returns null when another beat is still running (overlap\n * guard) — a skipped beat is free, a queue of them is not.\n *\n * The beat runs in the workers logDir: no session renders its status line\n * there, so the spawn adopts no spawnerSession and no iTerm identity, and\n * worker supervision has no owner to notify — heartbeats are infrastructure,\n * not somebody's workers.\n */\nexport async function runKeepaliveBeat(opts: BeatOptions = {}): Promise<BeatMetrics | null> {\n if (beatInFlight) return null;\n beatInFlight = true;\n try {\n const { workers: config } = readWorkersSection(opts.configPath);\n const logDir = workersLogDir(config);\n const id = opts.id ?? beatId();\n const rc = await runWorker({\n claudeArgs: [\"-p\", HEARTBEAT_PROMPT],\n className: KEEPALIVE_CLASS,\n label: KEEPALIVE_LABEL,\n noPane: true,\n quiet: true,\n worktreeFlag: false,\n id,\n cwd: logDir,\n });\n const status = loadStatus(logDir, id);\n const metrics = extractBeatMetrics(\n {\n id,\n ok: rc === 0,\n rc,\n provider: status?.provider ?? null,\n model: status?.model ?? null,\n },\n parseLastResultEvent(eventsPath(logDir, id))\n );\n appendLedger(ledgerPath(logDir), \"WORKER-KEEPALIVE\", {\n id,\n provider: metrics.provider,\n model: metrics.model,\n input_tokens: metrics.input_tokens,\n cache_read_input_tokens: metrics.cache_read_input_tokens,\n cache_creation_input_tokens: metrics.cache_creation_input_tokens,\n duration_api_ms: metrics.duration_api_ms,\n });\n return metrics;\n } finally {\n beatInFlight = false;\n }\n}\n","/**\n * supervision.ts — daemon-side worker supervision, event push not polling.\n *\n * An orchestrating session must keep an eye on its workers without sleeping\n * (the Worker skill forbids busy-waiting), the same way the AIBroker daemon\n * keeps an eye on sessions. The PAI daemon already runs all day; this module\n * gives it a tick that watches every worker's status file and pushes one-line\n * events to the owning session when something happens:\n *\n * finished — the run ended clean (state done, rc 0)\n * failed — the run ended badly (state failed/killed/lost, or rc != 0,\n * or the runner pid vanished while state was still running)\n * stalled — state running, runner alive, but no new turn for longer than\n * the stall threshold (PAI_WORKER_STALL_MINUTES, default 10)\n * thrashing — state running, runner alive, turning, but the last N tool\n * executions all failed (PAI_WORKER_THRASH_FAILS, default 5) —\n * a worker that keeps failing every command never goes quiet,\n * so the stall detector alone cannot see it\n * sleeping — state running, one Bash command sleeping N >= 60s with nothing\n * to wait on — under the stall threshold for runs like\n * `sleep 500` (8.3m), which would otherwise be invisible until\n * they cross it. The bounded-wait pattern (background a build or\n * test, then `sleep 60; ps` cycles) has live children and stays\n * quiet; stalled and thrashing remain the backstop for a child\n * that never finishes or dies silently\n *\n * The interactive chat pane (origin \"chat\") is never supervised: it is the\n * operator's own session, not a task worker. Its status file flips to done\n * when the session ends, and reporting that as \"worker finished\" to whoever\n * next opens the same tab is noise, not an outcome.\n *\n * Delivery, in order of preference, never both counted as required:\n * 1. AIBroker `send_to_session` into the orchestrator's terminal — the\n * one-line Agentish-style notification, arriving as a user turn without\n * the orchestrator asking for it. Needs the iTerm identity of the owner,\n * which the status line bridges into the session map (see scope.ts).\n * 2. An append-only event file, <logDir>/supervision/<owner>.events, one\n * JSON line per event. The UserPromptSubmit hook\n * (src/hooks/ts/user-prompt/worker-supervision.ts) surfaces undelivered\n * lines into the session that owns them, so the events reach the model\n * on its next turn even with AIBroker absent.\n *\n * Exactly-once: every event carries a deterministic id and the set of\n * delivered ids per worker is persisted (<logDir>/supervision/state.json), so\n * a daemon restart replays nothing. A first run with no state adopts every\n * already-terminal worker as delivered — supervision reports transitions it\n * observes, not history. Chain stages are separate workers with their own\n * status files and are supervised individually.\n *\n * No model is ever called from here — this is daemon code, and it must stay\n * that way: supervision that bills tokens to watch tokens is a bug.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { alive, isChatPane, loadStatuses, nowStamp, type WorkerStatus } from \"./status.js\";\nimport { itermForClaudeSession } from \"./scope.js\";\n\n// ---------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------\n\nexport type SupervisionKind = \"finished\" | \"failed\" | \"stalled\" | \"thrashing\" | \"sleeping\";\n\n/** The kinds that fire mid-run and re-arm on turn progress (see eventId). */\nconst MID_RUN_KINDS: readonly SupervisionKind[] = [\"stalled\", \"thrashing\", \"sleeping\"];\n\nexport interface SupervisionEvent {\n /** Deterministic dedup key — see eventId(). */\n id: string;\n /** nowStamp() at detection. */\n ts: string;\n kind: SupervisionKind;\n worker: string;\n label: string;\n /** Owning session key: the spawner's claude session id, or the iTerm uuid. */\n session: string;\n /** Exit code when the run reported one, else null. */\n rc: number | null;\n /** Minutes without a turn, for stalled events; else null. */\n stalledMin: number | null;\n /** Consecutive failed commands, for thrashing events; else null. */\n consecFails?: number | null;\n /** Seconds of the one sleeping command, for sleeping events; else null. */\n sleepSec?: number | null;\n /** The one-liner that reaches the orchestrator. */\n text: string;\n}\n\n/**\n * The owning session of a worker, or null when nobody claims it (and so\n * nothing to notify): the spawner's claude session id first — that is what\n * the hook knows a session by — else the AIBroker iTerm uuid.\n */\nexport function ownershipKey(s: WorkerStatus): string | null {\n if (s.spawnerSession) return s.spawnerSession;\n if (s.session?.id) return s.session.id;\n return null;\n}\n\nconst TERMINAL: readonly WorkerStatus[\"state\"][] = [\"done\", \"failed\", \"killed\", \"lost\"];\n\nfunction isTerminal(s: WorkerStatus): boolean {\n return TERMINAL.includes(s.state);\n}\n\n/** Age of a \"YYYY-MM-DD HH:MM:SS\" stamp in ms, NaN-safe (unparsable → 0). */\nfunction ageMs(ts: string, now: Date): number {\n const t = Date.parse(ts.replace(\" \", \"T\"));\n if (Number.isNaN(t)) return 0;\n return Math.max(0, now.getTime() - t);\n}\n\n/**\n * The stall threshold in minutes from PAI_WORKER_STALL_MINUTES (default 10).\n * Anything unparsible or non-positive falls back to the default — a broken\n * value must not turn supervision off or make it fire every tick.\n */\nexport const DEFAULT_STALL_MINUTES = 10;\n\nexport function stallMinutesFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.PAI_WORKER_STALL_MINUTES;\n if (raw === undefined || raw === \"\") return DEFAULT_STALL_MINUTES;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : DEFAULT_STALL_MINUTES;\n}\n\n/**\n * The consecutive-failure threshold from PAI_WORKER_THRASH_FAILS (default 5),\n * with the same broken-value tolerance as stallMinutesFromEnv.\n */\nexport const DEFAULT_THRASH_FAILS = 5;\n\nexport function thrashFailsFromEnv(env: NodeJS.ProcessEnv = process.env): number {\n const raw = env.PAI_WORKER_THRASH_FAILS;\n if (raw === undefined || raw === \"\") return DEFAULT_THRASH_FAILS;\n const n = Number(raw);\n return Number.isFinite(n) && n > 0 ? n : DEFAULT_THRASH_FAILS;\n}\n\n/**\n * Event id: `<worker>#<kind>` for the once-per-run terminal kinds, and\n * `<worker>#<kind>@<turns>` for the mid-run kinds (stalled, thrashing,\n * sleeping) — the turn count re-arms the id, so a worker that resumes and\n * hits the same condition again gets a second event while a restart cannot\n * replay the first.\n */\nexport function eventId(worker: string, kind: SupervisionKind, turns = 0): string {\n return MID_RUN_KINDS.includes(kind) ? `${worker}#${kind}@${turns}` : `${worker}#${kind}`;\n}\n\n/**\n * Live child pids of a runner via `pgrep -P` (its exit 1 means \"no\n * children\"). Any failure — here or in an injected stub — is caught at the\n * one call site in detectSupervisionEvents and reads as an empty list, so a\n * broken check degrades to firing the sleeping event, never to silencing it.\n */\nfunction pgrepChildren(pid: number): number[] {\n return execFileSync(\"pgrep\", [\"-P\", String(pid)], { encoding: \"utf8\" })\n .split(\"\\n\")\n .flatMap((l) => {\n const n = Number(l.trim());\n return Number.isInteger(n) && n > 0 ? [n] : [];\n });\n}\n\nexport interface DetectOptions {\n /** How long a running worker may go without a turn before \"stalled\". */\n stallMs: number;\n now: Date;\n /** Consecutive failed tool executions before \"thrashing\" (default 5). */\n thrashFails?: number;\n /** pid liveness — injected so tests need no real processes. */\n isAlive?: (pid: number) => boolean;\n /**\n * Live child pids of a runner — a sleeping worker with children is a\n * bounded wait on real work, not a stuck one. Injected so tests need no\n * pgrep; the default is pgrepChildren.\n */\n childPids?: (pid: number) => number[];\n /**\n * How stale `updated` must be before a dead pid counts as killed rather\n * than as a status write racing the tick.\n */\n deadGraceMs?: number;\n}\n\n/** One event's one-liner — the exact line the orchestrator reads. */\nfunction eventText(kind: SupervisionKind, s: WorkerStatus, rc: number | null, stalledMin: number | null): string {\n const tail = `- see pai worker replay ${s.id}`;\n if (kind === \"finished\") return `worker ${s.id} ${s.label} finished rc=0 ${tail}`;\n if (kind === \"stalled\") return `worker ${s.id} ${s.label} stalled ${stalledMin}m no turns ${tail}`;\n if (kind === \"thrashing\")\n return `worker ${s.id} ${s.label} thrashing: ${s.consecFails} consecutive failed commands ${tail}`;\n if (kind === \"sleeping\") return `worker ${s.id} ${s.label} sleeping ${s.sleepSec}s in one command ${tail}`;\n const why = rc === null ? \"runner gone\" : `rc=${rc}`;\n return `worker ${s.id} ${s.label} failed ${why} ${tail}`;\n}\n\n/**\n * Detect conditions across a ledger snapshot. Pure: statuses in, events out;\n * dedup against delivered ids is the caller's job (see filterUndelivered).\n * Workers without an owning session are skipped — nobody to notify.\n */\nexport function detectSupervisionEvents(\n statuses: WorkerStatus[],\n opts: DetectOptions\n): SupervisionEvent[] {\n const isAlive = opts.isAlive ?? alive;\n const deadGraceMs = opts.deadGraceMs ?? 15_000;\n const out: SupervisionEvent[] = [];\n for (const s of statuses) {\n const session = ownershipKey(s);\n if (!session) continue;\n // the operator's own pane: ending it is not a worker outcome, idling is\n // not a stall — nothing here is worth a push into anybody's terminal\n if (isChatPane(s)) continue;\n let kind: SupervisionKind | null = null;\n let rc: number | null = null;\n let stalledMin: number | null = null;\n if (isTerminal(s)) {\n // a finished run is state done with rc 0; every other terminal shape\n // (failed, killed, lost, done with a non-zero rc) is a failure\n if (s.state === \"done\" && (s.rc ?? 0) === 0) {\n kind = \"finished\";\n rc = 0;\n } else {\n kind = \"failed\";\n rc = s.state === \"lost\" ? null : s.rc;\n }\n } else if (!isAlive(s.pid) && ageMs(s.updated, opts.now) > deadGraceMs) {\n // state says running but the runner is gone (SIGKILL, crash) — the\n // status will never get its rc, so supervision is the only witness\n kind = \"failed\";\n rc = null;\n } else if (ageMs(s.updated, opts.now) > opts.stallMs) {\n kind = \"stalled\";\n stalledMin = Math.floor(ageMs(s.updated, opts.now) / 60_000);\n } else if ((s.consecFails ?? 0) >= (opts.thrashFails ?? DEFAULT_THRASH_FAILS)) {\n kind = \"thrashing\";\n } else if (s.sleepSec != null) {\n // bounded wait on real work: a sleep while a spawned child still runs\n // is healthy, so flag only a sleep with nothing to wait on. The check\n // runs once per sleeping candidate, only for a live pid; a failed\n // check reads as no children and fires. stalled (no turns) and\n // thrashing stay the backstop and never pass through here.\n let hasChildren = false;\n if (isAlive(s.pid)) {\n try {\n hasChildren = (opts.childPids ?? pgrepChildren)(s.pid).length > 0;\n } catch {\n hasChildren = false; // pgrep gone: degrade to today's behavior\n }\n }\n if (!hasChildren) kind = \"sleeping\";\n }\n if (!kind) continue;\n out.push({\n id: eventId(s.id, kind, s.turns),\n ts: nowStamp(opts.now),\n kind,\n worker: s.id,\n label: s.label,\n session,\n rc,\n stalledMin,\n ...(kind === \"thrashing\" ? { consecFails: s.consecFails ?? 0 } : {}),\n ...(kind === \"sleeping\" ? { sleepSec: s.sleepSec ?? 0 } : {}),\n text: eventText(kind, s, rc, stalledMin),\n });\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Delivered-id persistence — the restart guard\n// ---------------------------------------------------------------------------\n\nexport interface SupervisionState {\n /** worker id → event ids already written to that owner's event file. */\n delivered: Record<string, string[]>;\n}\n\n/** Cap on remembered workers: terminal and delivered is history, not state. */\nconst MAX_REMEMBERED_WORKERS = 500;\n\nexport function supervisionDirPath(logDir: string): string {\n return join(logDir, \"supervision\");\n}\n\nexport function supervisionStatePath(logDir: string): string {\n return join(supervisionDirPath(logDir), \"state.json\");\n}\n\nexport function supervisionEventsPath(logDir: string, session: string): string {\n return join(supervisionDirPath(logDir), `${session}.events`);\n}\n\n/** Receipts of events already pushed into a terminal, one event id per line. */\nexport function supervisionPushedPath(logDir: string, session: string): string {\n return join(supervisionDirPath(logDir), `${session}.pushed`);\n}\n\nexport function loadSupervisionState(path: string): { state: SupervisionState; fresh: boolean } {\n if (!existsSync(path)) return { state: { delivered: {} }, fresh: true };\n try {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as SupervisionState;\n if (parsed && typeof parsed === \"object\" && typeof parsed.delivered === \"object\") {\n return { state: parsed, fresh: false };\n }\n } catch {\n // a damaged state file replays at most one round of terminal events;\n // rewriting it is the recovery, not the problem\n }\n return { state: { delivered: {} }, fresh: true };\n}\n\nexport function saveSupervisionState(path: string, state: SupervisionState): void {\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tmp = `${path}.tmp`;\n writeFileSync(tmp, JSON.stringify(state), \"utf8\");\n renameSync(tmp, path);\n}\n\n/**\n * Drop the delivered ids of workers the ledger no longer lists (absent means\n * history: their status files are gone) and cap only that history — newest\n * ids last, worker ids sort chronologically — so the persisted state stays\n * bounded. A worker the ledger still lists must never lose its receipts:\n * pruning one un-marks its terminal events as delivered, and every save\n * re-arms them into an unbounded re-delivery loop (the 2026-09-25 incident:\n * one old chain stage re-pushed 7,393 times, because the cap of 500 bit into\n * a ledger of ~1,700 live statuses).\n */\nexport function pruneState(\n state: SupervisionState,\n statuses: WorkerStatus[],\n maxAbsent = MAX_REMEMBERED_WORKERS\n): SupervisionState {\n const present = new Set(statuses.map((s) => s.id));\n const entries = Object.entries(state.delivered);\n const byId = ([a]: [string, string[]], [b]: [string, string[]]) => (a < b ? -1 : a > b ? 1 : 0);\n const keptPresent = entries.filter(([id]) => present.has(id)).sort(byId);\n const absent = entries.filter(([id]) => !present.has(id)).sort(byId);\n // not slice(-maxAbsent): with maxAbsent 0 that returns the whole array\n const keptAbsent = absent.slice(Math.max(0, absent.length - maxAbsent));\n return { delivered: Object.fromEntries([...keptAbsent, ...keptPresent]) };\n}\n\n/**\n * First run: adopt every already-terminal worker as delivered. Supervision\n * reports the transitions it observes from now on, never a replay of every\n * worker that ever finished before it started.\n */\nexport function baselineState(statuses: WorkerStatus[]): SupervisionState {\n const delivered: Record<string, string[]> = {};\n for (const s of statuses) {\n if (!isTerminal(s) || !ownershipKey(s)) continue;\n delivered[s.id] = [eventId(s.id, \"finished\"), eventId(s.id, \"failed\")];\n }\n return { delivered };\n}\n\n/** The events whose ids the state has not seen yet. */\nexport function filterUndelivered(events: SupervisionEvent[], state: SupervisionState): SupervisionEvent[] {\n return events.filter((e) => !(state.delivered[e.worker] ?? []).includes(e.id));\n}\n\nexport function markDelivered(state: SupervisionState, events: SupervisionEvent[]): void {\n for (const e of events) {\n const ids = state.delivered[e.worker] ?? [];\n if (!ids.includes(e.id)) ids.push(e.id);\n state.delivered[e.worker] = ids;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Delivery\n// ---------------------------------------------------------------------------\n\n/** Where an event's push goes, resolved from the worker's own identities. */\nexport interface PushTarget {\n claudeSession: string | null;\n iterm: string | null;\n}\n\nexport type PushFn = (logDir: string, target: PushTarget, ev: SupervisionEvent) => Promise<boolean>;\n\n/**\n * Default push: AIBroker `send_to_session` into the orchestrator's terminal,\n * using the iTerm uuid the worker recorded (`session.id`) or the one the\n * session map bridges for the spawner's claude session. A timeout still\n * delivered (see aibroker-client) counts as delivered; every failure is\n * reported as not-delivered so only the event file carries the event.\n */\nexport const aibrokerPush: PushFn = async (logDir, target, ev) => {\n const iterm =\n target.iterm ?? (target.claudeSession ? itermForClaudeSession(logDir, target.claudeSession) : null);\n if (!iterm) return false;\n try {\n const { sendToSession } = await import(\"../cli/lib/aibroker-client.js\");\n const r = await sendToSession(iterm, ev.text);\n return r.ok || r.timedOut === true;\n } catch {\n return false;\n }\n};\n\n/**\n * Whether an event should be relayed into the owner's terminal. The mid-run\n * kinds (stalled, thrashing, sleeping) ALWAYS relay: a json/stream-json\n * caller knows the run's result at exit but has no other way to hear\n * mid-run trouble. Only the terminal kinds (finished/failed) skip the relay\n * for those callers — they read the run's result itself, and the harness\n * already notifies them on process exit, so the one-liner would cost a full\n * extra turn for information they already have. Interactive/pane launches\n * and the default text format are never skipped.\n */\nexport function shouldRelay(\n kind: SupervisionKind,\n outputFormat: WorkerStatus[\"outputFormat\"] | undefined\n): boolean {\n if (MID_RUN_KINDS.includes(kind)) return true;\n return outputFormat !== \"json\" && outputFormat !== \"stream-json\";\n}\n\nfunction appendEventFile(logDir: string, ev: SupervisionEvent): void {\n const path = supervisionEventsPath(logDir, ev.session);\n const dir = supervisionDirPath(logDir);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(path, JSON.stringify(ev) + \"\\n\", \"utf8\");\n}\n\n/** Record that an event reached its owner's terminal — the hook then skips it. */\nfunction appendPushReceipt(logDir: string, ev: SupervisionEvent): void {\n try {\n appendFileSync(supervisionPushedPath(logDir, ev.session), `${ev.id}\\n`, \"utf8\");\n } catch {\n // a missing receipt only means the hook may repeat the line\n }\n}\n\nexport interface TickOptions {\n now?: Date;\n stallMs?: number;\n /** Consecutive-failure threshold for thrashing (default: PAI_WORKER_THRASH_FAILS). */\n thrashFails?: number;\n isAlive?: (pid: number) => boolean;\n /** Injectable for tests; production pushes through AIBroker. */\n push?: PushFn;\n /** Preloaded map, so a test (or a batch of ticks) can share one read. */\n statuses?: WorkerStatus[];\n}\n\nexport interface TickResult {\n events: SupervisionEvent[];\n /** Events that also reached a terminal through the push channel. */\n pushed: SupervisionEvent[];\n}\n\n/**\n * One supervision pass over the ledger: detect, append each new event to its\n * owner's event file, push it best effort, persist the delivered ids. The\n * event file is the durable record — an event is marked delivered only after\n * its line landed there, so a failed tick retries, a repeated one replays\n * nothing.\n */\nexport async function runSupervisionTick(logDir: string, opts: TickOptions = {}): Promise<TickResult> {\n const now = opts.now ?? new Date();\n const statuses = opts.statuses ?? loadStatuses(logDir);\n const statePath = supervisionStatePath(logDir);\n const loaded = loadSupervisionState(statePath);\n let state = loaded.state;\n if (loaded.fresh) state = baselineState(statuses);\n\n const pending = filterUndelivered(\n detectSupervisionEvents(statuses, {\n stallMs: opts.stallMs ?? DEFAULT_STALL_MINUTES * 60_000,\n thrashFails: opts.thrashFails ?? thrashFailsFromEnv(),\n now,\n ...(opts.isAlive ? { isAlive: opts.isAlive } : {}),\n }),\n state\n );\n\n const written: SupervisionEvent[] = [];\n for (const ev of pending) {\n appendEventFile(logDir, ev);\n written.push(ev);\n }\n\n const push = opts.push ?? aibrokerPush;\n const pushed: SupervisionEvent[] = [];\n for (const ev of written) {\n // the owner's iTerm identity is in its worker status, not the event\n const owner = statuses.find((s) => s.id === ev.worker);\n if (!shouldRelay(ev.kind, owner?.outputFormat)) continue; // caller reads its own JSON result\n const target: PushTarget = {\n claudeSession: owner?.spawnerSession ?? null,\n iterm: owner?.session?.id ?? null,\n };\n let ok = false;\n try {\n ok = await push(logDir, target, ev);\n } catch {\n ok = false;\n }\n if (ok) {\n pushed.push(ev);\n appendPushReceipt(logDir, ev);\n }\n }\n\n markDelivered(state, written);\n saveSupervisionState(statePath, pruneState(state, statuses));\n return { events: written, pushed };\n}\n","/**\n * Index, embed, vault index, and registry scan schedulers for the PAI daemon.\n * Exports run* functions (also called on-demand by the IPC handler)\n * and the start* functions invoked once at daemon startup.\n */\n\nimport { readWorkersSection } from \"../../workers/config.js\";\nimport { keepaliveSecs, runKeepaliveBeat, type BeatMetrics } from \"../../workers/keepalive.js\";\nimport { workersLogDir } from \"../../workers/paths.js\";\nimport { runSupervisionTick, stallMinutesFromEnv, thrashFailsFromEnv } from \"../../workers/supervision.js\";\nimport { runSessionKeepaliveTick } from \"../session-keepalive.js\";\nimport {\n registryBackend,\n storageBackend,\n daemonConfig,\n indexInProgress,\n embedInProgress,\n vaultIndexInProgress,\n shutdownRequested,\n setIndexInProgress,\n setLastIndexTime,\n setIndexSchedulerTimer,\n setEmbedInProgress,\n setLastEmbedTime,\n setEmbedSchedulerTimer,\n setVaultIndexInProgress,\n setLastVaultIndexTime,\n setCacheKeepaliveTimer,\n} from \"./state.js\";\n\n// ---------------------------------------------------------------------------\n// Index scheduler\n// ---------------------------------------------------------------------------\n\n/** Minimum interval between vault index runs (30 minutes). */\nconst VAULT_INDEX_MIN_INTERVAL_MS = 30 * 60 * 1000;\n\n/**\n * Run a full index pass. Guards against overlapping runs with indexInProgress.\n * Called both by the scheduler and by the index_now IPC method.\n */\nexport async function runIndex(): Promise<void> {\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index already in progress, skipping.\\n\");\n return;\n }\n\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed in progress, deferring index run.\\n\");\n return;\n }\n\n setIndexInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled index run...\\n\");\n\n const { projects, result } = await storageBackend.indexAll(registryBackend);\n const elapsed = Date.now() - t0;\n setLastIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Index complete: ${projects} projects, ` +\n `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n `(${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Index error: ${msg}\\n`);\n } finally {\n setIndexInProgress(false);\n }\n}\n\n/**\n * Run a vault index pass. Guards against overlapping runs with vaultIndexInProgress.\n * Skips if no vaultPath is configured, or if project index/embed is in progress.\n */\nexport async function runVaultIndex(): Promise<void> {\n if (!daemonConfig.vaultPath) return;\n\n if (vaultIndexInProgress) {\n process.stderr.write(\"[pai-daemon] Vault index already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Index/embed in progress, deferring vault index.\\n\");\n return;\n }\n\n // Import lastVaultIndexTime from state (re-read each call since it may change)\n const { lastVaultIndexTime } = await import(\"./state.js\");\n if (lastVaultIndexTime > 0 && Date.now() - lastVaultIndexTime < VAULT_INDEX_MIN_INTERVAL_MS) {\n return;\n }\n\n let vaultProjectId = daemonConfig.vaultProjectId;\n if (!vaultProjectId) {\n const row = await registryBackend.getProjectByRootPath(daemonConfig.vaultPath!);\n vaultProjectId = row?.id ?? 999;\n if (!row) {\n process.stderr.write(\"[pai-daemon] Vault not in project registry — using synthetic project ID 999.\\n\");\n }\n }\n\n setVaultIndexInProgress(true);\n const t0 = Date.now();\n\n process.stderr.write(\"[pai-daemon] Starting vault index run...\\n\");\n\n try {\n const { indexVault } = await import(\"../../memory/vault-indexer.js\");\n const r = await indexVault(storageBackend, vaultProjectId, daemonConfig.vaultPath!);\n const elapsed = Date.now() - t0;\n setLastVaultIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Vault index complete: ${r.filesIndexed} files, ` +\n `${r.linksExtracted} links, ${r.deadLinksFound} dead, ` +\n `${r.orphansFound} orphans (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Vault index error: ${msg}\\n`);\n } finally {\n setVaultIndexInProgress(false);\n }\n}\n\n/**\n * Start the periodic index scheduler. Runs an initial pass 2 seconds after startup.\n */\nexport function startIndexScheduler(): void {\n const intervalMs = daemonConfig.indexIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Index scheduler: every ${daemonConfig.indexIntervalSecs}s\\n`\n );\n\n // Indexing and embedding are mutually exclusive (each defers to the other),\n // and an index pass regularly outlasts the index interval. Left to two\n // independent timers, the embed tick therefore lands while indexing is in\n // progress and is dropped — observed live on 2026-08-01 as six consecutive\n // index passes with no embed pass at all, and a 143k-chunk embedding backlog.\n // Chaining embed onto the end of every index pass makes the alternation\n // guaranteed rather than a race: index, then vault, then embed, every cycle.\n const cycle = (label: string, withEmbed: boolean) =>\n runIndex()\n .then(() => runVaultIndex())\n .then(() => (withEmbed ? runEmbed() : undefined))\n .catch((e) => {\n process.stderr.write(`[pai-daemon] ${label} index error: ${e}\\n`);\n });\n\n // The startup pass indexes but must NOT embed unless embedOnStartup says so.\n // startEmbedScheduler already refuses a startup embed for exactly that reason;\n // chaining runEmbed onto this pass reinstated the same CPU storm through the\n // back door. Seen live after a reboot: \"Startup embed pass skipped\n // (embedOnStartup=false)\" in the log, and minutes later the same boot running\n // a 5000-chunk pass at ~450% CPU. A guard one scheduler honours and another\n // walks around is not a guard.\n // With a maintenance hour configured there is no startup pass at all. Every\n // restart otherwise buys itself a full index run, so on a machine that is\n // rebooted or has the daemon reloaded during the day the \"maintenance runs at\n // night\" guarantee is worth nothing: the heavy work simply follows the\n // restarts around. Indexing waits for the window like everything else.\n if (daemonConfig.maintenanceHour === undefined) {\n setTimeout(() => void cycle(\"Startup\", daemonConfig.embedOnStartup), 2_000);\n } else {\n process.stderr.write(\n \"[pai-daemon] Startup index pass skipped (maintenanceHour is set).\\n\"\n );\n }\n\n // Anchor the recurring cycle to a wall-clock hour when one is configured.\n // setInterval alone counts from daemon start, so a machine rebooted at noon\n // gets its \"daily\" maintenance at noon every day thereafter — the interval\n // says how often, never when. Without an anchor there is no such thing as a\n // night-only pass, however long the interval.\n const firstDelayMs = msUntilNextAnchor(daemonConfig.maintenanceHour, intervalMs);\n if (daemonConfig.maintenanceHour !== undefined) {\n process.stderr.write(\n `[pai-daemon] Index scheduler anchored to ${String(daemonConfig.maintenanceHour).padStart(2, \"0\")}:00 local ` +\n `(next pass in ${Math.round(firstDelayMs / 60_000)} min)\\n`\n );\n }\n\n let timer: ReturnType<typeof setInterval>;\n const startTimer = () => {\n timer = setInterval(() => void cycle(\"Scheduled\", true), intervalMs);\n if (timer.unref) timer.unref();\n setIndexSchedulerTimer(timer);\n };\n\n const first = setTimeout(() => {\n void cycle(\"Scheduled\", true);\n startTimer();\n }, firstDelayMs);\n if (first.unref) first.unref();\n}\n\n/**\n * Milliseconds until the next occurrence of `hour`:00 local time.\n * Falls back to the plain interval when no anchor hour is configured.\n */\nfunction msUntilNextAnchor(hour: number | undefined, intervalMs: number): number {\n if (hour === undefined || !Number.isInteger(hour) || hour < 0 || hour > 23) {\n return intervalMs;\n }\n const now = new Date();\n const next = new Date(now);\n next.setHours(hour, 0, 0, 0);\n if (next <= now) next.setDate(next.getDate() + 1);\n return next.getTime() - now.getTime();\n}\n\n// ---------------------------------------------------------------------------\n// Embed scheduler\n// ---------------------------------------------------------------------------\n\n/**\n * Run an embedding pass for all unembedded chunks (Postgres backend only).\n */\nexport async function runEmbed(): Promise<void> {\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index in progress, deferring embed pass.\\n\");\n return;\n }\n\n if (!storageBackend.supportsPostgresFeatures) {\n return;\n }\n\n setEmbedInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled embed pass...\\n\");\n\n const projectNames = new Map<number, string>();\n try {\n const rows = await registryBackend.listProjects({ status: \"active\" });\n for (const r of rows) projectNames.set(r.id, r.slug);\n } catch { /* registry unavailable — IDs will be used instead */ }\n\n const { embedChunksWithBackend } = await import(\"../../memory/indexer-backend.js\");\n // Explicit budgets so one embed phase can never outlast an index cycle.\n // Backlogs drain across passes, not within one.\n const count = await embedChunksWithBackend(\n storageBackend,\n () => shutdownRequested,\n projectNames,\n // Indexing settles to a few chunks per pass once caught up, so the daemon\n // is idle most of the cycle and the embed budget is what actually sets\n // drain rate. Measured 450 chunks per 90s against real (long) chunks, so\n // a backlog only clears at roughly the budget's share of the cycle. What\n // broke the starvation was bounding the pass at all, not this number —\n // it can be raised freely as long as it stays finite.\n { maxMillis: 240_000 },\n );\n\n // Vault chunks (project 999) are indexed through the same storageBackend\n // as everything else (indexVault() writes via backend.insertChunks()), so\n // the embedChunksWithBackend() call above already embeds them. A vault-only\n // SQLite pass here would open a stray federation database even under Postgres.\n\n const elapsed = Date.now() - t0;\n setLastEmbedTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Embed pass complete: ${count} chunks embedded (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Embed error: ${msg}\\n`);\n } finally {\n setEmbedInProgress(false);\n }\n}\n\n/**\n * Start the periodic embed scheduler. Initial run is 60 seconds after startup.\n */\nexport function startEmbedScheduler(): void {\n const intervalMs = daemonConfig.embedIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Embed scheduler: every ${daemonConfig.embedIntervalSecs}s\\n`\n );\n\n // Startup embed pass — OFF unless explicitly enabled.\n //\n // This used to run unconditionally 60s after every start, ignoring\n // embedIntervalSecs entirely. With a large backlog that makes every restart a\n // CPU storm: the interval was set to a day to stop the machine being bogged\n // down, the daemon was restarted, and it immediately began a 240-second pass\n // anyway. Throttling the recurring timer while leaving this armed means the\n // throttle silently does nothing for anyone who restarts.\n //\n // Measured backlog when this was found: 1,868,098 unembedded chunks at ~5.7\n // chunks/s — about 91 days of work that a restart would resume unbidden.\n if (daemonConfig.embedOnStartup) {\n setTimeout(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Startup embed error: ${e}\\n`);\n });\n }, 60_000);\n } else {\n process.stderr.write(\n \"[pai-daemon] Startup embed pass skipped (embedOnStartup=false).\\n\"\n );\n }\n\n // With a maintenance hour configured the index cycle already chains an embed\n // pass onto every anchored run, so this timer would only add a second pass\n // that counts from daemon start — observed as \"every 86400s\" armed at a\n // 20:08 boot, i.e. a daily daytime pass on a machine configured for 03:00.\n // The interval says how often, never when; without an anchor it cannot\n // deliver a night-only pass, so it is not armed at all.\n if (daemonConfig.maintenanceHour !== undefined) {\n process.stderr.write(\n \"[pai-daemon] Standalone embed timer not armed (maintenanceHour is set; \" +\n \"embed runs at the end of each anchored index cycle).\\n\"\n );\n return;\n }\n\n const timer = setInterval(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled embed error: ${e}\\n`);\n });\n }, intervalMs);\n\n if (timer.unref) timer.unref();\n setEmbedSchedulerTimer(timer);\n}\n\n// ---------------------------------------------------------------------------\n// Registry scan scheduler\n// ---------------------------------------------------------------------------\n\nconst REGISTRY_SCAN_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes\nconst REGISTRY_SCAN_STARTUP_DELAY_MS = 10_000; // 10 seconds after daemon start\n\n/**\n * Start the periodic registry scan scheduler.\n * First scan runs 10 seconds after startup (enough time for IPC to stabilise).\n * Subsequent scans run every 30 minutes.\n * Uses the work-queue so the scan runs on the worker thread without blocking IPC.\n */\nexport function startRegistryScanScheduler(): void {\n process.stderr.write(\n \"[pai-daemon] Registry scan scheduler: every 30min (first in 10s)\\n\"\n );\n\n setTimeout(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Startup registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_STARTUP_DELAY_MS);\n\n const timer = setInterval(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_INTERVAL_MS);\n\n if (timer.unref) timer.unref();\n}\n\n// ---------------------------------------------------------------------------\n// Worker supervision scheduler\n// ---------------------------------------------------------------------------\n\n/** How often the daemon looks at the worker ledger for supervision events. */\nconst SUPERVISION_TICK_MS = 30_000;\n\n/** First supervision pass waits for the first statuses to exist at all. */\nconst SUPERVISION_STARTUP_DELAY_MS = 15_000;\n\n/**\n * Start the worker supervisor: one tick every 30 s over the workers ledger\n * (src/workers/supervision.ts) that pushes finished/failed/stalled events to\n * the owning session — the daemon-side replacement for orchestrator polling.\n * Daemon code only: no model call has any business being here, and none is\n * made. Disabled workers disable the supervisor with it.\n */\nexport function startWorkerSupervisor(): void {\n let enabled = false;\n try {\n enabled = readWorkersSection().workers.enabled;\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Worker supervisor: not started (${e instanceof Error ? e.message : String(e)})\\n`\n );\n return;\n }\n if (!enabled) {\n process.stderr.write(\"[pai-daemon] Worker supervisor: disabled (workers are off)\\n\");\n return;\n }\n\n const tick = () => {\n runSupervisionTick(workersLogDir(readWorkersSection().workers), {\n stallMs: stallMinutesFromEnv() * 60_000,\n thrashFails: thrashFailsFromEnv(),\n })\n .then((r) => {\n for (const ev of r.events) {\n process.stderr.write(`[pai-daemon] Supervision: ${ev.text}\\n`);\n }\n })\n .catch((e) => {\n // a broken tick must never take the daemon down with it\n process.stderr.write(\n `[pai-daemon] Supervision tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n };\n\n const first = setTimeout(tick, SUPERVISION_STARTUP_DELAY_MS);\n if (first.unref) first.unref();\n const timer = setInterval(tick, SUPERVISION_TICK_MS);\n if (timer.unref) timer.unref();\n process.stderr.write(\n `[pai-daemon] Worker supervisor: every ${Math.round(SUPERVISION_TICK_MS / 1000)}s\\n`\n );\n}\n\n// ---------------------------------------------------------------------------\n// Worker prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** First beat waits a while after boot; the daemon start is itself a warm request. */\nconst KEEPALIVE_STARTUP_DELAY_MS = 60_000;\n\n/**\n * Start the cache keepalive: every workers.cacheKeepaliveSecs one trivial\n * single-turn worker (src/workers/keepalive.ts) re-arms the provider's\n * implicit prompt cache so real worker spawns start warm. Needs no storage\n * backend — it starts with the IPC server, not after the federation connect.\n * 0 (or workers off) keeps it off. A failed beat is logged and swallowed.\n */\nexport function startCacheKeepalive(opts: {\n configPath?: string;\n beat?: () => Promise<BeatMetrics | null>;\n} = {}): void {\n let workers: ReturnType<typeof readWorkersSection>[\"workers\"];\n try {\n workers = readWorkersSection(opts.configPath).workers;\n } catch (e) {\n process.stderr.write(\n `[pai-daemon] Cache keepalive: not started (${e instanceof Error ? e.message : String(e)})\\n`\n );\n return;\n }\n if (!workers.enabled) {\n process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (workers are off)\\n\");\n return;\n }\n const secs = keepaliveSecs(workers);\n if (!secs) {\n process.stderr.write(\"[pai-daemon] Cache keepalive: disabled (cacheKeepaliveSecs is 0)\\n\");\n return;\n }\n\n let notedNoPostgres = false;\n const supportsObservations = () => {\n if (storageBackend?.supportsPostgresFeatures) return true;\n if (!notedNoPostgres) {\n notedNoPostgres = true;\n process.stderr.write(\n \"[pai-daemon] Cache keepalive: observation rows need Postgres — ledger line only\\n\"\n );\n }\n return false;\n };\n\n const beat = async (): Promise<void> => {\n const m = await (opts.beat ?? runKeepaliveBeat)();\n if (!m) return; // overlap guard: previous beat still running\n if (!supportsObservations()) return; // ledger line already written by the beat itself\n try {\n await storageBackend.storeObservationWithProject(registryBackend, {\n session_id: m.id,\n type: \"change\",\n title: `cache keepalive ${m.ok ? \"ok\" : \"failed\"} (cache_read=${m.cache_read_input_tokens}, api_ms=${m.duration_api_ms})`,\n tool_name: \"cache-keepalive\",\n narrative: `beat ${m.id}: input=${m.input_tokens} cache_read=${m.cache_read_input_tokens} cache_creation=${m.cache_creation_input_tokens} duration_api_ms=${m.duration_api_ms}`,\n tool_input_summary: undefined,\n files_read: [],\n files_modified: [],\n concepts: [],\n });\n } catch (e) {\n // a failed observation must never fail the beat\n process.stderr.write(\n `[pai-daemon] Cache keepalive observation error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n }\n };\n\n const first = setTimeout(() => {\n beat().catch((e) => {\n process.stderr.write(\n `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n }, KEEPALIVE_STARTUP_DELAY_MS);\n if (first.unref) first.unref();\n\n const timer = setInterval(() => {\n beat().catch((e) => {\n process.stderr.write(\n `[pai-daemon] Cache keepalive beat error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n }, secs * 1000);\n if (timer.unref) timer.unref();\n setCacheKeepaliveTimer(timer);\n process.stderr.write(`[pai-daemon] Cache keepalive: every ${secs}s\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// Interactive-session prompt-cache keepalive scheduler\n// ---------------------------------------------------------------------------\n\n/** Tick cadence: coarse enough to be cheap, fine enough that a session\n * crossing idleMinutes gets beaten within a minute of becoming eligible. */\nconst SESSION_KEEPALIVE_TICK_MS = 60_000;\n\n/**\n * Start the interactive-session cache keepalive: every 60s, beat every live\n * session that has gone idle past `sessions.cacheKeepalive.idleMinutes`\n * (src/daemon/session-keepalive.ts). Off unless\n * `sessions.cacheKeepalive.enabled` is true — no hot-reload, matching\n * startCacheKeepalive above: config changes need a daemon restart.\n */\nexport function startSessionKeepalive(): void {\n const config = daemonConfig.sessions.cacheKeepalive;\n if (!config.enabled) {\n process.stderr.write(\"[pai-daemon] Session keepalive: disabled (sessions.cacheKeepalive.enabled is false)\\n\");\n return;\n }\n\n const tick = () => {\n runSessionKeepaliveTick(config).catch((e) => {\n process.stderr.write(\n `[pai-daemon] Session keepalive tick error: ${e instanceof Error ? e.message : String(e)}\\n`\n );\n });\n };\n\n const timer = setInterval(tick, SESSION_KEEPALIVE_TICK_MS);\n if (timer.unref) timer.unref();\n process.stderr.write(\n `[pai-daemon] Session keepalive: every ${SESSION_KEEPALIVE_TICK_MS / 1000}s ` +\n `(idle >= ${config.idleMinutes}min, hours ${config.activeHours}, max ${config.maxBeats} beats/stretch)\\n`\n );\n}\n","/**\n * Tool dispatcher — maps IPC method names to PAI tool functions.\n */\n\nimport {\n toolMemorySearch,\n toolMemoryGet,\n toolMemoryOutline,\n toolProjectInfo,\n toolProjectList,\n toolSessionList,\n toolRegistrySearch,\n toolProjectDetect,\n toolProjectHealth,\n toolProjectTodo,\n toolSessionRoute,\n toolMemoryWakeup,\n toolMemoryTaxonomy,\n toolMemoryFeedback,\n toolMemoryKgSearch,\n} from \"../../mcp/tools.js\";\nimport { detectTopicShift } from \"../../topics/detector.js\";\nimport { registryBackend, storageBackend, daemonConfig } from \"./state.js\";\n\n/**\n * Dispatch an IPC tool call to the appropriate tool function.\n * Returns the tool result or throws on unknown/failed methods.\n */\nexport async function dispatchTool(\n method: string,\n params: Record<string, unknown>\n): Promise<unknown> {\n // Cast through unknown to satisfy TypeScript's strict overlap check on\n // Record<string, unknown> → specific param types. Runtime validation is\n // the responsibility of each tool function (they surface errors gracefully).\n const p = params as unknown;\n\n switch (method) {\n case \"memory_search\":\n return toolMemorySearch(registryBackend, storageBackend, p as Parameters<typeof toolMemorySearch>[2]);\n\n case \"memory_get\":\n return toolMemoryGet(registryBackend, p as Parameters<typeof toolMemoryGet>[1]);\n\n case \"memory_outline\":\n return toolMemoryOutline(registryBackend, p as Parameters<typeof toolMemoryOutline>[1]);\n\n case \"project_info\":\n return toolProjectInfo(registryBackend, p as Parameters<typeof toolProjectInfo>[1]);\n\n case \"project_list\":\n return toolProjectList(registryBackend, p as Parameters<typeof toolProjectList>[1]);\n\n case \"session_list\":\n return toolSessionList(registryBackend, p as Parameters<typeof toolSessionList>[1]);\n\n case \"registry_search\":\n return toolRegistrySearch(registryBackend, p as Parameters<typeof toolRegistrySearch>[1]);\n\n case \"project_detect\":\n return toolProjectDetect(p as Parameters<typeof toolProjectDetect>[0]);\n\n case \"project_health\":\n return toolProjectHealth(registryBackend, p as Parameters<typeof toolProjectHealth>[1]);\n\n case \"project_todo\":\n return toolProjectTodo(registryBackend, p as Parameters<typeof toolProjectTodo>[1]);\n\n case \"memory_wakeup\":\n return toolMemoryWakeup(registryBackend, p as Parameters<typeof toolMemoryWakeup>[1]);\n\n case \"memory_taxonomy\":\n return toolMemoryTaxonomy(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTaxonomy>[2]);\n\n case \"topic_check\":\n return detectTopicShift(\n registryBackend,\n storageBackend,\n p as Parameters<typeof detectTopicShift>[2]\n );\n\n case \"session_auto_route\":\n return toolSessionRoute(\n registryBackend,\n storageBackend,\n p as Parameters<typeof toolSessionRoute>[2]\n );\n\n case \"zettel_explore\":\n case \"zettel_health\":\n case \"zettel_surprise\":\n case \"zettel_suggest\":\n case \"zettel_converse\":\n case \"zettel_themes\":\n case \"zettel_god_notes\":\n case \"zettel_communities\": {\n const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import(\"../../mcp/tools.js\");\n\n switch (method) {\n case \"zettel_explore\": return toolZettelExplore(storageBackend, p as Parameters<typeof toolZettelExplore>[1]);\n case \"zettel_health\": return toolZettelHealth(storageBackend, p as Parameters<typeof toolZettelHealth>[1]);\n case \"zettel_surprise\": return toolZettelSurprise(storageBackend, p as Parameters<typeof toolZettelSurprise>[1]);\n case \"zettel_suggest\": return toolZettelSuggest(storageBackend, p as Parameters<typeof toolZettelSuggest>[1]);\n case \"zettel_converse\": return toolZettelConverse(storageBackend, p as Parameters<typeof toolZettelConverse>[1]);\n case \"zettel_themes\": return toolZettelThemes(storageBackend, p as Parameters<typeof toolZettelThemes>[1]);\n case \"zettel_god_notes\": return toolZettelGodNotes(storageBackend, p as Parameters<typeof toolZettelGodNotes>[1]);\n case \"zettel_communities\": return toolZettelCommunities(storageBackend, p as Parameters<typeof toolZettelCommunities>[1]);\n }\n break;\n }\n\n case \"graph_clusters\": {\n const { handleGraphClusters } = await import(\"../../graph/clusters.js\");\n return handleGraphClusters(storageBackend, p as Parameters<typeof handleGraphClusters>[1]);\n }\n\n case \"graph_neighborhood\": {\n const { handleGraphNeighborhood } = await import(\"../../graph/neighborhood.js\");\n return handleGraphNeighborhood(storageBackend, p as Parameters<typeof handleGraphNeighborhood>[1]);\n }\n\n case \"graph_note_context\": {\n const { handleGraphNoteContext } = await import(\"../../graph/note-context.js\");\n return handleGraphNoteContext(storageBackend, p as Parameters<typeof handleGraphNoteContext>[1]);\n }\n\n case \"graph_trace\": {\n const { handleGraphTrace } = await import(\"../../graph/trace.js\");\n return handleGraphTrace(storageBackend, p as Parameters<typeof handleGraphTrace>[1]);\n }\n\n case \"graph_latent_ideas\": {\n const { handleGraphLatentIdeas } = await import(\"../../graph/latent-ideas.js\");\n return handleGraphLatentIdeas(storageBackend, p as Parameters<typeof handleGraphLatentIdeas>[1]);\n }\n\n case \"idea_materialize\": {\n const { handleIdeaMaterialize } = await import(\"../../graph/latent-ideas.js\");\n if (!daemonConfig.vaultPath) {\n throw new Error(\"idea_materialize requires vaultPath to be configured in the daemon config\");\n }\n return handleIdeaMaterialize(\n p as Parameters<typeof handleIdeaMaterialize>[0],\n daemonConfig.vaultPath\n );\n }\n\n case \"kg_add\":\n case \"kg_query\":\n case \"kg_invalidate\":\n case \"kg_contradictions\": {\n const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import(\"../../mcp/tools.js\");\n if (!storageBackend.supportsPostgresFeatures) {\n throw new Error(`${method} requires a Postgres storage backend`);\n }\n switch (method) {\n case \"kg_add\": return toolKgAdd(storageBackend, p as Parameters<typeof toolKgAdd>[1]);\n case \"kg_query\": return toolKgQuery(storageBackend, p as Parameters<typeof toolKgQuery>[1]);\n case \"kg_invalidate\": return toolKgInvalidate(storageBackend, p as Parameters<typeof toolKgInvalidate>[1]);\n case \"kg_contradictions\": return toolKgContradictions(storageBackend, p as Parameters<typeof toolKgContradictions>[1]);\n }\n break;\n }\n\n case \"memory_tunnels\": {\n const { toolMemoryTunnels } = await import(\"../../mcp/tools.js\");\n return toolMemoryTunnels(registryBackend, storageBackend, p as Parameters<typeof toolMemoryTunnels>[2]);\n }\n\n case \"memory_feedback\": {\n // MR2: feedback weight loop — relevance_score/feedback_weight go through\n // the StorageBackend, so this works on either backend.\n return await toolMemoryFeedback(storageBackend, p as Parameters<typeof toolMemoryFeedback>[1]);\n }\n\n case \"memory_kg_search\": {\n // MR1: graph-completion retrieval — active storage backend for KG triple expansion\n if (!storageBackend.supportsPostgresFeatures) {\n throw new Error(\"memory_kg_search requires a Postgres storage backend for KG triple expansion\");\n }\n return await toolMemoryKgSearch(storageBackend, p as Parameters<typeof toolMemoryKgSearch>[1]);\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n}\n","/**\n * IPC request handler — processes all inbound IPC methods and sends responses.\n */\n\nimport type { Socket } from \"node:net\";\nimport type { IpcRequest, IpcResponse } from \"./types.js\";\nimport type { NotificationMode } from \"../../notifications/types.js\";\nimport {\n patchNotificationConfig,\n} from \"../../notifications/config.js\";\nimport { routeNotification } from \"../../notifications/router.js\";\nimport {\n registryBackend,\n storageBackend,\n daemonConfig,\n startTime,\n indexInProgress,\n lastIndexTime,\n embedInProgress,\n lastEmbedTime,\n vaultIndexInProgress,\n lastVaultIndexTime,\n notificationConfig,\n setNotificationConfig,\n} from \"./state.js\";\nimport { runIndex, runVaultIndex } from \"./scheduler.js\";\nimport { dispatchTool } from \"./dispatcher.js\";\nimport { enqueue, getStats as getQueueStats } from \"../../daemon/work-queue.js\";\nimport { notifyNewWork } from \"../../daemon/work-queue-worker.js\";\nimport { getBackendOutage } from \"../../storage/outage.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nexport function sendResponse(socket: Socket, response: IpcResponse): void {\n try {\n socket.write(JSON.stringify(response) + \"\\n\");\n } catch {\n // Socket may already be closed\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single IPC request.\n */\nexport async function handleRequest(\n request: IpcRequest,\n socket: Socket\n): Promise<void> {\n const { id, method, params } = request;\n\n // Special: status\n if (method === \"status\") {\n const dbStats = await (async () => {\n try {\n if (!storageBackend) return null;\n const fedStats = await storageBackend.getStats();\n const projects = await registryBackend.countProjects();\n return { files: fedStats.files, chunks: fedStats.chunks, projects };\n } catch {\n return null;\n }\n })();\n\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n uptime: Math.floor((Date.now() - startTime) / 1000),\n indexInProgress,\n lastIndexTime: lastIndexTime ? new Date(lastIndexTime).toISOString() : null,\n indexIntervalSecs: daemonConfig.indexIntervalSecs,\n embedInProgress,\n lastEmbedTime: lastEmbedTime ? new Date(lastEmbedTime).toISOString() : null,\n embedIntervalSecs: daemonConfig.embedIntervalSecs,\n socketPath: daemonConfig.socketPath,\n storageBackend: storageBackend?.backendType ?? \"initializing\",\n db: dbStats,\n vaultIndexInProgress,\n lastVaultIndexTime: lastVaultIndexTime ? new Date(lastVaultIndexTime).toISOString() : null,\n vaultPath: daemonConfig.vaultPath ?? null,\n workQueue: getQueueStats(),\n // Null when the backend is answering. Present, this is the reason\n // everything else in this payload is stalled — and the reason status\n // used to report \"idle\" for two days while nothing progressed.\n backendOutage: getBackendOutage(),\n },\n });\n socket.end();\n return;\n }\n\n // Special: index_now — trigger immediate index (non-blocking response)\n if (method === \"index_now\") {\n runIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: vault_index_now — trigger immediate vault index (non-blocking response)\n if (method === \"vault_index_now\") {\n runVaultIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] vault_index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: notification_get_config\n if (method === \"notification_get_config\") {\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n config: notificationConfig,\n activeChannels: Object.entries(notificationConfig.channels)\n .filter(([ch, cfg]) => ch !== \"voice\" && (cfg as { enabled: boolean }).enabled)\n .map(([ch]) => ch),\n },\n });\n socket.end();\n return;\n }\n\n // Special: notification_set_config\n if (method === \"notification_set_config\") {\n try {\n const p = params as {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n routing?: Record<string, unknown>;\n };\n const updated = patchNotificationConfig({\n mode: p.mode,\n channels: p.channels as Parameters<typeof patchNotificationConfig>[0][\"channels\"],\n routing: p.routing as Parameters<typeof patchNotificationConfig>[0][\"routing\"],\n });\n setNotificationConfig(updated);\n sendResponse(socket, { id, ok: true, result: { config: updated } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // Special: notification_send\n if (method === \"notification_send\") {\n const p = params as {\n event?: string;\n message?: string;\n title?: string;\n };\n\n if (!p.message) {\n sendResponse(socket, { id, ok: false, error: \"notification_send: message is required\" });\n socket.end();\n return;\n }\n\n const event = (p.event as Parameters<typeof routeNotification>[0][\"event\"]) ?? \"info\";\n\n routeNotification(\n { event, message: p.message, title: p.title },\n notificationConfig\n ).then((result) => {\n sendResponse(socket, { id, ok: true, result });\n socket.end();\n }).catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n socket.end();\n });\n return;\n }\n\n // ---- Observation methods (Postgres only) --------------------------------\n\n if (method === \"observation_store\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n type: string;\n title: string;\n narrative?: string;\n tool_name: string;\n tool_input_summary?: string;\n files_read?: string[];\n files_modified?: string[];\n concepts?: string[];\n content_hash?: string;\n cwd?: string;\n };\n\n const insertedId = await storageBackend.storeObservationWithProject(registryBackend, {\n session_id: p.session_id,\n type: p.type as \"decision\" | \"bugfix\" | \"feature\" | \"refactor\" | \"discovery\" | \"change\",\n title: p.title,\n narrative: p.narrative ?? null,\n tool_name: p.tool_name,\n tool_input_summary: p.tool_input_summary ?? undefined,\n files_read: p.files_read ?? [],\n files_modified: p.files_modified ?? [],\n concepts: p.concepts ?? [],\n cwd: p.cwd,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true, id: insertedId } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // ---- Skill telemetry (Postgres only) ------------------------------------\n\n if (method === \"skill_telemetry_record\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n skill_name: string;\n source?: string;\n scope?: string;\n cwd?: string;\n };\n\n let project_slug: string | null = null;\n if (p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) project_slug = row.slug;\n }\n\n await storageBackend.recordSkillInvocation({\n skill_name: p.skill_name,\n source: p.source,\n scope: p.scope,\n project_slug,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"skill_telemetry_query\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { scope?: string; status?: string; limit?: number };\n const rows = await storageBackend.querySkillTelemetry({\n scope: p.scope,\n status: p.status,\n limit: p.limit,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_query\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_id?: number;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n const rows = await storageBackend.queryObservations({\n projectId: p.project_id,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit,\n offset: p.offset,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_recent\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { project_id?: number; cwd?: string; limit?: number };\n const limit = p.limit ?? 20;\n\n let resolvedProjectId = p.project_id;\n let resolvedProjectSlug: string | undefined;\n if (resolvedProjectId === undefined && p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n let rows;\n if (resolvedProjectId !== undefined) {\n rows = await storageBackend.queryRecentObservations(resolvedProjectId, limit);\n } else {\n rows = await storageBackend.queryObservations({ limit });\n }\n sendResponse(socket, { id, ok: true, result: { rows, project_slug: resolvedProjectSlug } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_list — alias for observation_query with project slug resolution\n if (method === \"observation_list\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_slug?: string;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n let projectId: number | undefined;\n if (p.project_slug) {\n const row = await registryBackend.getProjectBySlug(p.project_slug);\n projectId = row?.id;\n }\n\n const rows = await storageBackend.queryObservations({\n projectId,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit ?? 20,\n offset: p.offset ?? 0,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_stats — aggregate statistics\n if (method === \"observation_stats\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const stats = await storageBackend.getObservationStats();\n sendResponse(socket, { id, ok: true, result: stats });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"session_summary_store\") {\n if (!storageBackend?.supportsPostgresFeatures) {\n sendResponse(socket, { id, ok: false, error: \"Session summaries require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n project_id?: number;\n project_slug?: string;\n cwd?: string;\n request?: string;\n investigated?: string;\n learned?: string;\n completed?: string;\n next_steps?: string;\n observation_count?: number;\n };\n\n let resolvedProjectId = p.project_id ?? null;\n let resolvedProjectSlug = p.project_slug ?? null;\n if (resolvedProjectId === null && p.cwd) {\n const row = await registryBackend.findProjectByCwdPrefix(p.cwd);\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n await storageBackend.storeSessionSummary({\n session_id: p.session_id,\n project_id: resolvedProjectId,\n project_slug: resolvedProjectSlug,\n request: p.request ?? null,\n investigated: p.investigated ?? null,\n learned: p.learned ?? null,\n completed: p.completed ?? null,\n next_steps: p.next_steps ?? null,\n observation_count: p.observation_count ?? 0,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_enqueue — hooks push work items here instead of doing heavy work inline\n if (method === \"work_queue_enqueue\") {\n try {\n const p = params as {\n type?: string;\n priority?: number;\n payload?: Record<string, unknown>;\n maxAttempts?: number;\n };\n\n if (!p.type) {\n sendResponse(socket, { id, ok: false, error: \"work_queue_enqueue: type is required\" });\n socket.end();\n return;\n }\n\n const item = enqueue({\n type: p.type as import(\"../../daemon/work-queue.js\").WorkItemType,\n priority: p.priority,\n payload: p.payload ?? {},\n maxAttempts: p.maxAttempts,\n });\n\n notifyNewWork();\n\n sendResponse(socket, { id, ok: true, result: { id: item.id, status: item.status } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_stats — return current queue statistics\n if (method === \"work_queue_stats\") {\n sendResponse(socket, { id, ok: true, result: getQueueStats() });\n socket.end();\n return;\n }\n\n // All other methods: PAI tool dispatch\n try {\n const result = await dispatchTool(method, params);\n sendResponse(socket, { id, ok: true, result });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n}\n","/**\n * One-time repair for chunks written while the `pai_chunks_fts_update`\n * trigger used the 'english' text-search config instead of 'simple'.\n *\n * The trigger stemmed and stripped stop-words from every stored fts_vector,\n * but every query (searchKeyword, findTunnels) and the direct-insert path\n * (insertChunks) always used 'simple' — so a BEFORE trigger silently\n * overwrote each insert with a vector that keyword search could never match.\n * Measured on a real database: 0 rows for `fts_vector @@ to_tsquery('simple',\n * 'running')` against 142,559 chunks containing \"running\".\n *\n * Completion is marked by a comment on pai_chunks.fts_vector so this can run\n * on every daemon boot for free once migrated, and resume if a batch run\n * was interrupted: every 20 batches the last completed id is stored in the\n * comment as `fts:simple:inprogress:<id>` and the next start continues after it.\n *\n * The rebuild runs in the background while the indexer writes pai_chunks, so\n * each batch UPDATE locks its rows in id order (consistent with other writers)\n * and is retried up to 5 times on deadlock (40P01) or serialization failure\n * (40001) with a 200ms * attempt backoff. Rows whose fts_vector is already\n * correct are skipped, so a retry or restart is cheap.\n */\n\nimport type { StorageBackend } from \"../interface.js\";\nimport type { PostgresBackend } from \"../postgres.js\";\n\nconst DONE_MARKER = \"fts:simple\";\nconst PROGRESS_PREFIX = \"fts:simple:inprogress:\";\nconst BATCH_SIZE = 1000;\nconst MAX_ATTEMPTS = 5;\nconst RETRYABLE_CODES = new Set([\"40P01\", \"40001\"]);\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport async function migrateFtsConfig(backend: StorageBackend): Promise<void> {\n // Obtained inside the async function body, not by the caller: a throw here\n // (e.g. a bad cast) becomes a rejection of this function's own promise,\n // caught by the caller's `.catch`, instead of a synchronous throw inside\n // the daemon's storage-backend `.then()` that would abort loadQueue()/\n // startWorker() and hit the outer fatal-exit `.catch`.\n const pool = (backend as unknown as PostgresBackend).getPool();\n const tableCheck = await pool.query(\n `SELECT 1 FROM information_schema.tables WHERE table_name = 'pai_chunks'`\n );\n if (tableCheck.rowCount === 0) return;\n\n const commentCheck = await pool.query<{ comment: string | null }>(\n `SELECT col_description('pai_chunks'::regclass, ordinal_position) AS comment\n FROM information_schema.columns\n WHERE table_name = 'pai_chunks' AND column_name = 'fts_vector'`\n );\n const comment = commentCheck.rows[0]?.comment ?? null;\n if (comment === DONE_MARKER) return;\n const resumeId = comment?.startsWith(PROGRESS_PREFIX)\n ? comment.slice(PROGRESS_PREFIX.length)\n : \"\";\n\n process.stderr.write(\n \"[pai-postgres] FTS config migration: starting (english -> simple)\\n\"\n );\n\n const fnCheck = await pool.query(\n `SELECT 1 FROM pg_proc WHERE proname = 'pai_chunks_fts_update'`\n );\n if (fnCheck.rowCount !== 0) {\n await pool.query(`\n CREATE OR REPLACE FUNCTION pai_chunks_fts_update()\n RETURNS TRIGGER AS $$\n BEGIN\n NEW.fts_vector := to_tsvector('simple', COALESCE(NEW.text, ''));\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql;\n `);\n process.stderr.write(\n \"[pai-postgres] FTS config migration: trigger function now uses 'simple'\\n\"\n );\n }\n\n let lastId: string | null = resumeId || null;\n if (lastId !== null) {\n process.stderr.write(`[pai-postgres] FTS config migration: resuming from ${lastId}\\n`);\n }\n let totalUpdated = 0;\n let batchN = 0;\n while (true) {\n const sql = lastId === null\n ? `SELECT id FROM pai_chunks ORDER BY id LIMIT $1`\n : `SELECT id FROM pai_chunks WHERE id > $2 ORDER BY id LIMIT $1`;\n const params: Array<number | string> = lastId === null ? [BATCH_SIZE] : [BATCH_SIZE, lastId];\n const idRows = await pool.query<{ id: string }>(sql, params);\n if (idRows.rows.length === 0) break;\n\n const ids: string[] = idRows.rows.map((r) => r.id);\n for (let attempt = 1; ; attempt++) {\n try {\n const result = await pool.query(\n `UPDATE pai_chunks c SET fts_vector = to_tsvector('simple', COALESCE(c.text, ''))\n FROM (SELECT id FROM pai_chunks WHERE id = ANY($1::text[]) ORDER BY id FOR UPDATE) s\n WHERE c.id = s.id\n AND c.fts_vector IS DISTINCT FROM to_tsvector('simple', COALESCE(c.text, ''))`,\n [ids]\n );\n totalUpdated += result.rowCount ?? 0;\n break;\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (attempt >= MAX_ATTEMPTS || !code || !RETRYABLE_CODES.has(code)) throw err;\n await sleep(200 * attempt);\n }\n }\n lastId = ids[ids.length - 1];\n batchN++;\n if (batchN % 20 === 0) {\n // COMMENT takes no bind params; double quotes to escape the literal.\n await pool.query(\n `COMMENT ON COLUMN pai_chunks.fts_vector IS '${PROGRESS_PREFIX}${lastId.replace(/'/g, \"''\")}'`\n );\n process.stderr.write(\n `[pai-postgres] FTS config migration: ${totalUpdated} chunks rebuilt so far...\\n`\n );\n }\n if (idRows.rows.length < BATCH_SIZE) break;\n }\n\n await pool.query(\n `COMMENT ON COLUMN pai_chunks.fts_vector IS '${DONE_MARKER}'`\n );\n\n process.stderr.write(\n `[pai-postgres] FTS config migration: done, ${totalUpdated} chunks rebuilt\\n`\n );\n}\n","/**\n * IPC server and daemon entry point.\n * Owns: isSocketLive, startIpcServer, serve (exported).\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { createServer, connect, Socket, Server } from \"node:net\";\nimport { setPriority } from \"node:os\";\nimport { createStorageBackend, createRegistryBackend } from \"../../storage/factory.js\";\nimport { configureEmbeddingModel } from \"../../memory/embeddings.js\";\nimport { loadNotificationConfig } from \"../../notifications/config.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\nimport type { IpcRequest } from \"./types.js\";\nimport {\n setRegistryBackend,\n setStorageBackend,\n setDaemonConfig,\n setStartTime,\n setNotificationConfig,\n setShutdownRequested,\n indexInProgress,\n embedInProgress,\n indexSchedulerTimer,\n embedSchedulerTimer,\n cacheKeepaliveTimer,\n storageBackend,\n} from \"./state.js\";\nimport { startIndexScheduler, startEmbedScheduler, startRegistryScanScheduler, startWorkerSupervisor, startCacheKeepalive, startSessionKeepalive } from \"./scheduler.js\";\nimport { handleRequest, sendResponse } from \"./handler.js\";\nimport { loadQueue } from \"../../daemon/work-queue.js\";\nimport { startWorker, stopWorker } from \"../../daemon/work-queue-worker.js\";\nimport { migrateFtsConfig } from \"../../storage/postgres/fts-migration.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an existing socket file is actually being served by a live process.\n */\nfunction isSocketLive(path: string): Promise<boolean> {\n return new Promise((resolve) => {\n const client = connect(path);\n const timer = setTimeout(() => { client.destroy(); resolve(false); }, 500);\n client.on(\"connect\", () => { clearTimeout(timer); client.end(); resolve(true); });\n client.on(\"error\", () => { clearTimeout(timer); resolve(false); });\n });\n}\n\n/**\n * Start the Unix Domain Socket IPC server.\n */\nasync function startIpcServer(socketPath: string): Promise<Server> {\n if (existsSync(socketPath)) {\n const live = await isSocketLive(socketPath);\n if (live) {\n throw new Error(\"Another daemon is already running — socket is live. Aborting startup.\");\n }\n try {\n unlinkSync(socketPath);\n process.stderr.write(\"[pai-daemon] Removed stale socket file.\\n\");\n } catch {\n // If we can't remove it, bind will fail with a clear error\n }\n }\n\n const server = createServer((socket: Socket) => {\n let buffer = \"\";\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n\n if (line.trim() === \"\") continue;\n\n let request: IpcRequest;\n try {\n request = JSON.parse(line) as IpcRequest;\n } catch {\n sendResponse(socket, { id: \"?\", ok: false, error: \"Invalid JSON\" });\n socket.destroy();\n return;\n }\n\n handleRequest(request, socket).catch((e: unknown) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id: request.id, ok: false, error: msg });\n socket.destroy();\n });\n }\n });\n\n socket.on(\"error\", () => {\n // Client disconnected — nothing to do\n });\n });\n\n server.on(\"error\", (e) => {\n process.stderr.write(`[pai-daemon] IPC server error: ${e}\\n`);\n });\n\n server.listen(socketPath, () => {\n process.stderr.write(\n `[pai-daemon] IPC server listening on ${socketPath}\\n`\n );\n });\n\n return server;\n}\n\n// ---------------------------------------------------------------------------\n// Main daemon entry point\n// ---------------------------------------------------------------------------\n\nexport async function serve(config: PaiDaemonConfig): Promise<void> {\n setDaemonConfig(config);\n setStartTime(Date.now());\n\n setNotificationConfig(loadNotificationConfig());\n\n process.stderr.write(\"[pai-daemon] Starting daemon...\\n\");\n process.stderr.write(`[pai-daemon] Socket: ${config.socketPath}\\n`);\n process.stderr.write(`[pai-daemon] Storage backend: ${config.storageBackend}\\n`);\n const { notificationConfig } = await import(\"./state.js\");\n process.stderr.write(\n `[pai-daemon] Notification mode: ${notificationConfig.mode}\\n`\n );\n\n // Lower scheduling priority so the daemon yields CPU to interactive sessions\n try { setPriority(process.pid, 10); } catch { /* non-fatal */ }\n\n configureEmbeddingModel(config.embeddingModel);\n\n // Connect the registry backend in the background — non-blocking, since it\n // may take a few retries under Postgres (waitForPostgres: true never\n // silently falls back to SQLite, matching the federation backend below).\n // Callers that need it before it resolves (status, observation IPC methods)\n // already tolerate an unready backend via their own try/catch.\n void createRegistryBackend(config, { waitForPostgres: true })\n .then((backend) => {\n setRegistryBackend(backend);\n process.stderr.write(`[pai-daemon] Registry backend: ${backend.backendType}\\n`);\n })\n .catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Fatal: Could not open registry: ${msg}\\n`);\n process.exit(1);\n });\n\n // Start the IPC server immediately so `pai daemon status` answers even while\n // we are still waiting for the federation backend (e.g. Postgres coming up\n // after a reboot). The status handler tolerates a not-yet-ready backend.\n const server = await startIpcServer(config.socketPath);\n\n // Registry scan scheduler only needs the registry DB — safe to start now.\n startRegistryScanScheduler();\n\n // Worker supervision reads the workers ledger off disk and pushes events to\n // owning sessions — no backend dependency, so it starts with the IPC server.\n startWorkerSupervisor();\n\n // Cache keepalive spawns trivial workers on the workers config — also no\n // backend dependency (observations store opportunistically when Postgres\n // is up), so it starts with the IPC server too.\n startCacheKeepalive();\n\n // Interactive-session cache keepalive: also no backend dependency (reads\n // AIBroker + transcript files, writes the workers ledger + its own state\n // file), so it starts with the IPC server too.\n startSessionKeepalive();\n\n // Connect the federation backend in the background. When storageBackend is\n // \"postgres\" this retries forever (never silently falls back to SQLite), so\n // the boot race with Docker/Postgres resolves itself once Postgres is up.\n // Only then do we start the federation-dependent schedulers and work queue —\n // this also keeps the first index run from firing against an unready backend.\n void createStorageBackend(config, { waitForPostgres: true })\n .then((backend) => {\n setStorageBackend(backend);\n process.stderr.write(\n `[pai-daemon] Federation backend: ${backend.backendType}\\n`\n );\n\n startIndexScheduler();\n\n if (backend.supportsPostgresFeatures) {\n startEmbedScheduler();\n\n // Repair fts_vector rows written under the old ('english') trigger.\n // Async, non-blocking: real tables hold millions of rows, and a\n // rebuild must never delay daemon startup. Idempotent — safe to\n // resume from scratch if interrupted (see fts-migration.ts).\n void migrateFtsConfig(backend).catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] FTS config migration failed: ${msg}\\n`);\n });\n } else {\n process.stderr.write(\n \"[pai-daemon] Embed scheduler: disabled (SQLite backend)\\n\"\n );\n }\n\n // Work queue — load persisted items and start worker loop\n loadQueue();\n startWorker();\n })\n .catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Fatal: Could not open federation storage: ${msg}\\n`\n );\n process.exit(1);\n });\n\n const shutdown = async (signal: string): Promise<void> => {\n process.stderr.write(`\\n[pai-daemon] ${signal} received. Stopping.\\n`);\n\n setShutdownRequested(true);\n\n if (indexSchedulerTimer) clearInterval(indexSchedulerTimer);\n if (embedSchedulerTimer) clearInterval(embedSchedulerTimer);\n if (cacheKeepaliveTimer) clearInterval(cacheKeepaliveTimer);\n\n stopWorker();\n\n server.close();\n\n const SHUTDOWN_TIMEOUT_MS = 10_000;\n const POLL_INTERVAL_MS = 100;\n const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS;\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\n `[pai-daemon] Waiting for in-progress operations to finish ` +\n `(index=${indexInProgress}, embed=${embedInProgress})...\\n`\n );\n\n while ((indexInProgress || embedInProgress) && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Shutdown timeout reached — forcing exit.\\n\");\n } else {\n process.stderr.write(\"[pai-daemon] In-progress operations finished.\\n\");\n }\n }\n\n try {\n await storageBackend.close();\n } catch {\n // ignore\n }\n\n try {\n unlinkSync(config.socketPath);\n } catch {\n // ignore\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => { shutdown(\"SIGINT\").catch(() => process.exit(0)); });\n process.on(\"SIGTERM\", () => { shutdown(\"SIGTERM\").catch(() => process.exit(0)); });\n\n // Keep process alive\n await new Promise(() => {});\n}\n"],"mappings":"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"}
|