8 Commits

Author SHA1 Message Date
Henry e75c8b37c8 feat(executors): keep worktree and local agents running across a backend restart (#3467)
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-09-13 21:42:24 +01:00
Henry dfb53409f3 fix(backend): retry SSH agentctl handshake rejection instead of failing the launch (#3511)
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com>
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-09-08 22:26:48 +01:00
Carlos Florêncio 431e5d40fe fix: enforce classified Git admission paths (#2150) (#2181) 2026-08-03 11:04:34 +01:00
João Salavisa dccff23cc0 feat(backend): per-launch authentication for agentctl (#666)
* feat(backend): per-launch authentication for agentctl

Add nonce-based handshake protocol to secure all agentctl HTTP/WS
endpoints. On launch, the parent (backend) passes a one-time bootstrap
nonce via AGENTCTL_BOOTSTRAP_NONCE env var. agentctl generates its own
auth token, enables Bearer auth immediately, and returns the token to
the parent via POST /auth/handshake (validated by nonce).

Standalone, Docker, and Sprites executors all use the same handshake
flow. Removes the old AGENTCTL_AUTH_TOKEN env var approach. For
Docker/Sprites, the retrieved token is persisted encrypted in
SecretStore (AES-256-GCM) for crash recovery.

Security properties:
- Auth enabled from boot (no unprotected window)
- Token never in env vars (only the burned nonce)
- Nonce is one-shot (replay impossible)
- Constant-time token comparison (timing-safe)

* fix: check fmt.Sscanf return value in integration test

* fix: address review feedback - constant-time nonce comparison, safe ID truncation

* fix(backend): exempt MCP routes from agentctl auth middleware

The agent subprocess connects to agentctl's MCP endpoints (/sse, /message,
/mcp) to use kandev tools (create_task, ask_user_question, etc.). These
endpoints run on the same HTTP server as the authenticated API, but the
agent process doesn't possess the auth token — it was only exchanged
between the backend and agentctl during the handshake.

Exempt /sse, /message, and /mcp from the Bearer auth middleware since the
agent process runs in the same trust boundary as agentctl.

* refactor(backend): remove query param token fallback and document SetAuthToken constraint

Remove the ?token= query parameter fallback from extractBearerToken —
tokens in URLs leak via access logs, referer headers, and browser history.
All WS callers already use Authorization headers via wsAuthHeaders().

Document SetAuthToken's concurrency constraint: it must not be called
concurrently with HTTP requests on the same client.

* fix(backend): enforce mutex in ControlClient/Launcher auth, fix Start() lock scope

- Add sync.Mutex to ControlClient; protect SetAuthToken and AuthToken() reads
- Guard Launcher.AuthToken() with l.mu (written under lock in Start, read unprotected)
- Extract buildAndStartProcess and performHandshake from Start() (was 106 lines, over 80-line limit)
- Release l.mu before blocking health/handshake calls so monitorExit() can close
  l.exited — previously the fast-fail path in waitForHealthy was unreachable
- Update stale executor_backend.go comment (standalone no longer uses env var auth)

Co-authored-by: João Salavisa <undefined@users.noreply.github.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
2026-04-21 14:00:49 +01:00
Carlos Florêncio 9af1b05bfb fix: resolve acp chat ux issues with permissions, plans, and tool states (#428) 2026-03-12 23:22:26 +00:00
Carlos Florêncio efd36ffbb7 feat: http logging middleware + mcp random port (#74) 2026-01-22 02:27:14 +00:00
João Salavisa 18f4b4e6fb Cleanup deprecated code and fix session recovery (#37) 2026-01-18 15:58:56 +00:00
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