yao/sandbox/ipc/manager.go
Max c1e92b726d Implement MCP Configuration and Tool Integration for Sandbox
- Added functionality to build and manage MCP configuration for sandbox environments, allowing for dynamic tool execution.
- Enhanced the Assistant's Stream method to skip MCP tool calls in sandbox mode, with internal handling by Claude CLI.
- Introduced unit tests for MCP configuration building and skills directory resolution, ensuring robust integration.
- Updated sandbox manager to create IPC sessions and manage tool exposure dynamically, improving interaction with external agents.
- Enhanced documentation to reflect new features and integration points for MCP and skills within the sandbox.
2026-01-30 19:57:31 +08:00

117 lines
2.9 KiB
Go

package ipc
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"net"
"os"
"path/filepath"
"sync"
)
// Manager manages IPC sessions
type Manager struct {
sessions sync.Map // sessionID → *Session
sockDir string // Socket directory
}
// NewManager creates a new IPC manager
func NewManager(sockDir string) *Manager {
return &Manager{
sockDir: sockDir,
}
}
// Create creates a new IPC session
func (m *Manager) Create(ctx context.Context, sessionID string, agentCtx *AgentContext, mcpTools map[string]*MCPTool) (*Session, error) {
// Close existing session if any
m.Close(sessionID)
// Create socket path using hash to avoid path length issues
// Unix socket paths are limited to ~104-108 bytes
socketPath := m.socketPath(sessionID)
// Ensure directory exists
if err := os.MkdirAll(m.sockDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create socket directory: %w", err)
}
// Remove existing socket file if any
os.Remove(socketPath)
// Create Unix socket listener
listener, err := net.Listen("unix", socketPath)
if err != nil {
return nil, fmt.Errorf("failed to create Unix socket: %w", err)
}
// Set socket permissions (readable/writable by all users)
// This allows container processes running as non-root to connect
if err := os.Chmod(socketPath, 0666); err != nil {
listener.Close()
os.Remove(socketPath)
return nil, fmt.Errorf("failed to set socket permissions: %w", err)
}
// Create cancellable context
sessionCtx, cancel := context.WithCancel(ctx)
session := &Session{
ID: sessionID,
SocketPath: socketPath,
Listener: listener,
Context: agentCtx,
MCPTools: mcpTools,
cancel: cancel,
}
// Start serving in background
go session.serve(sessionCtx)
// Store session
m.sessions.Store(sessionID, session)
return session, nil
}
// Close closes an IPC session
func (m *Manager) Close(sessionID string) error {
if s, ok := m.sessions.LoadAndDelete(sessionID); ok {
session := s.(*Session)
return session.Close()
}
return nil
}
// Get returns an existing session
func (m *Manager) Get(sessionID string) (*Session, bool) {
if s, ok := m.sessions.Load(sessionID); ok {
return s.(*Session), true
}
return nil, false
}
// CloseAll closes all sessions
func (m *Manager) CloseAll() {
m.sessions.Range(func(key, value interface{}) bool {
session := value.(*Session)
session.Close()
m.sessions.Delete(key)
return true
})
}
// socketPath generates a short socket path using hash
// Unix socket paths are limited to ~104-108 bytes on most systems
func (m *Manager) socketPath(sessionID string) string {
hash := sha256.Sum256([]byte(sessionID))
shortHash := hex.EncodeToString(hash[:8]) // 16 chars
return filepath.Join(m.sockDir, shortHash+".sock")
}
// GetSocketPath returns the socket path for a session ID (for external use)
func (m *Manager) GetSocketPath(sessionID string) string {
return m.socketPath(sessionID)
}