Simplify session lifecycle and MCP cleanup
This commit is contained in:
@@ -105,6 +105,7 @@ func Run(ctx context.Context, opts Options) error {
|
||||
host.attention = st
|
||||
host.focus = st
|
||||
host.prompter = st
|
||||
host.scratch = st
|
||||
st.lastExit.Store(-1)
|
||||
sess.Subscribe(st)
|
||||
|
||||
@@ -241,10 +242,10 @@ type uiState struct {
|
||||
// usually doesn't change between calls — caching the rendered
|
||||
// output and skipping a write when it matches eliminates the
|
||||
// flicker (especially in the sidebar's session tree).
|
||||
chromeCacheMu sync.Mutex
|
||||
tabBarCache string
|
||||
sidebarCache string
|
||||
statusLineCache string
|
||||
chromeCacheMu sync.Mutex
|
||||
tabBarCache string
|
||||
sidebarCache string
|
||||
statusLineCache string
|
||||
|
||||
lastExit atomic.Int32
|
||||
}
|
||||
@@ -278,10 +279,11 @@ func (st *uiState) focusProcess(processID string) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.renderer = newViewportRenderer(st.layoutSnapshot())
|
||||
st.renderer = newViewportRenderer(layout)
|
||||
st.mu.Unlock()
|
||||
st.repaintFocused()
|
||||
st.drawTabBar()
|
||||
@@ -297,7 +299,7 @@ func (st *uiState) notifyAttention(childID, reason string) {
|
||||
c := st.sess.FindChild(childID)
|
||||
name := childID
|
||||
if c != nil {
|
||||
name = c.Name
|
||||
name = c.DisplayName()
|
||||
}
|
||||
st.mu.Lock()
|
||||
st.attentionText = fmt.Sprintf("attention: %s — %s", name, reason)
|
||||
@@ -306,12 +308,20 @@ func (st *uiState) notifyAttention(childID, reason string) {
|
||||
st.drawStatusLine()
|
||||
}
|
||||
|
||||
func (st *uiState) scratchpadsChanged() {
|
||||
st.chromeCacheMu.Lock()
|
||||
st.sidebarCache = ""
|
||||
st.chromeCacheMu.Unlock()
|
||||
st.drawSidebar()
|
||||
}
|
||||
|
||||
// OnChildSpawned auto-focuses the new child.
|
||||
func (st *uiState) OnChildSpawned(c *Child) {
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.Name
|
||||
renderer := newViewportRenderer(st.layoutSnapshot())
|
||||
st.focusedName = c.DisplayName()
|
||||
renderer := newViewportRenderer(layout)
|
||||
st.renderer = renderer
|
||||
palOpen := st.palette != nil
|
||||
if palOpen {
|
||||
@@ -343,17 +353,19 @@ func (st *uiState) OnChildSpawned(c *Child) {
|
||||
// focused child.
|
||||
func (st *uiState) OnChildExited(c *Child) {
|
||||
st.lastExit.Store(int32(c.ExitCode()))
|
||||
layout := st.layoutSnapshot()
|
||||
renderEmpty := false
|
||||
st.mu.Lock()
|
||||
if c.ID == st.focusedID {
|
||||
next := firstRunningTopLevel(st.sess.Children())
|
||||
if next == nil {
|
||||
st.focusedID = ""
|
||||
st.focusedName = ""
|
||||
st.renderEmptyStateLocked()
|
||||
renderEmpty = true
|
||||
} else {
|
||||
st.focusedID = next.ID
|
||||
st.focusedName = next.Name
|
||||
st.renderer = newViewportRenderer(st.layoutSnapshot())
|
||||
st.focusedName = next.DisplayName()
|
||||
st.renderer = newViewportRenderer(layout)
|
||||
}
|
||||
}
|
||||
if st.palette != nil {
|
||||
@@ -362,8 +374,12 @@ func (st *uiState) OnChildExited(c *Child) {
|
||||
st.palette.rebuild()
|
||||
st.renderPaletteLocked()
|
||||
}
|
||||
repaint := st.focusedID != ""
|
||||
st.mu.Unlock()
|
||||
if st.focusedID != "" {
|
||||
if renderEmpty {
|
||||
st.renderEmptyState()
|
||||
}
|
||||
if repaint {
|
||||
st.repaintFocused()
|
||||
}
|
||||
st.drawTabBar()
|
||||
@@ -417,13 +433,16 @@ func (st *uiState) OnPTYOut(childID string, chunk []byte) {
|
||||
// contained one of those escapes; when set, drop the sidebar cache
|
||||
// so the next drawSidebar repaints over the clobber instead of
|
||||
// hitting the cache and leaving the gap visible.
|
||||
if renderer.TookScrollAction() {
|
||||
scrolled := renderer.TookScrollAction()
|
||||
if scrolled {
|
||||
st.chromeCacheMu.Lock()
|
||||
st.sidebarCache = ""
|
||||
st.chromeCacheMu.Unlock()
|
||||
}
|
||||
st.drawTabBar()
|
||||
st.drawSidebar()
|
||||
if scrolled {
|
||||
st.drawSidebar()
|
||||
}
|
||||
st.drawStatusLine()
|
||||
}
|
||||
|
||||
@@ -559,15 +578,9 @@ func (st *uiState) drawStatusLine() {
|
||||
// renderEmptyState is the SPEC §4 blank-canvas hint. Drawn whenever no
|
||||
// child is focused.
|
||||
func (st *uiState) renderEmptyState() {
|
||||
st.mu.Lock()
|
||||
defer st.mu.Unlock()
|
||||
st.renderEmptyStateLocked()
|
||||
}
|
||||
|
||||
func (st *uiState) renderEmptyStateLocked() {
|
||||
layout := st.layoutSnapshot()
|
||||
st.outMu.Lock()
|
||||
defer st.outMu.Unlock()
|
||||
layout := st.layoutSnapshot()
|
||||
line := "Press Ctrl-K to spawn an agent or process"
|
||||
row := int(layout.mainTop) + (int(layout.childRows()) / 2)
|
||||
col := int(layout.mainLeft) + ((int(layout.childCols()) - len(line)) / 2)
|
||||
@@ -897,10 +910,11 @@ func (st *uiState) closePalette(action paletteAction) {
|
||||
st.repaintFocused()
|
||||
return
|
||||
}
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
st.focusedID = action.childID
|
||||
st.focusedName = c.Name
|
||||
st.renderer = newViewportRenderer(st.layoutSnapshot())
|
||||
st.focusedName = c.DisplayName()
|
||||
st.renderer = newViewportRenderer(layout)
|
||||
st.mu.Unlock()
|
||||
st.repaintFocused()
|
||||
st.drawTabBar()
|
||||
@@ -953,10 +967,10 @@ func (st *uiState) flashTransient(msg string) {
|
||||
// emulator grid; the padded snapshot is the source of truth for visible
|
||||
// cells.
|
||||
func (st *uiState) repaintFocused() {
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
id := st.focusedID
|
||||
renderer := st.renderer
|
||||
layout := st.layoutLocked()
|
||||
st.mu.Unlock()
|
||||
if id == "" {
|
||||
st.renderEmptyState()
|
||||
|
||||
Reference in New Issue
Block a user