Land staged session/MCP/chrome work + sidebar clear-J fix
This batches the in-flight [Unreleased] block from CHANGELOG.md into a single commit. Highlights: - Real MCP protocol layer (initialize / tools/list / tools/call) so vendor MCP clients can complete the handshake against the per-PID socket. Legacy direct-dispatch preserved for the harness. - New mcp_injection kinds — cli_override for codex, config_env for opencode — joining the existing env-var and config_file paths so patterm can slot into more agents without touching their real config or auth. - Ctrl+A/D and Ctrl+W/S focus navigation across tabs and intra-tab process lists, recognised in legacy / kitty CSI u / xterm modifyOtherKeys encodings. - Palette macros (sw / k / sp ) and reordering so open sessions surface above spawn-new entries. - Two-row tab bar, sidebar/tabbar/status chrome cache, viewport-wipe on agent spawn, CR-terminated orchestrator injections, and split- Enter PTY writes so paste-detecting TUIs see Enter as a key event. Also fixes the bug logged in TODO: claude's Ctrl+O tool-call expansion emits CSI 0 J, which the viewport renderer was forwarding verbatim — wiping the sidebar to the right of the cursor and leaving the chrome cache convinced nothing had changed. CSI 0 J and CSI 1 J are now translated into per-row ECH sequences clamped to the viewport, same as CSI 2 J and CSI K already were. Agent guides (CLAUDE.md / AGENTS.md) now spell out the TODO->CHANGELOG workflow so completed items land in the changelog rather than as ticked entries left behind in TODO.
This commit is contained in:
@@ -57,3 +57,96 @@ func firstRunningTopLevel(children []*Child) *Child {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runningTopLevels lists every running top-level session in the order
|
||||
// they appear in the snapshot — the same order the tab bar uses, so
|
||||
// Ctrl+A/D navigation matches what the user sees on screen.
|
||||
func runningTopLevels(children []*Child) []*Child {
|
||||
out := make([]*Child, 0, len(children))
|
||||
for _, c := range children {
|
||||
if c.ParentID == "" && c.Status() == StatusRunning {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// nextTabID returns the id of the top-level session `step` positions
|
||||
// away from the current focus in the runningTopLevels list, wrapping
|
||||
// at both ends. Returns "" when there's nothing to switch to.
|
||||
func nextTabID(children []*Child, focusID string, step int) string {
|
||||
roots := runningTopLevels(children)
|
||||
if len(roots) == 0 {
|
||||
return ""
|
||||
}
|
||||
rootID := activeRootID(children, focusID)
|
||||
idx := -1
|
||||
for i, r := range roots {
|
||||
if r.ID == rootID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
idx = (idx + step) % len(roots)
|
||||
if idx < 0 {
|
||||
idx += len(roots)
|
||||
}
|
||||
if roots[idx].ID == focusID {
|
||||
return ""
|
||||
}
|
||||
return roots[idx].ID
|
||||
}
|
||||
|
||||
// currentTabFlat returns the focused tab's processes (root first, then
|
||||
// its running children) in display order. Used to step focus with
|
||||
// Ctrl+W/S.
|
||||
func currentTabFlat(children []*Child, focusID string) []*Child {
|
||||
rootID := activeRootID(children, focusID)
|
||||
if rootID == "" {
|
||||
return nil
|
||||
}
|
||||
out := make([]*Child, 0, 4)
|
||||
for _, c := range children {
|
||||
if c.ID == rootID && c.Status() == StatusRunning {
|
||||
out = append(out, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, c := range children {
|
||||
if c.ParentID == rootID && c.Status() == StatusRunning {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// nextChildID returns the process id `step` positions away from the
|
||||
// current focus inside its tab, wrapping at both ends. Empty when
|
||||
// there's only one process in the tab.
|
||||
func nextChildID(children []*Child, focusID string, step int) string {
|
||||
flat := currentTabFlat(children, focusID)
|
||||
if len(flat) < 2 {
|
||||
return ""
|
||||
}
|
||||
idx := -1
|
||||
for i, c := range flat {
|
||||
if c.ID == focusID {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
idx = (idx + step) % len(flat)
|
||||
if idx < 0 {
|
||||
idx += len(flat)
|
||||
}
|
||||
if flat[idx].ID == focusID {
|
||||
return ""
|
||||
}
|
||||
return flat[idx].ID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user