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
169 changes: 169 additions & 0 deletions desktop/electron/src/localagent/codexattach.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/// End-to-end against a REAL codex app-server (vision-parity L4b).
///
/// The unit tests fake both transports, which proves the plumbing and proves
/// nothing about the handshake — and the handshake is exactly where L4a went
/// wrong. It shipped `codex app-server proxy --sock <path>` as the daemon rung
/// on the strength of that subcommand's help text; against a live daemon the
/// proxy relays raw bytes without the WebSocket upgrade the control socket
/// requires, so the socket closes and the proxy exits **0 with no output**. A
/// dead channel that looks exactly like a quiet agent.
///
/// So this file exists to ask the one question no fixture can answer: does a
/// frame we send come back answered?
///
/// **Opt-in.** It brings up a shared background daemon on the operator's
/// machine — cheap (no model turn, no tokens) but a real side effect. So:
///
/// TERMIPOD_CODEX_ATTACH_E2E=1 npm test
///
/// If the daemon was not already running, the test stops it again afterwards
/// and leaves the box as it found it. Skipped without the variable, and skipped
/// with a clear reason when codex is absent or was not installed by the
/// official installer script (no managed standalone → no daemon rung at all,
/// which is the common case and not a failure).
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';

import {
codexBinary,
managedCodexPath,
codexHome,
planCodexAttach,
} from './codexattach.ts';
import { openCodexChannel, type CodexFrame } from './codexchannel.ts';

const ENABLED = process.env['TERMIPOD_CODEX_ATTACH_E2E'] === '1';

const HOME = os.homedir();
const CHOME = codexHome(HOME, process.env);
const BIN = codexBinary(HOME, process.env);

function managedInstallPresent(): boolean {
return fs.existsSync(managedCodexPath(CHOME));
}

/// Is the daemon up right now? `daemon version` is the vendor's own client for
/// the control socket, so this is their answer, not ours.
function daemonRunning(): boolean {
try {
const out = execFileSync(BIN, ['app-server', 'daemon', 'version'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 30_000,
});
return (JSON.parse(out) as { status?: string }).status === 'running';
} catch {
return false;
}
}

function skipReason(): string | null {
if (!ENABLED) return 'set TERMIPOD_CODEX_ATTACH_E2E=1 to run the live codex attach test';
if (BIN === 'codex') {
try {
execFileSync('codex', ['--version'], { stdio: 'ignore', timeout: 30_000 });
} catch {
return 'codex is not installed';
}
}
if (!managedInstallPresent()) {
return `no installer-managed codex at ${managedCodexPath(CHOME)} — this box has no daemon rung`;
}
return null;
}

test('the daemon rung carries a real JSON-RPC round trip', async (t) => {
const skip = skipReason();
if (skip !== null) {
t.skip(skip);
return;
}

const wasRunning = daemonRunning();
const plan = planCodexAttach(HOME, { managedInstallPresent: true, bin: BIN }, process.env);
assert.equal(plan.mode, 'daemon', 'a managed install must take the daemon rung');

const frames: CodexFrame[] = [];
let resolveInit: (() => void) | undefined;
const initialized = new Promise<void>((r) => {
resolveInit = r;
});

const channel = await openCodexChannel(
plan,
{
onFrame: (f) => {
frames.push(f);
if (f['id'] === 1 && f['result'] !== undefined) resolveInit?.();
},
onClose: () => {},
},
{ cwd: process.cwd(), connectTimeoutMs: 30_000 },
);

try {
// If this reports 'spawn' we fell back, which means the socket handshake
// failed — the precise failure L4a shipped and could not see.
assert.equal(channel.mode, 'daemon', `expected the daemon rung, got ${channel.mode}: ${channel.reason}`);

channel.send({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: { clientInfo: { name: 'termipod', title: 'TermiPod', version: '0.1.0' } },
});

await Promise.race([
initialized,
new Promise((_, reject) => setTimeout(() => reject(new Error('no initialize result within 30s')), 30_000)),
]);

const result = frames.find((f) => f['id'] === 1)?.['result'] as Record<string, unknown> | undefined;
assert.ok(result !== undefined, 'initialize must be answered');
// The server's own view of where it lives — proof the answer came from a
// real app-server and not from an echo.
assert.equal(result['codexHome'], CHOME);
} finally {
channel.close();
if (!wasRunning) {
try {
execFileSync(BIN, ['app-server', 'daemon', 'stop'], { stdio: 'ignore', timeout: 30_000 });
} catch {
/* best effort — leaving a daemon up is untidy, not broken */
}
}
}
});

test('closing our channel leaves the shared daemon running', async (t) => {
const skip = skipReason();
if (skip !== null) {
t.skip(skip);
return;
}

const wasRunning = daemonRunning();
const plan = planCodexAttach(HOME, { managedInstallPresent: true, bin: BIN }, process.env);
const channel = await openCodexChannel(
plan,
{ onFrame: () => {}, onClose: () => {} },
{ cwd: process.cwd(), connectTimeoutMs: 30_000 },
);
channel.close();

// The rung's entire promise is that the session outlives the app. If closing
// a client took the daemon down, every "your session survives" claim in the
// UI would be false.
assert.equal(daemonRunning(), true, 'the daemon must survive a client disconnect');

if (!wasRunning) {
try {
execFileSync(BIN, ['app-server', 'daemon', 'stop'], { stdio: 'ignore', timeout: 30_000 });
} catch {
/* best effort */
}
}
});
98 changes: 77 additions & 21 deletions desktop/electron/src/localagent/codexattach.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/// L4a attach-rung checks. Every expectation here was measured against
/// codex-cli 0.147.0 on 2026-08-16 (`codex app-server --help`, `... daemon
/// --help`, `... proxy --help`, and a real `daemon start` refusal), not read
/// from the plan — whose L4 line described a WebSocket and a bearer scheme that
/// do not exist. Run with `node --test`.
/// L4a/L4b attach-rung checks. Every expectation here was measured against
/// codex-cli 0.147.0 (`codex app-server --help`, `... daemon --help`, a real
/// `daemon start`, and a logging relay placed between the CLI and its own
/// control socket), not read from the plan. Run with `node --test`.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import {
codexBinary,
codexBinDirs,
codexHome,
controlSocketPath,
findCodexOnPath,
managedCodexPath,
MAX_UNIX_SOCKET_PATH,
planCodexAttach,
Expand All @@ -33,25 +35,26 @@ test('no installer-managed codex: spawn, because `daemon start` would refuse', (
// no managed standalone install, and `daemon start` fails outright on it.
const p = planCodexAttach(HOME, { managedInstallPresent: false }, {});
assert.equal(p.mode, 'spawn');
assert.deepEqual(p.argv, ['codex', 'app-server']);
assert.equal(p.startArgv, undefined, 'spawn needs no preparation step');
assert.deepEqual(p.mode === 'spawn' ? p.argv : null, ['codex', 'app-server']);
assert.match(p.reason, /packages\/standalone\/current\/codex/);
});

test('installer-managed codex: attach to the shared daemon via the stdio proxy', () => {
test('installer-managed codex: the daemon rung is a SOCKET, never an argv', () => {
const p = planCodexAttach(HOME, { managedInstallPresent: true }, {});
assert.equal(p.mode, 'daemon');
// The transport is a Unix socket reached through `app-server proxy` — there
// is no WebSocket URL and no token anywhere in this argv.
assert.deepEqual(p.argv, [
'codex',
'app-server',
'proxy',
'--sock',
'/home/u/.codex/app-server-control/app-server-control.sock',
]);
if (p.mode !== 'daemon') return;
assert.equal(p.socketPath, '/home/u/.codex/app-server-control/app-server-control.sock');
// Bringing the daemon UP is a command; talking to it is not.
assert.deepEqual(p.startArgv, ['codex', 'app-server', 'daemon', 'start']);
assert.ok(!p.argv.some((a) => /ws:|wss:|token|bearer/i.test(a)), 'no WebSocket or bearer anywhere in the argv');
// L4a shipped `app-server proxy --sock <path>` as the data path. Measured
// against the live daemon, that subcommand relays raw stdin bytes without the
// WebSocket upgrade the socket requires, so the daemon closes the connection
// and the proxy exits 0 with no output at all. Nothing may reintroduce it.
assert.ok(!('argv' in p), 'a daemon plan must not carry an argv to run');
assert.ok(
!JSON.stringify(p).includes('proxy'),
'the `app-server proxy` subcommand cannot carry this protocol — it never upgrades',
);
});

test('a socket path over SUN_LEN disqualifies the daemon rung up front', () => {
Expand All @@ -78,8 +81,61 @@ test('every rung explains itself', () => {
});

test('the codex binary is overridable for a non-PATH install', () => {
// A GUI-launched Electron app does not inherit the login shell PATH — the
// problem kimiweb.ts already had to solve for kimi.
const p = planCodexAttach(HOME, { managedInstallPresent: false }, { TERMIPOD_CODEX_BIN: '/opt/bin/codex' });
assert.equal(p.argv[0], '/opt/bin/codex');
assert.equal(p.mode === 'spawn' ? p.argv[0] : null, '/opt/bin/codex');
// The override also reaches the daemon rung's start command, which is the
// only argv that rung has.
const d = planCodexAttach(HOME, { managedInstallPresent: true }, { TERMIPOD_CODEX_BIN: '/opt/bin/codex' });
assert.equal(d.mode === 'daemon' ? d.startArgv[0] : null, '/opt/bin/codex');
});

test('the well-known dirs cover the installer location a GUI app cannot see', () => {
// The official installer writes ~/.local/bin/codex and appends its PATH line
// to .bashrc — which an Electron app launched from a Dock icon never sources.
const dirs = codexBinDirs(HOME, {});
assert.ok(dirs.includes('/home/u/.local/bin'), 'the installer target must be searched');
assert.ok(
dirs.includes('/home/u/.codex/packages/standalone/current/bin'),
'the managed standalone bin dir must be searched',
);
// A relocated CODEX_HOME moves the managed dir with it.
assert.ok(codexBinDirs(HOME, { CODEX_HOME: '/srv/cx' }).includes('/srv/cx/packages/standalone/current/bin'));
});

test('findCodexOnPath returns an absolute path, and null when nothing exists', () => {
const present = new Set(['/home/u/.local/bin/codex']);
const exists = (p: string): boolean => present.has(p);
assert.equal(findCodexOnPath('/usr/bin:/bin', ['/home/u/.local/bin'], exists), '/home/u/.local/bin/codex');
// PATH wins when it has one, since that is what the user's shell would run.
present.add('/usr/bin/codex');
assert.equal(findCodexOnPath('/usr/bin:/bin', ['/home/u/.local/bin'], exists), '/usr/bin/codex');
assert.equal(findCodexOnPath('/usr/bin', [], () => false), null);
// Empty PATH segments must not produce a relative candidate like "codex".
assert.equal(findCodexOnPath('', [], () => true), null);
});

test('codexBinary prefers the override, then a real file, then the bare name', () => {
assert.equal(codexBinary(HOME, { TERMIPOD_CODEX_BIN: '/opt/x' }, () => false), '/opt/x');
assert.equal(codexBinary(HOME, { PATH: '/usr/bin' }, (p) => p === '/usr/bin/codex'), '/usr/bin/codex');
// Nothing found: hand back the bare name and let the OS have its say, rather
// than failing here on a guess about what is installed.
assert.equal(codexBinary(HOME, { PATH: '/usr/bin' }, () => false), 'codex');
});

test('codexBinary finds an installer-managed codex that is NOT on PATH', () => {
// The separating input, and the entire reason `codexBinDirs` exists: a GUI
// -launched Electron app inherits a PATH that never sourced .bashrc, so the
// official installer's ~/.local/bin/codex is invisible to it. A test whose
// codex is also on PATH cannot tell whether the well-known dirs are consulted
// at all — searching PATH alone would pass it just as well.
const onlyInInstallerDir = (p: string): boolean => p === '/home/u/.local/bin/codex';
assert.equal(
codexBinary(HOME, { PATH: '/usr/bin:/bin' }, onlyInInstallerDir),
'/home/u/.local/bin/codex',
'a codex reachable only from the installer dir must still be found',
);
// Same for the managed standalone tree, which is where the installer's
// symlink actually points.
const onlyInManagedDir = (p: string): boolean => p === '/home/u/.codex/packages/standalone/current/bin/codex';
assert.equal(codexBinary(HOME, { PATH: '/usr/bin' }, onlyInManagedDir), '/home/u/.codex/packages/standalone/current/bin/codex');
});
Loading
Loading