Don't steal focus when an agent spawns a child via MCP
This commit is contained in:
@@ -20,6 +20,12 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
child agent, even though you were still within that thread.
|
child agent, even though you were still within that thread.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Agent-initiated `spawn_agent` and `spawn_process` MCP calls no
|
||||||
|
longer steal viewport focus from the currently active tab. The
|
||||||
|
new child still appears in the sidebar and tab bar; switch to it
|
||||||
|
explicitly via the palette or `select_process`. Palette-initiated
|
||||||
|
spawns and persistence restores are unchanged — they still auto-
|
||||||
|
focus the new pane.
|
||||||
- Sidebar rows (Processes, Agent Tree, Scratchpads) now truncate
|
- Sidebar rows (Processes, Agent Tree, Scratchpads) now truncate
|
||||||
overflowing names with a trailing `…` instead of spilling into
|
overflowing names with a trailing `…` instead of spilling into
|
||||||
the main viewport. The focused row marquees its name when it
|
the main viewport. The focused row marquees its name when it
|
||||||
|
|||||||
@@ -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) {
|
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()
|
st.marquee.reset()
|
||||||
layout := st.layoutSnapshot()
|
layout := st.layoutSnapshot()
|
||||||
onAlt := childIsOnAlt(c)
|
onAlt := childIsOnAlt(c)
|
||||||
|
|||||||
46
internal/app/spawn_focus_test.go
Normal file
46
internal/app/spawn_focus_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user