* 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>
The default Docker API version was hardcoded to 1.41, which fails on
newer Docker daemons that require at least API version 1.44.
Change the default to empty string so the Docker client uses the
existing WithAPIVersionNegotiation() option to auto-negotiate a
compatible version with the daemon.
* feat(backend): accept github_url in task creation and clone on launch
Add github_url field to task creation DTO and propagate through HTTP/WS
handlers and service layer. When a task is created with a GitHub URL
instead of a local repository, the service resolves (or creates) the
repository record via FindOrCreateRepository and triggers a clone/fetch
via repoclone.Cloner during executor pre-flight.
Wire RepoCloner into the orchestrator executor so remote repositories
are fetched before the agent process starts. On resume, RepoUpdater
performs a fetch to ensure the worktree is up-to-date.
* feat(backend): add GitHub branches endpoint for remote repos
Add ListRepoBranches to the GitHub client interface (GHClient,
PATClient, NoopClient, MockClient) and expose it via
GET /api/v1/github/repos/:owner/:repo/branches.
The mock controller gains POST /api/v1/github/mock/branches for
seeding branch data in E2E tests.
* feat(web): add GitHub URL input to task create dialog
Add a toggle in the task create dialog that switches between the
existing repository selector and a free-text GitHub URL input. When a
valid GitHub URL is entered (owner/repo parsed), branches are fetched
from the new backend endpoint and auto-selected.
The dialog state, effects, validation, and submit logic are extended to
support the github_url flow alongside the existing repository flow.
* test(e2e): add tests for task creation from GitHub URL
Add E2E tests verifying:
- Full flow: toggle to GitHub URL mode, enter URL, create task, verify
agent runs and completes in session view
- Toggle behavior: switching between GitHub URL and repository selector
modes updates UI correctly
* fix(web): dynamic URL input sizing and extract RepoSourceInput
Replace fixed-width GitHub URL input with size-based auto-sizing
(matching InlineTaskName pattern). Extract RepoSourceInput component
from DialogHeaderContent to stay within cyclomatic complexity limits.
Add testId prop to Combobox and wire it to ExecutorProfileSelector
for E2E test targeting.
* test(e2e): add worktree executor test and fix GitHub URL repo seeding
Update GitHub URL E2E tests to pre-seed a repository with GitHub
provider info pointing to the local git repo, avoiding actual clone
attempts during task creation.
Add test for GitHub URL task creation with worktree executor type,
verifying the full flow including executor profile selection.
Extend ApiClient with listExecutors and provider fields on
createRepository for test seeding.
* fix(backend): use protocol-aware clone URL for provider-backed repos
The ensureRepoCloned function was hardcoding HTTPS clone URLs via
repositoryCloneURL(), but the user's git setup may require SSH.
Add BuildCloneURL to the RepoCloner interface so the executor uses
the Cloner's configured protocol (SSH/HTTPS) instead of always
generating HTTPS URLs. Falls back to HTTPS if BuildCloneURL fails.
This fixes workspace loading issues (terminal, file tree) for cloned
repos when the user has SSH-only authentication configured.
* test(e2e): enhance GitHub URL test with terminal and changes panel verification
* feat(web): add inline validation for GitHub URL input
- Show inline error for invalid URL format (not matching github.com/owner/repo)
- Show inline error when repository is not found or not accessible
- Mock client returns error for unknown repos (matches real GitHub API)
- E2E tests for invalid URL, nonexistent repo, and error-clearing on valid input
* fix(web): fix GitHub URL branch auto-select and E2E test stability
- Pick branch by name (main/master) for GitHub URL mode instead of
using selectPreferredBranch which returns origin-prefixed names
- Add test timeout for worktree executor test
- Use shorter filename in terminal test to avoid xterm line-wrap issues
* fix(web): fix GitHub URL dialog polish and wrong-repo bug
UI fixes:
- Align toggle button text with dropdown text content (pl-3)
- Match GitHub URL input height to dropdown trigger (min-h-9)
- Position error text absolutely to prevent layout shift
- Remove redundant toast on repo-not-found (inline error suffices)
Bug fix:
- Prevent repo auto-select from overriding GitHub URL mode by
guarding useRepositoryAutoSelectEffect with useGitHubUrl
- Reorder getRepositoriesPayload to check useGitHubUrl first,
preventing stale repositoryId from winning over the GitHub URL
Add E2E test verifying correct repo usage across consecutive tasks.
* fix(web): fix dialog height mismatch and error text overlap
- Use exact h-9 on GitHub URL input to match dropdown trigger height
- Move error text from absolute-positioned overlay to inline next to
the toggle button, preventing overlap while keeping layout stable
* fix(web): make GitHub URL input auto-size to content
Reduce minimum size from 30 to 20 and shorten placeholder so the
"/ task-name" sits closer to the URL text, matching the dropdown's
compact w-auto behavior.
* fix(web): polish GitHub URL input sizing, floating error, and placeholder color
- Match input height (h-7) and padding (px-2) to Combobox trigger button
- Size input width to exactly fit placeholder text
- Show validation error as floating popover below input instead of inline
- Keep placeholder text grey even when input has error state
- Extract useGitHubUrlState hook to fix max-lines-per-function lint
* fix(backend): return proper HTTP status codes from GitHub branches endpoint
Add GitHubAPIError type to distinguish upstream GitHub API status codes.
The branches endpoint now returns 404 (not 500) for non-existent repos,
which the logging middleware correctly logs as DEBUG instead of ERROR.
* feat(web): show error toast when a task session fails to start
Add session failure notification plumbing: WS handler detects FAILED
state with error_message, pushes to Zustand store, and a global bridge
component shows an error toast via the ToastProvider. Also adds
data-testid attributes to toast elements and types error_message on
the session state changed payload.
* style: fix prettier formatting in task-create-dialog and related files
* fix(web): extract ToastList component to fix max-lines-per-function lint
* fix: address code review feedback for GitHub URL task creation
- Preserve 403 status instead of collapsing to 401 in branches endpoint
- Use clone-generic error instead of remote-docker-specific in ensureRepoCloned
- Trim whitespace in GitHub URL validation (backend + frontend)
- Add GitHubURL field to wsUpdateTask path
- Fix DefaultBranch mapping to prefer repoInput.DefaultBranch over BaseBranch
- Clear githubBranchesLoading on early-return paths to prevent stuck state
- Gate GitHub branch loading by mode to avoid false loading states
- Encode owner/repo in API path for special characters
- Add aria-label, aria-invalid, aria-errormessage for accessibility
* fix(e2e): improve diff-update test stability with fresh locator and longer timeout
* fix(backend): add --paginate flag to gh api branches call for repos with >100 branches
* fix: address second round of code review feedback
- Add pagination support to PATClient.ListRepoBranches for repos with >100 branches
- Validate owner/repo in mock controller addBranches endpoint
- Use aria-describedby instead of aria-errormessage for broader AT support
- Add aria-live region to toast container for screen reader announcements
- Assert absence of FIRST_MODIFICATION in diff-update E2E test
* fix(backend): downgrade HTTP request logger from error to warn for 4xx/5xx responses
* fix: address third round of code review feedback
- Validate repositoryID/localPath in UpdateRepositoryLocalPath
- Copy slices in mock client to prevent external mutation
- Extract GitHub header constants and shared setter in PATClient
- Redact clone URL from logs to prevent credential leaks
- Guard against persisting empty localPath after clone
- Show fallback toast message when FAILED state has no error_message
* fix: strip origin/ prefix from base branch for rebase/merge operations
The frontend was passing 'origin/main' as the base branch, but the backend
expects just the branch name (e.g., 'main') and constructs 'origin/' itself.
This caused git fetch to fail with:
fatal: couldn't find remote ref origin/main
Because the command became 'git fetch origin origin/main' instead of
'git fetch origin main'.
* fix: increase prompt timeout to 60min, add error feedback, rename acp to agent API
- Add PromptTimeout constant (60 minutes) in common/constants/timeouts.go
- Update agentctl client and server to use the constant
- Add error message creation when prompt fails (timeout, agent not running, etc)
- Rename /acp/* endpoints to /agent/* (protocol-agnostic naming)
- Rename acp.go files to agent.go in both client and server
- Rename handler functions from handleACP* to handleAgent*
* feat: replace agent_type with agent_profile_id
- Replace agent_type field with agent_profile_id across backend and frontend
- Add ProfileResolver interface and StoreProfileResolver implementation
- Update task creation/update to use agent_profile_id
- Change default agent runtime from docker to standalone
- Fix workspace state types to include all required fields
- Fix websocket handlers type mismatches for environments, executors, workspaces
- Update integration tests to use agent_profile_id
* feat: pass model as CLI argument to agents
- Add ModelFlag field to AgentTypeConfig for specifying the CLI flag
- Set ModelFlag to '--model' for all agents (auggie, claude, codex, gemini, opencode, copilot)
- Update launchStandalone to append model flag to agent command
- Update launchDocker/buildContainerConfig to append model flag to container command
- Model is passed when both agent supports ModelFlag and profile has Model configured
* fix: make WebSocket message handling async to prevent blocking
When a long-running handler like orchestrator.prompt was executing,
it blocked the ReadPump loop, preventing other requests (like
workspace.tree.get) from being processed until the handler completed.
Now each message is handled in its own goroutine, allowing concurrent
message handling so file tree requests work while the agent is working.
* feat: show task description as first message in chat
- Add 'Task' label with distinct styling (lighter background, border)
- Pass taskDescription from page to TaskChatPanel component
- Differentiate from regular user messages visually
- Add worktree package with Manager, Store, Config, and error types
- Integrate worktree creation/reuse into lifecycle manager
- Update executor to pass worktree configuration
- Add WorktreeConfig to common config package
- Enable worktrees by default for agent isolation
- Add unit tests for worktree manager
This allows multiple agents to work on the same repository simultaneously
by creating isolated worktrees for each task. Worktrees persist between
agent runs for task resumption and are only cleaned up when tasks are
explicitly deleted.
- Use acp-go-sdk for all ACP communication in agentctl
- Mark agent as completed when Prompt() returns successfully
- Update task state to COMPLETED when agent completes
- Update task state to FAILED when agent fails and retries exhausted
- Enhanced E2E test to verify agent actually creates files
- E2E test now validates full flow: task creation -> agent work -> file creation -> task completion