diff --git a/packages/coding-agent/src/core/export-html/index.ts b/packages/coding-agent/src/core/export-html/index.ts index 3af74df6..a25d39b7 100644 --- a/packages/coding-agent/src/core/export-html/index.ts +++ b/packages/coding-agent/src/core/export-html/index.ts @@ -1,5 +1,5 @@ -import type { AgentState } from "@mariozechner/pi-agent-core"; -import { buildCodexPiBridge, getCodexInstructions, getModelFamily } from "@mariozechner/pi-ai"; +import type { AgentState, AgentTool } from "@mariozechner/pi-agent-core"; +import { buildCodexPiBridge, getCodexInstructions } from "@mariozechner/pi-ai"; import { existsSync, readFileSync, writeFileSync } from "fs"; import { basename, join } from "path"; import { APP_NAME, getExportTemplateDir } from "../../config.js"; @@ -11,44 +11,34 @@ export interface ExportOptions { themeName?: string; } -interface ProviderSystemPrompt { - title: string; - content: string; - note?: string; +/** Info about Codex injection to show inline with model_change entries */ +interface CodexInjectionInfo { + /** Codex instructions text */ + instructions: string; + /** Bridge text (tool list) */ + bridge: string; } /** - * Build the provider-specific system prompt for display in exports. - * Currently only supports OpenAI Codex provider. + * Build Codex injection info for display inline with model_change entries. */ -async function buildProviderSystemPrompt(state?: AgentState): Promise { - if (!state?.model || state.model.provider !== "openai-codex") { - return undefined; - } - +async function buildCodexInjectionInfo(tools?: AgentTool[]): Promise { + // Try to get cached instructions for default model family let instructions: string | null = null; try { - instructions = await getCodexInstructions(state.model.id); + instructions = await getCodexInstructions("gpt-5.1-codex"); } catch { - // Cache miss or fetch failed - that's fine + // Cache miss - that's fine } - const bridgeText = buildCodexPiBridge(state.tools); - const userPrompt = state.systemPrompt || ""; - const modelFamily = getModelFamily(state.model.id); + const bridgeText = buildCodexPiBridge(tools); const instructionsText = instructions || "(Codex instructions not cached. Run a Codex request to populate the local cache.)"; - const note = instructions - ? `Injected by the OpenAI Codex provider for model family "${modelFamily}" (instructions + bridge + user system prompt).` - : "Codex instructions unavailable; showing bridge and user system prompt only."; - - const content = `# Codex Instructions\n${instructionsText}\n\n# Codex-Pi Bridge\n${bridgeText}\n\n# User System Prompt\n${userPrompt || "(empty)"}`; return { - title: "Injected Prompt (OpenAI Codex)", - content, - note, + instructions: instructionsText, + bridge: bridgeText, }; } @@ -145,7 +135,8 @@ interface SessionData { entries: ReturnType; leafId: string | null; systemPrompt?: string; - providerSystemPrompt?: ProviderSystemPrompt; + /** Info for rendering Codex injection inline with model_change entries */ + codexInjectionInfo?: CodexInjectionInfo; tools?: { name: string; description: string }[]; } @@ -209,7 +200,7 @@ export async function exportSessionToHtml( entries: sm.getEntries(), leafId: sm.getLeafId(), systemPrompt: state?.systemPrompt, - providerSystemPrompt: await buildProviderSystemPrompt(state), + codexInjectionInfo: await buildCodexInjectionInfo(state?.tools), tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })), }; @@ -229,7 +220,7 @@ export async function exportSessionToHtml( * Export session file to HTML (standalone, without AgentState). * Used by CLI for exporting arbitrary session files. */ -export function exportFromFile(inputPath: string, options?: ExportOptions | string): string { +export async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise { const opts: ExportOptions = typeof options === "string" ? { outputPath: options } : options || {}; if (!existsSync(inputPath)) { @@ -243,7 +234,7 @@ export function exportFromFile(inputPath: string, options?: ExportOptions | stri entries: sm.getEntries(), leafId: sm.getLeafId(), systemPrompt: undefined, - providerSystemPrompt: undefined, + codexInjectionInfo: await buildCodexInjectionInfo(undefined), tools: undefined, }; diff --git a/packages/coding-agent/src/core/export-html/template.css b/packages/coding-agent/src/core/export-html/template.css index eac001a1..354ae74a 100644 --- a/packages/coding-agent/src/core/export-html/template.css +++ b/packages/coding-agent/src/core/export-html/template.css @@ -512,6 +512,39 @@ font-weight: bold; } + .codex-bridge-toggle { + color: var(--muted); + cursor: pointer; + text-decoration: underline; + font-size: 10px; + } + + .codex-bridge-toggle:hover { + color: var(--accent); + } + + .codex-bridge-content { + display: none; + margin-top: 8px; + padding: 8px; + background: var(--exportCardBg); + border-radius: 4px; + font-size: 11px; + max-height: 300px; + overflow: auto; + } + + .codex-bridge-content pre { + margin: 0; + white-space: pre-wrap; + word-break: break-word; + color: var(--muted); + } + + .model-change.show-bridge .codex-bridge-content { + display: block; + } + /* Compaction / Branch Summary - matches customMessage colors from TUI */ .compaction { background: var(--customMessageBg); diff --git a/packages/coding-agent/src/core/export-html/template.js b/packages/coding-agent/src/core/export-html/template.js index 52c2e493..48311d87 100644 --- a/packages/coding-agent/src/core/export-html/template.js +++ b/packages/coding-agent/src/core/export-html/template.js @@ -12,7 +12,7 @@ bytes[i] = binary.charCodeAt(i); } const data = JSON.parse(new TextDecoder('utf-8').decode(bytes)); - const { header, entries, leafId: defaultLeafId, systemPrompt, providerSystemPrompt, tools } = data; + const { header, entries, leafId: defaultLeafId, systemPrompt, codexInjectionInfo, tools } = data; // ============================================================ // URL PARAMETER HANDLING @@ -954,7 +954,17 @@ } if (entry.type === 'model_change') { - return `
${tsHtml}Switched to model: ${escapeHtml(entry.provider)}/${escapeHtml(entry.modelId)}
`; + let html = `
${tsHtml}Switched to model: ${escapeHtml(entry.provider)}/${escapeHtml(entry.modelId)}`; + + // Show expandable bridge prompt info when switching to openai-codex + if (entry.provider === 'openai-codex' && codexInjectionInfo) { + const fullContent = `# Codex Instructions\n${codexInjectionInfo.instructions}\n\n# Codex-Pi Bridge\n${codexInjectionInfo.bridge}`; + html += ` [bridge prompt]`; + html += `
${escapeHtml(fullContent)}
`; + } + + html += '
'; + return html; } if (entry.type === 'compaction') { @@ -1060,32 +1070,8 @@ `; - // Render provider-injected system prompt (e.g., Codex) if present - if (providerSystemPrompt) { - const lines = providerSystemPrompt.content.split('\n'); - const previewLines = 10; - const noteHtml = providerSystemPrompt.note - ? `
${escapeHtml(providerSystemPrompt.note)}
` - : ''; - if (lines.length > previewLines) { - const preview = lines.slice(0, previewLines).join('\n'); - const remaining = lines.length - previewLines; - html += ``; - } else { - html += `
-
${escapeHtml(providerSystemPrompt.title)}
- ${noteHtml} -
${escapeHtml(providerSystemPrompt.content)}
-
`; - } - } else if (systemPrompt) { - // Standard system prompt (non-Codex providers) + // Render system prompt (user's base prompt, applies to all providers) + if (systemPrompt) { const lines = systemPrompt.split('\n'); const previewLines = 10; if (lines.length > previewLines) { diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index eaa164cd..099c47ae 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -345,7 +345,7 @@ export async function main(args: string[]) { if (parsed.export) { try { const outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined; - const result = exportFromFile(parsed.export, outputPath); + const result = await exportFromFile(parsed.export, outputPath); console.log(`Exported to: ${result}`); return; } catch (error: unknown) {