Don't steal focus when an agent spawns a child via MCP

This commit is contained in:
2026-05-15 15:53:50 +01:00
parent 24c8183832
commit 08187aed77
3 changed files with 71 additions and 1 deletions

View File

@@ -700,8 +700,26 @@ func (st *uiState) scratchpadsChanged() {
}
}
// OnChildSpawned auto-focuses the new child.
// OnChildSpawned auto-focuses the new child when the spawn came from
// the user (palette, persistence restore, or an external MCP client with
// no resolved identity). When ParentID is set — meaning a patterm-managed
// agent spawned this child via spawn_agent/spawn_process — focus stays
// on whatever the user was watching; the new child is still surfaced in
// the sidebar/tab bar so it's reachable via the palette or select_process.
func (st *uiState) OnChildSpawned(c *Child) {
if c.ParentID != "" {
st.mu.Lock()
if st.palette != nil {
st.palette.children = st.sess.Children()
st.palette.focused = st.focusedID
st.palette.rebuild()
st.renderPaletteLocked()
}
st.mu.Unlock()
st.drawTabBar()
st.drawSidebar()
return
}
st.marquee.reset()
layout := st.layoutSnapshot()
onAlt := childIsOnAlt(c)

View File

@@ -0,0 +1,46 @@
package app
import (
"testing"
)
// TestOnChildSpawnedAgentChildKeepsFocus verifies that when a child is
// spawned with a ParentID set (i.e. a patterm-managed agent caused the
// spawn over MCP), OnChildSpawned does NOT steal viewport focus from
// the currently focused child.
func TestOnChildSpawnedAgentChildKeepsFocus(t *testing.T) {
sess := NewSession(t.TempDir(), "test")
st := &uiState{sess: sess}
parent := newChildEntry("p_parent", "parent", KindAgent, nil, nil, "", "", "")
st.focusedID = parent.ID
st.focusedName = parent.Name
subAgent := newChildEntry("p_sub", "sub", KindAgent, nil, nil, parent.ID, "", "")
st.OnChildSpawned(subAgent)
if got := st.focusedID; got != parent.ID {
t.Fatalf("agent-initiated spawn should not change focusedID: want %q, got %q", parent.ID, got)
}
if got := st.focusedName; got != parent.Name {
t.Fatalf("focusedName changed: want %q, got %q", parent.Name, got)
}
}
// TestOnChildSpawnedPaletteChildTakesFocus verifies the legacy path is
// preserved: spawns with an empty ParentID (palette, restore, external
// MCP caller) still auto-focus the new child.
func TestOnChildSpawnedPaletteChildTakesFocus(t *testing.T) {
sess := NewSession(t.TempDir(), "test")
st := &uiState{sess: sess}
st.lastExit.Store(-1)
c := newChildEntry("p_new", "newchild", KindAgent, nil, nil, "", "", "")
st.OnChildSpawned(c)
if got := st.focusedID; got != c.ID {
t.Fatalf("palette-initiated spawn should auto-focus: want %q, got %q", c.ID, got)
}
}