terminals: re-attach on reopen, and make orphaned shells findable
Closing a terminal panel abandoned its shell. TerminalWrapper deleted the panel -> session mapping on *unmount*, so any layout or route change generated a fresh uuid on the way back and left the old shell running: alive, unreachable, and never killed, because nothing has ever sent pty:close. The mapping now outlives the mount, so reopening a panel re-attaches to the shell you left — which is also what finally makes the sidecar's replay buffer worth having. It is persisted dashboard state, so this survives a reload too. That trades an invisible leak for a visible one: a panel deleted for good still leaves its shell behind. So `pty:list` now enumerates live sessions, and GET /api/terminal/sessions + DELETE /api/terminal/sessions/:id expose them. pty:close finally has a sender. Each session carries createdAt, lastActivityAt, pid, and the title the shell sets for itself via OSC 0/2 — usually the running command, which is what turns "some uuid" into "the one running claude" when you are deciding what to kill. Killing on unmount is still not an option: it needs the panel system to distinguish a real close from an incidental remount, which it cannot currently do. Also raises the sidecar replay buffer from 50KB to 512KB — 50KB was about one long agent turn, so reconnecting mid-task showed you the tail and nothing before it — and cuts the buffer on a line boundary rather than a byte offset. A blind slice can land inside an escape sequence, and the replay then opens with the tail of a colour or cursor-move code, which xterm renders as garbage or applies as a real instruction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import { transmissionRouter } from './api/transmission/router';
|
||||
import { invoiceshelfRouter } from './api/invoiceshelf/router';
|
||||
import { walletRouter } from './api/wallet/router';
|
||||
import { vpnRouter } from './api/vpn/router';
|
||||
import { terminalRouter } from './api/terminal/router';
|
||||
import { systemMonitorRouter } from './api/system-monitor/system-monitor';
|
||||
import { activityRouter } from './api/activity/router';
|
||||
import './api/music/sidecar-server'; // side-effect: capture the officer-music audio server port
|
||||
@@ -113,6 +114,7 @@ protectedRouter.route('/task-logs', taskLogsRouter);
|
||||
protectedRouter.route('/file-browser', fileBrowserRouter);
|
||||
protectedRouter.route('/music', musicRouter);
|
||||
protectedRouter.route('/slskd', slskdRouter);
|
||||
protectedRouter.route('/terminal', terminalRouter);
|
||||
protectedRouter.route('/headscale', headscaleRouter);
|
||||
protectedRouter.route('/transmission', transmissionRouter);
|
||||
protectedRouter.route('/invoiceshelf', invoiceshelfRouter);
|
||||
|
||||
Reference in New Issue
Block a user