From 11ccdda579c70bdcc7569aaea4a62ade05e99390 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:10:50 +0800 Subject: [PATCH] feat(service): expose subscribe, input, exit status for ws handler Co-Authored-By: Claude --- internal/process/manager.go | 37 +++++++++++++++++++++++++++++ internal/service/process_service.go | 26 ++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/internal/process/manager.go b/internal/process/manager.go index 46ba7e5..bac6d37 100644 --- a/internal/process/manager.go +++ b/internal/process/manager.go @@ -23,6 +23,8 @@ type Manager interface { Restart(workspaceID string, workspaceRoot string) error Status(workspaceID string) Status Subscribe(workspaceID string) (Subscription, error) + Stdin(workspaceID string) (io.WriteCloser, error) + ExitStatus(workspaceID string) (ExitInfo, error) } // LocalManager implements Manager using local OS processes. @@ -237,6 +239,41 @@ func (m *LocalManager) Subscribe(workspaceID string) (Subscription, error) { 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. type ringBuffer struct { mu sync.Mutex diff --git a/internal/service/process_service.go b/internal/service/process_service.go index 5c7512a..e183c9d 100644 --- a/internal/service/process_service.go +++ b/internal/service/process_service.go @@ -3,6 +3,8 @@ package service import ( "log/slog" + "io" + "codespace/internal/process" "codespace/internal/workspace" ) @@ -70,3 +72,27 @@ func (s *ProcessService) Status(workspaceID string) (process.Status, error) { } 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) +}