fix: COD-24 stabilize remote host sessions

This commit is contained in:
Saqeb Akhter
2026-07-09 09:06:06 -04:00
committed by Aamer Akhter
parent 568d93efb0
commit 26e78daf58
11 changed files with 512 additions and 30 deletions
+29 -3
View File
@@ -232,7 +232,7 @@ describe('case-routes', () => {
const list = await harness.app.inject({ method: 'GET', url: '/api/remote-hosts' });
expect(list.statusCode).toBe(200);
expect(JSON.parse(list.body)).toEqual([
expect(JSON.parse(list.body).data).toEqual([
expect.objectContaining({ id: 'gpu-box', label: 'GPU Box', commands: { codex: 'exec codx personal' } }),
]);
});
@@ -255,12 +255,38 @@ describe('case-routes', () => {
expect(link.statusCode).toBe(200);
const cases = await harness.app.inject({ method: 'GET', url: '/api/cases' });
expect(JSON.parse(cases.body)).toContainEqual(
expect(JSON.parse(cases.body).data).toContainEqual(
expect.objectContaining({
name: 'gpu-work',
location: 'remote',
path: 'ubuntu@10.0.0.42:/home/ubuntu/work',
remote: expect.objectContaining({ hostId: 'gpu-box', path: '/home/ubuntu/work' }),
})
);
});
it('prefers remote case metadata over a same-name local managed case', async () => {
setupRemoteConfigStore();
mockedReaddir.mockResolvedValue([{ name: 'gpu-work', isDirectory: () => true }] as never);
await harness.app.inject({
method: 'POST',
url: '/api/remote-hosts',
payload: { id: 'gpu-box', label: 'GPU Box', host: '10.0.0.42', username: 'ubuntu' },
});
await harness.app.inject({
method: 'POST',
url: '/api/cases/remote-link',
payload: { name: 'gpu-work', hostId: 'gpu-box', remotePath: '/home/ubuntu/work' },
});
mockedExistsSync.mockReturnValue(true);
const cases = await harness.app.inject({ method: 'GET', url: '/api/cases' });
expect(JSON.parse(cases.body).data).toContainEqual(
expect.objectContaining({
name: 'gpu-work',
location: 'remote',
path: 'ubuntu@10.0.0.42:/home/ubuntu/work',
remote: expect.objectContaining({ hostId: 'gpu-box', hostLabel: 'GPU Box', path: '/home/ubuntu/work' }),
})
);
});
+231
View File
@@ -19,6 +19,7 @@ import fastifyCookie from '@fastify/cookie';
import { createMockRouteContext, type MockRouteContext } from '../mocks/index.js';
import { installRouteErrorHandler } from '../../src/web/route-error-handler.js';
import { ApiErrorCode, httpStatusForErrorCode } from '../../src/types.js';
import { Session } from '../../src/session.js';
// Mock execFile so the send-key route's `tmux` invocation is observable (not run for real).
const { execFile } = vi.hoisted(() => ({ execFile: vi.fn() }));
@@ -481,6 +482,166 @@ describe('session-routes', () => {
expect(body.data.terminalBuffer).toBeDefined();
});
it('does not strip VPA-like shell scrollback as Ink redraw bloat', async () => {
const shellHistory = Array.from(
{ length: 3000 },
(_, index) => `SHELL_SCROLLBACK_${String(index + 1).padStart(6, '0')} payload payload payload \x1b[1d`
).join('\n');
harness.ctx._session.terminalBuffer = shellHistory;
harness.ctx._session.mode = 'shell';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(() => null);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain('SHELL_SCROLLBACK_000001');
expect(body.data.terminalBuffer).toContain('SHELL_SCROLLBACK_003000');
});
it('preserves accumulated history before the live mux pane snapshot for Codex TUI replay', async () => {
harness.ctx._session.terminalBuffer = 'hello world\nlater accumulated history';
harness.ctx._session.mode = 'codex';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible tmux pane only\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain('hello world');
expect(body.data.terminalBuffer).toContain('later accumulated history');
expect(body.data.terminalBuffer).toContain('\x1b[H\x1b[2Jvisible tmux pane only');
expect(body.data.terminalBuffer.indexOf('hello world')).toBeLessThan(
body.data.terminalBuffer.indexOf('visible tmux pane only')
);
expect(harness.ctx.mux.capturePaneBuffer).toHaveBeenCalledWith(harness.ctx._session.muxName);
});
it('treats stale Codex scrollback config as TUI replay', async () => {
harness.ctx._session.terminalBuffer = 'hello world\nlater accumulated history';
harness.ctx._session.mode = 'codex';
harness.ctx._session.codexConfig = { renderMode: 'scrollback' } as any;
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible tmux pane only\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain('hello world');
expect(body.data.terminalBuffer).toContain('later accumulated history');
expect(body.data.terminalBuffer).toContain('\x1b[H\x1b[2Jvisible tmux pane only');
expect(body.data.terminalBuffer.indexOf('hello world')).toBeLessThan(
body.data.terminalBuffer.indexOf('visible tmux pane only')
);
expect(harness.ctx.mux.capturePaneBuffer).toHaveBeenCalledWith(harness.ctx._session.muxName);
});
it('preserves one-time OAuth authorization URLs in Codex TUI replay history', async () => {
const authUrl =
'https://auth.atlassian.com/authorize?response_type=code&client_id=abc&redirect_uri=http%3A%2F%2F127.0.0.1%3A35547%2Fcallback%2Fxyz';
harness.ctx._session.terminalBuffer =
'Authorize `atlassian` by opening this URL in your browser:\n' +
authUrl +
'\n(Browser launch failed; please copy the URL above manually.)\n';
harness.ctx._session.mode = 'codex';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible tmux pane only\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain(authUrl);
expect(body.data.terminalBuffer).toContain('visible tmux pane only');
expect(body.data.terminalBuffer.indexOf(authUrl)).toBeLessThan(
body.data.terminalBuffer.indexOf('visible tmux pane only')
);
});
it('preserves incidental OAuth URL mentions as ordinary Codex TUI history', async () => {
const authUrl =
'https://auth.atlassian.com/authorize?response_type=code&client_id=abc&redirect_uri=http%3A%2F%2F127.0.0.1%3A35547%2Fcallback%2Fxyz';
harness.ctx._session.terminalBuffer =
'Root cause: URLs like ' +
authUrl +
' could be present in history but missing from browser-rendered terminal replay.\n' +
"+ 'Authorize `atlassian` by opening this URL in your browser:\\n' +\n";
harness.ctx._session.mode = 'codex';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible tmux pane only\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain(authUrl);
expect(body.data.terminalBuffer).toContain('visible tmux pane only');
});
it('preserves accumulated history before a live mux pane snapshot for non-Codex sessions', async () => {
harness.ctx._session.terminalBuffer = 'hello world\nlater accumulated history';
harness.ctx._session.mode = 'claude';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible tmux pane only\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain('hello world');
expect(body.data.terminalBuffer).toContain('later accumulated history');
expect(body.data.terminalBuffer).toContain('visible tmux pane only');
expect(body.data.terminalBuffer).toContain('\x1b[H\x1b[2Jvisible tmux pane only');
expect(body.data.terminalBuffer.indexOf('hello world')).toBeLessThan(
body.data.terminalBuffer.indexOf('visible tmux pane only')
);
expect(harness.ctx.mux.capturePaneBuffer).toHaveBeenCalledWith(harness.ctx._session.muxName);
});
it('uses live mux pane capture only when the accumulated buffer is empty', async () => {
harness.ctx._session.terminalBuffer = '';
harness.ctx._session.mode = 'codex';
(harness.ctx.mux as { capturePaneBuffer?: unknown }).capturePaneBuffer = vi.fn(
() => 'visible restored tmux pane\n› current prompt'
);
const res = await harness.app.inject({
method: 'GET',
url: `/api/sessions/${harness.ctx._sessionId}/terminal`,
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.data.terminalBuffer).toContain('visible restored tmux pane');
expect(body.data.terminalBuffer).toContain('› current prompt');
expect(harness.ctx.mux.capturePaneBuffer).toHaveBeenCalledWith(harness.ctx._session.muxName);
});
it('returns error for unknown session', async () => {
const res = await harness.app.inject({
method: 'GET',
@@ -765,6 +926,76 @@ describe('session-routes', () => {
// ========== POST /api/sessions (with resumeSessionId) ==========
describe('POST /api/sessions with resumeSessionId', () => {
it('creates session from a remote case without local stat validation', async () => {
remoteStore.hosts = [
{
id: 'gpu-box',
label: 'GPU Box',
host: '10.0.0.42',
username: 'ubuntu',
commands: { codex: 'exec codx personal' },
},
];
remoteStore.cases = [{ name: 'gpu-work', type: 'remote', hostId: 'gpu-box', remotePath: '/home/ubuntu/work' }];
const res = await harness.app.inject({
method: 'POST',
url: '/api/sessions',
payload: { caseName: 'gpu-work', mode: 'shell', name: 'Remote Shell' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data.session.workingDir).toBe('/home/ubuntu/work');
expect(body.data.session.remote).toMatchObject({
hostId: 'gpu-box',
host: '10.0.0.42',
username: 'ubuntu',
remotePath: '/home/ubuntu/work',
commands: { codex: 'exec codx personal' },
});
});
it('quick-start creates remote case sessions through ssh metadata', async () => {
const startShell = vi.spyOn(Session.prototype, 'startShell').mockResolvedValue(undefined);
try {
remoteStore.hosts = [
{
id: 'gpu-box',
label: 'GPU Box',
host: '10.0.0.42',
username: 'ubuntu',
commands: { codex: 'exec codx personal' },
},
];
remoteStore.cases = [{ name: 'gpu-work', type: 'remote', hostId: 'gpu-box', remotePath: '/home/ubuntu/work' }];
const res = await harness.app.inject({
method: 'POST',
url: '/api/quick-start',
payload: { caseName: 'gpu-work', mode: 'shell' },
});
expect(res.statusCode).toBe(200);
const body = JSON.parse(res.body);
expect(body.success).toBe(true);
expect(body.data.casePath).toBe('/home/ubuntu/work');
const session = [...harness.ctx.sessions.values()].find((item) => item.id === body.data.sessionId);
expect(session?.toState()).toMatchObject({
workingDir: '/home/ubuntu/work',
remote: expect.objectContaining({
hostId: 'gpu-box',
host: '10.0.0.42',
username: 'ubuntu',
remotePath: '/home/ubuntu/work',
}),
});
} finally {
startShell.mockRestore();
}
});
it('creates session with valid resumeSessionId', async () => {
const res = await harness.app.inject({
method: 'POST',
+14 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { Session } from '../src/session.js';
import { resolveMuxAttachCwd, Session } from '../src/session.js';
import type { SessionAttachmentHistoryItem } from '../src/types/session.js';
import {
ATTACHMENT_HISTORY_LIMIT,
@@ -161,4 +161,17 @@ describe('session attachment history', () => {
expect(persisted).toHaveLength(1);
expect(persisted?.[0].fileName).toBe('ok.png');
});
it('attaches remote mux sessions from a local cwd', () => {
expect(
resolveMuxAttachCwd('/Users/remote/project', {
hostId: 'mac-mini',
label: 'Mac Mini',
host: '192.168.21.109',
username: 'saqebakhter',
remotePath: '/Users/remote/project',
})
).toBe('/tmp');
expect(resolveMuxAttachCwd('/opt/projects/Codeman')).toBe('/opt/projects/Codeman');
});
});
+45 -1
View File
@@ -8,7 +8,13 @@
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { TmuxManager, formatPaneSnapshot, parsePaneList, resolveActivePaneTarget } from '../src/tmux-manager.js';
import {
TmuxManager,
buildRemoteLaunchCommand,
formatPaneSnapshot,
parsePaneList,
resolveActivePaneTarget,
} from '../src/tmux-manager.js';
import { execSync, exec } from 'node:child_process';
// ============================================================================
@@ -91,6 +97,44 @@ describe('TmuxManager (unit)', () => {
});
});
describe('remote launch command builder', () => {
it('wraps codex command overrides in ssh with remote cd', () => {
const command = buildRemoteLaunchCommand({
mode: 'codex',
remote: {
hostId: 'gpu-box',
label: 'GPU Box',
host: '10.0.0.42',
username: 'ubuntu',
remotePath: '/home/ubuntu/work',
commands: { codex: 'exec codx personal' },
},
});
expect(command).toContain('ssh');
expect(command).toContain('BatchMode=yes');
expect(command).toContain('ubuntu@10.0.0.42');
expect(command).toContain('/home/ubuntu/work');
expect(command).toContain('bash -lc');
expect(command).toContain('exec codx personal');
});
it('uses default shell command when no override is configured', () => {
const command = buildRemoteLaunchCommand({
mode: 'shell',
remote: {
hostId: 'gpu-box',
label: 'GPU Box',
host: '10.0.0.42',
username: 'ubuntu',
remotePath: '/home/ubuntu/work',
},
});
expect(command).toContain('exec bash -l');
});
});
describe('getAttachCommand', () => {
it('should return tmux', () => {
expect(manager.getAttachCommand()).toBe('tmux');