package workspace import ( "context" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strings" "time" "chorus.services/bzzz/pkg/config" ) // HCFSWorkspaceManager manages agent workspaces using HCFS as the backing store type HCFSWorkspaceManager struct { hcfsAPIURL string mountPath string client *http.Client activeSpaces map[string]*HCFSWorkspace } // HCFSWorkspace represents an HCFS-backed workspace for an agent type HCFSWorkspace struct { ID string `json:"id"` AgentID string `json:"agent_id"` TaskID string `json:"task_id"` HCFSPath string `json:"hcfs_path"` LocalMount string `json:"local_mount"` CreatedAt time.Time `json:"created_at"` LastUsed time.Time `json:"last_used"` } // HCFSContextRequest represents a request to create context in HCFS type HCFSContextRequest struct { Path string `json:"path"` Content string `json:"content"` Summary string `json:"summary"` Metadata map[string]interface{} `json:"metadata"` } // HCFSContextResponse represents the response from HCFS context creation type HCFSContextResponse struct { ID string `json:"id"` Path string `json:"path"` CreatedAt string `json:"created_at"` Metadata map[string]interface{} `json:"metadata"` } // NewHCFSWorkspaceManager creates a new HCFS workspace manager func NewHCFSWorkspaceManager(hcfsAPIURL string, mountPath string) *HCFSWorkspaceManager { return &HCFSWorkspaceManager{ hcfsAPIURL: hcfsAPIURL, mountPath: mountPath, client: &http.Client{Timeout: 30 * time.Second}, activeSpaces: make(map[string]*HCFSWorkspace), } } // CreateWorkspace creates a new HCFS-backed workspace for an agent func (m *HCFSWorkspaceManager) CreateWorkspace(ctx context.Context, agentID, taskID string, agentConfig *config.AgentConfig) (*HCFSWorkspace, error) { // Generate unique workspace ID workspaceID := fmt.Sprintf("bzzz-workspace-%s-%d", agentID, time.Now().Unix()) // Create HCFS path for the workspace hcfsPath := fmt.Sprintf("/agents/%s/workspaces/%s", agentID, workspaceID) // Create workspace context in HCFS contextReq := HCFSContextRequest{ Path: hcfsPath, Content: fmt.Sprintf("Workspace for agent %s, task %s", agentID, taskID), Summary: fmt.Sprintf("Agent workspace: %s", workspaceID), Metadata: map[string]interface{}{ "agent_id": agentID, "task_id": taskID, "workspace_id": workspaceID, "created_by": "bzzz_sandbox", "workspace_type": "agent_sandbox", }, } contextID, err := m.createHCFSContext(ctx, contextReq) if err != nil { return nil, fmt.Errorf("failed to create HCFS context: %w", err) } // Create local mount point using HCFS FUSE localMount := filepath.Join(m.mountPath, workspaceID) if err := m.createHCFSMount(ctx, hcfsPath, localMount); err != nil { return nil, fmt.Errorf("failed to create HCFS mount: %w", err) } workspace := &HCFSWorkspace{ ID: contextID, AgentID: agentID, TaskID: taskID, HCFSPath: hcfsPath, LocalMount: localMount, CreatedAt: time.Now(), LastUsed: time.Now(), } // Track active workspace m.activeSpaces[workspaceID] = workspace fmt.Printf("✅ HCFS workspace created: %s -> %s\n", hcfsPath, localMount) return workspace, nil } // DestroyWorkspace cleans up an HCFS workspace func (m *HCFSWorkspaceManager) DestroyWorkspace(ctx context.Context, workspace *HCFSWorkspace) error { if workspace == nil { return nil } // Unmount HCFS FUSE mount if err := m.unmountHCFS(workspace.LocalMount); err != nil { fmt.Printf("⚠️ Warning: failed to unmount HCFS: %v\n", err) } // Update HCFS context with final state if err := m.finalizeWorkspaceContext(ctx, workspace); err != nil { fmt.Printf("⚠️ Warning: failed to finalize workspace context: %v\n", err) } // Remove from active workspaces delete(m.activeSpaces, workspace.ID) fmt.Printf("✅ HCFS workspace destroyed: %s\n", workspace.HCFSPath) return nil } // GetWorkspacePath returns the local path for container mounting func (m *HCFSWorkspaceManager) GetWorkspacePath(workspace *HCFSWorkspace) string { if workspace == nil { return "" } return workspace.LocalMount } // UpdateWorkspaceUsage updates the last used timestamp func (m *HCFSWorkspaceManager) UpdateWorkspaceUsage(workspace *HCFSWorkspace) { if workspace != nil { workspace.LastUsed = time.Now() } } // StoreWorkspaceArtifacts stores final workspace artifacts in HCFS func (m *HCFSWorkspaceManager) StoreWorkspaceArtifacts(ctx context.Context, workspace *HCFSWorkspace, artifacts map[string]string) error { if workspace == nil { return fmt.Errorf("workspace is nil") } // Store each artifact as a separate context in HCFS for artifactName, content := range artifacts { artifactPath := fmt.Sprintf("%s/artifacts/%s", workspace.HCFSPath, artifactName) artifactReq := HCFSContextRequest{ Path: artifactPath, Content: content, Summary: fmt.Sprintf("Artifact from workspace %s: %s", workspace.ID, artifactName), Metadata: map[string]interface{}{ "workspace_id": workspace.ID, "agent_id": workspace.AgentID, "task_id": workspace.TaskID, "artifact_name": artifactName, "artifact_type": "workspace_output", "created_by": "bzzz_sandbox", }, } _, err := m.createHCFSContext(ctx, artifactReq) if err != nil { return fmt.Errorf("failed to store artifact %s: %w", artifactName, err) } } return nil } // createHCFSContext creates a new context in HCFS func (m *HCFSWorkspaceManager) createHCFSContext(ctx context.Context, req HCFSContextRequest) (string, error) { reqBody, err := json.Marshal(req) if err != nil { return "", fmt.Errorf("failed to marshal request: %w", err) } httpReq, err := http.NewRequestWithContext(ctx, "POST", m.hcfsAPIURL+"/contexts", strings.NewReader(string(reqBody))) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") resp, err := m.client.Do(httpReq) if err != nil { return "", fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { body, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("HCFS API returned status %d: %s", resp.StatusCode, string(body)) } var contextResp HCFSContextResponse if err := json.NewDecoder(resp.Body).Decode(&contextResp); err != nil { return "", fmt.Errorf("failed to decode response: %w", err) } return contextResp.ID, nil } // createHCFSMount creates a FUSE mount for the HCFS path func (m *HCFSWorkspaceManager) createHCFSMount(ctx context.Context, hcfsPath, localPath string) error { // Ensure the mount directory exists if err := os.MkdirAll(localPath, 0755); err != nil { return fmt.Errorf("failed to create mount directory: %w", err) } // TODO: Implement HCFS FUSE mount // For now, we'll create a placeholder directory structure // In a full implementation, this would mount the HCFS path using FUSE fmt.Printf("🔗 Creating HCFS FUSE mount: %s -> %s\n", hcfsPath, localPath) // Create basic workspace structure subdirs := []string{"src", "build", "output", "logs"} for _, subdir := range subdirs { if err := os.MkdirAll(filepath.Join(localPath, subdir), 0755); err != nil { return fmt.Errorf("failed to create workspace subdir %s: %w", subdir, err) } } return nil } // unmountHCFS unmounts the HCFS FUSE mount func (m *HCFSWorkspaceManager) unmountHCFS(localPath string) error { // TODO: Implement HCFS FUSE unmount // For now, we'll just remove the directory fmt.Printf("🔌 Unmounting HCFS: %s\n", localPath) return os.RemoveAll(localPath) } // finalizeWorkspaceContext updates the workspace context with final metadata func (m *HCFSWorkspaceManager) finalizeWorkspaceContext(ctx context.Context, workspace *HCFSWorkspace) error { // Update workspace context with completion metadata finalReq := HCFSContextRequest{ Path: workspace.HCFSPath + "/completion", Content: fmt.Sprintf("Workspace %s completed at %s", workspace.ID, time.Now().Format(time.RFC3339)), Summary: fmt.Sprintf("Completion record for workspace %s", workspace.ID), Metadata: map[string]interface{}{ "workspace_id": workspace.ID, "agent_id": workspace.AgentID, "task_id": workspace.TaskID, "completed_at": time.Now().Format(time.RFC3339), "total_duration": time.Since(workspace.CreatedAt).Seconds(), "record_type": "workspace_completion", }, } _, err := m.createHCFSContext(ctx, finalReq) return err } // GetActiveWorkspaces returns all currently active workspaces func (m *HCFSWorkspaceManager) GetActiveWorkspaces() map[string]*HCFSWorkspace { return m.activeSpaces } // CleanupIdleWorkspaces removes workspaces that haven't been used recently func (m *HCFSWorkspaceManager) CleanupIdleWorkspaces(ctx context.Context, maxIdleTime time.Duration) error { now := time.Now() var toCleanup []*HCFSWorkspace for _, workspace := range m.activeSpaces { if now.Sub(workspace.LastUsed) > maxIdleTime { toCleanup = append(toCleanup, workspace) } } for _, workspace := range toCleanup { if err := m.DestroyWorkspace(ctx, workspace); err != nil { fmt.Printf("⚠️ Failed to cleanup idle workspace %s: %v\n", workspace.ID, err) } } return nil }