@agentproto/runtime 5.5.0 → 5.7.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tool-presenter.ts","../src/tool-call-record.ts","../src/pr-provenance.ts","../src/usage.ts","../src/transcript-writer.ts","../src/transcript-export.ts","../src/conversation-store.ts","../src/resume-strategies.ts"],"names":["join","resolve","homedir","fs","text","createReadStream","createInterface","exportClaudeCodeSession","discoverHermesSessions","exportHermesSession","discoverCodexSessions","exportCodexSession","discoverOpenCodeSessions","exportOpenCodeSession","discoverMastracodeInprocessSessions","exportMastracodeInprocessSession","discoverPiSessions","exportPiSession"],"mappings":"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","file":"resume-strategies.mjs","sourcesContent":["/**\n * Turns a raw tool-call name + args (and its eventual result) into a short,\n * human-readable line for ring-buffer / CLI / transcript rendering. Without\n * this, every tool call renders as a bare `[tool] view` with no args and no\n * outcome — this module is the one place that knows how to summarize both.\n */\n\nconst SALIENT_ARG_KEYS = [\n \"file_path\",\n \"path\",\n \"filePath\",\n \"file\",\n \"command\",\n \"pattern\",\n \"query\",\n \"q\",\n \"url\",\n \"todos\",\n \"description\",\n \"prompt\",\n] as const\n\nconst MAX_CALL_LENGTH = 120\nconst MAX_RESULT_LENGTH = 160\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction truncate(value: string, max: number): string {\n const oneLine = value.replace(/\\s+/g, \" \").trim()\n return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine\n}\n\nfunction formatArgValue(value: unknown): string {\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? \"\" : \"s\"}`\n if (isRecord(value)) return JSON.stringify(value)\n return String(value)\n}\n\nfunction pickSalientArg(args: Record<string, unknown>): string | null {\n for (const key of SALIENT_ARG_KEYS) {\n const value = args[key]\n if (value !== undefined && value !== null && value !== \"\") {\n return formatArgValue(value)\n }\n }\n return null\n}\n\ntype BespokeFormatter = (args: Record<string, unknown>) => string\n\nfunction subagentSummary(args: Record<string, unknown>): string {\n const description =\n typeof args.description === \"string\"\n ? args.description\n : typeof args.prompt === \"string\"\n ? args.prompt\n : \"subagent\"\n return `↳ subagent: ${truncate(description, MAX_CALL_LENGTH)}`\n}\n\n// Bespoke one-liners for control tools where the generic arg-sniffing\n// below would either miss the point (ScheduleWakeup's payload isn't a\n// file/command/pattern) or just repeat the tool name pointlessly\n// (TodoWrite, ExitPlanMode carry no salient single-value arg).\nconst BESPOKE_TOOLS: Record<string, BespokeFormatter> = {\n schedulewakeup: (args) => {\n const delay = args.delaySeconds ?? args.delay ?? \"?\"\n const reason = typeof args.reason === \"string\" ? args.reason : \"\"\n return reason ? `⏰ wake in ${delay}s — ${reason}` : `⏰ wake in ${delay}s`\n },\n task: subagentSummary,\n agent: subagentSummary,\n todowrite: (args) => {\n const todos = Array.isArray(args.todos) ? args.todos : []\n return `☑ todos (${todos.length})`\n },\n exitplanmode: () => \"📋 plan ready\",\n}\n\n/** A one-line human summary of a tool call, e.g. `read src/foo.ts` or `⏰ wake in 30s — checking CI`. */\nexport function formatToolCall(toolName: string, args: unknown): string {\n const name = toolName || \"tool\"\n const argsRecord = isRecord(args) ? args : {}\n\n const bespoke = BESPOKE_TOOLS[name.toLowerCase()]\n if (bespoke) return bespoke(argsRecord)\n\n const salient = pickSalientArg(argsRecord)\n if (salient !== null) {\n // Some ACP agents (e.g. claude-agent-acp) already bake the salient\n // value into a curated title — \"Read src/foo.ts\" alongside\n // arguments.path === \"src/foo.ts\". Appending it again would render\n // \"Read src/foo.ts src/foo.ts\". Only append when it adds new info.\n if (name.toLowerCase().includes(salient.toLowerCase())) {\n return truncate(name, MAX_CALL_LENGTH)\n }\n return truncate(`${name} ${salient}`, MAX_CALL_LENGTH)\n }\n\n if (Object.keys(argsRecord).length === 0) return name\n\n return truncate(`${name} ${JSON.stringify(args)}`, MAX_CALL_LENGTH)\n}\n\nfunction extractText(value: unknown): string | null {\n if (value == null) return null\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) {\n const parts = value.map(extractText).filter((v): v is string => v != null)\n return parts.length ? parts.join(\"\\n\") : null\n }\n if (isRecord(value)) {\n if (typeof value.text === \"string\") return value.text\n if (typeof value.message === \"string\") return value.message\n // ACP agents such as opencode wrap a tool's stdout as `{ output, metadata }`\n // (claude-code sends a bare string). Without this key the ring showed no\n // [tool-result] line at all for opencode, and the CI artifact-ledger\n // passthrough (`artifactMarkerLines`) never saw the delivery helper's\n // `::agentproto-artifact::` marker — `driver: artifacts=[]` on every run.\n if (typeof value.output === \"string\") return value.output\n if (Array.isArray(value.content)) return extractText(value.content)\n if (typeof value.error === \"string\") return value.error\n if (isRecord(value.error) && typeof value.error.message === \"string\") {\n return value.error.message\n }\n return null\n }\n return null\n}\n\n/**\n * A short outcome line for a completed tool call, or `null` when there's\n * nothing useful to show (e.g. an empty/void result). `toolName` is\n * accepted for parity with `formatToolCall` and future bespoke result\n * formatting, but generic text extraction covers today's cases.\n */\nexport function formatToolResult(\n toolName: string | undefined,\n result: unknown,\n isError: boolean,\n): string | null {\n void toolName\n const text = extractText(result)\n\n if (isError) {\n const message = text ?? (result != null ? JSON.stringify(result) : \"failed\")\n const firstLine = message.split(/\\r?\\n/)[0] ?? message\n return truncate(firstLine, MAX_RESULT_LENGTH)\n }\n\n if (text == null) return null\n const trimmed = text.trim()\n if (!trimmed) return null\n\n const lines = trimmed.split(/\\r?\\n/)\n if (lines.length > 1) {\n const bytes = Buffer.byteLength(trimmed, \"utf8\")\n return `${lines.length} lines, ${bytes}B`\n }\n return truncate(lines[0]!, MAX_RESULT_LENGTH)\n}\n\n/** The stdout sentinel the CI artifact-ledger delivery helper prints when it\n * creates a PR / review / comment — MUST byte-match `ARTIFACT_MARKER` in\n * `scripts/lib/artifact-ledger.mjs` (the runner-side harvester). */\nexport const ARTIFACT_MARKER = \"::agentproto-artifact::\"\n\n/** Ring lines per tool result the marker passthrough will re-emit at most —\n * a delivery tool call creates one artifact, so >1 is already unusual; the\n * cap only bounds a pathological result that echoes markers in a loop. */\nconst MAX_ARTIFACT_MARKER_LINES = 8\n\n/**\n * The `::agentproto-artifact::` marker lines buried in a tool result, if any.\n *\n * The one-line summary {@link formatToolResult} produces is deliberately\n * lossy (\"N lines, XB\") — correct for humans, fatal for the CI artifact\n * ledger: the agentflow delivery helper (`deliver-artifact.mjs`) prints its\n * marker to a tool's stdout precisely so the `agentproto-run` driver can\n * harvest created PR/review/comment ids back out of `agent_output`. This\n * extracts those lines so the ring can carry them verbatim alongside the\n * summary. Returned WITHOUT ANSI styling on purpose: the harvester\n * (`parseArtifactMarkers`) does `indexOf(marker)` then `JSON.parse` of the\n * line's remainder, so a trailing reset code would corrupt the JSON.\n */\nexport function artifactMarkerLines(result: unknown): string[] {\n const text = extractText(result)\n if (text == null || !text.includes(ARTIFACT_MARKER)) return []\n return text\n .split(/\\r?\\n/)\n .filter(line => line.includes(ARTIFACT_MARKER))\n .slice(0, MAX_ARTIFACT_MARKER_LINES)\n .map(line => line.trim())\n}\n","/**\n * ONE normalized shape for a finished tool/command call — the unification\n * target for the two disjoint logs this repo has today: `recordCommand`'s\n * `CommandLogEntry` (command-log.ts, the `command_execute` proxy path) and\n * `transcript-writer.ts`'s per-kind `tool-call`/`tool-result` records (an\n * agent's own in-process Bash/Edit/…). Both writers append a\n * `ToolCallRecord` line (tagged `kind: \"tool-call-record\"` on disk — see\n * tool-call-log.ts) to the SAME per-session `events.jsonl` they already\n * own, so `tool_calls_list` can read either without a new store.\n *\n * Deliberately excludes session-level provenance (`harness`, `origin`,\n * `callerSessionId`): that already lives once on the owning\n * `SessionDescriptor` (see sessions.ts), and duplicating it onto every\n * call-level line would just be a snapshot that can drift. `tool_calls_list`\n * joins it in at read time from the descriptor instead.\n */\n\nexport interface ToolCallRecord {\n /** The session (command or agent-cli) whose events.jsonl this call was\n * written into. */\n sessionId: string\n /** Tool/command name — an MCP tool name for the proxy path\n * (\"command_execute\"), or an ACP tool name for the in-agent path\n * (\"Bash\", \"Read\", \"Edit\", …). */\n tool: string\n /** Shell command string, when this call is shell-shaped. Present for\n * every proxy call; only present in-agent when `extractCommandArgs`\n * recognizes the tool's `arguments` as `{command, args?}` (a Bash-style\n * tool) — every other in-agent tool (Read, Edit, …) legitimately has no\n * shell command, so this stays absent rather than guessing. */\n command?: string\n args?: string[]\n /** Process exit code — only meaningful for the proxy path, which runs a\n * real subprocess. Absent for in-agent calls (ACP has no exit-code\n * concept, only `isError`). */\n exitCode?: number\n isError?: boolean\n durationMs?: number\n /** The PR this call CREATED, when the call was a successful shell-shaped\n * `gh pr create` (detected at write time via `detectShellPrCreate` in\n * pr-provenance.ts, from the command string + the call's result text).\n * This is the exact-attribution source the PR-provenance reconciler\n * prefers over branch→PR guessing: the session whose events.jsonl holds\n * this record IS the session that opened the PR, no matter what branch\n * its cwd sits on by the time the turn ends. Absent for every other\n * call. */\n createdPrUrl?: string\n createdPrNumber?: number\n ts: string\n}\n\n/**\n * Best-effort extraction of a shell-shaped `{command, args}` out of an\n * arbitrary, harness-shaped tool-call `arguments` blob (typed `unknown` at\n * the ACP boundary — see `AgentStreamEvent.arguments` in sessions.ts).\n * Only recognizes the `{command: string, args?: string[]}` shape a\n * Bash-style tool sends; every other tool shape returns `{}` rather than\n * guessing, per AGENTS.md's \"no silent caps\" ethic applied to normalization\n * (don't claim a `command` this didn't actually see).\n */\nexport function extractCommandArgs(input: unknown): { command?: string; args?: string[] } {\n if (!input || typeof input !== \"object\") return {}\n const obj = input as Record<string, unknown>\n const command = typeof obj.command === \"string\" ? obj.command : undefined\n if (command === undefined) return {}\n const argv =\n Array.isArray(obj.args) && obj.args.every(a => typeof a === \"string\")\n ? (obj.args as string[])\n : undefined\n return argv ? { command, args: argv } : { command }\n}\n","/**\n * PR provenance footer — daemon/MCP lane.\n *\n * This is the SAME footer the runner-side agentflow scripts stamp on the\n * CI-PR, review, and local-`gh_open_pr` lanes; `scripts/lib/provenance-\n * footer.mjs` re-exports {@link buildFooter}/{@link MARKER}/{@link fmtTokens}\n * from HERE so there is exactly one format, one marker, one renderer. The\n * only reason this canonical copy lives in `@agentproto/runtime` rather than\n * in `scripts/` is direction of dependency: the daemon is a package and\n * cannot import a loose script, but a script can import a package's built\n * `dist/` (it already does for `computeProvenance`).\n *\n * What the DAEMON lane adds over the runner lanes: the footer is built from\n * a live {@link SessionDescriptor} (the executor agent-cli session that ran\n * `gh pr create`) plus its supervisor/parent session, rather than from a\n * `computeProvenance` join over the worktree. So this file also owns the\n * mapping from a session descriptor to the footer's provenance shape\n * ({@link sessionFooterProvenance}) and the small pure helpers the\n * command_execute stamp path needs ({@link parseGhPrCreate},\n * {@link pickExecutorSession}, {@link appendFooterOnce}).\n *\n * Pure by construction — only `node:path`, no daemon imports, no I/O — so it\n * bundles to a standalone `dist/pr-provenance.mjs` the scripts can import\n * without pulling the rest of the runtime in. Keep it that way. The\n * side-effecting orchestration (spawning `gh`, editing the PR body, recording\n * the opened PR) lives in `pr-provenance-stamp.ts`.\n */\n\nimport { basename } from \"node:path\"\n\n/** Deterministic marker the runner owns — a reliable native-vs-legacy\n * discriminator across every lane (CI-PR, review, local, daemon). */\nexport const MARKER = \"@agentproto-bot\"\n\nexport const fmtTokens = (n: unknown): string | null => {\n if (typeof n !== \"number\" || !Number.isFinite(n)) return null\n return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)\n}\n\n/** The footer's provenance shape — field names are the renderer's, not the\n * session registry's (see {@link sessionFooterProvenance} for the mapping). */\nexport interface FooterProvenance {\n sessionId?: string\n label?: string\n /** Adapter / harness slug that ran (\"claude-code\", \"codex\", …). */\n adapter?: string\n model?: string\n /** Bound auth-profile IDENTITY — a label or ref, NEVER the credential. */\n authProfile?: string\n sandboxId?: string\n /** Supervisor / parent session that spawned the executor. */\n parentSessionId?: string\n tokensIn?: number\n tokensOut?: number\n costUsd?: number\n /** \"adapter\" | \"local\" | \"daemon\" — drives host/cwd vs run-link rendering. */\n source?: string\n host?: string\n cwd?: string\n /** The session's registered workspace slug (`SessionDescriptor.workspaceSlug`,\n * e.g. \"ts\", \"default\"). Rendered alongside `cwd`'s leaf directory name so\n * the footer identifies WHICH workspace ran the command, not just an\n * arbitrary trailing path segment — a bare `basename(cwd)` reads as\n * meaningless (or actively misleading) when the session's cwd is a\n * workspace root rather than a per-branch worktree. */\n workspaceSlug?: string\n}\n\n/**\n * Render the footer's `cwd` value. A bare `basename(cwd)` collapses to a\n * meaningless (or actively misleading) fragment whenever the session's cwd IS\n * the workspace root rather than a per-branch worktree dir — e.g. a checkout\n * literally named `ts` renders as the opaque `cwd \\`ts\\`` with no indication\n * that's a workspace name, not a worktree/branch leaf. Prefixing the\n * registered workspace slug disambiguates it, and is dropped when it's\n * identical to the leaf (the common per-worktree case) so the common case\n * stays exactly as compact as before.\n */\nfunction cwdLabel(cwd: string, workspaceSlug?: string): string {\n const leaf = basename(cwd)\n if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf\n return `${workspaceSlug}/${leaf}`\n}\n\nexport interface BuildFooterInput {\n prov?: FooterProvenance\n authMode?: string\n runId?: string\n runUrl?: string\n sha?: string\n kind?: string\n}\n\nexport const buildFooter = ({\n prov,\n authMode,\n runId,\n runUrl,\n sha,\n kind = \"review\",\n}: BuildFooterInput): string => {\n const parts = [`🤖 **${MARKER}** — ${kind}`]\n if (prov?.sessionId) {\n parts.push(`session \\`${prov.sessionId}\\`${prov.label ? ` (\\`${prov.label}\\`)` : \"\"}`)\n }\n // Engine label: \"adapter / authMode\" when an adapter ran; \"legacy fallback\n // (authMode)\" when no agent session exists at all (the API-key fallback path);\n // bare authMode only if a session ran without a resolved adapter slug.\n if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(\" / \"))\n else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : \"\"}`)\n else if (authMode) parts.push(authMode)\n // Bound auth profile identity (the \"wallet\") — daemon lane; NEVER the secret.\n if (prov?.authProfile) parts.push(`auth-profile \\`${prov.authProfile}\\``)\n if (prov?.model) parts.push(`model \\`${prov.model}\\``)\n if (prov?.sandboxId) parts.push(`e2b \\`${prov.sandboxId}\\``)\n if (prov?.parentSessionId) parts.push(`supervisor \\`${prov.parentSessionId}\\``)\n const tin = fmtTokens(prov?.tokensIn)\n const tout = fmtTokens(prov?.tokensOut)\n if (tin || tout) parts.push(`${tin ?? \"?\"} in / ${tout ?? \"?\"} out`)\n if (typeof prov?.costUsd === \"number\") {\n parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== \"adapter\" ? ` (${prov.source})` : \"\"}`)\n }\n const showLocalHostCwd = prov?.source === \"local\" || prov?.source === \"daemon\" || !runId\n if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`)\n if (showLocalHostCwd) {\n if (prov?.host) parts.push(`host \\`${prov.host}\\``)\n if (prov?.cwd) parts.push(`cwd \\`${cwdLabel(prov.cwd, prov.workspaceSlug)}\\``)\n }\n if (sha) parts.push(`sha \\`${sha}\\``)\n return `\\n\\n---\\n<sub>${parts.join(\" · \")}</sub>`\n}\n\n/** Structural subset of `SessionDescriptor` the footer needs. The runtime's\n * full descriptor satisfies this; keeping it structural is what lets this\n * module stay free of a `./sessions.js` import (and thus standalone). */\nexport interface FooterSession {\n id: string\n kind?: string\n status?: string\n startedAt?: string\n label?: string\n cwd?: string\n workspaceSlug?: string\n adapterSlug?: string\n harness?: string\n model?: string\n parentSessionId?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n auth?: { mode?: string }\n accessProfile?: { profileRef: string; label?: string }\n}\n\nexport interface SessionFooterOptions {\n /** The supervisor/parent session descriptor, when resolvable. */\n supervisor?: { id: string } | null\n host?: string\n /** Provenance source tag; defaults to \"daemon\". */\n source?: string\n}\n\n/**\n * Map a live executor session (+ its supervisor) onto the footer's provenance\n * shape and the `authMode` the renderer takes separately. The adapter is the\n * canonical harness slug, falling back to `adapterSlug`. The bound auth\n * profile is the profile's human label (or its ref), never the credential.\n */\nexport function sessionFooterProvenance(\n session: FooterSession,\n options: SessionFooterOptions = {},\n): { prov: FooterProvenance; authMode: string | undefined } {\n const prov: FooterProvenance = {\n sessionId: session.id,\n label: session.label,\n adapter: session.harness ?? session.adapterSlug,\n model: session.model,\n authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,\n parentSessionId: options.supervisor?.id,\n costUsd: session.costUsd,\n tokensIn: session.tokensIn,\n tokensOut: session.tokensOut,\n source: options.source ?? \"daemon\",\n host: options.host,\n cwd: session.cwd,\n workspaceSlug: session.workspaceSlug,\n }\n return { prov, authMode: session.auth?.mode }\n}\n\n/** Build the daemon-lane PR footer for one executor session directly. */\nexport function buildSessionPrFooter(\n session: FooterSession,\n options: SessionFooterOptions & { sha?: string } = {},\n): string {\n const { prov, authMode } = sessionFooterProvenance(session, options)\n return buildFooter({ prov, authMode, sha: options.sha, kind: \"PR\" })\n}\n\n/**\n * Append the footer to a PR body exactly once. Idempotent by the marker: a\n * body that already carries a `@agentproto-bot` footer is returned unchanged,\n * so a retried stamp (network reply lost after the edit landed) never stacks\n * a second footer.\n */\nexport function appendFooterOnce(body: string, footer: string): string {\n if (hasProvenanceFooter(body)) return body\n return `${body}${footer}`\n}\n\n/**\n * Whether a PR/review body already carries a RENDERED provenance footer — the\n * `<sub>…@agentproto-bot…</sub>` line {@link buildFooter} emits — as opposed\n * to merely MENTIONING the marker in prose. The distinction matters: a PR\n * whose description discusses the provenance machinery itself (they exist —\n * #999 explains a footer bug and quotes the marker) would otherwise read as\n * \"already stamped\" forever and never receive its real footer.\n */\nexport function hasProvenanceFooter(body: string): boolean {\n return new RegExp(`<sub>[^\\\\n]*${MARKER}`).test(body)\n}\n\n/** The rendered footer block, as {@link buildFooter} emits it (with the\n * `\\n\\n---\\n` lead-in when present). */\nconst FOOTER_BLOCK_RE = new RegExp(`(?:\\\\n+---)?\\\\n*<sub>[^\\\\n]*${MARKER}[^\\\\n]*</sub>`)\n\n/** Just the `<sub>…</sub>` span of a rendered footer within `body` (or a\n * standalone footer string, which is itself `<sub>…</sub>`-shaped) — every\n * \"read a signal out of an existing footer\" helper below shares this\n * extraction so they agree on what \"the footer\" means. Undefined when `body`\n * carries no rendered footer at all. */\nfunction extractFooterBlock(body: string): string | undefined {\n return new RegExp(`<sub>[^\\\\n]*${MARKER}[^\\\\n]*</sub>`).exec(body)?.[0]\n}\n\n/** True when a body's provenance footer already carries a spend figure. */\nexport function footerHasCost(body: string): boolean {\n const block = extractFooterBlock(body)\n return block !== undefined && /\\$\\d/.test(block)\n}\n\n/**\n * The `session \\`<id>\\`` a rendered footer names, or undefined when the body\n * carries no footer or the footer names no session (the legacy-fallback\n * shape, e.g. a bare CI API-key run with no agent session behind it).\n *\n * Used to recognize \"this footer already names ME\" — both the daemon's own\n * `buildSessionPrFooter` render and the `gh` PATH shim's thin one\n * (gh-provenance-shim.ts) render the SAME `session \\`<id>\\`` segment, so a\n * footer this returns OUR session id for is safe to enrich/upgrade: the\n * misattribution risk `hasProvenanceFooter`'s callers guard against is a\n * footer naming a DIFFERENT session, not our own.\n */\nexport function footerSessionId(body: string): string | undefined {\n const block = extractFooterBlock(body)\n if (block === undefined) return undefined\n return /session `([^`]+)`/.exec(block)?.[1]\n}\n\n/**\n * Whether `candidate` (a standalone footer, as {@link buildFooter} renders\n * it, or a full body containing one) carries a provenance signal — spend,\n * bound auth-profile identity, or token counts — that `existing` doesn't.\n * Used to decide whether replacing an already-posted footer with a fresh\n * daemon render is a genuine UPGRADE rather than a no-op re-render: the `gh`\n * PATH shim's thin footer (gh-provenance-shim.ts) can only ever render\n * session/adapter/model/host/cwd — never cost, auth-profile, or tokens, since\n * those aren't known to a bare `gh` subprocess — so a shim-stamped body\n * always has ROOM for this kind of upgrade once the daemon knows more.\n */\nexport function footerIsRicherThan(candidate: string, existing: string): boolean {\n const candidateBlock = extractFooterBlock(candidate) ?? \"\"\n const existingBlock = extractFooterBlock(existing) ?? \"\"\n const gained = (has: (block: string) => boolean) => has(candidateBlock) && !has(existingBlock)\n const hasCost = (block: string) => /\\$\\d/.test(block)\n const hasAuthProfile = (block: string) => /auth-profile `/.test(block)\n const hasTokens = (block: string) => / in \\/ .{1,20} out\\b/.test(block)\n return gained(hasCost) || gained(hasAuthProfile) || gained(hasTokens)\n}\n\n/**\n * Replace an existing footer with a fresh one (append when there is none).\n * The daemon stamps a PR the instant `gh pr create` returns — mid-turn, when\n * a claude-code/claude-sdk session has reported no usage yet (cost only\n * arrives on the turn's `result`), so the first footer has no amount. At\n * turn-end the reconciler re-renders it with what the session now knows.\n */\nexport function replaceProvenanceFooter(body: string, footer: string): string {\n if (!hasProvenanceFooter(body)) return appendFooterOnce(body, footer)\n return body.replace(FOOTER_BLOCK_RE, footer)\n}\n\n/**\n * Recognise a successful `gh pr create` from its argv + stdout and pull the\n * created PR's URL + number out. Returns null for anything that isn't a\n * PR-creating `gh` invocation, or a create whose stdout carried no PR URL.\n *\n * `gh pr create` prints the PR URL on its own line (often the last line,\n * after any advisory notices) — we take the LAST `…/pull/<n>` match so a\n * \"Warning: …/pull/…\" style notice can't shadow the real one.\n */\nexport function parseGhPrCreate(\n command: string,\n args: readonly string[],\n stdout: string,\n): { url: string; number: number } | null {\n if (basename(command) !== \"gh\") return null\n // Skip global flags; the first two non-flag tokens must be `pr` then `create`.\n const positionals = args.filter(a => !a.startsWith(\"-\"))\n if (positionals[0] !== \"pr\" || positionals[1] !== \"create\") return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(stdout)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/**\n * String-form counterpart of {@link parseGhPrCreate} for the in-agent tool\n * lane: an ACP harness's Bash-style tool reports ONE shell string (possibly\n * compound — `git push && gh pr create … | tail -1`), not an argv, so the\n * argv parser above can't see it. Detects \"this call created a PR\" from the\n * command string plus the call's recorded result text, and returns the\n * created PR (LAST `…/pull/<n>` match in the result, same shadowing rule as\n * {@link parseGhPrCreate} — `gh pr create` prints its URL after any notices).\n *\n * Quoted spans are stripped before matching so a command that merely\n * MENTIONS the phrase — `grep \"gh pr create\" src/` over a result that quotes\n * a PR url — can never read as a create. Best-effort by design: the caller\n * additionally gates on the call's own `isError` (a failed create whose\n * stderr cites an existing PR must not attribute that PR here).\n */\nexport function detectShellPrCreate(\n command: string | undefined,\n resultText: string | undefined,\n): { url: string; number: number } | null {\n if (!command || !resultText) return null\n const unquoted = command.replace(/'[^']*'/g, \" \").replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, \" \")\n if (!/(^|[\\s;&|({])gh\\s+pr\\s+create(\\s|$)/.test(unquoted)) return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(resultText)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/** `session.cwd == cwd || one contains the other` — the executor's cwd is\n * usually the worktree root while the command may run in a subdir (or vice\n * versa), so containment is checked in both directions. */\nfunction cwdRelated(sessionCwd: string, cwd: string): boolean {\n if (sessionCwd === cwd) return true\n const sep = \"/\"\n return cwd.startsWith(sessionCwd + sep) || sessionCwd.startsWith(cwd + sep)\n}\n\n/**\n * Best-effort attribution of a `command_execute` run to the executor agent\n * session that most likely issued it: the newest agent-cli session whose cwd\n * is related to the command's cwd, preferring a still-alive one. Returns\n * undefined when no agent-cli session matches (e.g. a bare shell opened the\n * PR) — the caller then skips stamping rather than emit a misleading \"legacy\n * fallback\" footer.\n */\nexport function pickExecutorSession<T extends FooterSession>(\n sessions: readonly T[],\n cwd: string,\n): T | undefined {\n const candidates = sessions.filter(\n s => s.kind === \"agent-cli\" && typeof s.cwd === \"string\" && cwdRelated(s.cwd, cwd),\n )\n if (candidates.length === 0) return undefined\n const alive = (s: T) => s.status === \"running\" || s.status === \"starting\"\n const byRecency = (a: T, b: T) => (b.startedAt ?? \"\").localeCompare(a.startedAt ?? \"\")\n const live = candidates.filter(alive).sort(byRecency)\n if (live.length > 0) return live[0]\n return [...candidates].sort(byRecency)[0]\n}\n","/**\n * Per-session usage snapshot — the single place that decides a session's\n * `costUsd` and where that number came from.\n *\n * Two cost sources feed a session:\n * - an adapter's own usage reader (`readUsage`, e.g. hermes reads its\n * state.db) or an ACP `usage_update` that carries a `cost` block — these\n * are authoritative, tagged `source: \"adapter\"`.\n * - raw input/output token counts with NO adapter cost (ACP adapters like\n * claude-code / mastracode that report tokens but not dollars) — here we\n * price them ourselves against agentproto's in-repo LLM pricing catalog,\n * tagged `source: \"computed\"`.\n *\n * When tokens exist but the model is absent from the catalog we NEVER\n * fabricate a price: `costUsd` is left undefined and the snapshot is tagged\n * `source: \"no-pricing\"`. A session with neither cost nor tokens is\n * `source: \"none\"`.\n *\n * This module is pure (the catalog lookup is injected) so the decision tree is\n * unit-testable without building the whole model catalog.\n */\n\nimport { resolvePricing } from \"@agentproto/model-catalog/llm\"\n\n/** Where a session's `costUsd` came from — see the module doc. */\nexport type UsageSource = \"adapter\" | \"computed\" | \"no-pricing\" | \"none\"\n\n/** Per-token USD prices for a model (per 1M tokens), the subset of the\n * catalog's `LLMPricing` this module needs. */\nexport interface TokenPricing {\n inputPer1M: number\n outputPer1M: number\n /** Multiplier on `inputPer1M` for cache-read tokens (Anthropic: ~0.1).\n * Absent = 1 (no discount), same default as the catalog. */\n cacheReadMultiplier?: number\n /** Multiplier on `inputPer1M` for cache-creation tokens (Anthropic:\n * ~1.25). Absent = 1. */\n cacheWriteMultiplier?: number\n}\n\n/**\n * Token-detail and activity fields shared by every usage shape below. All\n * optional and never zero-filled: absent means \"this source doesn't report\n * it\", a present 0 is a measured zero.\n *\n * - `cacheReadTokens` / `cacheWriteTokens`: prompt-cache reads / cache\n * creation, cumulative. Kept OUT of `tokensIn` (which is uncached input\n * only when a cache split is known).\n * - `reasoningTokens`: thinking/reasoning tokens, cumulative. A subset of\n * `tokensOut` for providers that bill them as output (Anthropic).\n * - `turns`: completed turns (the descriptor's `turnsCompleted`).\n * - `toolCalls`: tool calls the daemon observed, cumulative.\n * - `durationMs`: wall time spent inside turns, cumulative.\n */\nexport interface UsageDetail {\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n turns?: number\n toolCalls?: number\n durationMs?: number\n}\n\n/** The `UsageDetail` keys, in response order. */\nexport const USAGE_DETAIL_KEYS = [\n \"cacheReadTokens\",\n \"cacheWriteTokens\",\n \"reasoningTokens\",\n \"turns\",\n \"toolCalls\",\n \"durationMs\",\n] as const satisfies readonly (keyof UsageDetail)[]\n\n/** Copy only the present (`number`) `UsageDetail` fields of `src`. */\nexport function pickUsageDetail(src: UsageDetail): UsageDetail {\n const out: UsageDetail = {}\n for (const k of USAGE_DETAIL_KEYS) {\n const v = src[k]\n if (typeof v === \"number\" && Number.isFinite(v)) out[k] = v\n }\n return out\n}\n\n/** Pluggable pricing accessor — defaults to the in-repo catalog's\n * `resolvePricing`, overridable in tests. Returns undefined for an\n * unknown model (the caller then tags `no-pricing`). */\nexport type PricingResolver = (model: string) => TokenPricing | undefined\n\n/**\n * What an adapter's usage reader (`readUsage`: hermes state.db, opencode.db,\n * the claude-code transcript JSONL) returns. Every field is cumulative for\n * the session and optional; `null` from the reader means \"nothing found\".\n */\nexport interface UsageReadResult {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n}\n\n/** Where a reader should look for the session's native store — the spawn's\n * cwd and isolated provider config dir (`CLAUDE_CONFIG_DIR` for\n * claude-code). Readers that key only on the session id ignore it. */\nexport interface UsageReadContext {\n cwd?: string\n configDir?: string\n}\n\n/** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */\nexport interface UsageComputeInput extends UsageDetail {\n /** Requested model id — needed to look up per-token prices. */\n model?: string\n /** Cost the adapter reported directly (readUsage or usage_update.cost).\n * Present → authoritative, wins over token-based computation. */\n adapterCostUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n}\n\n/** The resolved usage snapshot — the shape `session_usage` returns and the\n * durable `usage_snapshot` transcript record carries. Cost/token fields are\n * omitted when absent so a missing value never reads as a measured zero. */\nexport interface SessionUsage extends UsageDetail {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n source: UsageSource\n}\n\n/**\n * A `contextUsed` greater than the reported `contextSize` cannot be tokens\n * currently occupying the context window — a window can't hold more tokens\n * than it is wide. At least one adapter's ACP server has been observed\n * putting a cumulative session-lifetime token total in this field instead\n * of a point-in-time occupancy figure (a long-running session's running\n * total can land tens of times past a fixed window, e.g. 14.2M reported\n * against a 200K window). A value that fails this check is provably not\n * what the field claims to be, so it's dropped rather than clamped: an\n * absent `contextUsed` reads as \"unmeasured,\" a clamped one would read as\n * a plausible but fabricated occupancy percentage. `contextSize` itself is\n * untouched — it's a static model property, not the corrupted signal.\n *\n * When `contextSize` isn't known yet, `contextUsed` can't be disproven —\n * it's passed through unchanged rather than discarded.\n */\nexport function plausibleContextUsed(\n contextSize: number | undefined,\n contextUsed: number | undefined,\n): number | undefined {\n if (contextUsed === undefined) return undefined\n if (contextSize !== undefined && contextUsed > contextSize) return undefined\n return contextUsed\n}\n\nconst defaultResolver: PricingResolver = model => resolvePricing(model)\n\n/** Cost of `tokens` at `pricePer1M` USD/1M tokens. */\nfunction tokenCost(tokens: number | undefined, pricePer1M: number): number {\n return tokens === undefined ? 0 : (tokens * pricePer1M) / 1_000_000\n}\n\n/**\n * Decide a session's usage snapshot + cost source from the raw signals.\n *\n * Priority: an adapter-reported cost wins; else price the tokens against the\n * catalog; else `none`. Never fabricates a price for an unpriced model.\n */\nexport function deriveSessionUsage(\n input: UsageComputeInput,\n resolve: PricingResolver = defaultResolver,\n): SessionUsage {\n // Re-validated here, not just at usage_update ingestion: a rejected\n // ingress update leaves whatever was already on the descriptor in place\n // rather than clearing it, so a stale out-of-window value written before\n // that guard existed (or reloaded from a pre-guard persisted snapshot)\n // can still reach this call at a session's exit-time recap. Same\n // reasoning as `plausibleContextUsed`'s own doc comment — drop, don't\n // clamp.\n const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed)\n const base: Omit<SessionUsage, \"source\" | \"costUsd\"> = {\n ...(input.model !== undefined ? { model: input.model } : {}),\n ...(input.tokensIn !== undefined ? { tokensIn: input.tokensIn } : {}),\n ...(input.tokensOut !== undefined ? { tokensOut: input.tokensOut } : {}),\n ...pickUsageDetail(input),\n ...(input.contextSize !== undefined ? { contextSize: input.contextSize } : {}),\n ...(contextUsed !== undefined ? { contextUsed } : {}),\n }\n\n // 1. Adapter reported a cost directly — authoritative.\n if (input.adapterCostUsd !== undefined) {\n return { ...base, costUsd: input.adapterCostUsd, source: \"adapter\" }\n }\n\n // 2. We have token counts — price them against the in-repo catalog.\n const hasTokens =\n input.tokensIn !== undefined ||\n input.tokensOut !== undefined ||\n input.cacheReadTokens !== undefined ||\n input.cacheWriteTokens !== undefined\n if (hasTokens) {\n const pricing = input.model !== undefined ? resolve(input.model) : undefined\n if (!pricing) {\n // Model absent from the catalog — surface the tokens, but NEVER\n // invent a dollar figure.\n return { ...base, source: \"no-pricing\" }\n }\n // Cache tokens are priced off the input rate with the catalog's\n // multipliers (default 1, i.e. no discount) — they're disjoint from\n // `tokensIn`, so this adds, never double counts.\n const costUsd =\n tokenCost(input.tokensIn, pricing.inputPer1M) +\n tokenCost(input.tokensOut, pricing.outputPer1M) +\n tokenCost(input.cacheReadTokens, pricing.inputPer1M * (pricing.cacheReadMultiplier ?? 1)) +\n tokenCost(input.cacheWriteTokens, pricing.inputPer1M * (pricing.cacheWriteMultiplier ?? 1))\n return { ...base, costUsd, source: \"computed\" }\n }\n\n // 3. Neither cost nor tokens — nothing measured.\n return { ...base, source: \"none\" }\n}\n\n/** Descriptor-shaped fields `projectSessionUsage` reads. */\nexport interface UsageDescriptorFields\n extends Omit<UsageDetail, \"turns\"> {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n usageSource?: UsageSource\n /** Surfaced as `turns`. */\n turnsCompleted?: number\n}\n\n/**\n * Project a session descriptor's persisted usage fields into the\n * `session_usage` response shape. Omits absent fields; defaults an\n * unstamped `usageSource` to `\"none\"`.\n */\nexport function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage {\n // Same re-validation as `deriveSessionUsage`, for the same reason: this\n // formats a descriptor that may have been reloaded from a persisted\n // snapshot written before `plausibleContextUsed` existed — a session\n // that's already dead never gets a fresh usage_update to self-correct\n // it, so a stale out-of-window value would otherwise surface forever.\n const contextUsed = plausibleContextUsed(desc.contextSize, desc.contextUsed)\n return {\n ...(desc.model !== undefined ? { model: desc.model } : {}),\n ...(desc.costUsd !== undefined ? { costUsd: desc.costUsd } : {}),\n ...(desc.tokensIn !== undefined ? { tokensIn: desc.tokensIn } : {}),\n ...(desc.tokensOut !== undefined ? { tokensOut: desc.tokensOut } : {}),\n ...pickUsageDetail({ ...desc, turns: desc.turnsCompleted }),\n ...(desc.contextSize !== undefined ? { contextSize: desc.contextSize } : {}),\n ...(contextUsed !== undefined ? { contextUsed } : {}),\n source: desc.usageSource ?? \"none\",\n }\n}\n","/**\n * Per-session structured event capture — one append-only JSONL file per\n * agent-cli session, written at the same tap point where `projectEvent`\n * (sessions.ts) flattens `StreamEvent`s into the ANSI ring buffer, but\n * BEFORE that flattening happens. Gives agentproto the Claude-Code\n * property: a daemon-owned structured transcript that survives a daemon\n * restart (the ring buffer doesn't) and that any frontend can render,\n * not just the two adapters `transcript-export.ts` knows how to re-read\n * from their own native stores.\n *\n * File layout: `~/.agentproto/sessions/<sessionId>/events.jsonl` (the\n * root is overridable via the daemon config's `sessions.eventsDir` — see\n * `setDefaultSessionsBaseDir`), one JSON object per line: `{seq, ts, kind, ...fields}`.\n *\n * PTY (`terminal`) and `command` sessions never call into this writer —\n * they have no structured event source (see report-transcript-\n * architecture.md finding C.17), so this module is only ever driven from\n * `agent-cli` sessions' `runAgentTurn`.\n */\n\nimport { createHash } from \"node:crypto\"\nimport {\n createWriteStream,\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n type WriteStream,\n} from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { isAbsolute, join } from \"node:path\"\nimport type { SessionMessageSentRecord, SessionObserver } from \"./session-observer.js\"\nimport type { SessionMessage } from \"./session-message.js\"\nimport type { AgentStreamEvent } from \"./sessions.js\"\nimport { extractCommandArgs } from \"./tool-call-record.js\"\nimport { detectShellPrCreate } from \"./pr-provenance.js\"\nimport { pickUsageDetail, type SessionUsage } from \"./usage.js\"\nimport type { McpAppToolCallRecord } from \"./mcp-apps-host.js\"\n\n/** Debounce window for flushing a buffered text-delta/thought fragment\n * that hasn't hit a newline yet. Keeps a long no-newline stream from\n * sitting in memory indefinitely without also spamming one record per\n * token — mirrors the \"coalesce, don't spam\" rule `projectEvent`\n * already applies to the ring buffer, just with a disk-durability\n * angle added. */\nconst DEBOUNCE_MS = 250\n\n/** The one default root every per-session transcript directory resolves\n * against. Overridable so tests (and the daemon's `sessions.eventsDir`\n * config key) can redirect writes away from the real home directory\n * instead of mocking `node:os`. Explicit `baseDir` arguments (tests,\n * the registry's `transcriptDir` option) always win over this. */\nlet defaultSessionsBaseDir: string | undefined\n\n/** Point the no-`baseDir` path resolution at `dir`. Called once at daemon\n * boot from the loaded config (`sessions.eventsDir`); `undefined` restores\n * the hardcoded `~/.agentproto/sessions` default. Relative paths are\n * resolved against the home directory. */\nexport function setDefaultSessionsBaseDir(dir: string | undefined): void {\n if (!dir) {\n defaultSessionsBaseDir = undefined\n return\n }\n defaultSessionsBaseDir = isAbsolute(dir) ? dir : join(homedir(), dir)\n}\n\n/** The effective default base dir — the configured one, or the hardcoded\n * `~/.agentproto/sessions`. */\nexport function defaultTranscriptBaseDir(): string {\n return defaultSessionsBaseDir ?? join(homedir(), \".agentproto\", \"sessions\")\n}\n\nexport function sessionTranscriptDir(sessionId: string, baseDir?: string): string {\n return join(baseDir ?? defaultTranscriptBaseDir(), sessionId)\n}\n\nexport function sessionEventsPath(sessionId: string, baseDir?: string): string {\n return join(sessionTranscriptDir(sessionId, baseDir), \"events.jsonl\")\n}\n\nexport function sessionAttachmentsDir(sessionId: string, baseDir?: string): string {\n return join(sessionTranscriptDir(sessionId, baseDir), \"attachments\")\n}\n\n/**\n * One content-addressed attachment materialized from an inline-bytes\n * content block (a pasted image, a document sent as base64 rather than a\n * `POST /files/upload` path reference). Deliberately mirrors AIP-58's\n * `ArtifactEntry` (`packages/workflow-runtime/src/types.ts`) field-for-field\n * — `path` here is relative to the SESSION dir the same way `ArtifactEntry.\n * path` is relative to the run dir — so a later \"promote this attachment to\n * an artifact\" step is a metadata copy, never a data migration.\n */\nexport interface AttachmentEntry {\n /** Caller-supplied name when the block carried one, else the content-\n * addressed filename — the same value as the last path segment of `path`. */\n key: string\n /** `\"attachments/<sha256>.<ext>\"`, relative to `sessionTranscriptDir`. */\n path: string\n sha256: string\n size: number\n contentType?: string\n /** Display name for the UI (file chip / image alt text) — the block's own\n * name/filename/uri when present, else the same value as `key`. */\n name: string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null\n}\n\n/** Extensions this daemon actually sees on attachments — covers the ACP\n * image wire shapes and common document types a print-arm/path-reference\n * fallback wouldn't otherwise route through here. Unknown mime types fall\n * back to `bin` rather than guessing. */\nconst EXTENSION_BY_MIME_TYPE: Record<string, string> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n \"image/svg+xml\": \"svg\",\n \"application/pdf\": \"pdf\",\n \"text/plain\": \"txt\",\n \"text/csv\": \"csv\",\n \"text/markdown\": \"md\",\n \"application/json\": \"json\",\n}\n\n/** Reverse of `EXTENSION_BY_MIME_TYPE`, for serving a materialized attachment\n * back with a correct `Content-Type` (`GET /sessions/:id/attachments/:file`\n * in http-server.ts) — the extension is all that's on disk, the mime type\n * it was written with isn't persisted separately. */\nexport function mimeTypeForExtension(ext: string): string {\n for (const [mimeType, candidateExt] of Object.entries(EXTENSION_BY_MIME_TYPE)) {\n if (candidateExt === ext) return mimeType\n }\n return \"application/octet-stream\"\n}\n\nfunction extensionFor(mimeType: string | undefined, name: string | undefined): string {\n const dot = name?.lastIndexOf(\".\") ?? -1\n if (name && dot > 0 && dot < name.length - 1) {\n return name.slice(dot + 1).toLowerCase().slice(0, 10)\n }\n if (mimeType && EXTENSION_BY_MIME_TYPE[mimeType]) return EXTENSION_BY_MIME_TYPE[mimeType]\n return \"bin\"\n}\n\n/** Inline-bytes payload recovered from a content block, or `undefined` when\n * the block carries no bytes of its own (a text block, or a path/uri\n * reference with nothing to materialize). Recognizes the two inline-bytes\n * shapes this codebase actually produces:\n * - `{type:\"image\", data, mimeType, name?}` — the ACP wire shape (see\n * CLAUDE-CODE.md, opencode's adapter) hosts send a pasted image as.\n * - `{type:\"resource\", resource:{blob, mimeType, uri?}}` — MCP's embedded-\n * resource shape, for a document sent as bytes rather than a\n * `POST /files/upload` path reference. */\nfunction extractInlineBytes(\n block: Record<string, unknown>,\n): { data: string; mimeType?: string; name?: string } | undefined {\n if (typeof block.data === \"string\" && block.data.length > 0) {\n const name =\n typeof block.name === \"string\"\n ? block.name\n : typeof block.filename === \"string\"\n ? block.filename\n : undefined\n return {\n data: block.data,\n mimeType: typeof block.mimeType === \"string\" ? block.mimeType : undefined,\n name,\n }\n }\n const resource = isRecord(block.resource) ? block.resource : undefined\n if (resource && typeof resource.blob === \"string\" && resource.blob.length > 0) {\n const uri = typeof resource.uri === \"string\" ? resource.uri : undefined\n return {\n data: resource.blob,\n mimeType: typeof resource.mimeType === \"string\" ? resource.mimeType : undefined,\n name: uri?.split(\"/\").pop(),\n }\n }\n return undefined\n}\n\n/** Writes an inline-bytes attachment to this session's content-addressed\n * attachment store (`<sessionDir>/attachments/<sha256>.<ext>`) and returns\n * its metadata. Content-addressed so re-sending the same bytes (a retry, a\n * quoted-back image) reuses the existing file instead of duplicating it.\n * Returns `undefined` on a decode/write failure — the caller falls back to\n * keeping the block's raw shape in the transcript rather than losing the\n * reference entirely (recording must never throw and fail the turn). */\nfunction materializeAttachment(\n sessionId: string,\n baseDir: string | undefined,\n inline: { data: string; mimeType?: string; name?: string },\n): AttachmentEntry | undefined {\n try {\n const bytes = Buffer.from(inline.data, \"base64\")\n const sha256 = createHash(\"sha256\").update(bytes).digest(\"hex\")\n const ext = extensionFor(inline.mimeType, inline.name)\n const filename = `${sha256}.${ext}`\n const dir = sessionAttachmentsDir(sessionId, baseDir)\n const absPath = join(dir, filename)\n if (!existsSync(absPath)) {\n mkdirSync(dir, { recursive: true })\n writeFileSync(absPath, bytes)\n }\n return {\n key: inline.name ?? filename,\n path: join(\"attachments\", filename),\n sha256,\n size: bytes.length,\n ...(inline.mimeType ? { contentType: inline.mimeType } : {}),\n name: inline.name ?? filename,\n }\n } catch (err) {\n console.warn(\n `[transcript-writer] materializeAttachment: failed to write attachment for session ` +\n `${sessionId}: ${err instanceof Error ? err.message : String(err)}`,\n )\n return undefined\n }\n}\n\n/** Highest `seq` already durably on disk for a session, or 0 when the file\n * is absent/empty. A fresh writer instance (e.g. after a daemon restart)\n * opens the existing events.jsonl in append mode, so its first record must\n * continue from this cursor rather than restarting at 1 — otherwise a\n * restart mid-session emits duplicate seq numbers and any `since`-based\n * reader (GET /sessions/:id/events) silently drops the post-restart tail.\n * Records are written in ascending `seq`, so the max is on the last valid\n * line, but a truncated/partial final line is tolerated by scanning every\n * parseable line rather than trusting the tail. */\nfunction highestSeqOnDisk(path: string): number {\n let raw: string\n try {\n raw = readFileSync(path, \"utf8\")\n } catch {\n // ENOENT (fresh session) or any read error — start from 0.\n return 0\n }\n let max = 0\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n const rec = JSON.parse(trimmed) as { seq?: unknown }\n if (typeof rec.seq === \"number\" && rec.seq > max) max = rec.seq\n } catch {\n // Ignore a malformed/partial line (e.g. a torn final write).\n }\n }\n return max\n}\n\nexport interface TranscriptWriter extends SessionObserver {\n /** Record the outgoing message for a new turn. Not an ACP `StreamEvent`\n * kind (ACP's own \"agent-prompt\" means the agent asking the human a\n * question, the opposite direction) — recorded under its own\n * \"user-prompt\" kind so turns stay reconstructable without overloading\n * that name. `opts.source` (provenance, e.g. `agent:<sessionId>` for a\n * supervisor-injected prompt) rides onto the record's `source` field.\n *\n * `opts.system`, when present, is the daemon-composed SYSTEM slice of\n * `message` (role disposition + AGENTS.md + lineage — the preamble the\n * daemon prepended to a spawned child's prompt, which the ADAPTER still\n * receives as one string). It is recorded as its own `system-prompt`\n * record AHEAD of the `user-prompt`, and that user-prompt carries only\n * the CALLER's own ask (the preamble stripped) — so viewers can fold\n * the synthesized text instead of showing it as a user bubble. Recording-\n * only; never alters what the adapter receives or how the turn runs.\n *\n * When `message` is a content block or block array (a multimodal turn),\n * the record's `text` field carries only its text blocks joined by \"\\n\"\n * (never a JSON dump); the full blocks ride on a `blocks` field, with any\n * inline-bytes block (a pasted image) materialized to this session's\n * content-addressed attachment store and swapped for a lightweight\n * `{attachmentKey, attachmentPath}` reference — see `materializeAttachment`\n * — so events.jsonl never carries raw base64. Materialized attachments\n * also collect onto a top-level `attachments` field (`AttachmentEntry[]`). */\n recordPrompt(\n sessionId: string,\n message: unknown,\n opts?: { source?: string; system?: string; messages?: readonly SessionMessage[] }\n ): void\n /** Sender-side `session-message-sent` record — see SessionObserver. */\n recordSessionMessageSent(sessionId: string, record: SessionMessageSentRecord): void\n /** Standalone `session-message` record (a message surfaced outside a turn\n * prompt, e.g. as an `inbox_wait` result) — see SessionObserver. */\n recordSessionMessage(sessionId: string, message: SessionMessage): void\n /** Record one structured stream event, ahead of `projectEvent`'s\n * flattening. Coalesces consecutive text-delta/thought chunks the same\n * way the ring buffer does. */\n recordEvent(sessionId: string, evt: AgentStreamEvent): void\n /** Record a durable `usage_snapshot` recap at a turn boundary or on\n * session exit — the cumulative cost/token/context view resolved by\n * `deriveSessionUsage`. Distinct from the high-frequency `usage_update`\n * stream: this is the aggregable turn-boundary durable record, so a\n * daemon restart doesn't lose the session's accumulated usage. */\n recordUsageSnapshot(sessionId: string, usage: SessionUsage): void\n /** Record a `tools/call` an MCP App iframe made through the daemon\n * (`mcp_app_tool_call`, mcp-apps-host.ts) — a side-channel call the\n * model didn't make, so it gets its own `kind: \"mcp_app_tool_call\"`\n * record (\"called from app UI\") rather than a harness `tool-call`.\n * Carries no args and no result. */\n recordMcpAppToolCall(sessionId: string, record: McpAppToolCallRecord): void\n /** Flush buffers and close the session's append stream. Safe to call\n * more than once (subsequent calls are no-ops) and safe to call for a\n * session that never wrote anything (also a no-op). Production call\n * sites treat this as fire-and-forget (same as `agentSession.close()`);\n * the returned promise exists so tests can await the on-disk flush. */\n close(sessionId: string): Promise<void>\n /** Close every open session stream — called from the registry's\n * synchronous shutdown path (fire-and-forget there too). */\n closeAll(): Promise<void>\n /** Subscribe to every record as it's written for one session — the\n * live-push half of `GET /sessions/:id/events/stream`'s replay-then-\n * subscribe handoff (http-server.ts). Delivers the exact object shape\n * written to disk (`{seq, ts, kind, ...}`, pre-JSON.stringify), so a\n * consumer already reducing the poll route's `events` array can reduce\n * this stream with identical logic. No backfill here — history is a\n * file read the caller does itself; this only wires the live tap.\n * Notification happens synchronously, in the same call that appends the\n * record, so a caller that subscribes before doing its own disk read is\n * guaranteed to observe (via this callback) every record from that\n * point on, even ones the read might otherwise race. Returns an\n * unsubscribe fn; safe to call more than once. */\n subscribe(sessionId: string, onRecord: (record: Record<string, unknown>) => void): () => void\n}\n\ninterface WriterState {\n stream: WriteStream\n seq: number\n textBuf: string\n thoughtBuf: string\n textDebounce: ReturnType<typeof setTimeout> | null\n thoughtDebounce: ReturnType<typeof setTimeout> | null\n /**\n * Latest un-written tool-call ENRICHMENT per toolCallId — the same\n * coalescing textBuf/thoughtBuf do, for the same reason.\n *\n * An ACP agent streams a tool's `rawInput` as the model types it, so one\n * call arrives as a run of growing updates:\n * {} → {adapter} → {adapter,cwd} → {adapter,cwd,role} → {…,prompt} → …\n * Writing each would put six records on disk for one `agent_start`, and\n * only the last is true. They're superseding snapshots, not increments:\n * keep the newest and write it once, when something else needs ordering\n * around it (see flushBuffers).\n */\n toolUpdates: Map<string, Record<string, unknown>>\n /**\n * Latest known {toolName, arguments} + announce time per in-flight\n * toolCallId, kept across BOTH the announcement and any enrichment\n * updates (not just the held/coalesced ones above) — the \"tool-result\"\n * handler consumes this to emit ONE normalized `tool-call-record` line\n * (see tool-call-record.ts) per finished call, since a bare \"tool-result\"\n * event carries neither the tool's name nor its input.\n */\n toolCallMeta: Map<string, { toolName: string; arguments: unknown; startedAt: number }>\n}\n\nexport function createTranscriptWriter(opts?: { baseDir?: string }): TranscriptWriter {\n const baseDir = opts?.baseDir\n const states = new Map<string, WriterState>()\n const listeners = new Map<string, Set<(record: Record<string, unknown>) => void>>()\n\n const getState = (sessionId: string): WriterState => {\n const existing = states.get(sessionId)\n if (existing) return existing\n mkdirSync(sessionTranscriptDir(sessionId, baseDir), { recursive: true })\n const eventsPath = sessionEventsPath(sessionId, baseDir)\n // Resume the seq counter from what's already durable so a restart\n // mid-session keeps events.jsonl monotonic in append mode.\n const resumeSeq = highestSeqOnDisk(eventsPath)\n const stream = createWriteStream(eventsPath, { flags: \"a\" })\n stream.on(\"error\", err => {\n console.warn(`[transcript-writer] session ${sessionId}: ${err.message}`)\n })\n const state: WriterState = {\n stream,\n seq: resumeSeq,\n textBuf: \"\",\n thoughtBuf: \"\",\n textDebounce: null,\n thoughtDebounce: null,\n toolUpdates: new Map(),\n toolCallMeta: new Map(),\n }\n states.set(sessionId, state)\n return state\n }\n\n const writeRecord = (\n sessionId: string,\n state: WriterState,\n record: Record<string, unknown>\n ): void => {\n state.seq += 1\n const full = { seq: state.seq, ts: new Date().toISOString(), ...record }\n state.stream.write(JSON.stringify(full) + \"\\n\")\n const subs = listeners.get(sessionId)\n if (subs) {\n for (const onRecord of subs) onRecord(full)\n }\n }\n\n const flushTextBuf = (sessionId: string, state: WriterState): void => {\n if (state.textDebounce) {\n clearTimeout(state.textDebounce)\n state.textDebounce = null\n }\n if (!state.textBuf) return\n const text = state.textBuf\n state.textBuf = \"\"\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text })\n }\n\n const flushThoughtBuf = (sessionId: string, state: WriterState): void => {\n if (state.thoughtDebounce) {\n clearTimeout(state.thoughtDebounce)\n state.thoughtDebounce = null\n }\n if (!state.thoughtBuf) return\n const text = state.thoughtBuf\n state.thoughtBuf = \"\"\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text })\n }\n\n /** Write the newest held enrichment for each in-flight tool call. */\n const flushToolUpdates = (sessionId: string, state: WriterState): void => {\n if (state.toolUpdates.size === 0) return\n const held = [...state.toolUpdates.values()]\n state.toolUpdates.clear()\n for (const record of held) writeRecord(sessionId, state, record)\n }\n\n /** Flush every coalescing buffer as FINAL (non-partial) records — called\n * before any other event so on-disk order matches wall-clock order even\n * when a partial line, or a still-growing tool input, was still held. */\n const flushBuffers = (sessionId: string, state: WriterState): void => {\n flushThoughtBuf(sessionId, state)\n flushTextBuf(sessionId, state)\n flushToolUpdates(sessionId, state)\n }\n\n const scheduleTextDebounce = (sessionId: string, state: WriterState): void => {\n if (state.textDebounce) clearTimeout(state.textDebounce)\n state.textDebounce = setTimeout(() => {\n state.textDebounce = null\n if (!state.textBuf) return\n const text = state.textBuf\n state.textBuf = \"\"\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text, partial: true })\n }, DEBOUNCE_MS)\n }\n\n const scheduleThoughtDebounce = (sessionId: string, state: WriterState): void => {\n if (state.thoughtDebounce) clearTimeout(state.thoughtDebounce)\n state.thoughtDebounce = setTimeout(() => {\n state.thoughtDebounce = null\n if (!state.thoughtBuf) return\n const text = state.thoughtBuf\n state.thoughtBuf = \"\"\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text, partial: true })\n }, DEBOUNCE_MS)\n }\n\n return {\n recordPrompt(sessionId, message, opts) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n // Content blocks (image/resource) — `opts.system` is only ever set for\n // a plain-string composed prompt (a block-shaped `agent_start` prompt\n // SKIPS preamble composition entirely, see `SpawnAgentSessionInput.\n // prompt`'s doc), so the string-based preamble-stripping below stays\n // string-only and untouched by the block case.\n const blocks: unknown[] | undefined =\n typeof message === \"string\" ? undefined : Array.isArray(message) ? message : [message]\n // Readable text for downstream consumers (session-titler, transcript\n // previews) that just want a string — pulled from `text` blocks only,\n // same convention as `session-title.ts`'s own extraction. Non-text\n // blocks are never stringified into this field; they're recorded\n // structurally below instead (`blocks`/`attachments`).\n const text =\n typeof message === \"string\"\n ? message\n : (blocks ?? [])\n .filter((b): b is Record<string, unknown> => isRecord(b) && typeof b.text === \"string\")\n .map(b => b.text as string)\n .join(\"\\n\")\n // The daemon-composed SYSTEM slice of a spawned child's initial\n // prompt is recorded apart from the caller's ask. The user-prompt\n // text is the composed string with that preamble stripped (the\n // preamble was prepended verbatim at composition, joined by \"\\n\\n\").\n if (opts?.system) {\n writeRecord(sessionId, state, { kind: \"system-prompt\", sessionId, text: opts.system })\n }\n // Strip the system preamble off the front of `text` to get the\n // caller's own ask. Guarded by an explicit prefix check (not just a\n // length comparison) so a future change to the composition format\n // (session-spawn.ts's `composedPreamble`/`effectivePrompt` join) can't\n // silently slice the wrong bytes here — if the invariant doesn't\n // hold, fall back to recording the FULL text rather than producing\n // corrupted output.\n let userText = text\n if (opts?.system) {\n if (text.startsWith(opts.system)) {\n userText = text.slice(opts.system.length).replace(/^\\n\\n/, \"\")\n } else {\n console.warn(\n `[transcript-writer] recordPrompt: system preamble is not a prefix of the composed ` +\n `prompt for session ${sessionId} — composition invariant violated (see ` +\n `session-spawn.ts's composedPreamble), recording the full text as user-prompt ` +\n `instead of risking a bad slice.`,\n )\n }\n }\n // A typed-message turn is recorded as one `session-message` record per\n // delivered message — the daemon-attested envelope — and NEVER as a\n // `user-prompt`, so no reader can mistake it for the human.\n if (opts?.messages?.length) {\n for (const message of opts.messages) {\n writeRecord(sessionId, state, { kind: \"session-message\", sessionId, message })\n }\n return\n }\n // Materialize any inline-bytes block (a pasted image, a document sent\n // as base64 rather than a `POST /files/upload` path reference) to this\n // session's content-addressed attachment store, and swap the block's\n // heavy inline bytes for a lightweight `{attachmentKey, attachmentPath,\n // mimeType}` reference before it reaches events.jsonl — keeps the\n // durable transcript bounded regardless of how big an attachment was,\n // and gives the UI a stable path to re-render from after reload. A\n // block with no bytes of its own (text, or an already-path-based\n // reference) passes through unchanged. See `materializeAttachment`'s\n // doc for the AIP-58 `ArtifactEntry` parity this is designed for.\n const attachments: AttachmentEntry[] = []\n const blocksForTranscript = blocks?.map(block => {\n if (!isRecord(block)) return block\n const inline = extractInlineBytes(block)\n if (!inline) return block\n const entry = materializeAttachment(sessionId, baseDir, inline)\n if (!entry) return block\n attachments.push(entry)\n const rest: Record<string, unknown> = { ...block }\n delete rest.data\n delete rest.resource\n return {\n ...rest,\n attachmentKey: entry.key,\n attachmentPath: entry.path,\n ...(entry.contentType ? { mimeType: entry.contentType } : {}),\n }\n })\n writeRecord(sessionId, state, {\n kind: \"user-prompt\",\n sessionId,\n text: userText,\n ...(blocksForTranscript ? { blocks: blocksForTranscript } : {}),\n ...(attachments.length ? { attachments } : {}),\n ...(opts?.source ? { source: opts.source } : {}),\n })\n },\n recordSessionMessage(sessionId, message) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"session-message\", sessionId, message })\n },\n recordSessionMessageSent(sessionId, record) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n // `messageKind`, not `kind` — the record's own `kind` is the record type.\n writeRecord(sessionId, state, {\n kind: \"session-message-sent\",\n sessionId,\n messageId: record.messageId,\n to: record.to,\n messageKind: record.kind,\n urgency: record.urgency,\n })\n },\n recordEvent(sessionId, evt) {\n const state = getState(sessionId)\n switch (evt.kind) {\n case \"text-delta\": {\n if (!evt.text) break\n const combined = state.textBuf + evt.text\n const lines = combined.split(/\\r?\\n/)\n state.textBuf = lines.pop() ?? \"\"\n // `split` consumes each line's terminator — reappend it so the\n // on-disk record stays byte-for-byte reconstructable (a blank\n // line becomes \"\\n\" rather than a lossy \"\").\n for (const line of lines) {\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text: `${line}\\n` })\n }\n if (state.textBuf) scheduleTextDebounce(sessionId, state)\n break\n }\n case \"thought\": {\n if (!evt.text) break\n const combined = state.thoughtBuf + evt.text\n const lines = combined.split(/\\r?\\n/)\n state.thoughtBuf = lines.pop() ?? \"\"\n for (const line of lines) {\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text: `${line}\\n` })\n }\n if (state.thoughtBuf) scheduleThoughtDebounce(sessionId, state)\n break\n }\n case \"tool-call\": {\n // Remember the latest name/input for this call regardless of\n // whether this frame is an announcement or an enrichment — the\n // eventual \"tool-result\" needs both and carries neither itself.\n // An enrichment's `toolName` is `\"\"` when the update carried no\n // title (see toolCallEnrichment in @agentproto/acp); keep the\n // prior name in that case rather than overwriting it with blank.\n if (evt.toolCallId) {\n const existing = state.toolCallMeta.get(evt.toolCallId)\n state.toolCallMeta.set(evt.toolCallId, {\n toolName: evt.toolName || existing?.toolName || \"\",\n arguments: evt.arguments !== undefined ? evt.arguments : existing?.arguments,\n startedAt: existing?.startedAt ?? Date.now(),\n })\n }\n const record = {\n kind: \"tool-call\",\n sessionId,\n toolCallId: evt.toolCallId,\n toolName: evt.toolName,\n arguments: evt.arguments,\n // Carried through so a replay can tell an ENRICHMENT of an\n // announced call from a genuine second call. Readers merge by\n // toolCallId either way, but a record that silently drops the\n // distinction can't be reasoned about after the fact.\n ...(evt.isUpdate ? { isUpdate: true } : {}),\n }\n // An enrichment is a superseding SNAPSHOT of the call's input, and\n // the agent emits one per token as the model types it. Hold the\n // newest instead of writing all of them — flushBuffers puts it on\n // disk the moment anything else needs ordering around it (the\n // tool-result, the next call, a text delta, turn-end). Deliberately\n // no flushBuffers here: that's what lets consecutive updates for the\n // same call collapse into one record.\n if (evt.isUpdate && evt.toolCallId) {\n state.toolUpdates.set(evt.toolCallId, record)\n break\n }\n // The announcement is written at once — it's what makes the step\n // appear, and a pending row the user can see is the whole point.\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, record)\n break\n }\n case \"tool-result\": {\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"tool-result\",\n sessionId,\n toolCallId: evt.toolCallId,\n result: evt.result,\n isError: evt.isError ?? false,\n })\n // Normalized ToolCallRecord (see tool-call-record.ts) — the same\n // shape the command_execute proxy path writes via\n // `writeToolCallRecord`, so `tool_calls_list` reads one unified\n // log regardless of which path produced the call.\n const meta = evt.toolCallId ? state.toolCallMeta.get(evt.toolCallId) : undefined\n if (evt.toolCallId) state.toolCallMeta.delete(evt.toolCallId)\n const { command, args } = extractCommandArgs(meta?.arguments)\n // Exact PR-creation provenance, captured at the only moment both\n // halves exist together (command + result text) — see the\n // `createdPrUrl` field's doc in tool-call-record.ts. Some harnesses\n // surface the shell string as the tool NAME rather than in\n // arguments, so fall back to that.\n const createdPr =\n (evt.isError ?? false)\n ? null\n : detectShellPrCreate(\n command ?? meta?.toolName,\n typeof evt.result === \"string\" ? evt.result : undefined,\n )\n writeRecord(sessionId, state, {\n kind: \"tool-call-record\",\n sessionId,\n tool: meta?.toolName || \"unknown\",\n ...(command !== undefined ? { command } : {}),\n ...(args !== undefined ? { args } : {}),\n isError: evt.isError ?? false,\n ...(meta ? { durationMs: Date.now() - meta.startedAt } : {}),\n ...(createdPr ? { createdPrUrl: createdPr.url, createdPrNumber: createdPr.number } : {}),\n })\n break\n }\n case \"agent-prompt\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"agent-prompt\",\n sessionId,\n toolCallId: evt.toolCallId,\n options: evt.options,\n })\n break\n // Durable counterpart to \"agent-prompt\" — recorded when\n // `respondPermission` (or a dying session's cancel-in-flight sweep)\n // resolves the ask, keyed by the same `toolCallId` so a reader can\n // tell an answered request from a still-pending one without relying\n // on the in-memory pending-permissions map (see sessions.ts's\n // `resolvePendingPermission` / `cancelPendingPermissionsForSession`).\n case \"permission-resolved\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"permission-resolved\",\n sessionId,\n toolCallId: evt.toolCallId,\n decision: evt.decision,\n ...(evt.optionId ? { optionId: evt.optionId } : {}),\n })\n break\n case \"turn-end\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"turn-end\", sessionId, reason: evt.reason })\n break\n case \"notice\":\n // A daemon-side system notice (not agent output) — e.g. the\n // interrupted-turn banner written on resume of a session that died\n // mid-turn under a daemon restart. Durable in events.jsonl so the\n // warning survives beyond the in-memory ring buffer; readers render\n // it as a system message (see transcript-export's \"notice\" case).\n if (!evt.text) break\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"notice\", sessionId, text: evt.text })\n break\n case \"error\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"error\", sessionId, error: evt.error })\n break\n case \"plan\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"plan\",\n sessionId,\n ...(evt.title ? { title: evt.title } : {}),\n entries: evt.entries ?? [],\n })\n break\n case \"usage_update\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"usage_update\",\n sessionId,\n size: evt.size,\n used: evt.used,\n ...(evt.cost ? { cost: evt.cost } : {}),\n // Persist cumulative token counts when the adapter reports them\n // (claude-code over ACP, hermes via its state.db reader,\n // mastracode via its native usage_update) so the transcript can\n // price/aggregate a session even without a `cost` block.\n ...(evt.tokensIn !== undefined ? { tokensIn: evt.tokensIn } : {}),\n ...(evt.tokensOut !== undefined ? { tokensOut: evt.tokensOut } : {}),\n // The model the usage belongs to, and — when the daemon corrected\n // an inferred window (see context-window.ts) — the size the\n // adapter itself reported, so the correction stays auditable.\n ...(evt.model ? { model: evt.model } : {}),\n ...(evt.reportedSize !== undefined ? { reportedSize: evt.reportedSize } : {}),\n })\n break\n case \"background-task\":\n // The agent's background-task lifecycle (started / updated /\n // settled) — durable so a reader can tell \"idle, waiting on a\n // task\" from \"idle, done\", and what a later wake was about.\n if (!evt.task) break\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"background-task\",\n sessionId,\n phase: evt.phase,\n task: evt.task,\n })\n break\n case \"available-commands\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"available-commands\",\n sessionId,\n commands: evt.commands ?? [],\n })\n break\n default:\n // Unknown kind — drop, same as `projectEvent`'s implicit\n // no-op for anything it doesn't switch on.\n break\n }\n },\n recordUsageSnapshot(sessionId, usage) {\n const state = getState(sessionId)\n // Order after any buffered text/thought so on-disk order matches\n // wall-clock order.\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"usage_snapshot\",\n sessionId,\n // Omit absent fields so a missing value never reads as a measured 0.\n ...(usage.model !== undefined ? { model: usage.model } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n ...(usage.tokensIn !== undefined ? { tokensIn: usage.tokensIn } : {}),\n ...(usage.tokensOut !== undefined ? { tokensOut: usage.tokensOut } : {}),\n ...pickUsageDetail(usage),\n ...(usage.contextSize !== undefined ? { contextSize: usage.contextSize } : {}),\n ...(usage.contextUsed !== undefined ? { contextUsed: usage.contextUsed } : {}),\n source: usage.source,\n })\n },\n recordMcpAppToolCall(sessionId, record) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"mcp_app_tool_call\",\n sessionId,\n server: record.server,\n tool: record.tool,\n originToolCallId: record.originToolCallId,\n isError: record.isError,\n durationMs: record.durationMs,\n })\n },\n close(sessionId) {\n const state = states.get(sessionId)\n if (!state) return Promise.resolve()\n flushBuffers(sessionId, state)\n states.delete(sessionId)\n return new Promise<void>(resolve => {\n state.stream.end(() => resolve())\n })\n },\n closeAll() {\n const closings: Promise<void>[] = []\n for (const sessionId of Array.from(states.keys())) {\n const state = states.get(sessionId)\n if (!state) continue\n flushBuffers(sessionId, state)\n closings.push(new Promise<void>(resolve => state.stream.end(() => resolve())))\n }\n states.clear()\n return Promise.all(closings).then(() => undefined)\n },\n subscribe(sessionId, onRecord) {\n let subs = listeners.get(sessionId)\n if (!subs) {\n subs = new Set()\n listeners.set(sessionId, subs)\n }\n subs.add(onRecord)\n return () => {\n const current = listeners.get(sessionId)\n if (!current) return\n current.delete(onRecord)\n if (current.size === 0) listeners.delete(sessionId)\n }\n },\n }\n}\n","/**\n * Export a clean, readable transcript, dispatched between two kinds of\n * source:\n *\n * native — the adapter's OWN persistence, re-read after the fact:\n * claude-code — ~/.claude/projects/<cwd-encoded>/<sessionId>.jsonl\n * hermes — ~/.hermes/state.db via node:sqlite (read-only)\n * codex — $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl\n * opencode — ~/.local/share/opencode/opencode.db via node:sqlite\n * mastracode-inprocess — ~/.agentproto/mastracode-inprocess/storage.db (Mastra libsql)\n * pi — ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl\n * daemon — agentproto's own capture, `~/.agentproto/sessions/<id>/events.jsonl`\n * (written by transcript-writer.ts at the same point `projectEvent`\n * flattens events into the ANSI ring buffer, ahead of that flattening).\n * Available for ANY agent-cli session (acp or print protocol), not just\n * the two adapters with a native store.\n *\n * `source: \"auto\"` (the default) tries native first and falls back to\n * daemon events when there's no native strategy or its store can't be\n * read; `\"native\"` / `\"daemon\"` force one and surface its own error.\n *\n * Output: Markdown (default) or JSON, built from a shared ExportedSession\n * model so every source produces identical rendering logic.\n */\n\nimport { createReadStream, promises as fs, type Dirent } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport { createInterface } from \"node:readline\"\nimport type { SessionDescriptor, SessionsRegistry } from \"./sessions.js\"\nimport { formatToolCall } from \"./tool-presenter.js\"\nimport { sessionEventsPath } from \"./transcript-writer.js\"\nimport { claudeCodeProjectDir } from \"./conversation-store.js\"\nimport type { ConversationCandidate } from \"./conversation-store.js\"\n\n// ── Common model ──────────────────────────────────────────────────────\n\nexport interface ExportedMessage {\n role: \"user\" | \"assistant\" | \"tool\" | \"system\"\n text?: string\n /** Set on a `user`-role message that is a typed inter-session message\n * (a `session-message` record) rather than the human: the daemon-attested\n * sender. Absent for a human prompt. */\n from?: {\n sessionId?: string\n label?: string\n relation: \"child\" | \"parent\" | \"sibling\" | \"human\" | \"system\"\n kind: string\n messageId: string\n }\n reasoning?: string\n toolName?: string\n toolCalls?: { name: string; args: string }[]\n ts?: number\n}\n\nexport interface ExportedSessionMeta {\n title?: string\n model?: string\n startedAt?: string\n endedAt?: string\n messageCount?: number\n toolCallCount?: number\n tokens?: {\n input?: number\n output?: number\n cacheRead?: number\n cacheWrite?: number\n reasoning?: number\n contextSize?: number\n contextUsed?: number\n }\n costUsd?: number\n source?: string\n /** Provenance carried over from the session descriptor's own\n * `origin`/`callerSessionId` (see sessions.ts) — distinct from `source`,\n * which names the EXPORTER that produced this payload (e.g.\n * \"daemon-events\", \"codex\"), not who invoked the underlying session. */\n origin?: string\n callerSessionId?: string\n}\n\nexport interface ExportedSession {\n meta: ExportedSessionMeta\n messages: ExportedMessage[]\n}\n\n// ── Render ────────────────────────────────────────────────────────────\n\nconst ROLE_ICON: Record<string, string> = {\n user: \"🧑 User\",\n assistant: \"🤖 Assistant\",\n tool: \"🔧 Tool\",\n system: \"⚙️ System\",\n}\n\nfunction trunc(text: string, n: number): string {\n if (text.length <= n) return text\n return text.slice(0, n) + `\\n… [${text.length - n} chars truncated]`\n}\n\nexport function renderMarkdown(\n session: ExportedSession,\n opts: { maxToolChars?: number } = {},\n): string {\n const { meta, messages } = session\n const maxToolChars = opts.maxToolChars ?? 1200\n const out: string[] = []\n\n out.push(`# ${meta.title ?? \"(untitled)\"}`)\n out.push(\"\")\n\n const sourceNote = meta.source ? ` · source \\`${meta.source}\\`` : \"\"\n const originNote = meta.origin ? ` · origin \\`${meta.origin}\\`` : \"\"\n const callerNote = meta.callerSessionId ? ` · caller \\`${meta.callerSessionId}\\`` : \"\"\n out.push(`> Session${sourceNote}${originNote}${callerNote}`)\n out.push(\"\")\n out.push(\"| | |\")\n out.push(\"|---|---|\")\n if (meta.model) out.push(`| Model | \\`${meta.model}\\` |`)\n if (meta.startedAt || meta.endedAt) {\n out.push(`| Start → end | ${meta.startedAt ?? \"?\"} → ${meta.endedAt ?? \"?\"} |`)\n }\n if (meta.messageCount !== undefined || meta.toolCallCount !== undefined) {\n const parts: string[] = []\n if (meta.messageCount !== undefined) parts.push(`${meta.messageCount} messages`)\n if (meta.toolCallCount !== undefined) parts.push(`${meta.toolCallCount} tool calls`)\n out.push(`| Count | ${parts.join(\" · \")} |`)\n }\n if (meta.tokens && Object.keys(meta.tokens).length) {\n const t = meta.tokens\n const parts: string[] = []\n if (t.input !== undefined) parts.push(`in ${t.input.toLocaleString(\"en-US\")}`)\n if (t.output !== undefined) parts.push(`out ${t.output.toLocaleString(\"en-US\")}`)\n if (t.cacheRead !== undefined || t.cacheWrite !== undefined) {\n parts.push(\n `cache r/w ${(t.cacheRead ?? 0).toLocaleString(\"en-US\")}/${(t.cacheWrite ?? 0).toLocaleString(\"en-US\")}`,\n )\n }\n if (t.reasoning !== undefined) parts.push(`reason. ${t.reasoning.toLocaleString(\"en-US\")}`)\n if (parts.length) out.push(`| Tokens | ${parts.join(\" · \")} |`)\n }\n if (meta.costUsd !== undefined) {\n out.push(`| Cost | $${meta.costUsd.toFixed(4)} |`)\n }\n out.push(\"\")\n out.push(\"---\")\n out.push(\"\")\n\n for (const m of messages) {\n const icon = ROLE_ICON[m.role] ?? m.role\n const nameSuffix = m.toolName\n ? ` · \\`${m.toolName}\\``\n : m.from\n ? ` · from ${m.from.relation} \\`${m.from.label ?? m.from.sessionId ?? \"\"}\\` (${m.from.kind})`\n : \"\"\n out.push(`### ${icon}${nameSuffix}`)\n\n if (m.reasoning?.trim()) {\n out.push(\"\")\n out.push(\"<details><summary>💭 reasoning</summary>\")\n out.push(\"\")\n out.push(trunc(m.reasoning, 4000))\n out.push(\"\")\n out.push(\"</details>\")\n }\n\n if (m.text?.trim()) {\n out.push(\"\")\n if (m.role === \"tool\") {\n out.push(\"```\")\n out.push(trunc(m.text, maxToolChars))\n out.push(\"```\")\n } else {\n out.push(m.text.trim())\n }\n }\n\n if (m.toolCalls?.length) {\n out.push(\"\")\n for (const tc of m.toolCalls) {\n let parsedArgs: unknown\n try {\n parsedArgs = JSON.parse(tc.args)\n } catch {\n parsedArgs = tc.args\n }\n out.push(`> 📞 ${formatToolCall(tc.name, parsedArgs)}`)\n }\n }\n\n out.push(\"\")\n }\n\n return out.join(\"\\n\")\n}\n\nexport function renderJson(session: ExportedSession): string {\n return JSON.stringify(session, null, 2)\n}\n\n// ── Exporter interface ────────────────────────────────────────────────\n\ninterface ExportStrategy {\n /** `configDir` = the session's isolated provider config dir\n * (`SessionDescriptor.adapterConfigDir`) — only claude-code keys its\n * store off it; the other strategies ignore it. */\n exportSession(adapterSessionId: string, cwd?: string, configDir?: string): Promise<ExportedSession>\n}\n\n// ── claude-code exporter (JSONL) ──────────────────────────────────────\n\ninterface ClaudeTextBlock {\n type: \"text\"\n text: string\n}\ninterface ClaudeThinkingBlock {\n type: \"thinking\"\n thinking: string\n}\ninterface ClaudeToolUseBlock {\n type: \"tool_use\"\n name: string\n input?: unknown\n}\ninterface ClaudeToolResultBlock {\n type: \"tool_result\"\n tool_use_id?: string\n content?: unknown\n}\ntype ClaudeContentBlock =\n | ClaudeTextBlock\n | ClaudeThinkingBlock\n | ClaudeToolUseBlock\n | ClaudeToolResultBlock\n | { type: string }\n\ninterface ClaudeCodeJsonlLine {\n type: string\n message?: {\n role?: string\n content?: string | ClaudeContentBlock[]\n }\n}\n\nconst IGNORED_CLAUDE_TYPES = new Set([\n \"queue-operation\",\n \"attachment\",\n \"file-history-snapshot\",\n \"ai-title\",\n \"last-prompt\",\n])\n\nexport async function exportClaudeCodeSession(\n adapterSessionId: string,\n cwd?: string,\n configDir?: string,\n): Promise<ExportedSession> {\n if (!cwd) {\n throw new Error(\n \"claude-code exporter: cwd is required to locate the JSONL file.\\n\" +\n \"Pass cwd explicitly or use a session id that is in the registry.\",\n )\n }\n // A config-dir-isolated session (#824) writes its transcripts under its\n // own CLAUDE_CONFIG_DIR, same projects/<slug> layout as ~/.claude.\n const filePath = join(claudeCodeProjectDir(cwd, configDir), `${adapterSessionId}.jsonl`)\n\n let stream: ReturnType<typeof createReadStream>\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" })\n // Trigger ENOENT eagerly before readline wraps it.\n await new Promise<void>((resolve, reject) => {\n stream.once(\"error\", reject)\n stream.once(\"open\", resolve)\n })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n throw new Error(\n `claude-code: JSONL file not found: ${filePath}\\n` +\n `Verify cwd=\"${cwd}\" and adapterSessionId=\"${adapterSessionId}\".`,\n )\n }\n throw err\n }\n\n const messages: ExportedMessage[] = []\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: ClaudeCodeJsonlLine\n try {\n entry = JSON.parse(trimmed) as ClaudeCodeJsonlLine\n } catch {\n continue\n }\n if (IGNORED_CLAUDE_TYPES.has(entry.type)) continue\n if (entry.type !== \"user\" && entry.type !== \"assistant\") continue\n const msg = entry.message\n if (!msg) continue\n\n const role: ExportedMessage[\"role\"] =\n msg.role === \"assistant\" ? \"assistant\" : \"user\"\n const content = msg.content\n\n if (typeof content === \"string\") {\n if (content.trim()) messages.push({ role, text: content.trim() })\n continue\n }\n if (!Array.isArray(content)) continue\n\n // Collect all block types from this message event\n let textAcc = \"\"\n let reasoningAcc = \"\"\n const toolCalls: { name: string; args: string }[] = []\n const toolResults: { text: string }[] = []\n\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n textAcc += (block as ClaudeTextBlock).text\n break\n }\n case \"thinking\": {\n reasoningAcc += (block as ClaudeThinkingBlock).thinking\n break\n }\n case \"tool_use\": {\n const tb = block as ClaudeToolUseBlock\n const args =\n typeof tb.input === \"string\"\n ? tb.input\n : JSON.stringify(tb.input ?? {})\n toolCalls.push({ name: tb.name, args })\n break\n }\n case \"tool_result\": {\n const tb = block as ClaudeToolResultBlock\n let resultText = \"\"\n if (typeof tb.content === \"string\") {\n resultText = tb.content\n } else if (Array.isArray(tb.content)) {\n for (const c of tb.content as ClaudeContentBlock[]) {\n if (c.type === \"text\") resultText += (c as ClaudeTextBlock).text\n }\n }\n toolResults.push({ text: resultText })\n break\n }\n }\n }\n\n // Emit the main message (user text or assistant with optional tool calls)\n if (textAcc.trim() || toolCalls.length || reasoningAcc) {\n const m: ExportedMessage = { role }\n if (textAcc.trim()) m.text = textAcc.trim()\n if (reasoningAcc) m.reasoning = reasoningAcc\n if (toolCalls.length) m.toolCalls = toolCalls\n messages.push(m)\n }\n\n // Emit each tool result as its own role=tool message\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text })\n }\n }\n\n return { meta: { source: \"claude-code\" }, messages }\n}\n\n// ── hermes exporter (SQLite) ──────────────────────────────────────────\n// node:sqlite is experimental in Node 22.x but functional.\n// Isolated here so a future stable import replaces only this function.\n\n// esbuild (via tsup) strips the `node:` prefix from builtin imports. Most\n// builtins work without it, but `node:sqlite` has NO unprefixed name —\n// `import('sqlite')` fails at runtime. A computed specifier dodges the\n// static analysis so the prefix survives bundling.\nconst NODE_SQLITE = \"node:\" + \"sqlite\"\nasync function importNodeSqlite(): Promise<typeof import(\"node:sqlite\")> {\n return import(/* @vite-ignore */ NODE_SQLITE)\n}\n\ninterface HermesSessionRow {\n id: string\n title?: string\n model?: string\n started_at?: number\n ended_at?: number\n message_count?: number\n tool_call_count?: number\n api_call_count?: number\n input_tokens?: number\n output_tokens?: number\n cache_read_tokens?: number\n cache_write_tokens?: number\n reasoning_tokens?: number\n actual_cost_usd?: number\n estimated_cost_usd?: number\n source?: string\n end_reason?: string\n}\n\ninterface HermesMessageRow {\n id: number\n session_id: string\n role: string\n content?: string\n tool_calls?: string\n tool_name?: string\n reasoning?: string\n reasoning_content?: string\n timestamp?: number\n}\n\n// Shared by exportHermesSession AND discoverHermesSessions (below) — the\n// open + SQLITE_BUSY-retry handling lives in exactly one place so the two\n// callers can never drift.\ntype HermesDb = InstanceType<(typeof import(\"node:sqlite\"))[\"DatabaseSync\"]>\n\nasync function openHermesDb(dbPath: string): Promise<HermesDb> {\n let DatabaseSync: (typeof import(\"node:sqlite\"))[\"DatabaseSync\"]\n try {\n const sqlite = await importNodeSqlite()\n DatabaseSync = sqlite.DatabaseSync\n } catch {\n throw new Error(\"hermes: node:sqlite unavailable. Requires Node.js ≥22.5.0.\")\n }\n\n try {\n return new DatabaseSync(dbPath, { readOnly: true })\n } catch (err) {\n const msg = String(err)\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"unable to open database file\")\n ) {\n throw new Error(\n `hermes: state.db not found at ${dbPath}. ` +\n `Has hermes been run at least once?`,\n )\n }\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n throw new Error(\n `hermes: database is locked (SQLITE_BUSY). ` +\n `Hermes may be writing. Try again in a moment.`,\n )\n }\n throw err\n }\n}\n\nfunction withRetryOnBusy<T>(fn: () => T): T {\n try {\n return fn()\n } catch (err) {\n const msg = String(err)\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n // One brief retry — the lock is usually transient\n try {\n return fn()\n } catch {\n throw new Error(\n `hermes: database is locked. Hermes may be actively writing. Try again.`,\n )\n }\n }\n throw err\n }\n}\n\n/** Generic read-only sqlite open, sharing hermes's node:sqlite plumbing so\n * the opencode + mastracode-inprocess stores don't each reinvent the\n * experimental-module guard and the ENOENT/BUSY error mapping. `label` is\n * the provider name used in the error strings. Returns the same handle\n * type `withRetryOnBusy` operates on. */\nasync function openReadonlySqlite(dbPath: string, label: string): Promise<HermesDb> {\n let DatabaseSync: (typeof import(\"node:sqlite\"))[\"DatabaseSync\"]\n try {\n const sqlite = await importNodeSqlite()\n DatabaseSync = sqlite.DatabaseSync\n } catch {\n throw new Error(`${label}: node:sqlite unavailable. Requires Node.js ≥22.5.0.`)\n }\n try {\n return new DatabaseSync(dbPath, { readOnly: true })\n } catch (err) {\n const msg = String(err)\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"unable to open database file\")\n ) {\n throw new Error(`${label}: database not found at ${dbPath}. Has ${label} been run at least once?`)\n }\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n throw new Error(`${label}: database is locked (SQLITE_BUSY). It may be writing. Try again in a moment.`)\n }\n throw err\n }\n}\n\nexport async function exportHermesSession(adapterSessionId: string): Promise<ExportedSession> {\n const dbPath = join(homedir(), \".hermes\", \"state.db\")\n const db = await openHermesDb(dbPath)\n\n const session = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT * FROM sessions WHERE id = ?\")\n .get(adapterSessionId) as unknown as HermesSessionRow | undefined,\n )\n\n if (!session) {\n db.close()\n throw new Error(\n `hermes: session \"${adapterSessionId}\" not found in ${dbPath}. ` +\n `ACP sessions are indexed by the agentproto UUID passed as resumeSessionId.`,\n )\n }\n\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare(\n \"SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC\",\n )\n .all(adapterSessionId) as unknown as HermesMessageRow[],\n )\n\n db.close()\n\n const cost =\n session.actual_cost_usd != null\n ? session.actual_cost_usd\n : session.estimated_cost_usd\n\n const startedAt = session.started_at\n ? new Date(session.started_at * 1000).toISOString().replace(\"T\", \" \").slice(0, 19)\n : undefined\n const endedAt = session.ended_at\n ? new Date(session.ended_at * 1000).toISOString().replace(\"T\", \" \").slice(0, 19)\n : undefined\n\n const meta: ExportedSessionMeta = {\n ...(session.title ? { title: session.title } : {}),\n ...(session.model ? { model: session.model } : {}),\n ...(startedAt ? { startedAt } : {}),\n ...(endedAt ? { endedAt } : {}),\n ...(session.message_count !== undefined ? { messageCount: session.message_count } : {}),\n ...(session.tool_call_count !== undefined\n ? { toolCallCount: session.tool_call_count }\n : {}),\n ...(cost != null ? { costUsd: Number(cost) } : {}),\n ...(session.source ? { source: session.source } : {}),\n }\n\n // Only include tokens sub-object when at least one field is present\n const tokensInput = session.input_tokens\n const tokensOutput = session.output_tokens\n const tokensCacheRead = session.cache_read_tokens\n const tokensCacheWrite = session.cache_write_tokens\n const tokensReasoning = session.reasoning_tokens\n if (\n tokensInput !== undefined ||\n tokensOutput !== undefined ||\n tokensCacheRead !== undefined ||\n tokensCacheWrite !== undefined ||\n tokensReasoning !== undefined\n ) {\n meta.tokens = {\n ...(tokensInput !== undefined ? { input: tokensInput } : {}),\n ...(tokensOutput !== undefined ? { output: tokensOutput } : {}),\n ...(tokensCacheRead !== undefined ? { cacheRead: tokensCacheRead } : {}),\n ...(tokensCacheWrite !== undefined ? { cacheWrite: tokensCacheWrite } : {}),\n ...(tokensReasoning !== undefined ? { reasoning: tokensReasoning } : {}),\n }\n }\n\n const messages: ExportedMessage[] = rows.map(row => {\n const validRoles = new Set([\"user\", \"assistant\", \"tool\", \"system\"])\n const role = (\n validRoles.has(row.role) ? row.role : \"user\"\n ) as ExportedMessage[\"role\"]\n\n const reasoning = row.reasoning ?? row.reasoning_content\n\n let toolCalls: { name: string; args: string }[] | undefined\n if (row.tool_calls) {\n try {\n const tc = JSON.parse(row.tool_calls) as unknown[]\n if (Array.isArray(tc) && tc.length) {\n toolCalls = tc.map(c => {\n const entry = c as Record<string, unknown>\n const fn =\n entry.function != null\n ? (entry.function as Record<string, unknown>)\n : entry\n const name = String(fn.name ?? entry.name ?? \"tool\")\n const args =\n typeof fn.arguments === \"string\"\n ? fn.arguments\n : JSON.stringify(fn.arguments ?? {})\n return { name, args }\n })\n }\n } catch {\n // malformed tool_calls — skip\n }\n }\n\n const m: ExportedMessage = { role }\n if (row.content?.trim()) m.text = row.content.trim()\n if (reasoning && String(reasoning).trim()) m.reasoning = String(reasoning)\n if (row.tool_name) m.toolName = row.tool_name\n if (toolCalls) m.toolCalls = toolCalls\n if (row.timestamp) m.ts = row.timestamp\n return m\n })\n\n // Fire-and-forget: cross-check our row count against the binary's export.\n // Non-blocking — a mismatch only logs a warning, it never fails the call.\n void crossValidateHermesExport(adapterSessionId, messages.length).catch(() => {})\n\n return { meta, messages }\n}\n\n// ── hermes discovery (SQLite, cwd-scoped) ─────────────────────────────\n//\n// Reuses openHermesDb/withRetryOnBusy above — the SAME open + BUSY-retry\n// path exportHermesSession uses — so there is one sqlite plumbing to\n// maintain, not two. Called from conversation-store.ts's hermes `discover`\n// via a dynamic import (see that file for why it's dynamic, not static).\n\nfunction hermesRowToCandidate(row: HermesSessionRow): ConversationCandidate {\n const startedAtIso =\n row.started_at !== undefined ? new Date(row.started_at * 1000).toISOString() : undefined\n // \"Last activity\" is when the conversation was last written to — ended_at\n // if it has one, else fall back to started_at (a still-open session).\n const lastActivitySec = row.ended_at ?? row.started_at\n const lastActivityIso =\n lastActivitySec !== undefined ? new Date(lastActivitySec * 1000).toISOString() : undefined\n return {\n conversationId: row.id,\n ...(startedAtIso ? { startedAt: startedAtIso } : {}),\n ...(lastActivityIso ? { lastActivityAt: lastActivityIso } : {}),\n ...(row.message_count !== undefined ? { messageCount: row.message_count } : {}),\n ...(row.title ? { preview: row.title } : {}),\n ...(row.source ? { lastWriter: row.source } : {}),\n }\n}\n\nexport async function discoverHermesSessions(\n cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = join(homedir(), \".hermes\", \"state.db\")\n\n let db: HermesDb\n try {\n db = await openHermesDb(dbPath)\n } catch (err) {\n // \"state.db not found\" == hermes has never run on this box — a normal,\n // empty store, not a discovery error. Anything else (locked, node:sqlite\n // missing) is a genuinely unreadable store and propagates — see the\n // ConversationStore.discover contract in conversation-store.ts.\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"state.db not found\")) return []\n throw err\n }\n\n try {\n // Ground truth beats heuristic: bind to exactly the conversation we\n // were asked about. Binding is by `id`, which is cwd-independent, so\n // this never even looks at `cwd` — never falls through to the cwd-scan\n // below. See the exact-bind invariant in conversation-store.ts.\n if (expectedId) {\n const row = withRetryOnBusy(\n () =>\n db.prepare(\"SELECT * FROM sessions WHERE id = ?\").get(expectedId) as unknown as\n | HermesSessionRow\n | undefined,\n )\n return row ? [hermesRowToCandidate(row)] : []\n }\n\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT * FROM sessions WHERE cwd = ?\")\n .all(cwd) as unknown as HermesSessionRow[],\n )\n const sinceMs = since ? Date.parse(since) : NaN\n return rows\n .filter(row => {\n if (!Number.isFinite(sinceMs)) return true\n const lastActivitySec = row.ended_at ?? row.started_at\n if (lastActivitySec === undefined) return true\n return lastActivitySec * 1000 >= sinceMs - 1000\n })\n .map(hermesRowToCandidate)\n } finally {\n db.close()\n }\n}\n\n// ── Hermes cross-validation via native CLI ────────────────────────────\n//\n// `hermes sessions export --session-id <id> -` emits a stable JSONL\n// transcript on stdout. We use it as a non-blocking sanity-check:\n// if the message count from state.db differs from the binary's output,\n// something in the SQLite schema likely changed and the reader needs\n// updating. The check is fire-and-forget — a mismatch logs a warning but\n// does NOT affect the returned ExportedSession (SQLite stays primary).\n//\n// The export JSONL has one JSON object per message line; lines with a\n// `role` key are treated as messages (other metadata lines are ignored).\n\nexport interface HermesCrossValidationResult {\n sqliteCount: number\n binaryCount: number\n matched: boolean\n /** True when the hermes binary was not found on PATH */\n binaryUnavailable?: boolean\n}\n\n/** Default subprocess runner: spawns `hermes sessions export --session-id <id> -`\n * and returns stdout. Throws with `.code=\"ENOENT\"` when the binary isn't on PATH. */\nasync function defaultHermesRunner(adapterSessionId: string): Promise<string> {\n const { spawn } = await import(\"node:child_process\")\n return new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = []\n const errChunks: Buffer[] = []\n const proc = spawn(\n \"hermes\",\n [\"sessions\", \"export\", \"--session-id\", adapterSessionId, \"-\"],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n )\n proc.stdout.on(\"data\", (chunk: Buffer) => chunks.push(chunk))\n proc.stderr.on(\"data\", (chunk: Buffer) => errChunks.push(chunk))\n proc.on(\"error\", reject)\n proc.on(\"close\", code => {\n if (code !== 0) {\n reject(\n new Error(\n `hermes exited with code ${code}: ${Buffer.concat(errChunks).toString()}`,\n ),\n )\n } else {\n resolve(Buffer.concat(chunks).toString(\"utf8\"))\n }\n })\n })\n}\n\n/** Run `hermes sessions export` and compare its message count with\n * what our SQLite reader produced. Returns null on any subprocess\n * error (binary missing, timeout, parse failure).\n *\n * The optional `_runner` parameter lets tests inject a mock that returns stdout\n * directly, avoiding subprocess mocking complexity (promisify + child_process). */\nexport async function crossValidateHermesExport(\n adapterSessionId: string,\n sqliteCount: number,\n _runner: (id: string) => Promise<string> = defaultHermesRunner,\n): Promise<HermesCrossValidationResult | null> {\n let stdout: string\n try {\n stdout = await _runner(adapterSessionId)\n } catch (err) {\n const msg = String(err)\n // ENOENT = binary not on PATH — expected when hermes isn't installed\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"ENOENT\") ||\n msg.includes(\"not found\")\n ) {\n return { sqliteCount, binaryCount: -1, matched: true, binaryUnavailable: true }\n }\n // Any other error (timeout, non-zero exit, etc.) — skip validation\n return null\n }\n\n // Count lines that look like message objects (have a `role` field)\n const binaryCount = stdout\n .split(\"\\n\")\n .filter(l => l.trim())\n .filter(l => {\n try {\n const obj = JSON.parse(l) as Record<string, unknown>\n return typeof obj.role === \"string\"\n } catch {\n return false\n }\n }).length\n\n const matched = binaryCount === sqliteCount\n if (!matched) {\n console.warn(\n `[hermes export] cross-validation mismatch for session ${adapterSessionId}: ` +\n `SQLite=${sqliteCount} messages, binary=${binaryCount} messages. ` +\n `The state.db schema may have changed — check the SQLite reader in transcript-export.ts.`,\n )\n }\n return { sqliteCount, binaryCount, matched }\n}\n\n// ── daemon-events exporter (agentproto's own JSONL capture) ────────────\n\n/** Structural shape of one events.jsonl line — a superset of every\n * `AgentStreamEvent` kind (see sessions.ts) plus the writer's own\n * \"user-prompt\" record. Untyped fields default to undefined rather than\n * erroring, so a future field this reader doesn't know about is just\n * ignored instead of breaking the export. */\ninterface TranscriptRecord {\n seq: number\n ts: string\n kind: string\n text?: string\n partial?: boolean\n toolCallId?: string\n toolName?: string\n arguments?: unknown\n result?: unknown\n isError?: boolean\n reason?: string\n error?: { message: string; code?: number }\n entries?: Array<{ content: string; priority: string; status: string }>\n size?: number\n used?: number\n /** `session-message` records: the delivered envelope. */\n message?: {\n id: string\n text?: string\n kind?: string\n from?: { sessionId?: string; label?: string; relation?: string }\n }\n cost?: { amount: number; currency: string }\n // Kind-less CommandLogEntry fields (written by recordCommand — the line\n // has NO `kind` of its own). All optional so every other record shape\n // keeps parsing cleanly; unknown fields are ignored, never throw.\n command?: string\n args?: string[]\n cwd?: string\n exitCode?: number\n signal?: string | null\n durationMs?: number\n stdout?: string\n stderr?: string\n truncated?: boolean\n timedOut?: boolean\n}\n\nexport async function exportDaemonEventsSession(\n sessionId: string,\n desc?: SessionDescriptor,\n): Promise<ExportedSession> {\n const filePath = sessionEventsPath(sessionId)\n\n let stream: ReturnType<typeof createReadStream>\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" })\n await new Promise<void>((resolve, reject) => {\n stream.once(\"error\", reject)\n stream.once(\"open\", resolve)\n })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n throw new Error(\n `daemon-events: no events.jsonl for session \"${sessionId}\" (${filePath}).\\n` +\n `Either the session never drove an agent-cli turn, or it predates this feature.`,\n )\n }\n throw err\n }\n\n const messages: ExportedMessage[] = []\n const toolNameById = new Map<string, string>()\n let toolCallCount = 0\n let lastUsage: { size?: number; used?: number; cost?: { amount: number; currency: string } } | undefined\n\n // One assistant \"turn batch\" accumulator — text/thought/tool-calls that\n // arrive between two boundary events (a tool-result, a new prompt, a\n // turn-end, ...) collapse into ONE assistant message, mirroring how the\n // claude-code exporter batches a single API-level assistant entry.\n let asmText = \"\"\n let asmReasoning = \"\"\n let asmToolCalls: { name: string; args: string }[] = []\n let asmTs: number | undefined\n\n const flushAssistant = (): void => {\n if (!asmText.trim() && !asmReasoning.trim() && asmToolCalls.length === 0) return\n const m: ExportedMessage = { role: \"assistant\" }\n if (asmText.trim()) m.text = asmText.trim()\n if (asmReasoning.trim()) m.reasoning = asmReasoning.trim()\n if (asmToolCalls.length) m.toolCalls = asmToolCalls\n if (asmTs !== undefined) m.ts = asmTs\n messages.push(m)\n asmText = \"\"\n asmReasoning = \"\"\n asmToolCalls = []\n asmTs = undefined\n }\n\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let rec: TranscriptRecord\n try {\n rec = JSON.parse(trimmed) as TranscriptRecord\n } catch {\n continue\n }\n const ts = Date.parse(rec.ts)\n const tsOrUndefined = Number.isNaN(ts) ? undefined : ts\n\n // A command session (`command_execute` → recordCommand) writes a\n // kind-less CommandLogEntry line as its FIRST line: no `kind` field,\n // just {command, args, cwd, exitCode, signal, durationMs, stdout,\n // stderr, ...}. Detect it structurally and render it as one\n // assistant tool-call + one tool-result pair, so a command session's\n // transcript isn't empty.\n if (rec.kind === undefined && typeof rec.command === \"string\" && typeof rec.exitCode === \"number\") {\n flushAssistant()\n const toolName = \"command_execute\"\n const argsJson = JSON.stringify({\n command: rec.command,\n args: Array.isArray(rec.args) ? rec.args : [],\n ...(rec.cwd !== undefined ? { cwd: rec.cwd } : {}),\n })\n messages.push({\n role: \"assistant\",\n toolCalls: [{ name: toolName, args: argsJson }],\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n const header =\n `exit code ${rec.exitCode}` +\n (rec.signal ? `, signal ${rec.signal}` : \"\") +\n (typeof rec.durationMs === \"number\" ? `, ${rec.durationMs}ms` : \"\")\n const sections: string[] = [header]\n if (rec.stdout) sections.push(`stdout:\\n${rec.stdout}`)\n if (rec.stderr) sections.push(`stderr:\\n${rec.stderr}`)\n if (rec.truncated) sections.push(\"(output truncated)\")\n if (rec.timedOut) sections.push(\"(timed out)\")\n const text = sections.join(\"\\n\")\n messages.push({\n role: \"tool\",\n text: rec.exitCode !== 0 ? `[error] ${text}` : text,\n toolName,\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n toolCallCount += 1\n continue\n }\n\n switch (rec.kind) {\n case \"user-prompt\":\n flushAssistant()\n messages.push({ role: \"user\", text: rec.text ?? \"\", ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}) })\n break\n case \"session-message\": {\n // A typed message from another session — a user-role turn (that's\n // what the model received), but tagged with its attested sender so\n // a reader never attributes it to the human.\n flushAssistant()\n const m = rec.message\n if (!m) break\n const relation = (m.from?.relation ?? \"system\") as NonNullable<ExportedMessage[\"from\"]>[\"relation\"]\n messages.push({\n role: \"user\",\n text: m.text ?? \"\",\n from: {\n ...(m.from?.sessionId ? { sessionId: m.from.sessionId } : {}),\n ...(m.from?.label ? { label: m.from.label } : {}),\n relation,\n kind: m.kind ?? \"report\",\n messageId: m.id,\n },\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n break\n }\n case \"system-prompt\":\n // The daemon-composed SYSTEM slice of a spawned child's initial\n // prompt (role disposition + AGENTS.md + lineage). Recorded apart\n // from the caller's ask so viewers fold it; rendered as a system\n // message (never a user bubble).\n flushAssistant()\n messages.push({ role: \"system\", text: rec.text ?? \"\", ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}) })\n break\n case \"text-delta\":\n // Terminated lines already carry their own trailing \"\\n\" (see\n // transcript-writer.ts) — only an unterminated tail fragment\n // (still `partial`, or flushed bare at a turn boundary) lacks one,\n // and that's exactly the byte-for-byte original content.\n if (asmTs === undefined) asmTs = tsOrUndefined\n asmText += rec.text ?? \"\"\n break\n case \"thought\":\n if (asmTs === undefined) asmTs = tsOrUndefined\n asmReasoning += rec.text ?? \"\"\n break\n case \"tool-call\": {\n if (asmTs === undefined) asmTs = tsOrUndefined\n const name = rec.toolName ?? \"tool\"\n if (rec.toolCallId) toolNameById.set(rec.toolCallId, name)\n const args =\n typeof rec.arguments === \"string\" ? rec.arguments : JSON.stringify(rec.arguments ?? {})\n asmToolCalls.push({ name, args })\n toolCallCount += 1\n break\n }\n case \"tool-result\": {\n flushAssistant()\n const name = rec.toolCallId ? toolNameById.get(rec.toolCallId) : undefined\n const text = typeof rec.result === \"string\" ? rec.result : JSON.stringify(rec.result ?? \"\")\n messages.push({\n role: \"tool\",\n text: rec.isError ? `[error] ${text}` : text,\n ...(name ? { toolName: name } : {}),\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n break\n }\n case \"agent-prompt\":\n flushAssistant()\n messages.push({ role: \"system\", text: \"[awaiting input] agent requested a decision\" })\n break\n case \"plan\": {\n flushAssistant()\n const entries = rec.entries ?? []\n const done = entries.filter(e => e.status === \"completed\").length\n const list = entries.map(e => e.content).join(\"; \")\n messages.push({ role: \"system\", text: `[plan] ${done}/${entries.length} ${list}`.trim() })\n break\n }\n case \"usage_update\":\n lastUsage = { size: rec.size, used: rec.used, cost: rec.cost }\n break\n case \"error\":\n flushAssistant()\n messages.push({ role: \"system\", text: `[error] ${rec.error?.message ?? \"unknown\"}` })\n break\n case \"turn-end\":\n flushAssistant()\n break\n case \"notice\":\n // Daemon-side system notice (e.g. the interrupted-turn resume banner)\n // — surfaced as a system message, same as plan/error notices above.\n flushAssistant()\n messages.push({ role: \"system\", text: rec.text ?? \"\" })\n break\n default:\n // Deliberately NOT handling `kind: \"tool-call-record\"` here: an\n // agent-cli session's events.jsonl contains BOTH the real\n // `tool-call`/`tool-result` events AND a trailing normalized\n // `tool-call-record` line for the SAME call (see\n // tool-call-log.ts) — rendering or counting it would double-count\n // every agent session's tool calls. A command session's\n // `tool-call-record` line is intentionally dropped too; its\n // CommandLogEntry line above already rendered the call.\n break\n }\n }\n flushAssistant()\n\n const meta: ExportedSessionMeta = { source: \"daemon-events\" }\n // Title chain: spawner label, else the command itself (a command session\n // never gets a label from command_execute, but its descriptor carries the\n // quoted argv) — truncated so a giant argv doesn't become an H1.\n // renderMarkdown's `(untitled)` remains the final fallback.\n if (desc?.label) meta.title = desc.label\n else if (desc?.command) {\n meta.title = desc.command.length > 120 ? `${desc.command.slice(0, 120)}…` : desc.command\n }\n if (desc?.model) meta.model = desc.model\n if (desc?.startedAt) meta.startedAt = desc.startedAt\n if (desc?.endedAt) meta.endedAt = desc.endedAt\n if (desc?.origin) meta.origin = desc.origin\n if (desc?.callerSessionId) meta.callerSessionId = desc.callerSessionId\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n if (desc?.costUsd !== undefined) meta.costUsd = desc.costUsd\n else if (lastUsage?.cost) meta.costUsd = lastUsage.cost.amount\n if (desc?.tokensIn !== undefined || desc?.tokensOut !== undefined) {\n meta.tokens = {\n ...(desc?.tokensIn !== undefined ? { input: desc.tokensIn } : {}),\n ...(desc?.tokensOut !== undefined ? { output: desc.tokensOut } : {}),\n }\n }\n\n return { meta, messages }\n}\n\n// ── shared jsonl helper ───────────────────────────────────────────────\n\n/** Read only the first non-empty line of a jsonl file — used by the codex\n * and pi discovery scans, which only need each file's leading metadata\n * record (session_meta / session) to answer cwd/id questions without\n * parsing a possibly-multi-MB transcript. */\nasync function readFirstJsonLine(path: string): Promise<string | undefined> {\n const stream = createReadStream(path, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n try {\n for await (const line of rl) {\n const t = line.trim()\n if (t) return t\n }\n return undefined\n } finally {\n rl.close()\n stream.destroy()\n }\n}\n\n// ── codex exporter (rollout JSONL) ────────────────────────────────────\n//\n// Native store: $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl\n// (CODEX_HOME defaults to ~/.codex). One append-only JSONL file per\n// conversation, written by the Codex core that @agentclientprotocol/codex-acp\n// bundles. The first line is a `session_meta` record carrying the\n// conversation's `id` (== the trailing uuid in the filename == the ACP\n// session id agentproto records as `adapterSessionId` — verified\n// empirically against live rollouts) and its `cwd`. Subsequent\n// `response_item` lines carry the turns; `event_msg` / `turn_context`\n// lines are transport bookkeeping we skip.\n\nfunction codexHome(): string {\n return process.env.CODEX_HOME ?? join(homedir(), \".codex\")\n}\nfunction codexSessionsDir(): string {\n return join(codexHome(), \"sessions\")\n}\n\ninterface CodexRolloutLine {\n type?: string\n timestamp?: string\n payload?: {\n type?: string\n id?: string\n session_id?: string\n timestamp?: string\n cwd?: string\n model?: string\n model_provider?: string\n role?: string\n content?: Array<{ type?: string; text?: string }>\n summary?: Array<{ type?: string; text?: string }>\n name?: string\n input?: unknown\n arguments?: unknown\n call_id?: string\n output?: unknown\n }\n}\n\n/** Recursively collect `rollout-*.jsonl` paths under the YYYY/MM/DD tree. */\nasync function walkCodexRollouts(dir: string): Promise<string[]> {\n let entries: Dirent[]\n try {\n entries = await fs.readdir(dir, { withFileTypes: true })\n } catch {\n return []\n }\n const out: string[] = []\n for (const e of entries) {\n const p = join(dir, e.name)\n if (e.isDirectory()) {\n out.push(...(await walkCodexRollouts(p)))\n } else if (e.isFile() && e.name.startsWith(\"rollout-\") && e.name.endsWith(\".jsonl\")) {\n out.push(p)\n }\n }\n return out\n}\n\n/** Join codex output blocks (input_text / output_text) into one string. */\nfunction codexBlocksText(content: unknown): string {\n if (typeof content === \"string\") return content\n if (!Array.isArray(content)) return \"\"\n let acc = \"\"\n for (const c of content) {\n if (c && typeof c === \"object\") {\n const b = c as { type?: string; text?: string }\n if (typeof b.text === \"string\" && (b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"text\")) {\n acc += b.text\n }\n }\n }\n return acc\n}\n\nasync function findCodexRolloutFile(conversationId: string): Promise<string | undefined> {\n const files = await walkCodexRollouts(codexSessionsDir())\n // Fast path: the uuid is the filename suffix.\n const byName = files.find(f => f.endsWith(`-${conversationId}.jsonl`))\n if (byName) return byName\n // Fallback: match on the session_meta id/session_id (covers a future\n // codex build whose filename uuid diverges from the meta id).\n for (const f of files) {\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: CodexRolloutLine\n try {\n meta = JSON.parse(first) as CodexRolloutLine\n } catch {\n continue\n }\n const p = meta.payload\n if (p && (p.id === conversationId || p.session_id === conversationId)) return f\n }\n return undefined\n}\n\nexport async function exportCodexSession(conversationId: string): Promise<ExportedSession> {\n const file = await findCodexRolloutFile(conversationId)\n if (!file) {\n throw new Error(\n `codex: no rollout file for conversation \"${conversationId}\" under ${codexSessionsDir()}.\\n` +\n `The session may predate persistence, or CODEX_HOME points elsewhere.`,\n )\n }\n\n const stream = createReadStream(file, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n const messages: ExportedMessage[] = []\n const toolNameByCall = new Map<string, string>()\n let toolCallCount = 0\n let startedAt: string | undefined\n let model: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: CodexRolloutLine\n try {\n entry = JSON.parse(trimmed) as CodexRolloutLine\n } catch {\n continue\n }\n const p = entry.payload ?? {}\n\n if (entry.type === \"session_meta\") {\n startedAt = p.timestamp ?? entry.timestamp\n if (typeof p.model === \"string\") model = p.model\n continue\n }\n if (entry.type === \"turn_context\") {\n if (!model && typeof p.model === \"string\") model = p.model\n continue\n }\n if (entry.type !== \"response_item\") continue\n\n switch (p.type) {\n case \"message\": {\n const text = codexBlocksText(p.content).trim()\n if (!text) break\n const role: ExportedMessage[\"role\"] =\n p.role === \"assistant\" ? \"assistant\" : p.role === \"user\" ? \"user\" : \"system\"\n messages.push({ role, text })\n break\n }\n case \"reasoning\": {\n // Codex reasoning is usually an opaque `encrypted_content` blob; only\n // an unencrypted `summary` (rare) is human-readable. Emit only when\n // there's real text — never the ciphertext.\n const r = (p.summary ?? []).map(s => s.text ?? \"\").join(\"\\n\").trim()\n if (r) messages.push({ role: \"assistant\", reasoning: r })\n break\n }\n case \"function_call\":\n case \"custom_tool_call\": {\n const name = p.name ?? \"tool\"\n if (typeof p.call_id === \"string\") toolNameByCall.set(p.call_id, name)\n const raw = p.type === \"custom_tool_call\" ? p.input : p.arguments\n const args = typeof raw === \"string\" ? raw : JSON.stringify(raw ?? {})\n messages.push({ role: \"assistant\", toolCalls: [{ name, args }] })\n toolCallCount += 1\n break\n }\n case \"function_call_output\":\n case \"custom_tool_call_output\": {\n const name = typeof p.call_id === \"string\" ? toolNameByCall.get(p.call_id) : undefined\n const out = codexBlocksText(p.output) || (typeof p.output === \"string\" ? p.output : \"\")\n messages.push({ role: \"tool\", text: out, ...(name ? { toolName: name } : {}) })\n break\n }\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"codex\" }\n if (startedAt) meta.startedAt = startedAt\n if (model) meta.model = model\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n}\n\nexport async function discoverCodexSessions(\n cwd: string,\n since?: string,\n until?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const files = await walkCodexRollouts(codexSessionsDir())\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n\n for (const f of files) {\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(f)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: CodexRolloutLine\n try {\n meta = JSON.parse(first) as CodexRolloutLine\n } catch {\n continue\n }\n if (meta.type !== \"session_meta\") continue\n const p = meta.payload ?? {}\n const id = p.id ?? p.session_id\n if (!id) continue\n // Ground truth beats heuristic: bind to exactly the requested id and\n // ignore cwd (the id is globally unique). See the exact-bind invariant.\n if (expectedId) {\n if (id !== expectedId) continue\n } else if (p.cwd !== cwd) {\n continue\n }\n const startedAt = p.timestamp\n if (Number.isFinite(untilMs) && startedAt) {\n const startedMs = Date.parse(startedAt)\n if (Number.isFinite(startedMs) && startedMs > untilMs) continue\n }\n scored.push({\n candidate: {\n conversationId: id,\n ...(startedAt ? { startedAt } : {}),\n lastActivityAt: new Date(mtimeMs).toISOString(),\n },\n mtimeMs,\n })\n }\n\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\n// ── opencode exporter (opencode.db SQLite) ────────────────────────────\n//\n// Native store: ${XDG_DATA_HOME:-~/.local/share}/opencode/opencode.db — a\n// drizzle-managed sqlite with `session` (keyed by the `ses_…` id opencode\n// returns over ACP, which agentproto records verbatim as adapterSessionId\n// — verified empirically), `message` (JSON `data` per row), and `part`\n// (JSON `data`, one row per text/reasoning/tool block). The `session`\n// table's `directory` column is the conversation's cwd, enabling\n// cwd-scoped discovery.\n\ninterface OpenCodeSessionRow {\n id: string\n directory?: string\n title?: string\n model?: string\n cost?: number\n tokens_input?: number\n tokens_output?: number\n tokens_reasoning?: number\n tokens_cache_read?: number\n tokens_cache_write?: number\n time_created?: number\n time_updated?: number\n}\ninterface OpenCodeMessageRow {\n id: string\n data: string\n time_created: number\n}\ninterface OpenCodePartRow {\n message_id: string\n data: string\n time_created: number\n}\n\nfunction openCodeDbPath(): string {\n const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\")\n return join(dataHome, \"opencode\", \"opencode.db\")\n}\n\n/** `{\"id\":\"gpt-5-mini\",\"providerID\":\"openai\",…}` → `openai/gpt-5-mini`. */\nfunction openCodeModelLabel(raw: string | undefined): string | undefined {\n if (!raw) return undefined\n try {\n const m = JSON.parse(raw) as { id?: string; modelID?: string; providerID?: string }\n const id = m.modelID ?? m.id\n if (!id) return undefined\n return m.providerID ? `${m.providerID}/${id}` : id\n } catch {\n return undefined\n }\n}\n\nexport async function exportOpenCodeSession(conversationId: string): Promise<ExportedSession> {\n const dbPath = openCodeDbPath()\n const db = await openReadonlySqlite(dbPath, \"opencode\")\n try {\n const session = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE id = ?\").get(conversationId) as unknown as\n | OpenCodeSessionRow\n | undefined,\n )\n if (!session) {\n throw new Error(\n `opencode: session \"${conversationId}\" not found in ${dbPath}. ` +\n `Sessions are keyed by the ACP session id (ses_…) recorded as adapterSessionId.`,\n )\n }\n const msgRows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC\")\n .all(conversationId) as unknown as OpenCodeMessageRow[],\n )\n const partRows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT message_id, data, time_created FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC\")\n .all(conversationId) as unknown as OpenCodePartRow[],\n )\n\n const partsByMsg = new Map<string, OpenCodePartRow[]>()\n for (const r of partRows) {\n const list = partsByMsg.get(r.message_id) ?? []\n list.push(r)\n partsByMsg.set(r.message_id, list)\n }\n\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n for (const m of msgRows) {\n let mdata: { role?: string }\n try {\n mdata = JSON.parse(m.data) as { role?: string }\n } catch {\n continue\n }\n const role: ExportedMessage[\"role\"] = mdata.role === \"assistant\" ? \"assistant\" : \"user\"\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n const toolResults: { name?: string; text: string }[] = []\n for (const pr of partsByMsg.get(m.id) ?? []) {\n let part: {\n type?: string\n text?: string\n tool?: string\n state?: { input?: unknown; output?: unknown }\n }\n try {\n part = JSON.parse(pr.data) as typeof part\n } catch {\n continue\n }\n switch (part.type) {\n case \"text\":\n if (part.text) text += part.text\n break\n case \"reasoning\":\n if (part.text) reasoning += part.text\n break\n case \"tool\": {\n const name = part.tool ?? \"tool\"\n toolCalls.push({ name, args: JSON.stringify(part.state?.input ?? {}) })\n toolCallCount += 1\n const output = part.state?.output\n if (output !== undefined && output !== null && output !== \"\") {\n toolResults.push({ name, text: typeof output === \"string\" ? output : JSON.stringify(output) })\n }\n break\n }\n // step-start / step-finish / patch / snapshot / file — skip.\n }\n }\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n const em: ExportedMessage = { role }\n if (text.trim()) em.text = text.trim()\n if (reasoning.trim()) em.reasoning = reasoning.trim()\n if (toolCalls.length) em.toolCalls = toolCalls\n em.ts = m.time_created\n messages.push(em)\n }\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text, ...(tr.name ? { toolName: tr.name } : {}) })\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"opencode\" }\n if (session.title) meta.title = session.title\n const model = openCodeModelLabel(session.model)\n if (model) meta.model = model\n if (session.time_created) meta.startedAt = new Date(session.time_created).toISOString()\n if (session.time_updated) meta.endedAt = new Date(session.time_updated).toISOString()\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n if (session.cost != null) meta.costUsd = Number(session.cost)\n const tk = {\n ...(session.tokens_input != null ? { input: session.tokens_input } : {}),\n ...(session.tokens_output != null ? { output: session.tokens_output } : {}),\n ...(session.tokens_cache_read != null ? { cacheRead: session.tokens_cache_read } : {}),\n ...(session.tokens_cache_write != null ? { cacheWrite: session.tokens_cache_write } : {}),\n ...(session.tokens_reasoning != null ? { reasoning: session.tokens_reasoning } : {}),\n }\n if (Object.keys(tk).length) meta.tokens = tk\n return { meta, messages }\n } finally {\n db.close()\n }\n}\n\nexport async function discoverOpenCodeSessions(\n cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = openCodeDbPath()\n let db: HermesDb\n try {\n db = await openReadonlySqlite(dbPath, \"opencode\")\n } catch (err) {\n // A missing db == opencode never ran here — a normal empty store, not a\n // discovery error (mirrors discoverHermesSessions).\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"database not found\")) return []\n throw err\n }\n try {\n if (expectedId) {\n const row = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE id = ?\").get(expectedId) as unknown as\n | OpenCodeSessionRow\n | undefined,\n )\n return row ? [openCodeRowToCandidate(row)] : []\n }\n const rows = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE directory = ?\").all(cwd) as unknown as OpenCodeSessionRow[],\n )\n const sinceMs = since ? Date.parse(since) : NaN\n return rows\n .filter(r => {\n if (!Number.isFinite(sinceMs)) return true\n // Key off the conversation's START (time_created), never\n // time_updated — an OLD conversation whose time_updated moves at/\n // after the window boundary must not pass on that alone (#mis-bind).\n // A row with no time_created at all has an unprovable start and is\n // excluded rather than assumed to be in-window.\n if (r.time_created === undefined) return false\n return r.time_created >= sinceMs - 1000\n })\n .map(openCodeRowToCandidate)\n } finally {\n db.close()\n }\n}\n\nfunction openCodeRowToCandidate(row: OpenCodeSessionRow): ConversationCandidate {\n return {\n conversationId: row.id,\n ...(row.time_created ? { startedAt: new Date(row.time_created).toISOString() } : {}),\n ...(row.time_updated ? { lastActivityAt: new Date(row.time_updated).toISOString() } : {}),\n ...(row.title ? { preview: row.title } : {}),\n }\n}\n\n// ── mastracode-inprocess exporter (Mastra libsql) ─────────────────────\n//\n// Native store: ${AGENTPROTO_HOME:-~/.agentproto}/mastracode-inprocess/\n// storage.db — the dedicated Mastra libsql db the in-process arm writes\n// (adapters/mastracode-inprocess/src/client.ts). The arm's public\n// sessionId is the composite \"<resourceId>:<threadId>\"; agentproto records\n// it verbatim as adapterSessionId. Threads live in `mastra_threads` (keyed\n// by threadId) and messages in `mastra_messages` (keyed by `thread_id`,\n// each `content` a Mastra v2 `{format,parts}` JSON). The store has no cwd\n// column, so cwd-scoped discovery is not possible — but this in-process arm\n// always yields an adapterSessionId, so the exact-bind path is the only one\n// that ever runs in practice.\n\ninterface MastraThreadRow {\n id: string\n resourceId?: string\n title?: string\n createdAt?: string\n updatedAt?: string\n}\ninterface MastraMessageRow {\n role: string\n content: string\n createdAt: string\n}\n\nfunction mastracodeInprocessDbPath(): string {\n const home = process.env.AGENTPROTO_HOME ?? join(homedir(), \".agentproto\")\n return join(home, \"mastracode-inprocess\", \"storage.db\")\n}\n\n/** Split \"<resourceId>:<threadId>\"; tolerate a bare threadId (no colon). */\nfunction mastraThreadId(conversationId: string): string {\n const idx = conversationId.indexOf(\":\")\n return idx > 0 ? conversationId.slice(idx + 1) : conversationId\n}\n\n/** Pull user/assistant text, reasoning, and tool calls/results out of one\n * Mastra v2 `{format,parts}` content blob. */\nfunction mastraContentToMessage(\n role: ExportedMessage[\"role\"],\n content: string,\n): { message?: ExportedMessage; toolResults: { name?: string; text: string }[]; toolCalls: number } {\n const toolResults: { name?: string; text: string }[] = []\n let parsed: { parts?: unknown[] }\n try {\n parsed = JSON.parse(content) as { parts?: unknown[] }\n } catch {\n return { message: undefined, toolResults, toolCalls: 0 }\n }\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n for (const raw of parsed.parts ?? []) {\n if (!raw || typeof raw !== \"object\") continue\n const part = raw as {\n type?: string\n text?: string\n reasoning?: string\n toolInvocation?: { toolName?: string; args?: unknown; result?: unknown }\n }\n if (part.type === \"text\" && part.text) text += part.text\n else if (part.type === \"reasoning\" && part.reasoning) reasoning += part.reasoning\n else if (part.type === \"tool-invocation\" && part.toolInvocation) {\n const inv = part.toolInvocation\n const name = inv.toolName ?? \"tool\"\n toolCalls.push({ name, args: JSON.stringify(inv.args ?? {}) })\n if (inv.result !== undefined) {\n const r = inv.result as { content?: Array<{ type?: string; text?: string }> }\n const rtext = Array.isArray(r?.content)\n ? r.content.filter(c => c?.type === \"text\").map(c => c.text ?? \"\").join(\"\")\n : typeof inv.result === \"string\"\n ? inv.result\n : JSON.stringify(inv.result)\n toolResults.push({ name, text: rtext })\n }\n }\n // data-* and step-start parts are internal bookkeeping — skip.\n }\n let message: ExportedMessage | undefined\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n message = { role }\n if (text.trim()) message.text = text.trim()\n if (reasoning.trim()) message.reasoning = reasoning.trim()\n if (toolCalls.length) message.toolCalls = toolCalls\n }\n return { message, toolResults, toolCalls: toolCalls.length }\n}\n\nexport async function exportMastracodeInprocessSession(conversationId: string): Promise<ExportedSession> {\n const threadId = mastraThreadId(conversationId)\n const dbPath = mastracodeInprocessDbPath()\n const db = await openReadonlySqlite(dbPath, \"mastracode-inprocess\")\n try {\n const thread = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM mastra_threads WHERE id = ?\").get(threadId) as unknown as\n | MastraThreadRow\n | undefined,\n )\n if (!thread) {\n throw new Error(\n `mastracode-inprocess: thread \"${threadId}\" not found in ${dbPath}. ` +\n `The sessionId is \"<resourceId>:<threadId>\"; only the threadId is looked up.`,\n )\n }\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare('SELECT role, content, createdAt FROM mastra_messages WHERE thread_id = ? ORDER BY \"createdAt\" ASC')\n .all(threadId) as unknown as MastraMessageRow[],\n )\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n for (const r of rows) {\n // Mastra tags user turns with role \"signal\"; assistant with \"assistant\".\n const role: ExportedMessage[\"role\"] = r.role === \"assistant\" ? \"assistant\" : \"user\"\n const { message, toolResults, toolCalls } = mastraContentToMessage(role, r.content)\n if (message) messages.push(message)\n toolCallCount += toolCalls\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text, ...(tr.name ? { toolName: tr.name } : {}) })\n }\n }\n const meta: ExportedSessionMeta = { source: \"mastracode-inprocess\" }\n if (thread.title) meta.title = thread.title\n if (thread.createdAt) meta.startedAt = thread.createdAt\n if (thread.updatedAt) meta.endedAt = thread.updatedAt\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n } finally {\n db.close()\n }\n}\n\nexport async function discoverMastracodeInprocessSessions(\n _cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = mastracodeInprocessDbPath()\n let db: HermesDb\n try {\n db = await openReadonlySqlite(dbPath, \"mastracode-inprocess\")\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"database not found\")) return []\n throw err\n }\n try {\n // Exact-bind only: `mastra_threads` has no cwd column, so a cwd-scoped\n // scan is impossible. Absent an expectedId there is nothing to safely\n // scope to, so return none (this in-process arm always supplies an\n // adapterSessionId, so the exact-bind path is the real one).\n if (!expectedId) return []\n const threadId = mastraThreadId(expectedId)\n const thread = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM mastra_threads WHERE id = ?\").get(threadId) as unknown as\n | MastraThreadRow\n | undefined,\n )\n if (!thread) return []\n const sinceMs = since ? Date.parse(since) : NaN\n if (Number.isFinite(sinceMs) && thread.updatedAt) {\n const upd = Date.parse(thread.updatedAt)\n if (Number.isFinite(upd) && upd < sinceMs - 1000) return []\n }\n return [\n {\n // Echo back the full composite id so callers/read() round-trip it.\n conversationId: expectedId,\n ...(thread.createdAt ? { startedAt: thread.createdAt } : {}),\n ...(thread.updatedAt ? { lastActivityAt: thread.updatedAt } : {}),\n ...(thread.title ? { preview: thread.title } : {}),\n },\n ]\n } finally {\n db.close()\n }\n}\n\n// ── pi exporter (JSON-over-stdio session log) ─────────────────────────\n//\n// Native store: ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl — pi\n// writes one append-only JSONL per conversation. The first line is a\n// `session` record carrying the uuid `id` (== pi's RPC `sessionId`, which\n// agentproto records as adapterSessionId — pi's client captures it from\n// get_state) and the `cwd`. `message` lines carry the turns; the\n// cwd-slug directory encoding is version-dependent, so discovery scans and\n// keys off each file's authoritative `session.cwd` rather than the slug.\n\nfunction piSessionsDir(): string {\n return join(homedir(), \".pi\", \"agent\", \"sessions\")\n}\n\ninterface PiLine {\n type?: string\n id?: string\n cwd?: string\n timestamp?: string\n provider?: string\n modelId?: string\n message?: {\n role?: string\n toolName?: string\n isError?: boolean\n content?: unknown\n }\n}\n\n/** Text of pi content blocks (`{type:\"text\",text}`), string-tolerant. */\nfunction piBlocksText(content: unknown): string {\n if (typeof content === \"string\") return content\n if (!Array.isArray(content)) return \"\"\n let acc = \"\"\n for (const c of content) {\n if (c && typeof c === \"object\") {\n const b = c as { type?: string; text?: string }\n if (b.type === \"text\" && typeof b.text === \"string\") acc += b.text\n }\n }\n return acc\n}\n\nasync function walkPiSessionFiles(dir: string): Promise<string[]> {\n let entries: Dirent[]\n try {\n entries = await fs.readdir(dir, { withFileTypes: true })\n } catch {\n return []\n }\n const out: string[] = []\n for (const e of entries) {\n const p = join(dir, e.name)\n if (e.isDirectory()) out.push(...(await walkPiSessionFiles(p)))\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p)\n }\n return out\n}\n\nasync function findPiSessionFile(conversationId: string): Promise<string | undefined> {\n const files = await walkPiSessionFiles(piSessionsDir())\n const byName = files.find(f => f.endsWith(`_${conversationId}.jsonl`))\n if (byName) return byName\n for (const f of files) {\n const first = await readFirstJsonLine(f)\n if (!first) continue\n try {\n const meta = JSON.parse(first) as PiLine\n if (meta.type === \"session\" && meta.id === conversationId) return f\n } catch {\n // skip\n }\n }\n return undefined\n}\n\nexport async function exportPiSession(conversationId: string): Promise<ExportedSession> {\n const file = await findPiSessionFile(conversationId)\n if (!file) {\n throw new Error(\n `pi: no session file for conversation \"${conversationId}\" under ${piSessionsDir()}.`,\n )\n }\n const stream = createReadStream(file, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n let startedAt: string | undefined\n let model: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: PiLine\n try {\n entry = JSON.parse(trimmed) as PiLine\n } catch {\n continue\n }\n if (entry.type === \"session\") {\n startedAt = entry.timestamp\n continue\n }\n if (entry.type === \"model_change\") {\n model = entry.provider ? `${entry.provider}/${entry.modelId ?? \"\"}` : entry.modelId\n continue\n }\n if (entry.type !== \"message\" || !entry.message) continue\n const m = entry.message\n const content = m.content\n\n if (m.role === \"toolResult\") {\n const text = piBlocksText(content)\n messages.push({\n role: \"tool\",\n text: m.isError ? `[error] ${text}` : text,\n ...(m.toolName ? { toolName: m.toolName } : {}),\n })\n continue\n }\n\n const role: ExportedMessage[\"role\"] = m.role === \"assistant\" ? \"assistant\" : \"user\"\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n if (typeof content === \"string\") {\n text = content\n } else if (Array.isArray(content)) {\n for (const raw of content) {\n if (!raw || typeof raw !== \"object\") continue\n const part = raw as {\n type?: string\n text?: string\n thinking?: string\n name?: string\n arguments?: unknown\n }\n if (part.type === \"text\" && part.text) text += part.text\n else if (part.type === \"thinking\" && part.thinking) reasoning += part.thinking\n else if (part.type === \"toolCall\") {\n toolCalls.push({ name: part.name ?? \"tool\", args: JSON.stringify(part.arguments ?? {}) })\n toolCallCount += 1\n }\n }\n }\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n const em: ExportedMessage = { role }\n if (text.trim()) em.text = text.trim()\n if (reasoning.trim()) em.reasoning = reasoning.trim()\n if (toolCalls.length) em.toolCalls = toolCalls\n messages.push(em)\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"pi\" }\n if (startedAt) meta.startedAt = startedAt\n if (model) meta.model = model\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n}\n\nexport async function discoverPiSessions(\n cwd: string,\n since?: string,\n until?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const files = await walkPiSessionFiles(piSessionsDir())\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n\n for (const f of files) {\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(f)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: PiLine\n try {\n meta = JSON.parse(first) as PiLine\n } catch {\n continue\n }\n if (meta.type !== \"session\" || !meta.id) continue\n if (expectedId) {\n if (meta.id !== expectedId) continue\n } else if (meta.cwd !== cwd) {\n continue\n }\n const startedAt = meta.timestamp\n if (Number.isFinite(untilMs) && startedAt) {\n const s = Date.parse(startedAt)\n if (Number.isFinite(s) && s > untilMs) continue\n }\n scored.push({\n candidate: {\n conversationId: meta.id,\n ...(startedAt ? { startedAt } : {}),\n lastActivityAt: new Date(mtimeMs).toISOString(),\n },\n mtimeMs,\n })\n }\n\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\n// ── Strategy registry ─────────────────────────────────────────────────\n\nconst EXPORT_STRATEGIES: Record<string, ExportStrategy> = {\n \"claude-code\": {\n exportSession: exportClaudeCodeSession,\n },\n hermes: {\n exportSession: (id: string) => exportHermesSession(id),\n },\n codex: {\n exportSession: (id: string) => exportCodexSession(id),\n },\n opencode: {\n exportSession: (id: string) => exportOpenCodeSession(id),\n },\n \"mastracode-inprocess\": {\n exportSession: (id: string) => exportMastracodeInprocessSession(id),\n },\n pi: {\n exportSession: (id: string) => exportPiSession(id),\n },\n}\n\n// ── Public API ────────────────────────────────────────────────────────\n\nexport type ExportSource = \"auto\" | \"native\" | \"daemon\"\n\nexport interface ExportAgentSessionInput {\n /** agentproto session id (sess_xxx) or an adapter-native id */\n sessionId: string\n registry: SessionsRegistry\n format?: \"markdown\" | \"json\"\n maxToolChars?: number\n /** Override adapter slug when sessionId is an adapter-native id */\n adapter?: string\n /** Override cwd when sessionId is an adapter-native id (claude-code only) */\n cwd?: string\n /** Which backend to read from. \"auto\" (default) prefers the adapter's\n * own native store and falls back to agentproto's own events.jsonl\n * capture when there isn't one (or it can't be read); \"native\" / \"daemon\"\n * force one and surface its own error instead of falling back. */\n source?: ExportSource\n}\n\nexport interface ExportAgentSessionResult {\n sessionId: string\n adapter: string\n format: string\n meta: ExportedSessionMeta\n content: string\n}\n\nexport async function exportAgentSession(\n input: ExportAgentSessionInput,\n): Promise<ExportAgentSessionResult> {\n const { sessionId, registry, format = \"markdown\", maxToolChars = 1200 } = input\n const source = input.source ?? \"auto\"\n\n // Resolve via registry first. `err` closes over `adapterSlug` so it\n // returns the resolved slug (not the raw input) after registry lookup.\n let adapterSlug = input.adapter\n let cwd = input.cwd\n let adapterSessionId = sessionId\n let configDir: string | undefined\n\n const err = (msg: string): ExportAgentSessionResult => ({\n sessionId,\n adapter: adapterSlug ?? \"unknown\",\n format,\n meta: {},\n content: `Error: ${msg}`,\n })\n\n const desc = registry.findByIdOrName(sessionId)\n // events.jsonl is always keyed by the agentproto session id — never the\n // adapter-native id substituted below for the native strategies.\n const daemonSessionId = desc?.id ?? sessionId\n if (desc) {\n adapterSlug = adapterSlug ?? desc.adapterSlug\n cwd = cwd ?? desc.cwd\n // A config-dir-isolated session's transcripts live under its own\n // CLAUDE_CONFIG_DIR, not ~/.claude (#824) — thread it to the exporter.\n configDir = desc.adapterConfigDir\n // prefer the adapter-native id over the agentproto id\n if (desc.adapterSessionId) adapterSessionId = desc.adapterSessionId\n }\n\n const tryNative = async (): Promise<ExportedSession> => {\n if (!adapterSlug) {\n throw new Error(\n `session \"${sessionId}\" not found in registry and no adapter override supplied.\\n` +\n `Pass adapter explicitly or use a known session id (sess_xxx or name).`,\n )\n }\n const exporter = EXPORT_STRATEGIES[adapterSlug]\n if (!exporter) {\n const supported = Object.keys(EXPORT_STRATEGIES).join(\", \")\n throw new Error(\n `no exporter for adapter \"${adapterSlug}\". Supported: ${supported}.\\n` +\n `Only sessions spawned via claude-code or hermes can be exported.`,\n )\n }\n return exporter.exportSession(adapterSessionId, cwd, configDir)\n }\n\n const tryDaemon = (): Promise<ExportedSession> => exportDaemonEventsSession(daemonSessionId, desc)\n\n let session: ExportedSession\n try {\n if (source === \"native\") {\n session = await tryNative()\n } else if (source === \"daemon\") {\n session = await tryDaemon()\n } else {\n try {\n session = await tryNative()\n } catch (nativeErr) {\n try {\n session = await tryDaemon()\n } catch (daemonErr) {\n const nativeMsg = nativeErr instanceof Error ? nativeErr.message : String(nativeErr)\n const daemonMsg = daemonErr instanceof Error ? daemonErr.message : String(daemonErr)\n return err(`${nativeMsg}\\n(daemon-events fallback also failed: ${daemonMsg})`)\n }\n }\n }\n } catch (e) {\n return err(e instanceof Error ? e.message : String(e))\n }\n\n const content =\n format === \"json\"\n ? renderJson(session)\n : renderMarkdown(session, { maxToolChars })\n\n return {\n sessionId,\n adapter: adapterSlug ?? (session.meta.source === \"daemon-events\" ? \"daemon\" : \"unknown\"),\n format,\n meta: session.meta,\n content,\n }\n}\n","/**\n * Conversation as pivot: one store abstraction per provider, read on any\n * session. A \"conversation\" is the provider's own persisted session — the\n * durable object. An agentproto session (ACP or PTY) is just an attachment\n * to it. `CONVERSATION_STORES` is the single source of truth for \"where does\n * this provider keep its conversations, and how do I read/attach to one\" —\n * `RESUME_STRATEGIES` (resume-strategies.ts) and `EXPORT_STRATEGIES`\n * (transcript-export.ts) are thin adapters over it so the two historically\n * drifted tables can never diverge again.\n */\n\nimport { promises as fs, createReadStream } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { createInterface } from \"node:readline\"\nimport { join, resolve } from \"node:path\"\nimport type { ExportedSession, ExportedMessage } from \"./transcript-export.js\"\n\n/** Where a captured conversation id is recorded on the descriptor.\n * Same union as resume-strategies.ts's ResumeMetadataKey — re-exported\n * from there, NOT redefined, so the two can never drift. */\nexport type { ResumeMetadataKey } from \"./resume-strategies.js\"\nimport type { ResumeMetadataKey } from \"./resume-strategies.js\"\n\n/** One conversation found in a provider's native store, plus enough\n * evidence for a caller (or a human) to pick between several. */\nexport interface ConversationCandidate {\n /** The provider-native conversation id (claude: the jsonl uuid). */\n conversationId: string\n startedAt?: string\n lastActivityAt?: string\n messageCount?: number\n /** First user message, truncated to ~120 chars. Disambiguation aid. */\n preview?: string\n /** Which attachment wrote the tail, provider-native vocabulary\n * (claude-code: \"sdk-ts\" = ACP arm, \"cli\" = native TUI). */\n lastWriter?: string\n}\n\nexport interface DiscoverInput {\n cwd: string\n /** Only consider conversations active at-or-after this ISO ts. */\n since?: string\n /** Only consider conversations that STARTED at-or-before this ISO ts —\n * an upper bound, typically a dead session's `endedAt`. A conversation\n * whose own first message postdates it provably belongs to some other,\n * later session. Omit for a live/ongoing session (no `endedAt` yet) —\n * it must keep matching conversations that started after `since` with\n * no upper bound. Conservative: a candidate with no discoverable\n * `startedAt` is never dropped by this filter. */\n until?: string\n /** The requesting session's attachment mode, in ABSTRACT (provider-\n * agnostic) vocabulary — `\"native\"` for a real PTY/TUI attach,\n * `\"acp\"` for the agent-cli/ACP arm. When set, drops candidates whose\n * `lastWriter` is present and maps to the OPPOSITE mode (a native PTY's\n * conversation is never written by the ACP arm, and vice versa).\n * Conservative: a candidate with no `lastWriter` is never dropped, and\n * omitting this field applies no mode filtering at all. */\n attachmentMode?: \"native\" | \"acp\"\n /** The session's isolated provider config dir (`SessionDescriptor.\n * adapterConfigDir` — the `CLAUDE_CONFIG_DIR` the daemon spawns\n * claude-code with since the #824 MCP-isolation fix). When set, the\n * provider wrote its transcripts under THIS dir, not its global\n * default (`~/.claude`), so discovery must look there. Omit for a\n * native PTY / pre-#824 session — the global store applies. Only\n * claude-code keys its store off a config dir; other stores ignore\n * this field. */\n configDir?: string\n /** When set, bind to EXACTLY this conversation or return [].\n * MUST NOT fall back to a recency guess — see invariants. */\n expectedId?: string\n}\n\nexport interface ConversationStore {\n storeAs: ResumeMetadataKey\n /** Capture the conversation id from a live output line; group 1 = the id. */\n outputHint?: RegExp\n /** argv that attaches a NATIVE PTY to an existing conversation.\n * Omitted ⇒ this provider has no native attach path. */\n attachArgv?(conversationId: string): string[]\n /** Absolute path of the conversation's on-disk transcript, when the\n * provider's own resume command also accepts a path in place of a bare\n * id (claude-code: `claude --resume /abs/….jsonl`, which works from any\n * directory and does not depend on the project-slug folder). Pure path\n * arithmetic — never stats the file; callers check existence. Omitted\n * for providers whose resume takes ids only (hermes). `configDir` has\n * the same semantics as `DiscoverInput.configDir`. */\n transcriptPath?(cwd: string, conversationId: string, configDir?: string): string\n /** The env var `attachArgv`'s spawned PTY reads to find its conversation\n * store, when the store is keyed off a per-session isolated config dir\n * (`DiscoverInput.configDir`'s doc — today only claude-code's\n * `CLAUDE_CONFIG_DIR`; see #824). When set, `session_restart`'s\n * `pty-native` branch (session-tools.ts) passes\n * `{ [configDirEnvVar]: descriptor.adapterConfigDir }` to the resumed\n * PTY's env — omitting it is what leaves a `claude --resume <id>`\n * looking in the global `~/.claude` for a transcript that only exists\n * under the session's isolated dir, failing with \"No conversation\n * found\" before the resumed session's first turn. Omitted for stores\n * with no config-dir isolation (hermes: global store only). */\n configDirEnvVar?: string\n /** Find conversations in the provider's native store. `[]` = none —\n * a normal answer (e.g. the PTY is a bash shell). Never throws for\n * \"nothing found\"; throws only on a genuinely unreadable store. */\n discover(input: DiscoverInput): Promise<ConversationCandidate[]>\n /** Read a full conversation into the shared export model. `configDir`\n * has the same semantics as `DiscoverInput.configDir`. */\n read(conversationId: string, cwd?: string, configDir?: string): Promise<ExportedSession>\n /** Follow a conversation live. Resolves to an unsubscribe fn.\n * Omitted ⇒ no live tail for this provider (caller polls `read`). */\n follow?(input: {\n conversationId: string\n cwd?: string\n onMessage: (m: ExportedMessage) => void\n }): Promise<() => void>\n}\n\n// ── claude-code store ──────────────────────────────────────────────────\n//\n// Native store: ~/.claude/projects/<cwd-encoded>/<uuid>.jsonl, one file per\n// conversation. `<cwd-encoded>` is the absolute cwd run through\n// `claudeProjectSlug` (claude's own convention — see that function's\n// docblock for the empirically-verified rule). A daemon-spawned session is\n// config-dir-isolated (#824) and its store root is the session's\n// `CLAUDE_CONFIG_DIR` instead of `~/.claude` — same `projects/<slug>`\n// layout under it; see `DiscoverInput.configDir`.\n\ninterface ClaudeJsonlMeta {\n type?: string\n timestamp?: string\n entrypoint?: string\n message?: { role?: string; content?: unknown }\n}\n\n/**\n * Claude Code's own `cwd` → project-dir-name encoder.\n *\n * The real rule (verified empirically against `~/.claude/projects/`\n * directory names for dozens of known cwds, including dotted worktree\n * paths and macOS temp dirs): every character that is NOT `[a-zA-Z0-9]`\n * is replaced 1:1 with `-`. Crucially, runs of consecutive non-alnum\n * characters are NOT collapsed — each input character produces exactly\n * one output character.\n *\n * This used to be `cwd.replace(/\\//g, \"-\")` — slashes only. That's wrong\n * for any cwd containing a dot (every `.agentproto` worktree, any\n * dotdir), an underscore-prefixed segment, a space, etc., because those\n * characters are left untouched instead of becoming `-`, so the computed\n * directory never matches the one claude actually created.\n *\n * Reconstruction proof (both real, both `ls ~/.claude/projects/`-verified):\n * cwd /Users/jeremy/.agentproto/worktrees/ts/gc-fresh-hold\n * → -Users-jeremy--agentproto-worktrees-ts-gc-fresh-hold\n * (note the \"--\" from \"/.\" — one dash for \"/\", one for \".\", not collapsed)\n *\n * cwd /Volumes/SSDExternalMacStudio/Code/_agentproto-worktrees/adapter-claude-sdk\n * → -Volumes-SSDExternalMacStudio-Code--agentproto-worktrees-adapter-claude-sdk\n * (same double-dash shape, this time from \"/_\")\n */\nexport function claudeProjectSlug(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, \"-\")\n}\n\n/** Exported so `conversation-index.ts` can resolve the same directory\n * without re-deriving the slug rule itself.\n *\n * `configDir` is the session's isolated `CLAUDE_CONFIG_DIR` (see\n * `DiscoverInput.configDir`): the SDK mirrors the global layout under it\n * (`<configDir>/projects/<slug>/<uuid>.jsonl`, empirically verified\n * against real `~/.agentproto/adapter-config/sess_<id>` trees), so the\n * only difference is the base dir. */\nexport function claudeCodeProjectDir(cwd: string, configDir?: string): string {\n const base = configDir ?? resolve(homedir(), \".claude\")\n return resolve(base, \"projects\", claudeProjectSlug(cwd))\n}\n\nfunction extractFirstText(content: unknown): string | undefined {\n if (typeof content === \"string\") {\n const t = content.trim()\n return t || undefined\n }\n if (Array.isArray(content)) {\n for (const block of content) {\n if (\n block &&\n typeof block === \"object\" &&\n (block as { type?: string }).type === \"text\" &&\n typeof (block as { text?: unknown }).text === \"string\"\n ) {\n const t = ((block as { text: string }).text).trim()\n if (t) return t\n }\n }\n }\n return undefined\n}\n\n/** Claude records local slash-command bookkeeping as user messages. They are\n * not the user's prompt and must not become the session's title. */\nfunction isClaudeControlText(text: string): boolean {\n return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(text)\n}\n\n/** Light line-scan for candidate metadata — NOT the full transcript parser\n * (that's exportClaudeCodeSession). Streams the file so large jsonl files\n * don't get pulled fully into memory. */\nasync function scanClaudeJsonl(filePath: string): Promise<{\n startedAt?: string\n lastActivityAt?: string\n messageCount: number\n preview?: string\n lastWriter?: string\n}> {\n const stream = createReadStream(filePath, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n\n let startedAt: string | undefined\n let lastActivityAt: string | undefined\n let messageCount = 0\n let preview: string | undefined\n let lastWriter: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: ClaudeJsonlMeta\n try {\n entry = JSON.parse(trimmed) as ClaudeJsonlMeta\n } catch {\n continue\n }\n\n if (typeof entry.timestamp === \"string\") {\n if (!startedAt) startedAt = entry.timestamp\n lastActivityAt = entry.timestamp\n }\n if (typeof entry.entrypoint === \"string\") {\n lastWriter = entry.entrypoint\n }\n if (entry.type === \"user\" || entry.type === \"assistant\") {\n messageCount += 1\n if (preview === undefined && entry.type === \"user\") {\n const text = extractFirstText(entry.message?.content)\n if (text !== undefined && !isClaudeControlText(text)) {\n preview = text.length > 120 ? text.slice(0, 120) : text\n }\n }\n }\n }\n\n return { startedAt, lastActivityAt, messageCount, preview, lastWriter }\n}\n\nasync function buildClaudeCandidate(\n filePath: string,\n conversationId: string,\n): Promise<ConversationCandidate> {\n const scanned = await scanClaudeJsonl(filePath)\n return { conversationId, ...scanned }\n}\n\n/** Maps an abstract `attachmentMode` to claude's own jsonl `entrypoint`\n * vocabulary. Kept local to this store — callers deal only in\n * `\"native\" | \"acp\"`, never `\"cli\" | \"sdk-ts\"`. */\nfunction claudeEntrypointFor(mode: \"native\" | \"acp\"): string {\n return mode === \"native\" ? \"cli\" : \"sdk-ts\"\n}\n\nasync function discoverClaudeCode(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { cwd, since, until, attachmentMode, configDir, expectedId } = input\n const dir = claudeCodeProjectDir(cwd, configDir)\n\n // Ground truth beats heuristic: bind to exactly the conversation we were\n // asked about, never whichever transcript happens to be newest. Never\n // falls through to the mtime path below — see invariants.\n if (expectedId) {\n const filePath = join(dir, `${expectedId}.jsonl`)\n try {\n await fs.stat(filePath)\n } catch {\n return []\n }\n return [await buildClaudeCandidate(filePath, expectedId)]\n }\n\n let entries: string[]\n try {\n entries = await fs.readdir(dir)\n } catch {\n return []\n }\n const jsonlFiles = entries.filter(e => e.endsWith(\".jsonl\"))\n if (jsonlFiles.length === 0) return []\n\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : undefined\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n for (const f of jsonlFiles) {\n const filePath = join(dir, f)\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(filePath)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const conversationId = f.replace(/\\.jsonl$/, \"\")\n const candidate = await buildClaudeCandidate(filePath, conversationId)\n // `until` is an upper bound on the conversation's own first message —\n // provably not this session's if it started after the session ended.\n // Conservative: no discoverable startedAt ⇒ never dropped.\n if (Number.isFinite(untilMs) && candidate.startedAt !== undefined) {\n const startedMs = Date.parse(candidate.startedAt)\n if (Number.isFinite(startedMs) && startedMs > untilMs) continue\n }\n // Attachment-mode match: a native PTY's conversation is never written\n // by the ACP arm and vice versa. Conservative: no lastWriter ⇒ never\n // dropped, and an unset attachmentMode applies no filtering at all.\n if (wantEntrypoint !== undefined && candidate.lastWriter !== undefined && candidate.lastWriter !== wantEntrypoint) {\n continue\n }\n scored.push({ candidate, mtimeMs })\n }\n\n // Newest first — mirrors the old fsProbe's mtime-desc pick, and gives\n // callers of `discover` (which may just want \"the likely one\") a sane\n // default ordering without silently collapsing the list.\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\nasync function readClaudeCode(conversationId: string, cwd?: string, configDir?: string): Promise<ExportedSession> {\n // Dynamic import, not a top-level one: resume-strategies.ts is its own\n // lean tsup entry (splitting:false) and pulls this module in as a value\n // import via CONVERSATION_STORES. esbuild still bundles transcript-\n // export.ts's bytes into that entry either way (no code-splitting means\n // no separate chunk to defer to) — what the dynamic import buys is\n // deferred MODULE INIT: transcript-export.ts's top-level code only runs\n // the first time `read()` is actually called, not merely by importing\n // resume-strategies.ts. See resume-strategies.ts's splitting:false note.\n const { exportClaudeCodeSession } = await import(\"./transcript-export.js\")\n return exportClaudeCodeSession(conversationId, cwd, configDir)\n}\n\n// ── hermes store ────────────────────────────────────────────────────\n//\n// Native store: ~/.hermes/state.db, a sqlite `sessions` table keyed by\n// `id` (the agentproto UUID passed as resumeSessionId — the same id\n// agentproto records as `adapterSessionId`, unlike claude-code's on-disk\n// jsonl uuid). It has a `cwd` column (live-verified 2026-07-17), so\n// cwd-scoped discovery is possible, unlike a plain folder-of-files store.\n\nasync function discoverHermes(input: DiscoverInput): Promise<ConversationCandidate[]> {\n // Dynamic import, not a top-level one — same reasoning as readClaudeCode\n // above: keeps transcript-export.ts's sqlite plumbing out of\n // resume-strategies.ts's lean tsup entry until a hermes store method\n // actually runs.\n const { discoverHermesSessions } = await import(\"./transcript-export.js\")\n return discoverHermesSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readHermes(conversationId: string): Promise<ExportedSession> {\n const { exportHermesSession } = await import(\"./transcript-export.js\")\n return exportHermesSession(conversationId)\n}\n\n// ── codex store ───────────────────────────────────────────────────────\n//\n// Native store: $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl.\n// The rollout uuid == the ACP session id agentproto records as\n// `adapterSessionId` (verified empirically), so a codex session's export\n// binds by that id directly (step 1 of the conversation-read ladder), and\n// `discover` scopes a PTY-only codex by the session_meta `cwd`. Reader in\n// transcript-export.ts, dynamically imported for the same reason as\n// readClaudeCode above (keep it out of resume-strategies.ts's lean entry).\n\nasync function discoverCodex(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverCodexSessions } = await import(\"./transcript-export.js\")\n return discoverCodexSessions(input.cwd, input.since, input.until, input.expectedId)\n}\n\nasync function readCodex(conversationId: string): Promise<ExportedSession> {\n const { exportCodexSession } = await import(\"./transcript-export.js\")\n return exportCodexSession(conversationId)\n}\n\n// ── opencode store ────────────────────────────────────────────────────\n//\n// Native store: ${XDG_DATA_HOME:-~/.local/share}/opencode/opencode.db. The\n// `session` table's id is the `ses_…` id agentproto records as\n// `adapterSessionId` (verified), and its `directory` column is the cwd — so\n// discovery is cwd-scoped like hermes.\n\nasync function discoverOpenCode(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverOpenCodeSessions } = await import(\"./transcript-export.js\")\n return discoverOpenCodeSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readOpenCode(conversationId: string): Promise<ExportedSession> {\n const { exportOpenCodeSession } = await import(\"./transcript-export.js\")\n return exportOpenCodeSession(conversationId)\n}\n\n// ── mastracode-inprocess store ────────────────────────────────────────\n//\n// Native store: ${AGENTPROTO_HOME:-~/.agentproto}/mastracode-inprocess/\n// storage.db (Mastra libsql). sessionId is \"<resourceId>:<threadId>\";\n// `read` splits out the threadId. No cwd column, so `discover` is\n// exact-bind only (this in-process arm always supplies an adapterSessionId).\n\nasync function discoverMastracodeInprocess(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverMastracodeInprocessSessions } = await import(\"./transcript-export.js\")\n return discoverMastracodeInprocessSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readMastracodeInprocess(conversationId: string): Promise<ExportedSession> {\n const { exportMastracodeInprocessSession } = await import(\"./transcript-export.js\")\n return exportMastracodeInprocessSession(conversationId)\n}\n\n// ── pi store ──────────────────────────────────────────────────────────\n//\n// Native store: ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl. The uuid\n// == pi's RPC sessionId recorded as `adapterSessionId`; discovery keys off\n// each file's authoritative `session.cwd` (the slug encoding is\n// version-dependent, so it is never trusted for scoping).\n\nasync function discoverPi(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverPiSessions } = await import(\"./transcript-export.js\")\n return discoverPiSessions(input.cwd, input.since, input.until, input.expectedId)\n}\n\nasync function readPi(conversationId: string): Promise<ExportedSession> {\n const { exportPiSession } = await import(\"./transcript-export.js\")\n return exportPiSession(conversationId)\n}\n\nexport const CONVERSATION_STORES: Record<string, ConversationStore> = {\n \"claude-code\": {\n storeAs: \"claudeResumeId\",\n // Printed by claude on graceful exit when session persistence is on\n // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`\n outputHint: /claude\\s+--resume\\s+([0-9a-f-]{8,})/i,\n attachArgv: (conversationId: string) => [\"claude\", \"--resume\", conversationId],\n transcriptPath: (cwd: string, conversationId: string, configDir?: string) =>\n join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),\n configDirEnvVar: \"CLAUDE_CONFIG_DIR\",\n discover: discoverClaudeCode,\n read: readClaudeCode,\n },\n hermes: {\n storeAs: \"hermesResumeId\",\n // `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);\n // `--resume SESSION --tui` is the native TUI resume path — same binary,\n // different flags, unlike claude-code where the two arms are different\n // binaries. Verified via `hermes --help`: `--resume SESSION, -r` =\n // \"Resume a previous session by ID or title\", `--tui` = the real TUI.\n attachArgv: (conversationId: string) => [\"hermes\", \"--resume\", conversationId, \"--tui\"],\n discover: discoverHermes,\n read: readHermes,\n },\n // codex-acp writes no resume flag we sniff, and there's no native PTY\n // reattach argv to declare — export/read only, so no outputHint/attachArgv.\n codex: {\n storeAs: \"codexResumeId\",\n discover: discoverCodex,\n read: readCodex,\n },\n opencode: {\n storeAs: \"openCodeResumeId\",\n // Match the adapter's portable npx installation: a direct `opencode`\n // binary is not guaranteed to be on the daemon's PATH on every host.\n attachArgv: (conversationId: string) => [\"npx\", \"-y\", \"opencode-ai\", \"-s\", conversationId],\n discover: discoverOpenCode,\n read: readOpenCode,\n },\n \"mastracode-inprocess\": {\n storeAs: \"mastracodeInprocessResumeId\",\n discover: discoverMastracodeInprocess,\n read: readMastracodeInprocess,\n },\n pi: {\n storeAs: \"piResumeId\",\n discover: discoverPi,\n read: readPi,\n },\n}\n\n/**\n * Bare (non-resume) launch argv for a provider's native terminal/TUI — the\n * \"start fresh\" counterpart to `attachArgv`, used to open a NEW native\n * session (e.g. a harness card's \"Terminal\" button) rather than reattach an\n * existing conversation.\n *\n * Coverage is deliberately BROADER than `attachArgv`'s: reattach needs a\n * documented resume flag, but a fresh launch only needs the CLI's\n * interactive arm. npx-spawned adapters use the same npx form here so the\n * terminal works wherever the adapter itself works, PATH binary or not.\n *\n * Absent on purpose (no interactive arm to launch):\n * - `mastracode-inprocess` — in-process SDK, nothing to exec\n * - `mastra-agent`, `claude-sdk` — first-party ACP servers, no TUI\n * - `antigravity` — `agy` is headless-only (`-p`/stream-json), no TUI\n */\nexport const NATIVE_LAUNCH_ARGV: Record<string, string[]> = {\n \"claude-code\": [\"claude\"],\n hermes: [\"hermes\", \"--tui\"],\n // Bare arm of the same npx package the adapter spawns IS the TUI.\n opencode: [\"npx\", \"-y\", \"opencode-ai\"],\n mastracode: [\"npx\", \"-y\", \"mastracode\"],\n // The adapter bundles the codex-acp wrapper (ACP-only); the interactive\n // Codex TUI lives in @openai/codex, fetched by npx on first use.\n codex: [\"npx\", \"-y\", \"@openai/codex\"],\n // ACP arms add flags/subcommands to these binaries; bare is interactive.\n gemini: [\"gemini\"],\n \"grok-cli\": [\"grok\"],\n pi: [\"pi\"],\n jcode: [\"jcode\"],\n // `openclaw chat` = \"Open a local terminal UI (alias for tui --local)\".\n openclaw: [\"openclaw\", \"chat\"],\n // Generic ACP catalog CLIs whose ACP arm is a separate binary/subcommand;\n // the bare bin is the interactive TUI.\n \"gemini-cli\": [\"gemini\"],\n \"qwen-code\": [\"qwen\"],\n \"mistral-vibe\": [\"vibe\"],\n \"kimi-cli\": [\"kimi\"],\n}\n\n/**\n * Direct-installed binaries that ARE the same TUI as an npx launch arm,\n * keyed by slug. The npm package behind `npx -y <pkg>` often installs a\n * different bin name (opencode-ai → `opencode`), so a PATH entry appears\n * inargv as the bare bin, not the npx spec. Keep npx compatibility — the\n * NATIVE_LAUNCH_ARGV npx forms keep matching the full npx argv; this table\n * only adds the bare-binary arm.\n */\nconst BARE_LAUNCH_BINS: Record<string, readonly string[]> = {\n opencode: [\"opencode\"],\n mastracode: [\"mastracode\"],\n}\n\n// ── Conversation-terminal classification ───────────────────────────────\n//\n// A native provider TUI in a PTY (claude, hermes, grok, …) is a\n// CONVERSATION the operator happens to be driving through a terminal —\n// trackable in the Sessions list, linkable to its native transcript,\n// switchable to the ACP harness. A plain shell (`bash`, `zsh`, one-shot\n// `bash -lc …`) is none of those and stays Activity-only. This is the ONE\n// shared classifier for that split — the runtime owns it (sessions.ts's\n// list/lane/link paths) and the VS Code extension imports it; do not copy\n// the table client-side.\n\n/** The descriptor fields classification reads. A full `SessionDescriptor`\n * or `SessionSummary` satisfies this structurally. */\nexport interface ConversationTerminalSubject {\n kind?: string\n adapterSlug?: string\n argv?: readonly string[]\n resumeMetadata?: Record<string, string>\n}\n\n/** Every `resumeMetadata` key any conversation store records under —\n * presence of one on a PTY row is proof it holds a provider conversation\n * (the graceful-exit sniffer or the link probe put it there). */\nconst CONVERSATION_RESUME_KEYS: ReadonlySet<string> = new Set(\n Object.values(CONVERSATION_STORES).map(s => s.storeAs),\n)\n\nfunction argvBasename(path: string): string {\n const idx = path.lastIndexOf(\"/\")\n return idx === -1 ? path : path.slice(idx + 1)\n}\n\n/**\n * The adapter/store slug a terminal's argv (or stamped `adapterSlug`) maps\n * to, or `undefined` for anything that isn't a known provider TUI. Matches:\n * - a stamped `adapterSlug` that is a `CONVERSATION_STORES` /\n * `NATIVE_LAUNCH_ARGV` key;\n * - argv[0]'s basename against each launch table entry's own binary\n * (`claude` → `claude-code`, `grok` → `grok-cli`, …);\n * - an npx-launched TUI only when the FULL npx launch argv is a prefix\n * of the session's argv (`npx -y opencode-ai …` → `opencode`) — a bare\n * `npx` running anything else is not a conversation;\n * - a direct-installed TUI binary from `BARE_LAUNCH_BINS` (bare\n * `opencode` / `mastracode` on PATH) — same terminal as the npx arm.\n * The returned slug may have no `CONVERSATION_STORES` entry yet (grok):\n * classifiable and trackable, but no transcript to link or read.\n */\nexport function conversationTerminalSlugFor(\n subject: Pick<ConversationTerminalSubject, \"adapterSlug\" | \"argv\">,\n): string | undefined {\n if (\n subject.adapterSlug &&\n (CONVERSATION_STORES[subject.adapterSlug] || NATIVE_LAUNCH_ARGV[subject.adapterSlug])\n ) {\n return subject.adapterSlug\n }\n const argv = subject.argv\n if (!argv || argv.length === 0 || !argv[0]) return undefined\n const bin = argvBasename(argv[0])\n for (const [slug, bins] of Object.entries(BARE_LAUNCH_BINS)) {\n if (bins.some(b => argvBasename(b) === bin)) return slug\n }\n for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {\n const launchBin = launch[0]\n if (!launchBin || argvBasename(launchBin) !== bin) continue\n if (launchBin === \"npx\") {\n if (launch.every((arg, i) => argv[i] === arg)) return slug\n continue\n }\n return slug\n }\n return undefined\n}\n\n/**\n * True when this PTY row is a provider TUI we can treat as a conversation:\n * a known native launch/store binary or slug, OR a resume id one of the\n * conversation stores already recorded on it. False for plain shells and\n * for `kind === \"command\"` — those stay in the Activity panel only.\n */\nexport function isConversationTerminal(subject: ConversationTerminalSubject): boolean {\n if (subject.kind !== \"terminal\") return false\n if (conversationTerminalSlugFor(subject) !== undefined) return true\n const meta = subject.resumeMetadata\n if (meta) {\n for (const key of CONVERSATION_RESUME_KEYS) {\n if (meta[key]) return true\n }\n }\n return false\n}\n","/**\n * Per-adapter resume strategies — the daemon's view of \"how do I\n * continue a previous session for THIS adapter?\".\n *\n * The driver already has an `AgentCliContinuation` declaration in\n * the adapter manifest (AIP-45). That covers strategy NAMES\n * (`native-resume`, `pinned-session`, `transcript`, `none`) but is\n * silent on the mechanics that live OUTSIDE the runtime — like\n * \"where on disk does this adapter store its conversations?\" or\n * \"what argv do I pass to resume into PTY mode?\".\n *\n * This table is that practical bridge. Each entry declares three\n * optional hooks:\n *\n * outputHint regex matched against each output line; group 1 is\n * the resume id. Stored as `desc.resumeMetadata[storeAs]`.\n * fsProbe async function that probes a per-adapter directory\n * for a stored session id, scoped to a workspace cwd.\n * Used when `outputHint` never matched.\n * spawnArgs given a captured id, returns the argv to spawn a\n * PTY that resumes the session. agentproto then\n * bypasses ACP/agent-cli and runs the provider\n * directly — the most reliable resume path because\n * it leans on the provider's native UX.\n *\n * Adapters that don't declare anything here fall back to:\n * 1. ACP-level resume via `resumeSessionId` on POST /sessions/agent\n * 2. Fresh spawn (same shape, no continuity)\n *\n * Eventually each adapter should ship its own strategy on\n * `AgentCliHandle.resumeStrategy` (driver-side). This central table\n * is the bootstrap: it lets a new adapter become resume-capable\n * without bumping its npm package; once that lands, entries\n * graduate into the adapter packages.\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nimport { CONVERSATION_STORES } from \"./conversation-store.js\"\nimport type { ConversationStore } from \"./conversation-store.js\"\n\n/** Where on disk the resume id ends up on the session descriptor. */\nexport type ResumeMetadataKey =\n | \"claudeResumeId\"\n | \"hermesResumeId\"\n | \"codexResumeId\"\n | \"openClawResumeId\"\n | \"openCodeResumeId\"\n // Export/read-only stores (no native resume wired): the key labels the\n // conversation store entry in CONVERSATION_STORES but is never written to\n // a descriptor's resumeMetadata, since these have no attachArgv/outputHint.\n | \"mastracodeInprocessResumeId\"\n | \"piResumeId\"\n\nexport interface ResumeStrategy {\n /** When set, run on every output line for this adapter. Group 1\n * captures the resume id. The match is saved on the descriptor\n * as `resumeMetadata[storeAs]`. */\n outputHint?: RegExp\n /** Where the matched id is recorded on the descriptor. */\n storeAs: ResumeMetadataKey\n /** Probe the adapter's on-disk session store for the resume id of a\n * session in the given workspace. Returns the id or null when nothing\n * eligible.\n *\n * When `expectedId` is set the session's own conversation id is known\n * (agent-cli descriptors carry it as `adapterSessionId`; for\n * claude-code it IS the on-disk `.jsonl` uuid — see acp-host.ts) and\n * the probe MUST bind to exactly that transcript: it confirms\n * `<expectedId>` exists and returns it, else null. It never falls\n * through to a mtime guess in that case — resuming *some other*\n * session that merely shares the cwd is a silent cross-session\n * correctness bug, strictly worse than not resuming at all.\n *\n * Only when `expectedId` is absent (e.g. a raw PTY claude session that\n * never had an ACP session id) does the probe fall back to\n * most-recently-modified, filtered to files at-or-after `prevStartedAt`\n * (avoids resuming an unrelated prior conversation).\n *\n * `configDir` is the session's isolated provider config dir\n * (`SessionDescriptor.adapterConfigDir`): a daemon-spawned claude-code\n * session (#824) persists its transcripts under that dir, not the\n * provider's global store — the probe must look there. Omit for a\n * native PTY / pre-#824 session (global store applies).\n *\n * Skip when the adapter doesn't persist sessions externally. */\n fsProbe?(\n cwd: string,\n prevStartedAt: string,\n expectedId?: string,\n configDir?: string,\n ): Promise<string | null>\n /** Return the argv to spawn a PTY that resumes into the given id.\n * When omitted, the daemon falls back to ACP-level resume via\n * the agent-cli protocol instead of the provider's native CLI. */\n spawnArgs?(id: string): string[]\n /** Env var the resumed PTY needs pointed at the session's isolated\n * provider config dir to find ITS OWN conversation store — see\n * `ConversationStore.configDirEnvVar`'s doc (conversation-store.ts) for\n * why this exists and what silently omitting it breaks. Undefined for\n * a provider with no config-dir-isolated store. */\n configDirEnvVar?: string\n /** Absolute transcript path for a conversation id — see\n * `ConversationStore.transcriptPath`. Present only when the provider's\n * resume command also accepts a path in place of a bare id. */\n transcriptPath?(cwd: string, conversationId: string, configDir?: string): string\n}\n\n// Project every conversation store that declares a native PTY attach argv\n// into the resume-strategy table, so the table and the store can never drift\n// apart again. Only stores with `attachArgv` offer a provider-native TUI\n// resume; the rest are read/export-only. The actual decision to USE a\n// `pty-native` restart is still gated on `RestartCandidate.nativeTerminalResume`\n// below — a store entry alone does not license terminal mode.\nexport const RESUME_STRATEGIES: Record<string, ResumeStrategy> = Object.fromEntries(\n Object.entries(CONVERSATION_STORES)\n .filter(([, store]) => typeof store.attachArgv === \"function\")\n .map(([slug, store]) => {\n const s = store as Required<Pick<ConversationStore, \"attachArgv\" | \"discover\">> & ConversationStore\n return [\n slug,\n {\n outputHint: store.outputHint,\n storeAs: store.storeAs,\n ...(store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {}),\n ...(store.transcriptPath ? { transcriptPath: store.transcriptPath } : {}),\n fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {\n const candidates = await s.discover({\n cwd,\n since: prevStartedAt,\n expectedId,\n configDir,\n })\n return candidates[0]?.conversationId ?? null\n },\n spawnArgs: s.attachArgv,\n } satisfies ResumeStrategy,\n ]\n }),\n)\n\n/**\n * Helper: which adapters declare any resume capability? Used by\n * `agentproto sessions restart` to print a helpful \"this adapter\n * doesn't support resume; falling back to fresh spawn\" hint when\n * applicable.\n */\nexport function hasResumeStrategy(adapterSlug: string | undefined): boolean {\n if (!adapterSlug) return false\n const s = RESUME_STRATEGIES[adapterSlug]\n return !!(s && (s.outputHint || s.fsProbe || s.spawnArgs))\n}\n\n// ── Restart decision tree ────────────────────────────────────────\n//\n// Shared between the CLI (`agentproto sessions restart`, sessions.ts)\n// and the daemon's `session_restart` MCP tool (session-tools.ts) —\n// both need to answer \"given this dead/alive session, how do I bring\n// it back with the most continuity?\" the exact same way. Duck-typed\n// on a minimal shape rather than importing `SessionDescriptor` from\n// sessions.ts: this file is its own tsup entry (`splitting: false`),\n// so a value-level cross-entry import would inline the whole sessions\n// module here. A full `SessionDescriptor` satisfies these structurally.\n\nexport interface RestartCandidate {\n adapterSlug?: string\n resumeMetadata?: Record<string, string>\n pty?: boolean\n adapterSessionId?: string\n /** Manifest-declared `capabilities.resumable` for `adapterSlug` (see\n * `SessionDescriptor.resumable`'s doc) — the honesty gate for the whole\n * decision tree below. `false` means an `adapterSessionId` here is a\n * dead end: the adapter cannot rehydrate a prior conversation from it, no\n * matter how it's presented. Omitted/`true` preserves today's behaviour\n * (assumed resumable) for every adapter/row that doesn't set this. */\n resumable?: boolean\n /**\n * Manifest-declared `capabilities.nativeTerminalResume` for `adapterSlug`.\n * The `pty-native` strategy requires this flag for ACP-origin sessions.\n * Legacy PTY rows without the flag can use a newly-added store strategy\n * when they already carry an exact native resume id; explicit false blocks\n * that fallback. ACP resumability (`resumable`) does not imply a TUI resume.\n */\n nativeTerminalResume?: boolean\n}\n\nexport interface FsProbeCandidate extends RestartCandidate {\n cwd?: string\n startedAt: string\n /** The session's isolated provider config dir (`SessionDescriptor.\n * adapterConfigDir`) — where a daemon-spawned claude-code session's\n * transcripts actually live since #824. A full `SessionDescriptor`\n * satisfies this structurally; absent on pre-#824/PTY rows, where the\n * provider's global store is the right place to probe. */\n adapterConfigDir?: string\n}\n\n/**\n * Where a restart should land. Computed once so callers never diverge:\n *\n * 1. pty-native — the adapter has a captured resume id AND declares\n * `spawnArgs` (e.g. claude-code): respawn a PTY running the\n * provider's own resume command. Most reliable CONTINUITY mechanism\n * when it applies — but only actually reachable by default when the\n * prior session was ITSELF a real PTY (`prev.pty === true`), or the\n * caller explicitly opts in (`preferNativeTerminal`) — see the\n * `mayPreferNative` doc below for why.\n * 2. pty-plain — the previous session was a real PTY with no\n * native strategy match: re-run the same argv, no continuity.\n * 3. agent — an agent-cli session with no native strategy (or an\n * ACP-origin session where pty-native doesn't apply — see above):\n * resume at the ACP level via the adapter's own session id (may\n * still 404 if the adapter never persisted a turn — callers\n * should retry without `resumeSessionId` on a \"not found\" error).\n * 4. unsupported — a generic command session; no history to resume\n * and no shape to clone reliably.\n */\nexport type RestartStrategy =\n | { kind: \"pty-native\"; argv: string[] }\n | { kind: \"pty-plain\" }\n | { kind: \"agent\"; resumeSessionId?: string; resumeFallback?: boolean }\n | { kind: \"unsupported\"; reason: string }\n\nexport interface DecideRestartStrategyOptions {\n /**\n * Explicit opt-in to provider-native terminal resume for a session whose\n * ORIGIN was agent-cli/ACP (not itself a PTY) — a human who wants the raw\n * provider TUI instead of ACP-level resume, understanding the tradeoffs\n * (see `mayPreferNative`'s doc below). Default false/omitted: an\n * ACP-origin session always resumes via ACP.\n */\n preferNativeTerminal?: boolean\n}\n\nexport function decideRestartStrategy(\n prev: RestartCandidate,\n opts: DecideRestartStrategyOptions = {},\n): RestartStrategy {\n // Provider-native terminal resume takes precedence over ACP-level resume —\n // but ONLY when a verified native TUI resume strategy exists and the\n // capability/origin gates pass (closing\n // the \"restart starts a terminal but it doesn't work\" bug) either the prior\n // session was ITSELF a raw PTY (`prev.pty === true` — a real provider TUI a\n // human was already looking at, whose config dir went through the actual\n // interactive onboarding wizard), or the caller explicitly opted in\n // (`preferNativeTerminal`). An agent-cli/ACP session's isolated\n // `CLAUDE_CONFIG_DIR` (#824) is populated ONLY by the headless ACP\n // entrypoint, which never runs the interactive TUI wizard — so defaulting\n // an ACP-origin session's restart to pty-native silently drops it onto\n // claude-code's first-run onboarding screen (theme picker, \"detected a\n // custom API key\" prompt) with no one attached to answer it, forever.\n // `nativeTerminalResume` alone is a CAPABILITY declaration (\"this adapter\n // supports native resume for a real terminal\"), not license to mode-switch\n // a headless session into an unattended one.\n const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true\n // PTYs created before their store gained attachArgv lack the persisted\n // capability flag. Their native ID plus today's verified strategy is enough\n // to continue the same provider conversation; an explicit false still wins.\n const hasNativeCapability = prev.nativeTerminalResume === true ||\n (prev.pty === true && prev.nativeTerminalResume === undefined)\n if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {\n const strategy = RESUME_STRATEGIES[prev.adapterSlug]\n const id = strategy?.storeAs\n ? prev.resumeMetadata?.[strategy.storeAs]\n : undefined\n if (strategy?.spawnArgs && id) {\n return { kind: \"pty-native\", argv: strategy.spawnArgs(id) }\n }\n }\n if (prev.pty === true) {\n return { kind: \"pty-plain\" }\n }\n if (prev.adapterSlug) {\n // Honesty gate (resume-honesty fix): an adapter that declares\n // `resumable: false` (hermes, mastra-agent, …) cannot rehydrate a prior\n // conversation from `adapterSessionId` at all — passing it as\n // `resumeSessionId` either silently comes back blank or gets rejected by\n // the wrapper, and either way the caller must never present it as\n // continuity. Route straight to a flagged fresh spawn instead of\n // emitting a phantom id. `resumable === undefined` (unknown/legacy row,\n // or an adapter this fix hasn't reached) keeps today's behaviour.\n const canResumeAtAcpLevel = prev.resumable !== false\n return {\n kind: \"agent\",\n ...(prev.adapterSessionId && canResumeAtAcpLevel\n ? { resumeSessionId: prev.adapterSessionId }\n : {}),\n ...(prev.adapterSessionId && !canResumeAtAcpLevel\n ? { resumeFallback: true }\n : {}),\n }\n }\n return {\n kind: \"unsupported\",\n reason: \"generic command session — restart only supports pty + agent-cli\",\n }\n}\n\n/**\n * Matches an adapter's rejection of a `resumeSessionId` it can't honour —\n * the trigger for the \"retry as a fresh spawn\" fallback in\n * `session-restart-core.ts` and the CLI's `sessions restart`. Beyond a\n * literal \"not found\" (an unknown/never-persisted id), this also covers the\n * ACP wrapper-level rejection an adapter with `loadSession: false` throws\n * when a generic `resumeSessionId` reaches it despite `capabilities.\n * resumable: true` at the manifest level (e.g. claude-sdk — resumable via\n * its own SDK session store, but no ACP `session/load` surface): the\n * `@agentclientprotocol/sdk` server-side dispatcher throws a JSON-RPC\n * \"Method not found\" for an unimplemented `session/load`, and an agent may\n * instead reject with its own \"does not support\" / \"not supported\" wording.\n * Shared by every fallback call site so they can't drift out of sync (see\n * this module's own header comment on why that matters).\n */\nexport const RESUME_ID_REJECTED_RE =\n /not found|does not support|not supported|unsupported/i\n\n/**\n * Generic filesystem-fallback: for each known adapter strategy that\n * defines an `fsProbe`, run it against this session's cwd. If a\n * resume id is found, attach it to the descriptor's resumeMetadata.\n *\n * Lets `restart` recover continuity even when our own output sniffer\n * missed the resume hint (session killed too quickly, output buffered\n * past the kill, …). Eligible only when the file is at-or-after the\n * session's `startedAt` (avoid resuming an unrelated prior session\n * in the same cwd — see ResumeStrategy.fsProbe comment).\n *\n * Fix: when NO `adapterSessionId` was ever captured (session killed\n * before the ACP handshake), run a recency-window probe. When an\n * `adapterSessionId` exists but points to a missing JSONL, the probe\n * tries exact-bind and returns nothing — that's correct, we NEVER\n * silently resume a sibling conversation (safety: prevents cross-\n * session contamination). The adapter-level resume will fail with\n * \"not found\", triggering the fresh-spawn fallback + digest injection.\n */\nexport async function augmentWithFsResume<T extends FsProbeCandidate>(\n prev: T,\n): Promise<T> {\n const slug = prev.adapterSlug\n if (!slug) return prev\n const strategy = RESUME_STRATEGIES[slug]\n if (!strategy?.fsProbe) return prev\n // If we already have a captured id for this strategy's storage key,\n // skip the FS probe — the sniffer found it during the session.\n if (prev.resumeMetadata?.[strategy.storeAs]) return prev\n if (!prev.cwd) return prev\n // Pass the session's own conversation id (agent-cli sets it as\n // `adapterSessionId`; for claude-code it equals the on-disk `.jsonl`\n // uuid — acp-host.ts pins them equal) so the probe binds to THIS\n // session's transcript instead of the newest one in the cwd. Without\n // it, a restart of a session that shares a cwd with a concurrent one\n // could silently resume the wrong conversation. When the exact-bind\n // finds nothing (the JSONL is gone), we deliberately do NOT fall\n // through to a sibling — better to fail-fast and inject a digest.\n const id = await strategy.fsProbe(\n prev.cwd,\n prev.startedAt,\n prev.adapterSessionId,\n // A config-dir-isolated session (#824) persisted its transcript under\n // its own CLAUDE_CONFIG_DIR — probe there, not the global ~/.claude.\n prev.adapterConfigDir,\n )\n if (!id) return prev\n return {\n ...prev,\n // When `adapterSessionId` was never captured (session killed before\n // the ACP handshake completed), backfill it from the fsProbe result\n // so the agent restart path can attempt ACP-level resume too — not\n // just the PTY-native path. For claude-code (and most adapters) the\n // on-disk conversation id IS the ACP session id, so the values are\n // interchangeable. Only backfills; never overwrites an existing id.\n ...(!prev.adapterSessionId ? { adapterSessionId: id } : {}),\n resumeMetadata: {\n ...(prev.resumeMetadata ?? {}),\n [strategy.storeAs]: id,\n },\n }\n}\n\n/** What `probeNativeTranscript` found for a candidate's own transcript. */\nexport interface NativeTranscriptProbe {\n /** Directory that was (or would be) probed for the transcript. */\n dir: string\n /** Absolute path of the transcript file the id maps to. */\n path: string\n /** Whether that exact file exists on disk right now. */\n exists: boolean\n}\n\n/**\n * Locate the candidate's own on-disk transcript for a provider whose native\n * resume command accepts an absolute path (`ResumeStrategy.transcriptPath`).\n * Returns `undefined` when the adapter declares no `transcriptPath`, or the\n * candidate has no cwd / no conversation id to map — callers treat that as\n * \"nothing to upgrade, and no directory worth naming in diagnostics\".\n *\n * The id lookup mirrors `decideRestartStrategy`'s (`resumeMetadata[storeAs]`),\n * falling back to `adapterSessionId` (for claude-code the ACP session id IS\n * the on-disk `.jsonl` uuid — acp-host.ts pins them equal) so a probe can\n * still name the transcript it looked for when the sniffer never fired.\n * `exists: false` is the honest \"transcript not found\" answer — never fall\n * through to a sibling file (cross-session contamination, see fsProbe's doc).\n */\nexport async function probeNativeTranscript(\n prev: FsProbeCandidate,\n): Promise<NativeTranscriptProbe | undefined> {\n if (!prev.adapterSlug || !prev.cwd) return undefined\n const strategy = RESUME_STRATEGIES[prev.adapterSlug]\n if (!strategy?.transcriptPath) return undefined\n const id = prev.resumeMetadata?.[strategy.storeAs] ?? prev.adapterSessionId\n if (!id) return undefined\n const path = strategy.transcriptPath(prev.cwd, id, prev.adapterConfigDir)\n let exists = false\n try {\n exists = (await fs.stat(path)).isFile()\n } catch {\n exists = false\n }\n return { dir: dirname(path), path, exists }\n}\n\n/**\n * Describe which resume path a restart used, for CLI banners / MCP\n * responses. Returns a short phrase like \"resumed via claude --resume\",\n * or \"resumed via ACP\", or \"\" when no resume was attempted.\n *\n * Honesty gate (resume-honesty fix): \"resumed via ACP\" is a claim of actual\n * conversation continuity, so it's only returned when the adapter's\n * declared capability backs that claim (`resumable !== false`). An adapter\n * that declared `resumable: false` but still carries an `adapterSessionId`\n * gets an honest degraded label instead of the lie — never silently \"\".\n *\n * Origin gate (mirrors `decideRestartStrategy`'s own — MUST stay in sync or\n * this label lies about what actually happened): the native-resume phrasing\n * is only reported when `decideRestartStrategy` itself would have picked\n * pty-native for this `prev` — i.e. `prev.pty === true` or the caller passed\n * the SAME `preferNativeTerminal` it decided the strategy with. Without this\n * gate, a captured `resumeMetadata` alone used to make this function claim\n * \"resumed via claude --resume\" even when the actual decision (now)\n * downgrades an ACP-origin session to ACP-level resume instead.\n */\nexport function describeResumePath(\n prev: RestartCandidate,\n opts: DecideRestartStrategyOptions = {},\n): string {\n const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true\n const hasNativeCapability = prev.nativeTerminalResume === true ||\n (prev.pty === true && prev.nativeTerminalResume === undefined)\n if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {\n const s = RESUME_STRATEGIES[prev.adapterSlug]\n if (s?.spawnArgs && s.storeAs && prev.resumeMetadata?.[s.storeAs]) {\n const sample = s.spawnArgs(\"…\")\n const idIndex = sample.indexOf(\"…\")\n return `resumed via ${idIndex > 0 ? sample.slice(0, idIndex).join(\" \") : (sample[0] ?? prev.adapterSlug)}`\n }\n }\n if (prev.adapterSlug && prev.adapterSessionId) {\n if (prev.resumable === false) {\n return `fresh — resume not supported by ${prev.adapterSlug}`\n }\n return \"resumed via ACP\"\n }\n return \"\"\n}\n\n/**\n * Shell-style argv tokenizer for descriptors that don't carry `argv`\n * separately (legacy persisted rows predating that field). Same rules\n * as the spawn-dialog tokenizer in the web app: whitespace splits,\n * single + double quotes group.\n */\nexport function tokenizeCommand(s: string): string[] {\n const out: string[] = []\n let buf = \"\"\n let inSingle = false\n let inDouble = false\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"'\" && !inDouble) {\n inSingle = !inSingle\n continue\n }\n if (ch === '\"' && !inSingle) {\n inDouble = !inDouble\n continue\n }\n if (!inSingle && !inDouble && /\\s/.test(ch ?? \"\")) {\n if (buf) {\n out.push(buf)\n buf = \"\"\n }\n continue\n }\n buf += ch\n }\n if (buf) out.push(buf)\n return out\n}\n"]}
1
+ {"version":3,"sources":["../src/tool-presenter.ts","../src/tool-call-record.ts","../src/pr-provenance.ts","../src/usage.ts","../src/transcript-writer.ts","../src/transcript-export.ts","../src/conversation-store.ts","../src/resume-strategies.ts"],"names":["join","resolve","homedir","fs","text","createReadStream","createInterface","exportClaudeCodeSession","discoverHermesSessions","exportHermesSession","discoverCodexSessions","exportCodexSession","discoverOpenCodeSessions","exportOpenCodeSession","discoverMastracodeInprocessSessions","exportMastracodeInprocessSession","discoverPiSessions","exportPiSession"],"mappings":"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","file":"resume-strategies.mjs","sourcesContent":["/**\n * Turns a raw tool-call name + args (and its eventual result) into a short,\n * human-readable line for ring-buffer / CLI / transcript rendering. Without\n * this, every tool call renders as a bare `[tool] view` with no args and no\n * outcome — this module is the one place that knows how to summarize both.\n */\n\nconst SALIENT_ARG_KEYS = [\n \"file_path\",\n \"path\",\n \"filePath\",\n \"file\",\n \"command\",\n \"pattern\",\n \"query\",\n \"q\",\n \"url\",\n \"todos\",\n \"description\",\n \"prompt\",\n] as const\n\nconst MAX_CALL_LENGTH = 120\nconst MAX_RESULT_LENGTH = 160\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction truncate(value: string, max: number): string {\n const oneLine = value.replace(/\\s+/g, \" \").trim()\n return oneLine.length > max ? `${oneLine.slice(0, max - 1)}…` : oneLine\n}\n\nfunction formatArgValue(value: unknown): string {\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) return `${value.length} item${value.length === 1 ? \"\" : \"s\"}`\n if (isRecord(value)) return JSON.stringify(value)\n return String(value)\n}\n\nfunction pickSalientArg(args: Record<string, unknown>): string | null {\n for (const key of SALIENT_ARG_KEYS) {\n const value = args[key]\n if (value !== undefined && value !== null && value !== \"\") {\n return formatArgValue(value)\n }\n }\n return null\n}\n\ntype BespokeFormatter = (args: Record<string, unknown>) => string\n\nfunction subagentSummary(args: Record<string, unknown>): string {\n const description =\n typeof args.description === \"string\"\n ? args.description\n : typeof args.prompt === \"string\"\n ? args.prompt\n : \"subagent\"\n return `↳ subagent: ${truncate(description, MAX_CALL_LENGTH)}`\n}\n\n// Bespoke one-liners for control tools where the generic arg-sniffing\n// below would either miss the point (ScheduleWakeup's payload isn't a\n// file/command/pattern) or just repeat the tool name pointlessly\n// (TodoWrite, ExitPlanMode carry no salient single-value arg).\nconst BESPOKE_TOOLS: Record<string, BespokeFormatter> = {\n schedulewakeup: (args) => {\n const delay = args.delaySeconds ?? args.delay ?? \"?\"\n const reason = typeof args.reason === \"string\" ? args.reason : \"\"\n return reason ? `⏰ wake in ${delay}s — ${reason}` : `⏰ wake in ${delay}s`\n },\n task: subagentSummary,\n agent: subagentSummary,\n todowrite: (args) => {\n const todos = Array.isArray(args.todos) ? args.todos : []\n return `☑ todos (${todos.length})`\n },\n exitplanmode: () => \"📋 plan ready\",\n}\n\n/** A one-line human summary of a tool call, e.g. `read src/foo.ts` or `⏰ wake in 30s — checking CI`. */\nexport function formatToolCall(toolName: string, args: unknown): string {\n const name = toolName || \"tool\"\n const argsRecord = isRecord(args) ? args : {}\n\n const bespoke = BESPOKE_TOOLS[name.toLowerCase()]\n if (bespoke) return bespoke(argsRecord)\n\n const salient = pickSalientArg(argsRecord)\n if (salient !== null) {\n // Some ACP agents (e.g. claude-agent-acp) already bake the salient\n // value into a curated title — \"Read src/foo.ts\" alongside\n // arguments.path === \"src/foo.ts\". Appending it again would render\n // \"Read src/foo.ts src/foo.ts\". Only append when it adds new info.\n if (name.toLowerCase().includes(salient.toLowerCase())) {\n return truncate(name, MAX_CALL_LENGTH)\n }\n return truncate(`${name} ${salient}`, MAX_CALL_LENGTH)\n }\n\n if (Object.keys(argsRecord).length === 0) return name\n\n return truncate(`${name} ${JSON.stringify(args)}`, MAX_CALL_LENGTH)\n}\n\nfunction extractText(value: unknown): string | null {\n if (value == null) return null\n if (typeof value === \"string\") return value\n if (Array.isArray(value)) {\n const parts = value.map(extractText).filter((v): v is string => v != null)\n return parts.length ? parts.join(\"\\n\") : null\n }\n if (isRecord(value)) {\n if (typeof value.text === \"string\") return value.text\n if (typeof value.message === \"string\") return value.message\n // ACP agents such as opencode wrap a tool's stdout as `{ output, metadata }`\n // (claude-code sends a bare string). Without this key the ring showed no\n // [tool-result] line at all for opencode, and the CI artifact-ledger\n // passthrough (`artifactMarkerLines`) never saw the delivery helper's\n // `::agentproto-artifact::` marker — `driver: artifacts=[]` on every run.\n if (typeof value.output === \"string\") return value.output\n if (Array.isArray(value.content)) return extractText(value.content)\n if (typeof value.error === \"string\") return value.error\n if (isRecord(value.error) && typeof value.error.message === \"string\") {\n return value.error.message\n }\n return null\n }\n return null\n}\n\n/**\n * A short outcome line for a completed tool call, or `null` when there's\n * nothing useful to show (e.g. an empty/void result). `toolName` is\n * accepted for parity with `formatToolCall` and future bespoke result\n * formatting, but generic text extraction covers today's cases.\n */\nexport function formatToolResult(\n toolName: string | undefined,\n result: unknown,\n isError: boolean,\n): string | null {\n void toolName\n const text = extractText(result)\n\n if (isError) {\n const message = text ?? (result != null ? JSON.stringify(result) : \"failed\")\n const firstLine = message.split(/\\r?\\n/)[0] ?? message\n return truncate(firstLine, MAX_RESULT_LENGTH)\n }\n\n if (text == null) return null\n const trimmed = text.trim()\n if (!trimmed) return null\n\n const lines = trimmed.split(/\\r?\\n/)\n if (lines.length > 1) {\n const bytes = Buffer.byteLength(trimmed, \"utf8\")\n return `${lines.length} lines, ${bytes}B`\n }\n return truncate(lines[0]!, MAX_RESULT_LENGTH)\n}\n\n/** The stdout sentinel the CI artifact-ledger delivery helper prints when it\n * creates a PR / review / comment — MUST byte-match `ARTIFACT_MARKER` in\n * `scripts/lib/artifact-ledger.mjs` (the runner-side harvester). */\nexport const ARTIFACT_MARKER = \"::agentproto-artifact::\"\n\n/** Ring lines per tool result the marker passthrough will re-emit at most —\n * a delivery tool call creates one artifact, so >1 is already unusual; the\n * cap only bounds a pathological result that echoes markers in a loop. */\nconst MAX_ARTIFACT_MARKER_LINES = 8\n\n/**\n * The `::agentproto-artifact::` marker lines buried in a tool result, if any.\n *\n * The one-line summary {@link formatToolResult} produces is deliberately\n * lossy (\"N lines, XB\") — correct for humans, fatal for the CI artifact\n * ledger: the agentflow delivery helper (`deliver-artifact.mjs`) prints its\n * marker to a tool's stdout precisely so the `agentproto-run` driver can\n * harvest created PR/review/comment ids back out of `agent_output`. This\n * extracts those lines so the ring can carry them verbatim alongside the\n * summary. Returned WITHOUT ANSI styling on purpose: the harvester\n * (`parseArtifactMarkers`) does `indexOf(marker)` then `JSON.parse` of the\n * line's remainder, so a trailing reset code would corrupt the JSON.\n */\nexport function artifactMarkerLines(result: unknown): string[] {\n const text = extractText(result)\n if (text == null || !text.includes(ARTIFACT_MARKER)) return []\n return text\n .split(/\\r?\\n/)\n .filter(line => line.includes(ARTIFACT_MARKER))\n .slice(0, MAX_ARTIFACT_MARKER_LINES)\n .map(line => line.trim())\n}\n","/**\n * ONE normalized shape for a finished tool/command call — the unification\n * target for the two disjoint logs this repo has today: `recordCommand`'s\n * `CommandLogEntry` (command-log.ts, the `command_execute` proxy path) and\n * `transcript-writer.ts`'s per-kind `tool-call`/`tool-result` records (an\n * agent's own in-process Bash/Edit/…). Both writers append a\n * `ToolCallRecord` line (tagged `kind: \"tool-call-record\"` on disk — see\n * tool-call-log.ts) to the SAME per-session `events.jsonl` they already\n * own, so `tool_calls_list` can read either without a new store.\n *\n * Deliberately excludes session-level provenance (`harness`, `origin`,\n * `callerSessionId`): that already lives once on the owning\n * `SessionDescriptor` (see sessions.ts), and duplicating it onto every\n * call-level line would just be a snapshot that can drift. `tool_calls_list`\n * joins it in at read time from the descriptor instead.\n */\n\nexport interface ToolCallRecord {\n /** The session (command or agent-cli) whose events.jsonl this call was\n * written into. */\n sessionId: string\n /** Tool/command name — an MCP tool name for the proxy path\n * (\"command_execute\"), or an ACP tool name for the in-agent path\n * (\"Bash\", \"Read\", \"Edit\", …). */\n tool: string\n /** Shell command string, when this call is shell-shaped. Present for\n * every proxy call; only present in-agent when `extractCommandArgs`\n * recognizes the tool's `arguments` as `{command, args?}` (a Bash-style\n * tool) — every other in-agent tool (Read, Edit, …) legitimately has no\n * shell command, so this stays absent rather than guessing. */\n command?: string\n args?: string[]\n /** Process exit code — only meaningful for the proxy path, which runs a\n * real subprocess. Absent for in-agent calls (ACP has no exit-code\n * concept, only `isError`). */\n exitCode?: number\n isError?: boolean\n durationMs?: number\n /** The PR this call CREATED, when the call was a successful shell-shaped\n * `gh pr create` (detected at write time via `detectShellPrCreate` in\n * pr-provenance.ts, from the command string + the call's result text).\n * This is the exact-attribution source the PR-provenance reconciler\n * prefers over branch→PR guessing: the session whose events.jsonl holds\n * this record IS the session that opened the PR, no matter what branch\n * its cwd sits on by the time the turn ends. Absent for every other\n * call. */\n createdPrUrl?: string\n createdPrNumber?: number\n ts: string\n}\n\n/**\n * Best-effort extraction of a shell-shaped `{command, args}` out of an\n * arbitrary, harness-shaped tool-call `arguments` blob (typed `unknown` at\n * the ACP boundary — see `AgentStreamEvent.arguments` in sessions.ts).\n * Only recognizes the `{command: string, args?: string[]}` shape a\n * Bash-style tool sends; every other tool shape returns `{}` rather than\n * guessing, per AGENTS.md's \"no silent caps\" ethic applied to normalization\n * (don't claim a `command` this didn't actually see).\n */\nexport function extractCommandArgs(input: unknown): { command?: string; args?: string[] } {\n if (!input || typeof input !== \"object\") return {}\n const obj = input as Record<string, unknown>\n const command = typeof obj.command === \"string\" ? obj.command : undefined\n if (command === undefined) return {}\n const argv =\n Array.isArray(obj.args) && obj.args.every(a => typeof a === \"string\")\n ? (obj.args as string[])\n : undefined\n return argv ? { command, args: argv } : { command }\n}\n","/**\n * PR provenance footer — daemon/MCP lane.\n *\n * This is the SAME footer the runner-side agentflow scripts stamp on the\n * CI-PR, review, and local-`gh_open_pr` lanes; `scripts/lib/provenance-\n * footer.mjs` re-exports {@link buildFooter}/{@link MARKER}/{@link fmtTokens}\n * from HERE so there is exactly one format, one marker, one renderer. The\n * only reason this canonical copy lives in `@agentproto/runtime` rather than\n * in `scripts/` is direction of dependency: the daemon is a package and\n * cannot import a loose script, but a script can import a package's built\n * `dist/` (it already does for `computeProvenance`).\n *\n * What the DAEMON lane adds over the runner lanes: the footer is built from\n * a live {@link SessionDescriptor} (the executor agent-cli session that ran\n * `gh pr create`) plus its supervisor/parent session, rather than from a\n * `computeProvenance` join over the worktree. So this file also owns the\n * mapping from a session descriptor to the footer's provenance shape\n * ({@link sessionFooterProvenance}) and the small pure helpers the\n * command_execute stamp path needs ({@link parseGhPrCreate},\n * {@link pickExecutorSession}, {@link appendFooterOnce}).\n *\n * Pure by construction — only `node:path`, no daemon imports, no I/O — so it\n * bundles to a standalone `dist/pr-provenance.mjs` the scripts can import\n * without pulling the rest of the runtime in. Keep it that way. The\n * side-effecting orchestration (spawning `gh`, editing the PR body, recording\n * the opened PR) lives in `pr-provenance-stamp.ts`.\n */\n\nimport { basename } from \"node:path\"\n\n/** Deterministic marker the runner owns — a reliable native-vs-legacy\n * discriminator across every lane (CI-PR, review, local, daemon). */\nexport const MARKER = \"@agentproto-bot\"\n\nexport const fmtTokens = (n: unknown): string | null => {\n if (typeof n !== \"number\" || !Number.isFinite(n)) return null\n return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)\n}\n\n/** The footer's provenance shape — field names are the renderer's, not the\n * session registry's (see {@link sessionFooterProvenance} for the mapping). */\nexport interface FooterProvenance {\n sessionId?: string\n label?: string\n /** Adapter / harness slug that ran (\"claude-code\", \"codex\", …). */\n adapter?: string\n model?: string\n /** Bound auth-profile IDENTITY — a label or ref, NEVER the credential. */\n authProfile?: string\n sandboxId?: string\n /** Supervisor / parent session that spawned the executor. */\n parentSessionId?: string\n tokensIn?: number\n tokensOut?: number\n costUsd?: number\n /** \"adapter\" | \"local\" | \"daemon\" — drives host/cwd vs run-link rendering. */\n source?: string\n host?: string\n cwd?: string\n /** The session's registered workspace slug (`SessionDescriptor.workspaceSlug`,\n * e.g. \"ts\", \"default\"). Rendered alongside `cwd`'s leaf directory name so\n * the footer identifies WHICH workspace ran the command, not just an\n * arbitrary trailing path segment — a bare `basename(cwd)` reads as\n * meaningless (or actively misleading) when the session's cwd is a\n * workspace root rather than a per-branch worktree. */\n workspaceSlug?: string\n}\n\n/**\n * Render the footer's `cwd` value. A bare `basename(cwd)` collapses to a\n * meaningless (or actively misleading) fragment whenever the session's cwd IS\n * the workspace root rather than a per-branch worktree dir — e.g. a checkout\n * literally named `ts` renders as the opaque `cwd \\`ts\\`` with no indication\n * that's a workspace name, not a worktree/branch leaf. Prefixing the\n * registered workspace slug disambiguates it, and is dropped when it's\n * identical to the leaf (the common per-worktree case) so the common case\n * stays exactly as compact as before.\n */\nfunction cwdLabel(cwd: string, workspaceSlug?: string): string {\n const leaf = basename(cwd)\n if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf\n return `${workspaceSlug}/${leaf}`\n}\n\nexport interface BuildFooterInput {\n prov?: FooterProvenance\n authMode?: string\n runId?: string\n runUrl?: string\n sha?: string\n kind?: string\n}\n\nexport const buildFooter = ({\n prov,\n authMode,\n runId,\n runUrl,\n sha,\n kind = \"review\",\n}: BuildFooterInput): string => {\n const parts = [`🤖 **${MARKER}** — ${kind}`]\n if (prov?.sessionId) {\n parts.push(`session \\`${prov.sessionId}\\`${prov.label ? ` (\\`${prov.label}\\`)` : \"\"}`)\n }\n // Engine label: \"adapter / authMode\" when an adapter ran; \"legacy fallback\n // (authMode)\" when no agent session exists at all (the API-key fallback path);\n // bare authMode only if a session ran without a resolved adapter slug.\n if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(\" / \"))\n else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : \"\"}`)\n else if (authMode) parts.push(authMode)\n // Bound auth profile identity (the \"wallet\") — daemon lane; NEVER the secret.\n if (prov?.authProfile) parts.push(`auth-profile \\`${prov.authProfile}\\``)\n if (prov?.model) parts.push(`model \\`${prov.model}\\``)\n if (prov?.sandboxId) parts.push(`e2b \\`${prov.sandboxId}\\``)\n if (prov?.parentSessionId) parts.push(`supervisor \\`${prov.parentSessionId}\\``)\n const tin = fmtTokens(prov?.tokensIn)\n const tout = fmtTokens(prov?.tokensOut)\n if (tin || tout) parts.push(`${tin ?? \"?\"} in / ${tout ?? \"?\"} out`)\n if (typeof prov?.costUsd === \"number\") {\n parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== \"adapter\" ? ` (${prov.source})` : \"\"}`)\n }\n const showLocalHostCwd = prov?.source === \"local\" || prov?.source === \"daemon\" || !runId\n if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`)\n if (showLocalHostCwd) {\n if (prov?.host) parts.push(`host \\`${prov.host}\\``)\n if (prov?.cwd) parts.push(`cwd \\`${cwdLabel(prov.cwd, prov.workspaceSlug)}\\``)\n }\n if (sha) parts.push(`sha \\`${sha}\\``)\n return `\\n\\n---\\n<sub>${parts.join(\" · \")}</sub>`\n}\n\n/** Structural subset of `SessionDescriptor` the footer needs. The runtime's\n * full descriptor satisfies this; keeping it structural is what lets this\n * module stay free of a `./sessions.js` import (and thus standalone). */\nexport interface FooterSession {\n id: string\n kind?: string\n status?: string\n startedAt?: string\n label?: string\n cwd?: string\n workspaceSlug?: string\n adapterSlug?: string\n harness?: string\n model?: string\n parentSessionId?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n auth?: { mode?: string }\n accessProfile?: { profileRef: string; label?: string }\n}\n\nexport interface SessionFooterOptions {\n /** The supervisor/parent session descriptor, when resolvable. */\n supervisor?: { id: string } | null\n host?: string\n /** Provenance source tag; defaults to \"daemon\". */\n source?: string\n}\n\n/**\n * Map a live executor session (+ its supervisor) onto the footer's provenance\n * shape and the `authMode` the renderer takes separately. The adapter is the\n * canonical harness slug, falling back to `adapterSlug`. The bound auth\n * profile is the profile's human label (or its ref), never the credential.\n */\nexport function sessionFooterProvenance(\n session: FooterSession,\n options: SessionFooterOptions = {},\n): { prov: FooterProvenance; authMode: string | undefined } {\n const prov: FooterProvenance = {\n sessionId: session.id,\n label: session.label,\n adapter: session.harness ?? session.adapterSlug,\n model: session.model,\n authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,\n parentSessionId: options.supervisor?.id,\n costUsd: session.costUsd,\n tokensIn: session.tokensIn,\n tokensOut: session.tokensOut,\n source: options.source ?? \"daemon\",\n host: options.host,\n cwd: session.cwd,\n workspaceSlug: session.workspaceSlug,\n }\n return { prov, authMode: session.auth?.mode }\n}\n\n/** Build the daemon-lane PR footer for one executor session directly. */\nexport function buildSessionPrFooter(\n session: FooterSession,\n options: SessionFooterOptions & { sha?: string } = {},\n): string {\n const { prov, authMode } = sessionFooterProvenance(session, options)\n return buildFooter({ prov, authMode, sha: options.sha, kind: \"PR\" })\n}\n\n/**\n * Append the footer to a PR body exactly once. Idempotent by the marker: a\n * body that already carries a `@agentproto-bot` footer is returned unchanged,\n * so a retried stamp (network reply lost after the edit landed) never stacks\n * a second footer.\n */\nexport function appendFooterOnce(body: string, footer: string): string {\n if (hasProvenanceFooter(body)) return body\n return `${body}${footer}`\n}\n\n/**\n * Whether a PR/review body already carries a RENDERED provenance footer — the\n * `<sub>…@agentproto-bot…</sub>` line {@link buildFooter} emits — as opposed\n * to merely MENTIONING the marker in prose. The distinction matters: a PR\n * whose description discusses the provenance machinery itself (they exist —\n * #999 explains a footer bug and quotes the marker) would otherwise read as\n * \"already stamped\" forever and never receive its real footer.\n */\nexport function hasProvenanceFooter(body: string): boolean {\n return new RegExp(`<sub>[^\\\\n]*${MARKER}`).test(body)\n}\n\n/** The rendered footer block, as {@link buildFooter} emits it (with the\n * `\\n\\n---\\n` lead-in when present). */\nconst FOOTER_BLOCK_RE = new RegExp(`(?:\\\\n+---)?\\\\n*<sub>[^\\\\n]*${MARKER}[^\\\\n]*</sub>`)\n\n/** Just the `<sub>…</sub>` span of a rendered footer within `body` (or a\n * standalone footer string, which is itself `<sub>…</sub>`-shaped) — every\n * \"read a signal out of an existing footer\" helper below shares this\n * extraction so they agree on what \"the footer\" means. Undefined when `body`\n * carries no rendered footer at all. */\nfunction extractFooterBlock(body: string): string | undefined {\n return new RegExp(`<sub>[^\\\\n]*${MARKER}[^\\\\n]*</sub>`).exec(body)?.[0]\n}\n\n/** True when a body's provenance footer already carries a spend figure. */\nexport function footerHasCost(body: string): boolean {\n const block = extractFooterBlock(body)\n return block !== undefined && /\\$\\d/.test(block)\n}\n\n/**\n * The `session \\`<id>\\`` a rendered footer names, or undefined when the body\n * carries no footer or the footer names no session (the legacy-fallback\n * shape, e.g. a bare CI API-key run with no agent session behind it).\n *\n * Used to recognize \"this footer already names ME\" — both the daemon's own\n * `buildSessionPrFooter` render and the `gh` PATH shim's thin one\n * (gh-provenance-shim.ts) render the SAME `session \\`<id>\\`` segment, so a\n * footer this returns OUR session id for is safe to enrich/upgrade: the\n * misattribution risk `hasProvenanceFooter`'s callers guard against is a\n * footer naming a DIFFERENT session, not our own.\n */\nexport function footerSessionId(body: string): string | undefined {\n const block = extractFooterBlock(body)\n if (block === undefined) return undefined\n return /session `([^`]+)`/.exec(block)?.[1]\n}\n\n/**\n * Whether `candidate` (a standalone footer, as {@link buildFooter} renders\n * it, or a full body containing one) carries a provenance signal — spend,\n * bound auth-profile identity, or token counts — that `existing` doesn't.\n * Used to decide whether replacing an already-posted footer with a fresh\n * daemon render is a genuine UPGRADE rather than a no-op re-render: the `gh`\n * PATH shim's thin footer (gh-provenance-shim.ts) can only ever render\n * session/adapter/model/host/cwd — never cost, auth-profile, or tokens, since\n * those aren't known to a bare `gh` subprocess — so a shim-stamped body\n * always has ROOM for this kind of upgrade once the daemon knows more.\n */\nexport function footerIsRicherThan(candidate: string, existing: string): boolean {\n const candidateBlock = extractFooterBlock(candidate) ?? \"\"\n const existingBlock = extractFooterBlock(existing) ?? \"\"\n const gained = (has: (block: string) => boolean) => has(candidateBlock) && !has(existingBlock)\n const hasCost = (block: string) => /\\$\\d/.test(block)\n const hasAuthProfile = (block: string) => /auth-profile `/.test(block)\n const hasTokens = (block: string) => / in \\/ .{1,20} out\\b/.test(block)\n return gained(hasCost) || gained(hasAuthProfile) || gained(hasTokens)\n}\n\n/**\n * Replace an existing footer with a fresh one (append when there is none).\n * The daemon stamps a PR the instant `gh pr create` returns — mid-turn, when\n * a claude-code/claude-sdk session has reported no usage yet (cost only\n * arrives on the turn's `result`), so the first footer has no amount. At\n * turn-end the reconciler re-renders it with what the session now knows.\n */\nexport function replaceProvenanceFooter(body: string, footer: string): string {\n if (!hasProvenanceFooter(body)) return appendFooterOnce(body, footer)\n return body.replace(FOOTER_BLOCK_RE, footer)\n}\n\n/**\n * Recognise a successful `gh pr create` from its argv + stdout and pull the\n * created PR's URL + number out. Returns null for anything that isn't a\n * PR-creating `gh` invocation, or a create whose stdout carried no PR URL.\n *\n * `gh pr create` prints the PR URL on its own line (often the last line,\n * after any advisory notices) — we take the LAST `…/pull/<n>` match so a\n * \"Warning: …/pull/…\" style notice can't shadow the real one.\n */\nexport function parseGhPrCreate(\n command: string,\n args: readonly string[],\n stdout: string,\n): { url: string; number: number } | null {\n if (basename(command) !== \"gh\") return null\n // Skip global flags; the first two non-flag tokens must be `pr` then `create`.\n const positionals = args.filter(a => !a.startsWith(\"-\"))\n if (positionals[0] !== \"pr\" || positionals[1] !== \"create\") return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(stdout)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/**\n * String-form counterpart of {@link parseGhPrCreate} for the in-agent tool\n * lane: an ACP harness's Bash-style tool reports ONE shell string (possibly\n * compound — `git push && gh pr create … | tail -1`), not an argv, so the\n * argv parser above can't see it. Detects \"this call created a PR\" from the\n * command string plus the call's recorded result text, and returns the\n * created PR (LAST `…/pull/<n>` match in the result, same shadowing rule as\n * {@link parseGhPrCreate} — `gh pr create` prints its URL after any notices).\n *\n * Quoted spans are stripped before matching so a command that merely\n * MENTIONS the phrase — `grep \"gh pr create\" src/` over a result that quotes\n * a PR url — can never read as a create. Best-effort by design: the caller\n * additionally gates on the call's own `isError` (a failed create whose\n * stderr cites an existing PR must not attribute that PR here).\n */\nexport function detectShellPrCreate(\n command: string | undefined,\n resultText: string | undefined,\n): { url: string; number: number } | null {\n if (!command || !resultText) return null\n const unquoted = command.replace(/'[^']*'/g, \" \").replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, \" \")\n if (!/(^|[\\s;&|({])gh\\s+pr\\s+create(\\s|$)/.test(unquoted)) return null\n const re = /https?:\\/\\/\\S+?\\/pull\\/(\\d+)/g\n let match: RegExpExecArray | null\n let last: { url: string; number: number } | null = null\n while ((match = re.exec(resultText)) !== null) {\n last = { url: match[0], number: Number(match[1]) }\n }\n return last\n}\n\n/** `session.cwd == cwd || one contains the other` — the executor's cwd is\n * usually the worktree root while the command may run in a subdir (or vice\n * versa), so containment is checked in both directions. */\nfunction cwdRelated(sessionCwd: string, cwd: string): boolean {\n if (sessionCwd === cwd) return true\n const sep = \"/\"\n return cwd.startsWith(sessionCwd + sep) || sessionCwd.startsWith(cwd + sep)\n}\n\n/**\n * Best-effort attribution of a `command_execute` run to the executor agent\n * session that most likely issued it: the newest agent-cli session whose cwd\n * is related to the command's cwd, preferring a still-alive one. Returns\n * undefined when no agent-cli session matches (e.g. a bare shell opened the\n * PR) — the caller then skips stamping rather than emit a misleading \"legacy\n * fallback\" footer.\n */\nexport function pickExecutorSession<T extends FooterSession>(\n sessions: readonly T[],\n cwd: string,\n): T | undefined {\n const candidates = sessions.filter(\n s => s.kind === \"agent-cli\" && typeof s.cwd === \"string\" && cwdRelated(s.cwd, cwd),\n )\n if (candidates.length === 0) return undefined\n const alive = (s: T) => s.status === \"running\" || s.status === \"starting\"\n const byRecency = (a: T, b: T) => (b.startedAt ?? \"\").localeCompare(a.startedAt ?? \"\")\n const live = candidates.filter(alive).sort(byRecency)\n if (live.length > 0) return live[0]\n return [...candidates].sort(byRecency)[0]\n}\n","/**\n * Per-session usage snapshot — the single place that decides a session's\n * `costUsd` and where that number came from.\n *\n * Two cost sources feed a session:\n * - an adapter's own usage reader (`readUsage`, e.g. hermes reads its\n * state.db) or an ACP `usage_update` that carries a `cost` block — these\n * are authoritative, tagged `source: \"adapter\"`.\n * - raw input/output token counts with NO adapter cost (ACP adapters like\n * claude-code / mastracode that report tokens but not dollars) — here we\n * price them ourselves against agentproto's in-repo LLM pricing catalog,\n * tagged `source: \"computed\"`.\n *\n * When tokens exist but the model is absent from the catalog we NEVER\n * fabricate a price: `costUsd` is left undefined and the snapshot is tagged\n * `source: \"no-pricing\"`. A session with neither cost nor tokens is\n * `source: \"none\"`.\n *\n * This module is pure (the catalog lookup is injected) so the decision tree is\n * unit-testable without building the whole model catalog.\n */\n\nimport { resolvePricing } from \"@agentproto/model-catalog/llm\"\n\n/** Where a session's `costUsd` came from — see the module doc. */\nexport type UsageSource = \"adapter\" | \"computed\" | \"no-pricing\" | \"none\"\n\n/** Per-token USD prices for a model (per 1M tokens), the subset of the\n * catalog's `LLMPricing` this module needs. */\nexport interface TokenPricing {\n inputPer1M: number\n outputPer1M: number\n /** Multiplier on `inputPer1M` for cache-read tokens (Anthropic: ~0.1).\n * Absent = 1 (no discount), same default as the catalog. */\n cacheReadMultiplier?: number\n /** Multiplier on `inputPer1M` for cache-creation tokens (Anthropic:\n * ~1.25). Absent = 1. */\n cacheWriteMultiplier?: number\n}\n\n/**\n * Token-detail and activity fields shared by every usage shape below. All\n * optional and never zero-filled: absent means \"this source doesn't report\n * it\", a present 0 is a measured zero.\n *\n * - `cacheReadTokens` / `cacheWriteTokens`: prompt-cache reads / cache\n * creation, cumulative. Kept OUT of `tokensIn` (which is uncached input\n * only when a cache split is known).\n * - `reasoningTokens`: thinking/reasoning tokens, cumulative. A subset of\n * `tokensOut` for providers that bill them as output (Anthropic).\n * - `turns`: completed turns (the descriptor's `turnsCompleted`).\n * - `toolCalls`: tool calls the daemon observed, cumulative.\n * - `durationMs`: wall time spent inside turns, cumulative.\n */\nexport interface UsageDetail {\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n turns?: number\n toolCalls?: number\n durationMs?: number\n}\n\n/** The `UsageDetail` keys, in response order. */\nexport const USAGE_DETAIL_KEYS = [\n \"cacheReadTokens\",\n \"cacheWriteTokens\",\n \"reasoningTokens\",\n \"turns\",\n \"toolCalls\",\n \"durationMs\",\n] as const satisfies readonly (keyof UsageDetail)[]\n\n/** Copy only the present (`number`) `UsageDetail` fields of `src`. */\nexport function pickUsageDetail(src: UsageDetail): UsageDetail {\n const out: UsageDetail = {}\n for (const k of USAGE_DETAIL_KEYS) {\n const v = src[k]\n if (typeof v === \"number\" && Number.isFinite(v)) out[k] = v\n }\n return out\n}\n\n/** Pluggable pricing accessor — defaults to the in-repo catalog's\n * `resolvePricing`, overridable in tests. Returns undefined for an\n * unknown model (the caller then tags `no-pricing`). */\nexport type PricingResolver = (model: string) => TokenPricing | undefined\n\n/**\n * What an adapter's usage reader (`readUsage`: hermes state.db, opencode.db,\n * the claude-code transcript JSONL) returns. Every field is cumulative for\n * the session and optional; `null` from the reader means \"nothing found\".\n */\nexport interface UsageReadResult {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n cacheReadTokens?: number\n cacheWriteTokens?: number\n reasoningTokens?: number\n}\n\n/** Where a reader should look for the session's native store — the spawn's\n * cwd and isolated provider config dir (`CLAUDE_CONFIG_DIR` for\n * claude-code). Readers that key only on the session id ignore it. */\nexport interface UsageReadContext {\n cwd?: string\n configDir?: string\n}\n\n/** Raw signals gathered over a turn, handed to `deriveSessionUsage`. */\nexport interface UsageComputeInput extends UsageDetail {\n /** Requested model id — needed to look up per-token prices. */\n model?: string\n /** Cost the adapter reported directly (readUsage or usage_update.cost).\n * Present → authoritative, wins over token-based computation. */\n adapterCostUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n}\n\n/** The resolved usage snapshot — the shape `session_usage` returns and the\n * durable `usage_snapshot` transcript record carries. Cost/token fields are\n * omitted when absent so a missing value never reads as a measured zero. */\nexport interface SessionUsage extends UsageDetail {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n source: UsageSource\n}\n\n/**\n * A `contextUsed` greater than the reported `contextSize` cannot be tokens\n * currently occupying the context window — a window can't hold more tokens\n * than it is wide. At least one adapter's ACP server has been observed\n * putting a cumulative session-lifetime token total in this field instead\n * of a point-in-time occupancy figure (a long-running session's running\n * total can land tens of times past a fixed window, e.g. 14.2M reported\n * against a 200K window). A value that fails this check is provably not\n * what the field claims to be, so it's dropped rather than clamped: an\n * absent `contextUsed` reads as \"unmeasured,\" a clamped one would read as\n * a plausible but fabricated occupancy percentage. `contextSize` itself is\n * untouched — it's a static model property, not the corrupted signal.\n *\n * When `contextSize` isn't known yet, `contextUsed` can't be disproven —\n * it's passed through unchanged rather than discarded.\n */\nexport function plausibleContextUsed(\n contextSize: number | undefined,\n contextUsed: number | undefined,\n): number | undefined {\n if (contextUsed === undefined) return undefined\n if (contextSize !== undefined && contextUsed > contextSize) return undefined\n return contextUsed\n}\n\nconst defaultResolver: PricingResolver = model => resolvePricing(model)\n\n/** Cost of `tokens` at `pricePer1M` USD/1M tokens. */\nfunction tokenCost(tokens: number | undefined, pricePer1M: number): number {\n return tokens === undefined ? 0 : (tokens * pricePer1M) / 1_000_000\n}\n\n/**\n * Decide a session's usage snapshot + cost source from the raw signals.\n *\n * Priority: an adapter-reported cost wins; else price the tokens against the\n * catalog; else `none`. Never fabricates a price for an unpriced model.\n */\nexport function deriveSessionUsage(\n input: UsageComputeInput,\n resolve: PricingResolver = defaultResolver,\n): SessionUsage {\n // Re-validated here, not just at usage_update ingestion: a rejected\n // ingress update leaves whatever was already on the descriptor in place\n // rather than clearing it, so a stale out-of-window value written before\n // that guard existed (or reloaded from a pre-guard persisted snapshot)\n // can still reach this call at a session's exit-time recap. Same\n // reasoning as `plausibleContextUsed`'s own doc comment — drop, don't\n // clamp.\n const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed)\n const base: Omit<SessionUsage, \"source\" | \"costUsd\"> = {\n ...(input.model !== undefined ? { model: input.model } : {}),\n ...(input.tokensIn !== undefined ? { tokensIn: input.tokensIn } : {}),\n ...(input.tokensOut !== undefined ? { tokensOut: input.tokensOut } : {}),\n ...pickUsageDetail(input),\n ...(input.contextSize !== undefined ? { contextSize: input.contextSize } : {}),\n ...(contextUsed !== undefined ? { contextUsed } : {}),\n }\n\n // 1. Adapter reported a cost directly — authoritative.\n if (input.adapterCostUsd !== undefined) {\n return { ...base, costUsd: input.adapterCostUsd, source: \"adapter\" }\n }\n\n // 2. We have token counts — price them against the in-repo catalog.\n const hasTokens =\n input.tokensIn !== undefined ||\n input.tokensOut !== undefined ||\n input.cacheReadTokens !== undefined ||\n input.cacheWriteTokens !== undefined\n if (hasTokens) {\n const pricing = input.model !== undefined ? resolve(input.model) : undefined\n if (!pricing) {\n // Model absent from the catalog — surface the tokens, but NEVER\n // invent a dollar figure.\n return { ...base, source: \"no-pricing\" }\n }\n // Cache tokens are priced off the input rate with the catalog's\n // multipliers (default 1, i.e. no discount) — they're disjoint from\n // `tokensIn`, so this adds, never double counts.\n const costUsd =\n tokenCost(input.tokensIn, pricing.inputPer1M) +\n tokenCost(input.tokensOut, pricing.outputPer1M) +\n tokenCost(input.cacheReadTokens, pricing.inputPer1M * (pricing.cacheReadMultiplier ?? 1)) +\n tokenCost(input.cacheWriteTokens, pricing.inputPer1M * (pricing.cacheWriteMultiplier ?? 1))\n return { ...base, costUsd, source: \"computed\" }\n }\n\n // 3. Neither cost nor tokens — nothing measured.\n return { ...base, source: \"none\" }\n}\n\n/** Descriptor-shaped fields `projectSessionUsage` reads. */\nexport interface UsageDescriptorFields\n extends Omit<UsageDetail, \"turns\"> {\n model?: string\n costUsd?: number\n tokensIn?: number\n tokensOut?: number\n contextSize?: number\n contextUsed?: number\n usageSource?: UsageSource\n /** Surfaced as `turns`. */\n turnsCompleted?: number\n}\n\n/**\n * Project a session descriptor's persisted usage fields into the\n * `session_usage` response shape. Omits absent fields; defaults an\n * unstamped `usageSource` to `\"none\"`.\n */\nexport function projectSessionUsage(desc: UsageDescriptorFields): SessionUsage {\n // Same re-validation as `deriveSessionUsage`, for the same reason: this\n // formats a descriptor that may have been reloaded from a persisted\n // snapshot written before `plausibleContextUsed` existed — a session\n // that's already dead never gets a fresh usage_update to self-correct\n // it, so a stale out-of-window value would otherwise surface forever.\n const contextUsed = plausibleContextUsed(desc.contextSize, desc.contextUsed)\n return {\n ...(desc.model !== undefined ? { model: desc.model } : {}),\n ...(desc.costUsd !== undefined ? { costUsd: desc.costUsd } : {}),\n ...(desc.tokensIn !== undefined ? { tokensIn: desc.tokensIn } : {}),\n ...(desc.tokensOut !== undefined ? { tokensOut: desc.tokensOut } : {}),\n ...pickUsageDetail({ ...desc, turns: desc.turnsCompleted }),\n ...(desc.contextSize !== undefined ? { contextSize: desc.contextSize } : {}),\n ...(contextUsed !== undefined ? { contextUsed } : {}),\n source: desc.usageSource ?? \"none\",\n }\n}\n","/**\n * Per-session structured event capture — one append-only JSONL file per\n * agent-cli session, written at the same tap point where `projectEvent`\n * (sessions.ts) flattens `StreamEvent`s into the ANSI ring buffer, but\n * BEFORE that flattening happens. Gives agentproto the Claude-Code\n * property: a daemon-owned structured transcript that survives a daemon\n * restart (the ring buffer doesn't) and that any frontend can render,\n * not just the two adapters `transcript-export.ts` knows how to re-read\n * from their own native stores.\n *\n * File layout: `~/.agentproto/sessions/<sessionId>/events.jsonl` (the\n * root is overridable via the daemon config's `sessions.eventsDir` — see\n * `setDefaultSessionsBaseDir`), one JSON object per line: `{seq, ts, kind, ...fields}`.\n *\n * PTY (`terminal`) and `command` sessions never call into this writer —\n * they have no structured event source (see report-transcript-\n * architecture.md finding C.17), so this module is only ever driven from\n * `agent-cli` sessions' `runAgentTurn`.\n */\n\nimport { createHash } from \"node:crypto\"\nimport {\n createWriteStream,\n existsSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n type WriteStream,\n} from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { isAbsolute, join } from \"node:path\"\nimport type { SessionMessageSentRecord, SessionObserver } from \"./session-observer.js\"\nimport type { SessionMessage } from \"./session-message.js\"\nimport type { AgentStreamEvent } from \"./sessions.js\"\nimport { extractCommandArgs } from \"./tool-call-record.js\"\nimport { detectShellPrCreate } from \"./pr-provenance.js\"\nimport { pickUsageDetail, type SessionUsage } from \"./usage.js\"\nimport type { McpAppToolCallRecord } from \"./mcp-apps-host.js\"\n\n/** Debounce window for flushing a buffered text-delta/thought fragment\n * that hasn't hit a newline yet. Keeps a long no-newline stream from\n * sitting in memory indefinitely without also spamming one record per\n * token — mirrors the \"coalesce, don't spam\" rule `projectEvent`\n * already applies to the ring buffer, just with a disk-durability\n * angle added. */\nconst DEBOUNCE_MS = 250\n\n/** The one default root every per-session transcript directory resolves\n * against. Overridable so tests (and the daemon's `sessions.eventsDir`\n * config key) can redirect writes away from the real home directory\n * instead of mocking `node:os`. Explicit `baseDir` arguments (tests,\n * the registry's `transcriptDir` option) always win over this. */\nlet defaultSessionsBaseDir: string | undefined\n\n/** Point the no-`baseDir` path resolution at `dir`. Called once at daemon\n * boot from the loaded config (`sessions.eventsDir`); `undefined` restores\n * the hardcoded `~/.agentproto/sessions` default. Relative paths are\n * resolved against the home directory. */\nexport function setDefaultSessionsBaseDir(dir: string | undefined): void {\n if (!dir) {\n defaultSessionsBaseDir = undefined\n return\n }\n defaultSessionsBaseDir = isAbsolute(dir) ? dir : join(homedir(), dir)\n}\n\n/** The effective default base dir — the configured one, or the hardcoded\n * `~/.agentproto/sessions`. */\nexport function defaultTranscriptBaseDir(): string {\n return defaultSessionsBaseDir ?? join(homedir(), \".agentproto\", \"sessions\")\n}\n\nexport function sessionTranscriptDir(sessionId: string, baseDir?: string): string {\n return join(baseDir ?? defaultTranscriptBaseDir(), sessionId)\n}\n\nexport function sessionEventsPath(sessionId: string, baseDir?: string): string {\n return join(sessionTranscriptDir(sessionId, baseDir), \"events.jsonl\")\n}\n\nexport function sessionAttachmentsDir(sessionId: string, baseDir?: string): string {\n return join(sessionTranscriptDir(sessionId, baseDir), \"attachments\")\n}\n\n/**\n * One content-addressed attachment materialized from an inline-bytes\n * content block (a pasted image, a document sent as base64 rather than a\n * `POST /files/upload` path reference). Deliberately mirrors AIP-58's\n * `ArtifactEntry` (`packages/workflow-runtime/src/types.ts`) field-for-field\n * — `path` here is relative to the SESSION dir the same way `ArtifactEntry.\n * path` is relative to the run dir — so a later \"promote this attachment to\n * an artifact\" step is a metadata copy, never a data migration.\n */\nexport interface AttachmentEntry {\n /** Caller-supplied name when the block carried one, else the content-\n * addressed filename — the same value as the last path segment of `path`. */\n key: string\n /** `\"attachments/<sha256>.<ext>\"`, relative to `sessionTranscriptDir`. */\n path: string\n sha256: string\n size: number\n contentType?: string\n /** Display name for the UI (file chip / image alt text) — the block's own\n * name/filename/uri when present, else the same value as `key`. */\n name: string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null\n}\n\n/** Extensions this daemon actually sees on attachments — covers the ACP\n * image wire shapes and common document types a print-arm/path-reference\n * fallback wouldn't otherwise route through here. Unknown mime types fall\n * back to `bin` rather than guessing. */\nconst EXTENSION_BY_MIME_TYPE: Record<string, string> = {\n \"image/png\": \"png\",\n \"image/jpeg\": \"jpg\",\n \"image/webp\": \"webp\",\n \"image/gif\": \"gif\",\n \"image/svg+xml\": \"svg\",\n \"application/pdf\": \"pdf\",\n \"text/plain\": \"txt\",\n \"text/csv\": \"csv\",\n \"text/markdown\": \"md\",\n \"application/json\": \"json\",\n}\n\n/** Reverse of `EXTENSION_BY_MIME_TYPE`, for serving a materialized attachment\n * back with a correct `Content-Type` (`GET /sessions/:id/attachments/:file`\n * in http-server.ts) — the extension is all that's on disk, the mime type\n * it was written with isn't persisted separately. */\nexport function mimeTypeForExtension(ext: string): string {\n for (const [mimeType, candidateExt] of Object.entries(EXTENSION_BY_MIME_TYPE)) {\n if (candidateExt === ext) return mimeType\n }\n return \"application/octet-stream\"\n}\n\nfunction extensionFor(mimeType: string | undefined, name: string | undefined): string {\n const dot = name?.lastIndexOf(\".\") ?? -1\n if (name && dot > 0 && dot < name.length - 1) {\n return name.slice(dot + 1).toLowerCase().slice(0, 10)\n }\n if (mimeType && EXTENSION_BY_MIME_TYPE[mimeType]) return EXTENSION_BY_MIME_TYPE[mimeType]\n return \"bin\"\n}\n\n/** Inline-bytes payload recovered from a content block, or `undefined` when\n * the block carries no bytes of its own (a text block, or a path/uri\n * reference with nothing to materialize). Recognizes the two inline-bytes\n * shapes this codebase actually produces:\n * - `{type:\"image\", data, mimeType, name?}` — the ACP wire shape (see\n * CLAUDE-CODE.md, opencode's adapter) hosts send a pasted image as.\n * - `{type:\"resource\", resource:{blob, mimeType, uri?}}` — MCP's embedded-\n * resource shape, for a document sent as bytes rather than a\n * `POST /files/upload` path reference. */\nfunction extractInlineBytes(\n block: Record<string, unknown>,\n): { data: string; mimeType?: string; name?: string } | undefined {\n if (typeof block.data === \"string\" && block.data.length > 0) {\n const name =\n typeof block.name === \"string\"\n ? block.name\n : typeof block.filename === \"string\"\n ? block.filename\n : undefined\n return {\n data: block.data,\n mimeType: typeof block.mimeType === \"string\" ? block.mimeType : undefined,\n name,\n }\n }\n const resource = isRecord(block.resource) ? block.resource : undefined\n if (resource && typeof resource.blob === \"string\" && resource.blob.length > 0) {\n const uri = typeof resource.uri === \"string\" ? resource.uri : undefined\n return {\n data: resource.blob,\n mimeType: typeof resource.mimeType === \"string\" ? resource.mimeType : undefined,\n name: uri?.split(\"/\").pop(),\n }\n }\n return undefined\n}\n\n/** Writes an inline-bytes attachment to this session's content-addressed\n * attachment store (`<sessionDir>/attachments/<sha256>.<ext>`) and returns\n * its metadata. Content-addressed so re-sending the same bytes (a retry, a\n * quoted-back image) reuses the existing file instead of duplicating it.\n * Returns `undefined` on a decode/write failure — the caller falls back to\n * keeping the block's raw shape in the transcript rather than losing the\n * reference entirely (recording must never throw and fail the turn). */\nfunction materializeAttachment(\n sessionId: string,\n baseDir: string | undefined,\n inline: { data: string; mimeType?: string; name?: string },\n): AttachmentEntry | undefined {\n try {\n const bytes = Buffer.from(inline.data, \"base64\")\n const sha256 = createHash(\"sha256\").update(bytes).digest(\"hex\")\n const ext = extensionFor(inline.mimeType, inline.name)\n const filename = `${sha256}.${ext}`\n const dir = sessionAttachmentsDir(sessionId, baseDir)\n const absPath = join(dir, filename)\n if (!existsSync(absPath)) {\n mkdirSync(dir, { recursive: true })\n writeFileSync(absPath, bytes)\n }\n return {\n key: inline.name ?? filename,\n path: join(\"attachments\", filename),\n sha256,\n size: bytes.length,\n ...(inline.mimeType ? { contentType: inline.mimeType } : {}),\n name: inline.name ?? filename,\n }\n } catch (err) {\n console.warn(\n `[transcript-writer] materializeAttachment: failed to write attachment for session ` +\n `${sessionId}: ${err instanceof Error ? err.message : String(err)}`,\n )\n return undefined\n }\n}\n\n/** Highest `seq` already durably on disk for a session, or 0 when the file\n * is absent/empty. A fresh writer instance (e.g. after a daemon restart)\n * opens the existing events.jsonl in append mode, so its first record must\n * continue from this cursor rather than restarting at 1 — otherwise a\n * restart mid-session emits duplicate seq numbers and any `since`-based\n * reader (GET /sessions/:id/events) silently drops the post-restart tail.\n * Records are written in ascending `seq`, so the max is on the last valid\n * line, but a truncated/partial final line is tolerated by scanning every\n * parseable line rather than trusting the tail. */\nfunction highestSeqOnDisk(path: string): number {\n let raw: string\n try {\n raw = readFileSync(path, \"utf8\")\n } catch {\n // ENOENT (fresh session) or any read error — start from 0.\n return 0\n }\n let max = 0\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n const rec = JSON.parse(trimmed) as { seq?: unknown }\n if (typeof rec.seq === \"number\" && rec.seq > max) max = rec.seq\n } catch {\n // Ignore a malformed/partial line (e.g. a torn final write).\n }\n }\n return max\n}\n\nexport interface TranscriptWriter extends SessionObserver {\n /** Record the outgoing message for a new turn. Not an ACP `StreamEvent`\n * kind (ACP's own \"agent-prompt\" means the agent asking the human a\n * question, the opposite direction) — recorded under its own\n * \"user-prompt\" kind so turns stay reconstructable without overloading\n * that name. `opts.source` (provenance, e.g. `agent:<sessionId>` for a\n * supervisor-injected prompt) rides onto the record's `source` field.\n *\n * `opts.system`, when present, is the daemon-composed SYSTEM slice of\n * `message` (role disposition + AGENTS.md + lineage — the preamble the\n * daemon prepended to a spawned child's prompt, which the ADAPTER still\n * receives as one string). It is recorded as its own `system-prompt`\n * record AHEAD of the `user-prompt`, and that user-prompt carries only\n * the CALLER's own ask (the preamble stripped) — so viewers can fold\n * the synthesized text instead of showing it as a user bubble. Recording-\n * only; never alters what the adapter receives or how the turn runs.\n *\n * When `message` is a content block or block array (a multimodal turn),\n * the record's `text` field carries only its text blocks joined by \"\\n\"\n * (never a JSON dump); the full blocks ride on a `blocks` field, with any\n * inline-bytes block (a pasted image) materialized to this session's\n * content-addressed attachment store and swapped for a lightweight\n * `{attachmentKey, attachmentPath}` reference — see `materializeAttachment`\n * — so events.jsonl never carries raw base64. Materialized attachments\n * also collect onto a top-level `attachments` field (`AttachmentEntry[]`). */\n recordPrompt(\n sessionId: string,\n message: unknown,\n opts?: { source?: string; system?: string; messages?: readonly SessionMessage[] }\n ): void\n /** Sender-side `session-message-sent` record — see SessionObserver. */\n recordSessionMessageSent(sessionId: string, record: SessionMessageSentRecord): void\n /** Standalone `session-message` record (a message surfaced outside a turn\n * prompt, e.g. as an `inbox_wait` result) — see SessionObserver. */\n recordSessionMessage(sessionId: string, message: SessionMessage): void\n /** Record one structured stream event, ahead of `projectEvent`'s\n * flattening. Coalesces consecutive text-delta/thought chunks the same\n * way the ring buffer does. */\n recordEvent(sessionId: string, evt: AgentStreamEvent): void\n /** BOOTSTRAP P7b — append PRE-SHAPED mirror records (from\n * `device-mirror.ts`) verbatim, each already carrying its own `kind`,\n * `origin: \"device\"`, `hostSeq`, and `sourceRef`. Bypasses `recordEvent`'s\n * kind-specific logic and coalescing entirely: a mirrored replay must be\n * recorded exactly as it was computed. Still stamps the controller-local\n * `seq`/`ts` ordering fields and notifies live subscribers, same as any\n * write. A no-op for an empty list. */\n recordMirrorEvents(\n sessionId: string,\n records: readonly Record<string, unknown>[],\n ): void\n /** Record a durable `usage_snapshot` recap at a turn boundary or on\n * session exit — the cumulative cost/token/context view resolved by\n * `deriveSessionUsage`. Distinct from the high-frequency `usage_update`\n * stream: this is the aggregable turn-boundary durable record, so a\n * daemon restart doesn't lose the session's accumulated usage. */\n recordUsageSnapshot(sessionId: string, usage: SessionUsage): void\n /** Record a `tools/call` an MCP App iframe made through the daemon\n * (`mcp_app_tool_call`, mcp-apps-host.ts) — a side-channel call the\n * model didn't make, so it gets its own `kind: \"mcp_app_tool_call\"`\n * record (\"called from app UI\") rather than a harness `tool-call`.\n * Carries no args and no result. */\n recordMcpAppToolCall(sessionId: string, record: McpAppToolCallRecord): void\n /** Flush buffers and close the session's append stream. Safe to call\n * more than once (subsequent calls are no-ops) and safe to call for a\n * session that never wrote anything (also a no-op). Production call\n * sites treat this as fire-and-forget (same as `agentSession.close()`);\n * the returned promise exists so tests can await the on-disk flush. */\n close(sessionId: string): Promise<void>\n /** Close every open session stream — called from the registry's\n * synchronous shutdown path (fire-and-forget there too). */\n closeAll(): Promise<void>\n /** Subscribe to every record as it's written for one session — the\n * live-push half of `GET /sessions/:id/events/stream`'s replay-then-\n * subscribe handoff (http-server.ts). Delivers the exact object shape\n * written to disk (`{seq, ts, kind, ...}`, pre-JSON.stringify), so a\n * consumer already reducing the poll route's `events` array can reduce\n * this stream with identical logic. No backfill here — history is a\n * file read the caller does itself; this only wires the live tap.\n * Notification happens synchronously, in the same call that appends the\n * record, so a caller that subscribes before doing its own disk read is\n * guaranteed to observe (via this callback) every record from that\n * point on, even ones the read might otherwise race. Returns an\n * unsubscribe fn; safe to call more than once. */\n subscribe(sessionId: string, onRecord: (record: Record<string, unknown>) => void): () => void\n}\n\ninterface WriterState {\n stream: WriteStream\n seq: number\n textBuf: string\n thoughtBuf: string\n textDebounce: ReturnType<typeof setTimeout> | null\n thoughtDebounce: ReturnType<typeof setTimeout> | null\n /**\n * Latest un-written tool-call ENRICHMENT per toolCallId — the same\n * coalescing textBuf/thoughtBuf do, for the same reason.\n *\n * An ACP agent streams a tool's `rawInput` as the model types it, so one\n * call arrives as a run of growing updates:\n * {} → {adapter} → {adapter,cwd} → {adapter,cwd,role} → {…,prompt} → …\n * Writing each would put six records on disk for one `agent_start`, and\n * only the last is true. They're superseding snapshots, not increments:\n * keep the newest and write it once, when something else needs ordering\n * around it (see flushBuffers).\n */\n toolUpdates: Map<string, Record<string, unknown>>\n /**\n * Latest known {toolName, arguments} + announce time per in-flight\n * toolCallId, kept across BOTH the announcement and any enrichment\n * updates (not just the held/coalesced ones above) — the \"tool-result\"\n * handler consumes this to emit ONE normalized `tool-call-record` line\n * (see tool-call-record.ts) per finished call, since a bare \"tool-result\"\n * event carries neither the tool's name nor its input.\n */\n toolCallMeta: Map<string, { toolName: string; arguments: unknown; startedAt: number }>\n}\n\nexport function createTranscriptWriter(opts?: { baseDir?: string }): TranscriptWriter {\n const baseDir = opts?.baseDir\n const states = new Map<string, WriterState>()\n const listeners = new Map<string, Set<(record: Record<string, unknown>) => void>>()\n\n const getState = (sessionId: string): WriterState => {\n const existing = states.get(sessionId)\n if (existing) return existing\n mkdirSync(sessionTranscriptDir(sessionId, baseDir), { recursive: true })\n const eventsPath = sessionEventsPath(sessionId, baseDir)\n // Resume the seq counter from what's already durable so a restart\n // mid-session keeps events.jsonl monotonic in append mode.\n const resumeSeq = highestSeqOnDisk(eventsPath)\n const stream = createWriteStream(eventsPath, { flags: \"a\" })\n stream.on(\"error\", err => {\n console.warn(`[transcript-writer] session ${sessionId}: ${err.message}`)\n })\n const state: WriterState = {\n stream,\n seq: resumeSeq,\n textBuf: \"\",\n thoughtBuf: \"\",\n textDebounce: null,\n thoughtDebounce: null,\n toolUpdates: new Map(),\n toolCallMeta: new Map(),\n }\n states.set(sessionId, state)\n return state\n }\n\n const writeRecord = (\n sessionId: string,\n state: WriterState,\n record: Record<string, unknown>\n ): void => {\n state.seq += 1\n const full = { seq: state.seq, ts: new Date().toISOString(), ...record }\n state.stream.write(JSON.stringify(full) + \"\\n\")\n const subs = listeners.get(sessionId)\n if (subs) {\n for (const onRecord of subs) onRecord(full)\n }\n }\n\n const flushTextBuf = (sessionId: string, state: WriterState): void => {\n if (state.textDebounce) {\n clearTimeout(state.textDebounce)\n state.textDebounce = null\n }\n if (!state.textBuf) return\n const text = state.textBuf\n state.textBuf = \"\"\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text })\n }\n\n const flushThoughtBuf = (sessionId: string, state: WriterState): void => {\n if (state.thoughtDebounce) {\n clearTimeout(state.thoughtDebounce)\n state.thoughtDebounce = null\n }\n if (!state.thoughtBuf) return\n const text = state.thoughtBuf\n state.thoughtBuf = \"\"\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text })\n }\n\n /** Write the newest held enrichment for each in-flight tool call. */\n const flushToolUpdates = (sessionId: string, state: WriterState): void => {\n if (state.toolUpdates.size === 0) return\n const held = [...state.toolUpdates.values()]\n state.toolUpdates.clear()\n for (const record of held) writeRecord(sessionId, state, record)\n }\n\n /** Flush every coalescing buffer as FINAL (non-partial) records — called\n * before any other event so on-disk order matches wall-clock order even\n * when a partial line, or a still-growing tool input, was still held. */\n const flushBuffers = (sessionId: string, state: WriterState): void => {\n flushThoughtBuf(sessionId, state)\n flushTextBuf(sessionId, state)\n flushToolUpdates(sessionId, state)\n }\n\n const scheduleTextDebounce = (sessionId: string, state: WriterState): void => {\n if (state.textDebounce) clearTimeout(state.textDebounce)\n state.textDebounce = setTimeout(() => {\n state.textDebounce = null\n if (!state.textBuf) return\n const text = state.textBuf\n state.textBuf = \"\"\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text, partial: true })\n }, DEBOUNCE_MS)\n }\n\n const scheduleThoughtDebounce = (sessionId: string, state: WriterState): void => {\n if (state.thoughtDebounce) clearTimeout(state.thoughtDebounce)\n state.thoughtDebounce = setTimeout(() => {\n state.thoughtDebounce = null\n if (!state.thoughtBuf) return\n const text = state.thoughtBuf\n state.thoughtBuf = \"\"\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text, partial: true })\n }, DEBOUNCE_MS)\n }\n\n return {\n recordPrompt(sessionId, message, opts) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n // Content blocks (image/resource) — `opts.system` is only ever set for\n // a plain-string composed prompt (a block-shaped `agent_start` prompt\n // SKIPS preamble composition entirely, see `SpawnAgentSessionInput.\n // prompt`'s doc), so the string-based preamble-stripping below stays\n // string-only and untouched by the block case.\n const blocks: unknown[] | undefined =\n typeof message === \"string\" ? undefined : Array.isArray(message) ? message : [message]\n // Readable text for downstream consumers (session-titler, transcript\n // previews) that just want a string — pulled from `text` blocks only,\n // same convention as `session-title.ts`'s own extraction. Non-text\n // blocks are never stringified into this field; they're recorded\n // structurally below instead (`blocks`/`attachments`).\n const text =\n typeof message === \"string\"\n ? message\n : (blocks ?? [])\n .filter((b): b is Record<string, unknown> => isRecord(b) && typeof b.text === \"string\")\n .map(b => b.text as string)\n .join(\"\\n\")\n // The daemon-composed SYSTEM slice of a spawned child's initial\n // prompt is recorded apart from the caller's ask. The user-prompt\n // text is the composed string with that preamble stripped (the\n // preamble was prepended verbatim at composition, joined by \"\\n\\n\").\n if (opts?.system) {\n writeRecord(sessionId, state, { kind: \"system-prompt\", sessionId, text: opts.system })\n }\n // Strip the system preamble off the front of `text` to get the\n // caller's own ask. Guarded by an explicit prefix check (not just a\n // length comparison) so a future change to the composition format\n // (session-spawn.ts's `composedPreamble`/`effectivePrompt` join) can't\n // silently slice the wrong bytes here — if the invariant doesn't\n // hold, fall back to recording the FULL text rather than producing\n // corrupted output.\n let userText = text\n if (opts?.system) {\n if (text.startsWith(opts.system)) {\n userText = text.slice(opts.system.length).replace(/^\\n\\n/, \"\")\n } else {\n console.warn(\n `[transcript-writer] recordPrompt: system preamble is not a prefix of the composed ` +\n `prompt for session ${sessionId} — composition invariant violated (see ` +\n `session-spawn.ts's composedPreamble), recording the full text as user-prompt ` +\n `instead of risking a bad slice.`,\n )\n }\n }\n // A typed-message turn is recorded as one `session-message` record per\n // delivered message — the daemon-attested envelope — and NEVER as a\n // `user-prompt`, so no reader can mistake it for the human.\n if (opts?.messages?.length) {\n for (const message of opts.messages) {\n writeRecord(sessionId, state, { kind: \"session-message\", sessionId, message })\n }\n return\n }\n // Materialize any inline-bytes block (a pasted image, a document sent\n // as base64 rather than a `POST /files/upload` path reference) to this\n // session's content-addressed attachment store, and swap the block's\n // heavy inline bytes for a lightweight `{attachmentKey, attachmentPath,\n // mimeType}` reference before it reaches events.jsonl — keeps the\n // durable transcript bounded regardless of how big an attachment was,\n // and gives the UI a stable path to re-render from after reload. A\n // block with no bytes of its own (text, or an already-path-based\n // reference) passes through unchanged. See `materializeAttachment`'s\n // doc for the AIP-58 `ArtifactEntry` parity this is designed for.\n const attachments: AttachmentEntry[] = []\n const blocksForTranscript = blocks?.map(block => {\n if (!isRecord(block)) return block\n const inline = extractInlineBytes(block)\n if (!inline) return block\n const entry = materializeAttachment(sessionId, baseDir, inline)\n if (!entry) return block\n attachments.push(entry)\n const rest: Record<string, unknown> = { ...block }\n delete rest.data\n delete rest.resource\n return {\n ...rest,\n attachmentKey: entry.key,\n attachmentPath: entry.path,\n ...(entry.contentType ? { mimeType: entry.contentType } : {}),\n }\n })\n writeRecord(sessionId, state, {\n kind: \"user-prompt\",\n sessionId,\n text: userText,\n ...(blocksForTranscript ? { blocks: blocksForTranscript } : {}),\n ...(attachments.length ? { attachments } : {}),\n ...(opts?.source ? { source: opts.source } : {}),\n })\n },\n recordSessionMessage(sessionId, message) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"session-message\", sessionId, message })\n },\n recordSessionMessageSent(sessionId, record) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n // `messageKind`, not `kind` — the record's own `kind` is the record type.\n writeRecord(sessionId, state, {\n kind: \"session-message-sent\",\n sessionId,\n messageId: record.messageId,\n to: record.to,\n messageKind: record.kind,\n urgency: record.urgency,\n })\n },\n recordMirrorEvents(sessionId, records) {\n if (records.length === 0) return\n const state = getState(sessionId)\n for (const record of records) writeRecord(sessionId, state, record)\n },\n recordEvent(sessionId, evt) {\n const state = getState(sessionId)\n switch (evt.kind) {\n case \"text-delta\": {\n if (!evt.text) break\n const combined = state.textBuf + evt.text\n const lines = combined.split(/\\r?\\n/)\n state.textBuf = lines.pop() ?? \"\"\n // `split` consumes each line's terminator — reappend it so the\n // on-disk record stays byte-for-byte reconstructable (a blank\n // line becomes \"\\n\" rather than a lossy \"\").\n for (const line of lines) {\n writeRecord(sessionId, state, { kind: \"text-delta\", sessionId, text: `${line}\\n` })\n }\n if (state.textBuf) scheduleTextDebounce(sessionId, state)\n break\n }\n case \"thought\": {\n if (!evt.text) break\n const combined = state.thoughtBuf + evt.text\n const lines = combined.split(/\\r?\\n/)\n state.thoughtBuf = lines.pop() ?? \"\"\n for (const line of lines) {\n writeRecord(sessionId, state, { kind: \"thought\", sessionId, text: `${line}\\n` })\n }\n if (state.thoughtBuf) scheduleThoughtDebounce(sessionId, state)\n break\n }\n case \"tool-call\": {\n // Remember the latest name/input for this call regardless of\n // whether this frame is an announcement or an enrichment — the\n // eventual \"tool-result\" needs both and carries neither itself.\n // An enrichment's `toolName` is `\"\"` when the update carried no\n // title (see toolCallEnrichment in @agentproto/acp); keep the\n // prior name in that case rather than overwriting it with blank.\n if (evt.toolCallId) {\n const existing = state.toolCallMeta.get(evt.toolCallId)\n state.toolCallMeta.set(evt.toolCallId, {\n toolName: evt.toolName || existing?.toolName || \"\",\n arguments: evt.arguments !== undefined ? evt.arguments : existing?.arguments,\n startedAt: existing?.startedAt ?? Date.now(),\n })\n }\n const record = {\n kind: \"tool-call\",\n sessionId,\n toolCallId: evt.toolCallId,\n toolName: evt.toolName,\n arguments: evt.arguments,\n // Carried through so a replay can tell an ENRICHMENT of an\n // announced call from a genuine second call. Readers merge by\n // toolCallId either way, but a record that silently drops the\n // distinction can't be reasoned about after the fact.\n ...(evt.isUpdate ? { isUpdate: true } : {}),\n }\n // An enrichment is a superseding SNAPSHOT of the call's input, and\n // the agent emits one per token as the model types it. Hold the\n // newest instead of writing all of them — flushBuffers puts it on\n // disk the moment anything else needs ordering around it (the\n // tool-result, the next call, a text delta, turn-end). Deliberately\n // no flushBuffers here: that's what lets consecutive updates for the\n // same call collapse into one record.\n if (evt.isUpdate && evt.toolCallId) {\n state.toolUpdates.set(evt.toolCallId, record)\n break\n }\n // The announcement is written at once — it's what makes the step\n // appear, and a pending row the user can see is the whole point.\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, record)\n break\n }\n case \"tool-result\": {\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"tool-result\",\n sessionId,\n toolCallId: evt.toolCallId,\n result: evt.result,\n isError: evt.isError ?? false,\n })\n // Normalized ToolCallRecord (see tool-call-record.ts) — the same\n // shape the command_execute proxy path writes via\n // `writeToolCallRecord`, so `tool_calls_list` reads one unified\n // log regardless of which path produced the call.\n const meta = evt.toolCallId ? state.toolCallMeta.get(evt.toolCallId) : undefined\n if (evt.toolCallId) state.toolCallMeta.delete(evt.toolCallId)\n const { command, args } = extractCommandArgs(meta?.arguments)\n // Exact PR-creation provenance, captured at the only moment both\n // halves exist together (command + result text) — see the\n // `createdPrUrl` field's doc in tool-call-record.ts. Some harnesses\n // surface the shell string as the tool NAME rather than in\n // arguments, so fall back to that.\n const createdPr =\n (evt.isError ?? false)\n ? null\n : detectShellPrCreate(\n command ?? meta?.toolName,\n typeof evt.result === \"string\" ? evt.result : undefined,\n )\n writeRecord(sessionId, state, {\n kind: \"tool-call-record\",\n sessionId,\n tool: meta?.toolName || \"unknown\",\n ...(command !== undefined ? { command } : {}),\n ...(args !== undefined ? { args } : {}),\n isError: evt.isError ?? false,\n ...(meta ? { durationMs: Date.now() - meta.startedAt } : {}),\n ...(createdPr ? { createdPrUrl: createdPr.url, createdPrNumber: createdPr.number } : {}),\n })\n break\n }\n case \"agent-prompt\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"agent-prompt\",\n sessionId,\n toolCallId: evt.toolCallId,\n options: evt.options,\n })\n break\n // Durable counterpart to \"agent-prompt\" — recorded when\n // `respondPermission` (or a dying session's cancel-in-flight sweep)\n // resolves the ask, keyed by the same `toolCallId` so a reader can\n // tell an answered request from a still-pending one without relying\n // on the in-memory pending-permissions map (see sessions.ts's\n // `resolvePendingPermission` / `cancelPendingPermissionsForSession`).\n case \"permission-resolved\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"permission-resolved\",\n sessionId,\n toolCallId: evt.toolCallId,\n decision: evt.decision,\n ...(evt.optionId ? { optionId: evt.optionId } : {}),\n })\n break\n case \"turn-end\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"turn-end\", sessionId, reason: evt.reason })\n break\n case \"notice\":\n // A daemon-side system notice (not agent output) — e.g. the\n // interrupted-turn banner written on resume of a session that died\n // mid-turn under a daemon restart. Durable in events.jsonl so the\n // warning survives beyond the in-memory ring buffer; readers render\n // it as a system message (see transcript-export's \"notice\" case).\n if (!evt.text) break\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"notice\", sessionId, text: evt.text })\n break\n case \"error\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, { kind: \"error\", sessionId, error: evt.error })\n break\n case \"plan\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"plan\",\n sessionId,\n ...(evt.title ? { title: evt.title } : {}),\n entries: evt.entries ?? [],\n })\n break\n case \"usage_update\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"usage_update\",\n sessionId,\n size: evt.size,\n used: evt.used,\n ...(evt.cost ? { cost: evt.cost } : {}),\n // Persist cumulative token counts when the adapter reports them\n // (claude-code over ACP, hermes via its state.db reader,\n // mastracode via its native usage_update) so the transcript can\n // price/aggregate a session even without a `cost` block.\n ...(evt.tokensIn !== undefined ? { tokensIn: evt.tokensIn } : {}),\n ...(evt.tokensOut !== undefined ? { tokensOut: evt.tokensOut } : {}),\n // The model the usage belongs to, and — when the daemon corrected\n // an inferred window (see context-window.ts) — the size the\n // adapter itself reported, so the correction stays auditable.\n ...(evt.model ? { model: evt.model } : {}),\n ...(evt.reportedSize !== undefined ? { reportedSize: evt.reportedSize } : {}),\n })\n break\n case \"background-task\":\n // The agent's background-task lifecycle (started / updated /\n // settled) — durable so a reader can tell \"idle, waiting on a\n // task\" from \"idle, done\", and what a later wake was about.\n if (!evt.task) break\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"background-task\",\n sessionId,\n phase: evt.phase,\n task: evt.task,\n })\n break\n case \"available-commands\":\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"available-commands\",\n sessionId,\n commands: evt.commands ?? [],\n })\n break\n default:\n // Unknown kind — drop, same as `projectEvent`'s implicit\n // no-op for anything it doesn't switch on.\n break\n }\n },\n recordUsageSnapshot(sessionId, usage) {\n const state = getState(sessionId)\n // Order after any buffered text/thought so on-disk order matches\n // wall-clock order.\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"usage_snapshot\",\n sessionId,\n // Omit absent fields so a missing value never reads as a measured 0.\n ...(usage.model !== undefined ? { model: usage.model } : {}),\n ...(usage.costUsd !== undefined ? { costUsd: usage.costUsd } : {}),\n ...(usage.tokensIn !== undefined ? { tokensIn: usage.tokensIn } : {}),\n ...(usage.tokensOut !== undefined ? { tokensOut: usage.tokensOut } : {}),\n ...pickUsageDetail(usage),\n ...(usage.contextSize !== undefined ? { contextSize: usage.contextSize } : {}),\n ...(usage.contextUsed !== undefined ? { contextUsed: usage.contextUsed } : {}),\n source: usage.source,\n })\n },\n recordMcpAppToolCall(sessionId, record) {\n const state = getState(sessionId)\n flushBuffers(sessionId, state)\n writeRecord(sessionId, state, {\n kind: \"mcp_app_tool_call\",\n sessionId,\n server: record.server,\n tool: record.tool,\n originToolCallId: record.originToolCallId,\n isError: record.isError,\n durationMs: record.durationMs,\n })\n },\n close(sessionId) {\n const state = states.get(sessionId)\n if (!state) return Promise.resolve()\n flushBuffers(sessionId, state)\n states.delete(sessionId)\n return new Promise<void>(resolve => {\n state.stream.end(() => resolve())\n })\n },\n closeAll() {\n const closings: Promise<void>[] = []\n for (const sessionId of Array.from(states.keys())) {\n const state = states.get(sessionId)\n if (!state) continue\n flushBuffers(sessionId, state)\n closings.push(new Promise<void>(resolve => state.stream.end(() => resolve())))\n }\n states.clear()\n return Promise.all(closings).then(() => undefined)\n },\n subscribe(sessionId, onRecord) {\n let subs = listeners.get(sessionId)\n if (!subs) {\n subs = new Set()\n listeners.set(sessionId, subs)\n }\n subs.add(onRecord)\n return () => {\n const current = listeners.get(sessionId)\n if (!current) return\n current.delete(onRecord)\n if (current.size === 0) listeners.delete(sessionId)\n }\n },\n }\n}\n","/**\n * Export a clean, readable transcript, dispatched between two kinds of\n * source:\n *\n * native — the adapter's OWN persistence, re-read after the fact:\n * claude-code — ~/.claude/projects/<cwd-encoded>/<sessionId>.jsonl\n * hermes — ~/.hermes/state.db via node:sqlite (read-only)\n * codex — $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl\n * opencode — ~/.local/share/opencode/opencode.db via node:sqlite\n * mastracode-inprocess — ~/.agentproto/mastracode-inprocess/storage.db (Mastra libsql)\n * pi — ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl\n * daemon — agentproto's own capture, `~/.agentproto/sessions/<id>/events.jsonl`\n * (written by transcript-writer.ts at the same point `projectEvent`\n * flattens events into the ANSI ring buffer, ahead of that flattening).\n * Available for ANY agent-cli session (acp or print protocol), not just\n * the two adapters with a native store.\n *\n * `source: \"auto\"` (the default) tries native first and falls back to\n * daemon events when there's no native strategy or its store can't be\n * read; `\"native\"` / `\"daemon\"` force one and surface its own error.\n *\n * Output: Markdown (default) or JSON, built from a shared ExportedSession\n * model so every source produces identical rendering logic.\n */\n\nimport { createReadStream, promises as fs, type Dirent } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\nimport { createInterface } from \"node:readline\"\nimport type { SessionDescriptor, SessionsRegistry } from \"./sessions.js\"\nimport { formatToolCall } from \"./tool-presenter.js\"\nimport { sessionEventsPath } from \"./transcript-writer.js\"\nimport { claudeCodeProjectDir } from \"./conversation-store.js\"\nimport type { ConversationCandidate } from \"./conversation-store.js\"\n\n// ── Common model ──────────────────────────────────────────────────────\n\nexport interface ExportedMessage {\n role: \"user\" | \"assistant\" | \"tool\" | \"system\"\n text?: string\n /** Set on a `user`-role message that is a typed inter-session message\n * (a `session-message` record) rather than the human: the daemon-attested\n * sender. Absent for a human prompt. */\n from?: {\n sessionId?: string\n label?: string\n relation: \"child\" | \"parent\" | \"sibling\" | \"human\" | \"system\"\n kind: string\n messageId: string\n }\n reasoning?: string\n toolName?: string\n toolCalls?: { name: string; args: string }[]\n ts?: number\n}\n\nexport interface ExportedSessionMeta {\n title?: string\n model?: string\n startedAt?: string\n endedAt?: string\n messageCount?: number\n toolCallCount?: number\n tokens?: {\n input?: number\n output?: number\n cacheRead?: number\n cacheWrite?: number\n reasoning?: number\n contextSize?: number\n contextUsed?: number\n }\n costUsd?: number\n source?: string\n /** Provenance carried over from the session descriptor's own\n * `origin`/`callerSessionId` (see sessions.ts) — distinct from `source`,\n * which names the EXPORTER that produced this payload (e.g.\n * \"daemon-events\", \"codex\"), not who invoked the underlying session. */\n origin?: string\n callerSessionId?: string\n}\n\nexport interface ExportedSession {\n meta: ExportedSessionMeta\n messages: ExportedMessage[]\n}\n\n// ── Render ────────────────────────────────────────────────────────────\n\nconst ROLE_ICON: Record<string, string> = {\n user: \"🧑 User\",\n assistant: \"🤖 Assistant\",\n tool: \"🔧 Tool\",\n system: \"⚙️ System\",\n}\n\nfunction trunc(text: string, n: number): string {\n if (text.length <= n) return text\n return text.slice(0, n) + `\\n… [${text.length - n} chars truncated]`\n}\n\nexport function renderMarkdown(\n session: ExportedSession,\n opts: { maxToolChars?: number } = {},\n): string {\n const { meta, messages } = session\n const maxToolChars = opts.maxToolChars ?? 1200\n const out: string[] = []\n\n out.push(`# ${meta.title ?? \"(untitled)\"}`)\n out.push(\"\")\n\n const sourceNote = meta.source ? ` · source \\`${meta.source}\\`` : \"\"\n const originNote = meta.origin ? ` · origin \\`${meta.origin}\\`` : \"\"\n const callerNote = meta.callerSessionId ? ` · caller \\`${meta.callerSessionId}\\`` : \"\"\n out.push(`> Session${sourceNote}${originNote}${callerNote}`)\n out.push(\"\")\n out.push(\"| | |\")\n out.push(\"|---|---|\")\n if (meta.model) out.push(`| Model | \\`${meta.model}\\` |`)\n if (meta.startedAt || meta.endedAt) {\n out.push(`| Start → end | ${meta.startedAt ?? \"?\"} → ${meta.endedAt ?? \"?\"} |`)\n }\n if (meta.messageCount !== undefined || meta.toolCallCount !== undefined) {\n const parts: string[] = []\n if (meta.messageCount !== undefined) parts.push(`${meta.messageCount} messages`)\n if (meta.toolCallCount !== undefined) parts.push(`${meta.toolCallCount} tool calls`)\n out.push(`| Count | ${parts.join(\" · \")} |`)\n }\n if (meta.tokens && Object.keys(meta.tokens).length) {\n const t = meta.tokens\n const parts: string[] = []\n if (t.input !== undefined) parts.push(`in ${t.input.toLocaleString(\"en-US\")}`)\n if (t.output !== undefined) parts.push(`out ${t.output.toLocaleString(\"en-US\")}`)\n if (t.cacheRead !== undefined || t.cacheWrite !== undefined) {\n parts.push(\n `cache r/w ${(t.cacheRead ?? 0).toLocaleString(\"en-US\")}/${(t.cacheWrite ?? 0).toLocaleString(\"en-US\")}`,\n )\n }\n if (t.reasoning !== undefined) parts.push(`reason. ${t.reasoning.toLocaleString(\"en-US\")}`)\n if (parts.length) out.push(`| Tokens | ${parts.join(\" · \")} |`)\n }\n if (meta.costUsd !== undefined) {\n out.push(`| Cost | $${meta.costUsd.toFixed(4)} |`)\n }\n out.push(\"\")\n out.push(\"---\")\n out.push(\"\")\n\n for (const m of messages) {\n const icon = ROLE_ICON[m.role] ?? m.role\n const nameSuffix = m.toolName\n ? ` · \\`${m.toolName}\\``\n : m.from\n ? ` · from ${m.from.relation} \\`${m.from.label ?? m.from.sessionId ?? \"\"}\\` (${m.from.kind})`\n : \"\"\n out.push(`### ${icon}${nameSuffix}`)\n\n if (m.reasoning?.trim()) {\n out.push(\"\")\n out.push(\"<details><summary>💭 reasoning</summary>\")\n out.push(\"\")\n out.push(trunc(m.reasoning, 4000))\n out.push(\"\")\n out.push(\"</details>\")\n }\n\n if (m.text?.trim()) {\n out.push(\"\")\n if (m.role === \"tool\") {\n out.push(\"```\")\n out.push(trunc(m.text, maxToolChars))\n out.push(\"```\")\n } else {\n out.push(m.text.trim())\n }\n }\n\n if (m.toolCalls?.length) {\n out.push(\"\")\n for (const tc of m.toolCalls) {\n let parsedArgs: unknown\n try {\n parsedArgs = JSON.parse(tc.args)\n } catch {\n parsedArgs = tc.args\n }\n out.push(`> 📞 ${formatToolCall(tc.name, parsedArgs)}`)\n }\n }\n\n out.push(\"\")\n }\n\n return out.join(\"\\n\")\n}\n\nexport function renderJson(session: ExportedSession): string {\n return JSON.stringify(session, null, 2)\n}\n\n// ── Exporter interface ────────────────────────────────────────────────\n\ninterface ExportStrategy {\n /** `configDir` = the session's isolated provider config dir\n * (`SessionDescriptor.adapterConfigDir`) — only claude-code keys its\n * store off it; the other strategies ignore it. */\n exportSession(adapterSessionId: string, cwd?: string, configDir?: string): Promise<ExportedSession>\n}\n\n// ── claude-code exporter (JSONL) ──────────────────────────────────────\n\ninterface ClaudeTextBlock {\n type: \"text\"\n text: string\n}\ninterface ClaudeThinkingBlock {\n type: \"thinking\"\n thinking: string\n}\ninterface ClaudeToolUseBlock {\n type: \"tool_use\"\n name: string\n input?: unknown\n}\ninterface ClaudeToolResultBlock {\n type: \"tool_result\"\n tool_use_id?: string\n content?: unknown\n}\ntype ClaudeContentBlock =\n | ClaudeTextBlock\n | ClaudeThinkingBlock\n | ClaudeToolUseBlock\n | ClaudeToolResultBlock\n | { type: string }\n\ninterface ClaudeCodeJsonlLine {\n type: string\n message?: {\n role?: string\n content?: string | ClaudeContentBlock[]\n }\n}\n\nconst IGNORED_CLAUDE_TYPES = new Set([\n \"queue-operation\",\n \"attachment\",\n \"file-history-snapshot\",\n \"ai-title\",\n \"last-prompt\",\n])\n\nexport async function exportClaudeCodeSession(\n adapterSessionId: string,\n cwd?: string,\n configDir?: string,\n): Promise<ExportedSession> {\n if (!cwd) {\n throw new Error(\n \"claude-code exporter: cwd is required to locate the JSONL file.\\n\" +\n \"Pass cwd explicitly or use a session id that is in the registry.\",\n )\n }\n // A config-dir-isolated session (#824) writes its transcripts under its\n // own CLAUDE_CONFIG_DIR, same projects/<slug> layout as ~/.claude.\n const filePath = join(claudeCodeProjectDir(cwd, configDir), `${adapterSessionId}.jsonl`)\n\n let stream: ReturnType<typeof createReadStream>\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" })\n // Trigger ENOENT eagerly before readline wraps it.\n await new Promise<void>((resolve, reject) => {\n stream.once(\"error\", reject)\n stream.once(\"open\", resolve)\n })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n throw new Error(\n `claude-code: JSONL file not found: ${filePath}\\n` +\n `Verify cwd=\"${cwd}\" and adapterSessionId=\"${adapterSessionId}\".`,\n )\n }\n throw err\n }\n\n const messages: ExportedMessage[] = []\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: ClaudeCodeJsonlLine\n try {\n entry = JSON.parse(trimmed) as ClaudeCodeJsonlLine\n } catch {\n continue\n }\n if (IGNORED_CLAUDE_TYPES.has(entry.type)) continue\n if (entry.type !== \"user\" && entry.type !== \"assistant\") continue\n const msg = entry.message\n if (!msg) continue\n\n const role: ExportedMessage[\"role\"] =\n msg.role === \"assistant\" ? \"assistant\" : \"user\"\n const content = msg.content\n\n if (typeof content === \"string\") {\n if (content.trim()) messages.push({ role, text: content.trim() })\n continue\n }\n if (!Array.isArray(content)) continue\n\n // Collect all block types from this message event\n let textAcc = \"\"\n let reasoningAcc = \"\"\n const toolCalls: { name: string; args: string }[] = []\n const toolResults: { text: string }[] = []\n\n for (const block of content) {\n switch (block.type) {\n case \"text\": {\n textAcc += (block as ClaudeTextBlock).text\n break\n }\n case \"thinking\": {\n reasoningAcc += (block as ClaudeThinkingBlock).thinking\n break\n }\n case \"tool_use\": {\n const tb = block as ClaudeToolUseBlock\n const args =\n typeof tb.input === \"string\"\n ? tb.input\n : JSON.stringify(tb.input ?? {})\n toolCalls.push({ name: tb.name, args })\n break\n }\n case \"tool_result\": {\n const tb = block as ClaudeToolResultBlock\n let resultText = \"\"\n if (typeof tb.content === \"string\") {\n resultText = tb.content\n } else if (Array.isArray(tb.content)) {\n for (const c of tb.content as ClaudeContentBlock[]) {\n if (c.type === \"text\") resultText += (c as ClaudeTextBlock).text\n }\n }\n toolResults.push({ text: resultText })\n break\n }\n }\n }\n\n // Emit the main message (user text or assistant with optional tool calls)\n if (textAcc.trim() || toolCalls.length || reasoningAcc) {\n const m: ExportedMessage = { role }\n if (textAcc.trim()) m.text = textAcc.trim()\n if (reasoningAcc) m.reasoning = reasoningAcc\n if (toolCalls.length) m.toolCalls = toolCalls\n messages.push(m)\n }\n\n // Emit each tool result as its own role=tool message\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text })\n }\n }\n\n return { meta: { source: \"claude-code\" }, messages }\n}\n\n// ── hermes exporter (SQLite) ──────────────────────────────────────────\n// node:sqlite is experimental in Node 22.x but functional.\n// Isolated here so a future stable import replaces only this function.\n\n// esbuild (via tsup) strips the `node:` prefix from builtin imports. Most\n// builtins work without it, but `node:sqlite` has NO unprefixed name —\n// `import('sqlite')` fails at runtime. A computed specifier dodges the\n// static analysis so the prefix survives bundling.\nconst NODE_SQLITE = \"node:\" + \"sqlite\"\nasync function importNodeSqlite(): Promise<typeof import(\"node:sqlite\")> {\n return import(/* @vite-ignore */ NODE_SQLITE)\n}\n\ninterface HermesSessionRow {\n id: string\n title?: string\n model?: string\n started_at?: number\n ended_at?: number\n message_count?: number\n tool_call_count?: number\n api_call_count?: number\n input_tokens?: number\n output_tokens?: number\n cache_read_tokens?: number\n cache_write_tokens?: number\n reasoning_tokens?: number\n actual_cost_usd?: number\n estimated_cost_usd?: number\n source?: string\n end_reason?: string\n}\n\ninterface HermesMessageRow {\n id: number\n session_id: string\n role: string\n content?: string\n tool_calls?: string\n tool_name?: string\n reasoning?: string\n reasoning_content?: string\n timestamp?: number\n}\n\n// Shared by exportHermesSession AND discoverHermesSessions (below) — the\n// open + SQLITE_BUSY-retry handling lives in exactly one place so the two\n// callers can never drift.\ntype HermesDb = InstanceType<(typeof import(\"node:sqlite\"))[\"DatabaseSync\"]>\n\nasync function openHermesDb(dbPath: string): Promise<HermesDb> {\n let DatabaseSync: (typeof import(\"node:sqlite\"))[\"DatabaseSync\"]\n try {\n const sqlite = await importNodeSqlite()\n DatabaseSync = sqlite.DatabaseSync\n } catch {\n throw new Error(\"hermes: node:sqlite unavailable. Requires Node.js ≥22.5.0.\")\n }\n\n try {\n return new DatabaseSync(dbPath, { readOnly: true })\n } catch (err) {\n const msg = String(err)\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"unable to open database file\")\n ) {\n throw new Error(\n `hermes: state.db not found at ${dbPath}. ` +\n `Has hermes been run at least once?`,\n )\n }\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n throw new Error(\n `hermes: database is locked (SQLITE_BUSY). ` +\n `Hermes may be writing. Try again in a moment.`,\n )\n }\n throw err\n }\n}\n\nfunction withRetryOnBusy<T>(fn: () => T): T {\n try {\n return fn()\n } catch (err) {\n const msg = String(err)\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n // One brief retry — the lock is usually transient\n try {\n return fn()\n } catch {\n throw new Error(\n `hermes: database is locked. Hermes may be actively writing. Try again.`,\n )\n }\n }\n throw err\n }\n}\n\n/** Generic read-only sqlite open, sharing hermes's node:sqlite plumbing so\n * the opencode + mastracode-inprocess stores don't each reinvent the\n * experimental-module guard and the ENOENT/BUSY error mapping. `label` is\n * the provider name used in the error strings. Returns the same handle\n * type `withRetryOnBusy` operates on. */\nasync function openReadonlySqlite(dbPath: string, label: string): Promise<HermesDb> {\n let DatabaseSync: (typeof import(\"node:sqlite\"))[\"DatabaseSync\"]\n try {\n const sqlite = await importNodeSqlite()\n DatabaseSync = sqlite.DatabaseSync\n } catch {\n throw new Error(`${label}: node:sqlite unavailable. Requires Node.js ≥22.5.0.`)\n }\n try {\n return new DatabaseSync(dbPath, { readOnly: true })\n } catch (err) {\n const msg = String(err)\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"unable to open database file\")\n ) {\n throw new Error(`${label}: database not found at ${dbPath}. Has ${label} been run at least once?`)\n }\n if (msg.includes(\"SQLITE_BUSY\") || msg.includes(\"database is locked\")) {\n throw new Error(`${label}: database is locked (SQLITE_BUSY). It may be writing. Try again in a moment.`)\n }\n throw err\n }\n}\n\nexport async function exportHermesSession(adapterSessionId: string): Promise<ExportedSession> {\n const dbPath = join(homedir(), \".hermes\", \"state.db\")\n const db = await openHermesDb(dbPath)\n\n const session = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT * FROM sessions WHERE id = ?\")\n .get(adapterSessionId) as unknown as HermesSessionRow | undefined,\n )\n\n if (!session) {\n db.close()\n throw new Error(\n `hermes: session \"${adapterSessionId}\" not found in ${dbPath}. ` +\n `ACP sessions are indexed by the agentproto UUID passed as resumeSessionId.`,\n )\n }\n\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare(\n \"SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC\",\n )\n .all(adapterSessionId) as unknown as HermesMessageRow[],\n )\n\n db.close()\n\n const cost =\n session.actual_cost_usd != null\n ? session.actual_cost_usd\n : session.estimated_cost_usd\n\n const startedAt = session.started_at\n ? new Date(session.started_at * 1000).toISOString().replace(\"T\", \" \").slice(0, 19)\n : undefined\n const endedAt = session.ended_at\n ? new Date(session.ended_at * 1000).toISOString().replace(\"T\", \" \").slice(0, 19)\n : undefined\n\n const meta: ExportedSessionMeta = {\n ...(session.title ? { title: session.title } : {}),\n ...(session.model ? { model: session.model } : {}),\n ...(startedAt ? { startedAt } : {}),\n ...(endedAt ? { endedAt } : {}),\n ...(session.message_count !== undefined ? { messageCount: session.message_count } : {}),\n ...(session.tool_call_count !== undefined\n ? { toolCallCount: session.tool_call_count }\n : {}),\n ...(cost != null ? { costUsd: Number(cost) } : {}),\n ...(session.source ? { source: session.source } : {}),\n }\n\n // Only include tokens sub-object when at least one field is present\n const tokensInput = session.input_tokens\n const tokensOutput = session.output_tokens\n const tokensCacheRead = session.cache_read_tokens\n const tokensCacheWrite = session.cache_write_tokens\n const tokensReasoning = session.reasoning_tokens\n if (\n tokensInput !== undefined ||\n tokensOutput !== undefined ||\n tokensCacheRead !== undefined ||\n tokensCacheWrite !== undefined ||\n tokensReasoning !== undefined\n ) {\n meta.tokens = {\n ...(tokensInput !== undefined ? { input: tokensInput } : {}),\n ...(tokensOutput !== undefined ? { output: tokensOutput } : {}),\n ...(tokensCacheRead !== undefined ? { cacheRead: tokensCacheRead } : {}),\n ...(tokensCacheWrite !== undefined ? { cacheWrite: tokensCacheWrite } : {}),\n ...(tokensReasoning !== undefined ? { reasoning: tokensReasoning } : {}),\n }\n }\n\n const messages: ExportedMessage[] = rows.map(row => {\n const validRoles = new Set([\"user\", \"assistant\", \"tool\", \"system\"])\n const role = (\n validRoles.has(row.role) ? row.role : \"user\"\n ) as ExportedMessage[\"role\"]\n\n const reasoning = row.reasoning ?? row.reasoning_content\n\n let toolCalls: { name: string; args: string }[] | undefined\n if (row.tool_calls) {\n try {\n const tc = JSON.parse(row.tool_calls) as unknown[]\n if (Array.isArray(tc) && tc.length) {\n toolCalls = tc.map(c => {\n const entry = c as Record<string, unknown>\n const fn =\n entry.function != null\n ? (entry.function as Record<string, unknown>)\n : entry\n const name = String(fn.name ?? entry.name ?? \"tool\")\n const args =\n typeof fn.arguments === \"string\"\n ? fn.arguments\n : JSON.stringify(fn.arguments ?? {})\n return { name, args }\n })\n }\n } catch {\n // malformed tool_calls — skip\n }\n }\n\n const m: ExportedMessage = { role }\n if (row.content?.trim()) m.text = row.content.trim()\n if (reasoning && String(reasoning).trim()) m.reasoning = String(reasoning)\n if (row.tool_name) m.toolName = row.tool_name\n if (toolCalls) m.toolCalls = toolCalls\n if (row.timestamp) m.ts = row.timestamp\n return m\n })\n\n // Fire-and-forget: cross-check our row count against the binary's export.\n // Non-blocking — a mismatch only logs a warning, it never fails the call.\n void crossValidateHermesExport(adapterSessionId, messages.length).catch(() => {})\n\n return { meta, messages }\n}\n\n// ── hermes discovery (SQLite, cwd-scoped) ─────────────────────────────\n//\n// Reuses openHermesDb/withRetryOnBusy above — the SAME open + BUSY-retry\n// path exportHermesSession uses — so there is one sqlite plumbing to\n// maintain, not two. Called from conversation-store.ts's hermes `discover`\n// via a dynamic import (see that file for why it's dynamic, not static).\n\nfunction hermesRowToCandidate(row: HermesSessionRow): ConversationCandidate {\n const startedAtIso =\n row.started_at !== undefined ? new Date(row.started_at * 1000).toISOString() : undefined\n // \"Last activity\" is when the conversation was last written to — ended_at\n // if it has one, else fall back to started_at (a still-open session).\n const lastActivitySec = row.ended_at ?? row.started_at\n const lastActivityIso =\n lastActivitySec !== undefined ? new Date(lastActivitySec * 1000).toISOString() : undefined\n return {\n conversationId: row.id,\n ...(startedAtIso ? { startedAt: startedAtIso } : {}),\n ...(lastActivityIso ? { lastActivityAt: lastActivityIso } : {}),\n ...(row.message_count !== undefined ? { messageCount: row.message_count } : {}),\n ...(row.title ? { preview: row.title } : {}),\n ...(row.source ? { lastWriter: row.source } : {}),\n }\n}\n\nexport async function discoverHermesSessions(\n cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = join(homedir(), \".hermes\", \"state.db\")\n\n let db: HermesDb\n try {\n db = await openHermesDb(dbPath)\n } catch (err) {\n // \"state.db not found\" == hermes has never run on this box — a normal,\n // empty store, not a discovery error. Anything else (locked, node:sqlite\n // missing) is a genuinely unreadable store and propagates — see the\n // ConversationStore.discover contract in conversation-store.ts.\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"state.db not found\")) return []\n throw err\n }\n\n try {\n // Ground truth beats heuristic: bind to exactly the conversation we\n // were asked about. Binding is by `id`, which is cwd-independent, so\n // this never even looks at `cwd` — never falls through to the cwd-scan\n // below. See the exact-bind invariant in conversation-store.ts.\n if (expectedId) {\n const row = withRetryOnBusy(\n () =>\n db.prepare(\"SELECT * FROM sessions WHERE id = ?\").get(expectedId) as unknown as\n | HermesSessionRow\n | undefined,\n )\n return row ? [hermesRowToCandidate(row)] : []\n }\n\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT * FROM sessions WHERE cwd = ?\")\n .all(cwd) as unknown as HermesSessionRow[],\n )\n const sinceMs = since ? Date.parse(since) : NaN\n return rows\n .filter(row => {\n if (!Number.isFinite(sinceMs)) return true\n const lastActivitySec = row.ended_at ?? row.started_at\n if (lastActivitySec === undefined) return true\n return lastActivitySec * 1000 >= sinceMs - 1000\n })\n .map(hermesRowToCandidate)\n } finally {\n db.close()\n }\n}\n\n// ── Hermes cross-validation via native CLI ────────────────────────────\n//\n// `hermes sessions export --session-id <id> -` emits a stable JSONL\n// transcript on stdout. We use it as a non-blocking sanity-check:\n// if the message count from state.db differs from the binary's output,\n// something in the SQLite schema likely changed and the reader needs\n// updating. The check is fire-and-forget — a mismatch logs a warning but\n// does NOT affect the returned ExportedSession (SQLite stays primary).\n//\n// The export JSONL has one JSON object per message line; lines with a\n// `role` key are treated as messages (other metadata lines are ignored).\n\nexport interface HermesCrossValidationResult {\n sqliteCount: number\n binaryCount: number\n matched: boolean\n /** True when the hermes binary was not found on PATH */\n binaryUnavailable?: boolean\n}\n\n/** Default subprocess runner: spawns `hermes sessions export --session-id <id> -`\n * and returns stdout. Throws with `.code=\"ENOENT\"` when the binary isn't on PATH. */\nasync function defaultHermesRunner(adapterSessionId: string): Promise<string> {\n const { spawn } = await import(\"node:child_process\")\n return new Promise<string>((resolve, reject) => {\n const chunks: Buffer[] = []\n const errChunks: Buffer[] = []\n const proc = spawn(\n \"hermes\",\n [\"sessions\", \"export\", \"--session-id\", adapterSessionId, \"-\"],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n )\n proc.stdout.on(\"data\", (chunk: Buffer) => chunks.push(chunk))\n proc.stderr.on(\"data\", (chunk: Buffer) => errChunks.push(chunk))\n proc.on(\"error\", reject)\n proc.on(\"close\", code => {\n if (code !== 0) {\n reject(\n new Error(\n `hermes exited with code ${code}: ${Buffer.concat(errChunks).toString()}`,\n ),\n )\n } else {\n resolve(Buffer.concat(chunks).toString(\"utf8\"))\n }\n })\n })\n}\n\n/** Run `hermes sessions export` and compare its message count with\n * what our SQLite reader produced. Returns null on any subprocess\n * error (binary missing, timeout, parse failure).\n *\n * The optional `_runner` parameter lets tests inject a mock that returns stdout\n * directly, avoiding subprocess mocking complexity (promisify + child_process). */\nexport async function crossValidateHermesExport(\n adapterSessionId: string,\n sqliteCount: number,\n _runner: (id: string) => Promise<string> = defaultHermesRunner,\n): Promise<HermesCrossValidationResult | null> {\n let stdout: string\n try {\n stdout = await _runner(adapterSessionId)\n } catch (err) {\n const msg = String(err)\n // ENOENT = binary not on PATH — expected when hermes isn't installed\n if (\n (err as NodeJS.ErrnoException).code === \"ENOENT\" ||\n msg.includes(\"ENOENT\") ||\n msg.includes(\"not found\")\n ) {\n return { sqliteCount, binaryCount: -1, matched: true, binaryUnavailable: true }\n }\n // Any other error (timeout, non-zero exit, etc.) — skip validation\n return null\n }\n\n // Count lines that look like message objects (have a `role` field)\n const binaryCount = stdout\n .split(\"\\n\")\n .filter(l => l.trim())\n .filter(l => {\n try {\n const obj = JSON.parse(l) as Record<string, unknown>\n return typeof obj.role === \"string\"\n } catch {\n return false\n }\n }).length\n\n const matched = binaryCount === sqliteCount\n if (!matched) {\n console.warn(\n `[hermes export] cross-validation mismatch for session ${adapterSessionId}: ` +\n `SQLite=${sqliteCount} messages, binary=${binaryCount} messages. ` +\n `The state.db schema may have changed — check the SQLite reader in transcript-export.ts.`,\n )\n }\n return { sqliteCount, binaryCount, matched }\n}\n\n// ── daemon-events exporter (agentproto's own JSONL capture) ────────────\n\n/** Structural shape of one events.jsonl line — a superset of every\n * `AgentStreamEvent` kind (see sessions.ts) plus the writer's own\n * \"user-prompt\" record. Untyped fields default to undefined rather than\n * erroring, so a future field this reader doesn't know about is just\n * ignored instead of breaking the export. */\ninterface TranscriptRecord {\n seq: number\n ts: string\n kind: string\n text?: string\n partial?: boolean\n toolCallId?: string\n toolName?: string\n arguments?: unknown\n result?: unknown\n isError?: boolean\n reason?: string\n error?: { message: string; code?: number }\n entries?: Array<{ content: string; priority: string; status: string }>\n size?: number\n used?: number\n /** `session-message` records: the delivered envelope. */\n message?: {\n id: string\n text?: string\n kind?: string\n from?: { sessionId?: string; label?: string; relation?: string }\n }\n cost?: { amount: number; currency: string }\n // Kind-less CommandLogEntry fields (written by recordCommand — the line\n // has NO `kind` of its own). All optional so every other record shape\n // keeps parsing cleanly; unknown fields are ignored, never throw.\n command?: string\n args?: string[]\n cwd?: string\n exitCode?: number\n signal?: string | null\n durationMs?: number\n stdout?: string\n stderr?: string\n truncated?: boolean\n timedOut?: boolean\n}\n\nexport async function exportDaemonEventsSession(\n sessionId: string,\n desc?: SessionDescriptor,\n): Promise<ExportedSession> {\n const filePath = sessionEventsPath(sessionId)\n\n let stream: ReturnType<typeof createReadStream>\n try {\n stream = createReadStream(filePath, { encoding: \"utf8\" })\n await new Promise<void>((resolve, reject) => {\n stream.once(\"error\", reject)\n stream.once(\"open\", resolve)\n })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n throw new Error(\n `daemon-events: no events.jsonl for session \"${sessionId}\" (${filePath}).\\n` +\n `Either the session never drove an agent-cli turn, or it predates this feature.`,\n )\n }\n throw err\n }\n\n const messages: ExportedMessage[] = []\n const toolNameById = new Map<string, string>()\n let toolCallCount = 0\n let lastUsage: { size?: number; used?: number; cost?: { amount: number; currency: string } } | undefined\n\n // One assistant \"turn batch\" accumulator — text/thought/tool-calls that\n // arrive between two boundary events (a tool-result, a new prompt, a\n // turn-end, ...) collapse into ONE assistant message, mirroring how the\n // claude-code exporter batches a single API-level assistant entry.\n let asmText = \"\"\n let asmReasoning = \"\"\n let asmToolCalls: { name: string; args: string }[] = []\n let asmTs: number | undefined\n\n const flushAssistant = (): void => {\n if (!asmText.trim() && !asmReasoning.trim() && asmToolCalls.length === 0) return\n const m: ExportedMessage = { role: \"assistant\" }\n if (asmText.trim()) m.text = asmText.trim()\n if (asmReasoning.trim()) m.reasoning = asmReasoning.trim()\n if (asmToolCalls.length) m.toolCalls = asmToolCalls\n if (asmTs !== undefined) m.ts = asmTs\n messages.push(m)\n asmText = \"\"\n asmReasoning = \"\"\n asmToolCalls = []\n asmTs = undefined\n }\n\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let rec: TranscriptRecord\n try {\n rec = JSON.parse(trimmed) as TranscriptRecord\n } catch {\n continue\n }\n const ts = Date.parse(rec.ts)\n const tsOrUndefined = Number.isNaN(ts) ? undefined : ts\n\n // A command session (`command_execute` → recordCommand) writes a\n // kind-less CommandLogEntry line as its FIRST line: no `kind` field,\n // just {command, args, cwd, exitCode, signal, durationMs, stdout,\n // stderr, ...}. Detect it structurally and render it as one\n // assistant tool-call + one tool-result pair, so a command session's\n // transcript isn't empty.\n if (rec.kind === undefined && typeof rec.command === \"string\" && typeof rec.exitCode === \"number\") {\n flushAssistant()\n const toolName = \"command_execute\"\n const argsJson = JSON.stringify({\n command: rec.command,\n args: Array.isArray(rec.args) ? rec.args : [],\n ...(rec.cwd !== undefined ? { cwd: rec.cwd } : {}),\n })\n messages.push({\n role: \"assistant\",\n toolCalls: [{ name: toolName, args: argsJson }],\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n const header =\n `exit code ${rec.exitCode}` +\n (rec.signal ? `, signal ${rec.signal}` : \"\") +\n (typeof rec.durationMs === \"number\" ? `, ${rec.durationMs}ms` : \"\")\n const sections: string[] = [header]\n if (rec.stdout) sections.push(`stdout:\\n${rec.stdout}`)\n if (rec.stderr) sections.push(`stderr:\\n${rec.stderr}`)\n if (rec.truncated) sections.push(\"(output truncated)\")\n if (rec.timedOut) sections.push(\"(timed out)\")\n const text = sections.join(\"\\n\")\n messages.push({\n role: \"tool\",\n text: rec.exitCode !== 0 ? `[error] ${text}` : text,\n toolName,\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n toolCallCount += 1\n continue\n }\n\n switch (rec.kind) {\n case \"user-prompt\":\n flushAssistant()\n messages.push({ role: \"user\", text: rec.text ?? \"\", ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}) })\n break\n case \"session-message\": {\n // A typed message from another session — a user-role turn (that's\n // what the model received), but tagged with its attested sender so\n // a reader never attributes it to the human.\n flushAssistant()\n const m = rec.message\n if (!m) break\n const relation = (m.from?.relation ?? \"system\") as NonNullable<ExportedMessage[\"from\"]>[\"relation\"]\n messages.push({\n role: \"user\",\n text: m.text ?? \"\",\n from: {\n ...(m.from?.sessionId ? { sessionId: m.from.sessionId } : {}),\n ...(m.from?.label ? { label: m.from.label } : {}),\n relation,\n kind: m.kind ?? \"report\",\n messageId: m.id,\n },\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n break\n }\n case \"system-prompt\":\n // The daemon-composed SYSTEM slice of a spawned child's initial\n // prompt (role disposition + AGENTS.md + lineage). Recorded apart\n // from the caller's ask so viewers fold it; rendered as a system\n // message (never a user bubble).\n flushAssistant()\n messages.push({ role: \"system\", text: rec.text ?? \"\", ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}) })\n break\n case \"text-delta\":\n // Terminated lines already carry their own trailing \"\\n\" (see\n // transcript-writer.ts) — only an unterminated tail fragment\n // (still `partial`, or flushed bare at a turn boundary) lacks one,\n // and that's exactly the byte-for-byte original content.\n if (asmTs === undefined) asmTs = tsOrUndefined\n asmText += rec.text ?? \"\"\n break\n case \"thought\":\n if (asmTs === undefined) asmTs = tsOrUndefined\n asmReasoning += rec.text ?? \"\"\n break\n case \"tool-call\": {\n if (asmTs === undefined) asmTs = tsOrUndefined\n const name = rec.toolName ?? \"tool\"\n if (rec.toolCallId) toolNameById.set(rec.toolCallId, name)\n const args =\n typeof rec.arguments === \"string\" ? rec.arguments : JSON.stringify(rec.arguments ?? {})\n asmToolCalls.push({ name, args })\n toolCallCount += 1\n break\n }\n case \"tool-result\": {\n flushAssistant()\n const name = rec.toolCallId ? toolNameById.get(rec.toolCallId) : undefined\n const text = typeof rec.result === \"string\" ? rec.result : JSON.stringify(rec.result ?? \"\")\n messages.push({\n role: \"tool\",\n text: rec.isError ? `[error] ${text}` : text,\n ...(name ? { toolName: name } : {}),\n ...(tsOrUndefined !== undefined ? { ts: tsOrUndefined } : {}),\n })\n break\n }\n case \"agent-prompt\":\n flushAssistant()\n messages.push({ role: \"system\", text: \"[awaiting input] agent requested a decision\" })\n break\n case \"plan\": {\n flushAssistant()\n const entries = rec.entries ?? []\n const done = entries.filter(e => e.status === \"completed\").length\n const list = entries.map(e => e.content).join(\"; \")\n messages.push({ role: \"system\", text: `[plan] ${done}/${entries.length} ${list}`.trim() })\n break\n }\n case \"usage_update\":\n lastUsage = { size: rec.size, used: rec.used, cost: rec.cost }\n break\n case \"error\":\n flushAssistant()\n messages.push({ role: \"system\", text: `[error] ${rec.error?.message ?? \"unknown\"}` })\n break\n case \"turn-end\":\n flushAssistant()\n break\n case \"notice\":\n // Daemon-side system notice (e.g. the interrupted-turn resume banner)\n // — surfaced as a system message, same as plan/error notices above.\n flushAssistant()\n messages.push({ role: \"system\", text: rec.text ?? \"\" })\n break\n default:\n // Deliberately NOT handling `kind: \"tool-call-record\"` here: an\n // agent-cli session's events.jsonl contains BOTH the real\n // `tool-call`/`tool-result` events AND a trailing normalized\n // `tool-call-record` line for the SAME call (see\n // tool-call-log.ts) — rendering or counting it would double-count\n // every agent session's tool calls. A command session's\n // `tool-call-record` line is intentionally dropped too; its\n // CommandLogEntry line above already rendered the call.\n break\n }\n }\n flushAssistant()\n\n const meta: ExportedSessionMeta = { source: \"daemon-events\" }\n // Title chain: spawner label, else the command itself (a command session\n // never gets a label from command_execute, but its descriptor carries the\n // quoted argv) — truncated so a giant argv doesn't become an H1.\n // renderMarkdown's `(untitled)` remains the final fallback.\n if (desc?.label) meta.title = desc.label\n else if (desc?.command) {\n meta.title = desc.command.length > 120 ? `${desc.command.slice(0, 120)}…` : desc.command\n }\n if (desc?.model) meta.model = desc.model\n if (desc?.startedAt) meta.startedAt = desc.startedAt\n if (desc?.endedAt) meta.endedAt = desc.endedAt\n if (desc?.origin) meta.origin = desc.origin\n if (desc?.callerSessionId) meta.callerSessionId = desc.callerSessionId\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n if (desc?.costUsd !== undefined) meta.costUsd = desc.costUsd\n else if (lastUsage?.cost) meta.costUsd = lastUsage.cost.amount\n if (desc?.tokensIn !== undefined || desc?.tokensOut !== undefined) {\n meta.tokens = {\n ...(desc?.tokensIn !== undefined ? { input: desc.tokensIn } : {}),\n ...(desc?.tokensOut !== undefined ? { output: desc.tokensOut } : {}),\n }\n }\n\n return { meta, messages }\n}\n\n// ── shared jsonl helper ───────────────────────────────────────────────\n\n/** Read only the first non-empty line of a jsonl file — used by the codex\n * and pi discovery scans, which only need each file's leading metadata\n * record (session_meta / session) to answer cwd/id questions without\n * parsing a possibly-multi-MB transcript. */\nasync function readFirstJsonLine(path: string): Promise<string | undefined> {\n const stream = createReadStream(path, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n try {\n for await (const line of rl) {\n const t = line.trim()\n if (t) return t\n }\n return undefined\n } finally {\n rl.close()\n stream.destroy()\n }\n}\n\n// ── codex exporter (rollout JSONL) ────────────────────────────────────\n//\n// Native store: $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl\n// (CODEX_HOME defaults to ~/.codex). One append-only JSONL file per\n// conversation, written by the Codex core that @agentclientprotocol/codex-acp\n// bundles. The first line is a `session_meta` record carrying the\n// conversation's `id` (== the trailing uuid in the filename == the ACP\n// session id agentproto records as `adapterSessionId` — verified\n// empirically against live rollouts) and its `cwd`. Subsequent\n// `response_item` lines carry the turns; `event_msg` / `turn_context`\n// lines are transport bookkeeping we skip.\n\nfunction codexHome(): string {\n return process.env.CODEX_HOME ?? join(homedir(), \".codex\")\n}\nfunction codexSessionsDir(): string {\n return join(codexHome(), \"sessions\")\n}\n\ninterface CodexRolloutLine {\n type?: string\n timestamp?: string\n payload?: {\n type?: string\n id?: string\n session_id?: string\n timestamp?: string\n cwd?: string\n model?: string\n model_provider?: string\n role?: string\n content?: Array<{ type?: string; text?: string }>\n summary?: Array<{ type?: string; text?: string }>\n name?: string\n input?: unknown\n arguments?: unknown\n call_id?: string\n output?: unknown\n }\n}\n\n/** Recursively collect `rollout-*.jsonl` paths under the YYYY/MM/DD tree. */\nasync function walkCodexRollouts(dir: string): Promise<string[]> {\n let entries: Dirent[]\n try {\n entries = await fs.readdir(dir, { withFileTypes: true })\n } catch {\n return []\n }\n const out: string[] = []\n for (const e of entries) {\n const p = join(dir, e.name)\n if (e.isDirectory()) {\n out.push(...(await walkCodexRollouts(p)))\n } else if (e.isFile() && e.name.startsWith(\"rollout-\") && e.name.endsWith(\".jsonl\")) {\n out.push(p)\n }\n }\n return out\n}\n\n/** Join codex output blocks (input_text / output_text) into one string. */\nfunction codexBlocksText(content: unknown): string {\n if (typeof content === \"string\") return content\n if (!Array.isArray(content)) return \"\"\n let acc = \"\"\n for (const c of content) {\n if (c && typeof c === \"object\") {\n const b = c as { type?: string; text?: string }\n if (typeof b.text === \"string\" && (b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"text\")) {\n acc += b.text\n }\n }\n }\n return acc\n}\n\nasync function findCodexRolloutFile(conversationId: string): Promise<string | undefined> {\n const files = await walkCodexRollouts(codexSessionsDir())\n // Fast path: the uuid is the filename suffix.\n const byName = files.find(f => f.endsWith(`-${conversationId}.jsonl`))\n if (byName) return byName\n // Fallback: match on the session_meta id/session_id (covers a future\n // codex build whose filename uuid diverges from the meta id).\n for (const f of files) {\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: CodexRolloutLine\n try {\n meta = JSON.parse(first) as CodexRolloutLine\n } catch {\n continue\n }\n const p = meta.payload\n if (p && (p.id === conversationId || p.session_id === conversationId)) return f\n }\n return undefined\n}\n\nexport async function exportCodexSession(conversationId: string): Promise<ExportedSession> {\n const file = await findCodexRolloutFile(conversationId)\n if (!file) {\n throw new Error(\n `codex: no rollout file for conversation \"${conversationId}\" under ${codexSessionsDir()}.\\n` +\n `The session may predate persistence, or CODEX_HOME points elsewhere.`,\n )\n }\n\n const stream = createReadStream(file, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n const messages: ExportedMessage[] = []\n const toolNameByCall = new Map<string, string>()\n let toolCallCount = 0\n let startedAt: string | undefined\n let model: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: CodexRolloutLine\n try {\n entry = JSON.parse(trimmed) as CodexRolloutLine\n } catch {\n continue\n }\n const p = entry.payload ?? {}\n\n if (entry.type === \"session_meta\") {\n startedAt = p.timestamp ?? entry.timestamp\n if (typeof p.model === \"string\") model = p.model\n continue\n }\n if (entry.type === \"turn_context\") {\n if (!model && typeof p.model === \"string\") model = p.model\n continue\n }\n if (entry.type !== \"response_item\") continue\n\n switch (p.type) {\n case \"message\": {\n const text = codexBlocksText(p.content).trim()\n if (!text) break\n const role: ExportedMessage[\"role\"] =\n p.role === \"assistant\" ? \"assistant\" : p.role === \"user\" ? \"user\" : \"system\"\n messages.push({ role, text })\n break\n }\n case \"reasoning\": {\n // Codex reasoning is usually an opaque `encrypted_content` blob; only\n // an unencrypted `summary` (rare) is human-readable. Emit only when\n // there's real text — never the ciphertext.\n const r = (p.summary ?? []).map(s => s.text ?? \"\").join(\"\\n\").trim()\n if (r) messages.push({ role: \"assistant\", reasoning: r })\n break\n }\n case \"function_call\":\n case \"custom_tool_call\": {\n const name = p.name ?? \"tool\"\n if (typeof p.call_id === \"string\") toolNameByCall.set(p.call_id, name)\n const raw = p.type === \"custom_tool_call\" ? p.input : p.arguments\n const args = typeof raw === \"string\" ? raw : JSON.stringify(raw ?? {})\n messages.push({ role: \"assistant\", toolCalls: [{ name, args }] })\n toolCallCount += 1\n break\n }\n case \"function_call_output\":\n case \"custom_tool_call_output\": {\n const name = typeof p.call_id === \"string\" ? toolNameByCall.get(p.call_id) : undefined\n const out = codexBlocksText(p.output) || (typeof p.output === \"string\" ? p.output : \"\")\n messages.push({ role: \"tool\", text: out, ...(name ? { toolName: name } : {}) })\n break\n }\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"codex\" }\n if (startedAt) meta.startedAt = startedAt\n if (model) meta.model = model\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n}\n\nexport async function discoverCodexSessions(\n cwd: string,\n since?: string,\n until?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const files = await walkCodexRollouts(codexSessionsDir())\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n\n for (const f of files) {\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(f)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: CodexRolloutLine\n try {\n meta = JSON.parse(first) as CodexRolloutLine\n } catch {\n continue\n }\n if (meta.type !== \"session_meta\") continue\n const p = meta.payload ?? {}\n const id = p.id ?? p.session_id\n if (!id) continue\n // Ground truth beats heuristic: bind to exactly the requested id and\n // ignore cwd (the id is globally unique). See the exact-bind invariant.\n if (expectedId) {\n if (id !== expectedId) continue\n } else if (p.cwd !== cwd) {\n continue\n }\n const startedAt = p.timestamp\n if (Number.isFinite(untilMs) && startedAt) {\n const startedMs = Date.parse(startedAt)\n if (Number.isFinite(startedMs) && startedMs > untilMs) continue\n }\n scored.push({\n candidate: {\n conversationId: id,\n ...(startedAt ? { startedAt } : {}),\n lastActivityAt: new Date(mtimeMs).toISOString(),\n },\n mtimeMs,\n })\n }\n\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\n// ── opencode exporter (opencode.db SQLite) ────────────────────────────\n//\n// Native store: ${XDG_DATA_HOME:-~/.local/share}/opencode/opencode.db — a\n// drizzle-managed sqlite with `session` (keyed by the `ses_…` id opencode\n// returns over ACP, which agentproto records verbatim as adapterSessionId\n// — verified empirically), `message` (JSON `data` per row), and `part`\n// (JSON `data`, one row per text/reasoning/tool block). The `session`\n// table's `directory` column is the conversation's cwd, enabling\n// cwd-scoped discovery.\n\ninterface OpenCodeSessionRow {\n id: string\n directory?: string\n title?: string\n model?: string\n cost?: number\n tokens_input?: number\n tokens_output?: number\n tokens_reasoning?: number\n tokens_cache_read?: number\n tokens_cache_write?: number\n time_created?: number\n time_updated?: number\n}\ninterface OpenCodeMessageRow {\n id: string\n data: string\n time_created: number\n}\ninterface OpenCodePartRow {\n message_id: string\n data: string\n time_created: number\n}\n\nfunction openCodeDbPath(): string {\n const dataHome = process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\")\n return join(dataHome, \"opencode\", \"opencode.db\")\n}\n\n/** `{\"id\":\"gpt-5-mini\",\"providerID\":\"openai\",…}` → `openai/gpt-5-mini`. */\nfunction openCodeModelLabel(raw: string | undefined): string | undefined {\n if (!raw) return undefined\n try {\n const m = JSON.parse(raw) as { id?: string; modelID?: string; providerID?: string }\n const id = m.modelID ?? m.id\n if (!id) return undefined\n return m.providerID ? `${m.providerID}/${id}` : id\n } catch {\n return undefined\n }\n}\n\nexport async function exportOpenCodeSession(conversationId: string): Promise<ExportedSession> {\n const dbPath = openCodeDbPath()\n const db = await openReadonlySqlite(dbPath, \"opencode\")\n try {\n const session = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE id = ?\").get(conversationId) as unknown as\n | OpenCodeSessionRow\n | undefined,\n )\n if (!session) {\n throw new Error(\n `opencode: session \"${conversationId}\" not found in ${dbPath}. ` +\n `Sessions are keyed by the ACP session id (ses_…) recorded as adapterSessionId.`,\n )\n }\n const msgRows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT id, data, time_created FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC\")\n .all(conversationId) as unknown as OpenCodeMessageRow[],\n )\n const partRows = withRetryOnBusy(\n () =>\n db\n .prepare(\"SELECT message_id, data, time_created FROM part WHERE session_id = ? ORDER BY time_created ASC, id ASC\")\n .all(conversationId) as unknown as OpenCodePartRow[],\n )\n\n const partsByMsg = new Map<string, OpenCodePartRow[]>()\n for (const r of partRows) {\n const list = partsByMsg.get(r.message_id) ?? []\n list.push(r)\n partsByMsg.set(r.message_id, list)\n }\n\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n for (const m of msgRows) {\n let mdata: { role?: string }\n try {\n mdata = JSON.parse(m.data) as { role?: string }\n } catch {\n continue\n }\n const role: ExportedMessage[\"role\"] = mdata.role === \"assistant\" ? \"assistant\" : \"user\"\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n const toolResults: { name?: string; text: string }[] = []\n for (const pr of partsByMsg.get(m.id) ?? []) {\n let part: {\n type?: string\n text?: string\n tool?: string\n state?: { input?: unknown; output?: unknown }\n }\n try {\n part = JSON.parse(pr.data) as typeof part\n } catch {\n continue\n }\n switch (part.type) {\n case \"text\":\n if (part.text) text += part.text\n break\n case \"reasoning\":\n if (part.text) reasoning += part.text\n break\n case \"tool\": {\n const name = part.tool ?? \"tool\"\n toolCalls.push({ name, args: JSON.stringify(part.state?.input ?? {}) })\n toolCallCount += 1\n const output = part.state?.output\n if (output !== undefined && output !== null && output !== \"\") {\n toolResults.push({ name, text: typeof output === \"string\" ? output : JSON.stringify(output) })\n }\n break\n }\n // step-start / step-finish / patch / snapshot / file — skip.\n }\n }\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n const em: ExportedMessage = { role }\n if (text.trim()) em.text = text.trim()\n if (reasoning.trim()) em.reasoning = reasoning.trim()\n if (toolCalls.length) em.toolCalls = toolCalls\n em.ts = m.time_created\n messages.push(em)\n }\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text, ...(tr.name ? { toolName: tr.name } : {}) })\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"opencode\" }\n if (session.title) meta.title = session.title\n const model = openCodeModelLabel(session.model)\n if (model) meta.model = model\n if (session.time_created) meta.startedAt = new Date(session.time_created).toISOString()\n if (session.time_updated) meta.endedAt = new Date(session.time_updated).toISOString()\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n if (session.cost != null) meta.costUsd = Number(session.cost)\n const tk = {\n ...(session.tokens_input != null ? { input: session.tokens_input } : {}),\n ...(session.tokens_output != null ? { output: session.tokens_output } : {}),\n ...(session.tokens_cache_read != null ? { cacheRead: session.tokens_cache_read } : {}),\n ...(session.tokens_cache_write != null ? { cacheWrite: session.tokens_cache_write } : {}),\n ...(session.tokens_reasoning != null ? { reasoning: session.tokens_reasoning } : {}),\n }\n if (Object.keys(tk).length) meta.tokens = tk\n return { meta, messages }\n } finally {\n db.close()\n }\n}\n\nexport async function discoverOpenCodeSessions(\n cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = openCodeDbPath()\n let db: HermesDb\n try {\n db = await openReadonlySqlite(dbPath, \"opencode\")\n } catch (err) {\n // A missing db == opencode never ran here — a normal empty store, not a\n // discovery error (mirrors discoverHermesSessions).\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"database not found\")) return []\n throw err\n }\n try {\n if (expectedId) {\n const row = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE id = ?\").get(expectedId) as unknown as\n | OpenCodeSessionRow\n | undefined,\n )\n return row ? [openCodeRowToCandidate(row)] : []\n }\n const rows = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM session WHERE directory = ?\").all(cwd) as unknown as OpenCodeSessionRow[],\n )\n const sinceMs = since ? Date.parse(since) : NaN\n return rows\n .filter(r => {\n if (!Number.isFinite(sinceMs)) return true\n // Key off the conversation's START (time_created), never\n // time_updated — an OLD conversation whose time_updated moves at/\n // after the window boundary must not pass on that alone (#mis-bind).\n // A row with no time_created at all has an unprovable start and is\n // excluded rather than assumed to be in-window.\n if (r.time_created === undefined) return false\n return r.time_created >= sinceMs - 1000\n })\n .map(openCodeRowToCandidate)\n } finally {\n db.close()\n }\n}\n\nfunction openCodeRowToCandidate(row: OpenCodeSessionRow): ConversationCandidate {\n return {\n conversationId: row.id,\n ...(row.time_created ? { startedAt: new Date(row.time_created).toISOString() } : {}),\n ...(row.time_updated ? { lastActivityAt: new Date(row.time_updated).toISOString() } : {}),\n ...(row.title ? { preview: row.title } : {}),\n }\n}\n\n// ── mastracode-inprocess exporter (Mastra libsql) ─────────────────────\n//\n// Native store: ${AGENTPROTO_HOME:-~/.agentproto}/mastracode-inprocess/\n// storage.db — the dedicated Mastra libsql db the in-process arm writes\n// (adapters/mastracode-inprocess/src/client.ts). The arm's public\n// sessionId is the composite \"<resourceId>:<threadId>\"; agentproto records\n// it verbatim as adapterSessionId. Threads live in `mastra_threads` (keyed\n// by threadId) and messages in `mastra_messages` (keyed by `thread_id`,\n// each `content` a Mastra v2 `{format,parts}` JSON). The store has no cwd\n// column, so cwd-scoped discovery is not possible — but this in-process arm\n// always yields an adapterSessionId, so the exact-bind path is the only one\n// that ever runs in practice.\n\ninterface MastraThreadRow {\n id: string\n resourceId?: string\n title?: string\n createdAt?: string\n updatedAt?: string\n}\ninterface MastraMessageRow {\n role: string\n content: string\n createdAt: string\n}\n\nfunction mastracodeInprocessDbPath(): string {\n const home = process.env.AGENTPROTO_HOME ?? join(homedir(), \".agentproto\")\n return join(home, \"mastracode-inprocess\", \"storage.db\")\n}\n\n/** Split \"<resourceId>:<threadId>\"; tolerate a bare threadId (no colon). */\nfunction mastraThreadId(conversationId: string): string {\n const idx = conversationId.indexOf(\":\")\n return idx > 0 ? conversationId.slice(idx + 1) : conversationId\n}\n\n/** Pull user/assistant text, reasoning, and tool calls/results out of one\n * Mastra v2 `{format,parts}` content blob. */\nfunction mastraContentToMessage(\n role: ExportedMessage[\"role\"],\n content: string,\n): { message?: ExportedMessage; toolResults: { name?: string; text: string }[]; toolCalls: number } {\n const toolResults: { name?: string; text: string }[] = []\n let parsed: { parts?: unknown[] }\n try {\n parsed = JSON.parse(content) as { parts?: unknown[] }\n } catch {\n return { message: undefined, toolResults, toolCalls: 0 }\n }\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n for (const raw of parsed.parts ?? []) {\n if (!raw || typeof raw !== \"object\") continue\n const part = raw as {\n type?: string\n text?: string\n reasoning?: string\n toolInvocation?: { toolName?: string; args?: unknown; result?: unknown }\n }\n if (part.type === \"text\" && part.text) text += part.text\n else if (part.type === \"reasoning\" && part.reasoning) reasoning += part.reasoning\n else if (part.type === \"tool-invocation\" && part.toolInvocation) {\n const inv = part.toolInvocation\n const name = inv.toolName ?? \"tool\"\n toolCalls.push({ name, args: JSON.stringify(inv.args ?? {}) })\n if (inv.result !== undefined) {\n const r = inv.result as { content?: Array<{ type?: string; text?: string }> }\n const rtext = Array.isArray(r?.content)\n ? r.content.filter(c => c?.type === \"text\").map(c => c.text ?? \"\").join(\"\")\n : typeof inv.result === \"string\"\n ? inv.result\n : JSON.stringify(inv.result)\n toolResults.push({ name, text: rtext })\n }\n }\n // data-* and step-start parts are internal bookkeeping — skip.\n }\n let message: ExportedMessage | undefined\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n message = { role }\n if (text.trim()) message.text = text.trim()\n if (reasoning.trim()) message.reasoning = reasoning.trim()\n if (toolCalls.length) message.toolCalls = toolCalls\n }\n return { message, toolResults, toolCalls: toolCalls.length }\n}\n\nexport async function exportMastracodeInprocessSession(conversationId: string): Promise<ExportedSession> {\n const threadId = mastraThreadId(conversationId)\n const dbPath = mastracodeInprocessDbPath()\n const db = await openReadonlySqlite(dbPath, \"mastracode-inprocess\")\n try {\n const thread = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM mastra_threads WHERE id = ?\").get(threadId) as unknown as\n | MastraThreadRow\n | undefined,\n )\n if (!thread) {\n throw new Error(\n `mastracode-inprocess: thread \"${threadId}\" not found in ${dbPath}. ` +\n `The sessionId is \"<resourceId>:<threadId>\"; only the threadId is looked up.`,\n )\n }\n const rows = withRetryOnBusy(\n () =>\n db\n .prepare('SELECT role, content, createdAt FROM mastra_messages WHERE thread_id = ? ORDER BY \"createdAt\" ASC')\n .all(threadId) as unknown as MastraMessageRow[],\n )\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n for (const r of rows) {\n // Mastra tags user turns with role \"signal\"; assistant with \"assistant\".\n const role: ExportedMessage[\"role\"] = r.role === \"assistant\" ? \"assistant\" : \"user\"\n const { message, toolResults, toolCalls } = mastraContentToMessage(role, r.content)\n if (message) messages.push(message)\n toolCallCount += toolCalls\n for (const tr of toolResults) {\n messages.push({ role: \"tool\", text: tr.text, ...(tr.name ? { toolName: tr.name } : {}) })\n }\n }\n const meta: ExportedSessionMeta = { source: \"mastracode-inprocess\" }\n if (thread.title) meta.title = thread.title\n if (thread.createdAt) meta.startedAt = thread.createdAt\n if (thread.updatedAt) meta.endedAt = thread.updatedAt\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n } finally {\n db.close()\n }\n}\n\nexport async function discoverMastracodeInprocessSessions(\n _cwd: string,\n since?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const dbPath = mastracodeInprocessDbPath()\n let db: HermesDb\n try {\n db = await openReadonlySqlite(dbPath, \"mastracode-inprocess\")\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes(\"database not found\")) return []\n throw err\n }\n try {\n // Exact-bind only: `mastra_threads` has no cwd column, so a cwd-scoped\n // scan is impossible. Absent an expectedId there is nothing to safely\n // scope to, so return none (this in-process arm always supplies an\n // adapterSessionId, so the exact-bind path is the real one).\n if (!expectedId) return []\n const threadId = mastraThreadId(expectedId)\n const thread = withRetryOnBusy(\n () => db.prepare(\"SELECT * FROM mastra_threads WHERE id = ?\").get(threadId) as unknown as\n | MastraThreadRow\n | undefined,\n )\n if (!thread) return []\n const sinceMs = since ? Date.parse(since) : NaN\n if (Number.isFinite(sinceMs) && thread.updatedAt) {\n const upd = Date.parse(thread.updatedAt)\n if (Number.isFinite(upd) && upd < sinceMs - 1000) return []\n }\n return [\n {\n // Echo back the full composite id so callers/read() round-trip it.\n conversationId: expectedId,\n ...(thread.createdAt ? { startedAt: thread.createdAt } : {}),\n ...(thread.updatedAt ? { lastActivityAt: thread.updatedAt } : {}),\n ...(thread.title ? { preview: thread.title } : {}),\n },\n ]\n } finally {\n db.close()\n }\n}\n\n// ── pi exporter (JSON-over-stdio session log) ─────────────────────────\n//\n// Native store: ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl — pi\n// writes one append-only JSONL per conversation. The first line is a\n// `session` record carrying the uuid `id` (== pi's RPC `sessionId`, which\n// agentproto records as adapterSessionId — pi's client captures it from\n// get_state) and the `cwd`. `message` lines carry the turns; the\n// cwd-slug directory encoding is version-dependent, so discovery scans and\n// keys off each file's authoritative `session.cwd` rather than the slug.\n\nfunction piSessionsDir(): string {\n return join(homedir(), \".pi\", \"agent\", \"sessions\")\n}\n\ninterface PiLine {\n type?: string\n id?: string\n cwd?: string\n timestamp?: string\n provider?: string\n modelId?: string\n message?: {\n role?: string\n toolName?: string\n isError?: boolean\n content?: unknown\n }\n}\n\n/** Text of pi content blocks (`{type:\"text\",text}`), string-tolerant. */\nfunction piBlocksText(content: unknown): string {\n if (typeof content === \"string\") return content\n if (!Array.isArray(content)) return \"\"\n let acc = \"\"\n for (const c of content) {\n if (c && typeof c === \"object\") {\n const b = c as { type?: string; text?: string }\n if (b.type === \"text\" && typeof b.text === \"string\") acc += b.text\n }\n }\n return acc\n}\n\nasync function walkPiSessionFiles(dir: string): Promise<string[]> {\n let entries: Dirent[]\n try {\n entries = await fs.readdir(dir, { withFileTypes: true })\n } catch {\n return []\n }\n const out: string[] = []\n for (const e of entries) {\n const p = join(dir, e.name)\n if (e.isDirectory()) out.push(...(await walkPiSessionFiles(p)))\n else if (e.isFile() && e.name.endsWith(\".jsonl\")) out.push(p)\n }\n return out\n}\n\nasync function findPiSessionFile(conversationId: string): Promise<string | undefined> {\n const files = await walkPiSessionFiles(piSessionsDir())\n const byName = files.find(f => f.endsWith(`_${conversationId}.jsonl`))\n if (byName) return byName\n for (const f of files) {\n const first = await readFirstJsonLine(f)\n if (!first) continue\n try {\n const meta = JSON.parse(first) as PiLine\n if (meta.type === \"session\" && meta.id === conversationId) return f\n } catch {\n // skip\n }\n }\n return undefined\n}\n\nexport async function exportPiSession(conversationId: string): Promise<ExportedSession> {\n const file = await findPiSessionFile(conversationId)\n if (!file) {\n throw new Error(\n `pi: no session file for conversation \"${conversationId}\" under ${piSessionsDir()}.`,\n )\n }\n const stream = createReadStream(file, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n const messages: ExportedMessage[] = []\n let toolCallCount = 0\n let startedAt: string | undefined\n let model: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: PiLine\n try {\n entry = JSON.parse(trimmed) as PiLine\n } catch {\n continue\n }\n if (entry.type === \"session\") {\n startedAt = entry.timestamp\n continue\n }\n if (entry.type === \"model_change\") {\n model = entry.provider ? `${entry.provider}/${entry.modelId ?? \"\"}` : entry.modelId\n continue\n }\n if (entry.type !== \"message\" || !entry.message) continue\n const m = entry.message\n const content = m.content\n\n if (m.role === \"toolResult\") {\n const text = piBlocksText(content)\n messages.push({\n role: \"tool\",\n text: m.isError ? `[error] ${text}` : text,\n ...(m.toolName ? { toolName: m.toolName } : {}),\n })\n continue\n }\n\n const role: ExportedMessage[\"role\"] = m.role === \"assistant\" ? \"assistant\" : \"user\"\n let text = \"\"\n let reasoning = \"\"\n const toolCalls: { name: string; args: string }[] = []\n if (typeof content === \"string\") {\n text = content\n } else if (Array.isArray(content)) {\n for (const raw of content) {\n if (!raw || typeof raw !== \"object\") continue\n const part = raw as {\n type?: string\n text?: string\n thinking?: string\n name?: string\n arguments?: unknown\n }\n if (part.type === \"text\" && part.text) text += part.text\n else if (part.type === \"thinking\" && part.thinking) reasoning += part.thinking\n else if (part.type === \"toolCall\") {\n toolCalls.push({ name: part.name ?? \"tool\", args: JSON.stringify(part.arguments ?? {}) })\n toolCallCount += 1\n }\n }\n }\n if (text.trim() || reasoning.trim() || toolCalls.length) {\n const em: ExportedMessage = { role }\n if (text.trim()) em.text = text.trim()\n if (reasoning.trim()) em.reasoning = reasoning.trim()\n if (toolCalls.length) em.toolCalls = toolCalls\n messages.push(em)\n }\n }\n\n const meta: ExportedSessionMeta = { source: \"pi\" }\n if (startedAt) meta.startedAt = startedAt\n if (model) meta.model = model\n meta.messageCount = messages.length\n meta.toolCallCount = toolCallCount\n return { meta, messages }\n}\n\nexport async function discoverPiSessions(\n cwd: string,\n since?: string,\n until?: string,\n expectedId?: string,\n): Promise<ConversationCandidate[]> {\n const files = await walkPiSessionFiles(piSessionsDir())\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n\n for (const f of files) {\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(f)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const first = await readFirstJsonLine(f)\n if (!first) continue\n let meta: PiLine\n try {\n meta = JSON.parse(first) as PiLine\n } catch {\n continue\n }\n if (meta.type !== \"session\" || !meta.id) continue\n if (expectedId) {\n if (meta.id !== expectedId) continue\n } else if (meta.cwd !== cwd) {\n continue\n }\n const startedAt = meta.timestamp\n if (Number.isFinite(untilMs) && startedAt) {\n const s = Date.parse(startedAt)\n if (Number.isFinite(s) && s > untilMs) continue\n }\n scored.push({\n candidate: {\n conversationId: meta.id,\n ...(startedAt ? { startedAt } : {}),\n lastActivityAt: new Date(mtimeMs).toISOString(),\n },\n mtimeMs,\n })\n }\n\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\n// ── Strategy registry ─────────────────────────────────────────────────\n\nconst EXPORT_STRATEGIES: Record<string, ExportStrategy> = {\n \"claude-code\": {\n exportSession: exportClaudeCodeSession,\n },\n hermes: {\n exportSession: (id: string) => exportHermesSession(id),\n },\n codex: {\n exportSession: (id: string) => exportCodexSession(id),\n },\n opencode: {\n exportSession: (id: string) => exportOpenCodeSession(id),\n },\n \"mastracode-inprocess\": {\n exportSession: (id: string) => exportMastracodeInprocessSession(id),\n },\n pi: {\n exportSession: (id: string) => exportPiSession(id),\n },\n}\n\n// ── Public API ────────────────────────────────────────────────────────\n\nexport type ExportSource = \"auto\" | \"native\" | \"daemon\"\n\nexport interface ExportAgentSessionInput {\n /** agentproto session id (sess_xxx) or an adapter-native id */\n sessionId: string\n registry: SessionsRegistry\n format?: \"markdown\" | \"json\"\n maxToolChars?: number\n /** Override adapter slug when sessionId is an adapter-native id */\n adapter?: string\n /** Override cwd when sessionId is an adapter-native id (claude-code only) */\n cwd?: string\n /** Which backend to read from. \"auto\" (default) prefers the adapter's\n * own native store and falls back to agentproto's own events.jsonl\n * capture when there isn't one (or it can't be read); \"native\" / \"daemon\"\n * force one and surface its own error instead of falling back. */\n source?: ExportSource\n}\n\nexport interface ExportAgentSessionResult {\n sessionId: string\n adapter: string\n format: string\n meta: ExportedSessionMeta\n content: string\n}\n\nexport async function exportAgentSession(\n input: ExportAgentSessionInput,\n): Promise<ExportAgentSessionResult> {\n const { sessionId, registry, format = \"markdown\", maxToolChars = 1200 } = input\n const source = input.source ?? \"auto\"\n\n // Resolve via registry first. `err` closes over `adapterSlug` so it\n // returns the resolved slug (not the raw input) after registry lookup.\n let adapterSlug = input.adapter\n let cwd = input.cwd\n let adapterSessionId = sessionId\n let configDir: string | undefined\n\n const err = (msg: string): ExportAgentSessionResult => ({\n sessionId,\n adapter: adapterSlug ?? \"unknown\",\n format,\n meta: {},\n content: `Error: ${msg}`,\n })\n\n const desc = registry.findByIdOrName(sessionId)\n // events.jsonl is always keyed by the agentproto session id — never the\n // adapter-native id substituted below for the native strategies.\n const daemonSessionId = desc?.id ?? sessionId\n if (desc) {\n adapterSlug = adapterSlug ?? desc.adapterSlug\n cwd = cwd ?? desc.cwd\n // A config-dir-isolated session's transcripts live under its own\n // CLAUDE_CONFIG_DIR, not ~/.claude (#824) — thread it to the exporter.\n configDir = desc.adapterConfigDir\n // prefer the adapter-native id over the agentproto id\n if (desc.adapterSessionId) adapterSessionId = desc.adapterSessionId\n }\n\n const tryNative = async (): Promise<ExportedSession> => {\n if (!adapterSlug) {\n throw new Error(\n `session \"${sessionId}\" not found in registry and no adapter override supplied.\\n` +\n `Pass adapter explicitly or use a known session id (sess_xxx or name).`,\n )\n }\n const exporter = EXPORT_STRATEGIES[adapterSlug]\n if (!exporter) {\n const supported = Object.keys(EXPORT_STRATEGIES).join(\", \")\n throw new Error(\n `no exporter for adapter \"${adapterSlug}\". Supported: ${supported}.\\n` +\n `Only sessions spawned via claude-code or hermes can be exported.`,\n )\n }\n return exporter.exportSession(adapterSessionId, cwd, configDir)\n }\n\n const tryDaemon = (): Promise<ExportedSession> => exportDaemonEventsSession(daemonSessionId, desc)\n\n let session: ExportedSession\n try {\n if (source === \"native\") {\n session = await tryNative()\n } else if (source === \"daemon\") {\n session = await tryDaemon()\n } else {\n try {\n session = await tryNative()\n } catch (nativeErr) {\n try {\n session = await tryDaemon()\n } catch (daemonErr) {\n const nativeMsg = nativeErr instanceof Error ? nativeErr.message : String(nativeErr)\n const daemonMsg = daemonErr instanceof Error ? daemonErr.message : String(daemonErr)\n return err(`${nativeMsg}\\n(daemon-events fallback also failed: ${daemonMsg})`)\n }\n }\n }\n } catch (e) {\n return err(e instanceof Error ? e.message : String(e))\n }\n\n const content =\n format === \"json\"\n ? renderJson(session)\n : renderMarkdown(session, { maxToolChars })\n\n return {\n sessionId,\n adapter: adapterSlug ?? (session.meta.source === \"daemon-events\" ? \"daemon\" : \"unknown\"),\n format,\n meta: session.meta,\n content,\n }\n}\n","/**\n * Conversation as pivot: one store abstraction per provider, read on any\n * session. A \"conversation\" is the provider's own persisted session — the\n * durable object. An agentproto session (ACP or PTY) is just an attachment\n * to it. `CONVERSATION_STORES` is the single source of truth for \"where does\n * this provider keep its conversations, and how do I read/attach to one\" —\n * `RESUME_STRATEGIES` (resume-strategies.ts) and `EXPORT_STRATEGIES`\n * (transcript-export.ts) are thin adapters over it so the two historically\n * drifted tables can never diverge again.\n */\n\nimport { promises as fs, createReadStream } from \"node:fs\"\nimport { homedir } from \"node:os\"\nimport { createInterface } from \"node:readline\"\nimport { join, resolve } from \"node:path\"\nimport type { ExportedSession, ExportedMessage } from \"./transcript-export.js\"\n\n/** Where a captured conversation id is recorded on the descriptor.\n * Same union as resume-strategies.ts's ResumeMetadataKey — re-exported\n * from there, NOT redefined, so the two can never drift. */\nexport type { ResumeMetadataKey } from \"./resume-strategies.js\"\nimport type { ResumeMetadataKey } from \"./resume-strategies.js\"\n\n/** One conversation found in a provider's native store, plus enough\n * evidence for a caller (or a human) to pick between several. */\nexport interface ConversationCandidate {\n /** The provider-native conversation id (claude: the jsonl uuid). */\n conversationId: string\n startedAt?: string\n lastActivityAt?: string\n messageCount?: number\n /** First user message, truncated to ~120 chars. Disambiguation aid. */\n preview?: string\n /** Which attachment wrote the tail, provider-native vocabulary\n * (claude-code: \"sdk-ts\" = ACP arm, \"cli\" = native TUI). */\n lastWriter?: string\n}\n\nexport interface DiscoverInput {\n cwd: string\n /** Only consider conversations active at-or-after this ISO ts. */\n since?: string\n /** Only consider conversations that STARTED at-or-before this ISO ts —\n * an upper bound, typically a dead session's `endedAt`. A conversation\n * whose own first message postdates it provably belongs to some other,\n * later session. Omit for a live/ongoing session (no `endedAt` yet) —\n * it must keep matching conversations that started after `since` with\n * no upper bound. Conservative: a candidate with no discoverable\n * `startedAt` is never dropped by this filter. */\n until?: string\n /** The requesting session's attachment mode, in ABSTRACT (provider-\n * agnostic) vocabulary — `\"native\"` for a real PTY/TUI attach,\n * `\"acp\"` for the agent-cli/ACP arm. When set, drops candidates whose\n * `lastWriter` is present and maps to the OPPOSITE mode (a native PTY's\n * conversation is never written by the ACP arm, and vice versa).\n * Conservative: a candidate with no `lastWriter` is never dropped, and\n * omitting this field applies no mode filtering at all. */\n attachmentMode?: \"native\" | \"acp\"\n /** The session's isolated provider config dir (`SessionDescriptor.\n * adapterConfigDir` — the `CLAUDE_CONFIG_DIR` the daemon spawns\n * claude-code with since the #824 MCP-isolation fix). When set, the\n * provider wrote its transcripts under THIS dir, not its global\n * default (`~/.claude`), so discovery must look there. Omit for a\n * native PTY / pre-#824 session — the global store applies. Only\n * claude-code keys its store off a config dir; other stores ignore\n * this field. */\n configDir?: string\n /** When set, bind to EXACTLY this conversation or return [].\n * MUST NOT fall back to a recency guess — see invariants. */\n expectedId?: string\n}\n\nexport interface ConversationStore {\n storeAs: ResumeMetadataKey\n /** Capture the conversation id from a live output line; group 1 = the id. */\n outputHint?: RegExp\n /** argv that attaches a NATIVE PTY to an existing conversation.\n * Omitted ⇒ this provider has no native attach path. */\n attachArgv?(conversationId: string): string[]\n /** Absolute path of the conversation's on-disk transcript, when the\n * provider's own resume command also accepts a path in place of a bare\n * id (claude-code: `claude --resume /abs/….jsonl`, which works from any\n * directory and does not depend on the project-slug folder). Pure path\n * arithmetic — never stats the file; callers check existence. Omitted\n * for providers whose resume takes ids only (hermes). `configDir` has\n * the same semantics as `DiscoverInput.configDir`. */\n transcriptPath?(cwd: string, conversationId: string, configDir?: string): string\n /** The env var `attachArgv`'s spawned PTY reads to find its conversation\n * store, when the store is keyed off a per-session isolated config dir\n * (`DiscoverInput.configDir`'s doc — today only claude-code's\n * `CLAUDE_CONFIG_DIR`; see #824). When set, `session_restart`'s\n * `pty-native` branch (session-tools.ts) passes\n * `{ [configDirEnvVar]: descriptor.adapterConfigDir }` to the resumed\n * PTY's env — omitting it is what leaves a `claude --resume <id>`\n * looking in the global `~/.claude` for a transcript that only exists\n * under the session's isolated dir, failing with \"No conversation\n * found\" before the resumed session's first turn. Omitted for stores\n * with no config-dir isolation (hermes: global store only). */\n configDirEnvVar?: string\n /** Find conversations in the provider's native store. `[]` = none —\n * a normal answer (e.g. the PTY is a bash shell). Never throws for\n * \"nothing found\"; throws only on a genuinely unreadable store. */\n discover(input: DiscoverInput): Promise<ConversationCandidate[]>\n /** Read a full conversation into the shared export model. `configDir`\n * has the same semantics as `DiscoverInput.configDir`. */\n read(conversationId: string, cwd?: string, configDir?: string): Promise<ExportedSession>\n /** Follow a conversation live. Resolves to an unsubscribe fn.\n * Omitted ⇒ no live tail for this provider (caller polls `read`). */\n follow?(input: {\n conversationId: string\n cwd?: string\n onMessage: (m: ExportedMessage) => void\n }): Promise<() => void>\n}\n\n// ── claude-code store ──────────────────────────────────────────────────\n//\n// Native store: ~/.claude/projects/<cwd-encoded>/<uuid>.jsonl, one file per\n// conversation. `<cwd-encoded>` is the absolute cwd run through\n// `claudeProjectSlug` (claude's own convention — see that function's\n// docblock for the empirically-verified rule). A daemon-spawned session is\n// config-dir-isolated (#824) and its store root is the session's\n// `CLAUDE_CONFIG_DIR` instead of `~/.claude` — same `projects/<slug>`\n// layout under it; see `DiscoverInput.configDir`.\n\ninterface ClaudeJsonlMeta {\n type?: string\n timestamp?: string\n entrypoint?: string\n message?: { role?: string; content?: unknown }\n}\n\n/**\n * Claude Code's own `cwd` → project-dir-name encoder.\n *\n * The real rule (verified empirically against `~/.claude/projects/`\n * directory names for dozens of known cwds, including dotted worktree\n * paths and macOS temp dirs): every character that is NOT `[a-zA-Z0-9]`\n * is replaced 1:1 with `-`. Crucially, runs of consecutive non-alnum\n * characters are NOT collapsed — each input character produces exactly\n * one output character.\n *\n * This used to be `cwd.replace(/\\//g, \"-\")` — slashes only. That's wrong\n * for any cwd containing a dot (every `.agentproto` worktree, any\n * dotdir), an underscore-prefixed segment, a space, etc., because those\n * characters are left untouched instead of becoming `-`, so the computed\n * directory never matches the one claude actually created.\n *\n * Reconstruction proof (both real, both `ls ~/.claude/projects/`-verified):\n * cwd /Users/jeremy/.agentproto/worktrees/ts/gc-fresh-hold\n * → -Users-jeremy--agentproto-worktrees-ts-gc-fresh-hold\n * (note the \"--\" from \"/.\" — one dash for \"/\", one for \".\", not collapsed)\n *\n * cwd /Volumes/SSDExternalMacStudio/Code/_agentproto-worktrees/adapter-claude-sdk\n * → -Volumes-SSDExternalMacStudio-Code--agentproto-worktrees-adapter-claude-sdk\n * (same double-dash shape, this time from \"/_\")\n */\nexport function claudeProjectSlug(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, \"-\")\n}\n\n/** Exported so `conversation-index.ts` can resolve the same directory\n * without re-deriving the slug rule itself.\n *\n * `configDir` is the session's isolated `CLAUDE_CONFIG_DIR` (see\n * `DiscoverInput.configDir`): the SDK mirrors the global layout under it\n * (`<configDir>/projects/<slug>/<uuid>.jsonl`, empirically verified\n * against real `~/.agentproto/adapter-config/sess_<id>` trees), so the\n * only difference is the base dir. */\nexport function claudeCodeProjectDir(cwd: string, configDir?: string): string {\n const base = configDir ?? resolve(homedir(), \".claude\")\n return resolve(base, \"projects\", claudeProjectSlug(cwd))\n}\n\nfunction extractFirstText(content: unknown): string | undefined {\n if (typeof content === \"string\") {\n const t = content.trim()\n return t || undefined\n }\n if (Array.isArray(content)) {\n for (const block of content) {\n if (\n block &&\n typeof block === \"object\" &&\n (block as { type?: string }).type === \"text\" &&\n typeof (block as { text?: unknown }).text === \"string\"\n ) {\n const t = ((block as { text: string }).text).trim()\n if (t) return t\n }\n }\n }\n return undefined\n}\n\n/** Claude records local slash-command bookkeeping as user messages. They are\n * not the user's prompt and must not become the session's title. */\nfunction isClaudeControlText(text: string): boolean {\n return /^<(?:local-command-caveat|local-command-stdout|command-name|task-notification)>/i.test(text)\n}\n\n/** Light line-scan for candidate metadata — NOT the full transcript parser\n * (that's exportClaudeCodeSession). Streams the file so large jsonl files\n * don't get pulled fully into memory. */\nasync function scanClaudeJsonl(filePath: string): Promise<{\n startedAt?: string\n lastActivityAt?: string\n messageCount: number\n preview?: string\n lastWriter?: string\n}> {\n const stream = createReadStream(filePath, { encoding: \"utf8\" })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n\n let startedAt: string | undefined\n let lastActivityAt: string | undefined\n let messageCount = 0\n let preview: string | undefined\n let lastWriter: string | undefined\n\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n let entry: ClaudeJsonlMeta\n try {\n entry = JSON.parse(trimmed) as ClaudeJsonlMeta\n } catch {\n continue\n }\n\n if (typeof entry.timestamp === \"string\") {\n if (!startedAt) startedAt = entry.timestamp\n lastActivityAt = entry.timestamp\n }\n if (typeof entry.entrypoint === \"string\") {\n lastWriter = entry.entrypoint\n }\n if (entry.type === \"user\" || entry.type === \"assistant\") {\n messageCount += 1\n if (preview === undefined && entry.type === \"user\") {\n const text = extractFirstText(entry.message?.content)\n if (text !== undefined && !isClaudeControlText(text)) {\n preview = text.length > 120 ? text.slice(0, 120) : text\n }\n }\n }\n }\n\n return { startedAt, lastActivityAt, messageCount, preview, lastWriter }\n}\n\nasync function buildClaudeCandidate(\n filePath: string,\n conversationId: string,\n): Promise<ConversationCandidate> {\n const scanned = await scanClaudeJsonl(filePath)\n return { conversationId, ...scanned }\n}\n\n/** Maps an abstract `attachmentMode` to claude's own jsonl `entrypoint`\n * vocabulary. Kept local to this store — callers deal only in\n * `\"native\" | \"acp\"`, never `\"cli\" | \"sdk-ts\"`. */\nfunction claudeEntrypointFor(mode: \"native\" | \"acp\"): string {\n return mode === \"native\" ? \"cli\" : \"sdk-ts\"\n}\n\nasync function discoverClaudeCode(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { cwd, since, until, attachmentMode, configDir, expectedId } = input\n const dir = claudeCodeProjectDir(cwd, configDir)\n\n // Ground truth beats heuristic: bind to exactly the conversation we were\n // asked about, never whichever transcript happens to be newest. Never\n // falls through to the mtime path below — see invariants.\n if (expectedId) {\n const filePath = join(dir, `${expectedId}.jsonl`)\n try {\n await fs.stat(filePath)\n } catch {\n return []\n }\n return [await buildClaudeCandidate(filePath, expectedId)]\n }\n\n let entries: string[]\n try {\n entries = await fs.readdir(dir)\n } catch {\n return []\n }\n const jsonlFiles = entries.filter(e => e.endsWith(\".jsonl\"))\n if (jsonlFiles.length === 0) return []\n\n const sinceMs = since ? Date.parse(since) : NaN\n const untilMs = until ? Date.parse(until) : NaN\n const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : undefined\n const scored: { candidate: ConversationCandidate; mtimeMs: number }[] = []\n for (const f of jsonlFiles) {\n const filePath = join(dir, f)\n let mtimeMs: number\n try {\n mtimeMs = (await fs.stat(filePath)).mtimeMs\n } catch {\n continue\n }\n if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1000) continue\n const conversationId = f.replace(/\\.jsonl$/, \"\")\n const candidate = await buildClaudeCandidate(filePath, conversationId)\n // `until` is an upper bound on the conversation's own first message —\n // provably not this session's if it started after the session ended.\n // Conservative: no discoverable startedAt ⇒ never dropped.\n if (Number.isFinite(untilMs) && candidate.startedAt !== undefined) {\n const startedMs = Date.parse(candidate.startedAt)\n if (Number.isFinite(startedMs) && startedMs > untilMs) continue\n }\n // Attachment-mode match: a native PTY's conversation is never written\n // by the ACP arm and vice versa. Conservative: no lastWriter ⇒ never\n // dropped, and an unset attachmentMode applies no filtering at all.\n if (wantEntrypoint !== undefined && candidate.lastWriter !== undefined && candidate.lastWriter !== wantEntrypoint) {\n continue\n }\n scored.push({ candidate, mtimeMs })\n }\n\n // Newest first — mirrors the old fsProbe's mtime-desc pick, and gives\n // callers of `discover` (which may just want \"the likely one\") a sane\n // default ordering without silently collapsing the list.\n scored.sort((a, b) => b.mtimeMs - a.mtimeMs)\n return scored.map(s => s.candidate)\n}\n\nasync function readClaudeCode(conversationId: string, cwd?: string, configDir?: string): Promise<ExportedSession> {\n // Dynamic import, not a top-level one: resume-strategies.ts is its own\n // lean tsup entry (splitting:false) and pulls this module in as a value\n // import via CONVERSATION_STORES. esbuild still bundles transcript-\n // export.ts's bytes into that entry either way (no code-splitting means\n // no separate chunk to defer to) — what the dynamic import buys is\n // deferred MODULE INIT: transcript-export.ts's top-level code only runs\n // the first time `read()` is actually called, not merely by importing\n // resume-strategies.ts. See resume-strategies.ts's splitting:false note.\n const { exportClaudeCodeSession } = await import(\"./transcript-export.js\")\n return exportClaudeCodeSession(conversationId, cwd, configDir)\n}\n\n// ── hermes store ────────────────────────────────────────────────────\n//\n// Native store: ~/.hermes/state.db, a sqlite `sessions` table keyed by\n// `id` (the agentproto UUID passed as resumeSessionId — the same id\n// agentproto records as `adapterSessionId`, unlike claude-code's on-disk\n// jsonl uuid). It has a `cwd` column (live-verified 2026-07-17), so\n// cwd-scoped discovery is possible, unlike a plain folder-of-files store.\n\nasync function discoverHermes(input: DiscoverInput): Promise<ConversationCandidate[]> {\n // Dynamic import, not a top-level one — same reasoning as readClaudeCode\n // above: keeps transcript-export.ts's sqlite plumbing out of\n // resume-strategies.ts's lean tsup entry until a hermes store method\n // actually runs.\n const { discoverHermesSessions } = await import(\"./transcript-export.js\")\n return discoverHermesSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readHermes(conversationId: string): Promise<ExportedSession> {\n const { exportHermesSession } = await import(\"./transcript-export.js\")\n return exportHermesSession(conversationId)\n}\n\n// ── codex store ───────────────────────────────────────────────────────\n//\n// Native store: $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl.\n// The rollout uuid == the ACP session id agentproto records as\n// `adapterSessionId` (verified empirically), so a codex session's export\n// binds by that id directly (step 1 of the conversation-read ladder), and\n// `discover` scopes a PTY-only codex by the session_meta `cwd`. Reader in\n// transcript-export.ts, dynamically imported for the same reason as\n// readClaudeCode above (keep it out of resume-strategies.ts's lean entry).\n\nasync function discoverCodex(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverCodexSessions } = await import(\"./transcript-export.js\")\n return discoverCodexSessions(input.cwd, input.since, input.until, input.expectedId)\n}\n\nasync function readCodex(conversationId: string): Promise<ExportedSession> {\n const { exportCodexSession } = await import(\"./transcript-export.js\")\n return exportCodexSession(conversationId)\n}\n\n// ── opencode store ────────────────────────────────────────────────────\n//\n// Native store: ${XDG_DATA_HOME:-~/.local/share}/opencode/opencode.db. The\n// `session` table's id is the `ses_…` id agentproto records as\n// `adapterSessionId` (verified), and its `directory` column is the cwd — so\n// discovery is cwd-scoped like hermes.\n\nasync function discoverOpenCode(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverOpenCodeSessions } = await import(\"./transcript-export.js\")\n return discoverOpenCodeSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readOpenCode(conversationId: string): Promise<ExportedSession> {\n const { exportOpenCodeSession } = await import(\"./transcript-export.js\")\n return exportOpenCodeSession(conversationId)\n}\n\n// ── mastracode-inprocess store ────────────────────────────────────────\n//\n// Native store: ${AGENTPROTO_HOME:-~/.agentproto}/mastracode-inprocess/\n// storage.db (Mastra libsql). sessionId is \"<resourceId>:<threadId>\";\n// `read` splits out the threadId. No cwd column, so `discover` is\n// exact-bind only (this in-process arm always supplies an adapterSessionId).\n\nasync function discoverMastracodeInprocess(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverMastracodeInprocessSessions } = await import(\"./transcript-export.js\")\n return discoverMastracodeInprocessSessions(input.cwd, input.since, input.expectedId)\n}\n\nasync function readMastracodeInprocess(conversationId: string): Promise<ExportedSession> {\n const { exportMastracodeInprocessSession } = await import(\"./transcript-export.js\")\n return exportMastracodeInprocessSession(conversationId)\n}\n\n// ── pi store ──────────────────────────────────────────────────────────\n//\n// Native store: ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl. The uuid\n// == pi's RPC sessionId recorded as `adapterSessionId`; discovery keys off\n// each file's authoritative `session.cwd` (the slug encoding is\n// version-dependent, so it is never trusted for scoping).\n\nasync function discoverPi(input: DiscoverInput): Promise<ConversationCandidate[]> {\n const { discoverPiSessions } = await import(\"./transcript-export.js\")\n return discoverPiSessions(input.cwd, input.since, input.until, input.expectedId)\n}\n\nasync function readPi(conversationId: string): Promise<ExportedSession> {\n const { exportPiSession } = await import(\"./transcript-export.js\")\n return exportPiSession(conversationId)\n}\n\nexport const CONVERSATION_STORES: Record<string, ConversationStore> = {\n \"claude-code\": {\n storeAs: \"claudeResumeId\",\n // Printed by claude on graceful exit when session persistence is on\n // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`\n outputHint: /claude\\s+--resume\\s+([0-9a-f-]{8,})/i,\n attachArgv: (conversationId: string) => [\"claude\", \"--resume\", conversationId],\n transcriptPath: (cwd: string, conversationId: string, configDir?: string) =>\n join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),\n configDirEnvVar: \"CLAUDE_CONFIG_DIR\",\n discover: discoverClaudeCode,\n read: readClaudeCode,\n },\n hermes: {\n storeAs: \"hermesResumeId\",\n // `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);\n // `--resume SESSION --tui` is the native TUI resume path — same binary,\n // different flags, unlike claude-code where the two arms are different\n // binaries. Verified via `hermes --help`: `--resume SESSION, -r` =\n // \"Resume a previous session by ID or title\", `--tui` = the real TUI.\n attachArgv: (conversationId: string) => [\"hermes\", \"--resume\", conversationId, \"--tui\"],\n discover: discoverHermes,\n read: readHermes,\n },\n // codex-acp writes no resume flag we sniff, and there's no native PTY\n // reattach argv to declare — export/read only, so no outputHint/attachArgv.\n codex: {\n storeAs: \"codexResumeId\",\n discover: discoverCodex,\n read: readCodex,\n },\n opencode: {\n storeAs: \"openCodeResumeId\",\n // Match the adapter's portable npx installation: a direct `opencode`\n // binary is not guaranteed to be on the daemon's PATH on every host.\n attachArgv: (conversationId: string) => [\"npx\", \"-y\", \"opencode-ai\", \"-s\", conversationId],\n discover: discoverOpenCode,\n read: readOpenCode,\n },\n \"mastracode-inprocess\": {\n storeAs: \"mastracodeInprocessResumeId\",\n discover: discoverMastracodeInprocess,\n read: readMastracodeInprocess,\n },\n pi: {\n storeAs: \"piResumeId\",\n discover: discoverPi,\n read: readPi,\n },\n}\n\n/**\n * Bare (non-resume) launch argv for a provider's native terminal/TUI — the\n * \"start fresh\" counterpart to `attachArgv`, used to open a NEW native\n * session (e.g. a harness card's \"Terminal\" button) rather than reattach an\n * existing conversation.\n *\n * Coverage is deliberately BROADER than `attachArgv`'s: reattach needs a\n * documented resume flag, but a fresh launch only needs the CLI's\n * interactive arm. npx-spawned adapters use the same npx form here so the\n * terminal works wherever the adapter itself works, PATH binary or not.\n *\n * Absent on purpose (no interactive arm to launch):\n * - `mastracode-inprocess` — in-process SDK, nothing to exec\n * - `mastra-agent`, `claude-sdk` — first-party ACP servers, no TUI\n * - `antigravity` — `agy` is headless-only (`-p`/stream-json), no TUI\n */\nexport const NATIVE_LAUNCH_ARGV: Record<string, string[]> = {\n \"claude-code\": [\"claude\"],\n hermes: [\"hermes\", \"--tui\"],\n // Bare arm of the same npx package the adapter spawns IS the TUI.\n opencode: [\"npx\", \"-y\", \"opencode-ai\"],\n mastracode: [\"npx\", \"-y\", \"mastracode\"],\n // The adapter bundles the codex-acp wrapper (ACP-only); the interactive\n // Codex TUI lives in @openai/codex, fetched by npx on first use.\n codex: [\"npx\", \"-y\", \"@openai/codex\"],\n // ACP arms add flags/subcommands to these binaries; bare is interactive.\n gemini: [\"gemini\"],\n \"grok-cli\": [\"grok\"],\n pi: [\"pi\"],\n jcode: [\"jcode\"],\n // `openclaw chat` = \"Open a local terminal UI (alias for tui --local)\".\n openclaw: [\"openclaw\", \"chat\"],\n // Generic ACP catalog CLIs whose ACP arm is a separate binary/subcommand;\n // the bare bin is the interactive TUI.\n \"gemini-cli\": [\"gemini\"],\n \"qwen-code\": [\"qwen\"],\n \"mistral-vibe\": [\"vibe\"],\n \"kimi-cli\": [\"kimi\"],\n}\n\n/**\n * Direct-installed binaries that ARE the same TUI as an npx launch arm,\n * keyed by slug. The npm package behind `npx -y <pkg>` often installs a\n * different bin name (opencode-ai → `opencode`), so a PATH entry appears\n * inargv as the bare bin, not the npx spec. Keep npx compatibility — the\n * NATIVE_LAUNCH_ARGV npx forms keep matching the full npx argv; this table\n * only adds the bare-binary arm.\n */\nconst BARE_LAUNCH_BINS: Record<string, readonly string[]> = {\n opencode: [\"opencode\"],\n mastracode: [\"mastracode\"],\n}\n\n// ── Conversation-terminal classification ───────────────────────────────\n//\n// A native provider TUI in a PTY (claude, hermes, grok, …) is a\n// CONVERSATION the operator happens to be driving through a terminal —\n// trackable in the Sessions list, linkable to its native transcript,\n// switchable to the ACP harness. A plain shell (`bash`, `zsh`, one-shot\n// `bash -lc …`) is none of those and stays Activity-only. This is the ONE\n// shared classifier for that split — the runtime owns it (sessions.ts's\n// list/lane/link paths) and the VS Code extension imports it; do not copy\n// the table client-side.\n\n/** The descriptor fields classification reads. A full `SessionDescriptor`\n * or `SessionSummary` satisfies this structurally. */\nexport interface ConversationTerminalSubject {\n kind?: string\n adapterSlug?: string\n argv?: readonly string[]\n resumeMetadata?: Record<string, string>\n}\n\n/** Every `resumeMetadata` key any conversation store records under —\n * presence of one on a PTY row is proof it holds a provider conversation\n * (the graceful-exit sniffer or the link probe put it there). */\nconst CONVERSATION_RESUME_KEYS: ReadonlySet<string> = new Set(\n Object.values(CONVERSATION_STORES).map(s => s.storeAs),\n)\n\nfunction argvBasename(path: string): string {\n const idx = path.lastIndexOf(\"/\")\n return idx === -1 ? path : path.slice(idx + 1)\n}\n\n/**\n * The adapter/store slug a terminal's argv (or stamped `adapterSlug`) maps\n * to, or `undefined` for anything that isn't a known provider TUI. Matches:\n * - a stamped `adapterSlug` that is a `CONVERSATION_STORES` /\n * `NATIVE_LAUNCH_ARGV` key;\n * - argv[0]'s basename against each launch table entry's own binary\n * (`claude` → `claude-code`, `grok` → `grok-cli`, …);\n * - an npx-launched TUI only when the FULL npx launch argv is a prefix\n * of the session's argv (`npx -y opencode-ai …` → `opencode`) — a bare\n * `npx` running anything else is not a conversation;\n * - a direct-installed TUI binary from `BARE_LAUNCH_BINS` (bare\n * `opencode` / `mastracode` on PATH) — same terminal as the npx arm.\n * The returned slug may have no `CONVERSATION_STORES` entry yet (grok):\n * classifiable and trackable, but no transcript to link or read.\n */\nexport function conversationTerminalSlugFor(\n subject: Pick<ConversationTerminalSubject, \"adapterSlug\" | \"argv\">,\n): string | undefined {\n if (\n subject.adapterSlug &&\n (CONVERSATION_STORES[subject.adapterSlug] || NATIVE_LAUNCH_ARGV[subject.adapterSlug])\n ) {\n return subject.adapterSlug\n }\n const argv = subject.argv\n if (!argv || argv.length === 0 || !argv[0]) return undefined\n const bin = argvBasename(argv[0])\n for (const [slug, bins] of Object.entries(BARE_LAUNCH_BINS)) {\n if (bins.some(b => argvBasename(b) === bin)) return slug\n }\n for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {\n const launchBin = launch[0]\n if (!launchBin || argvBasename(launchBin) !== bin) continue\n if (launchBin === \"npx\") {\n if (launch.every((arg, i) => argv[i] === arg)) return slug\n continue\n }\n return slug\n }\n return undefined\n}\n\n/**\n * True when this PTY row is a provider TUI we can treat as a conversation:\n * a known native launch/store binary or slug, OR a resume id one of the\n * conversation stores already recorded on it. False for plain shells and\n * for `kind === \"command\"` — those stay in the Activity panel only.\n */\nexport function isConversationTerminal(subject: ConversationTerminalSubject): boolean {\n if (subject.kind !== \"terminal\") return false\n if (conversationTerminalSlugFor(subject) !== undefined) return true\n const meta = subject.resumeMetadata\n if (meta) {\n for (const key of CONVERSATION_RESUME_KEYS) {\n if (meta[key]) return true\n }\n }\n return false\n}\n","/**\n * Per-adapter resume strategies — the daemon's view of \"how do I\n * continue a previous session for THIS adapter?\".\n *\n * The driver already has an `AgentCliContinuation` declaration in\n * the adapter manifest (AIP-45). That covers strategy NAMES\n * (`native-resume`, `pinned-session`, `transcript`, `none`) but is\n * silent on the mechanics that live OUTSIDE the runtime — like\n * \"where on disk does this adapter store its conversations?\" or\n * \"what argv do I pass to resume into PTY mode?\".\n *\n * This table is that practical bridge. Each entry declares three\n * optional hooks:\n *\n * outputHint regex matched against each output line; group 1 is\n * the resume id. Stored as `desc.resumeMetadata[storeAs]`.\n * fsProbe async function that probes a per-adapter directory\n * for a stored session id, scoped to a workspace cwd.\n * Used when `outputHint` never matched.\n * spawnArgs given a captured id, returns the argv to spawn a\n * PTY that resumes the session. agentproto then\n * bypasses ACP/agent-cli and runs the provider\n * directly — the most reliable resume path because\n * it leans on the provider's native UX.\n *\n * Adapters that don't declare anything here fall back to:\n * 1. ACP-level resume via `resumeSessionId` on POST /sessions/agent\n * 2. Fresh spawn (same shape, no continuity)\n *\n * Eventually each adapter should ship its own strategy on\n * `AgentCliHandle.resumeStrategy` (driver-side). This central table\n * is the bootstrap: it lets a new adapter become resume-capable\n * without bumping its npm package; once that lands, entries\n * graduate into the adapter packages.\n */\n\nimport { promises as fs } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nimport { CONVERSATION_STORES } from \"./conversation-store.js\"\nimport type { ConversationStore } from \"./conversation-store.js\"\n\n/** Where on disk the resume id ends up on the session descriptor. */\nexport type ResumeMetadataKey =\n | \"claudeResumeId\"\n | \"hermesResumeId\"\n | \"codexResumeId\"\n | \"openClawResumeId\"\n | \"openCodeResumeId\"\n // Export/read-only stores (no native resume wired): the key labels the\n // conversation store entry in CONVERSATION_STORES but is never written to\n // a descriptor's resumeMetadata, since these have no attachArgv/outputHint.\n | \"mastracodeInprocessResumeId\"\n | \"piResumeId\"\n\nexport interface ResumeStrategy {\n /** When set, run on every output line for this adapter. Group 1\n * captures the resume id. The match is saved on the descriptor\n * as `resumeMetadata[storeAs]`. */\n outputHint?: RegExp\n /** Where the matched id is recorded on the descriptor. */\n storeAs: ResumeMetadataKey\n /** Probe the adapter's on-disk session store for the resume id of a\n * session in the given workspace. Returns the id or null when nothing\n * eligible.\n *\n * When `expectedId` is set the session's own conversation id is known\n * (agent-cli descriptors carry it as `adapterSessionId`; for\n * claude-code it IS the on-disk `.jsonl` uuid — see acp-host.ts) and\n * the probe MUST bind to exactly that transcript: it confirms\n * `<expectedId>` exists and returns it, else null. It never falls\n * through to a mtime guess in that case — resuming *some other*\n * session that merely shares the cwd is a silent cross-session\n * correctness bug, strictly worse than not resuming at all.\n *\n * Only when `expectedId` is absent (e.g. a raw PTY claude session that\n * never had an ACP session id) does the probe fall back to\n * most-recently-modified, filtered to files at-or-after `prevStartedAt`\n * (avoids resuming an unrelated prior conversation).\n *\n * `configDir` is the session's isolated provider config dir\n * (`SessionDescriptor.adapterConfigDir`): a daemon-spawned claude-code\n * session (#824) persists its transcripts under that dir, not the\n * provider's global store — the probe must look there. Omit for a\n * native PTY / pre-#824 session (global store applies).\n *\n * Skip when the adapter doesn't persist sessions externally. */\n fsProbe?(\n cwd: string,\n prevStartedAt: string,\n expectedId?: string,\n configDir?: string,\n ): Promise<string | null>\n /** Return the argv to spawn a PTY that resumes into the given id.\n * When omitted, the daemon falls back to ACP-level resume via\n * the agent-cli protocol instead of the provider's native CLI. */\n spawnArgs?(id: string): string[]\n /** Env var the resumed PTY needs pointed at the session's isolated\n * provider config dir to find ITS OWN conversation store — see\n * `ConversationStore.configDirEnvVar`'s doc (conversation-store.ts) for\n * why this exists and what silently omitting it breaks. Undefined for\n * a provider with no config-dir-isolated store. */\n configDirEnvVar?: string\n /** Absolute transcript path for a conversation id — see\n * `ConversationStore.transcriptPath`. Present only when the provider's\n * resume command also accepts a path in place of a bare id. */\n transcriptPath?(cwd: string, conversationId: string, configDir?: string): string\n}\n\n// Project every conversation store that declares a native PTY attach argv\n// into the resume-strategy table, so the table and the store can never drift\n// apart again. Only stores with `attachArgv` offer a provider-native TUI\n// resume; the rest are read/export-only. The actual decision to USE a\n// `pty-native` restart is still gated on `RestartCandidate.nativeTerminalResume`\n// below — a store entry alone does not license terminal mode.\nexport const RESUME_STRATEGIES: Record<string, ResumeStrategy> = Object.fromEntries(\n Object.entries(CONVERSATION_STORES)\n .filter(([, store]) => typeof store.attachArgv === \"function\")\n .map(([slug, store]) => {\n const s = store as Required<Pick<ConversationStore, \"attachArgv\" | \"discover\">> & ConversationStore\n return [\n slug,\n {\n outputHint: store.outputHint,\n storeAs: store.storeAs,\n ...(store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {}),\n ...(store.transcriptPath ? { transcriptPath: store.transcriptPath } : {}),\n fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {\n const candidates = await s.discover({\n cwd,\n since: prevStartedAt,\n expectedId,\n configDir,\n })\n return candidates[0]?.conversationId ?? null\n },\n spawnArgs: s.attachArgv,\n } satisfies ResumeStrategy,\n ]\n }),\n)\n\n/**\n * Helper: which adapters declare any resume capability? Used by\n * `agentproto sessions restart` to print a helpful \"this adapter\n * doesn't support resume; falling back to fresh spawn\" hint when\n * applicable.\n */\nexport function hasResumeStrategy(adapterSlug: string | undefined): boolean {\n if (!adapterSlug) return false\n const s = RESUME_STRATEGIES[adapterSlug]\n return !!(s && (s.outputHint || s.fsProbe || s.spawnArgs))\n}\n\n// ── Restart decision tree ────────────────────────────────────────\n//\n// Shared between the CLI (`agentproto sessions restart`, sessions.ts)\n// and the daemon's `session_restart` MCP tool (session-tools.ts) —\n// both need to answer \"given this dead/alive session, how do I bring\n// it back with the most continuity?\" the exact same way. Duck-typed\n// on a minimal shape rather than importing `SessionDescriptor` from\n// sessions.ts: this file is its own tsup entry (`splitting: false`),\n// so a value-level cross-entry import would inline the whole sessions\n// module here. A full `SessionDescriptor` satisfies these structurally.\n\nexport interface RestartCandidate {\n adapterSlug?: string\n resumeMetadata?: Record<string, string>\n pty?: boolean\n adapterSessionId?: string\n /** Manifest-declared `capabilities.resumable` for `adapterSlug` (see\n * `SessionDescriptor.resumable`'s doc) — the honesty gate for the whole\n * decision tree below. `false` means an `adapterSessionId` here is a\n * dead end: the adapter cannot rehydrate a prior conversation from it, no\n * matter how it's presented. Omitted/`true` preserves today's behaviour\n * (assumed resumable) for every adapter/row that doesn't set this. */\n resumable?: boolean\n /**\n * Manifest-declared `capabilities.nativeTerminalResume` for `adapterSlug`.\n * The `pty-native` strategy requires this flag for ACP-origin sessions.\n * Legacy PTY rows without the flag can use a newly-added store strategy\n * when they already carry an exact native resume id; explicit false blocks\n * that fallback. ACP resumability (`resumable`) does not imply a TUI resume.\n */\n nativeTerminalResume?: boolean\n}\n\nexport interface FsProbeCandidate extends RestartCandidate {\n cwd?: string\n startedAt: string\n /** The session's isolated provider config dir (`SessionDescriptor.\n * adapterConfigDir`) — where a daemon-spawned claude-code session's\n * transcripts actually live since #824. A full `SessionDescriptor`\n * satisfies this structurally; absent on pre-#824/PTY rows, where the\n * provider's global store is the right place to probe. */\n adapterConfigDir?: string\n}\n\n/**\n * Where a restart should land. Computed once so callers never diverge:\n *\n * 1. pty-native — the adapter has a captured resume id AND declares\n * `spawnArgs` (e.g. claude-code): respawn a PTY running the\n * provider's own resume command. Most reliable CONTINUITY mechanism\n * when it applies — but only actually reachable by default when the\n * prior session was ITSELF a real PTY (`prev.pty === true`), or the\n * caller explicitly opts in (`preferNativeTerminal`) — see the\n * `mayPreferNative` doc below for why.\n * 2. pty-plain — the previous session was a real PTY with no\n * native strategy match: re-run the same argv, no continuity.\n * 3. agent — an agent-cli session with no native strategy (or an\n * ACP-origin session where pty-native doesn't apply — see above):\n * resume at the ACP level via the adapter's own session id (may\n * still 404 if the adapter never persisted a turn — callers\n * should retry without `resumeSessionId` on a \"not found\" error).\n * 4. unsupported — a generic command session; no history to resume\n * and no shape to clone reliably.\n */\nexport type RestartStrategy =\n | { kind: \"pty-native\"; argv: string[] }\n | { kind: \"pty-plain\" }\n | { kind: \"agent\"; resumeSessionId?: string; resumeFallback?: boolean }\n | { kind: \"unsupported\"; reason: string }\n\nexport interface DecideRestartStrategyOptions {\n /**\n * Explicit opt-in to provider-native terminal resume for a session whose\n * ORIGIN was agent-cli/ACP (not itself a PTY) — a human who wants the raw\n * provider TUI instead of ACP-level resume, understanding the tradeoffs\n * (see `mayPreferNative`'s doc below). Default false/omitted: an\n * ACP-origin session always resumes via ACP.\n */\n preferNativeTerminal?: boolean\n}\n\nexport function decideRestartStrategy(\n prev: RestartCandidate,\n opts: DecideRestartStrategyOptions = {},\n): RestartStrategy {\n // Provider-native terminal resume takes precedence over ACP-level resume —\n // but ONLY when a verified native TUI resume strategy exists and the\n // capability/origin gates pass (closing\n // the \"restart starts a terminal but it doesn't work\" bug) either the prior\n // session was ITSELF a raw PTY (`prev.pty === true` — a real provider TUI a\n // human was already looking at, whose config dir went through the actual\n // interactive onboarding wizard), or the caller explicitly opted in\n // (`preferNativeTerminal`). An agent-cli/ACP session's isolated\n // `CLAUDE_CONFIG_DIR` (#824) is populated ONLY by the headless ACP\n // entrypoint, which never runs the interactive TUI wizard — so defaulting\n // an ACP-origin session's restart to pty-native silently drops it onto\n // claude-code's first-run onboarding screen (theme picker, \"detected a\n // custom API key\" prompt) with no one attached to answer it, forever.\n // `nativeTerminalResume` alone is a CAPABILITY declaration (\"this adapter\n // supports native resume for a real terminal\"), not license to mode-switch\n // a headless session into an unattended one.\n const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true\n // PTYs created before their store gained attachArgv lack the persisted\n // capability flag. Their native ID plus today's verified strategy is enough\n // to continue the same provider conversation; an explicit false still wins.\n const hasNativeCapability = prev.nativeTerminalResume === true ||\n (prev.pty === true && prev.nativeTerminalResume === undefined)\n if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {\n const strategy = RESUME_STRATEGIES[prev.adapterSlug]\n const id = strategy?.storeAs\n ? prev.resumeMetadata?.[strategy.storeAs]\n : undefined\n if (strategy?.spawnArgs && id) {\n return { kind: \"pty-native\", argv: strategy.spawnArgs(id) }\n }\n }\n if (prev.pty === true) {\n return { kind: \"pty-plain\" }\n }\n if (prev.adapterSlug) {\n // Honesty gate (resume-honesty fix): an adapter that declares\n // `resumable: false` (hermes, mastra-agent, …) cannot rehydrate a prior\n // conversation from `adapterSessionId` at all — passing it as\n // `resumeSessionId` either silently comes back blank or gets rejected by\n // the wrapper, and either way the caller must never present it as\n // continuity. Route straight to a flagged fresh spawn instead of\n // emitting a phantom id. `resumable === undefined` (unknown/legacy row,\n // or an adapter this fix hasn't reached) keeps today's behaviour.\n const canResumeAtAcpLevel = prev.resumable !== false\n return {\n kind: \"agent\",\n ...(prev.adapterSessionId && canResumeAtAcpLevel\n ? { resumeSessionId: prev.adapterSessionId }\n : {}),\n ...(prev.adapterSessionId && !canResumeAtAcpLevel\n ? { resumeFallback: true }\n : {}),\n }\n }\n return {\n kind: \"unsupported\",\n reason: \"generic command session — restart only supports pty + agent-cli\",\n }\n}\n\n/**\n * Matches an adapter's rejection of a `resumeSessionId` it can't honour —\n * the trigger for the \"retry as a fresh spawn\" fallback in\n * `session-restart-core.ts` and the CLI's `sessions restart`. Beyond a\n * literal \"not found\" (an unknown/never-persisted id), this also covers the\n * ACP wrapper-level rejection an adapter with `loadSession: false` throws\n * when a generic `resumeSessionId` reaches it despite `capabilities.\n * resumable: true` at the manifest level (e.g. claude-sdk — resumable via\n * its own SDK session store, but no ACP `session/load` surface): the\n * `@agentclientprotocol/sdk` server-side dispatcher throws a JSON-RPC\n * \"Method not found\" for an unimplemented `session/load`, and an agent may\n * instead reject with its own \"does not support\" / \"not supported\" wording.\n * Shared by every fallback call site so they can't drift out of sync (see\n * this module's own header comment on why that matters).\n */\nexport const RESUME_ID_REJECTED_RE =\n /not found|does not support|not supported|unsupported/i\n\n/**\n * Generic filesystem-fallback: for each known adapter strategy that\n * defines an `fsProbe`, run it against this session's cwd. If a\n * resume id is found, attach it to the descriptor's resumeMetadata.\n *\n * Lets `restart` recover continuity even when our own output sniffer\n * missed the resume hint (session killed too quickly, output buffered\n * past the kill, …). Eligible only when the file is at-or-after the\n * session's `startedAt` (avoid resuming an unrelated prior session\n * in the same cwd — see ResumeStrategy.fsProbe comment).\n *\n * Fix: when NO `adapterSessionId` was ever captured (session killed\n * before the ACP handshake), run a recency-window probe. When an\n * `adapterSessionId` exists but points to a missing JSONL, the probe\n * tries exact-bind and returns nothing — that's correct, we NEVER\n * silently resume a sibling conversation (safety: prevents cross-\n * session contamination). The adapter-level resume will fail with\n * \"not found\", triggering the fresh-spawn fallback + digest injection.\n */\nexport async function augmentWithFsResume<T extends FsProbeCandidate>(\n prev: T,\n): Promise<T> {\n const slug = prev.adapterSlug\n if (!slug) return prev\n const strategy = RESUME_STRATEGIES[slug]\n if (!strategy?.fsProbe) return prev\n // If we already have a captured id for this strategy's storage key,\n // skip the FS probe — the sniffer found it during the session.\n if (prev.resumeMetadata?.[strategy.storeAs]) return prev\n if (!prev.cwd) return prev\n // Pass the session's own conversation id (agent-cli sets it as\n // `adapterSessionId`; for claude-code it equals the on-disk `.jsonl`\n // uuid — acp-host.ts pins them equal) so the probe binds to THIS\n // session's transcript instead of the newest one in the cwd. Without\n // it, a restart of a session that shares a cwd with a concurrent one\n // could silently resume the wrong conversation. When the exact-bind\n // finds nothing (the JSONL is gone), we deliberately do NOT fall\n // through to a sibling — better to fail-fast and inject a digest.\n const id = await strategy.fsProbe(\n prev.cwd,\n prev.startedAt,\n prev.adapterSessionId,\n // A config-dir-isolated session (#824) persisted its transcript under\n // its own CLAUDE_CONFIG_DIR — probe there, not the global ~/.claude.\n prev.adapterConfigDir,\n )\n if (!id) return prev\n return {\n ...prev,\n // When `adapterSessionId` was never captured (session killed before\n // the ACP handshake completed), backfill it from the fsProbe result\n // so the agent restart path can attempt ACP-level resume too — not\n // just the PTY-native path. For claude-code (and most adapters) the\n // on-disk conversation id IS the ACP session id, so the values are\n // interchangeable. Only backfills; never overwrites an existing id.\n ...(!prev.adapterSessionId ? { adapterSessionId: id } : {}),\n resumeMetadata: {\n ...(prev.resumeMetadata ?? {}),\n [strategy.storeAs]: id,\n },\n }\n}\n\n/** What `probeNativeTranscript` found for a candidate's own transcript. */\nexport interface NativeTranscriptProbe {\n /** Directory that was (or would be) probed for the transcript. */\n dir: string\n /** Absolute path of the transcript file the id maps to. */\n path: string\n /** Whether that exact file exists on disk right now. */\n exists: boolean\n}\n\n/**\n * Locate the candidate's own on-disk transcript for a provider whose native\n * resume command accepts an absolute path (`ResumeStrategy.transcriptPath`).\n * Returns `undefined` when the adapter declares no `transcriptPath`, or the\n * candidate has no cwd / no conversation id to map — callers treat that as\n * \"nothing to upgrade, and no directory worth naming in diagnostics\".\n *\n * The id lookup mirrors `decideRestartStrategy`'s (`resumeMetadata[storeAs]`),\n * falling back to `adapterSessionId` (for claude-code the ACP session id IS\n * the on-disk `.jsonl` uuid — acp-host.ts pins them equal) so a probe can\n * still name the transcript it looked for when the sniffer never fired.\n * `exists: false` is the honest \"transcript not found\" answer — never fall\n * through to a sibling file (cross-session contamination, see fsProbe's doc).\n */\nexport async function probeNativeTranscript(\n prev: FsProbeCandidate,\n): Promise<NativeTranscriptProbe | undefined> {\n if (!prev.adapterSlug || !prev.cwd) return undefined\n const strategy = RESUME_STRATEGIES[prev.adapterSlug]\n if (!strategy?.transcriptPath) return undefined\n const id = prev.resumeMetadata?.[strategy.storeAs] ?? prev.adapterSessionId\n if (!id) return undefined\n const path = strategy.transcriptPath(prev.cwd, id, prev.adapterConfigDir)\n let exists = false\n try {\n exists = (await fs.stat(path)).isFile()\n } catch {\n exists = false\n }\n return { dir: dirname(path), path, exists }\n}\n\n/**\n * Describe which resume path a restart used, for CLI banners / MCP\n * responses. Returns a short phrase like \"resumed via claude --resume\",\n * or \"resumed via ACP\", or \"\" when no resume was attempted.\n *\n * Honesty gate (resume-honesty fix): \"resumed via ACP\" is a claim of actual\n * conversation continuity, so it's only returned when the adapter's\n * declared capability backs that claim (`resumable !== false`). An adapter\n * that declared `resumable: false` but still carries an `adapterSessionId`\n * gets an honest degraded label instead of the lie — never silently \"\".\n *\n * Origin gate (mirrors `decideRestartStrategy`'s own — MUST stay in sync or\n * this label lies about what actually happened): the native-resume phrasing\n * is only reported when `decideRestartStrategy` itself would have picked\n * pty-native for this `prev` — i.e. `prev.pty === true` or the caller passed\n * the SAME `preferNativeTerminal` it decided the strategy with. Without this\n * gate, a captured `resumeMetadata` alone used to make this function claim\n * \"resumed via claude --resume\" even when the actual decision (now)\n * downgrades an ACP-origin session to ACP-level resume instead.\n */\nexport function describeResumePath(\n prev: RestartCandidate,\n opts: DecideRestartStrategyOptions = {},\n): string {\n const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true\n const hasNativeCapability = prev.nativeTerminalResume === true ||\n (prev.pty === true && prev.nativeTerminalResume === undefined)\n if (prev.adapterSlug && hasNativeCapability && mayPreferNative) {\n const s = RESUME_STRATEGIES[prev.adapterSlug]\n if (s?.spawnArgs && s.storeAs && prev.resumeMetadata?.[s.storeAs]) {\n const sample = s.spawnArgs(\"…\")\n const idIndex = sample.indexOf(\"…\")\n return `resumed via ${idIndex > 0 ? sample.slice(0, idIndex).join(\" \") : (sample[0] ?? prev.adapterSlug)}`\n }\n }\n if (prev.adapterSlug && prev.adapterSessionId) {\n if (prev.resumable === false) {\n return `fresh — resume not supported by ${prev.adapterSlug}`\n }\n return \"resumed via ACP\"\n }\n return \"\"\n}\n\n/**\n * Shell-style argv tokenizer for descriptors that don't carry `argv`\n * separately (legacy persisted rows predating that field). Same rules\n * as the spawn-dialog tokenizer in the web app: whitespace splits,\n * single + double quotes group.\n */\nexport function tokenizeCommand(s: string): string[] {\n const out: string[] = []\n let buf = \"\"\n let inSingle = false\n let inDouble = false\n for (let i = 0; i < s.length; i++) {\n const ch = s[i]\n if (ch === \"'\" && !inDouble) {\n inSingle = !inSingle\n continue\n }\n if (ch === '\"' && !inSingle) {\n inDouble = !inDouble\n continue\n }\n if (!inSingle && !inDouble && /\\s/.test(ch ?? \"\")) {\n if (buf) {\n out.push(buf)\n buf = \"\"\n }\n continue\n }\n buf += ch\n }\n if (buf) out.push(buf)\n return out\n}\n"]}