fix(tmux-subagent): enable 2D grid layout with divider-aware calculations
- Account for tmux pane dividers (1 char) in all size calculations - Reduce MIN_PANE_WIDTH from 53 to 52 to fit 2 columns in standard terminals - Fix enforceMainPaneWidth to use (windowWidth - divider) / 2 - Add virtual mainPane handling for close-spawn eviction loop - Add comprehensive decision-engine tests (23 test cases)
This commit is contained in:
@@ -3,15 +3,10 @@ import type { WindowState, TmuxPaneInfo } from "./types"
|
||||
import { getTmuxPath } from "../../tools/interactive-bash/utils"
|
||||
import { log } from "../../shared"
|
||||
|
||||
/**
|
||||
* Query the current window state from tmux.
|
||||
* This is the source of truth - not our internal cache.
|
||||
*/
|
||||
export async function queryWindowState(sourcePaneId: string): Promise<WindowState | null> {
|
||||
const tmux = await getTmuxPath()
|
||||
if (!tmux) return null
|
||||
|
||||
// Get window width and all panes in the current window
|
||||
const proc = spawn(
|
||||
[
|
||||
tmux,
|
||||
@@ -19,7 +14,7 @@ export async function queryWindowState(sourcePaneId: string): Promise<WindowStat
|
||||
"-t",
|
||||
sourcePaneId,
|
||||
"-F",
|
||||
"#{pane_id},#{pane_width},#{pane_left},#{pane_title},#{pane_active},#{window_width}",
|
||||
"#{pane_id},#{pane_width},#{pane_height},#{pane_left},#{pane_top},#{pane_title},#{pane_active},#{window_width},#{window_height}",
|
||||
],
|
||||
{ stdout: "pipe", stderr: "pipe" }
|
||||
)
|
||||
@@ -36,33 +31,43 @@ export async function queryWindowState(sourcePaneId: string): Promise<WindowStat
|
||||
if (lines.length === 0) return null
|
||||
|
||||
let windowWidth = 0
|
||||
let windowHeight = 0
|
||||
const panes: TmuxPaneInfo[] = []
|
||||
|
||||
for (const line of lines) {
|
||||
const [paneId, widthStr, leftStr, title, activeStr, windowWidthStr] = line.split(",")
|
||||
const [paneId, widthStr, heightStr, leftStr, topStr, title, activeStr, windowWidthStr, windowHeightStr] = line.split(",")
|
||||
const width = parseInt(widthStr, 10)
|
||||
const height = parseInt(heightStr, 10)
|
||||
const left = parseInt(leftStr, 10)
|
||||
const top = parseInt(topStr, 10)
|
||||
const isActive = activeStr === "1"
|
||||
windowWidth = parseInt(windowWidthStr, 10)
|
||||
windowHeight = parseInt(windowHeightStr, 10)
|
||||
|
||||
if (!isNaN(width) && !isNaN(left)) {
|
||||
panes.push({ paneId, width, left, title, isActive })
|
||||
if (!isNaN(width) && !isNaN(left) && !isNaN(height) && !isNaN(top)) {
|
||||
panes.push({ paneId, width, height, left, top, title, isActive })
|
||||
}
|
||||
}
|
||||
|
||||
// Sort panes by left position (leftmost first)
|
||||
panes.sort((a, b) => a.left - b.left)
|
||||
panes.sort((a, b) => a.left - b.left || a.top - b.top)
|
||||
|
||||
// The main pane is the leftmost pane (where opencode runs)
|
||||
// Agent panes are all other panes to the right
|
||||
const mainPane = panes.find((p) => p.paneId === sourcePaneId) ?? panes[0] ?? null
|
||||
const agentPanes = panes.filter((p) => p.paneId !== mainPane?.paneId)
|
||||
const mainPane = panes.find((p) => p.paneId === sourcePaneId)
|
||||
if (!mainPane) {
|
||||
log("[pane-state-querier] CRITICAL: sourcePaneId not found in panes", {
|
||||
sourcePaneId,
|
||||
availablePanes: panes.map((p) => p.paneId),
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const agentPanes = panes.filter((p) => p.paneId !== mainPane.paneId)
|
||||
|
||||
log("[pane-state-querier] window state", {
|
||||
windowWidth,
|
||||
mainPane: mainPane?.paneId,
|
||||
windowHeight,
|
||||
mainPane: mainPane.paneId,
|
||||
agentPaneCount: agentPanes.length,
|
||||
})
|
||||
|
||||
return { windowWidth, mainPane, agentPanes }
|
||||
return { windowWidth, windowHeight, mainPane, agentPanes }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user