- 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.
117 lines
2.9 KiB
Go
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)
|
|
}
|