From b61aa94c28466706d84b921676b630926c27d53c Mon Sep 17 00:00:00 2001 From: Ryan McMillan Date: Wed, 28 Jan 2026 13:56:17 -0600 Subject: [PATCH] feat: write transcript for CLI provider runs [AI-assisted] CLI backends (claude-cli, codex-cli, etc.) don't normally save session transcripts because they run as external processes. This causes issues with subagent announce flow - when a CLI-backed subagent completes, the main agent can't read the reply because no transcript exists. This change writes a JSONL transcript file after CLI provider runs complete, using the same format as embedded Pi sessions: - Session header with version, id, timestamp, cwd - Message entry with assistant role, content, timestamp, usage The transcript path is resolved from the session key/id and agent id, matching the embedded runner's behavior. Transcript writes are best-effort and won't fail the run. Co-Authored-By: Claude Opus 4.5 --- .../reply/agent-runner-execution.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index f86ecb8a9..1aaf22fc6 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -1,5 +1,6 @@ import crypto from "node:crypto"; import fs from "node:fs"; +import path from "node:path"; import { resolveAgentModelFallbacksOverride } from "../../agents/agent-scope.js"; import { runCliAgent } from "../../agents/cli-runner.js"; import { getCliSessionId } from "../../agents/cli-session.js"; @@ -192,6 +193,50 @@ export async function runAgentTurnWithFallback(params: { data: { text: cliText }, }); } + // Write CLI result to transcript for subagent announce flow + if (cliText && params.sessionKey && params.followupRun.run.sessionId) { + try { + const agentId = resolveAgentIdFromSessionKey(params.sessionKey); + const transcriptPath = resolveSessionTranscriptPath( + params.followupRun.run.sessionId, + agentId, + ); + // Ensure directory exists + fs.mkdirSync(path.dirname(transcriptPath), { recursive: true }); + // Create session header if file doesn't exist + if (!fs.existsSync(transcriptPath)) { + const header = { + type: "session", + version: 1, + id: params.followupRun.run.sessionId, + timestamp: new Date().toISOString(), + cwd: process.cwd(), + }; + fs.writeFileSync(transcriptPath, JSON.stringify(header) + "\n", "utf-8"); + } + // Append assistant message + const messageEntry = { + type: "message", + id: crypto.randomUUID().slice(0, 8), + timestamp: new Date().toISOString(), + message: { + role: "assistant", + content: [{ type: "text", text: cliText }], + timestamp: Date.now(), + stopReason: "cli", + usage: result.meta?.agentMeta?.usage ?? { + input: 0, + output: 0, + totalTokens: 0, + }, + }, + }; + fs.appendFileSync(transcriptPath, JSON.stringify(messageEntry) + "\n", "utf-8"); + } catch (err) { + // Best effort - don't fail the run if transcript write fails + logVerbose(`CLI transcript write failed: ${String(err)}`); + } + } emitAgentEvent({ runId, stream: "lifecycle",