dbus: add detached process management methods

New D-Bus methods on SessionManager:
- DetachProcess(sessionID) - detach currently running process
- ListDetached(sessionID) - list detached processes (pid/cmd/started/status)
- SignalDetached(sessionID, pid, signal) - send TERM or KILL
- GetDetachedOutput(sessionID, pid) - read ring buffer output
- DismissDetached(sessionID, pid) - remove exited process from list

New signal: ProcessDetached(sessionID) emitted on detach.
This commit is contained in:
Levi Neely 2026-07-17 16:51:55 +02:00
parent 7900514548
commit 31cac84423
1 changed files with 81 additions and 0 deletions

81
main.go
View File

@ -575,6 +575,87 @@ func (m *SessionManager) PeerSubmit(sessionID, peerID, prompt string) (bool, *db
return true, nil
}
// --- Detached Process Management ---
func (m *SessionManager) DetachProcess(sessionID string) (bool, *dbus.Error) {
m.mu.Lock()
sess, ok := m.sessions[sessionID]
m.mu.Unlock()
if !ok {
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
}
if !sess.core.Detach() {
return false, dbus.MakeFailedError(fmt.Errorf("no running process to detach"))
}
m.conn.Emit(busPath, busIface+".ProcessDetached", sessionID)
return true, nil
}
func (m *SessionManager) ListDetached(sessionID string) ([]string, *dbus.Error) {
m.mu.Lock()
sess, ok := m.sessions[sessionID]
m.mu.Unlock()
if !ok {
return nil, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
}
infos := sess.core.ListDetached()
out := make([]string, len(infos))
for i, info := range infos {
status := "running"
if info.Exited {
status = fmt.Sprintf("exited(%d)", info.ExitCode)
}
out[i] = fmt.Sprintf("%d\t%s\t%d\t%s", info.PID, info.Command, info.Started, status)
}
return out, nil
}
func (m *SessionManager) SignalDetached(sessionID string, pid int32, signal string) (bool, *dbus.Error) {
m.mu.Lock()
sess, ok := m.sessions[sessionID]
m.mu.Unlock()
if !ok {
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
}
var sig int
switch strings.ToUpper(signal) {
case "TERM", "SIGTERM", "15":
sig = 15
case "KILL", "SIGKILL", "9":
sig = 9
default:
return false, dbus.MakeFailedError(fmt.Errorf("unsupported signal: %s (use TERM or KILL)", signal))
}
if err := sess.core.SignalDetached(int(pid), sig); err != nil {
return false, dbus.MakeFailedError(err)
}
return true, nil
}
func (m *SessionManager) GetDetachedOutput(sessionID string, pid int32) (string, *dbus.Error) {
m.mu.Lock()
sess, ok := m.sessions[sessionID]
m.mu.Unlock()
if !ok {
return "", dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
}
output, err := sess.core.GetDetachedOutput(int(pid))
if err != nil {
return "", dbus.MakeFailedError(err)
}
return output, nil
}
func (m *SessionManager) DismissDetached(sessionID string, pid int32) (bool, *dbus.Error) {
m.mu.Lock()
sess, ok := m.sessions[sessionID]
m.mu.Unlock()
if !ok {
return false, dbus.MakeFailedError(fmt.Errorf("session not found: %s", sessionID))
}
return sess.core.DismissDetached(int(pid)), nil
}
// --- Notifications ---
func (m *SessionManager) sendNotification(title, body string) {