Bundles the in-flight work into the first tagged release. See CHANGELOG.md `[0.0.1] - 2026-05-14` for the full per-change list. Highlights: - Sidebar / chrome stability: clamp absolute cursor positioning and printable bytes to the viewport so long-running TUIs (claude, codex) can't spray into the right rail; bound tab bar's row clear to the viewport width so the rail isn't wiped on every tab redraw; flag scroll escapes (RI/IND/NEL/SU/SD/IL/DL) and clamp `CSI 0/1/2 J`/`K` to viewport columns. - Palette: "Spawn process…" form, macros (`sw `, `k `, `sp `), kill entries mark the focused tab, dead agents drop out of the switch list. - Sidebar: split into Processes (session-wide) + Agent Tree (per-active-agent) sections; relaunch indicator; Ctrl+W/S walks the combined list, Ctrl+A/D steps tabs. - MCP: protocol handshake (`initialize`, `tools/list`, `tools/call`, `ping`), `mcp_injection.kind = cli_override / config_env` so codex and opencode pick up the server with no file writes, `lifecycle` help topic and tool-description cleanup-duty pointers. - Lifecycle: orchestrator-spawned children cascade-killed when the parent dies; orchestrator-injected prompts end with CR + delayed Enter so claude submits cleanly.
This commit is contained in:
@@ -1,5 +1,32 @@
|
||||
package app
|
||||
|
||||
// visibleAgentTree returns the running entries under the active agent
|
||||
// tab (root agent + its sub-agents). With the new Processes pane,
|
||||
// command processes live in their own section and never show up here —
|
||||
// the agent tree is for KindAgent (and KindTerminal sub-entries) only.
|
||||
func visibleAgentTree(children []*Child, activeAgentID string) []*Child {
|
||||
if activeAgentID == "" {
|
||||
return nil
|
||||
}
|
||||
out := make([]*Child, 0, len(children))
|
||||
for _, c := range children {
|
||||
if c.Status() != StatusRunning {
|
||||
continue
|
||||
}
|
||||
if c.Kind == KindCommand && c.ParentID == "" {
|
||||
continue
|
||||
}
|
||||
if c.ID == activeAgentID || c.ParentID == activeAgentID {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// visibleSessionTree is retained for the test suite and any pre-Processes
|
||||
// callers — it returns the active agent's tree given the focused id,
|
||||
// resolving the active agent root from focus the same way the previous
|
||||
// implementation did.
|
||||
func visibleSessionTree(children []*Child, focusID string) []*Child {
|
||||
rootID := activeRootID(children, focusID)
|
||||
if rootID == "" {
|
||||
@@ -17,12 +44,19 @@ func visibleSessionTree(children []*Child, focusID string) []*Child {
|
||||
return out
|
||||
}
|
||||
|
||||
// activeRootID resolves the agent root the user is "inside" right now.
|
||||
// If focus is on a sub-agent, it walks up. If focus is on a top-level
|
||||
// process (KindCommand), it falls through to the first running agent
|
||||
// root so the agent tree section keeps showing something coherent.
|
||||
func activeRootID(children []*Child, focusID string) string {
|
||||
if focusID != "" {
|
||||
for _, c := range children {
|
||||
if c.ID != focusID {
|
||||
continue
|
||||
}
|
||||
if c.Kind == KindCommand && c.ParentID == "" {
|
||||
break
|
||||
}
|
||||
if c.ParentID == "" {
|
||||
return c.ID
|
||||
}
|
||||
@@ -32,7 +66,14 @@ func activeRootID(children []*Child, focusID string) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return firstRunningAgentID(children)
|
||||
}
|
||||
|
||||
func firstRunningAgentID(children []*Child) string {
|
||||
for _, c := range children {
|
||||
if c.Kind != KindAgent {
|
||||
continue
|
||||
}
|
||||
if c.ParentID == "" && c.Status() == StatusRunning {
|
||||
return c.ID
|
||||
}
|
||||
@@ -40,6 +81,23 @@ func activeRootID(children []*Child, focusID string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// processList returns every top-level command/terminal entry in spawn
|
||||
// order, regardless of running state. The Processes sidebar section
|
||||
// keeps showing exited entries so the user can see what just died (and
|
||||
// because Session retains KindCommand entries for restart).
|
||||
func processList(children []*Child) []*Child {
|
||||
out := make([]*Child, 0, len(children))
|
||||
for _, c := range children {
|
||||
if c.ParentID != "" {
|
||||
continue
|
||||
}
|
||||
if c.Kind == KindCommand || c.Kind == KindTerminal {
|
||||
out = append(out, c)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findChildInSnapshot(children []*Child, id string) *Child {
|
||||
for _, c := range children {
|
||||
if c.ID == id {
|
||||
@@ -58,12 +116,16 @@ 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.
|
||||
// runningTopLevels lists every running top-level agent session in the
|
||||
// order they appear in the snapshot. Tabs only show agents — command
|
||||
// processes live in the Processes sidebar section — so Ctrl+A/D
|
||||
// navigation cycles through agent tabs exclusively.
|
||||
func runningTopLevels(children []*Child) []*Child {
|
||||
out := make([]*Child, 0, len(children))
|
||||
for _, c := range children {
|
||||
if c.Kind != KindAgent {
|
||||
continue
|
||||
}
|
||||
if c.ParentID == "" && c.Status() == StatusRunning {
|
||||
out = append(out, c)
|
||||
}
|
||||
@@ -123,11 +185,29 @@ func currentTabFlat(children []*Child, focusID string) []*Child {
|
||||
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)
|
||||
// sidebarNavList combines the Processes section and the active Agent
|
||||
// Tree into one flat list — top-to-bottom matching what the user sees
|
||||
// in the sidebar. Ctrl+W/S walks this list so the user can step out of
|
||||
// the agent tree, into the Processes section, and back.
|
||||
func sidebarNavList(children []*Child, activeAgentID string) []*Child {
|
||||
out := make([]*Child, 0, 8)
|
||||
for _, c := range processList(children) {
|
||||
if c.Status() != StatusRunning {
|
||||
continue
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
for _, c := range visibleAgentTree(children, activeAgentID) {
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// nextChildID returns the id `step` positions away from the current
|
||||
// focus in the combined Processes + active-agent-tree navigation list,
|
||||
// wrapping at both ends. Empty when there's nothing else to land on.
|
||||
func nextChildID(children []*Child, focusID, activeAgentID string, step int) string {
|
||||
flat := sidebarNavList(children, activeAgentID)
|
||||
if len(flat) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user