feat: tmux live agent discovery and launch API

- src/services/tmux.js: parse tmux panes (claude/node->codex engine match),
  collect running agents, launch agent in new or existing session via
  new-window/new-session + send-keys (window survives agent exit)
- routes: GET/POST /api/tmux/agents, GET /api/auth/check (for nginx
  auth_request), liveAgents in /api/dashboard aggregate
- tests for the pure parser

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 21:20:26 +08:00
parent bf926e327f
commit f7d6df8756
3 changed files with 143 additions and 2 deletions

46
test/tmux.test.js Normal file
View File

@@ -0,0 +1,46 @@
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', () => {
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\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 handles empty output', () => {
assert.deepEqual(parseTmuxPanes(''), []);
assert.deepEqual(parseTmuxPanes(null), []);
});
test('parseTmuxPanes ignores malformed lines', () => {
assert.deepEqual(parseTmuxPanes('garbage-without-tabs\n\n'), []);
});