feat(workspace): session view console as tabs, with New terminal
The 1/2/4 split view becomes a tab bar (ConsoleTabs.tsx): one tab per live tmux slot (0-3) of the workspace, the active one being ?run=, every live tab's pane kept mounted and hidden with CSS. The + menu opens a new tab in the first free slot: New terminal (a plain shell - shell: true on POST /api/lanes/:id/start, spawnShell, tool 'shell', no run_id/status change), any installed enabled AI tool started bare, or Settings -> Agents. A tab's x stops its session. splitViewStorage, &pane= and the sidebar's Ctrl/Cmd+click are gone.
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
* @file LaneConsolePane.tsx
|
||||
* @description One lane's run console: a TerminalView on the pane's tmux
|
||||
* session, or — with no run — an empty pane whose button goes back to the
|
||||
* workspace home, the ONE place a new session is started (its prompt box,
|
||||
* `WorkspaceHome.tsx`; there is no run form in a pane any more). Moved out of
|
||||
* Workspace.tsx so the Workspace page can render 1, 2, or 4 of these side by
|
||||
* side (split terminal view). Owns its own handle/busy/runHistory state —
|
||||
* workspace home, where a session starts with a prompt (its prompt box,
|
||||
* `WorkspaceHome.tsx`; there is no run form in a pane — the tab bar's `+`,
|
||||
* `ConsoleTabs.tsx`, starts a bare CLI or a shell). Moved out of
|
||||
* Workspace.tsx so the Workspace page can keep one per TAB (slot 0–3) mounted,
|
||||
* the inactive ones hidden with CSS. Owns its own handle/busy/runHistory state —
|
||||
* nothing is shared between panes. `lanes`, `binaryStatus`, `activeRuns`, and
|
||||
* `externalSessions` are supplied as props because they are global, not
|
||||
* lane-specific, and fetching them per pane would mean N redundant identical
|
||||
@@ -16,8 +17,8 @@
|
||||
* and its live tmux session — instead of leaving the previous lane's terminal
|
||||
* on screen under a new lane's header.
|
||||
*
|
||||
* Each pane is one tmux session on its lane — split panes all show the same
|
||||
* lane, pane `slot` N owning `agenthub-lane-<id>[-N]` — so starting or resuming
|
||||
* Each pane is one tmux session on its lane — every tab shows the same
|
||||
* lane, tab `slot` N owning `agenthub-lane-<id>[-N]` — so starting or resuming
|
||||
* into a pane already holding a live run answers 409 ERUNLIVE rather than overwriting a
|
||||
* prompt in flight — `onResumeFromHistory` catches
|
||||
* that and attaches to the still-live session instead of failing outright
|
||||
@@ -65,7 +66,7 @@ export interface LaneConsolePaneProps {
|
||||
externalSessions?: Session[];
|
||||
wsConnected: boolean;
|
||||
onHasActiveRunChange?: (active: boolean) => void;
|
||||
/** Which split pane this is. Every pane shows the SAME lane, each in its own
|
||||
/** Which tab this is. Every tab shows the SAME lane, each in its own
|
||||
* tmux session: slot 0 is the lane's own, 1–3 are `agenthub-lane-<id>-<slot>`. */
|
||||
slot?: number;
|
||||
/** Runs the OTHER panes are showing — left out of this pane's Active runs so
|
||||
@@ -78,8 +79,8 @@ export interface LaneConsolePaneProps {
|
||||
attachRequest?: { runId: string; nonce: string } | null;
|
||||
/** Opens the workspace home, where a new session is started. */
|
||||
onNewSession?: () => void;
|
||||
/** The pane's run exiting goes back to the home too (a single pane). */
|
||||
homeOnExit?: boolean;
|
||||
/** The pane's run exited (the tab bar opens another tab, or the home). */
|
||||
onRunExit?: () => void;
|
||||
}
|
||||
|
||||
/** The tmux session a lane's split pane owns (see server/lib/pty-run.js). */
|
||||
@@ -103,7 +104,7 @@ export function LaneConsolePane({
|
||||
onHandleChange,
|
||||
attachRequest,
|
||||
onNewSession,
|
||||
homeOnExit = false,
|
||||
onRunExit,
|
||||
}: LaneConsolePaneProps) {
|
||||
const { t } = useTranslation("run");
|
||||
const { t: tLanes } = useTranslation("lanes");
|
||||
@@ -490,12 +491,12 @@ export function LaneConsolePane({
|
||||
runId={handle!.id}
|
||||
wsBaseUrl={window.location.origin.replace(/^http/, "ws")}
|
||||
// The pane died (double Ctrl+C out of `claude`, `exit`, a crash) —
|
||||
// a single pane goes straight back to the home's prompt box rather
|
||||
// than leaving a dead terminal the user has to click out of.
|
||||
// the tab bar moves on (another tab, or the home's prompt box)
|
||||
// rather than leaving a dead terminal the user has to click out of.
|
||||
onExit={() => {
|
||||
newRun();
|
||||
refreshList();
|
||||
if (homeOnExit) onNewSession?.();
|
||||
onRunExit?.();
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
|
||||
Reference in New Issue
Block a user