{"version":3,"file":"pool-cli-kGGuZCzJ.mjs","names":["httpRequest"],"sources":["../src/workers/pool-cli.ts"],"sourcesContent":["/**\n * pool-cli.ts — the `pai worker pool on|off|status` implementations.\n *\n * on flips workers.pool.enabled in the main config and starts the detached\n * host (dist/hooks/worker-pool-host.mjs, same find-and-spawn pattern as the\n * openai proxy); off flips the flag back and shuts the host down; status\n * prints the live state: slots, tasks served, cache-carry total.\n */\n\nimport { spawn } from \"node:child_process\";\nimport { existsSync, mkdirSync, unlinkSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readWorkersSection, writeWorkersSection } from \"./config.js\";\nimport { fmtTokens } from \"./status.js\";\nimport {\n  poolCarry,\n  poolDir,\n  poolHostAlive,\n  poolPort,\n  poolSettings,\n  poolPidPath,\n  readPoolMetrics,\n  poolMetricsPath,\n  readPoolState,\n} from \"./pool.js\";\nimport { request as httpRequest } from \"node:http\";\n\n/** Where the standalone host bundle lives (see standaloneProxyPath in proxy/server.ts). */\nexport function standalonePoolHostPath(): string | null {\n  let dir = dirname(fileURLToPath(import.meta.url));\n  for (let i = 0; i < 6; i++) {\n    for (const cand of [\n      join(dir, \"worker-pool-host.mjs\"),\n      join(dir, \"hooks\", \"worker-pool-host.mjs\"),\n      join(dir, \"dist\", \"hooks\", \"worker-pool-host.mjs\"),\n    ]) {\n      if (existsSync(cand)) return cand;\n    }\n    const parent = dirname(dir);\n    if (parent === dir) break;\n    dir = parent;\n  }\n  return null;\n}\n\nfunction setPoolEnabled(enabled: boolean): void {\n  const { raw, workers } = readWorkersSection();\n  workers.pool = { ...(workers.pool ?? { enabled, maxSlots: 3, idleSecs: 900, port: 8798 }), enabled };\n  writeWorkersSection(raw, workers);\n}\n\n/** POST /shutdown, best effort: a dead host needs no shutdown. */\nfunction shutdownHost(port: number): void {\n  const req = httpRequest({ host: \"127.0.0.1\", port, path: \"/shutdown\", method: \"POST\", timeout: 1500 }, (res) => {\n    res.resume();\n  });\n  req.on(\"timeout\", () => req.destroy());\n  req.on(\"error\", () => {});\n  req.end();\n}\n\nexport async function poolOn(logDir: string): Promise<string[]> {\n  const { workers } = readWorkersSection();\n  const settings = poolSettings(workers);\n  const port = settings.port;\n  if (await poolHostAlive(port)) {\n    setPoolEnabled(true);\n    return [`pool host already running on port ${port} — routing enabled`];\n  }\n  const script = standalonePoolHostPath();\n  if (!script) {\n    throw new Error(\n      `the pool host build was not found — run \\`bun run build\\` first, ` +\n        `or start one by hand: node dist/hooks/worker-pool-host.mjs --port ${port}`\n    );\n  }\n  mkdirSync(logDir, { recursive: true });\n  mkdirSync(poolDir(logDir), { recursive: true }); // the pid file lands before the host's first state write\n  const child = spawn(process.execPath, [script, \"--port\", String(port)], {\n    detached: true,\n    stdio: \"ignore\",\n    cwd: logDir,\n  });\n  child.unref();\n  writeFileSync(poolPidPath(logDir), `${child.pid}\\n`, \"utf8\");\n  for (let i = 0; i < 40; i++) {\n    if (await poolHostAlive(port)) {\n      setPoolEnabled(true);\n      return [\n        `pool host started (pid ${child.pid}, port ${port}, cwd ${logDir})`,\n        `routing enabled: eligible headless runs (no worktree, no own system prompt/MCP) go through the pool`,\n        `slots: max ${settings.maxSlots}, idle kill ${settings.idleSecs}s`,\n      ];\n    }\n    await new Promise((r) => setTimeout(r, 250));\n  }\n  throw new Error(`the pool host did not come up on port ${port} (see ${poolPidPath(logDir)})`);\n}\n\nexport async function poolOff(logDir: string): Promise<string[]> {\n  const { workers } = readWorkersSection();\n  const port = poolPort(workers);\n  setPoolEnabled(false);\n  shutdownHost(port);\n  const lines = [`routing disabled — every run spawns fresh again`];\n  const pidPath = poolPidPath(logDir);\n  if (existsSync(pidPath)) {\n    const pid = parseInt(readFileSync(pidPath, \"utf8\").trim(), 10);\n    unlinkSync(pidPath);\n    try {\n      process.kill(pid, \"SIGTERM\");\n      lines.push(`pool host pid ${pid} stopped`);\n    } catch {\n      lines.push(`pool host pid ${pid} was not running (pid file removed)`);\n    }\n  } else {\n    lines.push(`no pool host pid file — nothing to stop`);\n  }\n  return lines;\n}\n\n/** The `pai worker pool status` text: config, host, slots, carry. */\nexport async function poolStatusText(logDir: string): Promise<string> {\n  const { workers } = readWorkersSection();\n  const settings = poolSettings(workers);\n  const port = settings.port;\n  const alive = await poolHostAlive(port);\n  const lines: string[] = [];\n  lines.push(`pool: ${settings.enabled ? \"enabled\" : \"disabled\"} (config workers.pool; host port ${port}, max ${settings.maxSlots} slots, idle kill ${settings.idleSecs}s)`);\n  const state = readPoolState(logDir);\n  if (!state) {\n    lines.push(alive ? `host: alive on port ${port} (no state file yet)` : `host: not running`);\n    return lines.join(\"\\n\");\n  }\n  lines.push(\n    `host: pid ${state.hostPid}, port ${state.port}, up since ${state.startedAt}, ` +\n      `${alive ? \"alive\" : \"NOT answering (state file is stale)\"}`\n  );\n  if (state.slots.length) {\n    lines.push(`slots (${state.slots.length}):`);\n    for (const s of state.slots) {\n      lines.push(\n        `  ${s.provider}/${s.model} pid ${s.pid} session ${(s.claudeSession ?? \"?\").slice(0, 8)} ` +\n          `tasks ${s.tasks} ${s.busyWith ? `busy ${s.busyWith}` : \"idle\"} last ${s.lastUsedAt}`\n      );\n    }\n  } else {\n    lines.push(`slots: none (a slot spawns with the first pooled task)`);\n  }\n  const carry = poolCarry(readPoolMetrics(poolMetricsPath(logDir)));\n  lines.push(\n    `tasks served ${state.tasksServed} · carried ${fmtTokens(carry.tokensCarried)} cache-read tokens ` +\n      `over ${carry.tasks} task(s) · first-request hit ratio ${(carry.hitRatio * 100).toFixed(1)}%`\n  );\n  return lines.join(\"\\n\");\n}\n"],"mappings":"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"}