Scaffold loopback daemon client split
This commit is contained in:
@@ -161,16 +161,37 @@ func Run(ctx context.Context, opts Options) error {
|
||||
// ctx is cancelled.
|
||||
go sess.runClassifier(ctx)
|
||||
|
||||
st := &uiState{
|
||||
sess: sess,
|
||||
core := &headlessCore{
|
||||
projectDir: opts.ProjectDir,
|
||||
projectKey: opts.ProjectKey,
|
||||
presets: presets,
|
||||
launcher: launcher,
|
||||
settings: appSettings,
|
||||
pads: pads,
|
||||
chromeWake: make(chan struct{}, 1),
|
||||
trust: trustStore,
|
||||
timers: host.timers,
|
||||
hostCols: cols,
|
||||
hostRows: rows,
|
||||
trustStore: trustStore,
|
||||
persistStore: persistStore,
|
||||
mcpSrv: mcpSrv,
|
||||
sess: sess,
|
||||
launcher: launcher,
|
||||
host: host,
|
||||
}
|
||||
_ = core
|
||||
|
||||
st := &uiState{
|
||||
sess: sess,
|
||||
presets: presets,
|
||||
launcher: launcher,
|
||||
pads: pads,
|
||||
chromeWake: make(chan struct{}, 1),
|
||||
trust: trustStore,
|
||||
timers: host.timers,
|
||||
hostCols: cols,
|
||||
hostRows: rows,
|
||||
view: ClientView{
|
||||
ID: "loopback",
|
||||
ProjectKey: opts.ProjectKey,
|
||||
Cols: cols,
|
||||
Rows: rows,
|
||||
},
|
||||
stdinTTY: term.IsTerminal(int(os.Stdin.Fd())),
|
||||
metrics: metrics,
|
||||
settings: appSettings,
|
||||
@@ -252,6 +273,7 @@ func Run(ctx context.Context, opts Options) error {
|
||||
}
|
||||
st.dimsMu.Lock()
|
||||
st.hostCols, st.hostRows = c, r
|
||||
st.view.Resize(c, r)
|
||||
l := st.layoutLocked()
|
||||
st.dimsMu.Unlock()
|
||||
st.mu.Lock()
|
||||
@@ -408,6 +430,7 @@ type uiState struct {
|
||||
outMu sync.Mutex
|
||||
|
||||
mu sync.Mutex
|
||||
view ClientView
|
||||
palette *paletteState
|
||||
focusedID string
|
||||
focusedName string
|
||||
@@ -574,6 +597,21 @@ func (st *uiState) promptTrust(processID, presetName, reason string) {
|
||||
st.drawStatusLine()
|
||||
}
|
||||
|
||||
func (st *uiState) focusChildLocked(c *Child) {
|
||||
st.focusedPad = ""
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.view.FocusChild(c.ID)
|
||||
}
|
||||
|
||||
func (st *uiState) focusPadLocked(name string) {
|
||||
st.view.FocusPad(name)
|
||||
st.focusedPad = st.view.FocusedPad
|
||||
st.focusedID = st.view.FocusedID
|
||||
st.padOffset = st.view.PadOffset
|
||||
st.padOffsetName = st.view.PadOffsetName
|
||||
}
|
||||
|
||||
// focusProcess is the SPEC §7 select_process hook. Routes through the
|
||||
// normal focus-change path; only takes effect if the process exists.
|
||||
func (st *uiState) focusProcess(processID string) {
|
||||
@@ -586,9 +624,7 @@ func (st *uiState) focusProcess(processID string) {
|
||||
onAlt := childIsOnAlt(c)
|
||||
st.mu.Lock()
|
||||
leavingPad := st.focusedPad != ""
|
||||
st.focusedPad = ""
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.focusChildLocked(c)
|
||||
st.updateActiveAgentLocked(c)
|
||||
r := newViewportRenderer(layout)
|
||||
r.SetChildOnAlt(onAlt)
|
||||
@@ -651,12 +687,7 @@ func (st *uiState) focusScratchpad(name string) {
|
||||
}
|
||||
st.marquee.reset()
|
||||
st.mu.Lock()
|
||||
if st.padOffsetName != name {
|
||||
st.padOffset = 0
|
||||
st.padOffsetName = name
|
||||
}
|
||||
st.focusedPad = name
|
||||
st.focusedID = ""
|
||||
st.focusPadLocked(name)
|
||||
st.focusedName = name
|
||||
st.renderer = nil
|
||||
st.mu.Unlock()
|
||||
@@ -711,8 +742,7 @@ func (st *uiState) restartFocusedCommand(processID string) {
|
||||
layout := st.layoutSnapshot()
|
||||
renderer := newViewportRenderer(layout)
|
||||
st.mu.Lock()
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.focusChildLocked(c)
|
||||
st.renderer = renderer
|
||||
st.repaintNextPTY = c.ID
|
||||
st.repaintNextPTYBudget = 2
|
||||
@@ -747,6 +777,7 @@ func (st *uiState) updateActiveAgentLocked(c *Child) {
|
||||
}
|
||||
if c.ParentID == "" {
|
||||
st.activeAgentID = c.ID
|
||||
st.view.ActiveAgentID = c.ID
|
||||
return
|
||||
}
|
||||
// Walk up to the top-level agent.
|
||||
@@ -760,6 +791,7 @@ func (st *uiState) updateActiveAgentLocked(c *Child) {
|
||||
}
|
||||
if root.Kind == KindAgent && root.ParentID == "" {
|
||||
st.activeAgentID = root.ID
|
||||
st.view.ActiveAgentID = root.ID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,9 +854,7 @@ func (st *uiState) OnChildSpawned(c *Child) {
|
||||
layout := st.layoutSnapshot()
|
||||
onAlt := childIsOnAlt(c)
|
||||
st.mu.Lock()
|
||||
st.focusedPad = ""
|
||||
st.focusedID = c.ID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.focusChildLocked(c)
|
||||
st.updateActiveAgentLocked(c)
|
||||
renderer := newViewportRenderer(layout)
|
||||
renderer.SetChildOnAlt(onAlt)
|
||||
@@ -899,10 +929,10 @@ func (st *uiState) OnChildExited(c *Child) {
|
||||
if next == nil {
|
||||
st.focusedID = ""
|
||||
st.focusedName = ""
|
||||
st.view.FocusedID = ""
|
||||
renderEmpty = true
|
||||
} else {
|
||||
st.focusedID = next.ID
|
||||
st.focusedName = next.DisplayName()
|
||||
st.focusChildLocked(next)
|
||||
st.updateActiveAgentLocked(next)
|
||||
st.renderer = newViewportRenderer(layout)
|
||||
}
|
||||
@@ -911,6 +941,7 @@ func (st *uiState) OnChildExited(c *Child) {
|
||||
// The active agent died; pin the agent tree to whatever agent
|
||||
// root is still running, or clear it if none remain.
|
||||
st.activeAgentID = firstRunningAgentID(st.sess.Children())
|
||||
st.view.ActiveAgentID = st.activeAgentID
|
||||
}
|
||||
if st.palette != nil {
|
||||
st.palette.children = st.sess.Children()
|
||||
@@ -1387,7 +1418,10 @@ func (st *uiState) renderEmptyState() {
|
||||
func (st *uiState) hostSizeSnapshot() (uint16, uint16) {
|
||||
st.dimsMu.Lock()
|
||||
defer st.dimsMu.Unlock()
|
||||
return st.hostCols, st.hostRows
|
||||
if st.view.Cols == 0 || st.view.Rows == 0 {
|
||||
return st.hostCols, st.hostRows
|
||||
}
|
||||
return st.view.Cols, st.view.Rows
|
||||
}
|
||||
|
||||
func (st *uiState) layoutSnapshot() terminalLayout {
|
||||
@@ -1397,7 +1431,10 @@ func (st *uiState) layoutSnapshot() terminalLayout {
|
||||
}
|
||||
|
||||
func (st *uiState) layoutLocked() terminalLayout {
|
||||
return newTerminalLayout(st.hostCols, st.hostRows)
|
||||
if st.view.Cols == 0 || st.view.Rows == 0 {
|
||||
return newTerminalLayout(st.hostCols, st.hostRows)
|
||||
}
|
||||
return newTerminalLayout(st.view.Cols, st.view.Rows)
|
||||
}
|
||||
|
||||
// splitOnEnter walks input and returns each Enter byte (CR or LF) as
|
||||
@@ -2086,9 +2123,7 @@ func (st *uiState) closePalette(action paletteAction) {
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
leavingPad := st.focusedPad != ""
|
||||
st.focusedPad = ""
|
||||
st.focusedID = action.childID
|
||||
st.focusedName = c.DisplayName()
|
||||
st.focusChildLocked(c)
|
||||
st.updateActiveAgentLocked(c)
|
||||
st.renderer = newViewportRenderer(layout)
|
||||
st.mu.Unlock()
|
||||
@@ -2232,13 +2267,8 @@ func (st *uiState) handlePadDelete(name string) {
|
||||
if entries := st.padsList(); len(entries) > 0 {
|
||||
next := entries[0].Name
|
||||
st.mu.Lock()
|
||||
st.focusedPad = next
|
||||
st.focusedID = ""
|
||||
st.focusPadLocked(next)
|
||||
st.focusedName = next
|
||||
if st.padOffsetName != next {
|
||||
st.padOffset = 0
|
||||
st.padOffsetName = next
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.repaintFocusedWithChrome()
|
||||
return
|
||||
@@ -2249,9 +2279,12 @@ func (st *uiState) handlePadDelete(name string) {
|
||||
}
|
||||
st.mu.Lock()
|
||||
st.focusedPad = ""
|
||||
st.view.FocusedPad = ""
|
||||
st.focusedName = ""
|
||||
st.padOffset = 0
|
||||
st.padOffsetName = ""
|
||||
st.view.PadOffset = 0
|
||||
st.view.PadOffsetName = ""
|
||||
st.mu.Unlock()
|
||||
st.renderEmptyState()
|
||||
st.drawTabBar()
|
||||
@@ -2278,7 +2311,7 @@ func (st *uiState) handlePadRename(oldName, newName string) {
|
||||
}
|
||||
st.mu.Lock()
|
||||
if st.focusedPad == oldName {
|
||||
st.focusedPad = newName
|
||||
st.focusPadLocked(newName)
|
||||
}
|
||||
st.mu.Unlock()
|
||||
st.scratchpadsChanged()
|
||||
@@ -2549,6 +2582,7 @@ func (st *uiState) renderPadView(name, content string, layout terminalLayout) []
|
||||
st.padOffset = 0
|
||||
}
|
||||
offset := st.padOffset
|
||||
st.view.PadOffset = offset
|
||||
st.mu.Unlock()
|
||||
|
||||
var b strings.Builder
|
||||
@@ -2606,6 +2640,7 @@ func (st *uiState) exitPadView() {
|
||||
return
|
||||
}
|
||||
st.focusedPad = ""
|
||||
st.view.FocusedPad = ""
|
||||
st.focusedName = ""
|
||||
st.mu.Unlock()
|
||||
st.clearViewportArea()
|
||||
@@ -2632,6 +2667,7 @@ func (st *uiState) padScroll(delta int) {
|
||||
if st.padOffset < 0 {
|
||||
st.padOffset = 0
|
||||
}
|
||||
st.view.PadOffset = st.padOffset
|
||||
st.mu.Unlock()
|
||||
st.repaintFocusedPad()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user