Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2252,11 +2252,29 @@ export class Session extends EventEmitter {
* ```
*/
write(data: string): void {
this._trackCodexSubmit(data);
if (this.ptyProcess) {
this.ptyProcess.write(data);
}
}

// ── Codex thread tracking ─────────────────────────────────────────────
// When a codex pane last submitted a message (Enter). The response-viewer
// correlates this against ~/.codex/history.jsonl entry timestamps to find
// the thread the pane is ACTUALLY on — the only signal that survives
// /resume, /new and /fork typed inside the codex TUI itself.
private _codexLastSubmitAt = 0;

get codexLastSubmitAt(): number {
return this._codexLastSubmitAt;
}

private _trackCodexSubmit(data: string): void {
if (this.mode === 'codex' && (data.includes('\r') || data.includes('\n'))) {
this._codexLastSubmitAt = Date.now();
}
}

/**
* Per-client highest-applied input sequence, for exactly-once input delivery.
* Keyed by the web client's stable `clientId`. Bounded so many devices over a
Expand Down Expand Up @@ -2310,6 +2328,7 @@ export class Session extends EventEmitter {
* ```
*/
async writeViaMux(data: string): Promise<boolean> {
this._trackCodexSubmit(data);
if (this._mux && this._muxSession) {
return this._mux.sendInput(this.id, data);
}
Expand Down
6 changes: 6 additions & 0 deletions src/tmux-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,12 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
'export LC_ALL=en_US.UTF-8',
mode === 'codex' || mode === 'gemini' ? 'export COLORTERM=truecolor' : 'unset COLORTERM',
...(mode === 'codex' || mode === 'gemini' ? ['unset NO_COLOR'] : []),
// Stamp each Codex pane with a unique originator so the response-viewer
// can locate THIS pane's rollout exactly — codex writes the value into
// session_meta.originator of every rollout it creates. Without it,
// rollouts are matched by cwd+mtime and two panes in the same directory
// bleed into each other.
...(mode === 'codex' ? [`export CODEX_INTERNAL_ORIGINATOR_OVERRIDE=codeman_${sessionId}`] : []),
'export CODEMAN_MUX=1',
`export CODEMAN_SESSION_ID=${sessionId}`,
`export CODEMAN_MUX_NAME=${muxName}`,
Expand Down
22 changes: 17 additions & 5 deletions src/web/public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1612,8 +1612,13 @@ 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 + shell only: _cleanTerminalBuffer knows Claude CLI's output, and
// shell sessions have no transcript source at all; 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' || sessionMode === 'shell')) {
const termRes = await fetch(`/api/sessions/${this.activeSessionId}/terminal`);
const termData = (await termRes.json())?.data ?? {};
if (termData.terminalBuffer) {
Expand All @@ -1622,8 +1627,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');
Expand Down Expand Up @@ -1657,6 +1666,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');
Expand All @@ -1665,7 +1677,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');
Expand Down
Loading