feat: response-viewer support for Codex sessions

The response-viewer (eye) currently reads only ~/.claude/projects — for
Codex panes it falls back to a raw terminal-buffer dump. This adds a
Codex-aware reader with exact per-pane rollout attribution.

Locating THIS pane's rollout (~/.codex/sessions/**), in confidence order:

1. history match — Session tracks the pane's last Enter
   (codexLastSubmitAt); correlating it against ~/.codex/history.jsonl
   {session_id, ts} entries identifies the thread the pane is ACTUALLY
   on, surviving /resume, /new and /fork typed inside the codex TUI.
   An entry is credited to the pane whose Enter is closest, so menu
   keystrokes in other panes can't steal attribution.
2. originator match — codex panes are spawned with
   CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codeman_<sessionId>, which codex
   (verified on 0.144.1) writes into session_meta.originator of every
   rollout it creates.
3. resume-id match — resumed rollouts keep their original session_meta
   (codex appends without rewriting), but the uuid is in the filename.
4. cwd+mtime heuristic — case-blind compare (codex records launch-time
   path case) and rollouts claimed by other panes are excluded.

Reader details: user turns come from event_msg/user_message (real input
only — AGENTS.md / environment_context injections never appear there),
deduped against legacy response_item rows per-text so mixed-version
rollouts keep full history; image inputs render an [image xN]
placeholder; session_meta identity is cached per path (write-once).

Frontend: thread role label follows session mode (Codex/Gemini/
OpenCode); the terminal-buffer fallback is Claude-only — TUI modes show
a clear placeholder instead of a repaint dump.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
codeman-local
2026-07-12 10:39:42 +08:00
co-authored by Claude Fable 5
parent 1fa88cd187
commit 116db81002
4 changed files with 372 additions and 6 deletions
+16 -5
View File
@@ -1612,8 +1612,12 @@ class CodemanApp {
const data = (await res.json())?.data ?? {};
let lastResponse = data.text || '';
// Source 2: Terminal buffer fallback — strip ANSI, drop Claude CLI chrome
if (!lastResponse) {
// Source 2: Terminal buffer fallback — strip ANSI, drop Claude CLI chrome.
// Claude-only: _cleanTerminalBuffer knows Claude CLI's output; for TUI
// modes (codex/opencode/gemini) it yields repaint garbage, so a clear
// placeholder beats a messy screen dump there.
const sessionMode = this.sessions.get(this.activeSessionId)?.mode || 'claude';
if (!lastResponse && sessionMode === 'claude') {
const termRes = await fetch(`/api/sessions/${this.activeSessionId}/terminal`);
const termData = (await termRes.json())?.data ?? {};
if (termData.terminalBuffer) {
@@ -1622,8 +1626,12 @@ class CodemanApp {
}
const body = document.getElementById('responseViewerBody');
body.innerHTML = this._renderMarkdown(lastResponse);
this._bindResponseViewerInteractions(body);
if (lastResponse) {
body.innerHTML = this._renderMarkdown(lastResponse);
this._bindResponseViewerInteractions(body);
} else {
body.textContent = 'No response yet — send a message in this session first.';
}
// Reset state for fresh open
const title = document.getElementById('responseViewerTitle');
@@ -1657,6 +1665,9 @@ class CodemanApp {
}
// Render conversation thread
const mode = this.sessions.get(this.activeSessionId)?.mode;
const agentLabel =
mode === 'codex' ? 'Codex' : mode === 'gemini' ? 'Gemini' : mode === 'opencode' ? 'OpenCode' : 'Claude';
body.innerHTML = '';
for (const msg of messages) {
const div = document.createElement('div');
@@ -1665,7 +1676,7 @@ class CodemanApp {
const role = document.createElement('div');
role.className = 'rv-role ' + (isUser ? 'rv-role-user' : 'rv-role-assistant');
role.textContent = isUser ? 'You' : 'Claude';
role.textContent = isUser ? 'You' : agentLabel;
div.appendChild(role);
const text = document.createElement('div');