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:
@@ -41,9 +41,9 @@ func childIDs(cs []*Child) []string {
|
||||
}
|
||||
|
||||
func TestNextTabIDWrapsAndSkipsCurrent(t *testing.T) {
|
||||
r1 := testChild("c1", "root1", "", StatusRunning)
|
||||
r2 := testChild("c2", "root2", "", StatusRunning)
|
||||
r3 := testChild("c3", "root3", "", StatusRunning)
|
||||
r1 := testAgent("c1", "root1", "", StatusRunning)
|
||||
r2 := testAgent("c2", "root2", "", StatusRunning)
|
||||
r3 := testAgent("c3", "root3", "", StatusRunning)
|
||||
children := []*Child{r1, r2, r3}
|
||||
|
||||
if got := nextTabID(children, "c1", +1); got != "c2" {
|
||||
@@ -58,9 +58,9 @@ func TestNextTabIDWrapsAndSkipsCurrent(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNextTabIDFromSubAgentJumpsByRoot(t *testing.T) {
|
||||
r1 := testChild("c1", "root1", "", StatusRunning)
|
||||
r1Child := testChild("c2", "child1", "c1", StatusRunning)
|
||||
r2 := testChild("c3", "root2", "", StatusRunning)
|
||||
r1 := testAgent("c1", "root1", "", StatusRunning)
|
||||
r1Child := testAgent("c2", "child1", "c1", StatusRunning)
|
||||
r2 := testAgent("c3", "root2", "", StatusRunning)
|
||||
children := []*Child{r1, r1Child, r2}
|
||||
|
||||
// Focus is on a sub-agent of root1; Ctrl+D should jump to root2,
|
||||
@@ -71,29 +71,89 @@ func TestNextTabIDFromSubAgentJumpsByRoot(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNextChildIDCyclesWithinTab(t *testing.T) {
|
||||
r1 := testChild("c1", "root1", "", StatusRunning)
|
||||
a := testChild("c2", "a", "c1", StatusRunning)
|
||||
b := testChild("c3", "b", "c1", StatusRunning)
|
||||
other := testChild("c4", "other-root", "", StatusRunning)
|
||||
r1 := testAgent("c1", "root1", "", StatusRunning)
|
||||
a := testAgent("c2", "a", "c1", StatusRunning)
|
||||
b := testAgent("c3", "b", "c1", StatusRunning)
|
||||
other := testAgent("c4", "other-root", "", StatusRunning)
|
||||
children := []*Child{r1, a, b, other}
|
||||
|
||||
if got := nextChildID(children, "c1", +1); got != "c2" {
|
||||
if got := nextChildID(children, "c1", "c1", +1); got != "c2" {
|
||||
t.Fatalf("root->first child: %q", got)
|
||||
}
|
||||
if got := nextChildID(children, "c2", +1); got != "c3" {
|
||||
if got := nextChildID(children, "c2", "c1", +1); got != "c3" {
|
||||
t.Fatalf("a->b: %q", got)
|
||||
}
|
||||
if got := nextChildID(children, "c3", +1); got != "c1" {
|
||||
if got := nextChildID(children, "c3", "c1", +1); got != "c1" {
|
||||
t.Fatalf("wrap b->root: %q", got)
|
||||
}
|
||||
if got := nextChildID(children, "c1", -1); got != "c3" {
|
||||
if got := nextChildID(children, "c1", "c1", -1); got != "c3" {
|
||||
t.Fatalf("wrap backward root->b: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextChildIDNoopWhenOnlyOneProcess(t *testing.T) {
|
||||
r := testChild("c1", "solo", "", StatusRunning)
|
||||
if got := nextChildID([]*Child{r}, "c1", +1); got != "" {
|
||||
r := testAgent("c1", "solo", "", StatusRunning)
|
||||
if got := nextChildID([]*Child{r}, "c1", "c1", +1); got != "" {
|
||||
t.Fatalf("expected empty when only one process in tab, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// testAgent is a testChild wrapper that sets KindAgent — the new
|
||||
// navigation/visibility helpers filter by kind, so tests need explicit
|
||||
// kinds to behave like real agents.
|
||||
func testAgent(id, name, parent string, status ChildStatus) *Child {
|
||||
c := testChild(id, name, parent, status)
|
||||
c.Kind = KindAgent
|
||||
return c
|
||||
}
|
||||
|
||||
func testProcess(id, name string, status ChildStatus) *Child {
|
||||
c := testChild(id, name, "", status)
|
||||
c.Kind = KindCommand
|
||||
return c
|
||||
}
|
||||
|
||||
func TestSidebarNavListIncludesProcessesAboveAgentTree(t *testing.T) {
|
||||
p1 := testProcess("p1", "bun", StatusRunning)
|
||||
p2 := testProcess("p2", "queue", StatusRunning)
|
||||
r := testAgent("a1", "claude", "", StatusRunning)
|
||||
sub := testAgent("a2", "sub", "a1", StatusRunning)
|
||||
flat := sidebarNavList([]*Child{p1, p2, r, sub}, "a1")
|
||||
if len(flat) != 4 || flat[0].ID != "p1" || flat[1].ID != "p2" ||
|
||||
flat[2].ID != "a1" || flat[3].ID != "a2" {
|
||||
t.Fatalf("flat = %v, want p1 p2 a1 a2", childIDs(flat))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextChildIDWalksProcessesThenAgentTree(t *testing.T) {
|
||||
p1 := testProcess("p1", "bun", StatusRunning)
|
||||
r := testAgent("a1", "claude", "", StatusRunning)
|
||||
sub := testAgent("a2", "sub", "a1", StatusRunning)
|
||||
children := []*Child{p1, r, sub}
|
||||
// From a process, Ctrl+S walks down into the agent tree.
|
||||
if got := nextChildID(children, "p1", "a1", +1); got != "a1" {
|
||||
t.Fatalf("p1 -> a1: %q", got)
|
||||
}
|
||||
// From the agent root, Ctrl+W walks back up into the process list.
|
||||
if got := nextChildID(children, "a1", "a1", -1); got != "p1" {
|
||||
t.Fatalf("a1 -> p1: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisibleAgentTreeExcludesTopLevelCommands(t *testing.T) {
|
||||
p := testProcess("p1", "bun", StatusRunning)
|
||||
r := testAgent("a1", "claude", "", StatusRunning)
|
||||
got := visibleAgentTree([]*Child{p, r}, "a1")
|
||||
if len(got) != 1 || got[0].ID != "a1" {
|
||||
t.Fatalf("agent tree = %v, want only a1", childIDs(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunningTopLevelsSkipsCommands(t *testing.T) {
|
||||
p := testProcess("p1", "bun", StatusRunning)
|
||||
r := testAgent("a1", "claude", "", StatusRunning)
|
||||
got := runningTopLevels([]*Child{p, r})
|
||||
if len(got) != 1 || got[0].ID != "a1" {
|
||||
t.Fatalf("top-levels = %v, want only a1", childIDs(got))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user