fix(tmux): identify agent processes and add Qoder and OMP
This commit is contained in:
@@ -2,10 +2,10 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseTmuxPanes } from '../src/services/tmux.js';
|
||||
|
||||
test('parseTmuxPanes maps claude and node panes to engines', () => {
|
||||
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\tnode\t/home/gahow/projects/vllm',
|
||||
'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), [
|
||||
@@ -38,9 +38,9 @@ test('parseTmuxPanes drops unrelated commands', () => {
|
||||
|
||||
test('parseTmuxPanes hides temporary web terminal sessions', () => {
|
||||
const output = [
|
||||
'main\t1\tcodex:kanban\t%5\tnode\t/home/gahow/projects/kanban',
|
||||
'web-1234-5678\t1\tcodex:kanban\t%5\tnode\t/home/gahow/projects/kanban',
|
||||
'web-development\t2\tcodex:web\t%6\tnode\t/home/gahow/projects/web'
|
||||
'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), [
|
||||
@@ -71,3 +71,75 @@ test('parseTmuxPanes handles empty output', () => {
|
||||
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')), []);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user