feat(service): expose subscribe, input, exit status for ws handler

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tao.chen
2026-07-03 12:10:50 +08:00
co-authored by Claude
parent 13a5aeda2c
commit 11ccdda579
2 changed files with 63 additions and 0 deletions
+37
View File
@@ -23,6 +23,8 @@ type Manager interface {
Restart(workspaceID string, workspaceRoot string) error Restart(workspaceID string, workspaceRoot string) error
Status(workspaceID string) Status Status(workspaceID string) Status
Subscribe(workspaceID string) (Subscription, error) Subscribe(workspaceID string) (Subscription, error)
Stdin(workspaceID string) (io.WriteCloser, error)
ExitStatus(workspaceID string) (ExitInfo, error)
} }
// LocalManager implements Manager using local OS processes. // LocalManager implements Manager using local OS processes.
@@ -237,6 +239,41 @@ func (m *LocalManager) Subscribe(workspaceID string) (Subscription, error) {
return sub, nil return sub, nil
} }
// Stdin returns the stdin writer for the workspace process.
func (m *LocalManager) Stdin(workspaceID string) (io.WriteCloser, error) {
return m.stdinOf(workspaceID)
}
// ExitStatus returns the exit status for the workspace process.
func (m *LocalManager) ExitStatus(workspaceID string) (ExitInfo, error) {
return m.exitOf(workspaceID)
}
// stdinOf returns the session's stdin writer or CodeNotFound.
func (m *LocalManager) stdinOf(workspaceID string) (io.WriteCloser, error) {
m.mu.Lock()
defer m.mu.Unlock()
sess, ok := m.sessions[workspaceID]
if !ok {
return nil, util.New(util.CodeNotFound, "no running process")
}
return sess.Stdin, nil
}
// exitOf returns the session's recorded exit info or CodeNotFound.
func (m *LocalManager) exitOf(workspaceID string) (ExitInfo, error) {
m.mu.Lock()
sess, ok := m.sessions[workspaceID]
m.mu.Unlock()
if !ok {
return ExitInfo{}, util.New(util.CodeNotFound, "no running process")
}
sess.mu.Lock()
defer sess.mu.Unlock()
return sess.Exit, nil
}
// ringBuffer is a fixed-size byte buffer that drops the oldest bytes when full. // ringBuffer is a fixed-size byte buffer that drops the oldest bytes when full.
type ringBuffer struct { type ringBuffer struct {
mu sync.Mutex mu sync.Mutex
+26
View File
@@ -3,6 +3,8 @@ package service
import ( import (
"log/slog" "log/slog"
"io"
"codespace/internal/process" "codespace/internal/process"
"codespace/internal/workspace" "codespace/internal/workspace"
) )
@@ -70,3 +72,27 @@ func (s *ProcessService) Status(workspaceID string) (process.Status, error) {
} }
return s.processes.Status(workspaceID), nil return s.processes.Status(workspaceID), nil
} }
// Subscribe subscribes to output events for the workspace process.
func (s *ProcessService) Subscribe(workspaceID string) (process.Subscription, error) {
if _, err := s.workspaces.Get(workspaceID); err != nil {
return nil, err
}
return s.processes.Subscribe(workspaceID)
}
// Input returns the stdin writer for the workspace process.
func (s *ProcessService) Input(workspaceID string) (io.WriteCloser, error) {
if _, err := s.workspaces.Get(workspaceID); err != nil {
return nil, err
}
return s.processes.Stdin(workspaceID)
}
// ExitStatus returns the exit status for the workspace process.
func (s *ProcessService) ExitStatus(workspaceID string) (process.ExitInfo, error) {
if _, err := s.workspaces.Get(workspaceID); err != nil {
return process.ExitInfo{}, err
}
return s.processes.ExitStatus(workspaceID)
}