From ec62eb91895cd523a40a5f3bacab8d1b0318b83d Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Wed, 29 Jul 2026 22:05:46 +0200 Subject: [PATCH] session: remove Interrupt, Queue, Detach, and all remaining agent pass-throughs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Session now has only genuinely session-level methods: - Agent(), Bus() — access points - Close(), SetEnv() — lifecycle - CWD(), SetCWD(), SetSessionID() — filesystem identity - WaitChange() — field watching for 9P - SaveSession(), saveSession(), flushSave() — persistence Everything else (Interrupt, Queue, Detach, Reactions, etc.) lives on Agent. Callers go through session.Agent() to reach agent operations. --- agent/agent.go | 80 +++++++++++++++++++++++++++++++++++++++ session/session.go | 93 ---------------------------------------------- 2 files changed, 80 insertions(+), 93 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index e2c2206..115e1bb 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -8,6 +8,7 @@ import ( "strings" "sync" "sync/atomic" + "syscall" "time" "github.com/simonfxr/pubsub" @@ -509,3 +510,82 @@ func (ag *Agent) WaitForChange(ctx context.Context) { } ag.changeMu.Unlock() } + +// Detach detaches the current running process to background. +func (ag *Agent) Detach() bool { + if srv := ag.ExecServer(); srv != nil { + if d, ok := srv.(interface{ Detach() bool }); ok { + return d.Detach() + } + } + return false +} + +// DetachedInfo describes a detached process. +type DetachedInfo struct { + PID int + Command string + Started int64 + Exited bool + ExitCode int +} + +// ListDetached returns info about all detached processes. +func (ag *Agent) ListDetached() []DetachedInfo { + srv := ag.ExecServer() + if srv == nil { + return nil + } + type listDetacher interface{ ListDetachedRaw() []any } + ld, ok := srv.(listDetacher) + if !ok { + return nil + } + raw := ld.ListDetachedRaw() + out := make([]DetachedInfo, 0, len(raw)) + for _, r := range raw { + if m, ok := r.(map[string]any); ok { + di := DetachedInfo{} + if v, ok := m["pid"].(int); ok { di.PID = v } + if v, ok := m["command"].(string); ok { di.Command = v } + if v, ok := m["started"].(int64); ok { di.Started = v } + if v, ok := m["exited"].(bool); ok { di.Exited = v } + if v, ok := m["exit_code"].(int); ok { di.ExitCode = v } + out = append(out, di) + } + } + return out +} + +// SignalDetached sends a signal to a detached process. +func (ag *Agent) SignalDetached(pid int, signal int) error { + if srv := ag.ExecServer(); srv != nil { + type signaler interface{ SignalDetached(int, syscall.Signal) error } + if sg, ok := srv.(signaler); ok { + return sg.SignalDetached(pid, syscall.Signal(signal)) + } + } + return fmt.Errorf("no execute server available") +} + +// GetDetachedOutput reads output from a detached process. +func (ag *Agent) GetDetachedOutput(pid int) (string, error) { + if srv := ag.ExecServer(); srv != nil { + type outputGetter interface{ GetDetachedOutput(int) (string, error) } + if og, ok := srv.(outputGetter); ok { + return og.GetDetachedOutput(pid) + } + } + return "", fmt.Errorf("no execute server available") +} + +// DismissDetached removes a finished detached process. +func (ag *Agent) DismissDetached(pid int) bool { + if srv := ag.ExecServer(); srv != nil { + type dismisser interface{ DismissDetached(int) bool } + if d, ok := srv.(dismisser); ok { + return d.DismissDetached(pid) + } + } + return false +} diff --git a/session/session.go b/session/session.go index e06c7f4..0066411 100644 --- a/session/session.go +++ b/session/session.go @@ -10,7 +10,6 @@ import ( "strconv" "strings" "sync" - "syscall" "time" "github.com/simonfxr/pubsub" @@ -188,19 +187,6 @@ func (a *Session) SetEnv(key, value string) { func (a *Session) Agent() *agent.Agent { return a.r } func (a *Session) Bus() *pubsub.Bus { return a.bus } -func (a *Session) Interrupt(cause error) bool { - return a.r.Interrupt(cause) -} - -func (a *Session) Queue(prompt string) { - a.r.Queue(prompt) - a.bus.Publish("queued", prompt) -} - -func (a *Session) InjectSystemEvent(content string) { - a.Queue("\n" + content + "\n") -} - // CWD returns the current working directory for tool execution. func (a *Session) CWD() string { if c := a.r.Cwd(); c != "" { @@ -279,10 +265,6 @@ func (a *Session) WaitChange(ctx context.Context, field, current string) (string return "", false } -func (a *Session) emit(ev agent.Event) { - a.bus.Publish("event", ev) -} - func (a *Session) activeSessionPath(id, suffix string) string { return filepath.Join(a.sessionsDir, "active", id+suffix) } @@ -322,78 +304,3 @@ func (a *Session) flushSave() { func (a *Session) SaveSession(path string) error { return a.r.SaveFull(path, a.id, a.CWD(), a.remote) } - -// Detach operations delegate to the agent's execute server. -func (a *Session) Detach() bool { - if srv := a.r.ExecServer(); srv != nil { - if d, ok := srv.(interface{ Detach() bool }); ok { - return d.Detach() - } - } - return false -} - -// DetachedInfo describes a detached process. -type DetachedInfo struct { - PID int - Command string - Started int64 - Exited bool - ExitCode int -} - -func (a *Session) ListDetached() []DetachedInfo { - srv := a.r.ExecServer() - if srv == nil { - return nil - } - type listDetacher interface{ ListDetachedRaw() []any } - ld, ok := srv.(listDetacher) - if !ok { - return nil - } - raw := ld.ListDetachedRaw() - out := make([]DetachedInfo, 0, len(raw)) - for _, r := range raw { - if m, ok := r.(map[string]any); ok { - di := DetachedInfo{} - if v, ok := m["pid"].(int); ok { di.PID = v } - if v, ok := m["command"].(string); ok { di.Command = v } - if v, ok := m["started"].(int64); ok { di.Started = v } - if v, ok := m["exited"].(bool); ok { di.Exited = v } - if v, ok := m["exit_code"].(int); ok { di.ExitCode = v } - out = append(out, di) - } - } - return out -} - -func (a *Session) SignalDetached(pid, signal int) error { - if srv := a.r.ExecServer(); srv != nil { - type signaler interface{ SignalDetached(int, syscall.Signal) error } - if sg, ok := srv.(signaler); ok { - return sg.SignalDetached(pid, syscall.Signal(signal)) - } - } - return fmt.Errorf("no execute server available") -} - -func (a *Session) GetDetachedOutput(pid int) (string, error) { - if srv := a.r.ExecServer(); srv != nil { - type outputGetter interface{ GetDetachedOutput(int) (string, error) } - if og, ok := srv.(outputGetter); ok { - return og.GetDetachedOutput(pid) - } - } - return "", fmt.Errorf("no execute server available") -} - -func (a *Session) DismissDetached(pid int) bool { - if srv := a.r.ExecServer(); srv != nil { - type dismisser interface{ DismissDetached(int) bool } - if d, ok := srv.(dismisser); ok { - return d.DismissDetached(pid) - } - } - return false -}