Files
kandev/apps/backend/internal/agent/lifecycle/runtime.go
T
2026-02-14 15:54:11 +00:00

131 lines
4.4 KiB
Go

// Package lifecycle provides agent runtime abstractions.
package lifecycle
import (
"context"
"time"
"github.com/kandev/kandev/internal/agent/agents"
"github.com/kandev/kandev/internal/agent/runtime"
agentctl "github.com/kandev/kandev/internal/agentctl/client"
"github.com/kandev/kandev/internal/agentctl/server/process"
v1 "github.com/kandev/kandev/pkg/api/v1"
)
// Runtime abstracts the agent execution environment (Docker, Standalone, K8s, SSH, etc.)
// Each runtime is responsible for creating and managing agentctl instances.
// Agent subprocess launching is handled separately via agentctl client methods.
type Runtime interface {
// Name returns the runtime identifier (e.g., "docker", "standalone", "k8s")
Name() runtime.Name
// HealthCheck verifies the runtime is available and operational
HealthCheck(ctx context.Context) error
// CreateInstance creates a new agentctl instance for a task.
// This starts the agentctl process/container with workspace access (shell, git, files).
// Agent subprocess is NOT started - use agentctl.Client.ConfigureAgent() + Start().
CreateInstance(ctx context.Context, req *RuntimeCreateRequest) (*RuntimeInstance, error)
// StopInstance stops an agentctl instance.
StopInstance(ctx context.Context, instance *RuntimeInstance, force bool) error
// RecoverInstances discovers and recovers instances that were running before a restart.
// Returns recovered instances that can be re-tracked by the manager.
RecoverInstances(ctx context.Context) ([]*RuntimeInstance, error)
// GetInteractiveRunner returns the interactive runner for passthrough mode.
// May return nil if the runtime doesn't support passthrough mode.
GetInteractiveRunner() *process.InteractiveRunner
}
// McpServerConfig holds configuration for an MCP server.
type McpServerConfig struct {
Name string `json:"name"`
URL string `json:"url,omitempty"`
Type string `json:"type,omitempty"`
Command string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
}
// Metadata keys for runtime-specific configuration
const (
MetadataKeyMainRepoGitDir = "main_repo_git_dir"
MetadataKeyWorktreeID = "worktree_id"
MetadataKeyWorktreeBranch = "worktree_branch"
)
// RuntimeCreateRequest contains parameters for creating an agentctl instance.
type RuntimeCreateRequest struct {
InstanceID string
TaskID string
SessionID string
AgentProfileID string
WorkspacePath string
Protocol string
Env map[string]string
Metadata map[string]interface{}
McpServers []McpServerConfig
AgentConfig agents.Agent // Agent type info needed by runtimes
}
// RuntimeInstance represents an agentctl instance created by a runtime.
// This is returned by the runtime and contains enough info to build an AgentExecution.
type RuntimeInstance struct {
// Core identifiers
InstanceID string
TaskID string
SessionID string
// Runtime name (e.g., "docker", "standalone") - set by the runtime that created this instance
RuntimeName string
// Agentctl client for communicating with this instance
Client *agentctl.Client
// Runtime-specific identifiers (only one set is populated)
ContainerID string // Docker
ContainerIP string // Docker
StandaloneInstanceID string // Standalone
StandalonePort int // Standalone
// Common fields
WorkspacePath string
Metadata map[string]interface{}
}
// ToAgentExecution converts a RuntimeInstance to an AgentExecution.
func (ri *RuntimeInstance) ToAgentExecution(req *RuntimeCreateRequest) *AgentExecution {
metadata := req.Metadata
if metadata == nil {
metadata = make(map[string]interface{})
}
// Merge runtime metadata
for k, v := range ri.Metadata {
metadata[k] = v
}
workspacePath := ri.WorkspacePath
if workspacePath == "" {
workspacePath = req.WorkspacePath
}
return &AgentExecution{
ID: ri.InstanceID,
TaskID: req.TaskID,
SessionID: req.SessionID,
AgentProfileID: req.AgentProfileID,
ContainerID: ri.ContainerID,
ContainerIP: ri.ContainerIP,
WorkspacePath: workspacePath,
RuntimeName: ri.RuntimeName,
Status: v1.AgentStatusRunning,
StartedAt: time.Now(),
Metadata: metadata,
agentctl: ri.Client,
standaloneInstanceID: ri.StandaloneInstanceID,
standalonePort: ri.StandalonePort,
promptDoneCh: make(chan PromptCompletionSignal, 1),
}
}