{"version":3,"sources":["../src/index.ts","../src/agent/agent-client.ts","../src/agent/agent-config.ts","../src/agent/conversation-writer.ts","../src/agent/dedupe.ts","../src/api/routes/progress.routes.ts","../src/auth.ts","../src/db/config.ts","../src/db/migrations.ts","../src/db/pool.ts","../src/process-discovery.ts","../src/platform.ts","../src/pty-manager.ts","../src/logger.ts","../src/services/questions/detectPermissionGate.ts","../src/services/questions/detectQuestionFromScreen.ts","../src/services/questions/detectShellPrompt.ts","../src/server.ts","../node_modules/nanoid/index.js","../node_modules/nanoid/url-alphabet/index.js","../src/agent/errors.ts","../src/agent/history-mapper.ts","../src/agent/payload-guard.ts","../src/agent/handle-send-agent-input.ts","../src/agent/handle-start-agent-session.ts","../src/api/app.ts","../src/api/middleware/auth.middleware.ts","../src/api/middleware/cors.middleware.ts","../src/api/middleware/error.middleware.ts","../src/api/routes/browse.routes.ts","../src/api/routes/conversations.routes.ts","../src/api/routes/health.routes.ts","../src/version.ts","../src/api/routes/misc.routes.ts","../src/config/update-config.ts","../src/schemas/updateConfig.schema.ts","../src/api/routes/pair.routes.ts","../src/api/routes/projects.routes.ts","../src/api/routes/scanner.routes.ts","../src/api/routes/sessions.routes.ts","../src/api/routes/ws.routes.ts","../src/browse.ts","../src/conversation-cache.ts","../src/db/sqlite-migrate.ts","../src/providers.ts","../src/services/conversations/isAgentConversation.ts","../src/db/repositories/cacheMetadata.repository.ts","../src/db/repositories/conversations.repository.ts","../src/db/repositories/projects.repository.ts","../src/utils/canonicalizeProjectPath.ts","../src/db/repositories/sessions.repository.ts","../src/db/upload-records.ts","../src/handlers/handleListProjects.ts","../src/pair-store.ts","../src/seal.ts","../src/services/conversations/conversationWatcher.ts","../src/services/conversations/pruneAgentConversations.ts","../src/services/projectChats/deriveProjectChatTitle.ts","../src/services/questions/detectAskUserQuestion.ts","../src/services/questions/questionBroadcast.ts","../src/services/questions/answersToKeystrokes.ts","../src/services/questions/resolveAnswer.ts","../src/session-store.ts","../src/uploads.ts","../src/utils/conversationEtag.ts","../src/utils/debounce.ts","../src/utils/dates.ts","../src/utils/isScannedSnapshotStale.ts","../src/utils/scanProgressThrottle.ts","../src/ws-hub.ts"],"sourcesContent":["export {\n  type AgentClient,\n  type AgentClientOpts,\n  createAgentClient,\n} from \"./agent/agent-client\";\n// Multi-agent mode surface. Consumed by tb-multi-agent integration tests and\n// by any external integrator wiring up the agent pipeline. See\n// docs/multi-agent-mode.md for operator-facing context.\nexport { type AgentConfig, readAgentConfig } from \"./agent/agent-config\";\nexport {\n  type AppendArgs,\n  type ConversationWriter,\n  createConversationWriter,\n} from \"./agent/conversation-writer\";\nexport { createProgressDedupeLRU, type ProgressDedupeLRU } from \"./agent/dedupe\";\nexport { createProgressRoutes } from \"./api/routes/progress.routes\";\nexport { generateApiKey, loadOrCreateApiKey, validateApiKey } from \"./auth\";\nexport type { DbConfig } from \"./db\";\nexport { createPool, getDbConfig, isDbEnabled, maskConnectionString } from \"./db\";\nexport { discoverClaudeProcesses } from \"./process-discovery\";\nexport { PTYManager } from \"./pty-manager\";\nexport { StreamerServer } from \"./server\";\nexport { ConversationWatcher } from \"./services/conversations/conversationWatcher\";\nexport { SessionStore } from \"./session-store\";\nexport * from \"./types\";\nexport { WSHub } from \"./ws-hub\";\n","// src/agent/agent-client.ts\n//\n// Thin Temporal client wrapper used by tb-streamer in multi-agent mode.\n// Does NOT import workflow code from tb-multi-agent — we identify the\n// workflow and its signals/queries by name. The workflow's wire contract\n// lives in @threadbase-sh/agent-types.\n\nimport type { Client } from \"@temporalio/client\";\nimport type { UserInputSignal } from \"@threadbase-sh/agent-types\";\n\n// `defineSignal` / `defineQuery` only ship in `@temporalio/workflow` (a worker-\n// side package we deliberately do NOT pull into the streamer). The handle\n// methods `.signal(...)` and `.query(...)` accept either a plain string OR a\n// definition object with `{ type, name }`. We construct minimal definition\n// objects here — Temporal matches by name, the rest of the shape is virtual\n// branding.\n//\n// Same identifiers as tb-multi-agent's src/workflows/signals.ts.\nconst userInputSignal = { type: \"signal\", name: \"userInput\" } as unknown as {\n  type: \"signal\";\n  name: \"userInput\";\n};\nconst stageQuery = { type: \"query\", name: \"stage\" } as unknown as {\n  type: \"query\";\n  name: \"stage\";\n};\n\nconst ORCHESTRATOR_WORKFLOW_TYPE = \"orchestratorWorkflow\";\n\nexport interface AgentClient {\n  startSession(sessionId: string): Promise<string>;\n  sendUserInput(sessionId: string, payload: UserInputSignal): Promise<void>;\n  endSession(sessionId: string): Promise<void>;\n  getSessionStage(sessionId: string): Promise<string>;\n}\n\nexport interface AgentClientOpts {\n  temporalClient: Client;\n  taskQueue: string;\n}\n\nconst sessionWorkflowId = (sessionId: string): string => `session-${sessionId}`;\n\nexport function createAgentClient({ temporalClient, taskQueue }: AgentClientOpts): AgentClient {\n  return {\n    async startSession(sessionId: string): Promise<string> {\n      const handle = await temporalClient.workflow.start(ORCHESTRATOR_WORKFLOW_TYPE, {\n        taskQueue,\n        workflowId: sessionWorkflowId(sessionId),\n        args: [sessionId],\n        workflowIdReusePolicy: \"REJECT_DUPLICATE\",\n      } as any);\n      return handle.workflowId;\n    },\n    async sendUserInput(sessionId: string, payload: UserInputSignal): Promise<void> {\n      await temporalClient.workflow\n        .getHandle(sessionWorkflowId(sessionId))\n        .signal(userInputSignal as any, payload);\n    },\n    async endSession(sessionId: string): Promise<void> {\n      await temporalClient.workflow.getHandle(sessionWorkflowId(sessionId)).cancel();\n    },\n    async getSessionStage(sessionId: string): Promise<string> {\n      return temporalClient.workflow\n        .getHandle(sessionWorkflowId(sessionId))\n        .query(stageQuery as any);\n    },\n  };\n}\n","// src/agent/agent-config.ts\n//\n// Runtime config for multi-agent mode. Read once at server startup so we don't\n// thread env-var lookups through the rest of the codebase.\n\nexport interface AgentConfig {\n  enabled: boolean;\n  temporal: {\n    address: string;\n    namespace: string;\n    taskQueue: string;\n  };\n  webhook: {\n    hmacSecret: string;\n    timestampSkewSeconds: number;\n  };\n  dedupe: {\n    perSessionCapacity: number;\n  };\n  payload: {\n    limitBytes: number;\n    trajectoryLogBytes: number;\n    trajectoryLogTurns: number;\n  };\n  sessionBusyRetryMs: number;\n  conversationsDir: string;\n}\n\nconst DEFAULTS = {\n  TEMPORAL_ADDRESS: \"localhost:7233\",\n  TEMPORAL_NAMESPACE: \"default\",\n  TEMPORAL_TASK_QUEUE: \"agent-tasks\",\n  PROGRESS_HMAC_SECRET: \"dev-secret-change-me\",\n  PROGRESS_WEBHOOK_TIMESTAMP_SKEW_SECONDS: \"300\",\n  PROGRESS_DEDUPE_CAPACITY: \"1024\",\n  AGENT_PAYLOAD_LIMIT_BYTES: \"1572864\", // 1.5 MB — 75% of Temporal's 2 MB ceiling\n  AGENT_TRAJECTORY_LOG_BYTES: \"512000\", // 500 KB\n  AGENT_TRAJECTORY_LOG_TURNS: \"20\", // First turn count for trajectory WARN\n  AGENT_SESSION_BUSY_RETRY_MS: \"1000\",\n};\n\nfunction isTruthy(v: string | undefined): boolean {\n  if (v === undefined) return false;\n  return v === \"true\" || v === \"1\" || v === \"yes\" || v === \"on\";\n}\n\nexport function readAgentConfig(env: NodeJS.ProcessEnv = process.env): AgentConfig {\n  const enabled = isTruthy(env.MULTI_AGENT_FLOW);\n  return {\n    enabled,\n    temporal: {\n      address: env.TEMPORAL_ADDRESS ?? DEFAULTS.TEMPORAL_ADDRESS,\n      namespace: env.TEMPORAL_NAMESPACE ?? DEFAULTS.TEMPORAL_NAMESPACE,\n      taskQueue: env.TEMPORAL_TASK_QUEUE ?? DEFAULTS.TEMPORAL_TASK_QUEUE,\n    },\n    webhook: {\n      hmacSecret: env.PROGRESS_HMAC_SECRET ?? DEFAULTS.PROGRESS_HMAC_SECRET,\n      timestampSkewSeconds: Number(\n        env.PROGRESS_WEBHOOK_TIMESTAMP_SKEW_SECONDS ??\n          DEFAULTS.PROGRESS_WEBHOOK_TIMESTAMP_SKEW_SECONDS,\n      ),\n    },\n    dedupe: {\n      perSessionCapacity: Number(env.PROGRESS_DEDUPE_CAPACITY ?? DEFAULTS.PROGRESS_DEDUPE_CAPACITY),\n    },\n    payload: {\n      limitBytes: Number(env.AGENT_PAYLOAD_LIMIT_BYTES ?? DEFAULTS.AGENT_PAYLOAD_LIMIT_BYTES),\n      trajectoryLogBytes: Number(\n        env.AGENT_TRAJECTORY_LOG_BYTES ?? DEFAULTS.AGENT_TRAJECTORY_LOG_BYTES,\n      ),\n      trajectoryLogTurns: Number(\n        env.AGENT_TRAJECTORY_LOG_TURNS ?? DEFAULTS.AGENT_TRAJECTORY_LOG_TURNS,\n      ),\n    },\n    sessionBusyRetryMs: Number(\n      env.AGENT_SESSION_BUSY_RETRY_MS ?? DEFAULTS.AGENT_SESSION_BUSY_RETRY_MS,\n    ),\n    // Mirrors ServerConfig.cacheDir's parent — the actual JSONL directory.\n    // We read it from env here; the conversation writer takes the resolved\n    // value from ServerConfig in Task 9.\n    conversationsDir: env.THREADBASE_CONVERSATIONS_DIR ?? \"\",\n  };\n}\n","// src/agent/conversation-writer.ts\n//\n// Persists assistant turns to JSONL when the worker's final agent_output for a\n// turn arrives. The existing ConversationCache + ConversationWatcher then\n// ingest the line via the existing watcher pipeline — see spec §6.3.\n\nimport { appendFile, mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\nexport interface AppendArgs {\n  sessionId: string;\n  turnId: string;\n  content: string;\n  reviewerOverruled?: boolean;\n}\n\nexport interface ConversationWriter {\n  appendAssistantTurn(args: AppendArgs): Promise<void>;\n}\n\nexport function createConversationWriter(opts: { baseDir: string }): ConversationWriter {\n  const { baseDir } = opts;\n\n  return {\n    async appendAssistantTurn(args: AppendArgs): Promise<void> {\n      if (!args.content || args.content.length === 0) {\n        throw new Error(\"ConversationWriter: refusing to write empty assistant turn\");\n      }\n      const file = join(baseDir, `${args.sessionId}.jsonl`);\n      await mkdir(dirname(file), { recursive: true });\n\n      const record = {\n        role: \"assistant\" as const,\n        turnId: args.turnId,\n        content: args.content,\n        timestamp: Date.now(),\n        ...(args.reviewerOverruled ? { reviewerOverruled: true } : {}),\n      };\n\n      const line = `${JSON.stringify(record)}\\n`;\n      await appendFile(file, line, { encoding: \"utf8\" });\n    },\n  };\n}\n","// src/agent/dedupe.ts\n//\n// Bounded LRU for per-session progress-event dedupe. Implementation uses the\n// fact that Map iterates in insertion order — re-inserting a key moves it to\n// the end, which is exactly LRU semantics with no extra bookkeeping.\n//\n// Spec §7.1: this is the milestone-B dedupe. The map lives on the session\n// record and dies with the session. Postgres-backed durability is option D,\n// deferred — see tb-multi-agent docs/plans/postgres-dedupe.md.\n\nexport interface ProgressDedupeLRU {\n  hasSeen(eventId: string): boolean;\n  readonly size: number;\n}\n\nexport function createProgressDedupeLRU(capacity: number): ProgressDedupeLRU {\n  if (!Number.isFinite(capacity) || capacity < 1) {\n    throw new Error(`dedupe LRU capacity must be >= 1, got ${capacity}`);\n  }\n  const map = new Map<string, true>();\n\n  return {\n    hasSeen(eventId: string): boolean {\n      if (map.has(eventId)) {\n        // Refresh recency: remove + reinsert moves to most-recent position.\n        map.delete(eventId);\n        map.set(eventId, true);\n        return true;\n      }\n      map.set(eventId, true);\n      if (map.size > capacity) {\n        // Evict the oldest entry (the first key in insertion order).\n        const oldest = map.keys().next().value;\n        if (oldest !== undefined) map.delete(oldest);\n      }\n      return false;\n    },\n    get size(): number {\n      return map.size;\n    },\n  };\n}\n","// src/api/routes/progress.routes.ts\n//\n// Webhook receiver for worker → tb-streamer progress events.\n//\n// Auth: HMAC over the raw request body, header X-Progress-Signature.\n// Auth bypass: the auth middleware skips this prefix because validation\n// happens inside the handler (mirrors /api/__update).\n//\n// Idempotency: per-session LRU on the ManagedSession record. Duplicates\n// return 200 with deduped:true and do not broadcast.\n\nimport crypto from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\nimport type { AgentOutputPayload, ProgressEvent, Stage } from \"@threadbase-sh/agent-types\";\nimport { Hono } from \"hono\";\nimport type { WSMessage } from \"../../types\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nfunction readRawBody(req: IncomingMessage): Promise<Buffer> {\n  return new Promise((resolve, reject) => {\n    const chunks: Buffer[] = [];\n    req.on(\"data\", (chunk) => chunks.push(chunk));\n    req.on(\"end\", () => resolve(Buffer.concat(chunks)));\n    req.on(\"error\", reject);\n  });\n}\n\ninterface AgentDeps {\n  sessionStore: {\n    getManaged: (sessionId: string) => {\n      id: string;\n      progressDedupeIds?: { hasSeen: (id: string) => boolean };\n      currentTurnId?: string | null;\n    } | null;\n  };\n  wsHub: { broadcast: (m: WSMessage) => void };\n  conversationWriter: {\n    appendAssistantTurn: (a: {\n      sessionId: string;\n      turnId: string;\n      content: string;\n      reviewerOverruled?: boolean;\n    }) => Promise<void>;\n  } | null;\n  agentConfig: {\n    enabled: boolean;\n    webhook: { hmacSecret: string; timestampSkewSeconds: number };\n    dedupe: { perSessionCapacity: number };\n  };\n}\n\nfunction verifySignature(rawBody: Buffer, signature: string, secret: string): boolean {\n  if (!signature || signature.length === 0) return false;\n  const expected = crypto.createHmac(\"sha256\", secret).update(rawBody).digest(\"hex\");\n  if (expected.length !== signature.length) return false;\n  try {\n    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));\n  } catch {\n    return false;\n  }\n}\n\nfunction isWithinSkew(timestampHeader: string | undefined, skewSeconds: number): boolean {\n  if (!timestampHeader) return true; // header optional in milestone B\n  const t = Number(timestampHeader);\n  if (!Number.isFinite(t)) return false;\n  const now = Math.floor(Date.now() / 1000);\n  return Math.abs(now - t) <= skewSeconds;\n}\n\nfunction stageToRole(stage: Stage | string | undefined): \"worker\" | \"reviewer\" | \"signoff\" {\n  if (stage === \"review\") return \"reviewer\";\n  if (stage === \"sign-off\") return \"signoff\";\n  return \"worker\";\n}\n\nexport const createProgressRoutes = (deps: ApiDeps & AgentDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.post(\"/sessions/:sessionId/progress\", async (c) => {\n    if (!deps.agentConfig.enabled) {\n      return c.json({ error: \"multi-agent mode not enabled\" }, 404);\n    }\n    const sessionId = c.req.param(\"sessionId\");\n    const session = deps.sessionStore.getManaged(sessionId);\n    if (!session) {\n      return c.json({ error: \"unknown session\" }, 404);\n    }\n\n    // Read raw body from the underlying Node IncomingMessage stream — mirrors\n    // /api/__update. Hono's c.req.arrayBuffer() returns empty when the request\n    // arrives via @hono/node-server's bindings, leaving HMAC verification with\n    // the wrong byte buffer. In tests (app.request), c.env.incoming is absent\n    // and arrayBuffer() works fine — fall back to it.\n    let rawBuf: Buffer;\n    try {\n      const incoming = c.env?.incoming;\n      rawBuf = incoming ? await readRawBody(incoming) : Buffer.from(await c.req.arrayBuffer());\n    } catch {\n      return c.json({ error: \"could not read body\" }, 400);\n    }\n    const sigHeader = c.req.header(\"x-progress-signature\") ?? \"\";\n    if (!verifySignature(rawBuf, sigHeader, deps.agentConfig.webhook.hmacSecret)) {\n      return c.json({ error: \"unauthorized\" }, 401);\n    }\n    if (\n      !isWithinSkew(\n        c.req.header(\"x-progress-timestamp\"),\n        deps.agentConfig.webhook.timestampSkewSeconds,\n      )\n    ) {\n      return c.json({ error: \"stale timestamp\" }, 401);\n    }\n\n    let event: ProgressEvent;\n    try {\n      event = JSON.parse(rawBuf.toString(\"utf8\")) as ProgressEvent;\n    } catch {\n      return c.json({ error: \"bad json\" }, 400);\n    }\n    if (!event.eventId || !event.sessionId || !event.turnId) {\n      return c.json({ error: \"missing required fields\" }, 400);\n    }\n\n    // Dedupe (per spec §7.1). If the session lacks a dedupe map (e.g., it was\n    // created in PTY mode and re-used), the receiver still works — every event\n    // is treated as new.\n    if (session.progressDedupeIds?.hasSeen(event.eventId)) {\n      return c.json({ ok: true, deduped: true }, 200);\n    }\n\n    // ─── Translate to WSMessage and broadcast ───────────────────────────\n    if (event.type === \"stage_transition\") {\n      const msg: WSMessage = {\n        type: \"session_update\",\n        sessionId: event.sessionId,\n        // turnId disambiguates which turn this stage applies to. Critical\n        // for `queued` (identifies the waiting turn) and `rework` (with\n        // reworkAttempt). Existing single-turn consumers can ignore it.\n        turnId: event.turnId,\n        // Existing session_update consumers expect status; we leave it\n        // undefined here (stage is the new-only field).\n        stage: event.stage,\n        reworkAttempt: event.reworkAttempt,\n        stalledSinceMs: 0,\n      } as WSMessage;\n      deps.wsHub.broadcast(msg);\n    } else if (event.type === \"agent_output\") {\n      const payload = (event.payload ?? {}) as unknown as AgentOutputPayload;\n      const msg: WSMessage = {\n        type: \"agent_output\",\n        sessionId: event.sessionId,\n        turnId: event.turnId,\n        role: stageToRole(event.stage),\n        content: payload.content ?? \"\",\n        partial: payload.partial,\n        reviewerOverruled: payload.reviewerOverruled,\n        stage: event.stage,\n        reworkAttempt: event.reworkAttempt,\n      } as WSMessage;\n      deps.wsHub.broadcast(msg);\n\n      // Persist final answer to JSONL.\n      if (event.stage === \"done\" && deps.conversationWriter && payload.content) {\n        await deps.conversationWriter.appendAssistantTurn({\n          sessionId: event.sessionId,\n          turnId: event.turnId,\n          content: payload.content,\n          reviewerOverruled: payload.reviewerOverruled,\n        });\n      }\n\n      // Release the session lock when the turn completes (spec §6).\n      if (event.stage === \"done\" && session.currentTurnId === event.turnId) {\n        (session as { currentTurnId: string | null }).currentTurnId = null;\n      }\n    } else if (event.type === \"terminal_failure\") {\n      const reason = (event.payload as { reason?: string } | undefined)?.reason ?? \"unknown\";\n      const msg: WSMessage = {\n        type: \"turn_failure\",\n        sessionId: event.sessionId,\n        turnId: event.turnId,\n        reason,\n      } as WSMessage;\n      deps.wsHub.broadcast(msg);\n\n      // Release the session lock on failure (spec §6).\n      if (session.currentTurnId === event.turnId) {\n        (session as { currentTurnId: string | null }).currentTurnId = null;\n      }\n    }\n\n    return c.json({ ok: true }, 200);\n  });\n\n  return app;\n};\n","import { randomBytes, timingSafeEqual } from \"crypto\";\nimport { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nconst CONFIG_DIR = join(homedir(), \".threadbase\");\nconst CONFIG_FILE = join(CONFIG_DIR, \"server.yaml\");\n\nexport function generateApiKey(): string {\n  return `tb_${randomBytes(16).toString(\"hex\")}`;\n}\n\nexport function validateApiKey(provided: string, expected: string): boolean {\n  const a = Buffer.from(provided);\n  const b = Buffer.from(expected);\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}\n\nexport function loadOrCreateApiKey(): string {\n  try {\n    const content = readFileSync(CONFIG_FILE, \"utf-8\");\n    const match = content.match(/api_key:\\s*(.+)/);\n    if (match?.[1]) return match[1].trim();\n  } catch {\n    // File doesn't exist, create one\n  }\n\n  const key = generateApiKey();\n  mkdirSync(CONFIG_DIR, { recursive: true });\n  writeFileSync(CONFIG_FILE, `api_key: ${key}\\n`, \"utf-8\");\n  return key;\n}\n\nexport function loadBrowseRoot(): string | undefined {\n  try {\n    const content = readFileSync(CONFIG_FILE, \"utf-8\");\n    const match = content.match(/browse_root:\\s*(.+)/);\n    if (match?.[1]) return match[1].trim();\n  } catch {\n    // File doesn't exist or not readable\n  }\n  return undefined;\n}\n\nexport function loadPublicUrl(): string | undefined {\n  try {\n    const content = readFileSync(CONFIG_FILE, \"utf-8\");\n    const match = content.match(/public_url:\\s*(.+)/);\n    if (match?.[1]) return match[1].trim();\n  } catch {\n    // File doesn't exist or not readable\n  }\n  return undefined;\n}\n\nexport function loadCacheDir(): string | undefined {\n  try {\n    const content = readFileSync(CONFIG_FILE, \"utf-8\");\n    const match = content.match(/cache_dir:\\s*(.+)/);\n    if (match?.[1]) return match[1].trim();\n  } catch {\n    // File doesn't exist or not readable\n  }\n  return undefined;\n}\n\nexport function loadTailSize(): number | undefined {\n  try {\n    const content = readFileSync(CONFIG_FILE, \"utf-8\");\n    const match = content.match(/tail_size:\\s*(\\d+)/);\n    if (match?.[1]) return Number.parseInt(match[1], 10);\n  } catch {\n    // File doesn't exist or not readable\n  }\n  return undefined;\n}\n\nexport type PublicUrlValidation = { ok: true; normalized: string } | { ok: false; error: string };\n\nexport function validatePublicUrl(raw: string): PublicUrlValidation {\n  let parsed: URL;\n  try {\n    parsed = new URL(raw);\n  } catch {\n    return { ok: false, error: `Invalid URL: ${raw}` };\n  }\n  const localHosts = new Set([\"localhost\", \"127.0.0.1\", \"::1\"]);\n  if (parsed.protocol === \"https:\") {\n    return { ok: true, normalized: stripTrailingSlash(parsed.toString()) };\n  }\n  if (parsed.protocol === \"http:\" && localHosts.has(parsed.hostname)) {\n    return { ok: true, normalized: stripTrailingSlash(parsed.toString()) };\n  }\n  return {\n    ok: false,\n    error: `publicUrl must be https:// (got ${parsed.protocol}//). Plain http is only allowed for localhost.`,\n  };\n}\n\nfunction stripTrailingSlash(url: string): string {\n  return url.endsWith(\"/\") ? url.slice(0, -1) : url;\n}\n\nexport function setApiKey(key: string): void {\n  mkdirSync(CONFIG_DIR, { recursive: true });\n\n  let content = \"\";\n  try {\n    content = readFileSync(CONFIG_FILE, \"utf-8\");\n  } catch (err) {\n    if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n    // file does not exist; we'll create it\n  }\n\n  const apiKeyLine = `api_key: ${key}`;\n  let updated: string;\n  if (/^api_key:\\s*.+$/m.test(content)) {\n    updated = content.replace(/^api_key:\\s*.+$/m, apiKeyLine);\n  } else if (content.length === 0 || content.endsWith(\"\\n\")) {\n    updated = `${content}${apiKeyLine}\\n`;\n  } else {\n    updated = `${content}\\n${apiKeyLine}\\n`;\n  }\n\n  const tmpFile = `${CONFIG_FILE}.tmp`;\n  writeFileSync(tmpFile, updated, { encoding: \"utf-8\", mode: 0o600 });\n  chmodSync(tmpFile, 0o600);\n  renameSync(tmpFile, CONFIG_FILE);\n}\n","export interface DbConfig {\n  connectionString: string;\n  max: number;\n  ssl?: string;\n  statementTimeout?: number;\n  instanceId: string;\n}\n\nexport function isDbEnabled(): boolean {\n  const url = process.env.THREADBASE_DATABASE_URL;\n  return typeof url === \"string\" && url.length > 0;\n}\n\nexport function getInstanceId(): string {\n  return process.env.THREADBASE_INSTANCE_ID || require(\"os\").hostname();\n}\n\nexport function getDbConfig(): DbConfig | null {\n  if (!isDbEnabled()) return null;\n\n  const connectionString = process.env.THREADBASE_DATABASE_URL ?? \"\";\n  const poolMax = Number.parseInt(process.env.THREADBASE_DATABASE_POOL_MAX ?? \"\", 10);\n  const stmtTimeout = Number.parseInt(\n    process.env.THREADBASE_DATABASE_STATEMENT_TIMEOUT_MS ?? \"\",\n    10,\n  );\n  const ssl = process.env.THREADBASE_DATABASE_SSL || undefined;\n\n  return {\n    connectionString,\n    max: Number.isNaN(poolMax) ? 10 : poolMax,\n    ssl,\n    statementTimeout: Number.isNaN(stmtTimeout) ? undefined : stmtTimeout,\n    instanceId: getInstanceId(),\n  };\n}\n","import { readdirSync, readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport type pg from \"pg\";\nimport { fileURLToPath } from \"url\";\n\nfunction getMigrationsDir(): string {\n  // ESM: import.meta.url is available\n  if (import.meta.url) {\n    return dirname(fileURLToPath(import.meta.url));\n  }\n  // CJS: __dirname is available (injected by tsup)\n  return __dirname;\n}\n\nexport async function runMigrations(pool: pg.Pool, migrationsDir?: string): Promise<void> {\n  await pool.query(`\n    CREATE TABLE IF NOT EXISTS _migrations (\n      id SERIAL PRIMARY KEY,\n      name TEXT NOT NULL UNIQUE,\n      applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n    )\n  `);\n\n  const { rows: applied } = await pool.query<{ name: string }>(\n    \"SELECT name FROM _migrations ORDER BY name\",\n  );\n  const appliedSet = new Set(applied.map((r) => r.name));\n\n  const dir = migrationsDir ?? join(getMigrationsDir(), \"pg-migrations\");\n  const files = readdirSync(dir)\n    .filter((f) => f.endsWith(\".sql\"))\n    .sort();\n\n  for (const file of files) {\n    if (appliedSet.has(file)) continue;\n\n    const sql = readFileSync(join(dir, file), \"utf-8\");\n    await pool.query(sql);\n    await pool.query(\"INSERT INTO _migrations (name) VALUES ($1)\", [file]);\n  }\n}\n","import type { Pool as PoolType } from \"pg\";\nimport type { DbConfig } from \"./config\";\n\nexport function maskConnectionString(url: string): string {\n  try {\n    const parsed = new URL(url);\n    // Check if password field is present (including empty password like \"user:@host\")\n    const hasPasswordField =\n      parsed.username.length > 0 && url.includes(`${parsed.username}:`) && url.includes(\"@\");\n    if (hasPasswordField) {\n      parsed.password = \"***\";\n    }\n    return parsed.toString();\n  } catch {\n    return \"***masked***\";\n  }\n}\n\nexport async function createPool(config: DbConfig): Promise<PoolType> {\n  const pg = await import(\"pg\");\n  const { Pool } = pg.default ?? pg;\n\n  const poolConfig: ConstructorParameters<typeof Pool>[0] = {\n    connectionString: config.connectionString,\n    max: config.max,\n  };\n\n  if (config.ssl === \"require\") {\n    poolConfig.ssl = { rejectUnauthorized: false };\n  } else if (config.ssl === \"disable\") {\n    poolConfig.ssl = false;\n  }\n\n  if (config.statementTimeout) {\n    poolConfig.statement_timeout = config.statementTimeout;\n  }\n\n  return new Pool(poolConfig);\n}\n","import { execFile } from \"child_process\";\nimport { platform } from \"os\";\nimport { basename, dirname } from \"path\";\nimport { isWindows } from \"./platform\";\nimport type { DiscoveredProcess } from \"./types\";\n\nexport async function discoverClaudeProcesses(): Promise<DiscoveredProcess[]> {\n  if (platform() === \"win32\") return discoverWindows();\n  return discoverUnix();\n}\n\nasync function discoverUnix(): Promise<DiscoveredProcess[]> {\n  const pids = await getPidsUnix();\n\n  const results = await Promise.all(\n    pids.map(async (pid) => {\n      try {\n        const [cwd, args, startedAt] = await Promise.all([\n          getProcessCwdUnix(pid),\n          getProcessArgsUnix(pid),\n          getProcessStartTimeUnix(pid),\n        ]);\n        const conversationId = extractResumeId(args);\n\n        return {\n          pid,\n          projectPath: cwd,\n          projectName: basename(cwd),\n          branch: await readGitBranch(cwd),\n          conversationId,\n          startedAt,\n        } satisfies DiscoveredProcess;\n      } catch {\n        return null;\n      }\n    }),\n  );\n\n  return results.filter((r): r is DiscoveredProcess => r !== null);\n}\n\nasync function discoverWindows(): Promise<DiscoveredProcess[]> {\n  const pids = await getPidsWindows();\n\n  const results = await Promise.all(\n    pids.map(async (pid) => {\n      try {\n        const info = await getProcessInfoWindows(pid);\n        if (!info) return null;\n\n        return {\n          pid,\n          projectPath: info.cwd,\n          projectName: basename(info.cwd),\n          branch: await readGitBranch(info.cwd),\n          conversationId: extractResumeId(info.args),\n          startedAt: info.startedAt,\n        } satisfies DiscoveredProcess;\n      } catch {\n        return null;\n      }\n    }),\n  );\n\n  return results.filter((r): r is DiscoveredProcess => r !== null);\n}\n\n// ─── Unix Helpers ──────────────────────────────────────────────────\n\nfunction run(\n  cmd: string,\n  args: string[],\n  opts: { cwd?: string; timeout?: number } = {},\n): Promise<string> {\n  return new Promise((resolve, reject) => {\n    execFile(\n      cmd,\n      args,\n      { windowsHide: isWindows, encoding: \"utf-8\", timeout: opts.timeout ?? 5000, cwd: opts.cwd },\n      (err, stdout) => {\n        if (err) reject(err);\n        else resolve(stdout as string);\n      },\n    );\n  });\n}\n\nasync function getPidsUnix(): Promise<number[]> {\n  try {\n    const output = await run(\"pgrep\", [\"-x\", \"claude\"]);\n    return output\n      .trim()\n      .split(\"\\n\")\n      .filter(Boolean)\n      .map((s) => Number.parseInt(s, 10));\n  } catch {\n    return [];\n  }\n}\n\nasync function getProcessCwdUnix(pid: number): Promise<string> {\n  const output = await run(\"lsof\", [\"-p\", String(pid), \"-a\", \"-d\", \"cwd\", \"-Fn\"]);\n  const match = output.match(/n(.+)/);\n  return match?.[1] ?? \"\";\n}\n\nasync function getProcessArgsUnix(pid: number): Promise<string> {\n  return (await run(\"ps\", [\"-p\", String(pid), \"-o\", \"args=\"])).trim();\n}\n\nasync function getProcessStartTimeUnix(pid: number): Promise<Date> {\n  const raw = (await run(\"ps\", [\"-p\", String(pid), \"-o\", \"lstart=\"])).trim();\n  const d = new Date(raw);\n  return Number.isNaN(d.getTime()) ? new Date() : d;\n}\n\n// ─── Windows Helpers ───────────────────────────────────────────────\n\nasync function getPidsWindows(): Promise<number[]> {\n  try {\n    const output = await run(\"tasklist\", [\"/FI\", \"IMAGENAME eq claude.exe\", \"/FO\", \"CSV\", \"/NH\"]);\n    return output\n      .trim()\n      .split(\"\\n\")\n      .filter(Boolean)\n      .map((line) => {\n        const parts = line.split(\",\");\n        return Number.parseInt(parts[1]?.replace(/\"/g, \"\") ?? \"0\", 10);\n      })\n      .filter((pid) => pid > 0);\n  } catch {\n    return [];\n  }\n}\n\nasync function getProcessInfoWindows(\n  pid: number,\n): Promise<{ cwd: string; args: string; startedAt: Date } | null> {\n  try {\n    const output = await run(\"wmic\", [\n      \"process\",\n      \"where\",\n      `ProcessId=${pid}`,\n      \"get\",\n      \"CommandLine,CreationDate,ExecutablePath\",\n      \"/FORMAT:CSV\",\n    ]);\n    // wmic uses CRLF; split on \\r?\\n so the blank separator line becomes \"\" and is filtered out.\n    const lines = output\n      .trim()\n      .split(/\\r?\\n/)\n      .filter((l) => l.trim().length > 0);\n    if (lines.length < 2) return null;\n\n    const parts = lines[1].split(\",\");\n    const args = parts[1] ?? \"\";\n    const creationDate = parts[2] ?? \"\";\n\n    // WMIC CreationDate format: 20260418153000.000000+000\n    const year = creationDate.slice(0, 4);\n    const month = creationDate.slice(4, 6);\n    const day = creationDate.slice(6, 8);\n    const hour = creationDate.slice(8, 10);\n    const min = creationDate.slice(10, 12);\n    const sec = creationDate.slice(12, 14);\n    const startedAt = new Date(`${year}-${month}-${day}T${hour}:${min}:${sec}`);\n    if (Number.isNaN(startedAt.getTime())) return null;\n\n    // CWD is not directly available via wmic; use the executable path's parent directory as fallback\n    const exePath = parts[3] ?? \"\";\n    const cwd = exePath ? dirname(exePath) : \"\";\n\n    return { cwd, args, startedAt };\n  } catch {\n    return null;\n  }\n}\n\n// ─── Shared Helpers ────────────────────────────────────────────────\n\nfunction extractResumeId(args: string): string | null {\n  const match = args.match(/--resume\\s+(\\S+)/);\n  return match?.[1] ?? null;\n}\n\nasync function readGitBranch(dir: string): Promise<string> {\n  try {\n    return (\n      await run(\"git\", [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], { cwd: dir, timeout: 3000 })\n    ).trim();\n  } catch {\n    return \"\";\n  }\n}\n","import { execFileSync } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport { homedir, platform } from \"os\";\nimport { join } from \"path\";\n\nexport const isWindows = platform() === \"win32\";\n\n// ─── Claude executable resolution ─────────────────────────────────────────────\n// On Windows, Task Scheduler strips PATH to bare system directories, so\n// `claude` alone will not resolve. We try where.exe first, then fall back to\n// well-known install locations before giving up and returning the bare name.\n//\n// On macOS, launchd inherits PATH=/usr/bin:/bin:/usr/sbin:/sbin by default,\n// which excludes both Homebrew prefixes. Without an explicit fallback,\n// node-pty's execvp(\"claude\", …) fails with ENOENT and every session\n// dies in milliseconds — see docs/troubleshooting.md. The plist's\n// EnvironmentVariables block is the primary fix; this is defense in depth.\n\nlet _claudeExe: string | undefined;\n\nexport function resolveClaudeExe(): string {\n  if (_claudeExe !== undefined) return _claudeExe;\n\n  if (isWindows) {\n    try {\n      const found = execFileSync(\"where.exe\", [\"claude\"], {\n        encoding: \"utf-8\",\n        windowsHide: true,\n        timeout: 3000,\n      })\n        .trim()\n        .split(\"\\n\")[0]\n        .trim();\n      if (found) {\n        _claudeExe = found;\n        return _claudeExe;\n      }\n    } catch {}\n\n    const candidates = [\n      join(homedir(), \".local\", \"bin\", \"claude.exe\"),\n      join(\n        process.env.LOCALAPPDATA ?? join(homedir(), \"AppData\", \"Local\"),\n        \"Microsoft\",\n        \"WindowsApps\",\n        \"claude.exe\",\n      ),\n    ];\n    for (const p of candidates) {\n      if (existsSync(p)) {\n        _claudeExe = p;\n        return _claudeExe;\n      }\n    }\n  } else {\n    try {\n      const found = execFileSync(\"/usr/bin/which\", [\"claude\"], {\n        encoding: \"utf-8\",\n        timeout: 3000,\n      })\n        .trim()\n        .split(\"\\n\")[0]\n        .trim();\n      if (found && existsSync(found)) {\n        _claudeExe = found;\n        return _claudeExe;\n      }\n    } catch {}\n\n    const candidates = [\n      \"/opt/homebrew/bin/claude\",\n      \"/usr/local/bin/claude\",\n      join(homedir(), \".local\", \"bin\", \"claude\"),\n    ];\n    for (const p of candidates) {\n      if (existsSync(p)) {\n        _claudeExe = p;\n        return _claudeExe;\n      }\n    }\n  }\n\n  _claudeExe = \"claude\";\n  return _claudeExe;\n}\n\n// ─── execHidden ────────────────────────────────────────────────────────────────\n// Thin wrapper around execFileSync that adds windowsHide: true on Windows so\n// spawned child processes (where.exe, tasklist, wmic, pgrep, git …) don't\n// flash a console window.\n\ntype SyncOptions = Parameters<typeof execFileSync>[2];\n\nexport function execHidden(\n  file: string,\n  args: string[],\n  opts?: SyncOptions & { encoding: \"utf-8\" },\n): string {\n  return execFileSync(file, args, {\n    windowsHide: isWindows,\n    ...opts,\n  }) as string;\n}\n","import { Terminal } from \"@xterm/headless\";\nimport { randomUUID } from \"crypto\";\nimport { existsSync } from \"fs\";\nimport { basename } from \"path\";\nimport { getLogger, type Logger } from \"./logger\";\nimport { resolveClaudeExe } from \"./platform\";\nimport { hasPermissionOsc, scrapePermissionGate } from \"./services/questions/detectPermissionGate\";\nimport {\n  detectQuestionFromScreen,\n  questionContentKey,\n} from \"./services/questions/detectQuestionFromScreen\";\nimport { detectShellPrompt } from \"./services/questions/detectShellPrompt\";\nimport type {\n  ManagedSession,\n  PTYManagerOptions,\n  StartFreshSessionOptions,\n  StartSessionOptions,\n} from \"./types\";\n\nconst OUTPUT_BUFFER_MAX = 65536;\n\n// PTY geometry. The headless render terminal (session.screen) MUST match these\n// so Claude's absolute cursor moves (ESC[<row>;<col>H) resolve to the same\n// screen coordinates the real TUI is painting against.\nconst PTY_COLS = 120;\nconst PTY_ROWS = 40;\n// Scrollback depth for the render terminal. Replay reads up to maxLines (200)\n// from the rendered buffer, so keep enough history above the viewport.\nconst SCREEN_SCROLLBACK = 1000;\n\n// Markers that indicate Claude has reached an interactive prompt and is ready\n// for user input. The TUI has at least two startup variants:\n//   - \"Tips\" banner: renders a box with corner ╭ characters\n//   - Connector/MCP-status splash: no box at all; shows ❯ as the prompt arrow\n// We treat either marker in a chunk as \"ready\". False-positives are harmless —\n// the worst case is flushing queued input slightly early, which Claude buffers.\nconst CLAUDE_PROMPT_MARKERS = [\"╭\", \"❯\"] as const;\n\n// If a fresh session has produced any output but neither prompt marker has\n// fired within this window, flush queued input anyway. Defense against future\n// TUI changes that drop both markers from the boot screen.\nconst PROMPT_MARKER_FALLBACK_MS = 10_000;\n\n// Build the two byte sequences for a paste-then-submit. We deliberately split\n// the paste body and the trailing \\r into two separate PTY writes (see\n// writeSubmit() below) so Claude's TUI gets one event-loop tick to process the\n// paste (clear input buffer, render `Pasting…`) before Enter arrives.\n//\n// Why bracketed paste at all: Claude's TUI enables bracketed paste mode\n// (\\x1b[?2004h) at startup. Content between \\x1b[200~ and \\x1b[201~ is\n// committed as a single insertion without triggering autocomplete or key\n// bindings. Without this wrap an input like \"@<path>\" opens the mention\n// picker and the trailing \\r gets consumed as \"accept completion\" rather\n// than \"submit\" — see docs/postmortems/2026-05-20-pty-bracketed-paste-fix.md.\n//\n// Why split paste and \\r: on 2026-05-27 a follow-up stuck session\n// (39118d3e) showed the bracketed-paste wrap was being written but the\n// trailing \\r still didn't submit — Claude's TUI was mid-render of a\n// startup status banner (\"Update available\", \"192 skill descriptions\n// dropped\", etc.) when the bytes arrived, and the whole chunk landed in\n// the wrong handler context. Splitting the write lets the TUI ingest the\n// paste in one tick (the data event runs after current render finishes)\n// before the next tick delivers the Enter.\nfunction buildPasteBytes(input: string): string {\n  return `\\x1b[200~${input}\\x1b[201~`;\n}\n\nconst SUBMIT_BYTES = \"\\r\";\n\n// Delay between the paste write and the submit \\r. We need to yield the event\n// loop at least once so Claude's TUI processes the paste before Enter lands;\n// a small real-time delay is more robust against the TUI batching renders\n// across multiple data events. Kept tiny so user-perceived latency is nil.\nconst SUBMIT_DELAY_MS = 16;\n\nfunction digestBytes(s: string): string {\n  // Replace control chars with their hex form so logs are grep-able.\n  // Building the regex via RegExp() sidesteps a Biome lint rule that flags\n  // literal control characters in regex literals.\n  const escaped = s\n    .replace(new RegExp(String.fromCharCode(0x1b), \"g\"), \"\\\\x1b\")\n    .replace(/\\r/g, \"\\\\r\")\n    .replace(/\\n/g, \"\\\\n\")\n    .replace(/\\t/g, \"\\\\t\");\n  if (escaped.length <= 200) return escaped;\n  return `${escaped.slice(0, 100)}…[${escaped.length - 200}B omitted]…${escaped.slice(-100)}`;\n}\n\n// node-pty is a native addon — import dynamically to allow graceful failure\nlet pty: typeof import(\"node-pty\") | null = null;\n\nasync function loadPty(): Promise<typeof import(\"node-pty\")> {\n  if (pty) return pty;\n  try {\n    pty = await import(\"node-pty\");\n    return pty;\n  } catch (err) {\n    throw new Error(\n      \"node-pty is required for PTY management but failed to load. \" +\n        \"Ensure it is installed: npm install node-pty\\n\" +\n        `Original error: ${err}`,\n    );\n  }\n}\n\ninterface InternalSession extends ManagedSession {\n  process: any; // node-pty IPty\n  outputBuffer: Buffer;\n  // Headless terminal that renders the raw PTY stream into a real screen grid.\n  // getOutputLines() reads its rendered buffer so replay reflects true screen\n  // order rather than raw byte order (which Claude's absolute-cursor repaints\n  // scramble — see getOutputLines for the desync this fixes).\n  screen: Terminal;\n}\n\nfunction createScreen(): Terminal {\n  return new Terminal({\n    cols: PTY_COLS,\n    rows: PTY_ROWS,\n    scrollback: SCREEN_SCROLLBACK,\n    allowProposedApi: true,\n  });\n}\n\n// Build the environment for a spawned `claude` process. The Anthropic API key\n// is injected only here — never exported into the streamer's global process\n// env — so it does not leak into unrelated child processes. CLAUDE_API_KEY\n// (the Fly secret) is mapped to ANTHROPIC_MODEL's sibling, ANTHROPIC_API_KEY,\n// which the CLI reads. If CLAUDE_API_KEY is unset, nothing is added and the CLI\n// falls back to its own auth (e.g. an interactive login).\nfunction buildSpawnEnv(): Record<string, string> {\n  const env = { ...process.env } as Record<string, string>;\n  if (env.CLAUDE_API_KEY) {\n    env.ANTHROPIC_API_KEY = env.CLAUDE_API_KEY;\n  }\n  return env;\n}\n\nexport class PTYManager {\n  private sessions = new Map<string, InternalSession>();\n  private onOutput: PTYManagerOptions[\"onOutput\"];\n  private onStatusChange: PTYManagerOptions[\"onStatusChange\"];\n  private onReady: PTYManagerOptions[\"onReady\"];\n  private onPermissionChange: PTYManagerOptions[\"onPermissionChange\"];\n  private onLiveQuestion: PTYManagerOptions[\"onLiveQuestion\"];\n  private onLiveQuestionGone: PTYManagerOptions[\"onLiveQuestionGone\"];\n  // Per-session permission-gate state. True between an OSC 777 (gate open) and\n  // the next prompt-ready without a fresh 777 (gate closed). Prevents\n  // re-broadcasting open/close on every chunk.\n  private permissionOpen = new Set<string>();\n  // Content key of the last AskUserQuestion broadcast from the rendered screen,\n  // per session — de-dupes the same menu firing on consecutive repaints.\n  private lastScreenQuestionKey = new Map<string, string>();\n  // Content key of the last unstructured shell prompt (detectShellPrompt)\n  // broadcast per session — present between open and resolve so we can clear it\n  // on a prompt-ready/marker return and de-dupe consecutive repaints. Modelled\n  // on permissionOpen but keyed by content (a shell prompt has no OSC trigger).\n  private shellPromptOpen = new Map<string, string>();\n  // Tracks sessions (both fresh and resume) whose PTY has spawned but Claude\n  // hasn't yet reached an interactive prompt — i.e. onReady hasn't fired.\n  private pendingReady = new Set<string>();\n  // Inputs received via sendInput() while the session was still in pendingReady.\n  // Flushed in arrival order once Claude reaches its first prompt. Without this,\n  // input written into the raw PTY mid-boot is consumed by Claude's startup TUI\n  // (welcome banner / first-run modals on fresh, JSONL restore on resume) and\n  // silently lost — the \"dot bug\".\n  private queuedInputs = new Map<string, string[]>();\n  private log: Logger;\n  // Timestamp of first PTY chunk per session; drives the prompt-marker fallback\n  // when neither ╭ nor ❯ shows up within PROMPT_MARKER_FALLBACK_MS.\n  private firstChunkAt = new Map<string, number>();\n  // Per-session chunk counter and last-chunk timestamp. Diagnostic-only,\n  // feeds the [pty.chunk] log lines so we can trace whether Claude responded\n  // to a given input or fell silent. Reset on dispose().\n  private chunkIndex = new Map<string, number>();\n  private lastChunkAt = new Map<string, number>();\n\n  constructor(options: PTYManagerOptions = {}) {\n    this.onOutput = options.onOutput;\n    this.onStatusChange = options.onStatusChange;\n    this.onReady = options.onReady;\n    this.onPermissionChange = options.onPermissionChange;\n    this.onLiveQuestion = options.onLiveQuestion;\n    this.onLiveQuestionGone = options.onLiveQuestionGone;\n    this.log = options.logger ?? getLogger(\"pty\");\n  }\n\n  // Resume an existing Claude conversation. sessionId is the JSONL UUID.\n  //\n  // We use `--permission-mode acceptEdits` rather than `--dangerously-skip-permissions`.\n  // Both suppress file-edit prompts, but in an interactive (TUI) launch the\n  // skip-permissions flag renders a blocking \"Bypass Permissions mode\" warning\n  // menu on every boot that no known ~/.claude.json flag suppressed (as of\n  // Claude CLI v2.1.x) — the session never reaches a usable prompt, so the\n  // mobile app shows an empty/stuck screen. `acceptEdits` auto-approves file edits\n  // without that warning gate, while still prompting for shell commands.\n  // (The other first-run gates — onboarding/theme, workspace trust,\n  // custom-API-key — are cleared by the seeded ~/.claude.json in\n  // docker/entrypoint.sh.) startFresh() uses the same flag for the same reason.\n  async start(sessionId: string, options: StartSessionOptions): Promise<ManagedSession> {\n    const nodePty = await loadPty();\n    const projectName = options.projectName ?? basename(options.projectPath);\n\n    const proc = nodePty.spawn(\n      resolveClaudeExe(),\n      [\n        \"--permission-mode\",\n        \"acceptEdits\",\n        \"--settings\",\n        '{\"spinnerTipsEnabled\":false}',\n        \"--resume\",\n        sessionId,\n      ],\n      {\n        name: \"xterm-256color\",\n        cols: 120,\n        rows: 40,\n        cwd: options.projectPath,\n        env: buildSpawnEnv(),\n      },\n    );\n\n    const session: InternalSession = {\n      id: sessionId,\n      projectPath: options.projectPath,\n      projectName,\n      branch: options.branch ?? \"\",\n      status: \"running\",\n      startedAt: new Date(),\n      completedAt: null,\n      promptCount: 0,\n      lastOutput: \"\",\n      process: proc,\n      outputBuffer: Buffer.alloc(0),\n      screen: createScreen(),\n    };\n\n    this.sessions.set(sessionId, session);\n    // Resume re-uses the same boot path as a fresh launch: --resume replays the\n    // JSONL into Claude's TUI, which can take several seconds before the prompt\n    // is reachable. Until then, raw pty.write() bytes land in the boot UI and\n    // are swallowed (the \"dot bug\" — first message vanishes, second message\n    // appears to trigger both). Same pendingReady + flush gating as startFresh.\n    this.pendingReady.add(sessionId);\n\n    proc.onData((data: string) => {\n      this.handleOutput(sessionId, data);\n    });\n\n    proc.onExit(({ exitCode }: { exitCode: number }) => {\n      this.pendingReady.delete(sessionId);\n      this.handleExit(sessionId, exitCode);\n    });\n\n    return toPublicSession(session);\n  }\n\n  // Start a brand-new Claude session. A stable UUID is generated here and passed\n  // to Claude via --session-id so the JSONL filename matches from the start.\n  // onReady fires once Claude reaches its first prompt (waiting_input).\n  async startFresh(options: StartFreshSessionOptions): Promise<ManagedSession> {\n    const nodePty = await loadPty();\n    const sessionId = randomUUID();\n    const projectName = options.projectName ?? basename(options.projectPath);\n\n    // `--permission-mode acceptEdits` for the same reason as start() above — do not\n    // swap back to --dangerously-skip-permissions (TUI warning gate). Guarded by\n    // __tests__/pty-ready-detection.test.ts.\n    const args = [\n      \"--permission-mode\",\n      \"acceptEdits\",\n      \"--settings\",\n      '{\"spinnerTipsEnabled\":false}',\n      \"--session-id\",\n      sessionId,\n    ];\n    if (options.systemPrompt) {\n      args.push(\"--system-prompt\", options.systemPrompt);\n    }\n\n    const proc = nodePty.spawn(resolveClaudeExe(), args, {\n      name: \"xterm-256color\",\n      cols: 120,\n      rows: 40,\n      cwd: options.projectPath,\n      env: buildSpawnEnv(),\n    });\n\n    const session: InternalSession = {\n      id: sessionId,\n      projectPath: options.projectPath,\n      projectName,\n      branch: \"\",\n      status: \"running\",\n      startedAt: new Date(),\n      completedAt: null,\n      promptCount: 0,\n      lastOutput: \"\",\n      process: proc,\n      outputBuffer: Buffer.alloc(0),\n      screen: createScreen(),\n    };\n\n    this.sessions.set(sessionId, session);\n    this.pendingReady.add(sessionId);\n\n    proc.onData((data: string) => {\n      this.handleOutput(sessionId, data);\n    });\n\n    proc.onExit(({ exitCode }: { exitCode: number }) => {\n      this.pendingReady.delete(sessionId);\n      this.handleExit(sessionId, exitCode);\n    });\n\n    return toPublicSession(session);\n  }\n\n  // Write raw key bytes directly to the PTY without bracketed-paste wrapping.\n  // Use for control sequences (arrow keys, Enter) that must not be quoted.\n  sendKeys(sessionId: string, keys: string): void {\n    const session = this.sessions.get(sessionId);\n    if (!session) throw new Error(`Session not found: ${sessionId}`);\n    if (session.status === \"idle\") {\n      throw new Error(`Session is idle (no active PTY): ${sessionId}`);\n    }\n    if (session.status === \"waiting_input\") {\n      session.status = \"running\";\n      this.onStatusChange?.(toPublicSession(session));\n    }\n    this.log.info(\n      `[pty.keys.write] ${sessionId.slice(0, 8)} bytes=${keys.length} digest=${digestBytes(keys)}`,\n      { event: \"pty.keys_write\", sessionId, byteLen: keys.length },\n    );\n    session.process.write(keys);\n    session.lastActivityAt = new Date();\n  }\n\n  sendInput(sessionId: string, input: string): number {\n    const session = this.sessions.get(sessionId);\n    if (!session) throw new Error(`Session not found: ${sessionId}`);\n    if (session.status === \"idle\") {\n      throw new Error(`Session is idle (no active PTY): ${sessionId}`);\n    }\n    // Claude is still booting (TUI not yet at first prompt). Writing into the\n    // raw PTY now would let the startup UI swallow the keystrokes. Queue and\n    // flush in flushQueuedInputs() once the prompt marker fires.\n    if (this.pendingReady.has(sessionId)) {\n      const queue = this.queuedInputs.get(sessionId) ?? [];\n      queue.push(input);\n      this.queuedInputs.set(sessionId, queue);\n      session.lastActivityAt = new Date();\n      session.promptCount++;\n      // Surface that input is being held because Claude hasn't yet emitted a\n      // prompt marker. If you see this without a corresponding pty.ready\n      // follow-up, the marker detection has regressed.\n      this.log.warn(\n        `[pty.input.queued] ${sessionId.slice(0, 8)} promptCount=${session.promptCount} queueLen=${queue.length}`,\n        {\n          event: \"pty.input_queued\",\n          sessionId,\n          promptCount: session.promptCount,\n          queueLen: queue.length,\n          inputLen: input.length,\n        },\n      );\n      return session.promptCount;\n    }\n    if (session.status === \"waiting_input\") {\n      session.status = \"running\";\n      this.onStatusChange?.(toPublicSession(session));\n    }\n    this.writeSubmit(sessionId, session, input, \"direct\", session.promptCount + 1);\n    session.lastActivityAt = new Date();\n    session.promptCount++;\n    return session.promptCount;\n  }\n\n  // Two-step paste-then-submit. Writes the bracketed-paste body, yields the\n  // event loop for SUBMIT_DELAY_MS, then writes \\r. See buildPasteBytes() for\n  // why the split matters.\n  private writeSubmit(\n    sessionId: string,\n    session: InternalSession,\n    input: string,\n    path: \"direct\" | \"flush\",\n    promptCount: number,\n  ): void {\n    const pasteBytes = buildPasteBytes(input);\n    this.log.info(\n      `[pty.input.write] ${sessionId.slice(0, 8)} promptCount=${promptCount} bytes=${pasteBytes.length} digest=${digestBytes(pasteBytes)}`,\n      {\n        event: \"pty.input_write\",\n        sessionId,\n        promptCount,\n        byteLen: pasteBytes.length,\n        digest: digestBytes(pasteBytes),\n        path,\n        phase: \"paste\",\n      },\n    );\n    session.process.write(pasteBytes);\n    setTimeout(() => {\n      const current = this.sessions.get(sessionId);\n      if (!current || current !== session) return;\n      this.log.info(\n        `[pty.input.submit] ${sessionId.slice(0, 8)} promptCount=${promptCount} digest=\\\\r`,\n        {\n          event: \"pty.input_write\",\n          sessionId,\n          promptCount,\n          byteLen: SUBMIT_BYTES.length,\n          digest: \"\\\\r\",\n          path,\n          phase: \"submit\",\n        },\n      );\n      current.process.write(SUBMIT_BYTES);\n    }, SUBMIT_DELAY_MS);\n  }\n\n  // Drain any inputs that were sent while the session was still pendingReady,\n  // writing them in arrival order now that Claude is at its prompt.\n  private flushQueuedInputs(sessionId: string): void {\n    const queue = this.queuedInputs.get(sessionId);\n    if (!queue || queue.length === 0) return;\n    this.queuedInputs.delete(sessionId);\n    const session = this.sessions.get(sessionId);\n    if (!session) return;\n    this.log.info(`[pty.flush] ${sessionId.slice(0, 8)} flushing ${queue.length} queued input(s)`, {\n      event: \"pty.flush_queued\",\n      sessionId,\n      queueLen: queue.length,\n    });\n    // Chain queued inputs serially: each writeSubmit() defers its \\r by\n    // SUBMIT_DELAY_MS, and we further stagger subsequent inputs by 2x the\n    // delay so paste/submit pairs don't interleave on the wire. Two queued\n    // inputs is rare in practice (user tapped Send twice during the brief\n    // boot window), but ordering must still produce two distinct submits.\n    queue.forEach((input, i) => {\n      const writeAt = i * SUBMIT_DELAY_MS * 2;\n      if (writeAt === 0) {\n        this.writeSubmit(sessionId, session, input, \"flush\", session.promptCount);\n      } else {\n        setTimeout(() => {\n          const current = this.sessions.get(sessionId);\n          if (!current || current !== session) return;\n          this.writeSubmit(sessionId, session, input, \"flush\", session.promptCount);\n        }, writeAt);\n      }\n    });\n  }\n\n  cancel(sessionId: string): void {\n    const session = this.sessions.get(sessionId);\n    if (!session) throw new Error(`Session not found: ${sessionId}`);\n    session.process.kill(\"SIGINT\");\n  }\n\n  killPid(pid: number): void {\n    try {\n      process.kill(pid, \"SIGTERM\");\n    } catch {\n      // Process may already be gone\n    }\n  }\n\n  // Kill the PTY and mark the session idle. Called by the WS grace timer.\n  putOnHold(sessionId: string): void {\n    const session = this.sessions.get(sessionId);\n    if (!session) return;\n    this.pendingReady.delete(sessionId);\n    this.queuedInputs.delete(sessionId);\n    this.firstChunkAt.delete(sessionId);\n    this.permissionOpen.delete(sessionId);\n    this.lastScreenQuestionKey.delete(sessionId);\n    this.shellPromptOpen.delete(sessionId);\n    try {\n      session.process.kill(\"SIGINT\");\n    } catch {\n      // already dead\n    }\n    session.status = \"idle\";\n    session.completedAt = new Date();\n    session.screen.dispose();\n    this.sessions.delete(sessionId);\n    this.onStatusChange?.(toPublicSession(session));\n  }\n\n  getOutput(sessionId: string): string {\n    const session = this.sessions.get(sessionId);\n    if (!session) throw new Error(`Session not found: ${sessionId}`);\n    return session.outputBuffer.toString(\"utf-8\");\n  }\n\n  // Render the last `maxLines` rows of the session's screen in true on-screen\n  // order. Reads the headless terminal (fed raw PTY bytes in handleOutput) so\n  // Claude's absolute-cursor repaints resolve to where they actually paint —\n  // unlike the old raw-byte slice, which scrambled order after a TUI repaint\n  // and made replayed conversations appear out of order on resume.\n  //\n  // Async because xterm parses writes on a deferred tick; we flush pending\n  // writes (empty write + callback) before reading so the buffer is current.\n  async getOutputLines(sessionId: string, maxLines: number): Promise<string[]> {\n    const session = this.sessions.get(sessionId);\n    if (!session) throw new Error(`Session not found: ${sessionId}`);\n    await new Promise<void>((resolve) => session.screen.write(\"\", () => resolve()));\n\n    const buf = session.screen.buffer.active;\n    const lines: string[] = [];\n    // buf.length spans scrollback + viewport; iterate the whole thing top-down\n    // so the rendered output preserves screen order, then keep the last N.\n    for (let y = 0; y < buf.length; y++) {\n      lines.push(buf.getLine(y)?.translateToString(true) ?? \"\");\n    }\n    // Drop trailing blank rows (the unused bottom of the viewport) before\n    // trimming to maxLines, so replay isn't padded with empty lines.\n    while (lines.length > 0 && lines[lines.length - 1] === \"\") {\n      lines.pop();\n    }\n    return lines.slice(-maxLines);\n  }\n\n  getSession(sessionId: string): ManagedSession | null {\n    const session = this.sessions.get(sessionId);\n    return session ? toPublicSession(session) : null;\n  }\n\n  hasSession(sessionId: string): boolean {\n    return this.sessions.has(sessionId);\n  }\n\n  listSessions(): ManagedSession[] {\n    return Array.from(this.sessions.values()).map(toPublicSession);\n  }\n\n  dispose(): void {\n    for (const session of this.sessions.values()) {\n      try {\n        session.process.kill();\n      } catch {\n        // Process may already be dead\n      }\n      session.screen.dispose();\n    }\n    this.sessions.clear();\n    this.firstChunkAt.clear();\n    this.chunkIndex.clear();\n    this.lastChunkAt.clear();\n    this.permissionOpen.clear();\n    this.lastScreenQuestionKey.clear();\n    this.shellPromptOpen.clear();\n  }\n\n  private handleOutput(sessionId: string, data: string): void {\n    const session = this.sessions.get(sessionId);\n    if (!session) return;\n\n    const chunk = Buffer.from(data, \"utf-8\");\n    const now = Date.now();\n    if (!this.firstChunkAt.has(sessionId)) {\n      this.firstChunkAt.set(sessionId, now);\n    }\n\n    // Per-chunk diagnostic log. Keep until the @<path> submit bug is solved.\n    const idx = (this.chunkIndex.get(sessionId) ?? 0) + 1;\n    this.chunkIndex.set(sessionId, idx);\n    const last = this.lastChunkAt.get(sessionId);\n    this.lastChunkAt.set(sessionId, now);\n    const gapMs = last == null ? 0 : now - last;\n    this.log.info(\n      `[pty.chunk] ${sessionId.slice(0, 8)} #${idx} +${chunk.length}B gap=${gapMs}ms status=${session.status} digest=${digestBytes(data)}`,\n      {\n        event: \"pty.chunk\",\n        sessionId,\n        chunkIndex: idx,\n        chunkBytes: chunk.length,\n        gapMs,\n        status: session.status,\n        pendingReady: this.pendingReady.has(sessionId),\n        digest: digestBytes(data),\n      },\n    );\n\n    session.outputBuffer = Buffer.concat([session.outputBuffer, chunk]);\n\n    if (session.outputBuffer.length > OUTPUT_BUFFER_MAX) {\n      session.outputBuffer = session.outputBuffer.subarray(\n        session.outputBuffer.length - OUTPUT_BUFFER_MAX,\n      );\n    }\n\n    // Render into the headless screen so getOutputLines() can reproduce true\n    // on-screen order. write() is async (parsed on a later tick) but we never\n    // read the screen synchronously after a single chunk — replay only happens\n    // on subscribe, long after these writes have drained.\n    session.screen.write(data);\n\n    const stripped = stripAnsi(data);\n    session.lastOutput = stripped;\n    const matchedMarker = CLAUDE_PROMPT_MARKERS.find((m) => stripped.includes(m));\n\n    if (session.status === \"running\" && matchedMarker) {\n      this.markReady(sessionId, session, `marker:${matchedMarker}`);\n    } else if (\n      session.status === \"running\" &&\n      this.pendingReady.has(sessionId) &&\n      now - (this.firstChunkAt.get(sessionId) ?? now) >= PROMPT_MARKER_FALLBACK_MS\n    ) {\n      // Fallback: PTY has produced output for >=10s but neither marker fired.\n      // Treat the session as ready so queued input doesn't sit forever.\n      this.markReady(sessionId, session, \"fallback:timeout\");\n    }\n\n    this.onOutput?.(sessionId, data);\n\n    // Live interactive-prompt detection from the PTY stream — fires the moment\n    // a prompt is on screen, independent of (and ahead of) the JSONL flush.\n    // Trigger-gated so we don't scrape the rendered buffer on every chunk:\n    //   - OSC 777 (raw byte signal) → permission gate opened.\n    //   - \"Enter to select\" footer (AskUserQuestion menu) → structured question.\n    //   - prompt-ready marker without a fresh 777 → gate may have closed.\n    this.detectLivePrompts(sessionId, data, stripped).catch((err) => {\n      this.log.warn(\"[pty.prompt_detect] failed\", {\n        event: \"pty.prompt_detect_failed\",\n        sessionId,\n        err,\n      });\n    });\n  }\n\n  // Detect permission gates (OSC 777 + scraped options) and AskUserQuestion\n  // menus from the rendered screen, firing the additive callbacks. Async because\n  // reading the rendered buffer needs the xterm write queue flushed. Pure\n  // detection lives in services/questions/*; this only orchestrates triggers,\n  // per-session debounce, and the callbacks.\n  private async detectLivePrompts(\n    sessionId: string,\n    rawData: string,\n    stripped: string,\n  ): Promise<void> {\n    const session = this.sessions.get(sessionId);\n    if (!session) return;\n\n    const oscPermission = hasPermissionOsc(rawData);\n    // Footer test on the CURRENT chunk — a cheap trigger only. The authoritative\n    // test runs on the full rendered screen below (askFooterOnScreen), because\n    // the OSC-777 notify and the \"Enter to select\" footer often arrive in\n    // SEPARATE chunks: the OSC lands first (no footer yet), so a chunk-only test\n    // misclassifies an AskUserQuestion menu as a permission gate.\n    const hasAskFooter = /Enter to select/i.test(stripped);\n    const hasPromptMarker = CLAUDE_PROMPT_MARKERS.some((m) => stripped.includes(m));\n    // Cheap raw-chunk hint that an UNSTRUCTURED shell prompt may be on screen:\n    // a y/N hint, a \"press enter\"/\"continue?\" tail, or a numbered menu row. Just\n    // a trigger gate (mirrors hasAskFooter) — the real conservative matching is\n    // in detectShellPrompt against the rendered tail. ponytail: substring gate.\n    const hasShellPromptHint =\n      /[[(]\\s*y\\s*\\/\\s*n\\s*[\\])]|press\\s+(enter|return|any key)|\\bcontinue\\b\\s*\\?|^\\s*(?:❯|>)?\\s*\\d+[.)]\\s+\\S/im.test(\n        stripped,\n      );\n\n    // Nothing to do unless a trigger fired or a prompt we already broadcast is\n    // open (so we can detect its close on the next prompt-ready).\n    if (\n      !oscPermission &&\n      !hasAskFooter &&\n      !hasShellPromptHint &&\n      !this.permissionOpen.has(sessionId) &&\n      !this.shellPromptOpen.has(sessionId) &&\n      !this.lastScreenQuestionKey.has(sessionId)\n    ) {\n      return;\n    }\n\n    const lines = await this.getOutputLines(sessionId, 60);\n\n    // Authoritative footer test on the FULL rendered screen (not just the\n    // trigger chunk). The \"Enter to select · Tab/Arrow keys to navigate\" footer\n    // is unique to an AskUserQuestion menu; a permission gate uses \"Tab to amend\n    // · ctrl+e to explain\". When this is on screen the prompt IS an\n    // AskUserQuestion — even if an OSC-777 notify also fired — so it must take\n    // priority over the permission path (Claude emits OSC 777 for BOTH).\n    const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));\n\n    // Diagnostic: dump the rendered window + detector verdicts when a trigger\n    // fires — the on-device source of truth for prompt detection (e.g. the\n    // multi-question carousel work). At debug level so it's silent under normal\n    // --verbose; enable with LOG_LEVEL=debug.\n    if (oscPermission || hasAskFooter || askFooterOnScreen) {\n      this.log.debug?.(`[pty.prompt_detect] ${sessionId.slice(0, 8)} trigger`, {\n        event: \"pty.prompt_detect\",\n        sessionId,\n        oscPermission,\n        hasAskFooter,\n        askFooterOnScreen,\n        permGate: oscPermission && !askFooterOnScreen ? scrapePermissionGate(lines) : undefined,\n        askQuestion: askFooterOnScreen ? detectQuestionFromScreen(lines) : undefined,\n        renderedTail: lines.slice(-25),\n      });\n    }\n\n    // ── Permission gate ──────────────────────────────────────────────\n    // Skip entirely when the screen is an AskUserQuestion menu — its OSC-777\n    // notify would otherwise be misread as a permission gate.\n    if (oscPermission && !askFooterOnScreen) {\n      const gate = scrapePermissionGate(lines);\n      this.permissionOpen.add(sessionId);\n      // Broadcast even if options aren't painted yet (gate: empty options) so\n      // the client can show \"Claude needs permission\" immediately; a later\n      // repaint with the footer/options re-broadcasts the populated gate.\n      this.onPermissionChange?.(sessionId, gate ?? { options: [] });\n    } else if (this.permissionOpen.has(sessionId) && !askFooterOnScreen) {\n      // Gate was open. If options are still on screen, refresh (cursor moved);\n      // if the prompt is ready again and the options are gone, the gate closed.\n      const gate = scrapePermissionGate(lines);\n      if (gate) {\n        this.onPermissionChange?.(sessionId, gate);\n      } else if (hasPromptMarker) {\n        this.permissionOpen.delete(sessionId);\n        this.onPermissionChange?.(sessionId, null);\n      }\n    }\n\n    // ── AskUserQuestion menu (rendered, ahead of JSONL) ──────────────\n    // The multi-question TUI's final \"Ready to submit your answers?\" screen is\n    // detected and broadcast as a normal card (Submit answers / Cancel) so the\n    // user can tap to confirm — the carousel doesn't reliably auto-submit.\n    // (Roadmap: robust carousel auto-submit.)\n    if (askFooterOnScreen) {\n      const detected = detectQuestionFromScreen(lines);\n      if (detected) {\n        const key = questionContentKey(detected.questions);\n        if (this.lastScreenQuestionKey.get(sessionId) !== key) {\n          this.lastScreenQuestionKey.set(sessionId, key);\n          this.onLiveQuestion?.(sessionId, detected.questions);\n        }\n      }\n    } else if (this.lastScreenQuestionKey.has(sessionId) && hasPromptMarker) {\n      // The menu was open but its footer is gone and Claude's prompt marker is\n      // back — the question was answered (or dismissed). Clear the screen key\n      // and tell the server so the pending question is cancelled; without this\n      // the answered menu lingers and a later repaint can re-broadcast it.\n      this.lastScreenQuestionKey.delete(sessionId);\n      this.onLiveQuestionGone?.(sessionId);\n    }\n\n    // ── Unstructured shell prompt (read -p \"[y/N]\", CLI picker, … ) ───\n    // Last fallback: only when this isn't already a structured permission gate\n    // or AskUserQuestion menu. Reuses the `permission` transport so mobile\n    // renders a QuestionCard with zero new event handling; each option carries\n    // its literal answerKeys (y\\r / n\\r / N\\r) so the client stays dumb.\n    if (!oscPermission && !askFooterOnScreen && !this.permissionOpen.has(sessionId)) {\n      const shell = detectShellPrompt(lines);\n      if (shell) {\n        const key = `${shell.prompt}\u0000${shell.options.map((o) => o.label).join(\"\u0000\")}`;\n        if (this.shellPromptOpen.get(sessionId) !== key) {\n          this.shellPromptOpen.set(sessionId, key);\n          this.onPermissionChange?.(sessionId, {\n            prompt: shell.prompt,\n            options: shell.options,\n          });\n        }\n      } else if (this.shellPromptOpen.has(sessionId) && hasPromptMarker) {\n        // Prompt is gone and Claude's marker is back (input was answered) —\n        // close the card.\n        this.shellPromptOpen.delete(sessionId);\n        this.onPermissionChange?.(sessionId, null);\n      }\n    }\n  }\n\n  // Transition a session from \"running\" to \"waiting_input\", clear pendingReady,\n  // and flush any queued input. Idempotent: callers can invoke at any chunk.\n  private markReady(sessionId: string, session: InternalSession, reason: string): void {\n    session.lastActivityAt = new Date();\n    session.status = \"waiting_input\";\n    // Log retained on purpose: `reason=fallback:timeout` would be the only\n    // signal that Claude's TUI introduced a new boot variant our markers miss.\n    const elapsedMs = Date.now() - (this.firstChunkAt.get(sessionId) ?? Date.now());\n    this.log.info(`[pty.ready] ${sessionId.slice(0, 8)} ${reason} (elapsed=${elapsedMs}ms)`, {\n      event: \"pty.ready\",\n      sessionId,\n      reason,\n      elapsedMs,\n    });\n    this.onStatusChange?.(toPublicSession(session));\n    if (this.pendingReady.has(sessionId)) {\n      this.pendingReady.delete(sessionId);\n      this.flushQueuedInputs(sessionId);\n      this.onReady?.(toPublicSession(session));\n    }\n  }\n\n  private handleExit(sessionId: string, exitCode: number): void {\n    const session = this.sessions.get(sessionId);\n    if (!session) return;\n\n    session.completedAt = new Date();\n    session.status = \"idle\";\n\n    // Instant exit with no output — diagnose the most likely cause.\n    const elapsedMs = session.completedAt.getTime() - session.startedAt.getTime();\n    if (exitCode !== 0 && elapsedMs < 2000 && session.lastOutput === \"\") {\n      if (!existsSync(session.projectPath)) {\n        session.failureReason = `Project directory not found: ${session.projectPath}`;\n      } else {\n        session.failureReason =\n          `Process exited immediately (code ${exitCode}). ` +\n          `Check that the Claude binary is installed and accessible.`;\n      }\n    }\n\n    this.onStatusChange?.(toPublicSession(session));\n    session.screen.dispose();\n    this.sessions.delete(sessionId);\n    this.queuedInputs.delete(sessionId);\n    this.firstChunkAt.delete(sessionId);\n    this.permissionOpen.delete(sessionId);\n    this.lastScreenQuestionKey.delete(sessionId);\n    this.shellPromptOpen.delete(sessionId);\n  }\n}\n\nfunction toPublicSession(s: InternalSession): ManagedSession {\n  return {\n    id: s.id,\n    projectPath: s.projectPath,\n    projectName: s.projectName,\n    branch: s.branch,\n    status: s.status,\n    startedAt: s.startedAt,\n    completedAt: s.completedAt,\n    promptCount: s.promptCount,\n    lastOutput: s.lastOutput,\n    ...(s.failureReason != null && { failureReason: s.failureReason }),\n    ...(s.lastActivityAt != null && { lastActivityAt: s.lastActivityAt }),\n    ...(s.filePath != null && { filePath: s.filePath }),\n  };\n}\n\n// Strip ANSI escape sequences for clean text preview\nfunction stripAnsi(str: string): string {\n  // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional ANSI stripping\n  return str.replace(/\\x1b\\[[0-9;]*[a-zA-Z]/g, \"\").replace(/\\x1b\\][^\\x07]*\\x07/g, \"\");\n}\n","import pino, { type Logger as PinoLogger } from \"pino\";\n\nexport type LogDest = \"console\" | \"pino\" | \"both\";\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst baseLogger: PinoLogger = pino({\n  level: process.env.LOG_LEVEL ?? \"info\",\n  base: { service: \"tb-streamer\" },\n  timestamp: pino.stdTimeFunctions.isoTime,\n  redact: {\n    paths: [\"req.headers.authorization\", \"req.headers.cookie\", 'req.headers[\"x-api-key\"]'],\n    censor: \"[redacted]\",\n  },\n});\n\nexport interface Logger {\n  debug(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;\n  info(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;\n  warn(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;\n  error(msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;\n  log(level: LogLevel, msg: string, fields?: Record<string, unknown>, dest?: LogDest): void;\n  pino: PinoLogger;\n}\n\nfunction emit(\n  pinoChild: PinoLogger,\n  level: LogLevel,\n  msg: string,\n  fields: Record<string, unknown> | undefined,\n  dest: LogDest,\n): void {\n  if (dest === \"pino\" || dest === \"both\") {\n    if (fields && Object.keys(fields).length > 0) pinoChild[level](fields, msg);\n    else pinoChild[level](msg);\n  }\n  if (dest === \"console\" || dest === \"both\") {\n    const consoleMethod: \"log\" | \"warn\" | \"error\" =\n      level === \"error\" ? \"error\" : level === \"warn\" ? \"warn\" : \"log\";\n    console[consoleMethod](msg);\n  }\n}\n\nfunction build(pinoChild: PinoLogger): Logger {\n  return {\n    debug: (m, f, d = \"both\") => emit(pinoChild, \"debug\", m, f, d),\n    info: (m, f, d = \"both\") => emit(pinoChild, \"info\", m, f, d),\n    warn: (m, f, d = \"both\") => emit(pinoChild, \"warn\", m, f, d),\n    error: (m, f, d = \"both\") => emit(pinoChild, \"error\", m, f, d),\n    log: (lvl, m, f, d = \"both\") => emit(pinoChild, lvl, m, f, d),\n    pino: pinoChild,\n  };\n}\n\nexport function getLogger(component?: string): Logger {\n  return build(component ? baseLogger.child({ component }) : baseLogger);\n}\n\nexport const logger: Logger = build(baseLogger);\n","// Permission-gate detection. Two independent signals:\n//\n//   1. OSC 777 escape — the deterministic TRIGGER. The PTY emits\n//      `\\x1b]777;notify;Claude Code;Claude needs your permission\\x07`\n//      (often tmux-wrapped: `\\x1bPtmux;\\x1b\\x1b]777;notify;…\\x1b\\`) the instant\n//      a gate opens. Confirmed in live logs (pty.chunk #53/#193). This tells us\n//      a gate is open; it carries no option text.\n//\n//   2. Rendered option scrape — the gate's numbered options are painted into the\n//      screen grid via absolute-cursor moves, so they live in the rendered\n//      headless buffer (getOutputLines), not the raw byte stream. We read the\n//      ACTUAL leading numbers and the `❯` cursor — the numbers are NOT a stable\n//      1-based index (a gate can show \"2. Yes / 3. No\"), which is exactly the\n//      \"2 didn't take\" bug this avoids.\n\nexport interface PermissionOption {\n  /** The real leading number shown on screen (NOT a 1-based array index). */\n  index: number;\n  label: string;\n  /**\n   * Literal keystroke bytes that answer this option, when the number alone\n   * isn't the answer (e.g. a y/N shell prompt answers \"y\\r\"/\"n\\r\", not \"1\\r\").\n   * Additive: OSC-777 gates omit it and the client answers via `index`; the\n   * unstructured shell-prompt path (detectShellPrompt) populates it.\n   */\n  answerKeys?: string;\n}\n\nexport interface PermissionGate {\n  /** Prompt text above the options, e.g. \"Claude needs your permission to use Bash\". */\n  prompt?: string;\n  options: PermissionOption[];\n  /** Index value (the on-screen number) of the `❯`-highlighted option, if any. */\n  cursor?: number;\n}\n\n// OSC 777 with the notify command from Claude Code. We match the stable core\n// (`]777;notify;Claude Code`) rather than the whole tmux-passthrough wrapper so\n// detection survives both the wrapped and unwrapped forms.\n// biome-ignore lint/suspicious/noControlCharactersInRegex: matching the literal OSC 777 escape\nconst OSC_777_RE = /\\x1b\\]777;notify;Claude Code;[^\\x07\\x1b]*/;\n\n/** True if a raw PTY chunk contains the Claude Code OSC 777 permission notify. */\nexport function hasPermissionOsc(rawData: string): boolean {\n  return OSC_777_RE.test(rawData);\n}\n\n// A rendered option row: optional `❯` cursor, then \"N. label\". Leading spaces\n// from the box/indent are tolerated (the box gutter is stripped first). We\n// capture N and the label separately.\nconst OPTION_RE = /^\\s*(❯)?\\s*(\\d+)\\.\\s+(.+?)\\s*$/;\n\n// Chrome lines that are never the prompt: footers, box-drawing, prompt arrows.\nconst FOOTER_RE = /Enter to select|Esc to cancel|↑|↓|to navigate|to cancel/i;\nconst BOX_ONLY_RE = /^[\\s│─┌┐└┘├┤┬┴┼╭╮╰╯╱╲=_-]+$/;\nconst PROMPT_ARROW_RE = /^[\\s]*[❯›>]\\s*$/;\n\n// Strip leading and trailing box-drawing gutters (\"│ … │\") so a boxed gate\n// frame parses the same as an unboxed one.\nfunction stripGutter(line: string): string {\n  return line.replace(/^\\s*[│|]\\s?/, \"\").replace(/\\s*[│|]\\s*$/, \"\");\n}\n\n/**\n * Scrape the permission gate's options + prompt from rendered screen lines.\n * Returns null when no numbered options are present (not a gate, or not painted\n * yet). Pure — no I/O.\n */\nexport function scrapePermissionGate(lines: string[]): PermissionGate | null {\n  const options: PermissionOption[] = [];\n  let cursor: number | undefined;\n  let firstOptionLine = -1;\n\n  for (let i = 0; i < lines.length; i++) {\n    const m = OPTION_RE.exec(stripGutter(lines[i]));\n    if (!m) continue;\n    const index = Number.parseInt(m[2], 10);\n    if (!Number.isFinite(index)) continue;\n    if (firstOptionLine === -1) firstOptionLine = i;\n    if (m[1]) cursor = index; // `❯` marks the highlighted option\n    options.push({ index, label: m[3] });\n  }\n\n  if (options.length === 0) return null;\n\n  // Prompt = nearest non-empty, non-chrome line above the first option row.\n  let prompt: string | undefined;\n  for (let i = firstOptionLine - 1; i >= 0; i--) {\n    const t = stripGutter(lines[i]).trim();\n    if (t.length === 0) continue;\n    if (BOX_ONLY_RE.test(lines[i].trim()) || FOOTER_RE.test(t) || PROMPT_ARROW_RE.test(t)) continue;\n    prompt = t || undefined;\n    break;\n  }\n\n  return { ...(prompt ? { prompt } : {}), options, ...(cursor !== undefined ? { cursor } : {}) };\n}\n","import type { AskOption, AskQuestion } from \"../../types\";\n\n// Detect a structured AskUserQuestion menu from the RENDERED screen lines\n// (getOutputLines), not JSONL. The JSONL path (detectAskUserQuestion) is\n// structurally late — the question is painted in the TUI before the tool_use\n// block flushes to disk, and a backgrounded sub-agent may write to a different\n// JSONL than the watched session. Scraping the rendered screen fires the moment\n// the menu is on screen.\n//\n// The on-screen AskUserQuestion menu looks like:\n//\n//     Which area are you focused on?\n//     ❯ 1. macOS / Chrome\n//       2. iOS / Safari\n//       …\n//       6. Chat about this\n//     Enter to select · Tab/Arrow keys to navigate · Esc to cancel\n//\n// Differences from the JSONL form: no header/description/preview (the screen\n// only shows labels), and the leading number is the on-screen index. We map it\n// to the AskQuestion shape (header/description default to \"\") so the existing\n// `question` WS event and QuestionCard render path are reused unchanged.\n\n// The footer that terminates an AskUserQuestion menu — its presence is the\n// positive signal that this block IS a structured picker (vs a permission gate,\n// which uses a different footer and y/n phrasing).\nconst ASK_FOOTER_RE = /Enter to select/i;\nconst ESC_FOOTER_RE = /Esc to cancel|to navigate/i;\n\n// An option row: optional `❯` cursor + \"N. label\". Tolerates the box indent.\nconst OPTION_RE = /^\\s*(?:❯)?\\s*(\\d+)\\.\\s+(.+?)\\s*$/;\n\n// A question header ends with \"?\" (AskUserQuestion questions are interrogative).\nconst QUESTION_RE = /\\?\\s*$/;\n\nconst BOX_ONLY_RE = /^[\\s│─┌┐└┘├┤┬┴┼╭╮╰╯╱╲=_-]+$/;\n\n// Reject permission-gate option labels so a gate (caught separately via OSC 777)\n// never doubles as a structured question.\nconst PERMISSION_LABEL_RE = /^(Yes|No)\\b/i;\n\n// The final confirmation screen of a multi-question AskUserQuestion TUI:\n//   Ready to submit your answers?\n//   ❯ 1. Submit answers\n//     2. Cancel\n//   Enter to select · …\n// The user already answered each question via cards, so this internal step is\n// auto-confirmed by the PTY layer and must never render as its own card.\nconst SUBMIT_QUESTION_RE = /ready to submit your answers\\?/i;\nconst SUBMIT_OPTION_RE = /^submit answers?$/i;\n\n// Strip leading AND trailing box-drawing gutters (\"│ … │\") so a menu drawn\n// inside a box parses the same as an unboxed one (the trailing gutter matters\n// for the \"?\"-suffix question test).\nfunction stripBoxGutter(line: string): string {\n  return line.replace(/^\\s*[│|]\\s?/, \"\").replace(/\\s*[│|]\\s*$/, \"\");\n}\n\n/**\n * Detect an AskUserQuestion menu in rendered screen lines. Returns the question\n * set (single-question; the TUI shows one picker at a time) or null.\n * Pure — no I/O.\n */\nexport function detectQuestionFromScreen(lines: string[]): { questions: AskQuestion[] } | null {\n  // Find the footer that closes the menu; scan upward from it.\n  let footerIdx = -1;\n  for (let i = lines.length - 1; i >= 0; i--) {\n    if (ASK_FOOTER_RE.test(lines[i])) {\n      footerIdx = i;\n      break;\n    }\n  }\n  if (footerIdx === -1) return null;\n\n  // Collect option rows above the footer. Real menus interleave each \"N. label\"\n  // row with wrapped DESCRIPTION lines and may insert a box border between\n  // options, so a non-option line is NOT a hard stop — it's skipped as long as\n  // we haven't reached the question line yet. We stop at: the question line (ends\n  // with \"?\"), a blank gap once options have started, or the top of the window.\n  const options: AskOption[] = [];\n  let firstOptionIdx = -1;\n  for (let i = footerIdx - 1; i >= 0; i--) {\n    const line = lines[i];\n    const inner = stripBoxGutter(line);\n    const trimmed = inner.trim();\n    if (trimmed.length === 0) {\n      if (options.length === 0) continue; // gap between footer and options\n      break; // blank line above the option block ends it\n    }\n    if (BOX_ONLY_RE.test(line.trim())) continue; // box border between/around options\n    if (ESC_FOOTER_RE.test(line) && options.length === 0) continue;\n    // The question line terminates the option block (it's the row right above\n    // the first option, after any descriptions). Stop before consuming it.\n    if (options.length > 0 && QUESTION_RE.test(trimmed) && !OPTION_RE.test(inner)) break;\n    const m = OPTION_RE.exec(inner);\n    if (m) {\n      const label = m[2].trim();\n      if (PERMISSION_LABEL_RE.test(label)) return null; // it's a permission gate\n      options.unshift({ label, description: \"\" });\n      firstOptionIdx = i;\n    }\n    // else: a wrapped description / continuation line for the option below it —\n    // skip it and keep scanning upward for more options.\n  }\n\n  if (options.length < 2 || firstOptionIdx === -1) return null;\n\n  // Question = nearest non-empty, non-box line above the first option row that\n  // ends with \"?\".\n  let question: string | undefined;\n  for (let i = firstOptionIdx - 1; i >= 0; i--) {\n    const raw = stripBoxGutter(lines[i]);\n    const trimmed = raw.trim();\n    if (trimmed.length === 0) continue;\n    if (BOX_ONLY_RE.test(lines[i].trim())) continue;\n    if (QUESTION_RE.test(trimmed)) {\n      question = trimmed;\n    }\n    break;\n  }\n\n  if (!question) return null;\n\n  // The multi-question \"Ready to submit your answers?\" confirmation IS surfaced\n  // as a real card (Submit answers / Cancel) so the user can tap to confirm —\n  // the multi-question carousel doesn't reliably auto-submit. (Roadmap: robust\n  // auto-submit.) So we no longer reject it here.\n\n  return {\n    questions: [{ question, header: \"\", multiSelect: false, options }],\n  };\n}\n\n/**\n * True when the rendered screen is the AskUserQuestion \"Ready to submit your\n * answers?\" confirmation (a question line + a \"Submit answers\" option under the\n * Ask footer). Pure — no I/O. Retained as a classifier (e.g. for analytics);\n * the submit screen now renders as a normal card.\n */\nexport function isSubmitConfirmationScreen(lines: string[]): boolean {\n  let hasFooter = false;\n  let hasSubmitQuestion = false;\n  let hasSubmitOption = false;\n  for (const line of lines) {\n    const inner = stripBoxGutter(line).trim();\n    if (ASK_FOOTER_RE.test(line)) hasFooter = true;\n    if (SUBMIT_QUESTION_RE.test(inner)) hasSubmitQuestion = true;\n    const m = OPTION_RE.exec(inner);\n    if (m && SUBMIT_OPTION_RE.test(m[2].trim())) hasSubmitOption = true;\n  }\n  return hasFooter && hasSubmitQuestion && hasSubmitOption;\n}\n\n/**\n * Stable content key for de-duping a screen-scraped question against the JSONL\n * detection of the same question (they share no toolUseId). Keyed on the\n * question text + option labels.\n */\nexport function questionContentKey(questions: AskQuestion[]): string {\n  return questions\n    .map((q) => `${q.question} ${q.options.map((o) => o.label).join(\",\")}`)\n    .join(\"::\");\n}\n","// Detect UNSTRUCTURED blocking prompts in the rendered tail — the ones that are\n// neither an AskUserQuestion menu (detectQuestionFromScreen) nor an OSC-777\n// permission gate (scrapePermissionGate): a shell `read -p \"… [y/N]\"`, a CLI\n// picker's `(y/n)`, a numbered menu printed by a plain script, etc.\n//\n// Scraped into the SAME structured shape the permission gate emits, so mobile\n// renders it as an ordinary QuestionCard with tappable options. Each option\n// carries the LITERAL keystroke that answers it (`answerKeys`) so the client\n// stays dumb — it just sends those bytes. This is the ONE net-new detector;\n// everything downstream (event shape, mobile rendering, answer-by-keystroke) is\n// reused.\n//\n// Philosophy mirrors the existing detectors: conservative but false-positive\n// tolerant. A spurious card is recoverable (the user ignores it or taps and the\n// keys are harmless); a MISSED prompt strands a blocked PTY. We only fire on a\n// tight set of patterns anchored to the LAST non-blank rendered line.\n\nconst ENTER = \"\\r\";\n\nexport interface ShellPromptOption {\n  /** Positional fallback number (1-based). Authoritative answer is `answerKeys`. */\n  index: number;\n  label: string;\n  /** The exact bytes to send to answer this option, e.g. \"y\\r\" or \"2\\r\". */\n  answerKeys: string;\n}\n\nexport interface ShellPrompt {\n  prompt: string;\n  options: ShellPromptOption[];\n}\n\n// A y/N family prompt: the last line ends in a bracketed/parenthesised yes-no\n// hint. Capture which letter is the default (upper-case) only to keep the label\n// order natural — both options are always offered.\n//   \"Continue? [y/N]\"  \"Overwrite (y/n)?\"  \"Proceed [Y/n] \"\nconst YN_RE = /[[(]\\s*y\\s*\\/\\s*n\\s*[\\])]/i;\n\n// A bare confirmation with no explicit options — Enter is the only sensible\n// answer. \"Press enter to continue\", \"Press any key…\", a trailing \"Continue?\".\nconst PRESS_ENTER_RE = /press\\s+(enter|return|any key)/i;\nconst CONTINUE_RE = /\\bcontinue\\b\\s*\\??\\s*$/i;\n\n// A numbered menu row: optional cursor, \"N. label\" or \"N) label\". Same grammar\n// scrapePermissionGate uses, but here the menu is a plain script's output with\n// no OSC-777 and no Claude box.\nconst NUMBERED_RE = /^\\s*(?:❯|>)?\\s*(\\d+)[.)]\\s+(.+?)\\s*$/;\n\n// Lines that are never a prompt: Claude's own box-drawing / menu chrome. If any\n// of these appear in the tail we defer to the structured detectors and bail —\n// this detector is strictly the fallback for plain shell output.\nconst CLAUDE_CHROME_RE = /Enter to select|Esc to cancel|╭|╰|│.*│/;\nconst BOX_ONLY_RE = /^[\\s│─┌┐└┘├┤┬┴┼╭╮╰╯╱╲=_-]+$/;\n\nfunction lastNonBlank(lines: string[]): { text: string; idx: number } | null {\n  for (let i = lines.length - 1; i >= 0; i--) {\n    const t = lines[i].trim();\n    if (t.length > 0) return { text: t, idx: i };\n  }\n  return null;\n}\n\n/**\n * Scrape an unstructured shell prompt from rendered screen lines. Returns null\n * when the tail isn't a recognised prompt (prose, Claude's own UI, empty).\n * Pure — no I/O.\n */\nexport function detectShellPrompt(lines: string[]): ShellPrompt | null {\n  // Defer to the structured detectors if Claude's TUI is on screen. Those paths\n  // own AskUserQuestion / permission gates; this is plain-shell only.\n  if (lines.some((l) => CLAUDE_CHROME_RE.test(l))) return null;\n\n  const last = lastNonBlank(lines);\n  if (!last) return null;\n\n  // ── y/N family ───────────────────────────────────────────────────\n  if (YN_RE.test(last.text)) {\n    return {\n      prompt: last.text,\n      options: [\n        { index: 1, label: \"Yes\", answerKeys: `y${ENTER}` },\n        { index: 2, label: \"No\", answerKeys: `n${ENTER}` },\n      ],\n    };\n  }\n\n  // ── numbered menu ────────────────────────────────────────────────\n  // Only when the LAST line is itself a numbered row (the menu just finished\n  // printing and is waiting) — collect the contiguous numbered block ending it.\n  if (NUMBERED_RE.test(last.text)) {\n    const options: ShellPromptOption[] = [];\n    for (let i = 0; i <= last.idx; i++) {\n      const m = NUMBERED_RE.exec(lines[i]);\n      if (!m) continue;\n      const num = Number.parseInt(m[1], 10);\n      if (!Number.isFinite(num)) continue;\n      options.push({ index: num, label: m[2].trim(), answerKeys: `${num}${ENTER}` });\n    }\n    if (options.length >= 2) {\n      // Prompt = nearest non-chrome line above the first numbered row.\n      const firstRow = lines.findIndex((l) => NUMBERED_RE.test(l));\n      let prompt = \"\";\n      for (let i = firstRow - 1; i >= 0; i--) {\n        const t = lines[i].trim();\n        if (t.length === 0 || BOX_ONLY_RE.test(t)) continue;\n        prompt = t;\n        break;\n      }\n      return { prompt: prompt || \"Select an option\", options };\n    }\n  }\n\n  // ── bare confirmation (Enter only) ───────────────────────────────\n  if (PRESS_ENTER_RE.test(last.text) || CONTINUE_RE.test(last.text)) {\n    return {\n      prompt: last.text,\n      options: [{ index: 1, label: \"Continue\", answerKeys: ENTER }],\n    };\n  }\n\n  return null;\n}\n","import { createNodeWebSocket } from \"@hono/node-ws\";\nimport { Connection, Client as TemporalClient } from \"@temporalio/client\";\nimport {\n  applyIncludeFilter,\n  applyPagination,\n  applyProjectFilter,\n  applySort,\n  type Conversation,\n  type ConversationMeta,\n  ConversationScanner,\n  type FileStatEntry,\n  type SortOrder,\n  search,\n} from \"@threadbase-sh/scanner\";\nimport { EventEmitter } from \"events\";\nimport {\n  createReadStream,\n  existsSync,\n  watch as fsWatch,\n  readdirSync,\n  readFileSync,\n  statSync,\n} from \"fs\";\nimport { realpath } from \"fs/promises\";\nimport type { Hono } from \"hono\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"http\";\nimport { homedir } from \"os\";\nimport { dirname, join } from \"path\";\nimport { createInterface } from \"readline\";\nimport type { WebSocket } from \"ws\";\nimport { type AgentClient, createAgentClient } from \"./agent/agent-client\";\nimport { type AgentConfig, readAgentConfig } from \"./agent/agent-config\";\nimport { type ConversationWriter, createConversationWriter } from \"./agent/conversation-writer\";\nimport { handleSendAgentInput } from \"./agent/handle-send-agent-input\";\nimport { handleStartAgentSession } from \"./agent/handle-start-agent-session\";\nimport { type AppEnv, createHonoApp } from \"./api/app\";\nimport { ALREADY_HANDLED } from \"./api/routes/sessions.routes\";\nimport { createWsRoutes } from \"./api/routes/ws.routes\";\nimport type { ApiDeps } from \"./api/types/api-deps\";\nimport {\n  generateApiKey,\n  loadBrowseRoot,\n  loadCacheDir,\n  loadPublicUrl,\n  loadTailSize,\n  setApiKey,\n  validatePublicUrl,\n} from \"./auth\";\nimport {\n  BrowsePathNotFoundError,\n  createDirectory,\n  listDirectories,\n  resolveBrowsePath,\n} from \"./browse\";\nimport { ConversationCache, type ConversationListItem } from \"./conversation-cache\";\nimport { createPool, getDbConfig, maskConnectionString, runMigrations } from \"./db\";\nimport { CacheMetadataRepository } from \"./db/repositories/cacheMetadata.repository\";\nimport { ConversationsRepository } from \"./db/repositories/conversations.repository\";\nimport { ProjectsRepository } from \"./db/repositories/projects.repository\";\nimport { SessionsRepository } from \"./db/repositories/sessions.repository\";\nimport { recordUpload } from \"./db/upload-records\";\nimport { handleListProjects } from \"./handlers/handleListProjects\";\nimport { getLogger } from \"./logger\";\nimport { PairTokenStore } from \"./pair-store\";\nimport { discoverClaudeProcesses } from \"./process-discovery\";\nimport { CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER, isProviderResumable } from \"./providers\";\nimport { PTYManager } from \"./pty-manager\";\nimport { seal } from \"./seal\";\nimport { ConversationWatcher } from \"./services/conversations/conversationWatcher\";\nimport { parseAgentEntrypointsEnv } from \"./services/conversations/isAgentConversation\";\nimport { pruneAgentConversations } from \"./services/conversations/pruneAgentConversations\";\nimport { deriveProjectChatTitle } from \"./services/projectChats/deriveProjectChatTitle\";\nimport { questionContentKey } from \"./services/questions/detectQuestionFromScreen\";\nimport {\n  questionsFromLines,\n  shouldBroadcastQuestion,\n} from \"./services/questions/questionBroadcast\";\nimport { resolveAnswer } from \"./services/questions/resolveAnswer\";\nimport { SessionStore } from \"./session-store\";\nimport type {\n  AskQuestion,\n  DiscoveredProcess,\n  PermissionOption,\n  ServerConfig,\n  SessionSortKey,\n  SortOrder as SessionSortOrder,\n  SessionStatus,\n} from \"./types\";\n\nimport { saveUploadFile } from \"./uploads\";\nimport { computeConversationEtag } from \"./utils/conversationEtag\";\nimport { debounce } from \"./utils/debounce\";\nimport { isScannedSnapshotStale } from \"./utils/isScannedSnapshotStale\";\nimport { createScanProgressThrottle } from \"./utils/scanProgressThrottle\";\nimport { WSHub } from \"./ws-hub\";\n\nconst BROWSE_SYSTEM_PROMPT = (browseRoot: string) =>\n  `You are working within the project boundary: ${browseRoot}. ` +\n  `Do not read, write, or execute commands that access files or directories outside this boundary.`;\n\nconst DEFAULT_SYSTEM_PROMPT =\n  \"When presenting options or choices to the user, limit the options to at most 3.\";\n\nconst DEFAULT_PTY_GRACE_PERIOD_MS = 270_000; // 4.5 minutes\n\n// Default OFF. Set to \"1\" or \"true\" to show Claude Agent SDK / claude-mem\n// runs in /api/conversations and /project-chats.\nexport function parseIncludeAgentsEnv(raw: string | undefined): boolean {\n  if (raw === undefined) return false;\n  const v = raw.trim().toLowerCase();\n  return !(v === \"0\" || v === \"false\" || v === \"no\" || v === \"off\" || v === \"\");\n}\n\nexport class StreamerServer {\n  private httpServer: ReturnType<typeof createServer>;\n  private ptyManager: PTYManager;\n  private sessionStore: SessionStore;\n  private wsHub: WSHub;\n  private fileWatcher: ConversationWatcher;\n  private sessionFileMap = new Map<string, string>(); // sessionId → JSONL filePath\n  private pendingQuestions = new Map<string, { toolUseId: string; questions: AskQuestion[] }>();\n  // Content key of the AskUserQuestion currently broadcast for a session (from\n  // either the rendered screen or JSONL), used to de-dupe the two paths: when\n  // the screen detection fires first, the later JSONL flush of the same question\n  // is suppressed. Cleared alongside pendingQuestions.\n  private pendingQuestionKey = new Map<string, string>();\n  // Per-session permission gate currently open (scraped via OSC 777). Parallel\n  // to pendingQuestions; mobile answers it by sending the option index via\n  // /input { keys }. Cleared when the gate closes.\n  private pendingPermission = new Map<\n    string,\n    { prompt?: string; options: PermissionOption[]; cursor?: number }\n  >();\n  private scanner: ConversationScanner | null = null;\n  // Tracks every ConversationScanner ever created so close() can shut them all\n  // down and release SQLite handles (open handles block temp-dir deletion on Windows).\n  private allScanners = new Set<ConversationScanner>();\n  private scannerReady: Promise<unknown> | null = null;\n  // Set by onConversationChanged while a scan is in-flight; getScanner() does\n  // a single rescan after the current one completes instead of restarting it.\n  private scannerStale = false;\n  // True only while bindWithRetry is actively retrying. The persistent\n  // listener-level 'error' handler demotes EADDRINUSE to debug during this\n  // window so the self-healing kickstart-relaunch race doesn't spam warn.\n  private binding = false;\n  private cacheReady = false;\n  private apiKey: string;\n  private apiKeySource: \"config\" | \"cli\";\n  private localNoAuth: boolean;\n  private logMenubarRequests: boolean;\n  private verbose: boolean;\n  private scanProfiles:\n    | Array<{ id: string; label: string; configDir: string; enabled: boolean; emoji: string }>\n    | undefined;\n  private dbPool: Awaited<ReturnType<typeof createPool>> | null = null;\n  private dbInstanceId: string | null = null;\n  private disableDb = false;\n  private browseRoot: string | null = null;\n  private publicUrl: string | null = null;\n  private pairTokens = new PairTokenStore();\n  private exchangeAttempts = new Map<string, number[]>();\n  private sessionStartAttempts = new Map<string, number[]>();\n  private sessionInputAttempts = new Map<string, number[]>();\n  private ptyGracePeriodMs: number;\n  private defaultSystemPrompt: string;\n  // Map of sessionId → grace timer; fires to kill PTY after WS disconnect\n  private ptyGraceTimers = new Map<string, ReturnType<typeof setTimeout>>();\n  // Map of sessionId → set of subscribed WS clients\n  private sessionSubscribers = new Map<string, Set<WebSocket>>();\n  // Map of clientId → WS socket (populated by the \"register\" WS handshake)\n  private clientIdToWs = new Map<string, WebSocket>();\n  // Reverse map for cleanup on close\n  private wsToClientId = new Map<WebSocket, string>();\n  private cache: ConversationCache | null = null;\n  private projectsRepo: ProjectsRepository | null = null;\n  private conversationsRepo: ConversationsRepository | null = null;\n  private sessionsRepo: SessionsRepository | null = null;\n  private cacheMetadataRepo: CacheMetadataRepository | null = null;\n  private discoveryCache: {\n    entries: DiscoveredProcess[];\n    fetchedAt: number;\n  } | null = null;\n  private cacheDir: string;\n  private tailSize: number;\n  private directoryDebounceMs: number;\n  // Trailing-debounced trigger that flags the scanner stale after a quiet\n  // period, collapsing a burst of directory events into one rescan. Assigned\n  // in the constructor body (NOT a field initializer) so directoryDebounceMs\n  // is already set when debounce() captures the wait.\n  private markScannerStaleDebounced!: ReturnType<typeof debounce>;\n  private codexRoots: string[];\n  private includeAgents: boolean;\n  private agentEntrypoints: ReadonlySet<string>;\n  private honoApp: Hono<AppEnv>;\n  private log = getLogger(\"server\");\n  private agentConfig: AgentConfig;\n  private agentClient: AgentClient | null = null;\n  private sessionStatusBus = new EventEmitter();\n\n  constructor(config: ServerConfig & { apiKey: string }) {\n    this.sessionStatusBus.setMaxListeners(0);\n    this.apiKey = config.apiKey;\n    this.apiKeySource = config.apiKeySource ?? \"config\";\n    this.localNoAuth = config.localNoAuth ?? false;\n    this.logMenubarRequests = config.logMenubarRequests ?? false;\n    if (this.localNoAuth) {\n      console.warn(\n        \"[WARN] localNoAuth is ENABLED — all requests from localhost bypass authentication. \" +\n          \"Do not run with --local-no-auth in shared or production environments.\",\n      );\n    }\n    this.verbose = config.verbose ?? false;\n    this.disableDb = config.disableDb ?? false;\n    this.scanProfiles = config.scanProfiles;\n    this.codexRoots = config.codexRoots ?? [join(homedir(), \".codex\", \"sessions\")];\n    this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;\n    this.defaultSystemPrompt = config.defaultSystemPrompt ?? DEFAULT_SYSTEM_PROMPT;\n    this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join(homedir(), \".threadbase\", \"cache\");\n    this.tailSize = config.tailSize ?? loadTailSize() ?? 10;\n    this.directoryDebounceMs =\n      parseDirScanDebounceEnv(process.env.THREADBASE_DIR_SCAN_DEBOUNCE_MS) ??\n      config.directoryScanDebounceMs ??\n      1000;\n    this.markScannerStaleDebounced = debounce(() => {\n      if (this.scannerReady) this.scannerStale = true;\n      else this.scanner = null;\n    }, this.directoryDebounceMs);\n    this.includeAgents = parseIncludeAgentsEnv(process.env.THREADBASE_INCLUDE_AGENTS);\n    this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);\n\n    const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config.browseRoot;\n    if (rawRoot) {\n      realpath(rawRoot)\n        .then((resolved) => {\n          this.browseRoot = resolved;\n          if (this.verbose) this.log.info(`Browse root: ${resolved}`, { browseRoot: resolved });\n        })\n        .catch(() => {\n          this.log.warn(`Warning: browse root does not exist: ${rawRoot}`, { browseRoot: rawRoot });\n        });\n    }\n\n    const rawPublicUrl = process.env.THREADBASE_PUBLIC_URL ?? config.publicUrl ?? loadPublicUrl();\n    if (rawPublicUrl) {\n      const result = validatePublicUrl(rawPublicUrl);\n      if (result.ok) {\n        this.publicUrl = result.normalized;\n        if (this.verbose)\n          this.log.info(`Public URL: ${this.publicUrl}`, { publicUrl: this.publicUrl });\n      } else {\n        this.log.warn(`Warning: ${result.error}`, { error: result.error });\n      }\n    }\n\n    this.sessionStore = new SessionStore();\n    this.wsHub = new WSHub();\n\n    this.fileWatcher = new ConversationWatcher({\n      onNewLines: (filePath, lines) => {\n        // One transactional cache write for the whole batch instead of per line.\n        this.cache?.updateFromLines(filePath, lines);\n        for (const [sessionId, watchedPath] of this.sessionFileMap) {\n          if (watchedPath === filePath) {\n            // toolUseId the client currently holds for this session (set by the\n            // live-screen path as `screen:…`, or a prior JSONL flush). Captured\n            // BEFORE the overwrite below so we can detect an id change.\n            const priorToolUseId = this.pendingQuestions.get(sessionId)?.toolUseId;\n            const { messages, pending } = questionsFromLines(sessionId, lines);\n            for (const p of pending) {\n              this.pendingQuestions.set(sessionId, p); // last pending wins\n              const t = setTimeout(() => {\n                if (this.pendingQuestions.get(sessionId)?.toolUseId === p.toolUseId) {\n                  this.cancelPendingQuestion(sessionId);\n                }\n              }, 60_000);\n              t.unref();\n            }\n            // De-dupe vs the live-screen path: if the rendered detection already\n            // broadcast this exact question (same content key), don't re-render —\n            // EXCEPT when the real JSONL toolUseId differs from the synthetic\n            // `screen:` id the client holds. The client answers with the id it\n            // was given; if it still has the screen id, resolveAnswer rejects the\n            // POST as tool_use_mismatch. Re-broadcasting the real id re-syncs the\n            // client (mapAskQuestionToBlock just replaces activeQuestion — the\n            // card re-renders identically) so answering works.\n            for (const m of messages) {\n              const key = questionContentKey(m.questions);\n              const broadcast = shouldBroadcastQuestion({\n                newContentKey: key,\n                lastContentKey: this.pendingQuestionKey.get(sessionId),\n                newToolUseId: m.toolUseId,\n                priorToolUseId,\n              });\n              this.pendingQuestionKey.set(sessionId, key);\n              if (broadcast) this.wsHub.broadcast(m);\n            }\n            // Additive batched event (one socket write) for newer clients...\n            this.wsHub.broadcast({ type: \"conversation_events\", sessionId, lines });\n            // ...plus per-line conversation_event so older mobile clients,\n            // which only know that shape, keep working byte-for-byte.\n            for (const line of lines) {\n              this.wsHub.broadcast({ type: \"conversation_event\", sessionId, line });\n            }\n            break;\n          }\n        }\n      },\n      onConversationChanged: (filePath) => {\n        // A new JSONL appeared (or changed) in a watched project directory.\n        // Invalidate only the affected file's cache row immediately (cheap\n        // single-row delete; wiping the whole cache on every event would\n        // prevent the warm-up from persisting while active sessions write).\n        this.cache?.invalidateByFilePath(filePath);\n        // Debounce the global scanner-staleness flip so a burst of directory\n        // events during active sessions collapses into one rescan trigger\n        // after a quiet period. The debounced callback still checks\n        // scannerReady at fire time, preserving the anti-infinite-loop rule\n        // (never null scannerReady mid-scan).\n        this.markScannerStaleDebounced();\n        this.log.debug?.(`Scanner invalidated by directory event: ${filePath}`, {\n          filePath,\n          event: \"cache.directory_change\",\n        });\n      },\n      onFileDeleted: (filePath) => {\n        const id = this.cache?.invalidateByFilePath(filePath);\n        if (id)\n          this.log.info(`Cache row invalidated after JSONL delete: ${id}`, {\n            id,\n            filePath,\n            event: \"cache.invalidate_on_unlink\",\n          });\n      },\n    });\n\n    this.ptyManager = new PTYManager({\n      logger: getLogger(\"pty\"),\n      onOutput: (sessionId, data) => {\n        this.wsHub.broadcast({ type: \"terminal_output\", sessionId, data });\n      },\n      onPermissionChange: (sessionId, gate) => {\n        this.handlePermissionChange(sessionId, gate);\n      },\n      onLiveQuestion: (sessionId, questions) => {\n        this.handleLiveQuestion(sessionId, questions);\n      },\n      onLiveQuestionGone: (sessionId) => {\n        // The rendered AskUserQuestion menu closed. Clear the screen-dedupe key\n        // and cancel the pending question so the answered card is dismissed and\n        // a later repaint can't re-broadcast it. Only acts on a screen-scoped\n        // question — once the JSONL flush recorded the real toolUseId, the\n        // answer path (handleSendAnswer) already cleared it.\n        this.pendingQuestionKey.delete(sessionId);\n        const pq = this.pendingQuestions.get(sessionId);\n        if (pq?.toolUseId.startsWith(\"screen:\")) {\n          this.cancelPendingQuestion(sessionId);\n        }\n      },\n      onReady: (session) => {\n        const resp = this.sessionStore.get(session.id, this.ptyAttachedIds());\n        if (resp) this.wsHub.broadcast({ type: \"session_ready\", session: resp });\n      },\n      onStatusChange: (session) => {\n        this.sessionStore.updateManaged(session.id, {\n          status: session.status,\n          completedAt: session.completedAt,\n          ...(session.lastActivityAt != null && { lastActivityAt: session.lastActivityAt }),\n        });\n        // Refresh the scanner index at the end of each Claude turn so the\n        // conversation is searchable with up-to-date content immediately.\n        if (session.status === \"waiting_input\" || session.status === \"idle\") {\n          const filePath = this.sessionFileMap.get(session.id);\n          if (filePath) {\n            this.getScanner()\n              .then((scanner) => scanner.refreshFile(filePath))\n              .then((meta) => {\n                this.log.info(\"scanner.refreshFile: ok\", {\n                  event: \"scanner.refresh\",\n                  sessionId: session.id,\n                  filePath,\n                  trigger: session.status,\n                  messageCount: meta?.messageCount,\n                });\n              })\n              .catch((err) => {\n                this.log.warn(\"scanner.refreshFile: failed\", {\n                  event: \"scanner.refresh_failed\",\n                  sessionId: session.id,\n                  filePath,\n                  trigger: session.status,\n                  err,\n                });\n              });\n          }\n        }\n        // Stop watching JSONL when PTY exits (session goes idle)\n        if (session.status === \"idle\") {\n          const filePath = this.sessionFileMap.get(session.id);\n          if (filePath) {\n            this.fileWatcher.unwatch(filePath);\n            this.sessionFileMap.delete(session.id);\n            this.cancelPendingQuestion(session.id);\n          }\n          // A gone PTY can never have an open gate; clear silently.\n          this.pendingPermission.delete(session.id);\n        }\n        const resp = this.sessionStore.get(session.id, this.ptyAttachedIds());\n        if (resp) {\n          this.wsHub.broadcast({ type: \"session_update\", session: resp });\n        }\n        this.sessionStatusBus.emit(`status:${session.id}`, session.status);\n      },\n    });\n\n    // ─── Multi-agent mode bootstrap ──────────────────────────────────\n    // When MULTI_AGENT_FLOW is on, construct the Temporal client + JSONL\n    // writer. We use Connection.lazy() so the constructor stays sync —\n    // the actual gRPC connection happens on first RPC.\n    this.agentConfig = readAgentConfig();\n    const agentConfig = this.agentConfig;\n    let conversationWriter: ConversationWriter | null = null;\n    if (agentConfig.enabled) {\n      const connection = Connection.lazy({\n        address: agentConfig.temporal.address,\n      });\n      const temporalClient = new TemporalClient({\n        connection,\n        namespace: agentConfig.temporal.namespace,\n      });\n      this.agentClient = createAgentClient({\n        temporalClient,\n        taskQueue: agentConfig.temporal.taskQueue,\n      });\n      // JSONL goes next to (not inside) the SQLite cacheDir, mirroring the\n      // existing convention: ~/.threadbase/conversations/.\n      const conversationsBaseDir =\n        agentConfig.conversationsDir || join(dirname(this.cacheDir), \"conversations\");\n      conversationWriter = createConversationWriter({\n        baseDir: conversationsBaseDir,\n      });\n    }\n    const agentClient = this.agentClient;\n\n    const self = this;\n    const apiDeps: ApiDeps = {\n      // ponytail: getter so rotateApiKey() takes effect without restarting the server\n      get apiKey() {\n        return self.apiKey;\n      },\n      localNoAuth: this.localNoAuth,\n      logMenubarRequests: this.logMenubarRequests,\n      rotateApiKey: () => this.rotateApiKey(),\n      publicUrl: this.publicUrl,\n      browseRoot: this.browseRoot,\n      ptyManager: this.ptyManager,\n      sessionStore: this.sessionStore,\n      wsHub: this.wsHub,\n      cache: () => this.cache,\n      projectsRepo: () => this.projectsRepo,\n      conversationsRepo: () => this.conversationsRepo,\n      sessionsRepo: () => this.sessionsRepo,\n      cacheMetadataRepo: () => this.cacheMetadataRepo,\n      ptyAttachedIds: () => this.ptyAttachedIds(),\n      handleListSessions: (url, res) => this.handleListSessions(url, res),\n      handleSessionsCount: (res) => this.handleSessionsCount(res),\n      handleGetRecentSessions: (url, res) => this.handleGetRecentSessions(url, res),\n      handleGetSessionNames: (res) => this.handleGetSessionNames(res),\n      handleGetSession: (id, res) => this.handleGetSession(id, res),\n      handleGetOutput: (id, res) => this.handleGetOutput(id, res),\n      handleSendInput: (id, req, res) => this.handleSendInput(id, req, res),\n      handleSendAnswer: (id, req, res) => this.handleSendAnswer(id, req, res),\n      handleCancel: (id, res) => this.handleCancel(id, res),\n      handleStopSession: (id, res) => this.handleStopSession(id, res),\n      handleSetSessionName: (id, req, res) => this.handleSetSessionName(id, req, res),\n      handleUploadFile: (id, req, res) => this.handleUploadFile(id, req, res),\n      handleAdopt: (id, res) => this.handleAdopt(id, res),\n      handleResume: (req, res) => this.handleResume(req, res),\n      handleStartSession: (req, res) => this.handleStartSession(req, res),\n      handleListConversations: (url, res) => this.handleListConversations(url, res),\n      handleConversationsCount: (url, res) => this.handleConversationsCount(url, res),\n      handleGetConversation: (id, url, res, ifNoneMatch) =>\n        this.handleGetConversation(id, url, res, ifNoneMatch),\n      handleSearch: (url, res) => this.handleSearch(url, res),\n      handleListProjects: (url, res) => handleListProjects(url, res),\n      handleGetPopularProjects: (url, res) => this.handleGetPopularProjects(url, res),\n      handlePairStart: (res) => this.handlePairStart(res),\n      handlePairExchange: (req, res) => this.handlePairExchange(req, res),\n      handleBrowse: (url, res) => this.handleBrowse(url, res),\n      handleMkdir: (req, res) => this.handleMkdir(req, res),\n      handleWsOpen: (ws) => {\n        this.wsHub.addClient(ws);\n        const sessions = this.sessionStore.list(this.ptyAttachedIds());\n        ws.send(JSON.stringify({ type: \"session_list\", sessions }));\n        if (this.cacheReady) {\n          ws.send(JSON.stringify({ type: \"cache_ready\" }));\n        }\n      },\n      handleWsMessage: async (ws, raw) => {\n        try {\n          const msg = JSON.parse(String(raw));\n          if (msg.type === \"register\" && typeof msg.clientId === \"string\") {\n            const oldClientId = this.wsToClientId.get(ws);\n            if (oldClientId) this.clientIdToWs.delete(oldClientId);\n            this.clientIdToWs.set(msg.clientId, ws);\n            this.wsToClientId.set(ws, msg.clientId);\n          }\n          if (msg.type === \"subscribe_session\" && typeof msg.sessionId === \"string\") {\n            this.addSessionSubscriber(msg.sessionId, ws);\n            if (this.ptyManager.hasSession(msg.sessionId)) {\n              const lines = await this.ptyManager.getOutputLines(msg.sessionId, 200);\n              ws.send(JSON.stringify({ type: \"terminal_replay\", sessionId: msg.sessionId, lines }));\n            }\n          }\n          if (msg.type === \"hold_session\" && typeof msg.sessionId === \"string\") {\n            this.startGraceTimer(msg.sessionId, 0);\n          }\n        } catch {\n          // malformed JSON, ignore\n        }\n      },\n      handleWsClose: (ws) => {\n        const clientId = this.wsToClientId.get(ws);\n        if (clientId) {\n          this.clientIdToWs.delete(clientId);\n          this.wsToClientId.delete(ws);\n        }\n        for (const [sessionId, subscribers] of this.sessionSubscribers) {\n          subscribers.delete(ws);\n          if (subscribers.size === 0) {\n            this.startGraceTimer(sessionId, this.ptyGracePeriodMs);\n          }\n        }\n      },\n      agentClient,\n      conversationWriter,\n      agentConfig,\n    };\n\n    this.httpServer = createServer((req, res) => this.handleRequest(req, res));\n\n    // Defense-in-depth against unhandled socket errors that would otherwise\n    // crash the process with \"Unhandled 'error' event\":\n    //\n    // 1. 'clientError' fires when the http parser rejects a request (bad\n    //    headers, etc.). Default behavior destroys the socket, but a stale\n    //    handler could leak. We respond 400 (or destroy on any I/O error)\n    //    and never throw.\n    this.httpServer.on(\"clientError\", (_err, socket) => {\n      try {\n        socket.end(\"HTTP/1.1 400 Bad Request\\r\\nConnection: close\\r\\n\\r\\n\");\n      } catch {\n        socket.destroy();\n      }\n    });\n    // 2. Listener-level 'error' (port in use, etc.) — log instead of crashing.\n    this.httpServer.on(\"error\", (err) => {\n      const e = err as NodeJS.ErrnoException;\n      // While bindWithRetry is retrying, each failed listen() attempt also\n      // reaches this persistent handler. That EADDRINUSE is the expected,\n      // self-healing kickstart race — log it at debug, not warn, so boots stay\n      // quiet. Genuine runtime errors (and the final give-up) still warn.\n      if (this.binding && e.code === \"EADDRINUSE\") {\n        this.log.debug?.(`httpServer error during bind: ${err.message}`, {\n          error: err.message,\n          event: \"http.server_error\",\n        });\n        return;\n      }\n      this.log.warn(`httpServer error: ${err.message}`, {\n        error: err.message,\n        event: \"http.server_error\",\n      });\n    });\n    // 3. The WebSocket upgrade race that caused real prod crashes:\n    //    @hono/node-ws registers an 'upgrade' listener that does `await\n    //    app.request(...)` before promoting the socket. If the peer RSTs\n    //    during the await, the raw net.Socket emits 'error' with no listener,\n    //    crashing the process. Registering our own 'upgrade' listener FIRST\n    //    attaches a noop 'error' handler to the raw socket so the upgrade\n    //    abort becomes a harmless event. Node fires upgrade listeners in\n    //    registration order, so this must be wired before injectWebSocket().\n    this.httpServer.on(\"upgrade\", (_req, socket) => {\n      socket.on(\"error\", () => {\n        // Intentional: a RST during the WS handshake is normal client\n        // behavior (network blip, peer kill). The socket is already torn\n        // down; we just need to absorb the event so Node doesn't crash.\n      });\n    });\n\n    // createNodeWebSocket needs the real Hono app (it calls app.request() on\n    // upgrade). Resolve the chicken-and-egg by creating the app without WS\n    // routes first, handing it to createNodeWebSocket, then mounting the WS\n    // route onto the same app instance.\n    this.honoApp = createHonoApp(apiDeps);\n    const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app: this.honoApp });\n    this.honoApp.route(\"/\", createWsRoutes(apiDeps, upgradeWebSocket));\n    injectWebSocket(this.httpServer);\n  }\n\n  // ─── PTY Grace Timer ────────────────────────────────────────────\n\n  private ptyAttachedIds(): Set<string> {\n    return new Set(this.ptyManager.listSessions().map((s) => s.id));\n  }\n\n  /**\n   * Send a session_list to only the client that triggered this HTTP request\n   * (identified by X-Client-Id header → registered WS socket). Falls back to\n   * a full broadcast if no match exists (old clients, or no WS registered yet).\n   */\n  private broadcastOrUnicastSessionList(req: IncomingMessage): void {\n    const clientId = req.headers[\"x-client-id\"];\n    const ws = typeof clientId === \"string\" ? this.clientIdToWs.get(clientId) : undefined;\n    const payload = {\n      type: \"session_list\" as const,\n      sessions: this.sessionStore.list(this.ptyAttachedIds()),\n    };\n    if (ws) {\n      this.wsHub.unicast(ws, payload);\n    } else {\n      this.wsHub.broadcast(payload);\n    }\n  }\n\n  private addSessionSubscriber(sessionId: string, ws: WebSocket): void {\n    let subs = this.sessionSubscribers.get(sessionId);\n    if (!subs) {\n      subs = new Set();\n      this.sessionSubscribers.set(sessionId, subs);\n    }\n    subs.add(ws);\n    // Cancel any pending grace timer since someone is now watching\n    const existing = this.ptyGraceTimers.get(sessionId);\n    if (existing) {\n      clearTimeout(existing);\n      this.ptyGraceTimers.delete(sessionId);\n    }\n  }\n\n  private startGraceTimer(sessionId: string, delayMs: number): void {\n    const existing = this.ptyGraceTimers.get(sessionId);\n    if (existing) clearTimeout(existing);\n\n    const timer = setTimeout(() => {\n      this.ptyGraceTimers.delete(sessionId);\n      if (this.ptyManager.hasSession(sessionId)) {\n        // Never interrupt a session mid-response. A `running` PTY is actively\n        // streaming a Claude turn that hasn't flushed to the JSONL yet; killing\n        // it (SIGINT) would lose the in-flight answer. Re-arm the grace timer\n        // and re-check after another grace period — it only becomes eligible\n        // for hold once it settles back to waiting_input/idle.\n        const resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n        if (resp?.status === \"running\") {\n          this.log.info(\n            `[grace] session ${sessionId} still running, deferring hold`,\n            { sessionId, event: \"pty.grace_defer\" },\n            \"pino\",\n          );\n          this.startGraceTimer(sessionId, delayMs);\n          return;\n        }\n        this.sessionSubscribers.delete(sessionId);\n        this.log.info(\n          `[grace] killing idle PTY for ${sessionId}`,\n          { sessionId, event: \"pty.grace_kill\" },\n          \"pino\",\n        );\n        this.ptyManager.putOnHold(sessionId);\n        const held = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n        if (held) this.wsHub.broadcast({ type: \"session_update\", session: held });\n      } else {\n        this.sessionSubscribers.delete(sessionId);\n      }\n    }, delayMs);\n\n    this.ptyGraceTimers.set(sessionId, timer);\n  }\n\n  get port(): number {\n    const addr = this.httpServer.address();\n    return typeof addr === \"object\" && addr ? addr.port : 0;\n  }\n\n  async listen(port: number, opts?: { awaitReady?: boolean }): Promise<void> {\n    // DB is still used for upload records and other non-session purposes.\n    // Session state is no longer persisted to DB.\n    const dbConfig = this.disableDb ? null : getDbConfig();\n    if (dbConfig) {\n      this.dbPool = await createPool(dbConfig);\n      this.dbInstanceId = dbConfig.instanceId;\n      const masked = maskConnectionString(dbConfig.connectionString);\n      this.log.info(`Database enabled: ${masked}`, {\n        connectionString: masked,\n        instanceId: dbConfig.instanceId,\n      });\n      this.log.info(`Instance ID: ${dbConfig.instanceId}`, { instanceId: dbConfig.instanceId });\n      await runMigrations(this.dbPool);\n      this.log.info(\"Database migrations applied\", { event: \"db.migrations_applied\" });\n    }\n\n    // Bind with bounded retry. `launchctl kickstart -k` kills the old prod\n    // instance and relaunches immediately; even after the old process has\n    // exited cleanly, the kernel can hold :PORT in a transient teardown state\n    // for a beat, so the fresh instance's first bind can race into EADDRINUSE.\n    // Retrying with a short backoff absorbs that window instead of leaving the\n    // process listener-less (the old behavior: the listener-level 'error'\n    // handler logged EADDRINUSE once and gave up, failing the deploy\n    // healthcheck). On the final attempt we let the error propagate so a\n    // genuinely occupied port still surfaces loudly.\n    await this.bindWithRetry(port);\n\n    const warmUp = new Promise<void>((resolveWarm) => {\n      {\n        this.log.info(`Streamer server listening on port ${port}`, {\n          port,\n          event: \"server.listening\",\n        });\n        try {\n          this.cache = ConversationCache.open(\n            join(this.cacheDir, \"cache.db\"),\n            this.tailSize,\n            undefined,\n            {\n              filterAgentConversations: !this.includeAgents,\n              agentEntrypoints: this.agentEntrypoints,\n              onAgentFileDetected: (fp) => this.fileWatcher.unwatch(fp),\n            },\n          );\n          if (!this.includeAgents) {\n            const result = pruneAgentConversations(this.cache);\n            if (result.pruned > 0 || result.missing > 0) {\n              this.log.info(\n                `Agent conversation prune: scanned=${result.scanned} pruned=${result.pruned} missing=${result.missing}`,\n                { ...result, event: \"cache.prune_agents\" },\n              );\n            }\n          }\n          const db = this.cache.getDatabase();\n          this.projectsRepo = new ProjectsRepository(db);\n          this.conversationsRepo = new ConversationsRepository(this.cache);\n          this.sessionsRepo = new SessionsRepository(this.sessionStore);\n          this.cacheMetadataRepo = new CacheMetadataRepository(db);\n          // Watch the active profile dirs (or ~/.claude/projects as fallback) so\n          // new JSONL files created after startup are discovered and the scanner\n          // and cache are invalidated without a restart.\n          if (this.scanProfiles && this.scanProfiles.length > 0) {\n            for (const profile of this.scanProfiles) {\n              if (profile.enabled) {\n                this.fileWatcher.watchDirectory(join(profile.configDir, \"projects\"));\n              }\n            }\n          } else {\n            this.fileWatcher.watchDirectory(join(homedir(), \".claude\", \"projects\"));\n          }\n        } catch (err) {\n          const message = err instanceof Error ? err.message : String(err);\n          this.log.warn(`ConversationCache failed to open (running without cache): ${message}`, {\n            error: message,\n            event: \"cache.open_failed\",\n          });\n        }\n        // Use a dedicated scanner for warm-up, independent of this.scanner, so\n        // that onConversationChanged invalidations during the scan cannot cause\n        // getScanner() to restart indefinitely and leave the warm-up stuck.\n        const warmupScanner = new ConversationScanner();\n        this.allScanners.add(warmupScanner);\n        const warmupStatCache = this.buildStatCache(null);\n        // Throttle the per-file onProgress firings to ~one frame per whole\n        // percent (plus the final tick) so a large scan doesn't flood every\n        // WebSocket client with thousands of scan_progress messages.\n        const shouldEmitProgress = createScanProgressThrottle();\n        const scanOpts = {\n          ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\n          ...this.codexScanOpts(),\n          ...(warmupStatCache ? { statCache: warmupStatCache } : {}),\n        };\n        warmupScanner\n          .scan({\n            ...scanOpts,\n            onProgress: (scanned, total) => {\n              if (shouldEmitProgress(scanned, total)) {\n                this.wsHub.broadcast({ type: \"scan_progress\", scanned, total });\n              }\n            },\n          })\n          .then(async () => {\n            if (!this.cache) return;\n            const metas = [...warmupScanner.getMetadataCache().values()] as any[];\n            // upsertFromScannerMeta returns IDs of rows actually upserted\n            // (excluding agent JSONLs skipped when includeAgents=false).\n            // Warming tails for filtered-out IDs would hit the\n            // conversation_tail.conversation_id → conversation_meta(id) FK\n            // and abort the whole warm-up before pruneGhostFiles can run.\n            const upsertedIds = new Set(this.cache.upsertFromScannerMeta(metas));\n            const tailTargets: Array<{ id: string; filePath: string }> = [];\n            for (const m of metas) {\n              if (!m.filePath) continue;\n              const id =\n                m.sessionId ||\n                m.id\n                  ?.split(\"/\")\n                  .pop()\n                  ?.replace(/\\.jsonl$/, \"\") ||\n                m.id;\n              if (upsertedIds.has(id)) tailTargets.push({ id, filePath: m.filePath });\n            }\n            const BATCH = 50;\n            let tailFailures = 0;\n            for (let i = 0; i < tailTargets.length; i += BATCH) {\n              const batch = tailTargets.slice(i, i + BATCH);\n              for (const t of batch) {\n                try {\n                  this.cache.populateTailFromFile(t.id, t.filePath);\n                } catch (err) {\n                  // Benign race: the live ConversationWatcher runs during\n                  // warm-up, so an active session writing/deleting its JSONL\n                  // fires invalidateByFilePath() → invalidate(id), which deletes\n                  // the conversation_meta row we just upserted. The follow-up\n                  // tail insert then trips the conversation_tail → conversation_meta\n                  // FK. Skipping is correct — the row was invalidated and gets\n                  // re-upserted on the next scan, and pruneGhostFiles (below)\n                  // reconciles any file that was genuinely deleted. We must not\n                  // throw here or pruneGhostFiles never runs. Logged at info (not\n                  // debug) so the failing id+reason is visible by default.\n                  tailFailures += 1;\n                  this.log.info(\n                    `populateTailFromFile skipped for ${t.id}: ${\n                      err instanceof Error ? err.message : String(err)\n                    }`,\n                    { id: t.id, event: \"cache.warmup_tail_failed\" },\n                  );\n                }\n              }\n              await new Promise<void>((r) => setImmediate(r));\n            }\n            if (tailFailures > 0) {\n              this.log.warn(\n                `Warm-up: ${tailFailures}/${tailTargets.length} tail populates skipped (see info logs for ids)`,\n                {\n                  failures: tailFailures,\n                  total: tailTargets.length,\n                  event: \"cache.warmup_tail_failures\",\n                },\n              );\n            }\n            const pruned = this.cache.pruneGhostFiles();\n            this.log.info(`Startup ghost prune: removed ${pruned.length} stale cache rows`, {\n              count: pruned.length,\n              event: \"cache.prune_ghosts\",\n            });\n          })\n          .catch((err) => {\n            const message = err instanceof Error ? err.message : String(err);\n            this.log.warn(`Startup cache warm-up failed: ${message}`, {\n              error: message,\n              event: \"cache.warmup_failed\",\n            });\n          })\n          .finally(() => {\n            // Adopt the warm-up scan as the live scanner so the first real\n            // request reuses it instead of paying for a second full scan.\n            // Guard: only adopt if nothing else already owns the slot — a\n            // request-path getScanner() that built its own scanner during\n            // warm-up, or an onConversationChanged that nulled both fields.\n            // If invalidation fires after we adopt, the next request rescans\n            // (pre-existing fallback); the per-request refreshFile path\n            // reconciles single-file drift.\n            if (!this.scannerReady && !this.scanner) {\n              this.scanner = warmupScanner;\n              this.scannerReady = Promise.resolve();\n            }\n            this.cacheReady = true;\n            this.wsHub.broadcast({ type: \"cache_ready\" });\n            resolveWarm();\n          });\n      }\n    });\n    if (opts?.awaitReady) await warmUp;\n  }\n\n  // Bind the HTTP listener, retrying on a transient EADDRINUSE. See the call\n  // site in listen() for why the race exists (kickstart -k relaunch). Total\n  // worst case ≈ 6 × 500 ms = 3 s before the final attempt rethrows.\n  private async bindWithRetry(port: number, attempts = 6, delayMs = 500): Promise<void> {\n    this.binding = true;\n    try {\n      await this.bindWithRetryLoop(port, attempts, delayMs);\n    } finally {\n      this.binding = false;\n    }\n  }\n\n  private async bindWithRetryLoop(port: number, attempts: number, delayMs: number): Promise<void> {\n    for (let attempt = 1; attempt <= attempts; attempt++) {\n      try {\n        await new Promise<void>((resolve, reject) => {\n          const onError = (err: NodeJS.ErrnoException) => {\n            this.httpServer.removeListener(\"listening\", onListening);\n            reject(err);\n          };\n          const onListening = () => {\n            this.httpServer.removeListener(\"error\", onError);\n            resolve();\n          };\n          this.httpServer.once(\"error\", onError);\n          this.httpServer.once(\"listening\", onListening);\n          this.httpServer.listen(port);\n        });\n        return;\n      } catch (err) {\n        const e = err as NodeJS.ErrnoException;\n        if (e.code === \"EADDRINUSE\" && attempt === attempts) {\n          // Final attempt exhausted on a still-busy port: this is a genuine\n          // failure (not the self-healing kickstart race), so surface it once\n          // before rethrowing.\n          this.log.error(\n            `port ${port} still busy (EADDRINUSE) after ${attempts} attempts; giving up`,\n            { port, attempts, event: \"server.bind_failed\" },\n          );\n        }\n        if (e.code !== \"EADDRINUSE\" || attempt === attempts) throw err;\n        // Routine kickstart-relaunch race: log at debug (invisible by default)\n        // since bindWithRetry recovers on its own within the attempt budget.\n        this.log.debug?.(\n          `port ${port} busy (EADDRINUSE), retry ${attempt}/${attempts - 1} in ${delayMs}ms`,\n          { port, attempt, event: \"server.bind_retry\" },\n        );\n        await new Promise<void>((r) => setTimeout(r, delayMs));\n      }\n    }\n  }\n\n  async close(): Promise<void> {\n    for (const timer of this.ptyGraceTimers.values()) clearTimeout(timer);\n    this.ptyGraceTimers.clear();\n    this.markScannerStaleDebounced.cancel();\n    // Close all scanner SQLite connections before the cache so file handles are\n    // released on Windows (open handles block temp-dir deletion in tests).\n    for (const s of this.allScanners) s.close();\n    this.allScanners.clear();\n    this.scanner = null;\n    this.cache?.close();\n    this.ptyManager.dispose();\n    this.fileWatcher.dispose();\n    this.wsHub.dispose();\n    this.pairTokens.dispose();\n    if (this.dbPool) {\n      await this.dbPool.end();\n    }\n    // Force any sockets that survived wsHub.dispose() (e.g. a half-open\n    // connection mid-upgrade) to close, so httpServer.close()'s callback —\n    // which only fires once every connection drains — can't hang. Without\n    // this the old process keeps :PORT bound until launchd's SIGKILL, and the\n    // freshly-started instance hits EADDRINUSE. Guarded for Node < 18.2.\n    this.httpServer.closeAllConnections?.();\n    return new Promise((resolve) => {\n      // Belt-and-suspenders: never let process exit block forever on the\n      // listener close. The port is released the moment closeAllConnections()\n      // runs; the timeout only guards against an unforeseen lingering socket.\n      const timer = setTimeout(resolve, 2000);\n      this.httpServer.close(() => {\n        clearTimeout(timer);\n        resolve();\n      });\n    });\n  }\n\n  // ─── Request Router ────────────────────────────────────────────\n\n  private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n    const host = req.headers.host ?? \"localhost\";\n    const webReq = new Request(`http://${host}${req.url ?? \"/\"}`, {\n      method: req.method ?? \"GET\",\n      headers: req.headers as Record<string, string>,\n    });\n    const honoRes = await this.honoApp.fetch(webReq, { incoming: req, outgoing: res });\n    if (honoRes.status !== ALREADY_HANDLED) {\n      await writeHonoResponse(honoRes, res);\n    }\n  }\n\n  // ─── Handlers ──────────────────────────────────────────────────\n\n  private handlePairStart(res: ServerResponse): void {\n    const minted = this.pairTokens.mint();\n    json(res, 200, {\n      token: minted.token,\n      expiresAt: minted.expiresAt,\n      expiresInSeconds: minted.expiresInSeconds,\n      publicUrl: this.publicUrl,\n    });\n  }\n\n  private async handlePairExchange(req: IncomingMessage, res: ServerResponse): Promise<void> {\n    const ct = req.headers[\"content-type\"] ?? \"\";\n    if (!String(ct).toLowerCase().includes(\"application/json\")) {\n      json(res, 415, { error: \"Content-Type: application/json required\" });\n      return;\n    }\n\n    const ip = req.socket.remoteAddress ?? \"unknown\";\n    if (!this.checkExchangeRateLimit(ip)) {\n      json(res, 429, { error: \"Too many pair exchange attempts; try again in a minute\" });\n      return;\n    }\n\n    let body: any;\n    try {\n      body = await readBody(req);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Invalid body\";\n      json(res, 400, { error: message });\n      return;\n    }\n\n    const { token, clientPublicKey } = body ?? {};\n    if (typeof token !== \"string\" || typeof clientPublicKey !== \"string\") {\n      json(res, 400, { error: \"Missing token or clientPublicKey\" });\n      return;\n    }\n\n    const result = this.pairTokens.consume(token);\n    if (!result.ok) {\n      json(res, 401, { error: `Pair token ${result.reason}` });\n      return;\n    }\n\n    let sealed: ReturnType<typeof seal>;\n    try {\n      sealed = seal(this.apiKey, clientPublicKey);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Invalid clientPublicKey\";\n      json(res, 400, { error: message });\n      return;\n    }\n\n    const { hostname } = require(\"os\");\n    const ts = new Date().toISOString();\n    this.log.info(`[pair] token exchanged from ${ip} at ${ts}`, {\n      event: \"pair.token_exchanged\",\n      ip,\n      ts,\n    });\n\n    json(res, 200, {\n      ciphertext: sealed.ciphertext,\n      nonce: sealed.nonce,\n      ephemeralPublicKey: sealed.ephemeralPublicKey,\n      publicUrl: this.publicUrl,\n      machineName: hostname(),\n    });\n  }\n\n  private rotateApiKey(): { newKey: string; persisted: boolean } {\n    const oldKey = this.apiKey;\n    const newKey = generateApiKey();\n    // Only persist to server.yaml when the key came from there.\n    // If --api-key was passed on the CLI, the flag wins on restart and\n    // would silently revert to the old key — so skip the write and let\n    // the caller know via the response.\n    const persisted = this.apiKeySource === \"config\";\n    if (persisted) setApiKey(newKey);\n    this.apiKey = newKey;\n    this.log.info(\"API key rotated\", {\n      event: \"auth.api_key_rotated\",\n      oldKeyMasked: `${oldKey.slice(0, 6)}…`,\n      newKeyMasked: `${newKey.slice(0, 6)}…`,\n      persisted,\n    });\n    return { newKey, persisted };\n  }\n\n  private checkRateLimit(\n    map: Map<string, number[]>,\n    key: string,\n    limit: number,\n    windowMs: number,\n  ): boolean {\n    const now = Date.now();\n    const arr = (map.get(key) ?? []).filter((t) => now - t < windowMs);\n    if (arr.length >= limit) {\n      map.set(key, arr);\n      return false;\n    }\n    arr.push(now);\n    map.set(key, arr);\n    // TTL-evict the entry once the window expires so the map doesn't grow unbounded.\n    setTimeout(() => {\n      const remaining = (map.get(key) ?? []).filter((t) => Date.now() - t < windowMs);\n      if (remaining.length === 0) map.delete(key);\n      else map.set(key, remaining);\n    }, windowMs);\n    return true;\n  }\n\n  private checkExchangeRateLimit(ip: string): boolean {\n    return this.checkRateLimit(this.exchangeAttempts, ip, 5, 60_000);\n  }\n\n  private checkSessionStartRateLimit(ip: string): boolean {\n    // 10 new sessions per minute per client IP\n    return this.checkRateLimit(this.sessionStartAttempts, ip, 10, 60_000);\n  }\n\n  private checkSessionInputRateLimit(sessionId: string): boolean {\n    // 500 keystrokes per minute per session\n    return this.checkRateLimit(this.sessionInputAttempts, sessionId, 500, 60_000);\n  }\n\n  private async handleListConversations(url: URL, res: ServerResponse): Promise<void> {\n    const limit = intParam(url, \"limit\", 50);\n    const offset = intParam(url, \"offset\", 0);\n    const sort = (url.searchParams.get(\"sort\") ?? \"recent\") as SortOrder;\n    const project = url.searchParams.get(\"project\") ?? undefined;\n    const providerFilter = url.searchParams.get(\"provider\") ?? undefined;\n    const bustCache = url.searchParams.get(\"refresh\") === \"1\";\n\n    if (bustCache) {\n      this.cache?.invalidate();\n      this.scanner = null;\n      this.scannerReady = null;\n    }\n\n    if (this.cache && !bustCache) {\n      const { conversations, total } = this.cache.listConversations({\n        project,\n        provider: providerFilter,\n        limit,\n        offset,\n      });\n      const adapted = conversations.map((c) => ({\n        id: c.id,\n        title: deriveProjectChatTitle({\n          title: c.title,\n          projectName: c.projectName,\n          projectPath: c.projectPath,\n          id: c.id,\n        }),\n        sessionName: undefined as string | undefined,\n        filePath: c.filePath,\n        projectPath: c.projectPath,\n        branch: c.branch ?? undefined,\n        account: c.account ?? undefined,\n        preview: c.preview ?? undefined,\n        messageCount: c.messageCount,\n        lastActivity: c.lastActivity,\n        firstMessage: c.firstMessage ? (JSON.parse(c.firstMessage) as unknown) : undefined,\n        lastMessage: c.lastMessage ? (JSON.parse(c.lastMessage) as unknown) : undefined,\n        model: c.model ?? undefined,\n        provider: c.provider ?? CLAUDE_CODE_PROVIDER,\n      }));\n      json(res, 200, { conversations: adapted, hasMore: offset + limit < total, offset, total });\n      return;\n    }\n\n    const scanner = await this.getScanner();\n    let metas = [...scanner.getMetadataCache().values()];\n    metas = applyIncludeFilter(metas, \"conversations\");\n    if (project) metas = applyProjectFilter(metas, project);\n    if (providerFilter)\n      metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);\n    metas = applySort(metas, sort);\n    const total = metas.length;\n    const page = applyPagination(metas, limit, offset);\n\n    const adapted = (page.items as ConversationMeta[]).map((c) => {\n      const id =\n        c.sessionId ||\n        c.id\n          .split(\"/\")\n          .pop()\n          ?.replace(/\\.jsonl$/, \"\") ||\n        c.id;\n      return {\n        id,\n        title: deriveProjectChatTitle({\n          title: c.sessionName,\n          projectName: c.projectName,\n          projectPath: c.projectPath,\n          id,\n        }),\n        sessionName: c.sessionName || undefined,\n        filePath: c.filePath,\n        projectPath: c.projectPath,\n        branch: c.gitBranch ?? undefined,\n        account: c.account,\n        preview: c.preview || undefined,\n        messageCount: c.messageCount,\n        lastActivity: c.timestamp,\n        firstMessage: c.firstMessage ?? undefined,\n        lastMessage: c.lastMessage ?? undefined,\n        model: c.model ?? undefined,\n        provider: (c as any).provider ?? CLAUDE_CODE_PROVIDER,\n      };\n    });\n    json(res, 200, { conversations: adapted, hasMore: offset + limit < total, offset, total });\n\n    if (this.cache && bustCache) {\n      try {\n        this.cache.upsertFromScannerMeta([...scanner.getMetadataCache().values()] as any[]);\n      } catch {\n        // Best-effort; response already sent\n      }\n    }\n  }\n\n  private async handleConversationsCount(url: URL, res: ServerResponse): Promise<void> {\n    const project = url.searchParams.get(\"project\") ?? undefined;\n    const providerFilter = url.searchParams.get(\"provider\") ?? undefined;\n    const bustCache = url.searchParams.get(\"refresh\") === \"1\";\n\n    // refresh=1 historically forced a full synchronous scan() to recount from\n    // disk. On a cold/empty index that scan walks every JSONL and blocks ~16s,\n    // tripping mobile's request timeout into a false \"unreachable\". Mirror the\n    // detail path's skipStaleRescan stance: serve the indexed/cached total\n    // immediately and reconcile from disk in the BACKGROUND so the count stays\n    // fast regardless of refresh.\n    if (this.cache) {\n      const { total } = this.cache.listConversations({\n        project,\n        provider: providerFilter,\n        limit: 0,\n        offset: 0,\n      });\n      json(res, 200, { total });\n      if (bustCache) this.refreshCountInBackground();\n      return;\n    }\n\n    const scanner = await this.getScanner(true);\n    let metas = [...scanner.getMetadataCache().values()];\n    metas = applyIncludeFilter(metas, \"conversations\");\n    if (project) metas = applyProjectFilter(metas, project);\n    if (providerFilter)\n      metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);\n    json(res, 200, { total: metas.length });\n  }\n\n  // Fire-and-forget full rescan that reconciles the SQLite cache from disk so a\n  // later count reflects new/removed conversations. Never awaited by the request\n  // path — refresh=1 returns the cached total synchronously and this catches up.\n  private refreshCountInBackground(): void {\n    void (async () => {\n      try {\n        const scanner = await this.getFreshScanner();\n        if (this.cache) {\n          this.cache.upsertFromScannerMeta([...scanner.getMetadataCache().values()] as any[]);\n        }\n      } catch (err) {\n        this.log.warn(\n          `Background count refresh failed: ${err instanceof Error ? err.message : String(err)}`,\n          { event: \"count.refresh_failed\" },\n        );\n      }\n    })();\n  }\n\n  private handleSessionsCount(res: ServerResponse): void {\n    json(res, 200, { total: this.sessionStore.list(this.ptyAttachedIds()).length });\n  }\n\n  private handleGetRecentSessions(url: URL, res: ServerResponse): void {\n    const limit = intParam(url, \"limit\", 20);\n    if (!this.cache) {\n      json(res, 200, { sessions: [], total: 0 });\n      return;\n    }\n    const { conversations } = this.cache.listConversations({ limit, offset: 0 });\n    // Items here are conversation cache rows, not live sessions in SessionStore.\n    // The `type` discriminator lets mobile route taps through /api/sessions/resume\n    // (which spawns a fresh PTY) instead of GET /api/sessions/:id (which 404s).\n    const sessions = conversations.map((c) => ({\n      type: \"conversation\" as const,\n      id: c.id,\n      status: \"idle\" as const,\n      ptyAttached: false,\n      projectId: c.projectId ?? undefined,\n      projectPath: c.projectPath ?? \"\",\n      projectName: c.projectName ?? \"\",\n      branch: c.branch ?? undefined,\n      lastOutput: \"\",\n      elapsedMs: 0,\n      promptCount: c.messageCount,\n      startedAt: c.lastActivity,\n      lastActivityAt: c.lastActivity,\n    }));\n    json(res, 200, { sessions, total: sessions.length });\n  }\n\n  private handleGetPopularProjects(url: URL, res: ServerResponse): void {\n    const limit = intParam(url, \"limit\", 20);\n    if (!this.cache) {\n      json(res, 200, { projects: [], total: 0 });\n      return;\n    }\n    const projects = this.cache.getPopularProjects(limit);\n    json(res, 200, { projects, total: projects.length });\n  }\n\n  private buildStatCache(\n    previousScanner: ConversationScanner | null,\n  ): Map<string, { stat: FileStatEntry; meta: ConversationMeta }> | undefined {\n    if (!this.cache) return undefined;\n    const dbStats = this.cache.getFileStats();\n    if (dbStats.size === 0) return undefined;\n    const metaByPath = new Map<string, ConversationMeta>();\n    if (previousScanner) {\n      for (const meta of previousScanner.getMetadataCache().values()) {\n        if (meta.filePath) metaByPath.set(meta.filePath, meta);\n      }\n    }\n    const statCache = new Map<string, { stat: FileStatEntry; meta: ConversationMeta }>();\n    for (const [filePath, stat] of dbStats) {\n      const meta = metaByPath.get(filePath);\n      if (meta) statCache.set(filePath, { stat, meta });\n    }\n    return statCache.size > 0 ? statCache : undefined;\n  }\n\n  // Returns the provider + codexRoots fragment to spread into every scan()/search() call.\n  // codexRoots=[] disables codex scanning (safe no-op per scanner contract).\n  private codexScanOpts() {\n    return {\n      providers: [CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER],\n      codexRoots: this.codexRoots,\n    };\n  }\n\n  // skipStaleRescan: when an indexed scanner already exists, return it directly\n  // even if scannerStale is set, leaving the flag untouched so the next\n  // list-level call still rescans. The single-conversation detail path passes\n  // this — its per-file refreshFile (in findConversationByUuid) already\n  // reconciles the one conversation being requested, so paying a full-tree\n  // rescan just because some OTHER file changed is the stall this avoids.\n  private async getScanner(skipStaleRescan = false): Promise<ConversationScanner> {\n    if (this.scannerReady) {\n      await this.scannerReady;\n      // onConversationChanged may have nulled this.scanner while we awaited —\n      // if so, fall through and create a fresh one.\n      if (this.scanner) {\n        if (skipStaleRescan) return this.scanner;\n        // If file events arrived while the scan was running, do one rescan now\n        // rather than serving a stale result. The stale flag is cleared first so\n        // any events during the rescan trigger another pass on the next call.\n        if (this.scannerStale) {\n          this.scannerStale = false;\n          this.scanner = null;\n          this.scannerReady = null;\n          return this.getScanner();\n        }\n        return this.scanner;\n      }\n    }\n    this.scannerStale = false;\n    const statCache = this.buildStatCache(this.scanner);\n    this.scanner = new ConversationScanner();\n    this.allScanners.add(this.scanner);\n    this.scannerReady = this.scanner.scan({\n      ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\n      ...this.codexScanOpts(),\n      ...(statCache ? { statCache } : {}),\n    });\n    await this.scannerReady;\n    // Capture before returning — onConversationChanged could null this.scanner\n    // in the microtask between the await and the return.\n    const scanner = this.scanner;\n    if (!scanner) return this.getScanner();\n    return scanner;\n  }\n\n  private async getFreshScanner(): Promise<ConversationScanner> {\n    this.scanner = null;\n    this.scannerReady = null;\n    return this.getScanner();\n  }\n\n  private findJsonlPath(uuid: string): string | null {\n    const projectsDir = join(homedir(), \".claude\", \"projects\");\n    if (!existsSync(projectsDir)) return null;\n    const filename = `${uuid}.jsonl`;\n    for (const dir of readdirSync(projectsDir)) {\n      const fp = join(projectsDir, dir, filename);\n      if (existsSync(fp)) return fp;\n      const projectDir = join(projectsDir, dir);\n      try {\n        for (const sub of readdirSync(projectDir)) {\n          const subagentPath = join(projectDir, sub, \"subagents\", filename);\n          if (existsSync(subagentPath)) return subagentPath;\n        }\n      } catch {\n        // Not a directory or no access\n      }\n    }\n    return null;\n  }\n\n  private async readCwdFromJsonl(filePath: string): Promise<string | null> {\n    return new Promise((resolve) => {\n      const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });\n      let found = false;\n      rl.on(\"line\", (line) => {\n        if (found) return;\n        try {\n          const entry = JSON.parse(line);\n          if (entry.cwd) {\n            found = true;\n            rl.close();\n            resolve(entry.cwd as string);\n          }\n        } catch {\n          // skip malformed lines\n        }\n      });\n      rl.on(\"close\", () => {\n        if (!found) resolve(null);\n      });\n      rl.on(\"error\", () => resolve(null));\n    });\n  }\n\n  private async findConversationByUuid(uuid: string): Promise<Conversation | null> {\n    // Cold-start fast path: until the warm-up scan has populated this.scanner\n    // (this.scannerReady is null), do NOT trigger a full scan to answer a\n    // single-conversation request — that scan walks every JSONL on disk and is\n    // the 20s+ stall that makes mobile abort. Resolve the file directly\n    // (findJsonlPath is an O(project-dirs) walk) and parse just that one file.\n    // The warm-up scan keeps running in the background; once it adopts the\n    // scanner, subsequent requests use the indexed hot path below.\n    if (!this.scannerReady && !this.scanProfiles) {\n      const filePath = this.findJsonlPath(uuid);\n      if (filePath) {\n        const account = this.cache?.getMetaById(uuid)?.account ?? undefined;\n        const coldScanner = this.scanner ?? new ConversationScanner();\n        const page = await coldScanner.parseSingleFilePage(filePath, account, {\n          limit: Number.MAX_SAFE_INTEGER,\n        });\n        if (page) return page.conversation;\n      }\n      // No JSONL on disk (or unparseable). Return null WITHOUT triggering a\n      // full scan — confirming not-found is not worth the 20s stall. The\n      // caller's cache-tail fallback / 404 self-heal handles it.\n      return null;\n    }\n\n    // Use the existing indexed scanner without honoring the global scannerStale\n    // full-rescan: the per-file refreshFile below reconciles the one\n    // conversation we care about, so a sibling file changing must not stall this\n    // single-conversation request behind a full-tree rescan.\n    const scanner = await this.getScanner(true);\n    const fromIndex = await scanner.getConversation(uuid);\n    if (fromIndex) {\n      // The scanner memoizes both its metadata index and parsed conversations\n      // for the server's lifetime. A conversation that grows after the initial\n      // scan (the chokidar watcher keeps the SQLite cache fresh, but never the\n      // scanner) keeps serving the startup snapshot here — so the detail/info\n      // view shows a stale message count + last activity that disagrees with\n      // the list view and with what --resume actually replays. If the JSONL on\n      // disk is newer than the snapshot, refresh just that one file's indexes\n      // (refreshFile evicts the stale parse and re-parses on the next read)\n      // rather than dropping and rebuilding the entire scanner.\n      if (fromIndex.filePath && this.isConversationSnapshotStale(fromIndex)) {\n        const refreshedMeta = await scanner.refreshFile(fromIndex.filePath);\n        // refreshFile returns null and drops the entry when the file no longer\n        // parses (deleted/emptied). In that case return null so the caller's\n        // ghost-prune + cache-tail fallback runs instead of serving the stale\n        // snapshot we already know is wrong.\n        if (!refreshedMeta) return null;\n        return (await scanner.getConversation(uuid)) ?? fromIndex;\n      }\n      return fromIndex;\n    }\n\n    if (this.scanProfiles) return null;\n\n    const filePath = this.findJsonlPath(uuid);\n    if (!filePath) return null;\n    this.scanner = null;\n    this.scannerReady = null;\n    const freshScanner = await this.getScanner();\n    return freshScanner.getConversation(uuid);\n  }\n\n  // True when the JSONL on disk is meaningfully newer than the scanned\n  // snapshot's last-activity timestamp — i.e. the file grew after the scan.\n  private isConversationSnapshotStale(conv: Conversation): boolean {\n    if (!conv.filePath) return false;\n    let mtimeMs: number | null = null;\n    try {\n      mtimeMs = statSync(conv.filePath).mtimeMs;\n    } catch {\n      // Stat failed (file moved/deleted mid-flight) — don't force a re-scan.\n      return false;\n    }\n    return isScannedSnapshotStale(conv.timestamp, mtimeMs);\n  }\n\n  private async handleGetConversation(\n    id: string,\n    url: URL,\n    res: ServerResponse,\n    ifNoneMatch?: string,\n  ): Promise<void> {\n    // Try the scanner first (has full content including tool_use blocks).\n    // Fall back to the cache tail only when the scanner can't find the file —\n    // e.g. a conversation that existed in a previous run but whose JSONL was deleted.\n    const conversation = await this.findConversationByUuid(id);\n\n    if (!conversation && this.cache) {\n      // Only `before_index` indicates the client is paginating backward (asking\n      // for messages older than a cursor) — `msg_limit` is just page size and is\n      // sent on the first page too. The tail fallback should serve any first-page\n      // request when the JSONL is missing, regardless of msg_limit.\n      const isFirstLoad = !url.searchParams.has(\"before_index\");\n      if (isFirstLoad) {\n        const tail = this.cache.getConversationTail(id);\n        if (tail && tail.messages.length > 0) {\n          const cachedMeta = this.cache.getMetaById(id);\n          const cachedProvider = cachedMeta?.provider ?? CLAUDE_CODE_PROVIDER;\n          const availability = classifyResumability(cachedMeta?.projectPath);\n          const messagesPayload = tail.messages.map((m, idx) => ({\n            message_index: idx,\n            role: m.role,\n            timestamp: m.timestamp,\n            text: m.text,\n            tool_calls: [] as unknown[],\n            content: (m.content ?? []).filter((b: any) => b.type !== \"text\"),\n          }));\n          json(res, 200, {\n            meta: {\n              id,\n              profile_id: cachedMeta?.account ?? undefined,\n              project_name: cachedMeta?.projectName ?? undefined,\n              project_path: cachedMeta?.projectPath ?? undefined,\n              file_path: cachedMeta?.filePath ?? undefined,\n              last_updated_at: cachedMeta?.lastActivity ?? undefined,\n              message_count: cachedMeta?.messageCount ?? undefined,\n              provider: cachedProvider,\n              resumable: isProviderResumable(cachedProvider, availability.resumable),\n              ...(availability.unavailable_reason && {\n                unavailable_reason: availability.unavailable_reason,\n              }),\n            },\n            messages: messagesPayload,\n            message_pagination: {\n              total: tail.tailSize,\n              before_index: tail.tailSize,\n              from_index: 0,\n              has_more_older: false,\n              next_before_index: null,\n            },\n          });\n          return;\n        }\n      }\n    }\n\n    if (!conversation) {\n      // Self-heal: the row is a ghost (JSONL gone, no usable tail). Drop it so\n      // the next list refresh doesn't keep offering this id to clients.\n      this.cache?.invalidate(id);\n      json(res, 404, { error: \"Conversation not found\", code: \"not_found\" });\n      return;\n    }\n\n    // Compute the conditional-fetch validator from the RESOLVED conversation —\n    // findConversationByUuid has already done its staleness refresh above, so\n    // these fields reflect the same state the body would. Computing it from a\n    // pre-refresh snapshot would let us hand out a 304 against stale data.\n    const etagSource = conversation as unknown as {\n      filePath: string;\n      messageCount: number;\n      timestamp: string;\n    };\n    const etag = computeConversationEtag({\n      filePath: etagSource.filePath,\n      messageCount: etagSource.messageCount,\n      timestamp: etagSource.timestamp,\n    });\n\n    // Only the first page (\"is the conversation as a whole still current?\")\n    // participates in the freshness check. Older pages are immutable history —\n    // a back-page request (before_index set) always returns its 200 body, never\n    // a 304, even when the client echoes a matching If-None-Match.\n    const isFirstPage = !url.searchParams.has(\"before_index\");\n    if (isFirstPage && ifNoneMatch && ifNoneMatch === etag) {\n      // This is a direct-`ServerResponse` write, so the Hono CORS middleware's\n      // headers don't reach it — set the expose header here so a cross-origin\n      // client can read the validator off the 304 too.\n      res.writeHead(304, { ETag: etag, \"Access-Control-Expose-Headers\": \"ETag\" });\n      res.end();\n      return;\n    }\n\n    const filtered = conversation.messages;\n    const total = filtered.length;\n\n    const usePaging = url.searchParams.has(\"msg_limit\") || url.searchParams.has(\"before_index\");\n\n    let slice = filtered;\n    let fromIdx = 0;\n    let messagePagination: Record<string, unknown> | undefined;\n\n    if (usePaging) {\n      const limit = Math.min(Math.max(intParam(url, \"msg_limit\", 80), 1), 500);\n      let beforeIndex = total;\n      if (url.searchParams.has(\"before_index\")) {\n        beforeIndex = intParam(url, \"before_index\", total);\n        beforeIndex = Math.min(Math.max(beforeIndex, 0), total);\n      }\n      // Only consult the scanner's paged reader when it's already warm. On the\n      // cold path `conversation` came from the single-file fast path and holds\n      // every message in memory, so slice it locally — calling getScanner()\n      // here would trigger the full scan the fast path exists to avoid. Pass\n      // skipStaleRescan: this is the same single-conversation detail path, whose\n      // refreshFile already reconciled the one file we page here, so a sibling\n      // file's stale flag must not stall this read behind a full-tree rescan.\n      const pagedScanner = this.scannerReady\n        ? ((await this.getScanner(true)) as unknown as {\n            getConversationPage?: (\n              id: string,\n              options: { beforeIndex: number; limit: number },\n            ) => Promise<{ messages: typeof filtered; total: number; fromIndex: number } | null>;\n          })\n        : null;\n      const page =\n        pagedScanner && typeof pagedScanner.getConversationPage === \"function\"\n          ? await pagedScanner.getConversationPage(id, { beforeIndex, limit })\n          : null;\n      const start = page?.fromIndex ?? Math.max(0, beforeIndex - limit);\n      slice = page?.messages ?? filtered.slice(start, beforeIndex);\n      fromIdx = start;\n      messagePagination = {\n        total: page?.total ?? total,\n        before_index: beforeIndex,\n        from_index: start,\n        has_more_older: start > 0,\n        next_before_index: start > 0 ? start : null,\n      };\n    }\n\n    const messagesPayload = slice.map((m: any, localIdx: number) => {\n      const content: unknown[] = [];\n      if (m.isThinking) {\n        content.push({\n          type: \"thinking\",\n          thinking: m.thinkingContent ?? \"\",\n          signature: m.thinkingSignature,\n        });\n      }\n      for (const b of m.metadata?.toolUseBlocks ?? []) {\n        content.push({ type: \"tool_use\", id: b.id, name: b.name, input: b.input });\n      }\n      for (const r of m.metadata?.toolResults ?? []) {\n        content.push({\n          type: \"tool_result\",\n          tool_use_id: r.toolUseId,\n          content: JSON.stringify(r.content),\n          is_error: r.isError ?? false,\n        });\n      }\n      return {\n        uuid: m.uuid ?? null,\n        message_index: fromIdx + localIdx,\n        role: m.role,\n        timestamp: m.timestamp,\n        text: m.text,\n        tool_calls: m.metadata?.toolUses ?? [],\n        has_images: m.hasImages ?? false,\n        parent_uuid: m.parentUuid ?? null,\n        permission_mode: m.permissionMode ?? null,\n        is_sidechain: m.isSidechain ?? false,\n        is_tool_result: m.isToolResult ?? false,\n        attachment: m.attachment ?? null,\n        content,\n      };\n    });\n\n    const conv = conversation as any;\n    const cachedConvMeta = this.cache?.getMetaById(id);\n    const convProvider: typeof CLAUDE_CODE_PROVIDER | typeof CODEX_CLI_PROVIDER =\n      conv.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER;\n    const availability = classifyResumability(conv.projectPath);\n    const body: Record<string, unknown> = {\n      meta: {\n        id,\n        profile_id: conv.account,\n        project_name: conv.projectName,\n        project_path: conv.projectPath,\n        file_path: conv.filePath,\n        last_updated_at: conv.timestamp,\n        message_count: conv.messageCount,\n        last_prompt: conv.lastPrompt ?? undefined,\n        provider: convProvider,\n        resumable: isProviderResumable(convProvider, availability.resumable),\n        ...(availability.unavailable_reason && {\n          unavailable_reason: availability.unavailable_reason,\n        }),\n      },\n      messages: messagesPayload,\n    };\n    if (messagePagination) body.message_pagination = messagePagination;\n    if (conv.turnDurations?.length) {\n      body.turn_durations = conv.turnDurations.map((d: any) => ({\n        duration_ms: d.durationMs,\n        message_count: d.messageCount,\n        uuid: d.uuid,\n      }));\n    }\n    // Always expose the ETag on the 200 so the client can store it and send it\n    // back as If-None-Match next time. Old clients ignore the header. This is a\n    // direct-`ServerResponse` write that bypasses the Hono CORS middleware, so\n    // the expose header is set here too — without it a cross-origin client\n    // can't read ETag.\n    res.writeHead(200, {\n      \"Content-Type\": \"application/json\",\n      ETag: etag,\n      \"Access-Control-Expose-Headers\": \"ETag\",\n    });\n    res.end(JSON.stringify(body));\n  }\n\n  private async handleSearch(url: URL, res: ServerResponse): Promise<void> {\n    const q = url.searchParams.get(\"q\") ?? \"\";\n    if (!q) {\n      json(res, 400, { error: \"Missing query parameter: q\" });\n      return;\n    }\n\n    const limit = intParam(url, \"limit\", 50);\n    const scanner = await this.getScanner();\n    const results = await search(\n      q,\n      {\n        limit,\n        include: \"conversations\",\n        ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\n        ...this.codexScanOpts(),\n      },\n      scanner,\n    );\n    const adapted = results.map((r: any) => ({\n      id:\n        r.meta.id\n          .split(\"/\")\n          .pop()\n          ?.replace(/\\.jsonl$/, \"\") || r.meta.id,\n      title: r.meta.projectName,\n      sessionName: r.meta.sessionName || undefined,\n      filePath: r.meta.filePath,\n      projectPath: r.meta.projectPath,\n      branch: r.meta.gitBranch ?? undefined,\n      account: r.meta.account,\n      preview: r.meta.preview || undefined,\n      messageCount: r.meta.messageCount,\n      lastActivity: r.meta.timestamp,\n      firstMessage: r.meta.firstMessage ?? undefined,\n      lastMessage: r.meta.lastMessage ?? undefined,\n      provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER,\n    }));\n    json(res, 200, {\n      conversations: adapted,\n      hasMore: false,\n      offset: 0,\n      total: adapted.length,\n    });\n  }\n\n  private async handleListSessions(url: URL, res: ServerResponse): Promise<void> {\n    const DISCOVERY_TTL_MS = 15_000;\n    const now = Date.now();\n\n    if (!this.discoveryCache || now - this.discoveryCache.fetchedAt >= DISCOVERY_TTL_MS) {\n      try {\n        const discovered = await discoverClaudeProcesses();\n        this.sessionStore.setDiscovered(discovered);\n        this.discoveryCache = { entries: discovered, fetchedAt: now };\n      } catch {\n        // Discovery is best-effort\n      }\n    }\n\n    // Backwards compat: a bare GET /api/sessions returns the legacy plain\n    // array. Any pagination param switches to the new envelope.\n    const hasPaginationParams =\n      url.searchParams.has(\"limit\") ||\n      url.searchParams.has(\"cursor\") ||\n      url.searchParams.has(\"sortBy\") ||\n      url.searchParams.has(\"order\") ||\n      url.searchParams.has(\"status\");\n\n    if (!hasPaginationParams) {\n      json(res, 200, this.sessionStore.list(this.ptyAttachedIds()));\n      return;\n    }\n\n    const parsed = parseSessionListQuery(url);\n    if (\"error\" in parsed) {\n      json(res, 400, { error: parsed.error });\n      return;\n    }\n\n    try {\n      const page = this.sessionStore.paginate(this.ptyAttachedIds(), parsed.query);\n      json(res, 200, page);\n    } catch (err) {\n      if (err instanceof Error && err.message === \"INVALID_CURSOR\") {\n        json(res, 400, { error: \"Invalid cursor\" });\n        return;\n      }\n      throw err;\n    }\n  }\n\n  private handleGetSession(sessionId: string, res: ServerResponse): void {\n    const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n    if (session) {\n      if (!existsSync(session.projectPath)) {\n        session.failureReason = `Project directory not found: ${session.projectPath}`;\n      }\n      json(res, 200, session);\n      return;\n    }\n    // Fall back to the conversation cache: older mobile builds tap recents\n    // entries via GET /api/sessions/:id even though those IDs are conversation\n    // UUIDs, not live sessions. Returning a resumable shape (status=on_hold)\n    // lets the mobile open flow proceed to /api/sessions/resume.\n    const conversation = this.cache?.getMetaById(sessionId);\n    if (conversation) {\n      json(res, 200, conversationToResumableSession(conversation));\n      return;\n    }\n    json(res, 404, { error: \"Session not found\" });\n  }\n\n  private async handleResume(req: IncomingMessage, res: ServerResponse): Promise<void> {\n    this.discoveryCache = null;\n    const body = await readBody(req);\n    // Accept both sessionId (new) and conversationId (legacy alias)\n    const sessionId: string | undefined = body.sessionId ?? body.conversationId;\n\n    if (!sessionId) {\n      json(res, 400, { error: \"Missing sessionId\" });\n      return;\n    }\n\n    // If a PTY is already running for this session, return it immediately\n    if (this.ptyManager.hasSession(sessionId)) {\n      const resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n      if (resp) {\n        json(res, 200, resp);\n        return;\n      }\n    }\n\n    // Authoritative cwd comes from the JSONL itself — the file Claude looks\n    // up by filename when processing --resume. The scanner index can return a\n    // stale or wrong path (e.g. …/tb-mobile/android vs …/tb-mobile), so we\n    // read the first cwd field directly, mirroring tb-scanner/src/parser.ts.\n    const jsonlPath = this.findJsonlPath(sessionId);\n    const jsonlCwd = jsonlPath ? await this.readCwdFromJsonl(jsonlPath) : null;\n\n    const conv = await this.findConversationByUuid(sessionId);\n    const projectPath: string = jsonlCwd ?? (conv as any)?.projectPath;\n    if (!projectPath) {\n      if (!conv && !jsonlPath) {\n        json(res, 404, { error: \"Conversation not found\" });\n        return;\n      }\n      json(res, 400, { error: \"Could not determine project path\" });\n      return;\n    }\n\n    const session = await this.ptyManager.start(sessionId, {\n      projectPath,\n      projectName: body.projectName,\n      branch: body.branch,\n    });\n\n    this.sessionStore.addManaged(session);\n\n    // Watch the conversation's JSONL file for structured events\n    void this.watchConversationFile(sessionId);\n\n    const resp = this.sessionStore.get(session.id, this.ptyAttachedIds());\n    this.broadcastOrUnicastSessionList(req);\n\n    json(res, 201, resp ?? session);\n\n    // Enrich session metadata and update DB in background (fire-and-forget).\n    // The conversation history is already in the JSONL; DB writes are\n    // bookkeeping that can happen asynchronously without blocking the response.\n    this.enrichResumedSessionAsync(sessionId, projectPath, conv);\n  }\n\n  private enrichResumedSessionAsync(sessionId: string, projectPath: string, conv: any): void {\n    try {\n      const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n      if (!session) return;\n\n      if (conv) {\n        session.sessionName = conv.sessionName ?? undefined;\n        session.messageCount = conv.messageCount ?? 0;\n        session.account = conv.account ?? undefined;\n        session.filePath = conv.filePath ?? undefined;\n      }\n\n      if (!this.cache || !this.projectsRepo || !this.conversationsRepo) return;\n\n      // Single SQLite read covers model, preview, timestamps, and projectId —\n      // no scanner round-trip needed; these fields are already cached.\n      const cached = this.cache.getMetaById(sessionId);\n      if (cached) {\n        session.model = cached.model ?? undefined;\n        session.preview = cached.preview ?? undefined;\n        const first = cached.firstMessage ? JSON.parse(cached.firstMessage as string) : null;\n        const last = cached.lastMessage ? JSON.parse(cached.lastMessage as string) : null;\n        session.firstMessageText = first?.text ?? undefined;\n        session.firstMessageAt = first?.timestamp\n          ? new Date(first.timestamp).toISOString()\n          : undefined;\n        session.lastMessageText = last?.text ?? undefined;\n        session.lastMessageAt = last?.timestamp\n          ? new Date(last.timestamp).toISOString()\n          : undefined;\n      }\n\n      let resolvedProjectId: string | null = cached?.projectId ?? null;\n      if (!resolvedProjectId) {\n        const project = this.projectsRepo.upsertProjectByPath(projectPath);\n        resolvedProjectId = project.id;\n        this.conversationsRepo.updateConversationProjectId({\n          conversationId: sessionId,\n          projectId: project.id,\n        });\n      }\n      if (resolvedProjectId) {\n        session.projectId = resolvedProjectId;\n        session.resumedFromConversationId = sessionId;\n      }\n    } catch (err) {\n      // ponytail: log but don't crash; session is already live and usable\n      console.error(`[enrichResumedSessionAsync] ${sessionId}:`, err);\n    }\n  }\n\n  private async handleSendInput(\n    sessionId: string,\n    req: IncomingMessage,\n    res: ServerResponse,\n  ): Promise<void> {\n    if (!this.checkSessionInputRateLimit(sessionId)) {\n      json(res, 429, { error: \"Too many input requests for this session. Please slow down.\" });\n      return;\n    }\n    if (this.agentConfig.enabled) {\n      const body = await readBody(req);\n      const cache = this.cache;\n      if (!cache) {\n        json(res, 503, {\n          error: \"Conversation cache is not available\",\n          code: \"INTERNAL_ERROR\",\n        });\n        return;\n      }\n      const result = await handleSendAgentInput(sessionId, body, {\n        sessionStore: this.sessionStore,\n        cache,\n        // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true\n        agentClient: this.agentClient!,\n        agentConfig: this.agentConfig,\n      });\n      json(res, result.status, result.body);\n      return;\n    }\n    const body = await readBody(req);\n    const { input, keys } = body;\n\n    if (typeof keys === \"string\") {\n      // Raw key bytes (e.g. arrow navigation for interactive prompts).\n      // These bypass bracketed-paste wrapping — caller is responsible for\n      // sending well-formed escape sequences.\n      try {\n        this.ptyManager.sendKeys(sessionId, keys);\n        const updated = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n        if (updated) {\n          this.wsHub.broadcast({ type: \"session_update\", session: updated });\n        }\n        json(res, 200, { ok: true });\n      } catch (err) {\n        const message = err instanceof Error ? err.message : \"Failed to send keys\";\n        json(res, 400, { error: message });\n      }\n      return;\n    }\n\n    if (typeof input !== \"string\") {\n      json(res, 400, { error: \"Missing input field\" });\n      return;\n    }\n\n    try {\n      const promptCount = this.ptyManager.sendInput(sessionId, input);\n      this.sessionStore.updateManaged(sessionId, { promptCount });\n      const updated = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n      if (updated) {\n        this.wsHub.broadcast({ type: \"session_update\", session: updated });\n      }\n      // Index the user's new message immediately so it's searchable right away.\n      const filePath = this.sessionFileMap.get(sessionId);\n      if (filePath) {\n        this.getScanner()\n          .then((scanner) => scanner.refreshFile(filePath))\n          .then((meta) => {\n            this.log.info(\"scanner.refreshFile: ok\", {\n              event: \"scanner.refresh\",\n              sessionId,\n              filePath,\n              trigger: \"sendInput\",\n              messageCount: meta?.messageCount,\n            });\n          })\n          .catch((err) => {\n            this.log.warn(\"scanner.refreshFile: failed\", {\n              event: \"scanner.refresh_failed\",\n              sessionId,\n              filePath,\n              trigger: \"sendInput\",\n              err,\n            });\n          });\n      }\n      json(res, 200, { ok: true });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Failed to send input\";\n      json(res, 400, { error: message });\n    }\n  }\n\n  private cancelPendingQuestion(sessionId: string): void {\n    const pq = this.pendingQuestions.get(sessionId);\n    if (!pq) return;\n    this.pendingQuestions.delete(sessionId);\n    this.pendingQuestionKey.delete(sessionId);\n    this.wsHub.broadcast({ type: \"question_cancelled\", sessionId, toolUseId: pq.toolUseId });\n  }\n\n  // Live AskUserQuestion detected from the rendered screen (ahead of JSONL).\n  // Broadcasts the `question` event immediately and records the content key so\n  // the later JSONL flush of the same question is de-duped. We synthesize a\n  // screen-scoped toolUseId; the JSONL path overwrites pendingQuestions with the\n  // real toolUseId when it lands, so answering works once JSONL catches up.\n  private handleLiveQuestion(sessionId: string, questions: AskQuestion[]): void {\n    const key = questionContentKey(questions);\n    if (this.pendingQuestionKey.get(sessionId) === key) return; // already shown\n    const toolUseId = `screen:${sessionId}:${key.length}`;\n    this.pendingQuestions.set(sessionId, { toolUseId, questions });\n    this.pendingQuestionKey.set(sessionId, key);\n    this.wsHub.broadcast({ type: \"question\", sessionId, toolUseId, questions });\n  }\n\n  // Permission gate opened/closed (OSC 777 + scraped options). Broadcasts the\n  // additive `permission` / `permission_cancelled` events. Mobile answers by\n  // sending the chosen option index via /input { keys } (e.g. \"2\\r\").\n  private handlePermissionChange(\n    sessionId: string,\n    gate: { prompt?: string; options: PermissionOption[]; cursor?: number } | null,\n  ): void {\n    if (gate === null) {\n      if (!this.pendingPermission.has(sessionId)) return;\n      this.pendingPermission.delete(sessionId);\n      this.wsHub.broadcast({ type: \"permission_cancelled\", sessionId });\n      return;\n    }\n    this.pendingPermission.set(sessionId, gate);\n    this.wsHub.broadcast({\n      type: \"permission\",\n      sessionId,\n      ...(gate.prompt ? { prompt: gate.prompt } : {}),\n      options: gate.options,\n      ...(gate.cursor !== undefined ? { cursor: gate.cursor } : {}),\n    });\n  }\n\n  private async handleSendAnswer(\n    sessionId: string,\n    req: IncomingMessage,\n    res: ServerResponse,\n  ): Promise<void> {\n    const body = await readBody(req);\n    const pending = this.pendingQuestions.get(sessionId);\n    const resolution = resolveAnswer(pending, body);\n    if (!resolution.ok) {\n      json(res, 400, { ok: false, reason: resolution.reason });\n      return;\n    }\n    // pending is guaranteed defined when resolution.ok is true (resolveAnswer guards it)\n    const toolUseId = pending?.toolUseId ?? \"\";\n    try {\n      this.ptyManager.sendKeys(sessionId, resolution.keys);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Failed to send answer\";\n      json(res, 400, { ok: false, reason: message });\n      return;\n    }\n    this.pendingQuestions.delete(sessionId);\n    this.wsHub.broadcast({ type: \"question_cancelled\", sessionId, toolUseId });\n    json(res, 200, { ok: true });\n  }\n\n  private async handleUploadFile(\n    sessionId: string,\n    req: IncomingMessage,\n    res: ServerResponse,\n  ): Promise<void> {\n    const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n    if (!session) {\n      json(res, 404, { error: \"Session not found\" });\n      return;\n    }\n    if (!session.projectPath) {\n      json(res, 400, { error: \"Session has no project path\" });\n      return;\n    }\n\n    const body = await readBody(req);\n    const { filename, mimeType, dataBase64 } = body ?? {};\n    if (\n      typeof filename !== \"string\" ||\n      typeof mimeType !== \"string\" ||\n      typeof dataBase64 !== \"string\"\n    ) {\n      json(res, 400, { error: \"Missing filename, mimeType, or dataBase64\" });\n      return;\n    }\n\n    try {\n      const saved = await saveUploadFile({\n        sessionId,\n        projectPath: session.projectPath,\n        originalName: filename,\n        mimeType,\n        dataBase64,\n      });\n\n      try {\n        await recordUpload(this.dbPool, this.dbInstanceId, {\n          id: saved.id,\n          sessionId,\n          filePath: saved.filePath,\n          originalName: saved.originalName,\n          mimeType: saved.mimeType,\n          sizeBytes: saved.sizeBytes,\n        });\n      } catch (err) {\n        const message = err instanceof Error ? err.message : String(err);\n        this.log.warn(\n          `[uploads] DB record failed: ${message}`,\n          { event: \"uploads.db_record_failed\", error: message },\n          \"pino\",\n        );\n      }\n\n      json(res, 201, {\n        id: saved.id,\n        path: saved.filePath,\n        originalName: saved.originalName,\n        mimeType: saved.mimeType,\n        sizeBytes: saved.sizeBytes,\n      });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Upload failed\";\n      json(res, 400, { error: message });\n    }\n  }\n\n  private handleGetOutput(sessionId: string, res: ServerResponse): void {\n    // Return PTY ring buffer if a PTY is attached; otherwise return empty\n    // so clients render \"no buffered output\" instead of an error.\n    try {\n      const output = this.ptyManager.getOutput(sessionId);\n      json(res, 200, { output });\n    } catch {\n      json(res, 200, { output: \"\" });\n    }\n  }\n\n  private handleCancel(sessionId: string, res: ServerResponse): void {\n    this.discoveryCache = null;\n    try {\n      this.ptyManager.cancel(sessionId);\n      json(res, 200, { ok: true });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Failed to cancel\";\n      json(res, 400, { error: message });\n    }\n  }\n\n  private async handleStopSession(sessionId: string, res: ServerResponse): Promise<void> {\n    const STOP_TIMEOUT_MS = 5000;\n\n    const session = this.ptyManager.getSession(sessionId);\n    if (!session) {\n      res.writeHead(404, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ error: \"Session not found\" }));\n      return;\n    }\n\n    if (session.status === \"idle\") {\n      res.writeHead(200, { \"Content-Type\": \"application/json\" });\n      res.end(JSON.stringify({ status: \"already_idle\", sessionId }));\n      return;\n    }\n\n    res.writeHead(200, {\n      \"Content-Type\": \"application/x-ndjson\",\n      \"Transfer-Encoding\": \"chunked\",\n      \"Cache-Control\": \"no-cache\",\n      \"X-Accel-Buffering\": \"no\",\n    });\n    res.write(`${JSON.stringify({ event: \"stopping\", sessionId })}\\n`);\n\n    const idlePromise = new Promise<\"idle\">((resolve) => {\n      const handler = (status: string) => {\n        if (status === \"idle\") {\n          this.sessionStatusBus.off(`status:${sessionId}`, handler);\n          resolve(\"idle\");\n        }\n      };\n      this.sessionStatusBus.on(`status:${sessionId}`, handler);\n    });\n\n    const timeoutPromise = new Promise<\"timeout\">((resolve) =>\n      setTimeout(() => resolve(\"timeout\"), STOP_TIMEOUT_MS),\n    );\n\n    this.ptyManager.putOnHold(sessionId);\n    this.discoveryCache = null;\n\n    const outcome = await Promise.race([idlePromise, timeoutPromise]);\n\n    if (outcome === \"idle\") {\n      res.write(`${JSON.stringify({ event: \"stopped\", sessionId })}\\n`);\n    } else {\n      res.write(`${JSON.stringify({ event: \"timeout\", sessionId })}\\n`);\n      this.log.warn(\n        `[stop] session ${sessionId.slice(0, 8)} did not idle within ${STOP_TIMEOUT_MS}ms`,\n      );\n    }\n\n    res.end();\n  }\n\n  private async handleAdopt(sessionId: string, res: ServerResponse): Promise<void> {\n    // Refresh discovery so we have the latest metadata\n    const discovered = await discoverClaudeProcesses();\n    this.sessionStore.setDiscovered(discovered);\n    this.discoveryCache = null;\n\n    const discSession = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n    if (!discSession || discSession.ptyAttached) {\n      json(res, 404, { error: \"Discovered session not found\" });\n      return;\n    }\n\n    const { projectPath, projectName, branch } = discSession;\n    const convId = discSession.id;\n\n    if (discSession.pid == null) {\n      json(res, 400, { error: \"Session has no known PID\" });\n      return;\n    }\n\n    // Kill the external process\n    this.ptyManager.killPid(discSession.pid);\n\n    // Start a new managed session, resuming the conversation\n    const session = await this.ptyManager.start(convId, {\n      projectPath,\n      projectName,\n      branch,\n    });\n\n    this.sessionStore.addManaged(session);\n    void this.watchConversationFile(session.id);\n\n    this.wsHub.broadcast({\n      type: \"session_list\",\n      sessions: this.sessionStore.list(this.ptyAttachedIds()),\n    });\n\n    json(res, 201, { sessionId: session.id });\n  }\n\n  private async handleStartSession(req: IncomingMessage, res: ServerResponse): Promise<void> {\n    const ip = req.socket?.remoteAddress ?? \"unknown\";\n    if (!this.checkSessionStartRateLimit(ip)) {\n      json(res, 429, {\n        error: \"Too many session start requests. Please wait before trying again.\",\n      });\n      return;\n    }\n    if (this.agentConfig.enabled) {\n      const body = await readBody(req);\n      const result = await handleStartAgentSession(body, {\n        sessionStore: this.sessionStore,\n        // biome-ignore lint/style/noNonNullAssertion: agentClient is set when agentConfig.enabled is true\n        agentClient: this.agentClient!,\n        conversationsDir: this.cacheDir ? join(dirname(this.cacheDir), \"conversations\") : \"\",\n        agentConfig: this.agentConfig,\n      });\n      json(res, result.status, result.body);\n      if (result.status === 200) {\n        this.broadcastOrUnicastSessionList(req);\n      }\n      return;\n    }\n    if (!this.browseRoot) {\n      json(res, 403, {\n        error: \"File browsing not configured. Set browseRoot on the server.\",\n        code: \"BROWSE_ROOT_NOT_SET\",\n      });\n      return;\n    }\n    const body = await readBody(req);\n    const { path: relativePath, systemPrompt: clientPrompt } = body;\n\n    if (typeof relativePath !== \"string\") {\n      json(res, 400, { error: \"Missing path field\" });\n      return;\n    }\n\n    let resolvedPath: string;\n    try {\n      resolvedPath = await resolveBrowsePath(this.browseRoot, relativePath);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Invalid path\";\n      json(res, 400, { error: message });\n      return;\n    }\n\n    this.discoveryCache = null;\n\n    const systemPromptParts = [\n      this.defaultSystemPrompt,\n      BROWSE_SYSTEM_PROMPT(this.browseRoot),\n      typeof clientPrompt === \"string\" ? clientPrompt : null,\n    ].filter(Boolean);\n\n    try {\n      const session = await this.ptyManager.startFresh({\n        projectPath: resolvedPath,\n        projectName: body.projectName,\n        systemPrompt: systemPromptParts.join(\"\\n\"),\n      });\n\n      this.sessionStore.addManaged(session);\n\n      // Return the real UUID immediately — no pending_ dance needed.\n      json(res, 202, { id: session.id, status: \"pending\" });\n\n      // Wire up JSONL watching once Claude creates the conversation file.\n      this.watchForJsonl(session.id, resolvedPath);\n\n      this.broadcastOrUnicastSessionList(req);\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Failed to start session\";\n      this.log.error(`[start] failed to start session: ${message}`, {\n        event: \"session.start_failed\",\n        error: message,\n      });\n      json(res, 500, { error: message });\n    }\n  }\n\n  // ─── Project linking ─────────────────────────────────────────────\n\n  private linkSessionToProject(sessionId: string, projectPath: string, filePath: string): void {\n    if (!this.projectsRepo || !this.conversationsRepo || !this.sessionsRepo || !this.cache) {\n      return;\n    }\n    try {\n      const project = this.projectsRepo.upsertProjectByPath(projectPath, {\n        lastConversationId: sessionId,\n        lastConversationCreatedAt: new Date().toISOString(),\n      });\n      // The conversation row may not exist yet (Claude is still writing the\n      // JSONL). Best-effort: only link if the row is present.\n      if (this.cache.hasConversation(sessionId)) {\n        this.conversationsRepo.updateConversationProjectId({\n          conversationId: sessionId,\n          projectId: project.id,\n        });\n      }\n      this.sessionsRepo.updateSessionProjectId({\n        sessionId,\n        projectId: project.id,\n      });\n      if (this.cacheMetadataRepo) {\n        this.cacheMetadataRepo.setCacheMetadata(\"last_conversation_id\", sessionId);\n      }\n    } catch (err) {\n      const message = err instanceof Error ? err.message : String(err);\n      this.log.warn(`[projects] failed to link session to project: ${message}`, {\n        event: \"session.project_link_failed\",\n        sessionId,\n        projectPath,\n        filePath,\n        error: message,\n      });\n    }\n  }\n\n  // ─── File Watcher Wiring ─────────────────────────────────────────\n\n  private async watchConversationFile(sessionId: string): Promise<void> {\n    try {\n      const conversation = await this.findConversationByUuid(sessionId);\n      if (conversation?.filePath) {\n        this.sessionFileMap.set(sessionId, conversation.filePath);\n        this.fileWatcher.watch(conversation.filePath);\n      }\n    } catch {\n      // Best-effort: if we can't find the JSONL file, raw terminal output still works\n    }\n  }\n\n  // Watch the project directory for the JSONL file Claude creates for sessionId.\n  // Once found, wire up structured event streaming. No rekeying needed — the UUID\n  // was passed to Claude via --session-id so the filename matches from the start.\n  private watchForJsonl(sessionId: string, projectPath: string): void {\n    const encoded = projectPath.replace(/[/\\\\:.]/g, \"-\");\n    const projectsDir = join(homedir(), \".claude\", \"projects\", encoded);\n    const expectedFile = `${sessionId}.jsonl`;\n    const filePath = join(projectsDir, expectedFile);\n    const deadline = Date.now() + 120_000;\n\n    let watcher: ReturnType<typeof fsWatch> | null = null;\n    const cleanup = () => {\n      try {\n        watcher?.close();\n      } catch {\n        /* ignore */\n      }\n    };\n\n    const tryWire = () => {\n      if (!this.ptyManager.hasSession(sessionId)) {\n        cleanup();\n        return;\n      }\n      if (Date.now() > deadline) {\n        cleanup();\n        return;\n      }\n\n      // Primary: Claude named the file after the session UUID\n      let resolvedFilePath = existsSync(filePath) ? filePath : null;\n\n      // Fallback: Claude resumed an existing conversation — the JSONL it writes\n      // to will be a different UUID. Pick the most recently modified JSONL in\n      // the directory that was touched within the last 5 seconds (session just started).\n      if (!resolvedFilePath && existsSync(projectsDir)) {\n        try {\n          const now = Date.now();\n          const recent = readdirSync(projectsDir)\n            .filter((f) => f.endsWith(\".jsonl\"))\n            .map((f) => ({ f, mtime: statSync(join(projectsDir, f)).mtimeMs }))\n            .filter(({ mtime }) => now - mtime < 5_000)\n            .sort((a, b) => b.mtime - a.mtime)[0];\n          if (recent) resolvedFilePath = join(projectsDir, recent.f);\n        } catch {\n          /* ignore */\n        }\n      }\n\n      if (!resolvedFilePath) return;\n\n      cleanup();\n      this.sessionFileMap.set(sessionId, resolvedFilePath);\n      this.fileWatcher.watch(resolvedFilePath);\n\n      // Broadcast any lines already written before the watcher started — Claude\n      // can finish writing the JSONL in the same tick as the watcher wires up,\n      // so chokidar won't emit a change event for those lines.\n      try {\n        const existing = readFileSync(resolvedFilePath, \"utf8\").split(\"\\n\").filter(Boolean);\n        if (existing.length > 0) {\n          this.wsHub.broadcast({ type: \"conversation_events\", sessionId, lines: existing });\n          for (const line of existing) {\n            this.wsHub.broadcast({ type: \"conversation_event\", sessionId, line });\n          }\n        }\n      } catch {\n        /* ignore — file may not be readable yet; watcher will catch future writes */\n      }\n\n      if (this.scannerReady) {\n        this.scannerStale = true;\n      } else {\n        this.scanner = null;\n      }\n      this.linkSessionToProject(sessionId, projectPath, resolvedFilePath);\n      this.cache?.markAsStreamer(sessionId);\n      this.log.info(\n        `[startFresh] wired JSONL for ${sessionId}`,\n        { event: \"session.jsonl_wired\", sessionId, filePath: resolvedFilePath },\n        \"pino\",\n      );\n    };\n\n    tryWire();\n    if (this.sessionFileMap.has(sessionId)) return; // already found\n\n    try {\n      require(\"fs\").mkdirSync(projectsDir, { recursive: true });\n      watcher = fsWatch(projectsDir, tryWire);\n      watcher.on(\"error\", cleanup);\n    } catch {\n      // fs.watch not available (e.g. in tests), ignore\n    }\n  }\n\n  private async handleBrowse(url: URL, res: ServerResponse): Promise<void> {\n    if (!this.browseRoot) {\n      json(res, 403, {\n        error: \"File browsing not configured. Set browseRoot on the server.\",\n        code: \"BROWSE_ROOT_NOT_SET\",\n      });\n      return;\n    }\n    const relativePath = url.searchParams.get(\"path\") ?? \"\";\n    try {\n      const resolved = await resolveBrowsePath(this.browseRoot, relativePath);\n      const directories = await listDirectories(resolved);\n      json(res, 200, { path: relativePath, directories });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Browse failed\";\n      if (err instanceof BrowsePathNotFoundError) {\n        json(res, 404, { error: message, code: \"PATH_NOT_FOUND\" });\n        return;\n      }\n      json(res, 400, { error: message });\n    }\n  }\n\n  private async handleMkdir(req: IncomingMessage, res: ServerResponse): Promise<void> {\n    if (!this.browseRoot) {\n      json(res, 403, {\n        error: \"File browsing not configured. Set browseRoot on the server.\",\n        code: \"BROWSE_ROOT_NOT_SET\",\n      });\n      return;\n    }\n    const body = await readBody(req);\n    const { path: relativePath, name } = body;\n    if (!name || typeof name !== \"string\") {\n      json(res, 400, { error: \"Missing name field\" });\n      return;\n    }\n    try {\n      const parentPath = await resolveBrowsePath(this.browseRoot, relativePath ?? \"\");\n      await createDirectory(parentPath, name);\n      const parentRelative = relativePath ?? \"\";\n      const created = parentRelative ? `${parentRelative}/${name}` : name;\n      json(res, 201, { created });\n    } catch (err) {\n      const message = err instanceof Error ? err.message : \"Failed to create directory\";\n      if (message.includes(\"already exists\")) {\n        json(res, 409, { error: message });\n      } else if (message.includes(\"Invalid directory name\")) {\n        json(res, 400, { error: message });\n      } else {\n        json(res, 400, { error: message });\n      }\n    }\n  }\n\n  private async handleSetSessionName(\n    sessionId: string,\n    req: IncomingMessage,\n    res: ServerResponse,\n  ): Promise<void> {\n    if (!this.cache) {\n      json(res, 503, { error: \"Cache not available\" });\n      return;\n    }\n    let parsed: { name?: string };\n    try {\n      parsed = await readBody(req);\n    } catch {\n      json(res, 400, { error: \"Invalid JSON\" });\n      return;\n    }\n    const name = parsed.name?.trim();\n    if (!name) {\n      json(res, 400, { error: \"name is required\" });\n      return;\n    }\n    this.cache.upsertSessionName(sessionId, name);\n    json(res, 200, { ok: true });\n  }\n\n  private handleGetSessionNames(res: ServerResponse): void {\n    if (!this.cache) {\n      json(res, 200, {});\n      return;\n    }\n    json(res, 200, this.cache.listSessionNames());\n  }\n}\n\n// ─── Utilities ─────────────────────────────────────────────────────\n\n// Classify whether a conversation can be resumed from the project directory\n// (cwd) the session ran in. Shared by the detail handler and the\n// resumable-session shape. A conversation's JSONL parses fine even when its\n// cwd is gone, so callers still serve the full history — this only flags that\n// resume would fail and why. Returns optional meta fields older clients\n// ignore: cwd exists → resumable; gone → not resumable, with a\n// worktree-specific reason when the path was a git worktree (now removed).\nfunction classifyResumability(cwd: string | null | undefined): {\n  resumable: boolean;\n  unavailable_reason?: \"path_missing\" | \"worktree_removed\";\n} {\n  if (!cwd) return { resumable: true };\n  if (existsSync(cwd)) return { resumable: true };\n  const ranInWorktree = /\\/\\.worktrees\\//.test(cwd) || /\\/\\.claude\\/worktrees\\//.test(cwd);\n  return {\n    resumable: false,\n    unavailable_reason: ranInWorktree ? \"worktree_removed\" : \"path_missing\",\n  };\n}\n\nfunction conversationToResumableSession(c: ConversationListItem) {\n  const availability = classifyResumability(c.projectPath);\n  const provider = c.provider ?? CLAUDE_CODE_PROVIDER;\n  return {\n    type: \"conversation\" as const,\n    id: c.id,\n    conversationId: c.id,\n    status: \"on_hold\" as const,\n    ptyAttached: false,\n    projectId: c.projectId ?? undefined,\n    projectPath: c.projectPath ?? \"\",\n    projectName: c.projectName ?? \"\",\n    branch: c.branch ?? undefined,\n    lastOutput: \"\",\n    elapsedMs: 0,\n    promptCount: c.messageCount,\n    startedAt: c.lastActivity,\n    completedAt: null,\n    lastActivityAt: c.lastActivity,\n    ...(c.title != null && { sessionName: c.title }),\n    ...(c.model != null && { model: c.model }),\n    ...(c.account != null && { account: c.account }),\n    messageCount: c.messageCount,\n    ...(c.preview != null && { preview: c.preview }),\n    ...(c.firstMessage != null && { firstMessageText: c.firstMessage }),\n    ...(c.lastMessage != null && { lastMessageText: c.lastMessage }),\n    filePath: c.filePath,\n    provider,\n    resumable: isProviderResumable(provider, availability.resumable),\n    ...(availability.unavailable_reason && {\n      unavailable_reason: availability.unavailable_reason,\n    }),\n  };\n}\n\nfunction json(res: ServerResponse, status: number, data: unknown): void {\n  res.writeHead(status, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify(data));\n}\n\nasync function writeHonoResponse(honoRes: Response, res: ServerResponse): Promise<void> {\n  const headers: Record<string, string> = {};\n  honoRes.headers.forEach((value, key) => {\n    headers[key] = value;\n  });\n  res.writeHead(honoRes.status, headers);\n  if (honoRes.body) {\n    const reader = honoRes.body.getReader();\n    try {\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        res.write(value);\n      }\n    } finally {\n      reader.releaseLock();\n    }\n  }\n  res.end();\n}\n\n// Parse THREADBASE_DIR_SCAN_DEBOUNCE_MS → a non-negative integer, or undefined\n// when unset/invalid so the caller can fall through to config/default.\nfunction parseDirScanDebounceEnv(raw: string | undefined): number | undefined {\n  if (raw == null || raw === \"\") return undefined;\n  const parsed = Number.parseInt(raw, 10);\n  return Number.isNaN(parsed) || parsed < 0 ? undefined : parsed;\n}\n\nfunction intParam(url: URL, name: string, defaultValue: number): number {\n  const val = url.searchParams.get(name);\n  if (!val) return defaultValue;\n  const parsed = Number.parseInt(val, 10);\n  return Number.isNaN(parsed) ? defaultValue : parsed;\n}\n\nconst VALID_SORT_KEYS: SessionSortKey[] = [\"startedAt\", \"lastActivityAt\", \"projectName\", \"status\"];\nconst VALID_ORDERS: SessionSortOrder[] = [\"asc\", \"desc\"];\nconst VALID_STATUSES: SessionStatus[] = [\"running\", \"waiting_input\", \"idle\"];\n\nconst SESSIONS_DEFAULT_LIMIT = 200;\nconst SESSIONS_MAX_LIMIT = 500;\n\ntype ParsedSessionListQuery = { query: import(\"./types\").SessionListQuery } | { error: string };\n\nfunction parseSessionListQuery(url: URL): ParsedSessionListQuery {\n  const limitRaw = url.searchParams.get(\"limit\");\n  let limit = SESSIONS_DEFAULT_LIMIT;\n  if (limitRaw !== null) {\n    const n = Number.parseInt(limitRaw, 10);\n    if (!Number.isFinite(n) || n < 1 || n > SESSIONS_MAX_LIMIT) {\n      return { error: `limit must be 1..${SESSIONS_MAX_LIMIT}` };\n    }\n    limit = n;\n  }\n\n  const sortByRaw = url.searchParams.get(\"sortBy\") ?? \"startedAt\";\n  if (!VALID_SORT_KEYS.includes(sortByRaw as SessionSortKey)) {\n    return { error: `sortBy must be one of ${VALID_SORT_KEYS.join(\",\")}` };\n  }\n  const sortBy = sortByRaw as SessionSortKey;\n\n  const orderRaw = url.searchParams.get(\"order\") ?? \"desc\";\n  if (!VALID_ORDERS.includes(orderRaw as SessionSortOrder)) {\n    return { error: `order must be asc or desc` };\n  }\n  const order = orderRaw as SessionSortOrder;\n\n  const statusRaw = url.searchParams.get(\"status\");\n  let status: SessionStatus[] | undefined;\n  if (statusRaw) {\n    const parts = statusRaw\n      .split(\",\")\n      .map((s) => s.trim())\n      .filter(Boolean);\n    for (const p of parts) {\n      if (!VALID_STATUSES.includes(p as SessionStatus)) {\n        return { error: `status entry \"${p}\" is invalid` };\n      }\n    }\n    status = parts as SessionStatus[];\n  }\n\n  const cursor = url.searchParams.get(\"cursor\") ?? undefined;\n\n  return { query: { limit, sortBy, order, status, cursor } };\n}\n\nfunction readBody(req: IncomingMessage): Promise<any> {\n  return new Promise((resolve, reject) => {\n    const chunks: Buffer[] = [];\n    req.on(\"data\", (chunk) => chunks.push(chunk));\n    req.on(\"end\", () => {\n      try {\n        const raw = Buffer.concat(chunks).toString(\"utf-8\");\n        resolve(raw ? JSON.parse(raw) : {});\n      } catch {\n        reject(new Error(\"Invalid JSON body\"));\n      }\n    });\n    req.on(\"error\", reject);\n  });\n}\n","import crypto from 'crypto'\n\nimport { urlAlphabet } from './url-alphabet/index.js'\n\nconst POOL_SIZE_MULTIPLIER = 128\nlet pool, poolOffset\n\nlet fillPool = bytes => {\n  if (bytes < 0) throw new RangeError('Wrong ID size')\n  try {\n    if (!pool || pool.length < bytes) {\n      pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER)\n      crypto.randomFillSync(pool)\n      poolOffset = 0\n    } else if (poolOffset + bytes > pool.length) {\n      crypto.randomFillSync(pool)\n      poolOffset = 0\n    }\n  } catch (e) {\n    pool = undefined\n    throw e\n  }\n  poolOffset += bytes\n}\n\nlet random = bytes => {\n  fillPool((bytes |= 0))\n  return pool.subarray(poolOffset - bytes, poolOffset)\n}\n\nlet customRandom = (alphabet, defaultSize, getRandom) => {\n  let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1\n\n\n  let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length)\n\n  return (size = defaultSize) => {\n    let id = ''\n    while (true) {\n      let bytes = getRandom(step)\n      let i = step\n      while (i--) {\n        id += alphabet[bytes[i] & mask] || ''\n        if (id.length === size) return id\n      }\n    }\n  }\n}\n\nlet customAlphabet = (alphabet, size = 21) =>\n  customRandom(alphabet, size, random)\n\nlet nanoid = (size = 21) => {\n  fillPool((size |= 0))\n  let id = ''\n  for (let i = poolOffset - size; i < poolOffset; i++) {\n    id += urlAlphabet[pool[i] & 63]\n  }\n  return id\n}\n\nexport { nanoid, customAlphabet, customRandom, urlAlphabet, random }\n","let urlAlphabet =\n  'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\n\nexport { urlAlphabet }\n","// src/agent/errors.ts\n//\n// Structured error codes for multi-agent HTTP endpoints.\n// Existing PTY-mode endpoints keep their unstructured {error: \"msg\"} shape;\n// the retrofit is captured in tb-multi-agent/docs/plans/structured-error-codes-retrofit.md.\n\nexport const AgentErrorCode = {\n  SESSION_NOT_FOUND: \"SESSION_NOT_FOUND\",\n  SESSION_HISTORY_FULL: \"SESSION_HISTORY_FULL\",\n  SESSION_BUSY: \"SESSION_BUSY\",\n  INVALID_SESSION_STATE: \"INVALID_SESSION_STATE\",\n  CONVERSATION_NOT_FOUND: \"CONVERSATION_NOT_FOUND\",\n  INPUT_REQUIRED: \"INPUT_REQUIRED\",\n  INVALID_BODY: \"INVALID_BODY\",\n  TEMPORAL_UNAVAILABLE: \"TEMPORAL_UNAVAILABLE\",\n  NOT_APPLICABLE_IN_MULTI_AGENT_MODE: \"NOT_APPLICABLE_IN_MULTI_AGENT_MODE\",\n  INTERNAL_ERROR: \"INTERNAL_ERROR\",\n} as const;\n\nexport type AgentErrorCode = (typeof AgentErrorCode)[keyof typeof AgentErrorCode];\n\n/**\n * Build a structured error response. `error` and `code` are canonical;\n * `extra` may carry hint fields like `retryAfterMs` or `limitBytes` but\n * cannot override the canonical fields.\n */\nexport function agentErrorResponse(\n  code: AgentErrorCode,\n  message: string,\n  extra: Record<string, unknown> = {},\n): { error: string; code: AgentErrorCode } & Record<string, unknown> {\n  return { ...extra, error: message, code };\n}\n","// src/agent/history-mapper.ts\n//\n// Converts a CachedTail (the shape ConversationCache returns) into a\n// ConversationTurn[] (the shape the worker's UserInputSignal expects).\n//\n// Rules per spec §4:\n// - Text blocks: keep, concatenate with \"\\n\".\n// - tool_use, tool_result, thinking blocks: drop.\n// - Messages with empty content after stripping: drop.\n// - Unknown roles: drop with WARN.\n\nimport type { ConversationTurn } from \"@threadbase-sh/agent-types\";\nimport type { CachedTail } from \"../conversation-cache\";\nimport { getLogger } from \"../logger\";\n\nconst log = getLogger(\"agent.history-mapper\");\n\ntype ContentBlock =\n  | { type: \"text\"; text: string }\n  | { type: \"tool_use\"; id: string; name: string; input: unknown }\n  | { type: \"tool_result\"; tool_use_id: string; content: unknown }\n  | { type: \"thinking\"; thinking: string }\n  | { type: string; [key: string]: unknown };\n\nfunction extractText(message: { text?: string; content?: unknown[] | null }): string {\n  // Prefer structured content[] over flat text if both are present.\n  if (Array.isArray(message.content)) {\n    const blocks = message.content as ContentBlock[];\n    const textParts: string[] = [];\n    for (const block of blocks) {\n      if (\n        block &&\n        typeof block === \"object\" &&\n        block.type === \"text\" &&\n        typeof block.text === \"string\"\n      ) {\n        textParts.push(block.text);\n      }\n      // tool_use / tool_result / thinking: ignored by design.\n    }\n    return textParts.join(\"\\n\");\n  }\n  return typeof message.text === \"string\" ? message.text : \"\";\n}\n\nexport function mapTailToConversationTurns(tail: CachedTail | null): ConversationTurn[] {\n  if (!tail || !Array.isArray(tail.messages) || tail.messages.length === 0) {\n    return [];\n  }\n\n  const turns: ConversationTurn[] = [];\n  for (const message of tail.messages) {\n    const role = message.role;\n    if (role !== \"user\" && role !== \"assistant\") {\n      log.warn(\"unknown role in tail; skipping\", {\n        role,\n        conversationId: tail.conversationId,\n      });\n      continue;\n    }\n    const content = extractText(message);\n    if (!content || content.length === 0) {\n      continue;\n    }\n    turns.push({ role, content });\n  }\n  return turns;\n}\n","// src/agent/payload-guard.ts\n//\n// Per spec §5: enforce a 1.5 MB ceiling on UserInputSignal payloads (75% of\n// Temporal's 2 MB hard limit) and emit trajectory WARN logs as a session\n// approaches the wall.\n\nimport type { UserInputSignal } from \"@threadbase-sh/agent-types\";\n\nexport interface PayloadMeasurement {\n  bytes: number;\n  exceedsLimit: boolean;\n}\n\n/**\n * Serialize the signal and measure its byte size. Returns both the count and\n * whether it exceeds the supplied limit.\n *\n * Callers should refuse the input and return 413 SESSION_HISTORY_FULL when\n * `exceedsLimit` is true.\n */\nexport function measureSignalPayload(\n  signal: UserInputSignal,\n  limitBytes: number,\n): PayloadMeasurement {\n  const bytes = Buffer.byteLength(JSON.stringify(signal), \"utf8\");\n  return { bytes, exceedsLimit: bytes > limitBytes };\n}\n\nexport interface TrajectoryConfig {\n  trajectoryLogBytes: number;\n  trajectoryLogTurns: number;\n}\n\n/**\n * Trajectory log trigger. Fires when EITHER:\n * - The session has reached a turn count that's a multiple of 5, starting at\n *   `trajectoryLogTurns` (default 20), OR\n * - The composed signal is >= `trajectoryLogBytes` (default 500 KB) regardless\n *   of turn count.\n *\n * Returning true means the caller should emit a WARN log line with current\n * size + turn count + percentage-of-limit info.\n */\nexport function shouldLogTrajectory(\n  turnCount: number,\n  bytes: number,\n  cfg: TrajectoryConfig,\n): boolean {\n  if (bytes >= cfg.trajectoryLogBytes) return true;\n  if (turnCount < cfg.trajectoryLogTurns) return false;\n  return (turnCount - cfg.trajectoryLogTurns) % 5 === 0;\n}\n","// src/agent/handle-send-agent-input.ts\n//\n// Pure function for multi-agent user-input. Server.ts wraps this in HTTP\n// plumbing. Implements spec §3.2 + §5 (payload guard) + §6 (lock check).\n\nimport type { UserInputSignal } from \"@threadbase-sh/agent-types\";\nimport { nanoid } from \"nanoid\";\nimport type { ConversationCache } from \"../conversation-cache\";\nimport { getLogger } from \"../logger\";\nimport type { ManagedSession } from \"../types\";\nimport type { AgentClient } from \"./agent-client\";\nimport type { AgentConfig } from \"./agent-config\";\nimport { AgentErrorCode, agentErrorResponse } from \"./errors\";\nimport { mapTailToConversationTurns } from \"./history-mapper\";\nimport { measureSignalPayload, shouldLogTrajectory } from \"./payload-guard\";\n\nconst log = getLogger(\"agent.send-input\");\n\nexport interface SendInputBody {\n  text?: string;\n}\n\nexport interface SendAgentInputDeps {\n  sessionStore: {\n    getManaged: (sessionId: string) => ManagedSession | null;\n  };\n  cache: ConversationCache;\n  agentClient: AgentClient;\n  agentConfig: AgentConfig;\n}\n\nexport interface SendAgentInputResult {\n  status: number;\n  body: Record<string, unknown>;\n}\n\nexport async function handleSendAgentInput(\n  sessionId: string,\n  body: SendInputBody,\n  deps: SendAgentInputDeps,\n): Promise<SendAgentInputResult> {\n  // 1. Validate body\n  if (typeof body.text !== \"string\" || body.text.length === 0) {\n    return {\n      status: 400,\n      body: agentErrorResponse(\n        AgentErrorCode.INPUT_REQUIRED,\n        \"Body must contain a non-empty `text` field\",\n      ),\n    };\n  }\n\n  // 2. Look up session\n  const session = deps.sessionStore.getManaged(sessionId);\n  if (!session) {\n    return {\n      status: 404,\n      body: agentErrorResponse(AgentErrorCode.SESSION_NOT_FOUND, `Session ${sessionId} not found`),\n    };\n  }\n\n  // 3. Session-busy check\n  if (session.currentTurnId) {\n    return {\n      status: 429,\n      body: agentErrorResponse(\n        AgentErrorCode.SESSION_BUSY,\n        \"A turn is already in flight; retry shortly\",\n        { retryAfterMs: deps.agentConfig.sessionBusyRetryMs },\n      ),\n    };\n  }\n\n  const turnId = nanoid();\n  // 4. Acquire the lock by setting currentTurnId before any I/O.\n  session.currentTurnId = turnId;\n\n  // 5. Build conversation history from cache\n  const conversationId = session.conversationId ?? session.id;\n  const tail = deps.cache.getConversationTail(conversationId);\n  const conversationHistory = mapTailToConversationTurns(tail);\n\n  // 6. Compose signal\n  const signal: UserInputSignal = {\n    turnId,\n    prompt: body.text,\n    conversationHistory,\n  };\n\n  // 7. Payload-size guard\n  const measurement = measureSignalPayload(signal, deps.agentConfig.payload.limitBytes);\n  const turnCount = conversationHistory.length;\n  if (shouldLogTrajectory(turnCount, measurement.bytes, deps.agentConfig.payload)) {\n    log.warn(`session payload trajectory`, {\n      sessionId,\n      turnCount,\n      observedBytes: measurement.bytes,\n      limitBytes: deps.agentConfig.payload.limitBytes,\n      pctOfLimit: Math.round((measurement.bytes / deps.agentConfig.payload.limitBytes) * 100),\n    });\n  }\n  if (measurement.exceedsLimit) {\n    session.currentTurnId = null; // release lock — no signal will be sent\n    return {\n      status: 413,\n      body: agentErrorResponse(\n        AgentErrorCode.SESSION_HISTORY_FULL,\n        \"Conversation history exceeds payload limit\",\n        {\n          limitBytes: deps.agentConfig.payload.limitBytes,\n          observedBytes: measurement.bytes,\n        },\n      ),\n    };\n  }\n\n  // 8. Send signal\n  try {\n    await deps.agentClient.sendUserInput(sessionId, signal);\n  } catch (err) {\n    session.currentTurnId = null; // release lock on failure\n    const message = err instanceof Error ? err.message : \"Temporal unavailable\";\n    return {\n      status: 503,\n      body: agentErrorResponse(AgentErrorCode.TEMPORAL_UNAVAILABLE, message),\n    };\n  }\n\n  return { status: 202, body: { turnId, status: \"queued\" } };\n}\n","// src/agent/handle-start-agent-session.ts\n//\n// Pure function (deps + body in, response out) for multi-agent session\n// creation. Server.ts wraps this in HTTP plumbing.\n\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { nanoid } from \"nanoid\"; // already a transitive dep of tb-streamer\nimport type { ManagedSession } from \"../types\";\nimport type { AgentClient } from \"./agent-client\";\nimport type { AgentConfig } from \"./agent-config\";\nimport { AgentErrorCode, agentErrorResponse } from \"./errors\";\n\nexport interface StartSessionBody {\n  conversationId?: string;\n}\n\nexport interface StartAgentSessionDeps {\n  sessionStore: {\n    addManaged: (session: ManagedSession) => void;\n    initAgentSession: (sessionId: string, dedupeCapacity: number) => void;\n  };\n  agentClient: AgentClient;\n  conversationsDir: string;\n  agentConfig: AgentConfig;\n}\n\nexport interface StartAgentSessionResult {\n  status: number;\n  body: Record<string, unknown>;\n}\n\n/**\n * Validate body shape. Accept only `{}` or `{conversationId: string}`.\n * Anything else is `INVALID_BODY`.\n */\nfunction validateBody(body: unknown): { ok: true; conversationId: string | null } | { ok: false } {\n  if (body === null || body === undefined || typeof body !== \"object\") {\n    return { ok: false };\n  }\n  const keys = Object.keys(body);\n  if (keys.length === 0) {\n    return { ok: true, conversationId: null };\n  }\n  if (keys.length === 1 && keys[0] === \"conversationId\") {\n    const v = (body as { conversationId: unknown }).conversationId;\n    if (typeof v === \"string\" && v.length > 0) {\n      return { ok: true, conversationId: v };\n    }\n  }\n  return { ok: false };\n}\n\nexport async function handleStartAgentSession(\n  body: unknown,\n  deps: StartAgentSessionDeps,\n): Promise<StartAgentSessionResult> {\n  const parsed = validateBody(body);\n  if (!parsed.ok) {\n    return {\n      status: 400,\n      body: agentErrorResponse(\n        AgentErrorCode.INVALID_BODY,\n        \"Body must be {} or {conversationId: string}\",\n      ),\n    };\n  }\n\n  let conversationId = parsed.conversationId;\n  if (conversationId) {\n    // Resume — JSONL must exist\n    const jsonlPath = join(deps.conversationsDir, `${conversationId}.jsonl`);\n    if (!existsSync(jsonlPath)) {\n      return {\n        status: 404,\n        body: agentErrorResponse(\n          AgentErrorCode.CONVERSATION_NOT_FOUND,\n          `No conversation found for id ${conversationId}`,\n        ),\n      };\n    }\n  }\n\n  const sessionId = nanoid();\n  if (!conversationId) conversationId = sessionId;\n\n  // Build a minimal ManagedSession. PTY-specific fields stay undefined/null;\n  // the spec (§3.3) says they're returned as null in multi-agent mode.\n  const now = new Date();\n  const session: ManagedSession = {\n    id: sessionId,\n    conversationId,\n    projectPath: \"\",\n    projectName: \"\",\n    branch: \"\",\n    status: \"running\",\n    startedAt: now,\n    completedAt: null,\n    promptCount: 0,\n    lastOutput: \"\",\n    currentTurnId: null,\n  };\n\n  try {\n    deps.sessionStore.addManaged(session);\n    deps.sessionStore.initAgentSession(sessionId, deps.agentConfig.dedupe.perSessionCapacity);\n    await deps.agentClient.startSession(sessionId);\n  } catch (err) {\n    const message = err instanceof Error ? err.message : \"Temporal unavailable\";\n    return {\n      status: 503,\n      body: agentErrorResponse(AgentErrorCode.TEMPORAL_UNAVAILABLE, message),\n    };\n  }\n\n  return {\n    status: 200,\n    body: { sessionId, conversationId, status: \"running\" },\n  };\n}\n","import type { HttpBindings } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport type { UpgradeWebSocket } from \"hono/ws\";\nimport type { WebSocket } from \"ws\";\nimport { getLogger } from \"../logger\";\nimport { authMiddleware } from \"./middleware/auth.middleware\";\nimport { corsMiddleware } from \"./middleware/cors.middleware\";\nimport { errorMiddleware } from \"./middleware/error.middleware\";\nimport { createBrowseRoutes } from \"./routes/browse.routes\";\nimport { createConversationRoutes } from \"./routes/conversations.routes\";\nimport { createHealthRoutes } from \"./routes/health.routes\";\nimport { createMiscRoutes } from \"./routes/misc.routes\";\nimport { createPairRoutes } from \"./routes/pair.routes\";\nimport { createProgressRoutes } from \"./routes/progress.routes\";\nimport { createProjectRoutes } from \"./routes/projects.routes\";\nimport { createScannerRoutes } from \"./routes/scanner.routes\";\nimport { createSessionRoutes } from \"./routes/sessions.routes\";\nimport { createWsRoutes } from \"./routes/ws.routes\";\nimport type { ApiDeps } from \"./types/api-deps\";\n\nexport type AppEnv = {\n  Bindings: HttpBindings;\n  Variables: {\n    requestId?: string;\n    validatedBody?: unknown;\n    validatedQuery?: unknown;\n  };\n};\n\nexport const createHonoApp = (deps: ApiDeps, upgradeWebSocket?: UpgradeWebSocket<WebSocket>) => {\n  const app = new Hono<AppEnv>();\n  const httpLog = getLogger(\"http\");\n\n  app.use(\"*\", async (c, next) => {\n    const start = Date.now();\n    const ua = c.req.header(\"user-agent\") ?? \"\";\n    await next();\n    if (!deps.logMenubarRequests && c.req.header(\"x-client\") === \"menubar\") return;\n    const ms = Date.now() - start;\n    httpLog.info(`[req] ${c.req.method} ${c.req.path} → ${c.res.status} ${ms}ms`, {\n      method: c.req.method,\n      path: c.req.path,\n      status: c.res.status,\n      ms,\n      ua,\n      event: \"http.request\",\n    });\n  });\n  app.use(\"*\", corsMiddleware());\n  app.use(\"*\", authMiddleware(deps));\n  app.onError(errorMiddleware);\n\n  app.route(\"/healthz\", createHealthRoutes());\n  app.route(\"/\", createMiscRoutes(deps));\n  app.route(\"/api/sessions\", createSessionRoutes(deps));\n  app.route(\"/api/conversations\", createConversationRoutes(deps));\n  app.route(\"/api/projects\", createProjectRoutes(deps));\n  app.route(\"/api/pair\", createPairRoutes(deps));\n  app.route(\"/api\", createBrowseRoutes(deps));\n  app.route(\"/\", createScannerRoutes(deps));\n  app.route(\"/internal\", createProgressRoutes(deps));\n\n  if (upgradeWebSocket) {\n    app.route(\"/\", createWsRoutes(deps, upgradeWebSocket));\n  }\n\n  return app;\n};\n","import type { MiddlewareHandler } from \"hono\";\nimport { validateApiKey } from \"../../auth\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nfunction isLocalRequest(remoteAddr: string | undefined): boolean {\n  const addr = remoteAddr ?? \"\";\n  return addr === \"127.0.0.1\" || addr === \"::1\" || addr === \"::ffff:127.0.0.1\";\n}\n\nconst PUBLIC_PATHS = new Set([\"/healthz\"]);\n// /api/__update uses HMAC signature auth instead of Bearer; skip the\n// Bearer-token middleware so the route handler can validate the signature.\nconst PUBLIC_POST_PATHS = new Set([\"/api/pair/exchange\", \"/api/__update\"]);\n// /internal/sessions/:sessionId/progress also uses HMAC (Progress webhook),\n// and the sessionId is dynamic so we match by prefix.\nconst PUBLIC_POST_PREFIXES = [\"/internal/sessions/\"];\n\nexport const authMiddleware =\n  (deps: Pick<ApiDeps, \"apiKey\" | \"localNoAuth\">): MiddlewareHandler<AppEnv> =>\n  async (c, next) => {\n    const path = new URL(c.req.url).pathname;\n    const method = c.req.method;\n    const isPublicPostPath =\n      method === \"POST\" &&\n      (PUBLIC_POST_PATHS.has(path) || PUBLIC_POST_PREFIXES.some((p) => path.startsWith(p)));\n    if (PUBLIC_PATHS.has(path) || isPublicPostPath) {\n      await next();\n      return;\n    }\n\n    if (deps.localNoAuth) {\n      const remoteAddr = c.env.incoming?.socket?.remoteAddress;\n      if (isLocalRequest(remoteAddr)) {\n        await next();\n        return;\n      }\n    }\n\n    const authorization = c.req.header(\"authorization\");\n    if (authorization?.startsWith(\"Bearer \")) {\n      const token = authorization.slice(7);\n      if (validateApiKey(token, deps.apiKey)) {\n        await next();\n        return;\n      }\n    }\n\n    const key = c.req.query(\"key\");\n    if (key && validateApiKey(key, deps.apiKey)) {\n      await next();\n      return;\n    }\n\n    return c.json({ error: \"Unauthorized\" }, 401);\n  };\n","import type { MiddlewareHandler } from \"hono\";\nimport type { AppEnv } from \"../app\";\n\n// Allow browser-originated requests only from known local dev origins.\n// Mobile app requests are not browser-originated and don't send an Origin header\n// so they are unaffected. Wildcard (*) would let any page make authenticated\n// requests if the caller obtains the API key out-of-band.\nconst ALLOWED_ORIGINS = new Set([\n  \"http://localhost:8081\",\n  \"http://localhost:19006\",\n  \"http://localhost:3000\",\n]);\n\nexport const corsMiddleware = (): MiddlewareHandler<AppEnv> => async (c, next) => {\n  const origin = c.req.header(\"origin\");\n  const allowedOrigin = origin && ALLOWED_ORIGINS.has(origin) ? origin : null;\n\n  if (allowedOrigin) {\n    c.res.headers.set(\"Access-Control-Allow-Origin\", allowedOrigin);\n    c.res.headers.set(\"Vary\", \"Origin\");\n    c.res.headers.set(\"Access-Control-Allow-Methods\", \"GET, POST, PATCH, OPTIONS\");\n    c.res.headers.set(\"Access-Control-Allow-Headers\", \"Authorization, Content-Type, If-None-Match\");\n    c.res.headers.set(\"Access-Control-Expose-Headers\", \"ETag\");\n  }\n\n  if (c.req.method === \"OPTIONS\") {\n    return c.newResponse(null, allowedOrigin ? 204 : 403);\n  }\n\n  await next();\n};\n","import type { ErrorHandler } from \"hono\";\nimport type { AppEnv } from \"../app\";\n\nexport const errorMiddleware: ErrorHandler<AppEnv> = (err, c) => {\n  const message = err instanceof Error ? err.message : \"Internal server error\";\n  return c.json({ error: message }, 500);\n};\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nconst ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createBrowseRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/browse\", async (c) => {\n    const url = new URL(c.req.url);\n    await deps.handleBrowse(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/browse/mkdir\", async (c) => {\n    await deps.handleMkdir(c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nconst ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createConversationRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/count\", async (c) => {\n    const url = new URL(c.req.url);\n    await deps.handleConversationsCount(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/:id{.+}\", async (c) => {\n    const id = c.req.param(\"id\");\n    const url = new URL(c.req.url);\n    const ifNoneMatch = c.req.header(\"if-none-match\");\n    await deps.handleGetConversation(id, url, c.env.outgoing, ifNoneMatch);\n    return alreadyHandled();\n  });\n\n  app.get(\"/\", async (c) => {\n    const url = new URL(c.req.url);\n    await deps.handleListConversations(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport { getVersion } from \"../../version\";\nimport type { AppEnv } from \"../app\";\n\nexport const createHealthRoutes = () => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/\", (c) => c.json({ ok: true, version: getVersion() }));\n\n  return app;\n};\n","// Runtime version reporting.\n//\n// The version string is NOT baked into the compiled bundle. Each installer\n// (Homebrew, auto-updater, scripts/deploy.sh) writes a `version.txt` next to\n// the script it activates, so the binary reports the correct version even\n// when the tarball it was built from carried a stale package.json.\n//\n// Resolution order:\n//   1. Read `<dirname(process.argv[1])>/version.txt` — set by the installer.\n//   2. Read `<dirname(process.argv[1])>/../version.txt` — the built CLI is a\n//      symlink whose realpath resolves one level into $INSTALL_DIR/releases/,\n//      so the installer's version.txt sits in the parent dir.\n//   3. Fall back to `<dirname(process.argv[1])>/../package.json` with a\n//      `+source` suffix — covers source-tree runs (vitest, ts-node,\n//      `npm run dev`) where no installer has stamped a version.\n//   4. If all fail, return \"0.0.0+unknown\" so callers never crash on a\n//      missing version.\n\nimport { readFileSync, realpathSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nlet cached: string | undefined;\n\nexport function getVersion(): string {\n  if (cached !== undefined) return cached;\n  cached = resolveVersion();\n  return cached;\n}\n\n// Exposed for tests to clear the memoization between cases.\nexport function resetVersionCache(): void {\n  cached = undefined;\n}\n\nfunction resolveVersion(): string {\n  const scriptPath = process.argv[1] ?? \"\";\n  const here = scriptPath ? dirname(scriptPath) : process.cwd();\n  // Also resolve the realpath so shims (e.g. /opt/homebrew/bin/tb-streamer →\n  // ~/.threadbase/cli.js → releases/cli.<sha>.cjs) don't cause version.txt\n  // lookups to land in the wrong directory.\n  let realHere = here;\n  try {\n    realHere = dirname(realpathSync(scriptPath));\n  } catch {}\n  // Deduplicate: if realHere === here we don't want to check the same dirs twice.\n  const searchDirs =\n    realHere === here\n      ? [here, join(here, \"..\")]\n      : [here, join(here, \"..\"), realHere, join(realHere, \"..\")];\n  // Check both the script directory and its parent — the installer places\n  // version.txt in $INSTALL_DIR (~/.threadbase/) but the built CLI is a\n  // symlink whose realpath resolves into $INSTALL_DIR/releases/, so `here`\n  // ends up one level too deep.\n  for (const dir of searchDirs) {\n    try {\n      const v = readFileSync(join(dir, \"version.txt\"), \"utf8\").trim();\n      if (v) return v;\n    } catch {}\n  }\n  try {\n    const pkg = JSON.parse(readFileSync(join(here, \"..\", \"package.json\"), \"utf8\")) as {\n      version?: string;\n    };\n    if (pkg.version) return `${pkg.version}+source`;\n  } catch {}\n  return \"0.0.0+unknown\";\n}\n","import { spawn } from \"node:child_process\";\nimport { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { Hono } from \"hono\";\nimport type { IncomingMessage } from \"http\";\nimport { hostname } from \"os\";\nimport { loadUpdateConfig } from \"../../config/update-config\";\nimport { getLogger } from \"../../logger\";\nimport { getVersion } from \"../../version\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nfunction readJsonBody(req: IncomingMessage): Promise<unknown> {\n  return new Promise((resolve, reject) => {\n    const chunks: Buffer[] = [];\n    req.on(\"data\", (chunk) => chunks.push(chunk));\n    req.on(\"end\", () => {\n      try {\n        const raw = Buffer.concat(chunks).toString(\"utf-8\");\n        resolve(raw ? JSON.parse(raw) : {});\n      } catch {\n        reject(new Error(\"Invalid JSON body\"));\n      }\n    });\n    req.on(\"error\", reject);\n  });\n}\n\nfunction readRawBody(req: IncomingMessage): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const chunks: Buffer[] = [];\n    req.on(\"data\", (chunk) => chunks.push(chunk));\n    req.on(\"end\", () => resolve(Buffer.concat(chunks).toString(\"utf-8\")));\n    req.on(\"error\", reject);\n  });\n}\n\nfunction verifyWebhookSignature(body: string, header: string | undefined, secret: string): boolean {\n  if (!header) return false;\n  const provided = header.startsWith(\"sha256=\") ? header.slice(7) : header;\n  const expected = createHmac(\"sha256\", secret).update(body).digest(\"hex\");\n  const a = Buffer.from(provided, \"utf-8\");\n  const b = Buffer.from(expected, \"utf-8\");\n  if (a.length !== b.length) return false;\n  return timingSafeEqual(a, b);\n}\n\nconst clientLog = getLogger(\"client\");\n\ntype ClientLogEntry = {\n  level?: \"debug\" | \"info\" | \"warn\" | \"error\";\n  msg?: string;\n  ts?: string;\n  tag?: string;\n  fields?: Record<string, unknown>;\n};\n\nexport const createMiscRoutes = (\n  deps: Pick<\n    ApiDeps,\n    \"publicUrl\" | \"sessionStore\" | \"ptyAttachedIds\" | \"rotateApiKey\" | \"localNoAuth\"\n  >,\n) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/api/info\", (c) => {\n    const ptyIds = deps.ptyAttachedIds();\n    return c.json({\n      version: getVersion(),\n      machineName: hostname(),\n      platform: process.platform,\n      activeSessions: deps.sessionStore.list(ptyIds).filter((s) => s.status === \"running\").length,\n      publicUrl: deps.publicUrl,\n    });\n  });\n\n  app.get(\"/api/profiles\", (c) => c.json([]));\n\n  app.post(\"/api/auth/rotate\", (c) => {\n    // Block rotation when localNoAuth is on — any localhost process could\n    // call this and lock out the legitimate owner.\n    if (deps.localNoAuth) {\n      return c.json({ error: \"key rotation is disabled while localNoAuth is active\" }, 403);\n    }\n    const { newKey, persisted } = deps.rotateApiKey();\n    return c.json({\n      apiKey: newKey,\n      persisted,\n      ...(persisted\n        ? {}\n        : {\n            warning:\n              \"Key rotated in memory only. The server was started with --api-key, so the \" +\n              \"old key will be restored on restart. Remove --api-key and let the server \" +\n              \"manage the key via ~/.threadbase/server.yaml for rotation to survive restarts.\",\n          }),\n    });\n  });\n\n  app.post(\"/api/push/register\", (c) => c.json({ ok: true }));\n\n  // Webhook for auto-update. Triggered by the release CI (or any caller that\n  // knows webhook_secret) to make this server pull the new release without\n  // waiting for the next poll. Enabled only when webhook_secret is set in\n  // ~/.threadbase/update.yaml. HMAC-SHA256 of the raw body using that secret\n  // must match the X-Threadbase-Signature header.\n  app.post(\"/api/__update\", async (c) => {\n    const cfg = loadUpdateConfig();\n    if (!cfg?.webhook_secret) {\n      return c.json({ error: \"webhook disabled\" }, 404);\n    }\n\n    let body: string;\n    try {\n      body = await readRawBody(c.env.incoming);\n    } catch {\n      return c.json({ error: \"could not read body\" }, 400);\n    }\n\n    const sig = c.req.header(\"x-threadbase-signature\");\n    if (!verifyWebhookSignature(body, sig, cfg.webhook_secret)) {\n      return c.json({ error: \"invalid signature\" }, 401);\n    }\n\n    const cliPath = process.argv[1];\n    if (!cliPath) {\n      return c.json({ error: \"cannot resolve updater path\" }, 500);\n    }\n    const child = spawn(process.execPath, [cliPath, \"update\", \"--force\"], {\n      detached: true,\n      stdio: \"ignore\",\n    });\n    child.unref();\n\n    return c.json({ accepted: true, pid: child.pid }, 202);\n  });\n\n  app.post(\"/api/__client-log\", async (c) => {\n    const ua = c.req.header(\"user-agent\") ?? \"\";\n    let body: { entries?: ClientLogEntry[] } = {};\n    try {\n      body = (await readJsonBody(c.env.incoming)) as { entries?: ClientLogEntry[] };\n    } catch {\n      return c.json({ ok: false, error: \"invalid json\" }, 400);\n    }\n    const entries = Array.isArray(body.entries) ? body.entries : [];\n    for (const e of entries) {\n      const level =\n        e.level === \"debug\" || e.level === \"warn\" || e.level === \"error\" ? e.level : \"info\";\n      clientLog[level](`[client] ${e.tag ?? \"log\"}: ${e.msg ?? \"\"}`, {\n        clientTs: e.ts,\n        tag: e.tag,\n        ua,\n        ...(e.fields ?? {}),\n      });\n    }\n    return c.json({ ok: true, accepted: entries.length });\n  });\n\n  return app;\n};\n","import { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport { type UpdateConfig, UpdateConfigSchema } from \"../schemas/updateConfig.schema\";\n\nconst DEFAULT_CONFIG_PATH = join(homedir(), \".threadbase\", \"update.yaml\");\n\nexport interface LoadUpdateConfigOptions {\n  path?: string;\n}\n\n/**\n * Loads ~/.threadbase/update.yaml. Returns null when the file does not exist\n * (auto-update disabled). Throws on malformed YAML or schema-invalid content\n * so misconfiguration is loud rather than silently disabling updates.\n */\nexport function loadUpdateConfig(opts: LoadUpdateConfigOptions = {}): UpdateConfig | null {\n  const path = opts.path ?? DEFAULT_CONFIG_PATH;\n\n  let raw: string;\n  try {\n    raw = readFileSync(path, \"utf-8\");\n  } catch (err) {\n    if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n    throw err;\n  }\n\n  const parsed: unknown = parseYaml(raw);\n  if (parsed === null || parsed === undefined) {\n    throw new Error(`update.yaml at ${path} is empty — github_repo is required`);\n  }\n\n  return UpdateConfigSchema.parse(parsed);\n}\n\nexport { DEFAULT_CONFIG_PATH as UPDATE_CONFIG_PATH };\n","import { z } from \"zod\";\n\nexport const UpdateConfigSchema = z\n  .object({\n    auto_update: z.boolean().default(false),\n    channel: z.enum([\"stable\", \"next\"]).default(\"stable\"),\n    allow: z.array(z.enum([\"patch\", \"minor\", \"major\"])).default([\"patch\", \"minor\"]),\n    poll_interval_minutes: z.number().int().min(0).default(60),\n    defer_if_active_sessions: z.boolean().default(true),\n    github_repo: z.string().regex(/^[^/]+\\/[^/]+$/, \"github_repo must be 'owner/name'\"),\n    webhook_secret: z.string().min(1).nullable().default(null),\n  })\n  .strict();\n\nexport type UpdateConfig = z.infer<typeof UpdateConfigSchema>;\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nconst ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createPairRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.post(\"/start\", (c) => {\n    deps.handlePairStart(c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  // /api/pair/exchange is public — auth middleware skips it\n  app.post(\"/exchange\", async (c) => {\n    await deps.handlePairExchange(c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nconst ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createProjectRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/\", (c) => {\n    const url = new URL(c.req.url);\n    deps.handleListProjects(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/popular\", (c) => {\n    const url = new URL(c.req.url);\n    deps.handleGetPopularProjects(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nconst ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createScannerRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/api/search\", async (c) => {\n    const url = new URL(c.req.url);\n    await deps.handleSearch(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\n// Sentinel status used to signal that the handler already wrote to the Node\n// ServerResponse directly. writeHonoResponse in server.ts skips piping when\n// it sees this status.\nexport const ALREADY_HANDLED = 597;\nconst alreadyHandled = () => new Response(null, { status: ALREADY_HANDLED });\n\nexport const createSessionRoutes = (deps: ApiDeps) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\"/count\", (c) => {\n    deps.handleSessionsCount(c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/recents\", (c) => {\n    const url = new URL(c.req.url);\n    deps.handleGetRecentSessions(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/names\", (c) => {\n    deps.handleGetSessionNames(c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/resume\", async (c) => {\n    await deps.handleResume(c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/start\", async (c) => {\n    await deps.handleStartSession(c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/\", async (c) => {\n    const url = new URL(c.req.url);\n    await deps.handleListSessions(url, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/:id/output\", (c) => {\n    deps.handleGetOutput(c.req.param(\"id\"), c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/input\", async (c) => {\n    await deps.handleSendInput(c.req.param(\"id\"), c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/answer\", async (c) => {\n    await deps.handleSendAnswer(c.req.param(\"id\"), c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/files\", async (c) => {\n    await deps.handleUploadFile(c.req.param(\"id\"), c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/cancel\", (c) => {\n    deps.handleCancel(c.req.param(\"id\"), c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.patch(\"/:id/name\", async (c) => {\n    await deps.handleSetSessionName(c.req.param(\"id\"), c.env.incoming, c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/adopt\", async (c) => {\n    await deps.handleAdopt(c.req.param(\"id\"), c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.post(\"/:id/stop\", async (c) => {\n    await deps.handleStopSession(c.req.param(\"id\"), c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  app.get(\"/:id\", (c) => {\n    deps.handleGetSession(c.req.param(\"id\"), c.env.outgoing);\n    return alreadyHandled();\n  });\n\n  return app;\n};\n","import { Hono } from \"hono\";\nimport type { UpgradeWebSocket } from \"hono/ws\";\nimport type { WebSocket } from \"ws\";\nimport type { AppEnv } from \"../app\";\nimport type { ApiDeps } from \"../types/api-deps\";\n\nexport const createWsRoutes = (deps: ApiDeps, upgradeWebSocket: UpgradeWebSocket<WebSocket>) => {\n  const app = new Hono<AppEnv>();\n\n  app.get(\n    \"/ws\",\n    upgradeWebSocket(() => {\n      let openWs: WebSocket | null = null;\n      return {\n        onOpen(_evt, ws) {\n          const raw = ws.raw;\n          if (!raw) return;\n          openWs = raw;\n          deps.handleWsOpen(raw);\n        },\n        onMessage(evt, _ws) {\n          if (openWs) deps.handleWsMessage(openWs, evt.data);\n        },\n        onClose(_evt, _ws) {\n          if (openWs) deps.handleWsClose(openWs);\n        },\n      };\n    }),\n  );\n\n  return app;\n};\n","import { mkdir, readdir, realpath, stat } from \"fs/promises\";\nimport { join, resolve, sep } from \"path\";\n\n/**\n * Thrown when a browse target is inside the root but does not exist on disk\n * (e.g. a mobile-cached path whose folder was since moved or deleted). Lets the\n * browse handler answer 404 instead of conflating it with an out-of-root 400.\n */\nexport class BrowsePathNotFoundError extends Error {\n  constructor(message: string) {\n    super(message);\n    this.name = \"BrowsePathNotFoundError\";\n  }\n}\n\nexport async function resolveBrowsePath(browseRoot: string, relativePath: string): Promise<string> {\n  const normalizedRoot = resolve(browseRoot);\n  // On Unix, if relativePath is already an absolute path under browseRoot, use it directly.\n  // Only strip the leading separator for bare names like \"/projectA\" sent by the mobile browse\n  // tree — not for full paths like \"/Users/foo/bar\" which are absolute, not drive-root-relative.\n  // On Windows we always strip because \"\\foo\" means \"drive root relative\", never a full path.\n  let sanitized: string;\n  if (\n    process.platform !== \"win32\" &&\n    relativePath.startsWith(\"/\") &&\n    relativePath.length > 1 &&\n    relativePath.includes(\"/\", 1)\n  ) {\n    sanitized = relativePath;\n  } else {\n    sanitized = relativePath.replace(/^[/\\\\]+/, \"\");\n  }\n  const target = sanitized ? resolve(normalizedRoot, sanitized) : normalizedRoot;\n  // Build the allowed prefix with exactly one separator — normalizedRoot may already end with sep\n  // when browseRoot is a drive root (e.g. \"C:\\\"), which would otherwise create a double-sep prefix.\n  const rootPrefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n  if (!target.startsWith(rootPrefix) && target !== normalizedRoot) {\n    throw new Error(\"Path outside browse root\");\n  }\n  // Verify the path exists; surface a not-found as a typed error so the handler\n  // can answer 404 (folder gone) rather than the out-of-root 400 above.\n  try {\n    await realpath(target);\n  } catch (err: any) {\n    if (err?.code === \"ENOENT\") {\n      throw new BrowsePathNotFoundError(`Path not found: ${target}`);\n    }\n    throw err;\n  }\n  return target;\n}\n\nexport async function listDirectories(absolutePath: string): Promise<Array<{ name: string }>> {\n  const entries = await readdir(absolutePath, { withFileTypes: true });\n  return entries\n    .filter((e) => e.isDirectory())\n    .map((e) => ({ name: e.name }))\n    .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nexport async function createDirectory(parentAbsolutePath: string, name: string): Promise<string> {\n  if (name.includes(\"/\") || name.includes(\"\\\\\") || name === \"..\" || name === \".\") {\n    throw new Error(\"Invalid directory name\");\n  }\n  const target = join(parentAbsolutePath, name);\n  try {\n    const s = await stat(target);\n    if (s.isDirectory()) throw new Error(\"Directory already exists\");\n  } catch (err: any) {\n    if (err.code !== \"ENOENT\") throw err;\n  }\n  await mkdir(target);\n  return target;\n}\n","import Database from \"better-sqlite3\";\nimport { closeSync, existsSync, mkdirSync, openSync, readSync, statSync } from \"fs\";\nimport { dirname } from \"path\";\nimport { runSqliteMigrations } from \"./db/sqlite-migrate\";\nimport { CLAUDE_CODE_PROVIDER } from \"./providers\";\nimport {\n  DEFAULT_AGENT_ENTRYPOINTS,\n  isAgentFile,\n  isAgentLine,\n} from \"./services/conversations/isAgentConversation\";\n\nexport interface ConversationCacheOptions {\n  // When true, drop conversations whose JSONL came from an agent entrypoint.\n  // Default false to preserve legacy behavior.\n  filterAgentConversations?: boolean;\n  // Set of `entrypoint` values to treat as agent traffic. Defaults to\n  // DEFAULT_AGENT_ENTRYPOINTS ({ sdk-cli, claude-vscode }).\n  agentEntrypoints?: ReadonlySet<string>;\n  // Fired the first time an agent JSONL is detected for a given file path\n  // (from updateFromLine). Lets the server unwatch the file.\n  onAgentFileDetected?: (filePath: string) => void;\n}\n\nexport interface ConversationListItem {\n  id: string;\n  filePath: string;\n  projectId: string | null;\n  projectPath: string | null;\n  projectName: string | null;\n  title: string | null;\n  model: string | null;\n  account: string | null;\n  branch: string | null;\n  messageCount: number;\n  lastActivity: string;\n  firstMessage: string | null;\n  lastMessage: string | null;\n  preview: string | null;\n  source: string | null;\n  provider: \"claude-code\" | \"codex-cli\";\n}\n\nexport interface CachedTailMessage {\n  role: string;\n  timestamp: string;\n  text: string;\n  content?: unknown[];\n}\n\nexport interface CachedTail {\n  conversationId: string;\n  messages: CachedTailMessage[];\n  tailSize: number;\n}\n\nexport interface ScannerMeta {\n  id: string;\n  sessionId?: string;\n  filePath: string;\n  projectPath?: string;\n  projectName?: string;\n  title?: string;\n  model?: string;\n  account?: string;\n  gitBranch?: string;\n  messageCount?: number;\n  timestamp?: string;\n  firstMessage?: unknown;\n  lastMessage?: unknown;\n  preview?: string;\n  provider?: \"claude-code\" | \"codex-cli\";\n}\n\ninterface MetaRow {\n  id: string;\n  file_path: string;\n  project_id: string | null;\n  project_path: string | null;\n  project_name: string | null;\n  title: string | null;\n  model: string | null;\n  account: string | null;\n  branch: string | null;\n  message_count: number;\n  last_activity: number | null;\n  first_message: string | null;\n  last_message: string | null;\n  preview: string | null;\n  source: string | null;\n  provider: \"claude-code\" | \"codex-cli\";\n  updated_at: number;\n}\n\ninterface TailRow {\n  conversation_id: string;\n  messages_json: string;\n  tail_size: number;\n  updated_at: number;\n}\n\ntype ContentBlock = { type: string; text?: string; [key: string]: unknown };\n\ninterface JsonlLine {\n  role?: string;\n  type?: string;\n  timestamp?: string;\n  // Set by Claude Code / Agent SDK on every real message line. \"cli\" = human\n  // interactive Claude Code; \"sdk-cli\" = Claude Agent SDK / claude-mem / hooks.\n  entrypoint?: string;\n  // Project context: the scanner sets `cwd` from any line that carries it\n  // (attachment, metadata, user, assistant). The live watcher must do the\n  // same — otherwise skeleton rows persist with NULL project_path.\n  cwd?: string;\n  slug?: string;\n  // Real Claude JSONL emits either an array of blocks or a raw string. Normalize\n  // via `normalizeContent` before consuming.\n  content?: ContentBlock[] | string;\n  message?: {\n    role?: string;\n    content?: ContentBlock[] | string;\n  };\n}\n\n// Last three path segments — mirrors @threadbase-sh/scanner's\n// `getShortProjectName`. Inlined here because the scanner does not export it.\nfunction shortProjectName(fullPath: string): string {\n  const parts = fullPath.split(/[/\\\\]/).filter(Boolean);\n  return parts.slice(-3).join(\"/\");\n}\n\nfunction normalizeContent(raw: ContentBlock[] | string | null | undefined): ContentBlock[] {\n  if (Array.isArray(raw)) return raw;\n  if (typeof raw === \"string\") return [{ type: \"text\", text: raw }];\n  return [];\n}\n\nconst SCHEMA = `\nCREATE TABLE IF NOT EXISTS conversation_meta (\n  id            TEXT PRIMARY KEY,\n  file_path     TEXT NOT NULL,\n  project_path  TEXT,\n  project_name  TEXT,\n  title         TEXT,\n  model         TEXT,\n  account       TEXT,\n  branch        TEXT,\n  message_count INTEGER DEFAULT 0,\n  last_activity INTEGER,\n  first_message TEXT,\n  last_message  TEXT,\n  preview       TEXT,\n  updated_at    INTEGER NOT NULL\n);\nCREATE INDEX IF NOT EXISTS idx_meta_last_activity ON conversation_meta(last_activity DESC);\nCREATE INDEX IF NOT EXISTS idx_meta_project ON conversation_meta(project_path);\n\nCREATE TABLE IF NOT EXISTS conversation_tail (\n  conversation_id TEXT PRIMARY KEY REFERENCES conversation_meta(id) ON DELETE CASCADE,\n  messages_json   TEXT NOT NULL,\n  tail_size       INTEGER NOT NULL,\n  updated_at      INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS session_names (\n  session_id  TEXT PRIMARY KEY,\n  name        TEXT NOT NULL,\n  updated_at  INTEGER NOT NULL\n);\n`;\n\nexport class ConversationCache {\n  private db: Database.Database;\n  private tailSize: number;\n  private fileIndex = new Map<string, string>();\n  private fileIndexLoaded = false;\n  // Monotonically increasing counter for tail updated_at — guarantees strict\n  // ordering even when multiple updateFromLine() calls land within the same ms.\n  private tailSeq = Date.now();\n  // Monotonically increasing counter for session_names updated_at.\n  private nameSeq = Date.now();\n\n  private stmts: {\n    getById: Database.Statement;\n    getFullById: Database.Statement;\n    updateMeta: Database.Statement;\n    updateMetaBatch: Database.Statement;\n    insertSkeleton: Database.Statement;\n    backfillSkeletonProject: Database.Statement;\n    upsertFull: Database.Statement;\n    getTail: Database.Statement;\n    hasTail: Database.Statement;\n    upsertTail: Database.Statement;\n    list: Database.Statement;\n    count: Database.Statement;\n    listByProject: Database.Statement;\n    countByProject: Database.Statement;\n    listByProvider: Database.Statement;\n    countByProvider: Database.Statement;\n    deleteById: Database.Statement;\n    deleteTailById: Database.Statement;\n    deleteAll: Database.Statement;\n    deleteTailAll: Database.Statement;\n    getIdByFilePath: Database.Statement;\n    allFilePaths: Database.Statement;\n    allFileStats: Database.Statement;\n    upsertSessionName: Database.Statement;\n    getSessionName: Database.Statement;\n    listSessionNames: Database.Statement;\n    setConversationProjectId: Database.Statement;\n    markAsStreamer: Database.Statement;\n    getLatestConversation: Database.Statement;\n    listConversationsForProjectBackfill: Database.Statement;\n    hasOrphanProjectId: Database.Statement;\n    popularProjects: Database.Statement;\n  };\n\n  private migrationsDir?: string;\n\n  // When true, ingestion drops conversations whose JSONL `entrypoint` belongs\n  // to `agentEntrypoints`. See isAgentConversation.ts.\n  private filterAgentConversations = false;\n  private agentEntrypoints: ReadonlySet<string> = DEFAULT_AGENT_ENTRYPOINTS;\n  private onAgentFileDetected?: (filePath: string) => void;\n\n  private constructor(\n    db: Database.Database,\n    tailSize: number,\n    migrationsDir?: string,\n    options?: ConversationCacheOptions,\n  ) {\n    this.migrationsDir = migrationsDir;\n    this.db = db;\n    this.tailSize = tailSize;\n    this.filterAgentConversations = options?.filterAgentConversations ?? false;\n    this.agentEntrypoints = options?.agentEntrypoints ?? DEFAULT_AGENT_ENTRYPOINTS;\n    this.onAgentFileDetected = options?.onAgentFileDetected;\n    db.exec(SCHEMA);\n    runSqliteMigrations(db, this.migrationsDir);\n    this.stmts = {\n      getById: db.prepare(\"SELECT id FROM conversation_meta WHERE id = ?\"),\n      getFullById: db.prepare(\"SELECT * FROM conversation_meta WHERE id = ?\"),\n      updateMeta: db.prepare(\n        \"UPDATE conversation_meta SET message_count = message_count + 1, last_activity = ?, last_message = ?, updated_at = ? WHERE id = ?\",\n      ),\n      // Batch equivalent of updateMeta: bumps message_count by N in one write\n      // (used by updateFromLines so a burst of appended lines is one UPDATE).\n      updateMetaBatch: db.prepare(\n        \"UPDATE conversation_meta SET message_count = message_count + @inc, last_activity = @last_activity, last_message = @last_message, updated_at = @updated_at WHERE id = @id\",\n      ),\n      insertSkeleton: db.prepare(\n        \"INSERT OR IGNORE INTO conversation_meta (id, file_path, message_count, updated_at) VALUES (?, ?, 1, ?)\",\n      ),\n      // Fills project_path / project_name / title on a row whose columns are\n      // still NULL. Never overwrites scanner-populated values — the scanner's\n      // upsertFromScannerMeta remains authoritative for those columns.\n      backfillSkeletonProject: db.prepare(\n        `UPDATE conversation_meta\n         SET project_path = COALESCE(project_path, @project_path),\n             project_name = COALESCE(project_name, @project_name),\n             title        = COALESCE(title,        @title)\n         WHERE id = @id\n           AND (project_path IS NULL OR project_name IS NULL OR title IS NULL)`,\n      ),\n      upsertFull: db.prepare(`\n        INSERT INTO conversation_meta\n          (id, file_path, project_path, project_name, title, model, account, branch,\n           message_count, last_activity, first_message, last_message, preview, updated_at,\n           mtime_ms, file_size, provider)\n        VALUES\n          (@id, @file_path, @project_path, @project_name, @title, @model, @account, @branch,\n           @message_count, @last_activity, @first_message, @last_message, @preview, @updated_at,\n           @mtime_ms, @file_size, @provider)\n        ON CONFLICT(id) DO UPDATE SET\n          file_path     = excluded.file_path,\n          project_path  = excluded.project_path,\n          project_name  = excluded.project_name,\n          title         = excluded.title,\n          model         = excluded.model,\n          account       = excluded.account,\n          branch        = excluded.branch,\n          message_count = excluded.message_count,\n          last_activity = excluded.last_activity,\n          first_message = excluded.first_message,\n          last_message  = excluded.last_message,\n          preview       = excluded.preview,\n          updated_at    = excluded.updated_at,\n          mtime_ms      = excluded.mtime_ms,\n          file_size     = excluded.file_size,\n          provider      = excluded.provider\n        WHERE conversation_meta.updated_at < excluded.updated_at\n      `),\n      getTail: db.prepare(\"SELECT * FROM conversation_tail WHERE conversation_id = ?\"),\n      hasTail: db.prepare(\"SELECT 1 FROM conversation_tail WHERE conversation_id = ? LIMIT 1\"),\n      upsertTail: db.prepare(`\n        INSERT INTO conversation_tail (conversation_id, messages_json, tail_size, updated_at)\n        VALUES (?, ?, ?, ?)\n        ON CONFLICT(conversation_id) DO UPDATE SET\n          messages_json = excluded.messages_json,\n          tail_size     = excluded.tail_size,\n          updated_at    = excluded.updated_at\n        WHERE conversation_tail.updated_at < excluded.updated_at\n      `),\n      list: db.prepare(\n        \"SELECT * FROM conversation_meta ORDER BY last_activity DESC LIMIT ? OFFSET ?\",\n      ),\n      count: db.prepare(\"SELECT COUNT(*) as n FROM conversation_meta\"),\n      listByProject: db.prepare(\n        \"SELECT * FROM conversation_meta WHERE project_path = ? ORDER BY last_activity DESC LIMIT ? OFFSET ?\",\n      ),\n      countByProject: db.prepare(\n        \"SELECT COUNT(*) as n FROM conversation_meta WHERE project_path = ?\",\n      ),\n      listByProvider: db.prepare(\n        \"SELECT * FROM conversation_meta WHERE provider = ? ORDER BY last_activity DESC LIMIT ? OFFSET ?\",\n      ),\n      countByProvider: db.prepare(\"SELECT COUNT(*) as n FROM conversation_meta WHERE provider = ?\"),\n      deleteById: db.prepare(\"DELETE FROM conversation_meta WHERE id = ?\"),\n      deleteTailById: db.prepare(\"DELETE FROM conversation_tail WHERE conversation_id = ?\"),\n      deleteAll: db.prepare(\"DELETE FROM conversation_meta\"),\n      deleteTailAll: db.prepare(\"DELETE FROM conversation_tail\"),\n      getIdByFilePath: db.prepare(\"SELECT id FROM conversation_meta WHERE file_path = ?\"),\n      allFilePaths: db.prepare(\"SELECT id, file_path FROM conversation_meta\"),\n      allFileStats: db.prepare(\n        \"SELECT file_path, mtime_ms, file_size FROM conversation_meta WHERE mtime_ms IS NOT NULL AND file_size IS NOT NULL\",\n      ),\n      upsertSessionName: db.prepare(`\n        INSERT INTO session_names (session_id, name, updated_at)\n        VALUES (?, ?, ?)\n        ON CONFLICT(session_id) DO UPDATE SET\n          name       = excluded.name,\n          updated_at = excluded.updated_at\n        WHERE session_names.updated_at < excluded.updated_at\n      `),\n      getSessionName: db.prepare(\"SELECT name FROM session_names WHERE session_id = ?\"),\n      listSessionNames: db.prepare(\"SELECT session_id, name FROM session_names\"),\n      setConversationProjectId: db.prepare(\n        \"UPDATE conversation_meta SET project_id = ? WHERE id = ?\",\n      ),\n      markAsStreamer: db.prepare(\"UPDATE conversation_meta SET source = 'streamer' WHERE id = ?\"),\n      getLatestConversation: db.prepare(\n        \"SELECT id, last_activity FROM conversation_meta WHERE last_activity IS NOT NULL ORDER BY last_activity DESC, id DESC LIMIT 1\",\n      ),\n      listConversationsForProjectBackfill: db.prepare(\n        \"SELECT id, project_path, project_id, last_activity FROM conversation_meta WHERE project_path IS NOT NULL\",\n      ),\n      hasOrphanProjectId: db.prepare(\n        \"SELECT 1 FROM conversation_meta WHERE project_id IS NULL AND project_path IS NOT NULL LIMIT 1\",\n      ),\n      popularProjects: db.prepare(\n        `SELECT project_path, project_name, COUNT(*) as cnt\n         FROM conversation_meta\n         WHERE project_path IS NOT NULL\n         GROUP BY project_path\n         ORDER BY cnt DESC\n         LIMIT ?`,\n      ),\n    };\n  }\n\n  /**\n   * Expose the underlying handle so projects/cache_metadata repositories can\n   * share the same connection. Internal API; not part of the public surface.\n   */\n  getDatabase(): Database.Database {\n    return this.db;\n  }\n\n  getAgentEntrypoints(): ReadonlySet<string> {\n    return this.agentEntrypoints;\n  }\n\n  static open(\n    dbPath: string,\n    tailSize = 10,\n    migrationsDir?: string,\n    options?: ConversationCacheOptions,\n  ): ConversationCache {\n    mkdirSync(dirname(dbPath), { recursive: true });\n    const db = new Database(dbPath);\n    db.pragma(\"journal_mode = WAL\");\n    db.pragma(\"foreign_keys = ON\");\n    return new ConversationCache(db, tailSize, migrationsDir, options);\n  }\n\n  close(): void {\n    this.db.close();\n  }\n\n  getPopularProjects(limit: number): Array<{ path: string; name: string; sessionCount: number }> {\n    const rows = this.stmts.popularProjects.all(limit) as Array<{\n      project_path: string;\n      project_name: string | null;\n      cnt: number;\n    }>;\n    return rows.map((r) => ({\n      path: r.project_path,\n      name: r.project_name ?? r.project_path.split(/[/\\\\]/).pop() ?? r.project_path,\n      sessionCount: r.cnt,\n    }));\n  }\n\n  private ensureFileIndex(): void {\n    if (this.fileIndexLoaded) return;\n    const rows = this.stmts.allFilePaths.all() as Array<{ id: string; file_path: string }>;\n    for (const row of rows) {\n      this.fileIndex.set(row.file_path, row.id);\n    }\n    this.fileIndexLoaded = true;\n  }\n\n  updateFromLine(filePath: string, rawLine: string): void {\n    let line: JsonlLine;\n    try {\n      line = JSON.parse(rawLine);\n    } catch {\n      return;\n    }\n\n    if (this.filterAgentConversations && isAgentLine(line, this.agentEntrypoints)) {\n      this.deleteByFilePath(filePath);\n      this.onAgentFileDetected?.(filePath);\n      return;\n    }\n\n    const role = line.role ?? line.type;\n    const isMessage = role === \"user\" || role === \"assistant\";\n\n    this.ensureFileIndex();\n\n    // Skip lines that carry neither a message nor project context — there's\n    // nothing for us to record.\n    if (!isMessage && !line.cwd && !line.slug) return;\n\n    let convId = this.fileIndex.get(filePath);\n    if (!convId) {\n      const pseudoId =\n        filePath\n          .split(/[/\\\\]/)\n          .pop()\n          ?.replace(/\\.jsonl$/, \"\") ?? filePath;\n      this.stmts.insertSkeleton.run(pseudoId, filePath, 0);\n      this.fileIndex.set(filePath, pseudoId);\n      convId = pseudoId;\n    }\n\n    // Backfill project_path / project_name / title from cwd on any line that\n    // carries it. COALESCE inside the SQL ensures we never overwrite a\n    // scanner-populated value. Without this, the chokidar watcher leaves\n    // skeleton rows with NULL project context, which renders as blank cards\n    // on the mobile Recents tab.\n    if (line.cwd || line.slug) {\n      const projectPath = line.cwd ?? null;\n      const projectName = projectPath ? shortProjectName(projectPath) : null;\n      const title = line.slug ?? projectName ?? null;\n      this.stmts.backfillSkeletonProject.run({\n        id: convId,\n        project_path: projectPath,\n        project_name: projectName,\n        title,\n      });\n    }\n\n    if (!isMessage) return;\n\n    const timestamp = line.timestamp ?? new Date().toISOString();\n    const activityMs = new Date(timestamp).getTime();\n    if (Number.isNaN(activityMs)) return;\n\n    const contentBlocks = normalizeContent(line.message?.content ?? line.content);\n    const text = contentBlocks.find((b) => b.type === \"text\")?.text?.slice(0, 200) ?? \"\";\n    const lastMessage = JSON.stringify({ role, timestamp, text });\n    const seq = ++this.tailSeq;\n\n    const result = this.stmts.updateMeta.run(activityMs, lastMessage, seq, convId);\n    if (result.changes === 0) return;\n\n    const tailRow = this.stmts.getTail.get(convId) as TailRow | undefined;\n    const msgs: CachedTailMessage[] = tailRow\n      ? (JSON.parse(tailRow.messages_json) as CachedTailMessage[])\n      : [];\n\n    msgs.push({ role, timestamp, text, content: contentBlocks });\n    if (msgs.length > this.tailSize) msgs.splice(0, msgs.length - this.tailSize);\n\n    this.stmts.upsertTail.run(convId, JSON.stringify(msgs), msgs.length, seq);\n  }\n\n  /**\n   * Batched form of updateFromLine: applies a burst of newly-appended lines\n   * (one chokidar read) in a single transaction with one message_count bump,\n   * one meta write, and one tail read/write — instead of 2-4 synchronous\n   * writes per line. Semantics are identical to replaying each line through\n   * updateFromLine in order: the agent filter short-circuits the whole batch,\n   * project context is backfilled last-wins, message_count increases by the\n   * number of surviving message lines, and last_activity/last_message reflect\n   * the final message line.\n   */\n  updateFromLines(filePath: string, rawLines: string[]): void {\n    // Classify all lines first (outside the transaction). A single watched\n    // file maps to one conversation, so we accumulate into scalars.\n    let sawProjectContext = false;\n    let backfillProjectPath: string | null = null;\n    let backfillProjectName: string | null = null;\n    let backfillTitle: string | null = null;\n    let msgCount = 0;\n    let lastActivityMs: number | null = null;\n    let lastMessage: string | null = null;\n    const newTail: CachedTailMessage[] = [];\n\n    for (const rawLine of rawLines) {\n      let line: JsonlLine;\n      try {\n        line = JSON.parse(rawLine);\n      } catch {\n        continue;\n      }\n\n      // The first agent line nukes the file and aborts the whole batch —\n      // matches updateFromLine's per-line return.\n      if (this.filterAgentConversations && isAgentLine(line, this.agentEntrypoints)) {\n        this.deleteByFilePath(filePath);\n        this.onAgentFileDetected?.(filePath);\n        return;\n      }\n\n      const role = line.role ?? line.type;\n      const isMessage = role === \"user\" || role === \"assistant\";\n\n      // Skip lines that carry neither a message nor project context.\n      if (!isMessage && !line.cwd && !line.slug) continue;\n\n      if (line.cwd || line.slug) {\n        sawProjectContext = true;\n        // First-wins per column, mirroring updateFromLine's per-line replay:\n        // backfillSkeletonProject COALESCEs each column independently, so the\n        // first non-null value seen for a column sticks and later lines can't\n        // override it. Accumulating last-wins here would diverge from per-line\n        // replay when a conversation's cwd/slug changes mid-batch.\n        const lineProjectPath = line.cwd ?? null;\n        const lineProjectName = lineProjectPath ? shortProjectName(lineProjectPath) : null;\n        const lineTitle = line.slug ?? lineProjectName ?? null;\n        backfillProjectPath ??= lineProjectPath;\n        backfillProjectName ??= lineProjectName;\n        backfillTitle ??= lineTitle;\n      }\n\n      if (!isMessage) continue;\n\n      const timestamp = line.timestamp ?? new Date().toISOString();\n      const activityMs = new Date(timestamp).getTime();\n      if (Number.isNaN(activityMs)) continue;\n\n      const contentBlocks = normalizeContent(line.message?.content ?? line.content);\n      const text = contentBlocks.find((b) => b.type === \"text\")?.text?.slice(0, 200) ?? \"\";\n      msgCount += 1;\n      lastActivityMs = activityMs;\n      lastMessage = JSON.stringify({ role, timestamp, text });\n      newTail.push({ role, timestamp, text, content: contentBlocks });\n    }\n\n    // Nothing recordable in this batch.\n    if (!sawProjectContext && msgCount === 0) return;\n\n    this.ensureFileIndex();\n\n    let convId = this.fileIndex.get(filePath);\n    if (!convId) {\n      const pseudoId =\n        filePath\n          .split(/[/\\\\]/)\n          .pop()\n          ?.replace(/\\.jsonl$/, \"\") ?? filePath;\n      this.stmts.insertSkeleton.run(pseudoId, filePath, 0);\n      this.fileIndex.set(filePath, pseudoId);\n      convId = pseudoId;\n    }\n    const id = convId;\n\n    const apply = this.db.transaction(() => {\n      if (sawProjectContext) {\n        this.stmts.backfillSkeletonProject.run({\n          id,\n          project_path: backfillProjectPath,\n          project_name: backfillProjectName,\n          title: backfillTitle,\n        });\n      }\n\n      if (msgCount === 0) return;\n\n      const seq = ++this.tailSeq;\n      const result = this.stmts.updateMetaBatch.run({\n        inc: msgCount,\n        last_activity: lastActivityMs,\n        last_message: lastMessage,\n        updated_at: seq,\n        id,\n      });\n      if (result.changes === 0) return;\n\n      const tailRow = this.stmts.getTail.get(id) as TailRow | undefined;\n      const msgs: CachedTailMessage[] = tailRow\n        ? (JSON.parse(tailRow.messages_json) as CachedTailMessage[])\n        : [];\n      msgs.push(...newTail);\n      if (msgs.length > this.tailSize) msgs.splice(0, msgs.length - this.tailSize);\n\n      this.stmts.upsertTail.run(id, JSON.stringify(msgs), msgs.length, seq);\n    });\n    apply();\n  }\n\n  // Returns the IDs of rows actually upserted (i.e. excluding any agent JSONLs\n  // skipped by the filter). The server's warm-up loop uses this to populate\n  // tails only for IDs that have a parent conversation_meta row — otherwise\n  // the conversation_tail FK fires and aborts the warm-up (regression covered\n  // in conversation-cache.test.ts).\n  upsertFromScannerMeta(metas: ScannerMeta[]): string[] {\n    const filter = this.filterAgentConversations;\n    const entrypoints = this.agentEntrypoints;\n    const upsertedIds: string[] = [];\n    const run = this.db.transaction((items: ScannerMeta[]) => {\n      for (const m of items) {\n        if (filter && isAgentFile(m.filePath, entrypoints)) continue;\n        const id =\n          m.sessionId ||\n          m.id\n            .split(\"/\")\n            .pop()\n            ?.replace(/\\.jsonl$/, \"\") ||\n          m.id;\n        const lastActivityMs = m.timestamp ? new Date(m.timestamp).getTime() : null;\n        let mtimeMs: number | null = null;\n        let fileSize: number | null = null;\n        try {\n          const s = statSync(m.filePath);\n          mtimeMs = s.mtimeMs;\n          fileSize = s.size;\n        } catch {\n          // file disappeared between scan and upsert — store without stat\n        }\n        this.stmts.upsertFull.run({\n          id,\n          file_path: m.filePath,\n          project_path: m.projectPath ?? null,\n          project_name: m.projectName ?? null,\n          title: m.title ?? m.projectName ?? null,\n          model: m.model ?? null,\n          account: m.account ?? null,\n          branch: m.gitBranch ?? null,\n          message_count: m.messageCount ?? 0,\n          last_activity: lastActivityMs,\n          first_message: m.firstMessage ? JSON.stringify(m.firstMessage) : null,\n          last_message: m.lastMessage ? JSON.stringify(m.lastMessage) : null,\n          preview: m.preview ?? null,\n          updated_at: 0,\n          mtime_ms: mtimeMs,\n          file_size: fileSize,\n          provider: m.provider ?? CLAUDE_CODE_PROVIDER,\n        });\n        if (this.fileIndexLoaded) this.fileIndex.set(m.filePath, id);\n        upsertedIds.push(id);\n      }\n    });\n    run(metas);\n    return upsertedIds;\n  }\n\n  // Returns true if a row was deleted. Used by the prune-on-startup step and\n  // by updateFromLine when a previously-cached file turns out to be an agent\n  // JSONL.\n  deleteByFilePath(filePath: string): boolean {\n    const row = this.stmts.getIdByFilePath.get(filePath) as { id: string } | undefined;\n    if (!row) return false;\n    this.stmts.deleteTailById.run(row.id);\n    const result = this.stmts.deleteById.run(row.id);\n    this.fileIndex.delete(filePath);\n    return result.changes > 0;\n  }\n\n  // Reads the last `tailSize` qualifying lines from a JSONL file and writes them\n  // to conversation_tail. Reads backward in 8 KB chunks so memory usage is\n  // bounded regardless of file size. Uses updated_at=0 so any live\n  // updateFromLine() call (which uses Date.now()) always wins the upsert.\n  // Returns false if the file cannot be read or the tail already exists.\n  populateTailFromFile(convId: string, filePath: string): boolean {\n    if (this.stmts.hasTail.get(convId)) return false;\n\n    let fileSize: number;\n    let fd: number;\n    try {\n      fileSize = statSync(filePath).size;\n      fd = openSync(filePath, \"r\");\n    } catch {\n      return false;\n    }\n\n    const CHUNK = 8192;\n    const buf = Buffer.allocUnsafe(CHUNK);\n    let pos = fileSize;\n    let partial = \"\";\n    const lines: string[] = [];\n\n    try {\n      while (pos > 0 && lines.length < this.tailSize * 4) {\n        const toRead = Math.min(CHUNK, pos);\n        pos -= toRead;\n        readSync(fd, buf, 0, toRead, pos);\n        const chunk = buf.subarray(0, toRead).toString(\"utf8\");\n        const combined = chunk + partial;\n        const parts = combined.split(\"\\n\");\n        // parts[0] may be a partial line — keep it for the next iteration\n        partial = parts[0];\n        for (let i = parts.length - 1; i >= 1; i--) {\n          lines.push(parts[i]);\n        }\n      }\n      if (partial) lines.push(partial);\n    } finally {\n      closeSync(fd);\n    }\n\n    const msgs: CachedTailMessage[] = [];\n    for (let i = 0; i < lines.length && msgs.length < this.tailSize; i++) {\n      const line = lines[i].trim();\n      if (!line) continue;\n      let parsed: JsonlLine;\n      try {\n        parsed = JSON.parse(line);\n      } catch {\n        continue;\n      }\n      const role = parsed.role ?? parsed.type;\n      if (!role) continue;\n      const timestamp = parsed.timestamp ?? \"\";\n      const contentBlocks = normalizeContent(parsed.message?.content ?? parsed.content);\n      const text = contentBlocks.find((b) => b.type === \"text\")?.text?.slice(0, 200) ?? \"\";\n      msgs.unshift({ role, timestamp, text, content: contentBlocks });\n    }\n    if (msgs.length === 0) return false;\n    this.stmts.upsertTail.run(convId, JSON.stringify(msgs), msgs.length, 0);\n    return true;\n  }\n\n  listConversations(opts: { project?: string; provider?: string; limit: number; offset: number }): {\n    conversations: ConversationListItem[];\n    total: number;\n  } {\n    const { project, provider, limit, offset } = opts;\n    let total: number;\n    let rows: MetaRow[];\n\n    if (project) {\n      total = (this.stmts.countByProject.get(project) as { n: number }).n;\n      rows = limit === 0 ? [] : (this.stmts.listByProject.all(project, limit, offset) as MetaRow[]);\n    } else if (provider) {\n      total = (this.stmts.countByProvider.get(provider) as { n: number }).n;\n      rows =\n        limit === 0 ? [] : (this.stmts.listByProvider.all(provider, limit, offset) as MetaRow[]);\n    } else {\n      total = (this.stmts.count.get() as { n: number }).n;\n      rows = limit === 0 ? [] : (this.stmts.list.all(limit, offset) as MetaRow[]);\n    }\n\n    return {\n      total,\n      conversations: rows.map((r) => ({\n        id: r.id,\n        filePath: r.file_path,\n        projectId: r.project_id,\n        projectPath: r.project_path,\n        projectName: r.project_name,\n        title: r.title,\n        model: r.model,\n        account: r.account,\n        branch: r.branch,\n        messageCount: r.message_count,\n        lastActivity: r.last_activity\n          ? new Date(r.last_activity).toISOString()\n          : new Date(0).toISOString(),\n        firstMessage: r.first_message,\n        lastMessage: r.last_message,\n        preview: r.preview,\n        source: r.source,\n        provider: r.provider ?? CLAUDE_CODE_PROVIDER,\n      })),\n    };\n  }\n\n  /** Returns a map of filePath → { mtimeMs, size } for all rows that have\n   *  stat data stored. Used by the server to build the statCache passed to\n   *  ConversationScanner.scan() so unchanged files are skipped. */\n  getFileStats(): Map<string, { mtimeMs: number; size: number }> {\n    const rows = this.stmts.allFileStats.all() as Array<{\n      file_path: string;\n      mtime_ms: number;\n      file_size: number;\n    }>;\n    const map = new Map<string, { mtimeMs: number; size: number }>();\n    for (const r of rows) {\n      map.set(r.file_path, { mtimeMs: r.mtime_ms, size: r.file_size });\n    }\n    return map;\n  }\n\n  getMetaById(id: string): ConversationListItem | null {\n    const row = this.stmts.getFullById.get(id) as MetaRow | undefined;\n    if (!row) return null;\n    return {\n      id: row.id,\n      filePath: row.file_path,\n      projectId: row.project_id,\n      projectPath: row.project_path,\n      projectName: row.project_name,\n      title: row.title,\n      model: row.model,\n      account: row.account,\n      branch: row.branch,\n      messageCount: row.message_count,\n      lastActivity: row.last_activity\n        ? new Date(row.last_activity).toISOString()\n        : new Date(0).toISOString(),\n      firstMessage: row.first_message,\n      lastMessage: row.last_message,\n      preview: row.preview,\n      source: row.source,\n      provider: row.provider ?? CLAUDE_CODE_PROVIDER,\n    };\n  }\n\n  setConversationProjectId(conversationId: string, projectId: string): void {\n    this.stmts.setConversationProjectId.run(projectId, conversationId);\n  }\n\n  markAsStreamer(id: string): void {\n    this.stmts.markAsStreamer.run(id);\n  }\n\n  getLatestConversation(): { id: string; lastActivity: string | null } | null {\n    const row = this.stmts.getLatestConversation.get() as\n      | { id: string; last_activity: number | null }\n      | undefined;\n    if (!row) return null;\n    return {\n      id: row.id,\n      lastActivity: row.last_activity ? new Date(row.last_activity).toISOString() : null,\n    };\n  }\n\n  hasOrphanProjectId(): boolean {\n    return this.stmts.hasOrphanProjectId.get() !== undefined;\n  }\n\n  listConversationsForProjectBackfill(): Array<{\n    id: string;\n    projectPath: string | null;\n    projectId: string | null;\n    lastActivity: string | null;\n  }> {\n    const rows = this.stmts.listConversationsForProjectBackfill.all() as Array<{\n      id: string;\n      project_path: string | null;\n      project_id: string | null;\n      last_activity: number | null;\n    }>;\n    return rows.map((r) => ({\n      id: r.id,\n      projectPath: r.project_path,\n      projectId: r.project_id,\n      lastActivity: r.last_activity ? new Date(r.last_activity).toISOString() : null,\n    }));\n  }\n\n  getConversationTail(id: string): CachedTail | null {\n    const row = this.stmts.getTail.get(id) as TailRow | undefined;\n    if (!row) return null;\n    return {\n      conversationId: id,\n      messages: JSON.parse(row.messages_json) as CachedTailMessage[],\n      tailSize: row.tail_size,\n    };\n  }\n\n  hasConversation(id: string): boolean {\n    return !!this.stmts.getById.get(id);\n  }\n\n  upsertSessionName(sessionId: string, name: string): void {\n    this.stmts.upsertSessionName.run(sessionId, name, ++this.nameSeq);\n  }\n\n  getSessionName(sessionId: string): string | null {\n    const row = this.stmts.getSessionName.get(sessionId) as { name: string } | undefined;\n    return row?.name ?? null;\n  }\n\n  listSessionNames(): Record<string, string> {\n    const rows = this.stmts.listSessionNames.all() as { session_id: string; name: string }[];\n    return Object.fromEntries(rows.map((r) => [r.session_id, r.name]));\n  }\n\n  invalidate(id?: string): void {\n    if (id) {\n      this.stmts.deleteTailById.run(id);\n      this.stmts.deleteById.run(id);\n      if (this.fileIndexLoaded) {\n        for (const [fp, cid] of this.fileIndex) {\n          if (cid === id) {\n            this.fileIndex.delete(fp);\n            break;\n          }\n        }\n      }\n    } else {\n      this.stmts.deleteTailAll.run();\n      this.stmts.deleteAll.run();\n      this.fileIndex.clear();\n    }\n  }\n\n  invalidateByFilePath(filePath: string): string | null {\n    const row = this.stmts.getIdByFilePath.get(filePath) as { id: string } | undefined;\n    if (!row) return null;\n    this.invalidate(row.id);\n    return row.id;\n  }\n\n  /**\n   * Drop rows whose `file_path` no longer exists on disk AND which have no\n   * cached tail to fall back to. Rows with a tail are left alone so\n   * `handleGetConversation` can still serve the cached tail even when the\n   * JSONL has been deleted.\n   */\n  pruneGhostFiles(exists: (filePath: string) => boolean = existsSync): string[] {\n    const rows = this.stmts.allFilePaths.all() as { id: string; file_path: string }[];\n    const ghosts: string[] = [];\n    const prune = this.db.transaction((ids: string[]) => {\n      for (const id of ids) {\n        this.stmts.deleteTailById.run(id);\n        this.stmts.deleteById.run(id);\n      }\n    });\n    for (const row of rows) {\n      if (exists(row.file_path)) continue;\n      if (this.stmts.hasTail.get(row.id)) continue;\n      ghosts.push(row.id);\n    }\n    if (ghosts.length > 0) {\n      prune(ghosts);\n      if (this.fileIndexLoaded) {\n        for (const id of ghosts) {\n          for (const [fp, cid] of this.fileIndex) {\n            if (cid === id) {\n              this.fileIndex.delete(fp);\n              break;\n            }\n          }\n        }\n      }\n    }\n    return ghosts;\n  }\n}\n","import type Database from \"better-sqlite3\";\nimport { readdirSync, readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nfunction getMigrationsDir(): string {\n  if (typeof import.meta !== \"undefined\" && import.meta.url) {\n    return dirname(fileURLToPath(import.meta.url));\n  }\n  return __dirname;\n}\n\nconst SCHEMA_MIGRATIONS_SQL = `\nCREATE TABLE IF NOT EXISTS schema_migrations (\n  id TEXT PRIMARY KEY,\n  applied_at TEXT NOT NULL\n);\n`;\n\nexport interface SqliteMigrationRunResult {\n  applied: string[];\n  skipped: string[];\n}\n\nexport function runSqliteMigrations(\n  db: Database.Database,\n  migrationsDir?: string,\n): SqliteMigrationRunResult {\n  db.exec(SCHEMA_MIGRATIONS_SQL);\n\n  const dir = migrationsDir ?? join(getMigrationsDir(), \"migrations\");\n  const files = readdirSync(dir)\n    .filter((f) => f.endsWith(\".sql\"))\n    .sort();\n\n  const appliedRows = db.prepare(\"SELECT id FROM schema_migrations\").all() as Array<{ id: string }>;\n  const appliedSet = new Set(appliedRows.map((r) => r.id));\n\n  const recordApplied = db.prepare(\"INSERT INTO schema_migrations (id, applied_at) VALUES (?, ?)\");\n\n  const applied: string[] = [];\n  const skipped: string[] = [];\n\n  for (const file of files) {\n    if (appliedSet.has(file)) {\n      skipped.push(file);\n      continue;\n    }\n    const sql = readFileSync(join(dir, file), \"utf-8\");\n    const tx = db.transaction(() => {\n      db.exec(sql);\n      recordApplied.run(file, new Date().toISOString());\n    });\n    tx();\n    applied.push(file);\n  }\n\n  return { applied, skipped };\n}\n","export const CLAUDE_CODE_PROVIDER = \"claude-code\" as const;\nexport const CODEX_CLI_PROVIDER = \"codex-cli\" as const;\nexport type ProviderName = typeof CLAUDE_CODE_PROVIDER | typeof CODEX_CLI_PROVIDER;\n\n// Codex conversations are read-only — the streamer cannot resume them — so a\n// codex provider forces resumable=false regardless of on-disk availability.\n// claude-code defers to the availability check (project path present, etc.).\nexport function isProviderResumable(\n  provider: string | null | undefined,\n  availabilityResumable: boolean,\n): boolean {\n  if (provider === CODEX_CLI_PROVIDER) return false;\n  return availabilityResumable;\n}\n","import { closeSync, openSync, readSync, statSync } from \"fs\";\n\n// Default agent entrypoints. Override via THREADBASE_AGENT_ENTRYPOINTS\n// (comma-separated). Interactive Claude Code emits entrypoint=\"cli\" and is\n// never in this set.\n//   - sdk-cli      → Claude Agent SDK, claude-mem, hook-spawned automation\n//   - claude-vscode → VS Code extension when invoked headlessly (memory\n//                     summarizers, etc.). Real interactive VS Code sessions\n//                     also use this value, so toggling via env var lets users\n//                     keep them visible if they want.\nexport const DEFAULT_AGENT_ENTRYPOINTS: ReadonlySet<string> = new Set([\"sdk-cli\", \"claude-vscode\"]);\n\n// Chunked scan: read 64 KB at a time with early-exit. We look for the first\n// `\"entrypoint\":` occurrence in the file:\n//   - matches an agent marker → return true\n//   - matches some other entrypoint value (e.g. \"cli\") → return false\n//   - never appears → return false\n// The entrypoint is fixed per-conversation, so the first occurrence is\n// authoritative. This keeps both agent and human files fast (typical first\n// hit is within the first chunk), while still tolerating long housekeeping\n// prefixes (observed agent markers as deep as 2.3 MB in 4.5 MB observer files).\nconst CHUNK_BYTES = 64 * 1024;\nconst ENTRYPOINT_PROBE = `\"entrypoint\":`;\n// Overlap consecutive chunks so a marker that straddles the boundary is still\n// found. The longest entrypoint we look for is ~24 chars; 64 bytes overlap is\n// plenty.\nconst CHUNK_OVERLAP = 64;\n\n// Per-file decision cache, keyed by `${filePath}::${sortedEntrypointsKey}` so\n// changing the set invalidates entries. Filled lazily; cleared on restart.\nconst fileDecisionCache = new Map<string, boolean>();\n\nfunction markersFor(entrypoints: ReadonlySet<string>): string[] {\n  return [...entrypoints].map((e) => `\"entrypoint\":\"${e}\"`);\n}\n\nfunction cacheKey(filePath: string, entrypoints: ReadonlySet<string>): string {\n  return `${filePath}::${[...entrypoints].sort().join(\",\")}`;\n}\n\nexport function isAgentLine(\n  line: { entrypoint?: string },\n  entrypoints: ReadonlySet<string> = DEFAULT_AGENT_ENTRYPOINTS,\n): boolean {\n  return line.entrypoint !== undefined && entrypoints.has(line.entrypoint);\n}\n\nexport function isAgentFile(\n  filePath: string,\n  entrypoints: ReadonlySet<string> = DEFAULT_AGENT_ENTRYPOINTS,\n): boolean {\n  if (entrypoints.size === 0) return false;\n  const key = cacheKey(filePath, entrypoints);\n  const cached = fileDecisionCache.get(key);\n  if (cached !== undefined) return cached;\n\n  let fd: number;\n  try {\n    fd = openSync(filePath, \"r\");\n  } catch {\n    return false;\n  }\n\n  try {\n    const fileSize = statSync(filePath).size;\n    if (fileSize === 0) {\n      fileDecisionCache.set(key, false);\n      return false;\n    }\n\n    const markers = markersFor(entrypoints);\n    const buf = Buffer.allocUnsafe(CHUNK_BYTES);\n    let offset = 0;\n    let carry = \"\";\n\n    while (offset < fileSize) {\n      const toRead = Math.min(CHUNK_BYTES, fileSize - offset);\n      const got = readSync(fd, buf, 0, toRead, offset);\n      if (got <= 0) break;\n      const chunk = carry + buf.toString(\"utf8\", 0, got);\n\n      // Agent match wins.\n      for (const marker of markers) {\n        if (chunk.includes(marker)) {\n          fileDecisionCache.set(key, true);\n          return true;\n        }\n      }\n\n      // If we see ANY entrypoint field, it's per-conversation and stable —\n      // since none of the agent markers matched, this is a non-agent file.\n      // Stops 11 MB human JSONLs from being read in full.\n      if (chunk.includes(ENTRYPOINT_PROBE)) {\n        fileDecisionCache.set(key, false);\n        return false;\n      }\n\n      carry = chunk.slice(-CHUNK_OVERLAP);\n      offset += got;\n    }\n\n    fileDecisionCache.set(key, false);\n    return false;\n  } catch {\n    return false;\n  } finally {\n    closeSync(fd);\n  }\n}\n\n// Comma-separated parser. Empty string → empty set (agents shown in practice\n// even when includeAgents=false, since no entrypoint qualifies).\nexport function parseAgentEntrypointsEnv(raw: string | undefined): ReadonlySet<string> {\n  if (raw === undefined) return DEFAULT_AGENT_ENTRYPOINTS;\n  const parts = raw\n    .split(\",\")\n    .map((s) => s.trim())\n    .filter((s) => s.length > 0);\n  return new Set(parts);\n}\n\nexport function clearAgentFileCacheForTests(): void {\n  fileDecisionCache.clear();\n}\n","import type Database from \"better-sqlite3\";\n\nexport type CacheMetadataKey =\n  | \"last_conversation_id\"\n  | \"last_conversation_created_at\"\n  | \"projects_last_indexed_at\"\n  | \"conversations_last_indexed_at\"\n  | \"conversations_dirty\";\n\nexport class CacheMetadataRepository {\n  private get: Database.Statement;\n  private upsert: Database.Statement;\n  private del: Database.Statement;\n\n  constructor(db: Database.Database) {\n    this.get = db.prepare(\"SELECT value FROM cache_metadata WHERE key = ?\");\n    this.upsert = db.prepare(`\n      INSERT INTO cache_metadata (key, value, updated_at)\n      VALUES (?, ?, ?)\n      ON CONFLICT(key) DO UPDATE SET\n        value      = excluded.value,\n        updated_at = excluded.updated_at\n    `);\n    this.del = db.prepare(\"DELETE FROM cache_metadata WHERE key = ?\");\n  }\n\n  getCacheMetadata(key: CacheMetadataKey): string | null {\n    const row = this.get.get(key) as { value: string } | undefined;\n    return row?.value ?? null;\n  }\n\n  setCacheMetadata(key: CacheMetadataKey, value: string): void {\n    this.upsert.run(key, value, new Date().toISOString());\n  }\n\n  deleteCacheMetadata(key: CacheMetadataKey): void {\n    this.del.run(key);\n  }\n}\n","import type { ConversationCache } from \"../../conversation-cache\";\n\n/**\n * Thin repository wrapper around ConversationCache for the project-id flow.\n * The cache is the source of truth for conversation rows; this just exposes\n * a stable, repo-style API for services that don't want to know about the\n * cache class directly.\n */\nexport class ConversationsRepository {\n  constructor(private cache: ConversationCache) {}\n\n  updateConversationProjectId(args: { conversationId: string; projectId: string }): void {\n    this.cache.setConversationProjectId(args.conversationId, args.projectId);\n  }\n\n  listConversationsForProjectBackfill() {\n    return this.cache.listConversationsForProjectBackfill();\n  }\n\n  getLatestConversation() {\n    return this.cache.getLatestConversation();\n  }\n\n  hasOrphanRows(): boolean {\n    return this.cache.hasOrphanProjectId();\n  }\n}\n","import type Database from \"better-sqlite3\";\nimport { randomUUID } from \"crypto\";\nimport type { Project } from \"../../schemas/project.schema\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\n\ninterface ProjectRow {\n  id: string;\n  path: string;\n  name: string | null;\n  last_conversation_id: string | null;\n  last_conversation_created_at: string | null;\n  last_indexed_at: string | null;\n  latest_message_at: string | null;\n  latest_message_id: string | null;\n  message_count: number;\n  created_at: string;\n  updated_at: string;\n}\n\nfunction rowToProject(row: ProjectRow): Project {\n  return {\n    id: row.id,\n    path: row.path,\n    name: row.name,\n    lastConversationId: row.last_conversation_id,\n    lastConversationCreatedAt: row.last_conversation_created_at,\n    lastIndexedAt: row.last_indexed_at,\n    latestMessageAt: row.latest_message_at,\n    latestMessageId: row.latest_message_id,\n    messageCount: row.message_count,\n    createdAt: row.created_at,\n    updatedAt: row.updated_at,\n  };\n}\n\nexport interface UpsertProjectInput {\n  lastConversationId?: string | null;\n  lastConversationCreatedAt?: string | null;\n  latestMessageAt?: string | null;\n  latestMessageId?: string | null;\n  name?: string | null;\n}\n\nexport class ProjectsRepository {\n  private getByPath: Database.Statement;\n  private getById: Database.Statement;\n  private listAll: Database.Statement;\n  private insert: Database.Statement;\n  private update: Database.Statement;\n\n  constructor(db: Database.Database) {\n    this.getByPath = db.prepare(\"SELECT * FROM projects WHERE path = ?\");\n    this.getById = db.prepare(\"SELECT * FROM projects WHERE id = ?\");\n    this.listAll = db.prepare(\"SELECT * FROM projects ORDER BY updated_at DESC\");\n    this.insert = db.prepare(`\n      INSERT INTO projects (\n        id, path, name,\n        last_conversation_id, last_conversation_created_at, last_indexed_at,\n        latest_message_at, latest_message_id, message_count,\n        created_at, updated_at\n      ) VALUES (\n        @id, @path, @name,\n        @last_conversation_id, @last_conversation_created_at, @last_indexed_at,\n        @latest_message_at, @latest_message_id, @message_count,\n        @created_at, @updated_at\n      )\n    `);\n    this.update = db.prepare(`\n      UPDATE projects SET\n        name                         = COALESCE(@name, name),\n        last_conversation_id         = COALESCE(@last_conversation_id, last_conversation_id),\n        last_conversation_created_at = COALESCE(@last_conversation_created_at, last_conversation_created_at),\n        last_indexed_at              = COALESCE(@last_indexed_at, last_indexed_at),\n        latest_message_at            = COALESCE(@latest_message_at, latest_message_at),\n        latest_message_id            = COALESCE(@latest_message_id, latest_message_id),\n        updated_at                   = @updated_at\n      WHERE id = @id\n    `);\n  }\n\n  getProjectByPath(rawPath: string): Project | null {\n    const path = canonicalizeProjectPath(rawPath);\n    const row = this.getByPath.get(path) as ProjectRow | undefined;\n    return row ? rowToProject(row) : null;\n  }\n\n  getProjectById(id: string): Project | null {\n    const row = this.getById.get(id) as ProjectRow | undefined;\n    return row ? rowToProject(row) : null;\n  }\n\n  listProjects(): Project[] {\n    return (this.listAll.all() as ProjectRow[]).map(rowToProject);\n  }\n\n  /**\n   * Insert a project at the given canonical path, or update its metadata\n   * if one already exists. Returns the persisted Project row.\n   *\n   * Idempotent: passing the same path twice returns the same project id.\n   */\n  upsertProjectByPath(rawPath: string, input: UpsertProjectInput = {}): Project {\n    const path = canonicalizeProjectPath(rawPath);\n    const now = new Date().toISOString();\n\n    const existing = this.getByPath.get(path) as ProjectRow | undefined;\n    if (existing) {\n      this.update.run({\n        id: existing.id,\n        name: input.name ?? null,\n        last_conversation_id: input.lastConversationId ?? null,\n        last_conversation_created_at: input.lastConversationCreatedAt ?? null,\n        last_indexed_at: now,\n        latest_message_at: input.latestMessageAt ?? null,\n        latest_message_id: input.latestMessageId ?? null,\n        updated_at: now,\n      });\n      return rowToProject(this.getById.get(existing.id) as ProjectRow);\n    }\n\n    const id = randomUUID();\n    this.insert.run({\n      id,\n      path,\n      name: input.name ?? deriveNameFromPath(path),\n      last_conversation_id: input.lastConversationId ?? null,\n      last_conversation_created_at: input.lastConversationCreatedAt ?? null,\n      last_indexed_at: now,\n      latest_message_at: input.latestMessageAt ?? null,\n      latest_message_id: input.latestMessageId ?? null,\n      message_count: 0,\n      created_at: now,\n      updated_at: now,\n    });\n    return rowToProject(this.getById.get(id) as ProjectRow);\n  }\n}\n\nfunction deriveNameFromPath(path: string): string | null {\n  const parts = path.split(/[\\\\/]/).filter(Boolean);\n  return parts.length > 0 ? parts[parts.length - 1] : null;\n}\n","/**\n * Canonicalize a project path so the same project always dedupes to the\n * same key, regardless of trailing slashes or surrounding whitespace.\n *\n * Rules:\n *   - Trim surrounding whitespace\n *   - Remove trailing forward or back slashes (one or more)\n *\n * Do NOT lowercase: project paths can be case-sensitive on Linux/macOS\n * and lowercasing them would silently merge two distinct real projects.\n */\nexport function canonicalizeProjectPath(projectPath: string): string {\n  return projectPath.trim().replace(/[\\\\/]+$/, \"\");\n}\n","import type { SessionStore } from \"../../session-store\";\n\n/**\n * Sessions live in-memory in SessionStore (Postgres-backed persistence\n * was dropped per the SQLite-only direction). This repository wraps the\n * store with a stable, project-id-aware API for the new services.\n */\nexport class SessionsRepository {\n  constructor(private store: SessionStore) {}\n\n  updateSessionProjectId(args: { sessionId: string; projectId: string }): void {\n    this.store.updateManaged(args.sessionId, { projectId: args.projectId });\n  }\n\n  listManagedSessions() {\n    return this.store.listManaged();\n  }\n}\n","import type pg from \"pg\";\n\nexport interface UploadRecord {\n  id: string;\n  sessionId: string;\n  filePath: string;\n  originalName: string;\n  mimeType: string;\n  sizeBytes: number;\n}\n\nexport async function recordUpload(\n  pool: pg.Pool | null,\n  instanceId: string | null,\n  row: UploadRecord,\n): Promise<void> {\n  if (!pool) return;\n  await pool.query(\n    `INSERT INTO session_uploads\n       (id, session_id, instance_id, file_path, original_name, mime_type, size_bytes)\n     VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n    [\n      row.id,\n      row.sessionId,\n      instanceId,\n      row.filePath,\n      row.originalName,\n      row.mimeType,\n      row.sizeBytes,\n    ],\n  );\n}\n","import { readdirSync, statSync } from \"fs\";\nimport type { ServerResponse } from \"http\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\n\nfunction decodeProjectPath(dirName: string): string {\n  // Claude encodes project paths by replacing '/' with '-', starting from root.\n  // e.g. '-Users-foo-Desktop-dev-myproject' → '/Users/foo/Desktop/dev/myproject'\n  return dirName.replace(/-/g, \"/\");\n}\n\nexport function handleListProjects(url: URL, res: ServerResponse): void {\n  const limit = Math.max(1, parseInt(url.searchParams.get(\"limit\") ?? \"50\", 10) || 50);\n  const offset = Math.max(0, parseInt(url.searchParams.get(\"offset\") ?? \"0\", 10) || 0);\n\n  const projectsDir = join(homedir(), \".claude\", \"projects\");\n\n  let entries: Array<{ name: string; path: string; dirName: string; mtime: number }>;\n  try {\n    entries = readdirSync(projectsDir)\n      .map((dirName) => {\n        const fullPath = join(projectsDir, dirName);\n        let mtime = 0;\n        try {\n          mtime = statSync(fullPath).mtimeMs;\n        } catch {\n          // ignore stat errors — directory may have been removed\n        }\n        const path = decodeProjectPath(dirName);\n        const name = path.split(\"/\").filter(Boolean).pop() ?? dirName;\n        return { name, path, dirName, mtime };\n      })\n      .sort((a, b) => b.mtime - a.mtime);\n  } catch {\n    res.writeHead(200, { \"Content-Type\": \"application/json\" });\n    res.end(JSON.stringify({ projects: [], total: 0 }));\n    return;\n  }\n\n  const total = entries.length;\n  const page = entries\n    .slice(offset, offset + limit)\n    .map(({ name, path, dirName }) => ({ name, path, dirName }));\n\n  res.writeHead(200, { \"Content-Type\": \"application/json\" });\n  res.end(JSON.stringify({ projects: page, total }));\n}\n","import { randomBytes } from \"crypto\";\n\nexport interface PairTokenRecord {\n  token: string;\n  expiresAt: number;\n  used: boolean;\n}\n\nexport interface MintResult {\n  token: string;\n  expiresAt: number;\n  expiresInSeconds: number;\n}\n\nexport interface ConsumeOk {\n  ok: true;\n}\n\nexport interface ConsumeErr {\n  ok: false;\n  reason: \"unknown\" | \"expired\" | \"used\";\n}\n\nexport type ConsumeResult = ConsumeOk | ConsumeErr;\n\nconst DEFAULT_TTL_SECONDS = 180;\nconst SWEEP_INTERVAL_MS = 60_000;\n\nexport class PairTokenStore {\n  private current: PairTokenRecord | null = null;\n  private readonly ttlMs: number;\n  private sweepTimer: ReturnType<typeof setInterval> | null = null;\n\n  constructor(opts: { ttlSeconds?: number; autoSweep?: boolean } = {}) {\n    this.ttlMs = (opts.ttlSeconds ?? DEFAULT_TTL_SECONDS) * 1000;\n    if (opts.autoSweep !== false) {\n      this.sweepTimer = setInterval(() => this.sweep(), SWEEP_INTERVAL_MS);\n      this.sweepTimer.unref?.();\n    }\n  }\n\n  mint(): MintResult {\n    const token = `pt_${randomBytes(16).toString(\"hex\")}`;\n    const expiresAt = Date.now() + this.ttlMs;\n    this.current = { token, expiresAt, used: false };\n    return {\n      token,\n      expiresAt,\n      expiresInSeconds: Math.floor(this.ttlMs / 1000),\n    };\n  }\n\n  consume(token: string): ConsumeResult {\n    const record = this.current;\n    if (!record || record.token !== token) return { ok: false, reason: \"unknown\" };\n    if (Date.now() > record.expiresAt) {\n      this.current = null;\n      return { ok: false, reason: \"expired\" };\n    }\n    if (record.used) return { ok: false, reason: \"used\" };\n    record.used = true;\n    return { ok: true };\n  }\n\n  peek(): PairTokenRecord | null {\n    return this.current;\n  }\n\n  clear(): void {\n    this.current = null;\n  }\n\n  sweep(): void {\n    if (this.current && Date.now() > this.current.expiresAt) {\n      this.current = null;\n    }\n  }\n\n  dispose(): void {\n    if (this.sweepTimer) clearInterval(this.sweepTimer);\n    this.sweepTimer = null;\n    this.current = null;\n  }\n}\n","import nacl from \"tweetnacl\";\nimport naclUtil from \"tweetnacl-util\";\n\nexport interface SealedPayload {\n  ciphertext: string;\n  nonce: string;\n  ephemeralPublicKey: string;\n}\n\n/**\n * Encrypts a plaintext to a recipient's X25519 public key using NaCl box with\n * an ephemeral sender keypair. The phone holds the recipient private key in\n * its own memory and decrypts with nacl.box.open(ciphertext, nonce, ephemeralPublicKey, recipientPrivateKey).\n *\n * The wire format is custom (not libsodium crypto_box_seal) but cryptographically equivalent.\n */\nexport function seal(plaintext: string, recipientPublicKeyBase64: string): SealedPayload {\n  const recipientPk = naclUtil.decodeBase64(recipientPublicKeyBase64);\n  if (recipientPk.length !== nacl.box.publicKeyLength) {\n    throw new Error(\n      `clientPublicKey must be ${nacl.box.publicKeyLength} bytes (got ${recipientPk.length})`,\n    );\n  }\n  const ephemeral = nacl.box.keyPair();\n  const nonce = nacl.randomBytes(nacl.box.nonceLength);\n  const message = naclUtil.decodeUTF8(plaintext);\n  const cipher = nacl.box(message, nonce, recipientPk, ephemeral.secretKey);\n  return {\n    ciphertext: naclUtil.encodeBase64(cipher),\n    nonce: naclUtil.encodeBase64(nonce),\n    ephemeralPublicKey: naclUtil.encodeBase64(ephemeral.publicKey),\n  };\n}\n","import chokidar, { type FSWatcher } from \"chokidar\";\nimport { statSync } from \"fs\";\nimport { open, stat } from \"fs/promises\";\n\nexport interface ConversationWatcherEvents {\n  /** Fires once per new newline-terminated line appended to a watched file. */\n  onNewLine?: (filePath: string, line: string) => void;\n  /**\n   * Fires once per chokidar read with ALL new lines from that read, batched.\n   * When set, it REPLACES the per-line onNewLine dispatch for that file —\n   * callers pick one. Lets a burst of appended lines collapse into a single\n   * downstream cache write + WebSocket broadcast.\n   */\n  onNewLines?: (filePath: string, lines: string[]) => void;\n  /** Fires when chokidar reports an add/change/unlink at the directory level. */\n  onConversationChanged?: (filePath: string) => void | Promise<void>;\n  /** Fires when a tailed file is deleted (per-file watcher unlink event). */\n  onFileDeleted?: (filePath: string) => void;\n  /** Reported errors per file. */\n  onError?: (filePath: string, error: Error) => void;\n}\n\ninterface WatchedFile {\n  watcher: FSWatcher;\n  offset: number;\n  // Async-read re-entrancy guard: `reading` is set while a readNewLines is in\n  // flight; `pending` records that a change arrived during that read so the\n  // in-flight loop re-runs once more after it finishes.\n  reading: boolean;\n  pending: boolean;\n}\n\n/**\n * Chokidar-backed replacement for src/file-watcher.ts.\n *\n *   - watch(filePath)      → tail a single JSONL file, emitting onNewLine\n *                            for each appended line.\n *   - watchDirectory(dir)  → mark cache dirty on add/change/unlink events\n *                            for any file inside the directory.\n *\n * Per the refactor plan, file watching is an OPTIMIZATION; correctness\n * still relies on refresh=1 / the latest HDD conversation id check.\n */\nexport class ConversationWatcher {\n  private files = new Map<string, WatchedFile>();\n  private directories = new Map<string, FSWatcher>();\n  private onNewLine: ConversationWatcherEvents[\"onNewLine\"];\n  private onNewLines: ConversationWatcherEvents[\"onNewLines\"];\n  private onConversationChanged: ConversationWatcherEvents[\"onConversationChanged\"];\n  private onFileDeleted: ConversationWatcherEvents[\"onFileDeleted\"];\n  private onError: ConversationWatcherEvents[\"onError\"];\n\n  constructor(events: ConversationWatcherEvents = {}) {\n    this.onNewLine = events.onNewLine;\n    this.onNewLines = events.onNewLines;\n    this.onConversationChanged = events.onConversationChanged;\n    this.onFileDeleted = events.onFileDeleted;\n    this.onError = events.onError;\n  }\n\n  watch(filePath: string): void {\n    if (this.files.has(filePath)) return;\n\n    let offset: number;\n    try {\n      offset = statSync(filePath).size;\n    } catch {\n      offset = 0;\n    }\n\n    const watcher = chokidar.watch(filePath, {\n      ignoreInitial: true,\n      awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 },\n    });\n\n    watcher.on(\"change\", () => {\n      void this.readNewLines(filePath);\n    });\n    watcher.on(\"add\", () => {\n      void this.readNewLines(filePath);\n    });\n    watcher.on(\"unlink\", () => this.onFileDeleted?.(filePath));\n    watcher.on(\"error\", (err) => {\n      const error = err instanceof Error ? err : new Error(String(err));\n      this.onError?.(filePath, error);\n    });\n\n    this.files.set(filePath, { watcher, offset, reading: false, pending: false });\n  }\n\n  unwatch(filePath: string): void {\n    const entry = this.files.get(filePath);\n    if (!entry) return;\n    void entry.watcher.close();\n    this.files.delete(filePath);\n  }\n\n  /**\n   * Watch a directory of conversation JSONL files. Fires\n   * onConversationChanged for any add/change/unlink event so the caller\n   * can mark the cache dirty without scanning everything immediately.\n   */\n  watchDirectory(directory: string): void {\n    if (this.directories.has(directory)) return;\n    const watcher = chokidar.watch(directory, {\n      ignoreInitial: true,\n      awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },\n    });\n    const fire = (filePath: string) => {\n      void Promise.resolve(this.onConversationChanged?.(filePath)).catch(() => {\n        // best-effort\n      });\n    };\n    watcher.on(\"add\", fire);\n    watcher.on(\"change\", fire);\n    watcher.on(\"unlink\", fire);\n    watcher.on(\"error\", (err) => {\n      const error = err instanceof Error ? err : new Error(String(err));\n      this.onError?.(directory, error);\n    });\n    this.directories.set(directory, watcher);\n  }\n\n  unwatchDirectory(directory: string): void {\n    const watcher = this.directories.get(directory);\n    if (!watcher) return;\n    void watcher.close();\n    this.directories.delete(directory);\n  }\n\n  dispose(): void {\n    for (const [path] of this.files) this.unwatch(path);\n    for (const [dir] of this.directories) this.unwatchDirectory(dir);\n  }\n\n  private async readNewLines(filePath: string): Promise<void> {\n    const entry = this.files.get(filePath);\n    if (!entry) return;\n\n    // Coalesce: if a read is already running, flag that another change arrived;\n    // the in-flight loop will pick up the freshly-appended bytes before exiting.\n    if (entry.reading) {\n      entry.pending = true;\n      return;\n    }\n    entry.reading = true;\n\n    try {\n      // Loop so bytes appended during a read (or while dispatching) are caught\n      // without re-entering — preserves offset correctness under async I/O.\n      for (;;) {\n        const st = await stat(filePath);\n        // size <= offset also covers truncation/rotation, exactly as before.\n        if (st.size <= entry.offset) break;\n\n        const readFrom = entry.offset;\n        const bytesToRead = st.size - readFrom;\n        const buf = Buffer.alloc(bytesToRead);\n        const fh = await open(filePath, \"r\");\n        try {\n          await fh.read(buf, 0, bytesToRead, readFrom);\n        } finally {\n          await fh.close();\n        }\n        // Advance by exactly what we read (NOT a re-stat'd size): the file may\n        // have grown again mid-read; the next loop iteration's stat catches it.\n        // This reproduces the old `offset = stat.size` semantics, including the\n        // drop of a trailing partial line (no \\n yet) via filter(Boolean).\n        entry.offset = readFrom + bytesToRead;\n\n        // The watcher may have been closed while we awaited; don't emit then.\n        if (!this.files.has(filePath)) return;\n\n        const lines = buf.toString(\"utf-8\").split(\"\\n\").filter(Boolean);\n        if (this.onNewLines) {\n          this.onNewLines(filePath, lines);\n        } else {\n          for (const line of lines) this.onNewLine?.(filePath, line);\n        }\n\n        if (entry.pending) {\n          entry.pending = false;\n          continue;\n        }\n        break;\n      }\n    } catch (err) {\n      this.onError?.(filePath, err instanceof Error ? err : new Error(String(err)));\n    } finally {\n      entry.reading = false;\n      // If a change arrived while this read was in flight but the loop exited\n      // before consuming it (e.g. an error broke out of the loop), the pending\n      // bytes would otherwise sit unread until the next write. Re-arm once so\n      // they're picked up — guarded by files.has so a disposed watcher stays put.\n      if (entry.pending && this.files.has(filePath)) {\n        entry.pending = false;\n        void this.readNewLines(filePath);\n      }\n    }\n  }\n}\n","import { existsSync } from \"fs\";\nimport type { ConversationCache } from \"../../conversation-cache\";\nimport { isAgentFile } from \"./isAgentConversation\";\n\nexport interface PruneAgentConversationsResult {\n  scanned: number;\n  pruned: number;\n  missing: number;\n}\n\n// Walks conversation_meta once at startup and deletes any row whose JSONL has\n// the sdk-cli marker. Idempotent. Safe to call on every boot — the second run\n// will simply find nothing to delete.\nexport function pruneAgentConversations(cache: ConversationCache): PruneAgentConversationsResult {\n  const db = cache.getDatabase();\n  const rows = db.prepare(\"SELECT id, file_path FROM conversation_meta\").all() as Array<{\n    id: string;\n    file_path: string;\n  }>;\n\n  let pruned = 0;\n  let missing = 0;\n  for (const row of rows) {\n    if (!existsSync(row.file_path)) {\n      missing += 1;\n      continue;\n    }\n    if (isAgentFile(row.file_path, cache.getAgentEntrypoints())) {\n      cache.deleteByFilePath(row.file_path);\n      pruned += 1;\n    }\n  }\n\n  return { scanned: rows.length, pruned, missing };\n}\n","/**\n * Resolve a user-visible title for a ProjectChat row.\n *\n * Mobile (ConversationListItem.tsx) renders `title?.trim() || pathSuffix ||\n * ''`, so when title and projectPath are both blank the row appears empty.\n * This helper produces a non-empty title for every input by falling back\n * through title → slug-ish projectName → path suffix → short id.\n */\nexport function deriveProjectChatTitle(input: {\n  title: string | null | undefined;\n  projectName: string | null | undefined;\n  projectPath: string | null | undefined;\n  id: string;\n}): string {\n  const trimmed = input.title?.trim();\n  if (trimmed) return trimmed;\n  const name = input.projectName?.trim();\n  if (name) return name;\n  const pathSuffix = input.projectPath\n    ? input.projectPath.split(/[/\\\\]/).filter(Boolean).slice(-2).join(\"/\")\n    : \"\";\n  if (pathSuffix) return pathSuffix;\n  return `Untitled · ${input.id.slice(0, 8)}`;\n}\n","import type { AskOption, AskQuestion } from \"../../types\";\n\ntype ContentBlock = {\n  type: string;\n  name?: string;\n  id?: string;\n  input?: unknown;\n  [key: string]: unknown;\n};\n\ninterface JsonlLineShape {\n  content?: ContentBlock[] | string;\n  message?: { content?: ContentBlock[] | string };\n}\n\nfunction normalizeContent(raw: ContentBlock[] | string | null | undefined): ContentBlock[] {\n  if (Array.isArray(raw)) return raw;\n  if (typeof raw === \"string\") return [{ type: \"text\", text: raw }];\n  return [];\n}\n\nfunction coerceOptions(raw: unknown): AskOption[] | null {\n  if (!Array.isArray(raw)) return null;\n  const out: AskOption[] = [];\n  for (const o of raw) {\n    if (o && typeof o === \"object\" && typeof (o as AskOption).label === \"string\") {\n      const opt = o as { label: string; description?: unknown; preview?: unknown };\n      out.push({\n        label: opt.label,\n        description: typeof opt.description === \"string\" ? opt.description : \"\",\n        ...(typeof opt.preview === \"string\" ? { preview: opt.preview } : {}),\n      });\n    }\n  }\n  return out.length > 0 ? out : null;\n}\n\nfunction coerceQuestions(raw: unknown): AskQuestion[] | null {\n  if (!Array.isArray(raw)) return null;\n  const out: AskQuestion[] = [];\n  for (const q of raw) {\n    if (!q || typeof q !== \"object\") continue;\n    const qq = q as {\n      question?: unknown;\n      header?: unknown;\n      multiSelect?: unknown;\n      options?: unknown;\n    };\n    const options = coerceOptions(qq.options);\n    if (typeof qq.question !== \"string\" || !options) continue;\n    out.push({\n      question: qq.question,\n      header: typeof qq.header === \"string\" ? qq.header : \"\",\n      multiSelect: qq.multiSelect === true,\n      options,\n    });\n  }\n  return out.length > 0 ? out : null;\n}\n\nexport function detectAskUserQuestion(\n  rawLine: string,\n): { toolUseId: string; questions: AskQuestion[] } | null {\n  let parsed: JsonlLineShape;\n  try {\n    parsed = JSON.parse(rawLine) as JsonlLineShape;\n  } catch {\n    return null;\n  }\n  const blocks = normalizeContent(parsed.message?.content ?? parsed.content);\n  for (const b of blocks) {\n    if (b.type === \"tool_use\" && b.name === \"AskUserQuestion\" && typeof b.id === \"string\") {\n      const input = b.input as { questions?: unknown } | undefined;\n      const questions = coerceQuestions(input?.questions);\n      if (questions) return { toolUseId: b.id, questions };\n    }\n  }\n  return null;\n}\n","import type { AskQuestion, WSMessage } from \"../../types\";\nimport { detectAskUserQuestion } from \"./detectAskUserQuestion\";\n\nexport interface PendingQuestion {\n  toolUseId: string;\n  questions: AskQuestion[];\n}\n\n// Pure decision: given the new lines for a session, returns the question messages\n// to broadcast and the pending-question records to store. No I/O.\nexport function questionsFromLines(\n  sessionId: string,\n  lines: string[],\n): {\n  messages: Extract<WSMessage, { type: \"question\" }>[];\n  pending: PendingQuestion[];\n} {\n  const messages: Extract<WSMessage, { type: \"question\" }>[] = [];\n  const pending: PendingQuestion[] = [];\n  for (const line of lines) {\n    const detected = detectAskUserQuestion(line);\n    if (detected) {\n      messages.push({\n        type: \"question\",\n        sessionId,\n        toolUseId: detected.toolUseId,\n        questions: detected.questions,\n      });\n      pending.push(detected);\n    }\n  }\n  return { messages, pending };\n}\n\n// Pure decision: should the JSONL-flush path (re-)broadcast this question?\n//\n// The live-screen detector may have already broadcast the same question under a\n// synthetic `screen:…` toolUseId. When that JSONL line lands with the REAL\n// toolUseId we must re-broadcast — even though the content is identical — so the\n// client swaps the stale screen id for the real one. Otherwise the client\n// answers with the screen id and resolveAnswer rejects it as tool_use_mismatch.\n//\n// - Not shown before (different/absent content key) → broadcast (new card).\n// - Shown before AND the toolUseId changed → broadcast (re-sync the id).\n// - Shown before AND same toolUseId → suppress (true duplicate).\nexport function shouldBroadcastQuestion(args: {\n  newContentKey: string;\n  lastContentKey: string | undefined;\n  newToolUseId: string;\n  priorToolUseId: string | undefined;\n}): boolean {\n  const alreadyShown = args.lastContentKey === args.newContentKey;\n  if (!alreadyShown) return true;\n  return args.priorToolUseId !== args.newToolUseId;\n}\n","import type { AskQuestion } from \"../../types\";\n\nconst DOWN = \"\\x1b[B\";\nconst ENTER = \"\\r\";\n\nexport class UnknownOptionError extends Error {\n  constructor(\n    public readonly question: string,\n    public readonly value: string,\n  ) {\n    super(`No option labelled \"${value}\" for question \"${question}\"`);\n    this.name = \"UnknownOptionError\";\n  }\n}\n\n// v1: single-select only. Cursor starts at index 0; N downs + Enter selects index N.\n// Multi-question calls replay each question's block in the order Claude presents them.\nexport function answersToKeystrokes(\n  questions: AskQuestion[],\n  answers: Record<string, string | string[]>,\n): string {\n  let out = \"\";\n  for (const q of questions) {\n    const raw = answers[q.question];\n    if (raw === undefined) {\n      throw new Error(`Missing answer for question \"${q.question}\"`);\n    }\n    const label = Array.isArray(raw) ? raw[0] : raw; // v1 ignores extra (multiSelect is v2)\n    const target = q.options.findIndex((o) => o.label === label);\n    if (target < 0) throw new UnknownOptionError(q.question, label);\n    out += DOWN.repeat(target) + ENTER;\n  }\n  return out;\n}\n","import type { AskQuestion } from \"../../types\";\nimport { answersToKeystrokes, UnknownOptionError } from \"./answersToKeystrokes\";\n\nexport type AnswerResolution =\n  | { ok: true; keys: string }\n  | { ok: false; reason: \"no_pending_question\" | \"tool_use_mismatch\" | \"unknown_option\" };\n\nexport function resolveAnswer(\n  pending: { toolUseId: string; questions: AskQuestion[] } | undefined,\n  body: { toolUseId?: unknown; answers?: unknown },\n): AnswerResolution {\n  if (!pending) return { ok: false, reason: \"no_pending_question\" };\n  if (typeof body.toolUseId !== \"string\" || body.toolUseId !== pending.toolUseId) {\n    return { ok: false, reason: \"tool_use_mismatch\" };\n  }\n  const answers = (body.answers ?? {}) as Record<string, string | string[]>;\n  try {\n    return { ok: true, keys: answersToKeystrokes(pending.questions, answers) };\n  } catch (e) {\n    if (e instanceof UnknownOptionError) return { ok: false, reason: \"unknown_option\" };\n    throw e;\n  }\n}\n","import { createProgressDedupeLRU } from \"./agent/dedupe\";\nimport type {\n  DiscoveredProcess,\n  ManagedSession,\n  SessionCursor,\n  SessionListPage,\n  SessionListQuery,\n  SessionResponse,\n  SessionSortKey,\n  SortOrder,\n} from \"./types\";\n\nexport class SessionStore {\n  private managed = new Map<string, ManagedSession>();\n  private discovered = new Map<number, DiscoveredProcess>();\n\n  addManaged(session: ManagedSession): void {\n    this.managed.set(session.id, session);\n  }\n\n  /**\n   * Multi-agent mode only. Attach a dedupe LRU to a session record. Idempotent —\n   * calling twice keeps the existing LRU (and its contents).\n   */\n  initAgentSession(sessionId: string, dedupeCapacity: number): void {\n    const session = this.managed.get(sessionId);\n    if (!session) return;\n    if (session.progressDedupeIds) return;\n    session.progressDedupeIds = createProgressDedupeLRU(dedupeCapacity);\n  }\n\n  updateManaged(sessionId: string, updates: Partial<ManagedSession>): ManagedSession | null {\n    const session = this.managed.get(sessionId);\n    if (!session) return null;\n    Object.assign(session, updates);\n    return session;\n  }\n\n  removeManaged(sessionId: string): boolean {\n    return this.managed.delete(sessionId);\n  }\n\n  getManaged(sessionId: string): ManagedSession | null {\n    return this.managed.get(sessionId) ?? null;\n  }\n\n  setDiscovered(processes: DiscoveredProcess[]): void {\n    this.discovered.clear();\n    for (const proc of processes) {\n      this.discovered.set(proc.pid, proc);\n    }\n  }\n\n  listManaged(): ManagedSession[] {\n    return Array.from(this.managed.values());\n  }\n\n  // Build the session list: live PTY sessions (managed) merged with externally\n  // discovered Claude processes. Managed sessions keyed by JSONL UUID take\n  // priority — discovered processes with the same UUID are skipped.\n  list(ptyAttachedIds: Set<string>): SessionResponse[] {\n    const results: SessionResponse[] = [];\n    const seenIds = new Set<string>();\n\n    for (const s of this.managed.values()) {\n      results.push(managedToResponse(s, ptyAttachedIds.has(s.id)));\n      seenIds.add(s.id);\n    }\n\n    for (const d of this.discovered.values()) {\n      if (!d.conversationId) continue;\n      if (seenIds.has(d.conversationId)) continue;\n      results.push(discoveredToResponse(d, d.conversationId));\n      seenIds.add(d.conversationId);\n    }\n\n    return results;\n  }\n\n  get(sessionId: string, ptyAttachedIds: Set<string>): SessionResponse | null {\n    const managed = this.managed.get(sessionId);\n    if (managed) return managedToResponse(managed, ptyAttachedIds.has(sessionId));\n\n    for (const d of this.discovered.values()) {\n      if (d.conversationId === sessionId) return discoveredToResponse(d, sessionId);\n    }\n\n    return null;\n  }\n\n  // Paginated, sorted, filtered view over the same merged data list() exposes.\n  // The discovery cache can mutate the underlying set between requests, so the\n  // cursor encodes both the chosen sort key value and the session id as a\n  // tiebreaker. New sessions appearing mid-scan are picked up on the next\n  // refetch — see plan for caveats.\n  paginate(ptyAttachedIds: Set<string>, query: SessionListQuery): SessionListPage {\n    const all = this.list(ptyAttachedIds);\n\n    const filtered = query.status?.length\n      ? all.filter((s) => query.status?.includes(s.status))\n      : all;\n\n    const sorted = [...filtered].sort(makeComparator(query.sortBy, query.order));\n\n    const total = sorted.length;\n\n    const startIdx = query.cursor\n      ? findCursorBoundary(sorted, decodeCursor(query.cursor), query.sortBy, query.order)\n      : 0;\n    const page = sorted.slice(startIdx, startIdx + query.limit);\n    const last = page[page.length - 1];\n    const nextCursor =\n      last && startIdx + page.length < sorted.length\n        ? encodeCursor({ k: getSortValue(last, query.sortBy) ?? \"\", id: last.id })\n        : null;\n\n    return { sessions: page, nextCursor, total };\n  }\n}\n\n// Cursor encoding is opaque to clients: base64url(JSON({ k, id })).\nexport function encodeCursor(c: SessionCursor): string {\n  return Buffer.from(JSON.stringify(c), \"utf8\").toString(\"base64url\");\n}\n\nexport function decodeCursor(s: string): SessionCursor {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(Buffer.from(s, \"base64url\").toString(\"utf8\"));\n  } catch {\n    throw new Error(\"INVALID_CURSOR\");\n  }\n  if (\n    !parsed ||\n    typeof parsed !== \"object\" ||\n    typeof (parsed as SessionCursor).id !== \"string\" ||\n    ![\"string\", \"number\"].includes(typeof (parsed as SessionCursor).k)\n  ) {\n    throw new Error(\"INVALID_CURSOR\");\n  }\n  return parsed as SessionCursor;\n}\n\nfunction getSortValue(s: SessionResponse, key: SessionSortKey): string | number | undefined {\n  switch (key) {\n    case \"startedAt\":\n      return s.startedAt;\n    case \"lastActivityAt\":\n      return s.lastActivityAt ?? s.startedAt;\n    case \"projectName\":\n      return s.projectName;\n    case \"status\":\n      return s.status;\n  }\n}\n\nfunction compareValues(a: string | number | undefined, b: string | number | undefined): number {\n  // Undefined sorts last regardless of order; callers normalise via getSortValue\n  // so this branch only fires for genuinely missing values.\n  if (a === undefined && b === undefined) return 0;\n  if (a === undefined) return 1;\n  if (b === undefined) return -1;\n  if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n  return String(a).localeCompare(String(b));\n}\n\nfunction makeComparator(key: SessionSortKey, order: SortOrder) {\n  const dir = order === \"asc\" ? 1 : -1;\n  return (a: SessionResponse, b: SessionResponse): number => {\n    const cmp = compareValues(getSortValue(a, key), getSortValue(b, key)) * dir;\n    if (cmp !== 0) return cmp;\n    // Tiebreaker on id is always ascending so the total order is stable\n    // regardless of `order`.\n    return a.id.localeCompare(b.id);\n  };\n}\n\n// Returns the index of the first element strictly *after* the cursor under the\n// chosen ordering. Linear scan is fine: total session counts are in the\n// hundreds, not millions.\nfunction findCursorBoundary(\n  sorted: SessionResponse[],\n  cursor: SessionCursor,\n  key: SessionSortKey,\n  order: SortOrder,\n): number {\n  const dir = order === \"asc\" ? 1 : -1;\n  for (let i = 0; i < sorted.length; i++) {\n    const item = sorted[i];\n    const cmp = compareValues(getSortValue(item, key), cursor.k) * dir;\n    if (cmp > 0) return i;\n    if (cmp === 0 && item.id.localeCompare(cursor.id) > 0) return i;\n  }\n  return sorted.length;\n}\n\nfunction managedToResponse(s: ManagedSession, ptyAttached: boolean): SessionResponse {\n  return {\n    id: s.id,\n    conversationId: s.id,\n    status: s.status,\n    projectPath: s.projectPath,\n    projectName: s.projectName,\n    branch: s.branch,\n    lastOutput: s.lastOutput,\n    elapsedMs: (s.completedAt ?? new Date()).getTime() - s.startedAt.getTime(),\n    promptCount: s.promptCount,\n    startedAt: s.startedAt.toISOString(),\n    completedAt: s.completedAt?.toISOString() ?? null,\n    ptyAttached,\n    ...(s.projectId != null && { projectId: s.projectId }),\n    ...(s.sessionName != null && { sessionName: s.sessionName }),\n    ...(s.model != null && { model: s.model }),\n    ...(s.account != null && { account: s.account }),\n    ...(s.messageCount != null && { messageCount: s.messageCount }),\n    ...(s.preview != null && { preview: s.preview }),\n    ...(s.firstMessageText != null && { firstMessageText: s.firstMessageText }),\n    ...(s.firstMessageAt != null && { firstMessageAt: s.firstMessageAt.toISOString() }),\n    ...(s.lastMessageText != null && { lastMessageText: s.lastMessageText }),\n    ...(s.lastMessageAt != null && { lastMessageAt: s.lastMessageAt.toISOString() }),\n    ...(s.lastActivityAt != null && { lastActivityAt: s.lastActivityAt.toISOString() }),\n    ...(s.filePath != null && { filePath: s.filePath }),\n    ...(s.failureReason != null && { failureReason: s.failureReason }),\n    ...(s.resumedFromConversationId != null && {\n      resumedFromConversationId: s.resumedFromConversationId,\n    }),\n  };\n}\n\nfunction discoveredToResponse(d: DiscoveredProcess, conversationId: string): SessionResponse {\n  return {\n    id: conversationId,\n    conversationId,\n    status: \"idle\",\n    projectPath: d.projectPath,\n    projectName: d.projectName,\n    branch: d.branch,\n    lastOutput: \"\",\n    elapsedMs: Date.now() - d.startedAt.getTime(),\n    promptCount: 0,\n    startedAt: d.startedAt.toISOString(),\n    completedAt: null,\n    ptyAttached: false,\n    pid: d.pid,\n  };\n}\n","import { randomBytes } from \"crypto\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport heicConvert from \"heic-convert\";\nimport { join } from \"path\";\n\nconst UPLOAD_DIR_NAME = \".threadbase-uploads\";\nconst MAX_BYTES = 25 * 1024 * 1024; // 25MB\n\nconst HEIC_MIMES = new Set([\"image/heic\", \"image/heif\"]);\n\nconst MIME_TO_EXT: Record<string, string> = {\n  \"image/jpeg\": \".jpg\",\n  \"image/png\": \".png\",\n  \"image/gif\": \".gif\",\n  \"image/webp\": \".webp\",\n  \"image/heic\": \".jpg\",\n  \"image/heif\": \".jpg\",\n  \"application/pdf\": \".pdf\",\n  \"application/msword\": \".doc\",\n  \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\": \".docx\",\n  \"text/plain\": \".txt\",\n  \"text/javascript\": \".js\",\n  \"application/typescript\": \".ts\",\n  \"application/json\": \".json\",\n  \"text/csv\": \".csv\",\n};\n\nexport interface SaveUploadInput {\n  sessionId: string;\n  projectPath: string;\n  originalName: string;\n  mimeType: string;\n  dataBase64: string;\n}\n\nexport interface SavedUpload {\n  id: string;\n  filePath: string;\n  originalName: string;\n  mimeType: string;\n  sizeBytes: number;\n}\n\nexport async function saveUploadFile(input: SaveUploadInput): Promise<SavedUpload> {\n  let buffer = Buffer.from(input.dataBase64, \"base64\");\n  if (buffer.length === 0) throw new Error(\"Empty file\");\n  if (buffer.length > MAX_BYTES) throw new Error(`File exceeds ${MAX_BYTES} bytes`);\n\n  let { mimeType } = input;\n  let originalName = input.originalName;\n\n  if (HEIC_MIMES.has(mimeType)) {\n    buffer = Buffer.from(await heicConvert({ buffer, format: \"JPEG\", quality: 0.85 }));\n    mimeType = \"image/jpeg\";\n    originalName = originalName.replace(/\\.(heic|heif)$/i, \".jpg\");\n  }\n\n  const id = `up_${randomBytes(8).toString(\"hex\")}`;\n  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? \"\"}`;\n  const dir = join(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);\n  await mkdir(dir, { recursive: true });\n\n  const filePath = join(dir, `${Date.now()}-${id}-${safeName}`);\n  await writeFile(filePath, buffer);\n\n  return {\n    id,\n    filePath,\n    originalName: safeName,\n    mimeType,\n    sizeBytes: buffer.length,\n  };\n}\n\nfunction sanitizeFilename(name: string): string {\n  // Take only the basename (block path traversal)\n  const base = name.split(/[\\\\/]/).pop() ?? \"\";\n  // Strip leading dots; keep all printable Unicode (charCode >= 32, != 127)\n  const cleaned = base\n    .replace(/^\\.+/, \"\")\n    .split(\"\")\n    .filter((c) => c.charCodeAt(0) >= 32 && c.charCodeAt(0) !== 127)\n    .join(\"\");\n  return cleaned;\n}\n","import { createHash } from \"node:crypto\";\n\n/**\n * Inputs that uniquely identify a conversation's current state for the\n * purpose of a conditional fetch. These all live on the parsed `Conversation`\n * and must be read AFTER `findConversationByUuid`'s staleness refresh — never\n * from a pre-refresh snapshot — so the validator reflects the same state the\n * response body would.\n */\nexport interface ConversationEtagInput {\n  filePath: string;\n  messageCount: number;\n  timestamp: string;\n}\n\n/**\n * Derive a stable, opaque ETag for a conversation. The client never parses it;\n * it only echoes the value back via `If-None-Match`. Wrapping the inputs in a\n * hash keeps the formula changeable without the client caring.\n */\nexport function computeConversationEtag({\n  filePath,\n  messageCount,\n  timestamp,\n}: ConversationEtagInput): string {\n  const digest = createHash(\"sha1\")\n    .update(`${filePath}:${messageCount}:${timestamp}`)\n    .digest(\"hex\")\n    .slice(0, 16);\n  return `\"${digest}\"`;\n}\n","/**\n * Trailing-edge debounce: collapses calls that arrive within `waitMs` of each\n * other into a single invocation, scheduled `waitMs` after the most recent\n * call. The last call's arguments win.\n *\n * The returned function carries two extra methods:\n *   - cancel(): drop any pending invocation (use on shutdown so a timer\n *     doesn't keep the process alive).\n *   - flush():  run any pending invocation immediately (useful in tests).\n */\nexport function debounce<A extends unknown[]>(\n  fn: (...args: A) => void,\n  waitMs: number,\n): ((...args: A) => void) & { flush: () => void; cancel: () => void } {\n  let timer: ReturnType<typeof setTimeout> | null = null;\n  let lastArgs: A | null = null;\n\n  const run = () => {\n    timer = null;\n    if (lastArgs) {\n      const args = lastArgs;\n      lastArgs = null;\n      fn(...args);\n    }\n  };\n\n  const debounced = (...args: A): void => {\n    lastArgs = args;\n    if (timer) clearTimeout(timer);\n    timer = setTimeout(run, waitMs);\n  };\n\n  debounced.cancel = (): void => {\n    if (timer) clearTimeout(timer);\n    timer = null;\n    lastArgs = null;\n  };\n\n  debounced.flush = (): void => {\n    if (timer) {\n      clearTimeout(timer);\n      run();\n    }\n  };\n\n  return debounced;\n}\n","import { compareDesc, isValid, parseISO } from \"date-fns\";\n\n/**\n * Parse an ISO timestamp string. Returns null for null/undefined input or\n * an unparseable value rather than throwing — callers can fall back.\n */\nexport function parseIsoDateOrNull(value?: string | null): Date | null {\n  if (!value) return null;\n  const parsed = parseISO(value);\n  return isValid(parsed) ? parsed : null;\n}\n\n/**\n * Compare two ISO timestamp strings descending. Null/undefined values sort\n * last regardless of order. Used for ProjectChat list ordering and latest\n * conversation selection.\n */\nexport function compareIsoDesc(a?: string | null, b?: string | null): number {\n  const dateA = parseIsoDateOrNull(a);\n  const dateB = parseIsoDateOrNull(b);\n\n  if (!dateA && !dateB) return 0;\n  if (!dateA) return 1;\n  if (!dateB) return -1;\n\n  return compareDesc(dateA, dateB);\n}\n","import { parseIsoDateOrNull } from \"./dates\";\n\n// The on-disk mtime can run slightly ahead of the last message's own timestamp\n// (filesystem flush latency, non-message trailer lines like pr-link/mode that\n// the scanner ignores for `timestamp` but still bump mtime). Require the file\n// to be at least this much newer than the snapshot before forcing a re-scan, so\n// a single read doesn't churn the whole scanner index on every detail fetch.\nconst STALENESS_TOLERANCE_MS = 1000;\n\n/**\n * Decide whether a scanned conversation snapshot is stale relative to the\n * JSONL on disk. The scanner memoizes both its metadata index and parsed\n * conversations for the server's lifetime, so a conversation that grows after\n * the initial scan keeps serving the startup snapshot from\n * `/api/conversations/{id}`. We compare the file's mtime against the\n * snapshot's last-activity timestamp to detect that drift.\n *\n * Returns true when the file is meaningfully newer than the snapshot (a\n * re-scan is warranted). Returns false when the snapshot is current, when the\n * snapshot timestamp is unparseable (nothing to compare against — leave it),\n * or when mtime is null (stat failed — don't churn the index on a transient\n * read error).\n */\nexport function isScannedSnapshotStale(\n  snapshotTimestamp: string | null | undefined,\n  fileMtimeMs: number | null,\n): boolean {\n  if (fileMtimeMs == null) return false;\n  const snapshotDate = parseIsoDateOrNull(snapshotTimestamp);\n  if (!snapshotDate) return false;\n  return fileMtimeMs - snapshotDate.getTime() > STALENESS_TOLERANCE_MS;\n}\n","/**\n * The scanner fires onProgress once per file, so a large scan would otherwise\n * broadcast thousands of scan_progress WebSocket frames. This throttle collapses\n * them to at most one per whole-percent step, plus a guaranteed final frame when\n * the scan completes (scanned === total). Caps frames at ~101 regardless of the\n * file count, while always delivering the terminal 100% update.\n *\n * Returns a predicate: call it with each (scanned, total) tick; it returns true\n * when that tick should be broadcast. Stateful — create one per scan.\n */\nexport function createScanProgressThrottle(): (scanned: number, total: number) => boolean {\n  let lastPercent = -1;\n  return (scanned: number, total: number): boolean => {\n    // total === 0 (empty scan): emit once so clients see a terminal tick.\n    if (total <= 0) {\n      if (lastPercent === 100) return false;\n      lastPercent = 100;\n      return true;\n    }\n    const isFinal = scanned >= total;\n    const percent = Math.floor((scanned / total) * 100);\n    if (isFinal) {\n      // Always let the final tick through, even if its percent already fired.\n      lastPercent = 100;\n      return true;\n    }\n    if (percent === lastPercent) return false;\n    lastPercent = percent;\n    return true;\n  };\n}\n","import type { WebSocket } from \"ws\";\nimport type { WSMessage } from \"./types\";\n\nconst PING_INTERVAL_MS = 30_000;\n// How long to wait for a pong before treating the socket as dead.\n// Must be less than PING_INTERVAL_MS.\nconst PONG_TIMEOUT_MS = 10_000;\n\nexport class WSHub {\n  private clients = new Set<WebSocket>();\n  private pingTimer: ReturnType<typeof setInterval> | null = null;\n  // Per-socket pong-timeout handle; set when ping is sent, cleared on pong/close.\n  private pongTimers = new Map<WebSocket, ReturnType<typeof setTimeout>>();\n\n  addClient(ws: WebSocket): void {\n    this.clients.add(ws);\n\n    ws.on(\"pong\", () => {\n      const t = this.pongTimers.get(ws);\n      if (t) {\n        clearTimeout(t);\n        this.pongTimers.delete(ws);\n      }\n    });\n\n    ws.on(\"close\", () => {\n      const t = this.pongTimers.get(ws);\n      if (t) {\n        clearTimeout(t);\n        this.pongTimers.delete(ws);\n      }\n      this.clients.delete(ws);\n    });\n\n    ws.on(\"error\", () => {\n      const t = this.pongTimers.get(ws);\n      if (t) {\n        clearTimeout(t);\n        this.pongTimers.delete(ws);\n      }\n      this.clients.delete(ws);\n    });\n\n    if (!this.pingTimer && this.clients.size > 0) {\n      this.startPing();\n    }\n  }\n\n  broadcast(message: WSMessage): void {\n    const data = JSON.stringify(message);\n    const dead: WebSocket[] = [];\n\n    for (const client of this.clients) {\n      try {\n        if (client.readyState === client.OPEN) {\n          client.send(data);\n        } else {\n          dead.push(client);\n        }\n      } catch {\n        dead.push(client);\n      }\n    }\n\n    for (const client of dead) {\n      this.clients.delete(client);\n    }\n  }\n\n  unicast(ws: WebSocket, message: WSMessage): void {\n    try {\n      if (ws.readyState === ws.OPEN) {\n        ws.send(JSON.stringify(message));\n      }\n    } catch {\n      this.clients.delete(ws);\n    }\n  }\n\n  get connectionCount(): number {\n    return this.clients.size;\n  }\n\n  dispose(): void {\n    if (this.pingTimer) {\n      clearInterval(this.pingTimer);\n      this.pingTimer = null;\n    }\n    for (const [, t] of this.pongTimers) {\n      clearTimeout(t);\n    }\n    this.pongTimers.clear();\n    for (const client of this.clients) {\n      try {\n        // terminate() (not close()) so the underlying TCP socket dies\n        // immediately. A graceful close() only sends a close frame and waits\n        // for the peer's reply — a slow/backgrounded client would keep the\n        // connection (and thus the HTTP listener's port) alive until the peer\n        // ACKs, which is what stalled shutdown and caused EADDRINUSE on the\n        // next deploy.\n        client.terminate();\n      } catch {\n        // Already closed\n      }\n    }\n    this.clients.clear();\n  }\n\n  private startPing(): void {\n    this.pingTimer = setInterval(() => {\n      if (this.clients.size === 0 && this.pingTimer) {\n        clearInterval(this.pingTimer);\n        this.pingTimer = null;\n        return;\n      }\n      for (const client of this.clients) {\n        if (client.readyState !== client.OPEN) continue;\n        // WS protocol ping — client must reply with a pong frame. If no pong\n        // arrives within PONG_TIMEOUT_MS the socket is considered dead and\n        // terminated. This is what detects iOS silently killing the TCP\n        // connection without delivering a close frame to the JS layer.\n        client.ping();\n        const t = setTimeout(() => {\n          this.pongTimers.delete(client);\n          client.terminate();\n        }, PONG_TIMEOUT_MS);\n        this.pongTimers.set(client, t);\n      }\n    }, PING_INTERVAL_MS);\n  }\n}\n"],"mappings":"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","names":["resolve","crypto","import_fs","import_path","pool","import_child_process","import_os","import_path","import_fs","import_os","import_path","resolve","import_crypto","import_fs","import_path","pino","OPTION_RE","BOX_ONLY_RE","BOX_ONLY_RE","OPTION_RE","BOX_ONLY_RE","resolve","import_scanner","import_fs","import_promises","import_os","import_path","import_crypto","crypto","log","import_node_path","import_hono","import_hono","import_hono","ALREADY_HANDLED","alreadyHandled","import_hono","import_node_fs","import_node_path","import_node_crypto","import_hono","import_os","import_node_fs","import_node_path","parseYaml","resolve","readRawBody","import_hono","ALREADY_HANDLED","alreadyHandled","import_hono","ALREADY_HANDLED","alreadyHandled","import_hono","ALREADY_HANDLED","alreadyHandled","import_hono","ALREADY_HANDLED","alreadyHandled","import_hono","import_promises","import_path","import_fs","import_path","import_fs","import_path","import_url","import_meta","getMigrationsDir","import_fs","cached","Database","run","import_crypto","pool","import_fs","import_os","import_path","import_crypto","naclUtil","nacl","import_fs","import_promises","chokidar","import_fs","normalizeContent","ENTER","import_crypto","import_promises","import_path","heicConvert","import_node_crypto","run","TemporalClient","resolve","ALREADY_HANDLED","hostname","total","adapted","stat","filePath","availability","messagesPayload","resp","cached","body","fsWatch"]}