feat(terminal): tmux pane-buffer primitives and session/render reliability

Mode-agnostic terminal foundation extracted from the downstream branch:
- formatPaneSnapshot: SGR/grapheme-aware tmux pane capture + active-pane
  resolution, with OSC/CSI redraw suppression and the buffer-load owner-token
  race fix on the terminal fetch path
- socket-correct tmux lifecycle: dedicated -L socket and /tmp launch cwd in
  createSession (restores the FUSE/getcwd hardening from #110), and a
  socket-aware re-attach window-size query (avoids the 120x40 flicker)
- inline-rename: commit/cancel state handling clears _activeRename and skips
  the API call on cancel
- selectSession: restored detached-window raise short-circuit

The codex-specific xterm snapshot/replay, the vendored serialize addon, and
the synchronous live pane-capture on the request path are intentionally
excluded: they depend on a 'codex' SessionMode that doesn't exist on master
and are deferred to COD-34 (which introduces that mode). The capture
primitives remain exported for COD-34 to build on.

Co-Authored-By: Saqeb Akhter <saqeb.akhter@gmail.com>
This commit is contained in:
Aamer Akhter
2026-06-09 15:39:03 -04:00
co-authored by Saqeb Akhter
parent 2952256d65
commit 0569f68b86
11 changed files with 840 additions and 66 deletions
+19
View File
@@ -0,0 +1,19 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
function cssRuleBody(css: string, selector: string): string {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = css.match(new RegExp(`${escaped}\\s*\\{([^}]*)\\}`));
return match?.[1] ?? '';
}
describe('terminal layout CSS', () => {
it('pins the xterm root to the terminal container width while scrollback moves', () => {
const css = readFileSync(resolve(import.meta.dirname, '../src/web/public/styles.css'), 'utf8');
const xtermRule = cssRuleBody(css, '.terminal-container .xterm');
expect(xtermRule).toContain('width: 100%');
expect(xtermRule).toContain('min-width: 0');
});
});
+15
View File
@@ -0,0 +1,15 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { describe, expect, it } from 'vitest';
describe('tmux styled pane capture', () => {
it('requests SGR style escapes for canonical browser replay frames', () => {
const source = readFileSync(resolve(import.meta.dirname, '../src/tmux-manager.ts'), 'utf8');
const captureMethod = source.indexOf('capturePaneBuffer(muxName: string');
const captureCommand = source.indexOf('capture-pane -p', captureMethod);
expect(captureMethod).toBeGreaterThan(-1);
expect(captureCommand).toBeGreaterThan(captureMethod);
expect(source.slice(captureCommand, captureCommand + 80)).toContain('capture-pane -p -e');
});
});
+102 -1
View File
@@ -8,7 +8,7 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { TmuxManager, parsePaneList } from '../src/tmux-manager.js';
import { TmuxManager, formatPaneSnapshot, parsePaneList, resolveActivePaneTarget } from '../src/tmux-manager.js';
import { execSync } from 'node:child_process';
// ============================================================================
@@ -118,6 +118,107 @@ describe('TmuxManager (unit)', () => {
});
});
describe('window sizing', () => {
it('pins a tmux window to manual sizing before browser attach', () => {
expect(manager.setManualWindowSize('codeman-abc12345')).toBe(true);
expect(mockedExecSync).toHaveBeenCalledWith(
"tmux -L 'codeman' set-window-option -t 'codeman-abc12345' window-size manual",
expect.objectContaining({ stdio: 'ignore' })
);
});
it('resizes the tmux window when Codeman accepts a desktop resize', () => {
expect(manager.resizeWindow('codeman-abc12345', 140, 42)).toBe(true);
expect(mockedExecSync).toHaveBeenCalledWith(
"tmux -L 'codeman' resize-window -t 'codeman-abc12345' -x 140 -y 42",
expect.objectContaining({ stdio: 'ignore' })
);
});
});
describe('environment exports', () => {
it('keeps COLORTERM unset for OpenCode sessions', () => {
const exports = (
manager as unknown as {
buildEnvExports(sessionId: string, muxName: string, mode: string): string[];
}
).buildEnvExports('session-1', 'codeman-abc12345', 'opencode');
expect(exports).toContain('unset COLORTERM');
});
});
describe('formatPaneSnapshot', () => {
it('paints captured rows with absolute cursor positions to avoid newline autowrap scroll', () => {
const fullWidthLine = 'x'.repeat(10);
const snapshot = formatPaneSnapshot([fullWidthLine, 'next line'], {
cols: 10,
rows: 4,
cursorX: 2,
cursorY: 1,
});
expect(snapshot).toBe(`\x1b[1;1H${'x'.repeat(9)}\x1b[2;1Hnext line\x1b[2;3H`);
expect(snapshot).not.toContain('\n');
});
it('preserves SGR color while stripping non-style pane controls', () => {
const snapshot = formatPaneSnapshot(['\x1b[32mgreen\x1b[0m\x1b[2K\x1b[10;20Htail'], {
cols: 40,
rows: 2,
cursorX: 0,
cursorY: 0,
});
expect(snapshot).toContain('\x1b[32mgreen\x1b[0m');
expect(snapshot).toContain('tail');
expect(snapshot).not.toContain('\x1b[2K');
expect(snapshot).not.toContain('\x1b[10;20H');
});
it('truncates styled rows by visible columns without cutting SGR escapes', () => {
const snapshot = formatPaneSnapshot(['\x1b[31mabcdef\x1b[0m'], {
cols: 4,
rows: 1,
cursorX: 0,
cursorY: 0,
});
expect(snapshot).toBe('\x1b[1;1H\x1b[31mabc\x1b[0m\x1b[1;1H');
});
it('does not let full-width glyphs cross the paint boundary', () => {
const snapshot = formatPaneSnapshot(['abc\u754cdef'], {
cols: 5,
rows: 1,
cursorX: 0,
cursorY: 0,
});
expect(snapshot).toBe('\x1b[1;1Habc\x1b[1;1H');
});
it('keeps combining marks attached without consuming a terminal column', () => {
const snapshot = formatPaneSnapshot(['a\u0301bc'], {
cols: 4,
rows: 1,
cursorX: 0,
cursorY: 0,
});
expect(snapshot).toBe('\x1b[1;1Ha\u0301bc\x1b[1;1H');
});
});
describe('resolveActivePaneTarget', () => {
it('selects the active pane instead of assuming pane zero', () => {
expect(resolveActivePaneTarget('%1:0\n%18:1\n')).toBe('%18');
});
});
describe('isAvailable', () => {
it('should return true when tmux is found', () => {
mockedExecSync.mockImplementation((cmd: string) => {