@threadbase-sh/streamer 1.15.3 → 1.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +7 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +7 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../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/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/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/schemas/queryParams.schema.ts","../src/services/cache/cacheMetadata.ts","../src/utils/dates.ts","../src/services/projects/ensureProjectsForConversations.ts","../src/services/conversations/refreshConversationCache.ts","../src/services/conversations/shouldRefreshProjectsFromHdd.ts","../src/services/sessions/ensureSessionProjectIdsFromExistingProjects.ts","../src/services/projectChats/sortProjectChats.ts","../src/services/projectChats/mergeProjectChats.ts","../src/services/projectChats/deriveProjectChatTitle.ts","../src/services/projectChats/normalizeConversationToProjectChat.ts","../src/services/projectChats/normalizeSessionToProjectChat.ts","../src/services/projectChats/listProjectChats.ts","../src/handlers/handleListProjectChats.ts","../src/pair-store.ts","../src/seal.ts","../src/services/conversations/conversationWatcher.ts","../src/services/conversations/pruneAgentConversations.ts","../src/session-store.ts","../src/uploads.ts","../src/utils/conversationEtag.ts","../src/utils/debounce.ts","../src/utils/isScannedSnapshotStale.ts","../src/utils/scanProgressThrottle.ts","../src/ws-hub.ts"],"sourcesContent":["// 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 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 // 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.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 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 }\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\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 }\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","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 {\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 loadBrowseRoot,\n loadCacheDir,\n loadPublicUrl,\n loadTailSize,\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 { handleListProjectChats } from \"./handlers/handleListProjectChats\";\nimport { getLogger } from \"./logger\";\nimport { PairTokenStore } from \"./pair-store\";\nimport { discoverClaudeProcesses } from \"./process-discovery\";\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 { SessionStore } from \"./session-store\";\nimport type {\n DiscoveredProcess,\n ServerConfig,\n SessionSortKey,\n SortOrder as SessionSortOrder,\n SessionStatus,\n} from \"./types\";\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_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 scanner: ConversationScanner | null = null;\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 localNoAuth: 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 ptyGracePeriodMs: number;\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 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\n constructor(config: ServerConfig & { apiKey: string }) {\n this.apiKey = config.apiKey;\n this.localNoAuth = config.localNoAuth ?? false;\n this.verbose = config.verbose ?? false;\n this.disableDb = config.disableDb ?? false;\n this.scanProfiles = config.scanProfiles;\n this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;\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 // 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 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 }\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 },\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 apiDeps: ApiDeps = {\n apiKey: this.apiKey,\n localNoAuth: this.localNoAuth,\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 handleCancel: (id, res) => this.handleCancel(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 handleGetPopularProjects: (url, res) => this.handleGetPopularProjects(url, res),\n handleListProjectChats: (url, res) => this.handleListProjectChats(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 this.sessionSubscribers.delete(sessionId);\n if (this.ptyManager.hasSession(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 resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n if (resp) this.wsHub.broadcast({ type: \"session_update\", session: resp });\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 ~/.claude/projects so new JSONL files created after startup\n // (e.g. resumed sessions, new conversations from other devices) are\n // discovered: onConversationChanged will invalidate the scanner and\n // cache so the next search/list picks them up without a restart.\n const projectsDir = join(homedir(), \".claude\", \"projects\");\n this.fileWatcher.watchDirectory(projectsDir);\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 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 warmupScanner\n .scan({\n ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\n ...(warmupStatCache ? { statCache: warmupStatCache } : {}),\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 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 checkExchangeRateLimit(ip: string): boolean {\n const now = Date.now();\n const windowMs = 60_000;\n const limit = 5;\n const arr = (this.exchangeAttempts.get(ip) ?? []).filter((t) => now - t < windowMs);\n if (arr.length >= limit) {\n this.exchangeAttempts.set(ip, arr);\n return false;\n }\n arr.push(now);\n this.exchangeAttempts.set(ip, arr);\n return true;\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 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({ project, limit, offset });\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 }));\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 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 };\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 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 { total } = this.cache.listConversations({ project, limit: 0, offset: 0 });\n json(res, 200, { 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 json(res, 200, { total: metas.length });\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 async handleListProjectChats(url: URL, res: ServerResponse): Promise<void> {\n if (\n !this.cache ||\n !this.projectsRepo ||\n !this.conversationsRepo ||\n !this.sessionsRepo ||\n !this.cacheMetadataRepo\n ) {\n json(res, 503, { error: \"Cache not available\" });\n return;\n }\n\n await handleListProjectChats(url, res, {\n cache: this.cache,\n projectsRepo: this.projectsRepo,\n conversationsRepo: this.conversationsRepo,\n sessionsRepo: this.sessionsRepo,\n cacheMetadataRepo: this.cacheMetadataRepo,\n getSessionResponses: () => this.sessionStore.list(this.ptyAttachedIds()),\n getFreshScanner: () => this.getFreshScanner(),\n });\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 // 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.scannerReady = this.scanner.scan({\n ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\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 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 resumable: 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 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 resumable: 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 },\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 }));\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.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 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 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 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 } = 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 try {\n const session = await this.ptyManager.startFresh({\n projectPath: resolvedPath,\n projectName: body.projectName,\n systemPrompt: BROWSE_SYSTEM_PROMPT(this.browseRoot),\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 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 resumable: 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'\nimport { urlAlphabet } from './url-alphabet/index.js'\nconst POOL_SIZE_MULTIPLIER = 128\nlet pool, poolOffset\nlet fillPool = bytes => {\n if (bytes < 0 || bytes > 1024) throw new RangeError('Wrong ID size')\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 poolOffset += bytes\n}\nlet random = bytes => {\n fillPool((bytes |= 0))\n return pool.subarray(poolOffset - bytes, poolOffset)\n}\nlet customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1\n let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length)\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}\nlet customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size, random)\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}\nexport { nanoid, customAlphabet, customRandom, urlAlphabet, random }\n","let urlAlphabet =\n 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\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 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\nexport const corsMiddleware = (): MiddlewareHandler<AppEnv> => async (c, next) => {\n c.res.headers.set(\"Access-Control-Allow-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 if (c.req.method === \"OPTIONS\") {\n return c.newResponse(null, 204);\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 } 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 // 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 [here, join(here, \"..\")]) {\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<ApiDeps, \"publicUrl\" | \"sessionStore\" | \"ptyAttachedIds\">,\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/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(\"/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 app.get(\"/project-chats\", async (c) => {\n const url = new URL(c.req.url);\n await deps.handleListProjectChats(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/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.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 {\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}\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}\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 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 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)\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)\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 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 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 });\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; limit: number; offset: number }): {\n conversations: ConversationListItem[];\n total: number;\n } {\n const { project, 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 {\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 })),\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 };\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","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 { z } from \"zod\";\n\n/**\n * Query parameters accepted by GET /project-chats.\n *\n * refreshConversations=1 → force a full conversation/projects refresh.\n * refresh=1 → legacy alias kept for compatibility with the\n * existing /api/conversations refresh semantic.\n */\nexport const ListProjectChatsQuerySchema = z.object({\n refresh: z.enum([\"1\"]).optional(),\n refreshConversations: z.enum([\"1\"]).optional(),\n});\n\nexport type ListProjectChatsQuery = z.infer<typeof ListProjectChatsQuerySchema>;\n","import type {\n CacheMetadataKey,\n CacheMetadataRepository,\n} from \"../../db/repositories/cacheMetadata.repository\";\n\n/**\n * Functional accessors over the cache_metadata table. Repositories give us\n * the SQL surface; these helpers give us the verbs the services use.\n */\nexport function getCacheMetadata(\n repo: CacheMetadataRepository,\n key: CacheMetadataKey,\n): string | null {\n return repo.getCacheMetadata(key);\n}\n\nexport function setCacheMetadata(\n repo: CacheMetadataRepository,\n key: CacheMetadataKey,\n value: string,\n): void {\n repo.setCacheMetadata(key, value);\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 type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\nimport { compareIsoDesc } from \"../../utils/dates\";\n\nexport interface ConversationLikeForProjects {\n id: string;\n projectPath: string | null | undefined;\n createdAt?: string | null;\n latestMessageAt?: string | null;\n}\n\n/**\n * Group cached conversations by their canonical project path, find the\n * latest conversation per project, then upsert one project row per unique\n * path. Returns a map from canonical path → projectId so callers can\n * backfill conversation/session project_id columns afterwards.\n */\nexport function ensureProjectsForConversations(\n repo: ProjectsRepository,\n conversations: ConversationLikeForProjects[],\n): Map<string, string> {\n const conversationsByPath = new Map<string, ConversationLikeForProjects[]>();\n\n for (const conversation of conversations) {\n if (!conversation.projectPath) continue;\n const canonical = canonicalizeProjectPath(conversation.projectPath);\n if (!canonical) continue;\n const existing = conversationsByPath.get(canonical) ?? [];\n existing.push(conversation);\n conversationsByPath.set(canonical, existing);\n }\n\n const pathToProjectId = new Map<string, string>();\n\n for (const [path, projectConversations] of conversationsByPath) {\n const latest = pickLatestConversation(projectConversations);\n\n const project = repo.upsertProjectByPath(path, {\n lastConversationId: latest?.id ?? null,\n lastConversationCreatedAt: latest?.createdAt ?? null,\n latestMessageAt: latest?.latestMessageAt ?? null,\n });\n\n pathToProjectId.set(path, project.id);\n }\n\n return pathToProjectId;\n}\n\nfunction pickLatestConversation(\n conversations: ConversationLikeForProjects[],\n): ConversationLikeForProjects | undefined {\n if (conversations.length === 0) return undefined;\n return [...conversations].sort((a, b) => {\n const cmp = compareIsoDesc(a.latestMessageAt ?? null, b.latestMessageAt ?? null);\n if (cmp !== 0) return cmp;\n return compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);\n })[0];\n}\n","import type { ConversationCache } from \"../../conversation-cache\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\nimport { setCacheMetadata } from \"../cache/cacheMetadata\";\nimport { ensureProjectsForConversations } from \"../projects/ensureProjectsForConversations\";\n\nexport interface RefreshConversationCacheDeps {\n cache: ConversationCache;\n projectsRepo: ProjectsRepository;\n conversationsRepo: ConversationsRepository;\n cacheMetadataRepo: CacheMetadataRepository;\n}\n\nexport interface RefreshConversationCacheResult {\n projectsTouched: number;\n conversationsBackfilled: number;\n latestConversationId: string | null;\n}\n\n/**\n * After a scanner-driven cache rebuild has run, walk the cache to:\n * 1. Upsert one project row per unique canonical project_path.\n * 2. Backfill conversation_meta.project_id for any rows missing it.\n * 3. Update cache_metadata.last_conversation_id so subsequent freshness\n * checks can short-circuit.\n *\n * The scanner itself runs in `server.ts` today; this function picks up\n * after it has populated `conversation_meta`.\n */\nexport function refreshConversationCache(\n deps: RefreshConversationCacheDeps,\n): RefreshConversationCacheResult {\n const { projectsRepo, conversationsRepo, cacheMetadataRepo } = deps;\n\n const conversations = conversationsRepo.listConversationsForProjectBackfill();\n\n const pathToProjectId = ensureProjectsForConversations(\n projectsRepo,\n conversations.map((c) => ({\n id: c.id,\n projectPath: c.projectPath,\n latestMessageAt: c.lastActivity ?? null,\n createdAt: c.lastActivity ?? null,\n })),\n );\n\n let conversationsBackfilled = 0;\n for (const conversation of conversations) {\n if (!conversation.projectPath) continue;\n if (conversation.projectId) continue;\n const projectId = pathToProjectId.get(canonicalizeProjectPath(conversation.projectPath));\n if (!projectId) continue;\n conversationsRepo.updateConversationProjectId({\n conversationId: conversation.id,\n projectId,\n });\n conversationsBackfilled += 1;\n }\n\n const latest = conversationsRepo.getLatestConversation();\n if (latest) {\n setCacheMetadata(cacheMetadataRepo, \"last_conversation_id\", latest.id);\n if (latest.lastActivity) {\n setCacheMetadata(cacheMetadataRepo, \"last_conversation_created_at\", latest.lastActivity);\n }\n }\n setCacheMetadata(cacheMetadataRepo, \"conversations_last_indexed_at\", new Date().toISOString());\n\n return {\n projectsTouched: pathToProjectId.size,\n conversationsBackfilled,\n latestConversationId: latest?.id ?? null,\n };\n}\n","import { statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport { getCacheMetadata } from \"../cache/cacheMetadata\";\n\nconst DEFAULT_PROJECTS_DIR = join(homedir(), \".claude\", \"projects\");\n\nexport interface ShouldRefreshOptions {\n /** Override the disk path checked for drift. Defaults to ~/.claude/projects. */\n projectsDir?: string;\n}\n\n/**\n * Decide whether the projects/conversations cache needs a refresh.\n *\n * Returns true when either:\n * 1. The cache has orphan rows — conversations with a project_path but no\n * project_id. These need a backfill pass to become visible to\n * /project-chats (which filters out null project_id).\n * 2. The projects dir on disk has changed since the last scanner pass —\n * its mtime is newer than cache_metadata.conversations_last_indexed_at.\n * This catches new JSONLs that the file watcher missed.\n *\n * The chokidar watcher only tails JSONLs for managed PTY sessions, so\n * conversations created outside the streamer (or before it started) never\n * trigger the watcher. The mtime check is the safety net.\n */\nexport function shouldRefreshProjectsFromHdd(\n conversationsRepo: ConversationsRepository,\n cacheMetadataRepo: CacheMetadataRepository,\n opts: ShouldRefreshOptions = {},\n): boolean {\n if (conversationsRepo.hasOrphanRows()) return true;\n\n const projectsDir = opts.projectsDir ?? DEFAULT_PROJECTS_DIR;\n let dirMtimeMs: number;\n try {\n dirMtimeMs = statSync(projectsDir).mtimeMs;\n } catch {\n return false;\n }\n\n const lastIndexedIso = getCacheMetadata(cacheMetadataRepo, \"conversations_last_indexed_at\");\n if (!lastIndexedIso) return true;\n\n const lastIndexedMs = Date.parse(lastIndexedIso);\n if (Number.isNaN(lastIndexedMs)) return true;\n\n return dirMtimeMs > lastIndexedMs;\n}\n","import type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport type { SessionsRepository } from \"../../db/repositories/sessions.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\n\n/**\n * Walk every managed session and, where a session's projectPath maps to\n * an existing project but the session's projectId is missing, link it.\n *\n * Sessions are not the source of project discovery (conversations are),\n * so this never CREATES a project — only links to ones that already\n * exist. Sessions that point at a path with no matching project are left\n * unlinked; the caller can refresh the conversation cache and try again.\n */\nexport function ensureSessionProjectIdsFromExistingProjects(\n projectsRepo: ProjectsRepository,\n sessionsRepo: SessionsRepository,\n): { linked: number; missing: number } {\n let linked = 0;\n let missing = 0;\n\n for (const session of sessionsRepo.listManagedSessions()) {\n if (session.projectId) continue;\n const canonical = canonicalizeProjectPath(session.projectPath);\n if (!canonical) continue;\n\n const project = projectsRepo.getProjectByPath(canonical);\n if (!project) {\n missing += 1;\n continue;\n }\n\n sessionsRepo.updateSessionProjectId({ sessionId: session.id, projectId: project.id });\n linked += 1;\n }\n\n return { linked, missing };\n}\n","import type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport { compareIsoDesc } from \"../../utils/dates\";\n\n/**\n * Stable sort comparator for ProjectChats.\n *\n * Order:\n * 1. latestMessageAt DESC\n * 2. updatedAt DESC\n * 3. createdAt DESC\n * 4. title ASC\n */\nexport function sortProjectChats(a: ProjectChat, b: ProjectChat): number {\n const byLatest = compareIsoDesc(a.latestMessageAt, b.latestMessageAt);\n if (byLatest !== 0) return byLatest;\n\n const byUpdated = compareIsoDesc(a.updatedAt ?? null, b.updatedAt ?? null);\n if (byUpdated !== 0) return byUpdated;\n\n const byCreated = compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);\n if (byCreated !== 0) return byCreated;\n\n return a.title.localeCompare(b.title);\n}\n","import type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport { sortProjectChats } from \"./sortProjectChats\";\n\n/**\n * Merge active sessions and historical conversations into a single\n * ProjectChat list, hiding any conversation that has been resumed into\n * an active session.\n */\nexport function mergeProjectChats(args: {\n sessions: ProjectChat[];\n conversations: ProjectChat[];\n}): ProjectChat[] {\n const { sessions, conversations } = args;\n\n const resumedConversationIds = new Set<string>(\n sessions\n .filter((c): c is Extract<ProjectChat, { type: \"session\" }> => c.type === \"session\")\n .map((c) => c.resumedFromConversationId)\n .filter((id): id is string => Boolean(id)),\n );\n\n const visibleConversations = conversations.filter((c) => {\n if (c.type !== \"conversation\") return true;\n return !resumedConversationIds.has(c.id);\n });\n\n return [...sessions, ...visibleConversations].sort(sortProjectChats);\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 { ConversationListItem } from \"../../conversation-cache\";\nimport type { ConversationProjectChat } from \"../../schemas/projectChat.schema\";\nimport { deriveProjectChatTitle } from \"./deriveProjectChatTitle\";\n\n/**\n * Normalize a cached conversation row into the union ProjectChat shape.\n *\n * Resolves to a \"resumable\" conversation by default. The caller (merge\n * step) may choose to suppress conversations that have been resumed into\n * an active session.\n */\nexport function normalizeConversationToProjectChat(\n conversation: ConversationListItem,\n): ConversationProjectChat {\n if (!conversation.projectId) {\n throw new Error(\n `normalizeConversationToProjectChat: conversation ${conversation.id} has no projectId — resolve it before normalizing`,\n );\n }\n return {\n type: \"conversation\",\n id: conversation.id,\n projectId: conversation.projectId,\n projectPath: conversation.projectPath ?? null,\n title: deriveProjectChatTitle({\n title: conversation.title,\n projectName: conversation.projectName,\n projectPath: conversation.projectPath,\n id: conversation.id,\n }),\n latestMessageAt: conversation.lastActivity ?? null,\n updatedAt: conversation.lastActivity ?? null,\n createdAt: null,\n status: \"resumable\",\n source: \"hdd-cache\",\n indexedAt: null,\n fileMtime: null,\n filePath: conversation.filePath ?? null,\n sourceHash: null,\n };\n}\n","import type { SessionProjectChat } from \"../../schemas/projectChat.schema\";\nimport type { SessionResponse } from \"../../types\";\nimport { deriveProjectChatTitle } from \"./deriveProjectChatTitle\";\n\n/**\n * Normalize a SessionResponse into the union ProjectChat shape used by\n * the GET /project-chats endpoint.\n *\n * The session must already have projectId resolved by the service layer\n * (see linkSessionAndConversationToProject /\n * ensureSessionProjectIdsFromExistingProjects). This pure function does\n * NOT attempt to repair missing ids; it expects them to be present.\n */\nexport function normalizeSessionToProjectChat(session: SessionResponse): SessionProjectChat {\n if (!session.projectId) {\n throw new Error(\n `normalizeSessionToProjectChat: session ${session.id} has no projectId — resolve it before normalizing`,\n );\n }\n return {\n type: \"session\",\n id: session.id,\n projectId: session.projectId,\n projectPath: session.projectPath ?? null,\n title: deriveProjectChatTitle({\n title: session.sessionName,\n projectName: session.projectName,\n projectPath: session.projectPath,\n id: session.id,\n }),\n latestMessageAt: session.lastMessageAt ?? session.lastActivityAt ?? null,\n updatedAt: session.lastActivityAt ?? null,\n createdAt: session.startedAt ?? null,\n status: \"active\",\n source: \"session-store\",\n resumedFromConversationId: session.resumedFromConversationId ?? null,\n };\n}\n","import type { ConversationScanner } from \"@threadbase-sh/scanner\";\nimport type { ConversationCache } from \"../../conversation-cache\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport type { SessionsRepository } from \"../../db/repositories/sessions.repository\";\nimport type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport type { SessionResponse } from \"../../types\";\nimport { refreshConversationCache } from \"../conversations/refreshConversationCache\";\nimport { shouldRefreshProjectsFromHdd } from \"../conversations/shouldRefreshProjectsFromHdd\";\nimport { ensureSessionProjectIdsFromExistingProjects } from \"../sessions/ensureSessionProjectIdsFromExistingProjects\";\nimport { mergeProjectChats } from \"./mergeProjectChats\";\nimport { normalizeConversationToProjectChat } from \"./normalizeConversationToProjectChat\";\nimport { normalizeSessionToProjectChat } from \"./normalizeSessionToProjectChat\";\n\n/**\n * Subset of ConversationScanner we depend on. Typed structurally so tests\n * can pass a lightweight fake without instantiating the real scanner.\n */\nexport type ScannerLike = Pick<ConversationScanner, \"scan\" | \"getMetadataCache\">;\n\nexport interface ListProjectChatsDeps {\n cache: ConversationCache;\n projectsRepo: ProjectsRepository;\n conversationsRepo: ConversationsRepository;\n sessionsRepo: SessionsRepository;\n cacheMetadataRepo: CacheMetadataRepository;\n /** Snapshot of session responses; the server already builds these. */\n getSessionResponses: () => SessionResponse[];\n /**\n * Return a scanner whose metadata cache reflects the current disk state.\n * Called only when the cache needs a rebuild. Implementations are\n * responsible for invalidating any prior scanner and running scan()\n * before returning — listProjectChats reads getMetadataCache() directly.\n */\n getFreshScanner: () => Promise<ScannerLike>;\n /** Override the disk path checked for drift. Defaults to ~/.claude/projects. */\n projectsDir?: string;\n}\n\nexport interface ListProjectChatsArgs {\n refreshConversations: boolean;\n}\n\n/**\n * Compose the full /project-chats list:\n *\n * 1. Force-refresh or detect drift (orphan rows or projects-dir mtime).\n * 2. When drift detected, run the scanner + upsert into the cache so new\n * JSONLs become visible. Then run project_id backfill.\n * 3. Ensure managed sessions are linked to existing projects.\n * 4. Normalize sessions + conversations into ProjectChat shape.\n * 5. Merge, dedupe (resumed conversations), sort.\n */\nexport async function listProjectChats(\n deps: ListProjectChatsDeps,\n args: ListProjectChatsArgs,\n): Promise<ProjectChat[]> {\n const {\n cache,\n projectsRepo,\n conversationsRepo,\n sessionsRepo,\n cacheMetadataRepo,\n getSessionResponses,\n getFreshScanner,\n projectsDir,\n } = deps;\n\n const needsRefresh =\n args.refreshConversations ||\n shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, { projectsDir });\n\n if (needsRefresh) {\n const scanner = await getFreshScanner();\n const metas = [...scanner.getMetadataCache().values()];\n if (metas.length > 0) {\n cache.upsertFromScannerMeta(metas as never);\n }\n refreshConversationCache({ cache, projectsRepo, conversationsRepo, cacheMetadataRepo });\n }\n\n ensureSessionProjectIdsFromExistingProjects(projectsRepo, sessionsRepo);\n\n const sessionResponses = getSessionResponses();\n const sessionChats: ProjectChat[] = [];\n for (const s of sessionResponses) {\n if (!s.projectId) continue; // skip sessions still missing a project link\n sessionChats.push(normalizeSessionToProjectChat(s));\n }\n\n const conversationChats: ProjectChat[] = [];\n const { conversations } = cache.listConversations({ limit: 1000, offset: 0 });\n for (const c of conversations) {\n if (!c.projectId) continue;\n conversationChats.push(normalizeConversationToProjectChat(c));\n }\n\n return mergeProjectChats({ sessions: sessionChats, conversations: conversationChats });\n}\n","import type { ServerResponse } from \"http\";\nimport { ListProjectChatsQuerySchema } from \"../schemas/queryParams.schema\";\nimport type { ListProjectChatsDeps } from \"../services/projectChats/listProjectChats\";\nimport { listProjectChats } from \"../services/projectChats/listProjectChats\";\n\n/**\n * GET /project-chats\n *\n * Returns the unified active-sessions + historical-conversations list.\n *\n * ?refreshConversations=1 → force a full conversation/projects refresh\n * ?refresh=1 → legacy alias, same effect\n */\nexport async function handleListProjectChats(\n url: URL,\n res: ServerResponse,\n deps: ListProjectChatsDeps,\n): Promise<void> {\n const queryObj = Object.fromEntries(url.searchParams.entries());\n const parsed = ListProjectChatsQuerySchema.safeParse(queryObj);\n\n if (!parsed.success) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Invalid query parameters\",\n details: parsed.error.flatten(),\n }),\n );\n return;\n }\n\n const refreshConversations =\n parsed.data.refreshConversations === \"1\" || parsed.data.refresh === \"1\";\n\n const chats = await listProjectChats(deps, { refreshConversations });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ projectChats: chats }));\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","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 { 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","join","readFileSync","dirname","join","pool","platform","dirname","homedir","join","platform","resolve","dirname","existsSync","basename","basename","resolve","existsSync","existsSync","readdirSync","readFileSync","statSync","realpath","homedir","dirname","join","crypto","crypto","log","existsSync","join","join","existsSync","Hono","Hono","Hono","ALREADY_HANDLED","alreadyHandled","Hono","readFileSync","dirname","join","dirname","join","readFileSync","Hono","timingSafeEqual","Hono","readFileSync","homedir","join","join","homedir","readFileSync","resolve","readRawBody","timingSafeEqual","Hono","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","Hono","mkdir","join","closeSync","existsSync","mkdirSync","openSync","readSync","statSync","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","getMigrationsDir","cached","mkdirSync","dirname","run","statSync","openSync","readSync","closeSync","existsSync","randomUUID","randomUUID","pool","z","statSync","homedir","join","join","homedir","statSync","randomBytes","statSync","stat","existsSync","existsSync","randomBytes","mkdir","join","run","join","homedir","realpath","dirname","resolve","ALREADY_HANDLED","hostname","total","adapted","stat","existsSync","readdirSync","filePath","statSync","availability","messagesPayload","resp","cached","body","readFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../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/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/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/schemas/queryParams.schema.ts","../src/services/cache/cacheMetadata.ts","../src/utils/dates.ts","../src/services/projects/ensureProjectsForConversations.ts","../src/services/conversations/refreshConversationCache.ts","../src/services/conversations/shouldRefreshProjectsFromHdd.ts","../src/services/sessions/ensureSessionProjectIdsFromExistingProjects.ts","../src/services/projectChats/sortProjectChats.ts","../src/services/projectChats/mergeProjectChats.ts","../src/services/projectChats/deriveProjectChatTitle.ts","../src/services/projectChats/normalizeConversationToProjectChat.ts","../src/services/projectChats/normalizeSessionToProjectChat.ts","../src/services/projectChats/listProjectChats.ts","../src/handlers/handleListProjectChats.ts","../src/pair-store.ts","../src/seal.ts","../src/services/conversations/conversationWatcher.ts","../src/services/conversations/pruneAgentConversations.ts","../src/session-store.ts","../src/uploads.ts","../src/utils/conversationEtag.ts","../src/utils/debounce.ts","../src/utils/isScannedSnapshotStale.ts","../src/utils/scanProgressThrottle.ts","../src/ws-hub.ts"],"sourcesContent":["// 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 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 // 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.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 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 }\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\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 }\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","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 {\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 loadBrowseRoot,\n loadCacheDir,\n loadPublicUrl,\n loadTailSize,\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 { handleListProjectChats } from \"./handlers/handleListProjectChats\";\nimport { getLogger } from \"./logger\";\nimport { PairTokenStore } from \"./pair-store\";\nimport { discoverClaudeProcesses } from \"./process-discovery\";\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 { SessionStore } from \"./session-store\";\nimport type {\n DiscoveredProcess,\n ServerConfig,\n SessionSortKey,\n SortOrder as SessionSortOrder,\n SessionStatus,\n} from \"./types\";\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_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 scanner: ConversationScanner | null = null;\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 localNoAuth: 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 ptyGracePeriodMs: number;\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 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\n constructor(config: ServerConfig & { apiKey: string }) {\n this.apiKey = config.apiKey;\n this.localNoAuth = config.localNoAuth ?? false;\n this.verbose = config.verbose ?? false;\n this.disableDb = config.disableDb ?? false;\n this.scanProfiles = config.scanProfiles;\n this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;\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 // 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 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 }\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 },\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 apiDeps: ApiDeps = {\n apiKey: this.apiKey,\n localNoAuth: this.localNoAuth,\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 handleCancel: (id, res) => this.handleCancel(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 handleGetPopularProjects: (url, res) => this.handleGetPopularProjects(url, res),\n handleListProjectChats: (url, res) => this.handleListProjectChats(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 this.sessionSubscribers.delete(sessionId);\n if (this.ptyManager.hasSession(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 resp = this.sessionStore.get(sessionId, this.ptyAttachedIds());\n if (resp) this.wsHub.broadcast({ type: \"session_update\", session: resp });\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 ~/.claude/projects so new JSONL files created after startup\n // (e.g. resumed sessions, new conversations from other devices) are\n // discovered: onConversationChanged will invalidate the scanner and\n // cache so the next search/list picks them up without a restart.\n const projectsDir = join(homedir(), \".claude\", \"projects\");\n this.fileWatcher.watchDirectory(projectsDir);\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 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 warmupScanner\n .scan({\n ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\n ...(warmupStatCache ? { statCache: warmupStatCache } : {}),\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 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 checkExchangeRateLimit(ip: string): boolean {\n const now = Date.now();\n const windowMs = 60_000;\n const limit = 5;\n const arr = (this.exchangeAttempts.get(ip) ?? []).filter((t) => now - t < windowMs);\n if (arr.length >= limit) {\n this.exchangeAttempts.set(ip, arr);\n return false;\n }\n arr.push(now);\n this.exchangeAttempts.set(ip, arr);\n return true;\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 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({ project, limit, offset });\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 }));\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 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 };\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 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 { total } = this.cache.listConversations({ project, limit: 0, offset: 0 });\n json(res, 200, { 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 json(res, 200, { total: metas.length });\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 async handleListProjectChats(url: URL, res: ServerResponse): Promise<void> {\n if (\n !this.cache ||\n !this.projectsRepo ||\n !this.conversationsRepo ||\n !this.sessionsRepo ||\n !this.cacheMetadataRepo\n ) {\n json(res, 503, { error: \"Cache not available\" });\n return;\n }\n\n await handleListProjectChats(url, res, {\n cache: this.cache,\n projectsRepo: this.projectsRepo,\n conversationsRepo: this.conversationsRepo,\n sessionsRepo: this.sessionsRepo,\n cacheMetadataRepo: this.cacheMetadataRepo,\n getSessionResponses: () => this.sessionStore.list(this.ptyAttachedIds()),\n getFreshScanner: () => this.getFreshScanner(),\n });\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 // 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.scannerReady = this.scanner.scan({\n ...(this.scanProfiles ? { profiles: this.scanProfiles } : {}),\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 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 resumable: 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 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 resumable: 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 },\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 }));\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.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 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 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 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 } = 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 try {\n const session = await this.ptyManager.startFresh({\n projectPath: resolvedPath,\n projectName: body.projectName,\n systemPrompt: BROWSE_SYSTEM_PROMPT(this.browseRoot),\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 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 resumable: 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'\nimport { urlAlphabet } from './url-alphabet/index.js'\nconst POOL_SIZE_MULTIPLIER = 128\nlet pool, poolOffset\nlet fillPool = bytes => {\n if (bytes < 0 || bytes > 1024) throw new RangeError('Wrong ID size')\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 poolOffset += bytes\n}\nlet random = bytes => {\n fillPool((bytes |= 0))\n return pool.subarray(poolOffset - bytes, poolOffset)\n}\nlet customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1\n let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length)\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}\nlet customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size, random)\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}\nexport { nanoid, customAlphabet, customRandom, urlAlphabet, random }\n","let urlAlphabet =\n 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\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 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\nexport const corsMiddleware = (): MiddlewareHandler<AppEnv> => async (c, next) => {\n c.res.headers.set(\"Access-Control-Allow-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 if (c.req.method === \"OPTIONS\") {\n return c.newResponse(null, 204);\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<ApiDeps, \"publicUrl\" | \"sessionStore\" | \"ptyAttachedIds\">,\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/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(\"/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 app.get(\"/project-chats\", async (c) => {\n const url = new URL(c.req.url);\n await deps.handleListProjectChats(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/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.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 {\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}\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}\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 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 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)\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)\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 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 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 });\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; limit: number; offset: number }): {\n conversations: ConversationListItem[];\n total: number;\n } {\n const { project, 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 {\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 })),\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 };\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","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 { z } from \"zod\";\n\n/**\n * Query parameters accepted by GET /project-chats.\n *\n * refreshConversations=1 → force a full conversation/projects refresh.\n * refresh=1 → legacy alias kept for compatibility with the\n * existing /api/conversations refresh semantic.\n */\nexport const ListProjectChatsQuerySchema = z.object({\n refresh: z.enum([\"1\"]).optional(),\n refreshConversations: z.enum([\"1\"]).optional(),\n});\n\nexport type ListProjectChatsQuery = z.infer<typeof ListProjectChatsQuerySchema>;\n","import type {\n CacheMetadataKey,\n CacheMetadataRepository,\n} from \"../../db/repositories/cacheMetadata.repository\";\n\n/**\n * Functional accessors over the cache_metadata table. Repositories give us\n * the SQL surface; these helpers give us the verbs the services use.\n */\nexport function getCacheMetadata(\n repo: CacheMetadataRepository,\n key: CacheMetadataKey,\n): string | null {\n return repo.getCacheMetadata(key);\n}\n\nexport function setCacheMetadata(\n repo: CacheMetadataRepository,\n key: CacheMetadataKey,\n value: string,\n): void {\n repo.setCacheMetadata(key, value);\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 type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\nimport { compareIsoDesc } from \"../../utils/dates\";\n\nexport interface ConversationLikeForProjects {\n id: string;\n projectPath: string | null | undefined;\n createdAt?: string | null;\n latestMessageAt?: string | null;\n}\n\n/**\n * Group cached conversations by their canonical project path, find the\n * latest conversation per project, then upsert one project row per unique\n * path. Returns a map from canonical path → projectId so callers can\n * backfill conversation/session project_id columns afterwards.\n */\nexport function ensureProjectsForConversations(\n repo: ProjectsRepository,\n conversations: ConversationLikeForProjects[],\n): Map<string, string> {\n const conversationsByPath = new Map<string, ConversationLikeForProjects[]>();\n\n for (const conversation of conversations) {\n if (!conversation.projectPath) continue;\n const canonical = canonicalizeProjectPath(conversation.projectPath);\n if (!canonical) continue;\n const existing = conversationsByPath.get(canonical) ?? [];\n existing.push(conversation);\n conversationsByPath.set(canonical, existing);\n }\n\n const pathToProjectId = new Map<string, string>();\n\n for (const [path, projectConversations] of conversationsByPath) {\n const latest = pickLatestConversation(projectConversations);\n\n const project = repo.upsertProjectByPath(path, {\n lastConversationId: latest?.id ?? null,\n lastConversationCreatedAt: latest?.createdAt ?? null,\n latestMessageAt: latest?.latestMessageAt ?? null,\n });\n\n pathToProjectId.set(path, project.id);\n }\n\n return pathToProjectId;\n}\n\nfunction pickLatestConversation(\n conversations: ConversationLikeForProjects[],\n): ConversationLikeForProjects | undefined {\n if (conversations.length === 0) return undefined;\n return [...conversations].sort((a, b) => {\n const cmp = compareIsoDesc(a.latestMessageAt ?? null, b.latestMessageAt ?? null);\n if (cmp !== 0) return cmp;\n return compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);\n })[0];\n}\n","import type { ConversationCache } from \"../../conversation-cache\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\nimport { setCacheMetadata } from \"../cache/cacheMetadata\";\nimport { ensureProjectsForConversations } from \"../projects/ensureProjectsForConversations\";\n\nexport interface RefreshConversationCacheDeps {\n cache: ConversationCache;\n projectsRepo: ProjectsRepository;\n conversationsRepo: ConversationsRepository;\n cacheMetadataRepo: CacheMetadataRepository;\n}\n\nexport interface RefreshConversationCacheResult {\n projectsTouched: number;\n conversationsBackfilled: number;\n latestConversationId: string | null;\n}\n\n/**\n * After a scanner-driven cache rebuild has run, walk the cache to:\n * 1. Upsert one project row per unique canonical project_path.\n * 2. Backfill conversation_meta.project_id for any rows missing it.\n * 3. Update cache_metadata.last_conversation_id so subsequent freshness\n * checks can short-circuit.\n *\n * The scanner itself runs in `server.ts` today; this function picks up\n * after it has populated `conversation_meta`.\n */\nexport function refreshConversationCache(\n deps: RefreshConversationCacheDeps,\n): RefreshConversationCacheResult {\n const { projectsRepo, conversationsRepo, cacheMetadataRepo } = deps;\n\n const conversations = conversationsRepo.listConversationsForProjectBackfill();\n\n const pathToProjectId = ensureProjectsForConversations(\n projectsRepo,\n conversations.map((c) => ({\n id: c.id,\n projectPath: c.projectPath,\n latestMessageAt: c.lastActivity ?? null,\n createdAt: c.lastActivity ?? null,\n })),\n );\n\n let conversationsBackfilled = 0;\n for (const conversation of conversations) {\n if (!conversation.projectPath) continue;\n if (conversation.projectId) continue;\n const projectId = pathToProjectId.get(canonicalizeProjectPath(conversation.projectPath));\n if (!projectId) continue;\n conversationsRepo.updateConversationProjectId({\n conversationId: conversation.id,\n projectId,\n });\n conversationsBackfilled += 1;\n }\n\n const latest = conversationsRepo.getLatestConversation();\n if (latest) {\n setCacheMetadata(cacheMetadataRepo, \"last_conversation_id\", latest.id);\n if (latest.lastActivity) {\n setCacheMetadata(cacheMetadataRepo, \"last_conversation_created_at\", latest.lastActivity);\n }\n }\n setCacheMetadata(cacheMetadataRepo, \"conversations_last_indexed_at\", new Date().toISOString());\n\n return {\n projectsTouched: pathToProjectId.size,\n conversationsBackfilled,\n latestConversationId: latest?.id ?? null,\n };\n}\n","import { statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport { getCacheMetadata } from \"../cache/cacheMetadata\";\n\nconst DEFAULT_PROJECTS_DIR = join(homedir(), \".claude\", \"projects\");\n\nexport interface ShouldRefreshOptions {\n /** Override the disk path checked for drift. Defaults to ~/.claude/projects. */\n projectsDir?: string;\n}\n\n/**\n * Decide whether the projects/conversations cache needs a refresh.\n *\n * Returns true when either:\n * 1. The cache has orphan rows — conversations with a project_path but no\n * project_id. These need a backfill pass to become visible to\n * /project-chats (which filters out null project_id).\n * 2. The projects dir on disk has changed since the last scanner pass —\n * its mtime is newer than cache_metadata.conversations_last_indexed_at.\n * This catches new JSONLs that the file watcher missed.\n *\n * The chokidar watcher only tails JSONLs for managed PTY sessions, so\n * conversations created outside the streamer (or before it started) never\n * trigger the watcher. The mtime check is the safety net.\n */\nexport function shouldRefreshProjectsFromHdd(\n conversationsRepo: ConversationsRepository,\n cacheMetadataRepo: CacheMetadataRepository,\n opts: ShouldRefreshOptions = {},\n): boolean {\n if (conversationsRepo.hasOrphanRows()) return true;\n\n const projectsDir = opts.projectsDir ?? DEFAULT_PROJECTS_DIR;\n let dirMtimeMs: number;\n try {\n dirMtimeMs = statSync(projectsDir).mtimeMs;\n } catch {\n return false;\n }\n\n const lastIndexedIso = getCacheMetadata(cacheMetadataRepo, \"conversations_last_indexed_at\");\n if (!lastIndexedIso) return true;\n\n const lastIndexedMs = Date.parse(lastIndexedIso);\n if (Number.isNaN(lastIndexedMs)) return true;\n\n return dirMtimeMs > lastIndexedMs;\n}\n","import type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport type { SessionsRepository } from \"../../db/repositories/sessions.repository\";\nimport { canonicalizeProjectPath } from \"../../utils/canonicalizeProjectPath\";\n\n/**\n * Walk every managed session and, where a session's projectPath maps to\n * an existing project but the session's projectId is missing, link it.\n *\n * Sessions are not the source of project discovery (conversations are),\n * so this never CREATES a project — only links to ones that already\n * exist. Sessions that point at a path with no matching project are left\n * unlinked; the caller can refresh the conversation cache and try again.\n */\nexport function ensureSessionProjectIdsFromExistingProjects(\n projectsRepo: ProjectsRepository,\n sessionsRepo: SessionsRepository,\n): { linked: number; missing: number } {\n let linked = 0;\n let missing = 0;\n\n for (const session of sessionsRepo.listManagedSessions()) {\n if (session.projectId) continue;\n const canonical = canonicalizeProjectPath(session.projectPath);\n if (!canonical) continue;\n\n const project = projectsRepo.getProjectByPath(canonical);\n if (!project) {\n missing += 1;\n continue;\n }\n\n sessionsRepo.updateSessionProjectId({ sessionId: session.id, projectId: project.id });\n linked += 1;\n }\n\n return { linked, missing };\n}\n","import type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport { compareIsoDesc } from \"../../utils/dates\";\n\n/**\n * Stable sort comparator for ProjectChats.\n *\n * Order:\n * 1. latestMessageAt DESC\n * 2. updatedAt DESC\n * 3. createdAt DESC\n * 4. title ASC\n */\nexport function sortProjectChats(a: ProjectChat, b: ProjectChat): number {\n const byLatest = compareIsoDesc(a.latestMessageAt, b.latestMessageAt);\n if (byLatest !== 0) return byLatest;\n\n const byUpdated = compareIsoDesc(a.updatedAt ?? null, b.updatedAt ?? null);\n if (byUpdated !== 0) return byUpdated;\n\n const byCreated = compareIsoDesc(a.createdAt ?? null, b.createdAt ?? null);\n if (byCreated !== 0) return byCreated;\n\n return a.title.localeCompare(b.title);\n}\n","import type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport { sortProjectChats } from \"./sortProjectChats\";\n\n/**\n * Merge active sessions and historical conversations into a single\n * ProjectChat list, hiding any conversation that has been resumed into\n * an active session.\n */\nexport function mergeProjectChats(args: {\n sessions: ProjectChat[];\n conversations: ProjectChat[];\n}): ProjectChat[] {\n const { sessions, conversations } = args;\n\n const resumedConversationIds = new Set<string>(\n sessions\n .filter((c): c is Extract<ProjectChat, { type: \"session\" }> => c.type === \"session\")\n .map((c) => c.resumedFromConversationId)\n .filter((id): id is string => Boolean(id)),\n );\n\n const visibleConversations = conversations.filter((c) => {\n if (c.type !== \"conversation\") return true;\n return !resumedConversationIds.has(c.id);\n });\n\n return [...sessions, ...visibleConversations].sort(sortProjectChats);\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 { ConversationListItem } from \"../../conversation-cache\";\nimport type { ConversationProjectChat } from \"../../schemas/projectChat.schema\";\nimport { deriveProjectChatTitle } from \"./deriveProjectChatTitle\";\n\n/**\n * Normalize a cached conversation row into the union ProjectChat shape.\n *\n * Resolves to a \"resumable\" conversation by default. The caller (merge\n * step) may choose to suppress conversations that have been resumed into\n * an active session.\n */\nexport function normalizeConversationToProjectChat(\n conversation: ConversationListItem,\n): ConversationProjectChat {\n if (!conversation.projectId) {\n throw new Error(\n `normalizeConversationToProjectChat: conversation ${conversation.id} has no projectId — resolve it before normalizing`,\n );\n }\n return {\n type: \"conversation\",\n id: conversation.id,\n projectId: conversation.projectId,\n projectPath: conversation.projectPath ?? null,\n title: deriveProjectChatTitle({\n title: conversation.title,\n projectName: conversation.projectName,\n projectPath: conversation.projectPath,\n id: conversation.id,\n }),\n latestMessageAt: conversation.lastActivity ?? null,\n updatedAt: conversation.lastActivity ?? null,\n createdAt: null,\n status: \"resumable\",\n source: \"hdd-cache\",\n indexedAt: null,\n fileMtime: null,\n filePath: conversation.filePath ?? null,\n sourceHash: null,\n };\n}\n","import type { SessionProjectChat } from \"../../schemas/projectChat.schema\";\nimport type { SessionResponse } from \"../../types\";\nimport { deriveProjectChatTitle } from \"./deriveProjectChatTitle\";\n\n/**\n * Normalize a SessionResponse into the union ProjectChat shape used by\n * the GET /project-chats endpoint.\n *\n * The session must already have projectId resolved by the service layer\n * (see linkSessionAndConversationToProject /\n * ensureSessionProjectIdsFromExistingProjects). This pure function does\n * NOT attempt to repair missing ids; it expects them to be present.\n */\nexport function normalizeSessionToProjectChat(session: SessionResponse): SessionProjectChat {\n if (!session.projectId) {\n throw new Error(\n `normalizeSessionToProjectChat: session ${session.id} has no projectId — resolve it before normalizing`,\n );\n }\n return {\n type: \"session\",\n id: session.id,\n projectId: session.projectId,\n projectPath: session.projectPath ?? null,\n title: deriveProjectChatTitle({\n title: session.sessionName,\n projectName: session.projectName,\n projectPath: session.projectPath,\n id: session.id,\n }),\n latestMessageAt: session.lastMessageAt ?? session.lastActivityAt ?? null,\n updatedAt: session.lastActivityAt ?? null,\n createdAt: session.startedAt ?? null,\n status: \"active\",\n source: \"session-store\",\n resumedFromConversationId: session.resumedFromConversationId ?? null,\n };\n}\n","import type { ConversationScanner } from \"@threadbase-sh/scanner\";\nimport type { ConversationCache } from \"../../conversation-cache\";\nimport type { CacheMetadataRepository } from \"../../db/repositories/cacheMetadata.repository\";\nimport type { ConversationsRepository } from \"../../db/repositories/conversations.repository\";\nimport type { ProjectsRepository } from \"../../db/repositories/projects.repository\";\nimport type { SessionsRepository } from \"../../db/repositories/sessions.repository\";\nimport type { ProjectChat } from \"../../schemas/projectChat.schema\";\nimport type { SessionResponse } from \"../../types\";\nimport { refreshConversationCache } from \"../conversations/refreshConversationCache\";\nimport { shouldRefreshProjectsFromHdd } from \"../conversations/shouldRefreshProjectsFromHdd\";\nimport { ensureSessionProjectIdsFromExistingProjects } from \"../sessions/ensureSessionProjectIdsFromExistingProjects\";\nimport { mergeProjectChats } from \"./mergeProjectChats\";\nimport { normalizeConversationToProjectChat } from \"./normalizeConversationToProjectChat\";\nimport { normalizeSessionToProjectChat } from \"./normalizeSessionToProjectChat\";\n\n/**\n * Subset of ConversationScanner we depend on. Typed structurally so tests\n * can pass a lightweight fake without instantiating the real scanner.\n */\nexport type ScannerLike = Pick<ConversationScanner, \"scan\" | \"getMetadataCache\">;\n\nexport interface ListProjectChatsDeps {\n cache: ConversationCache;\n projectsRepo: ProjectsRepository;\n conversationsRepo: ConversationsRepository;\n sessionsRepo: SessionsRepository;\n cacheMetadataRepo: CacheMetadataRepository;\n /** Snapshot of session responses; the server already builds these. */\n getSessionResponses: () => SessionResponse[];\n /**\n * Return a scanner whose metadata cache reflects the current disk state.\n * Called only when the cache needs a rebuild. Implementations are\n * responsible for invalidating any prior scanner and running scan()\n * before returning — listProjectChats reads getMetadataCache() directly.\n */\n getFreshScanner: () => Promise<ScannerLike>;\n /** Override the disk path checked for drift. Defaults to ~/.claude/projects. */\n projectsDir?: string;\n}\n\nexport interface ListProjectChatsArgs {\n refreshConversations: boolean;\n}\n\n/**\n * Compose the full /project-chats list:\n *\n * 1. Force-refresh or detect drift (orphan rows or projects-dir mtime).\n * 2. When drift detected, run the scanner + upsert into the cache so new\n * JSONLs become visible. Then run project_id backfill.\n * 3. Ensure managed sessions are linked to existing projects.\n * 4. Normalize sessions + conversations into ProjectChat shape.\n * 5. Merge, dedupe (resumed conversations), sort.\n */\nexport async function listProjectChats(\n deps: ListProjectChatsDeps,\n args: ListProjectChatsArgs,\n): Promise<ProjectChat[]> {\n const {\n cache,\n projectsRepo,\n conversationsRepo,\n sessionsRepo,\n cacheMetadataRepo,\n getSessionResponses,\n getFreshScanner,\n projectsDir,\n } = deps;\n\n const needsRefresh =\n args.refreshConversations ||\n shouldRefreshProjectsFromHdd(conversationsRepo, cacheMetadataRepo, { projectsDir });\n\n if (needsRefresh) {\n const scanner = await getFreshScanner();\n const metas = [...scanner.getMetadataCache().values()];\n if (metas.length > 0) {\n cache.upsertFromScannerMeta(metas as never);\n }\n refreshConversationCache({ cache, projectsRepo, conversationsRepo, cacheMetadataRepo });\n }\n\n ensureSessionProjectIdsFromExistingProjects(projectsRepo, sessionsRepo);\n\n const sessionResponses = getSessionResponses();\n const sessionChats: ProjectChat[] = [];\n for (const s of sessionResponses) {\n if (!s.projectId) continue; // skip sessions still missing a project link\n sessionChats.push(normalizeSessionToProjectChat(s));\n }\n\n const conversationChats: ProjectChat[] = [];\n const { conversations } = cache.listConversations({ limit: 1000, offset: 0 });\n for (const c of conversations) {\n if (!c.projectId) continue;\n conversationChats.push(normalizeConversationToProjectChat(c));\n }\n\n return mergeProjectChats({ sessions: sessionChats, conversations: conversationChats });\n}\n","import type { ServerResponse } from \"http\";\nimport { ListProjectChatsQuerySchema } from \"../schemas/queryParams.schema\";\nimport type { ListProjectChatsDeps } from \"../services/projectChats/listProjectChats\";\nimport { listProjectChats } from \"../services/projectChats/listProjectChats\";\n\n/**\n * GET /project-chats\n *\n * Returns the unified active-sessions + historical-conversations list.\n *\n * ?refreshConversations=1 → force a full conversation/projects refresh\n * ?refresh=1 → legacy alias, same effect\n */\nexport async function handleListProjectChats(\n url: URL,\n res: ServerResponse,\n deps: ListProjectChatsDeps,\n): Promise<void> {\n const queryObj = Object.fromEntries(url.searchParams.entries());\n const parsed = ListProjectChatsQuerySchema.safeParse(queryObj);\n\n if (!parsed.success) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n error: \"Invalid query parameters\",\n details: parsed.error.flatten(),\n }),\n );\n return;\n }\n\n const refreshConversations =\n parsed.data.refreshConversations === \"1\" || parsed.data.refresh === \"1\";\n\n const chats = await listProjectChats(deps, { refreshConversations });\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ projectChats: chats }));\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","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 { 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","join","readFileSync","dirname","join","pool","platform","dirname","homedir","join","platform","resolve","dirname","existsSync","basename","basename","resolve","existsSync","existsSync","readdirSync","readFileSync","statSync","realpath","homedir","dirname","join","crypto","crypto","log","existsSync","join","join","existsSync","Hono","Hono","Hono","ALREADY_HANDLED","alreadyHandled","Hono","readFileSync","dirname","join","dirname","join","readFileSync","Hono","timingSafeEqual","Hono","readFileSync","homedir","join","join","homedir","readFileSync","resolve","readRawBody","timingSafeEqual","Hono","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","ALREADY_HANDLED","alreadyHandled","Hono","Hono","mkdir","join","closeSync","existsSync","mkdirSync","openSync","readSync","statSync","dirname","readdirSync","readFileSync","dirname","join","fileURLToPath","getMigrationsDir","cached","mkdirSync","dirname","run","statSync","openSync","readSync","closeSync","existsSync","randomUUID","randomUUID","pool","z","statSync","homedir","join","join","homedir","statSync","randomBytes","statSync","stat","existsSync","existsSync","randomBytes","mkdir","join","run","join","homedir","realpath","dirname","resolve","ALREADY_HANDLED","hostname","total","adapted","stat","existsSync","readdirSync","filePath","statSync","availability","messagesPayload","resp","cached","body","readFileSync"]}
|