session: remove Interrupt, Queue, Detach, and all remaining agent pass-throughs

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.
This commit is contained in:
Levi Neely 2026-07-29 22:05:46 +02:00
parent 24b9a0bdd3
commit ec62eb9189
2 changed files with 80 additions and 93 deletions

View File

@ -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
}

View File

@ -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("<detached-process-result>\n" + content + "\n</detached-process-result>")
}
// 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
}