fix(tmux-panes): 60/40 layout with stacked right-column panes
First subagent gets a horizontal 60/40 split (main left, agent right). Subsequent subagents split vertically within the right column so they stack. rightColumnPanes tracks right-column pane order so each new split targets the last pane in the stack.
This commit is contained in:
@@ -6,7 +6,11 @@ import { spawn } from "bun"
|
||||
*
|
||||
* When opencode spawns a background subagent, this plugin automatically opens
|
||||
* a new tmux pane showing that subagent's live TUI via `opencode attach`.
|
||||
* The pane closes when the subagent session ends.
|
||||
*
|
||||
* Layout:
|
||||
* - First subagent: horizontal 60/40 split — main pane on left, subagent on right
|
||||
* - Additional subagents: stacked vertically in the right column
|
||||
* - Panes close automatically when subagent sessions end
|
||||
*
|
||||
* Only activates when running inside a tmux session (TMUX env var is set).
|
||||
*/
|
||||
@@ -21,6 +25,10 @@ const plugin: Plugin = async (ctx) => {
|
||||
const sourcePaneId = getCurrentPaneId()
|
||||
const serverUrl = ctx.serverUrl.toString()
|
||||
|
||||
// Ordered list of pane IDs in the right column.
|
||||
// Empty = no right column yet; length > 0 = right column exists.
|
||||
const rightColumnPanes: string[] = []
|
||||
|
||||
return {
|
||||
event: async ({ event }) => {
|
||||
// Spawn a new pane when a subagent session is created
|
||||
@@ -32,24 +40,45 @@ const plugin: Plugin = async (ctx) => {
|
||||
if (sessions.has(sessionId)) return
|
||||
|
||||
const cmd = `opencode attach ${serverUrl} --session ${sessionId}`
|
||||
const proc = spawn(
|
||||
[
|
||||
|
||||
let args: string[]
|
||||
if (rightColumnPanes.length === 0) {
|
||||
// First subagent: open a horizontal split, giving the new (right)
|
||||
// pane 40% of the window width so the main pane keeps 60%.
|
||||
args = [
|
||||
"tmux",
|
||||
"split-window",
|
||||
"-h", // horizontal split
|
||||
"-h", // horizontal split — new pane appears on the right
|
||||
"-p", "40", // new pane gets 40% of window width
|
||||
"-d", // don't focus the new pane
|
||||
"-P",
|
||||
"-F",
|
||||
"#{pane_id}", // print the new pane's ID
|
||||
"#{pane_id}",
|
||||
...(sourcePaneId ? ["-t", sourcePaneId] : []),
|
||||
cmd,
|
||||
],
|
||||
{ stdout: "pipe", stderr: "pipe" },
|
||||
)
|
||||
]
|
||||
} else {
|
||||
// Additional subagents: stack vertically within the right column
|
||||
// by splitting the last right-column pane horizontally.
|
||||
const lastRightPane = rightColumnPanes[rightColumnPanes.length - 1]
|
||||
args = [
|
||||
"tmux",
|
||||
"split-window",
|
||||
"-v", // vertical split — new pane stacks below the target
|
||||
"-d", // don't focus the new pane
|
||||
"-P",
|
||||
"-F",
|
||||
"#{pane_id}",
|
||||
"-t", lastRightPane,
|
||||
cmd,
|
||||
]
|
||||
}
|
||||
|
||||
const proc = spawn(args, { stdout: "pipe", stderr: "pipe" })
|
||||
const paneId = (await new Response(proc.stdout).text()).trim()
|
||||
if ((await proc.exited) === 0 && paneId) {
|
||||
sessions.set(sessionId, paneId)
|
||||
rightColumnPanes.push(paneId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +92,8 @@ const plugin: Plugin = async (ctx) => {
|
||||
stderr: "ignore",
|
||||
})
|
||||
sessions.delete(info.id)
|
||||
const idx = rightColumnPanes.indexOf(paneId)
|
||||
if (idx !== -1) rightColumnPanes.splice(idx, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user