Files
kandev/apps/backend/internal/agentctl/server/instance/instance.go
T
João Salavisa 2e108b040f refactor: session resumption cleanup and AgentInstance → AgentExecution rename (#23)
* feat: session resumption with ACP session ID support

- Add useSessionResumption hook for automatic session recovery on page reload
- Fix ACPSessionID not being set on AgentInstance in Launch() and createInstance()
- Add task.session.status and task.session.resume WebSocket actions
- Fix shell terminal retry logic for resumed sessions
- Fix taskSessionState reading from Zustand store instead of local state
- Remove dead code: unused stream handling functions in lifecycle/manager.go
- Fix test mock interfaces (CleanupStaleInstanceByTaskID, NewManager signature)
- Add enhanced logging for session resumption debugging
- Simplify page-client.tsx state merging logic

* refactor: remove unused AgentController (dead code)

- Delete agent_controller.go - entirely unused duplicate of Manager methods
- Remove agentController field from Manager struct
- Remove AgentController initialization in NewManager

The AgentController duplicated StartAgentProcess, initializeACPSession, and
PromptAgent from Manager but was never called. All agent control operations
go through Manager's own methods.

* refactor: remove unused MemoryRepository (dead code)

- Delete memory.go - entire MemoryRepository implementation unused
  (replaced by SQLite repository)
- Delete memory_test.go - tests for removed MemoryRepository

Found via 'deadcode' tool analysis.

* refactor: consolidate agentctl packages into server/client structure

Reorganize the confusing dual agentctl packages:
- internal/agentctl/* → internal/agentctl/server/
- internal/agent/agentctl/* → internal/agentctl/client/

Structure is now:
  internal/agentctl/
  ├── client/      - HTTP client for talking to agentctl servers
  │   ├── client.go
  │   ├── standalone.go
  │   ├── acp.go
  │   └── launcher/
  ├── server/      - agentctl server implementation (runs in containers)
  │   ├── api/
  │   ├── adapter/
  │   ├── acp/
  │   ├── config/
  │   ├── instance/
  │   ├── process/
  │   └── shell/
  └── types/       - Shared types used by both client and server

Also consolidated shared types (GitStatusUpdate, FileInfo, etc.) from
process/types.go into the shared types/ package.

* refactor: rename AgentInstance to AgentExecution for clarity

Rename AgentInstance -> AgentExecution across the entire codebase to better
reflect what it represents: an agent actively working on a task, not just
a generic 'instance'.

Changes:
- AgentInstance struct -> AgentExecution
- InstanceStore -> ExecutionStore (new file: execution_store.go)
- RecoveredInstance -> RecoveredExecution
- All method names: GetInstance* -> GetExecution*, ListInstances -> ListExecutions, etc.
- All parameters: instanceID -> executionID
- All local variables: instance -> execution
- DTOs and JSON tags: agent_instance_id -> agent_execution_id
- Database columns: agent_instance_id -> agent_execution_id

Also consolidates duplicate prompt logic:
- Merge sendInitialPrompt and SendPrompt into single SendPrompt method
- Add validateStatus parameter to differentiate initial vs follow-up prompts

Also extracts ACP session management to session.go:
- Move initializeACPSession logic to SessionManager.InitializeAndPrompt
- Move PromptAgent logic to SessionManager.SendPrompt
- Manager now delegates to SessionManager for all prompt operations

Also removes duplicate event publishing:
- Remove 9 duplicate m.publish*() methods from manager.go
- Use existing EventPublisher methods via m.eventPublisher.Publish*()

Also extracts types to dedicated file:
- Move AgentExecution, LaunchRequest, PromptResult, etc. to types.go

Net reduction: ~600 lines removed from manager.go through deduplication
and better separation of concerns.

* fix(backend): Docker runtime now creates instance via ControlServer

The Docker runtime was incorrectly creating an agentctl.Client pointing
directly to port 9999 (the ControlServer port), then trying to call
instance-specific APIs like /api/v1/agent/configure which only exist
on per-instance servers.

Fixed by making Docker runtime follow the same flow as standalone mode:
- LaunchContainer now uses StandaloneCtl to create an instance via the
  control API, then creates Client pointing to the instance port
- RecoverInstances queries the ControlServer to get the instance port
- Added waitForHealth helper to wait for agentctl to be ready

Also improved HTTP error handling in agentctl client:
- All client methods now check HTTP status codes before JSON decoding
- Better error messages include status code and response body

* fix: restore session resumption imports and variables

* fix: add sessionId prop back to TaskLayout
2026-01-16 22:14:19 +00:00

127 lines
4.1 KiB
Go

// Package instance provides data structures for multi-agent instance management.
// It defines the core types used to represent, create, and serialize agent instances
// that run as separate processes with their own HTTP servers.
package instance
import (
"net/http"
"time"
"github.com/kandev/kandev/internal/agentctl/server/process"
)
// Instance represents a single agent instance running as a subprocess.
// Each instance has its own process manager, HTTP server, and configuration.
type Instance struct {
// ID is the unique identifier for this instance
ID string
// Port is the HTTP port this instance is listening on
Port int
// Status is the current status of the instance (e.g., "running", "stopped", "error")
Status string
// WorkspacePath is the absolute path to the workspace directory for this instance
WorkspacePath string
// AgentCommand is the command used to start the agent subprocess
AgentCommand string
// Env contains environment variables passed to the agent process
Env map[string]string
// CreatedAt is the timestamp when this instance was created
CreatedAt time.Time
// manager is the process manager handling the agent subprocess (unexported)
manager *process.Manager
// server is the HTTP server for this instance's API (unexported)
server *http.Server
}
// CreateRequest contains the parameters for creating a new agent instance.
type CreateRequest struct {
// ID is an optional identifier for the instance. If empty, one will be generated.
ID string `json:"id,omitempty"`
// WorkspacePath is the required absolute path to the workspace directory.
WorkspacePath string `json:"workspace_path"`
// AgentCommand is an optional command to start the agent. If empty, a default is used.
AgentCommand string `json:"agent_command,omitempty"`
// Protocol is the protocol adapter to use (acp, codex). If empty, default is used.
Protocol string `json:"protocol,omitempty"`
// WorkspaceFlag is the CLI flag for workspace path (e.g., "--workspace-root").
// If empty, only cwd is used for workspace path.
WorkspaceFlag string `json:"workspace_flag,omitempty"`
// Env contains optional environment variables to pass to the agent process.
Env map[string]string `json:"env,omitempty"`
// AutoStart indicates whether to start the agent automatically after creation.
AutoStart bool `json:"auto_start,omitempty"`
}
// CreateResponse contains the result of creating a new agent instance.
type CreateResponse struct {
// ID is the unique identifier assigned to the created instance
ID string `json:"id"`
// Port is the HTTP port the instance is listening on
Port int `json:"port"`
}
// InstanceInfo contains serializable information about an instance for API responses.
// It mirrors the exported fields from Instance and is safe for JSON serialization.
type InstanceInfo struct {
// ID is the unique identifier for this instance
ID string `json:"id"`
// Port is the HTTP port this instance is listening on
Port int `json:"port"`
// Status is the current status of the instance
Status string `json:"status"`
// WorkspacePath is the absolute path to the workspace directory
WorkspacePath string `json:"workspace_path"`
// AgentCommand is the command used to start the agent subprocess
AgentCommand string `json:"agent_command"`
// Env contains environment variables passed to the agent process
Env map[string]string `json:"env,omitempty"`
// CreatedAt is the timestamp when this instance was created
CreatedAt time.Time `json:"created_at"`
}
// Info returns a safe copy of the instance data for API serialization.
// This method creates an InstanceInfo struct containing only the exported,
// serializable fields from the Instance.
func (i *Instance) Info() *InstanceInfo {
// Create a copy of the environment map to prevent external modification
var envCopy map[string]string
if i.Env != nil {
envCopy = make(map[string]string, len(i.Env))
for k, v := range i.Env {
envCopy[k] = v
}
}
return &InstanceInfo{
ID: i.ID,
Port: i.Port,
Status: i.Status,
WorkspacePath: i.WorkspacePath,
AgentCommand: i.AgentCommand,
Env: envCopy,
CreatedAt: i.CreatedAt,
}
}