Files
kanban/test/tmux.test.js

146 lines
5.2 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { parseTmuxPanes } from '../src/services/tmux.js';
test('parseTmuxPanes maps explicit claude and codex panes to engines', () => {
const output = [
'main\t0\tclaude:kanban\t%3\tclaude\t/home/gahow/projects/kanban',
'main\t1\tcodex:vllm\t%5\tcodex\t/home/gahow/projects/vllm',
'main\t2\tshell\t%7\tbash\t/home/gahow'
].join('\n');
assert.deepEqual(parseTmuxPanes(output), [
{
engine: 'claude',
session: 'main',
windowIndex: 0,
windowName: 'claude:kanban',
paneId: '%3',
cwd: '/home/gahow/projects/kanban'
},
{
engine: 'codex',
session: 'main',
windowIndex: 1,
windowName: 'codex:vllm',
paneId: '%5',
cwd: '/home/gahow/projects/vllm'
}
]);
});
test('parseTmuxPanes drops unrelated commands', () => {
const output = [
'dev\t0\teditor\t%1\tvim\t/home/gahow',
'dev\t1\tmonitor\t%2\thtop\t/home/gahow'
].join('\n');
assert.deepEqual(parseTmuxPanes(output), []);
});
test('parseTmuxPanes hides temporary web terminal sessions', () => {
const output = [
'main\t1\tcodex:kanban\t%5\tcodex\t/home/gahow/projects/kanban',
'web-1234-5678\t1\tcodex:kanban\t%5\tcodex\t/home/gahow/projects/kanban',
'web-development\t2\tcodex:web\t%6\tcodex\t/home/gahow/projects/web'
].join('\n');
assert.deepEqual(parseTmuxPanes(output), [
{
engine: 'codex',
session: 'main',
windowIndex: 1,
windowName: 'codex:kanban',
paneId: '%5',
cwd: '/home/gahow/projects/kanban'
},
{
engine: 'codex',
session: 'web-development',
windowIndex: 2,
windowName: 'codex:web',
paneId: '%6',
cwd: '/home/gahow/projects/web'
}
]);
});
test('parseTmuxPanes handles empty output', () => {
assert.deepEqual(parseTmuxPanes(''), []);
assert.deepEqual(parseTmuxPanes(null), []);
});
test('parseTmuxPanes ignores malformed lines', () => {
assert.deepEqual(parseTmuxPanes('garbage-without-tabs\n\n'), []);
});
function pane(command = 'node', pid = 100, name = 'renamed') {
return `main\t0\t${name}\t%1\t${command}\t/tmp\t${pid}`;
}
function processTable(command, { foreground = 101, background = '', child = '' } = {}) {
return `100 1 100 ${foreground} /bin/bash\n101 100 101 ${foreground} ${command}\n${background}\n${child}`;
}
test('ordinary Node servers and agent names in arguments/window names are not agents', () => {
for (const command of [
'node /srv/server.js codex claude omp',
'node /srv/codex/server.js',
'node -e "codex"',
'node --title codex /srv/server.js',
'node -ecodex codex',
'/bin/bash -c codex',
'bun run /srv/server.ts omp'
]) {
assert.deepEqual(parseTmuxPanes(pane('node', 100, 'codex:server'), processTable(command)), []);
}
assert.deepEqual(parseTmuxPanes(pane('node')), []);
});
test('recognizes native and runtime agent entrypoints regardless of window name', () => {
for (const [command, engine] of [
['/usr/local/bin/codex --model example', 'codex'],
['node /opt/node_modules/@openai/codex/bin/codex.js', 'codex'],
['node /home/user/.nvm/versions/node/v24.13.1/bin/codex', 'codex'],
['node --require preload.js /opt/node_modules/@anthropic-ai/claude-code/cli.js', 'claude'],
['/usr/local/bin/claude', 'claude'],
['/home/user/.local/share/claude/versions/2.1.241', 'claude'],
['/home/user/.qoder/bin/qodercli/qodercli-1.1.65', 'qoder'],
['qodercli', 'qoder'],
['/usr/local/bin/omp', 'omp'],
['bun /opt/node_modules/@oh-my-pi/pi-coding-agent/src/cli.ts', 'omp'],
['node /opt/node_modules/@oh-my-pi/pi-coding-agent/dist/cli.js', 'omp']
]) {
const agents = parseTmuxPanes(pane(), processTable(command));
assert.equal(agents.length, 1, command);
assert.equal(agents[0].engine, engine, command);
assert.equal(agents[0].windowName, 'renamed');
}
});
test('only inspects this pane and its foreground job, ignoring stopped/background agents', () => {
const output = processTable('node /srv/server.js', {
background: '102 100 102 101 codex\n200 1 200 201 /bin/bash\n201 200 201 201 claude'
});
assert.deepEqual(parseTmuxPanes(pane(), output), []);
assert.deepEqual(parseTmuxPanes(pane(), processTable('codex', { foreground: 100 })), []);
});
test('keeps the parent agent visible while it runs a foreground tool', () => {
const output = processTable('codex', {
foreground: 102,
child: '102 101 102 102 node /srv/server.js'
});
assert.equal(parseTmuxPanes(pane('node'), output)[0].engine, 'codex');
});
test('uses exact Linux argv to distinguish runtime entrypoint from prompt text', () => {
const argv = new Map([[101, ['node', '-e', 'console.log("hello")', 'codex']]]);
assert.deepEqual(parseTmuxPanes(pane(), processTable('node -e console.log("hello") codex'), argv), []);
argv.set(101, ['bun', '/opt/space dir/node_modules/@oh-my-pi/pi-coding-agent/src/cli.ts']);
assert.equal(parseTmuxPanes(pane(), processTable('bun /opt/space dir/...'), argv)[0].engine, 'omp');
});
test('expired pane PIDs and empty process snapshots do not reuse stale command names', () => {
assert.deepEqual(parseTmuxPanes(pane('codex'), ''), []);
assert.deepEqual(parseTmuxPanes(pane('codex', 999), processTable('codex')), []);
});