feat(preview): inspect mode with pin and area annotations (#917)

This commit is contained in:
Carlos Florêncio
2026-05-24 11:21:50 +01:00
committed by GitHub
parent 1395a7a416
commit 372779ad32
50 changed files with 3012 additions and 1205 deletions
+19 -314
View File
@@ -6,7 +6,7 @@
```
apps/
├── backend/ # Go backend (orchestrator, agent runtime, agentctl, WS gateway)
├── backend/ # Go backend (orchestrator, lifecycle, agentctl, WS gateway)
├── web/ # Next.js frontend (SSR + WS + Zustand)
├── cli/ # CLI tool (TypeScript)
├── landing/ # Landing page
@@ -21,7 +21,6 @@ apps/
- **UI**: Shadcn components via `@kandev/ui`
- **GitHub repo**: `https://github.com/kdlbs/kandev`
- **Container image**: `ghcr.io/kdlbs/kandev` (GitHub Container Registry)
- **Dev mock providers**: In `KANDEV_MOCK_AGENT=true` dev mode the base `mock-agent` is enabled alongside the real CLIs. To exercise the office provider-routing UI without installing real CLIs, set `KANDEV_MOCK_PROVIDERS=claude-acp,codex-acp,opencode-acp` (or any subset of `RoutableProviderIDs`) when launching `make dev` — those canonicals are then replaced with MockAgent aliases.
### Worktrees and commit hooks
@@ -29,275 +28,15 @@ The commit-msg hook runs `cd apps && pnpm exec commitlint --edit "$1"`. A fresh
---
## Backend Architecture
## Scoped guidance
### Package Structure
Architecture notes and per-area conventions live alongside the code they describe. Read the file in the directory you're working in:
```
apps/backend/
├── cmd/
│ ├── kandev/ # Main backend binary entry point
│ ├── agentctl/ # Agentctl binary (runs inside containers or standalone)
│ └── mock-agent/ # Mock agent for testing
├── internal/
│ ├── agent/
│ │ ├── runtime/ # Agent runtime: single seam for Launch/Resume/Stop/observe
│ │ │ ├── lifecycle/ # Agent instance management (moved from agent/lifecycle)
│ │ │ ├── agentctl/ # HTTP client for talking to agentctl (moved from agentctl/client)
│ │ │ └── routingerr/ # Provider error classifier + sanitizer + ProviderProber registry
│ │ ├── agents/ # Agent type implementations
│ │ ├── controller/ # Agent control operations
│ │ ├── credentials/ # Agent credential management
│ │ ├── discovery/ # Agent discovery
│ │ ├── docker/ # Docker-specific agent logic
│ │ ├── dto/ # Agent data transfer objects
│ │ ├── executor/ # Executor types, checks, and service
│ │ ├── handlers/ # Agent event handlers
│ │ ├── registry/ # Agent type registry and defaults
│ │ ├── settings/ # Agent settings
│ │ ├── mcpconfig/ # MCP server configuration
│ │ └── remoteauth/ # Remote auth catalog and method IDs for remote executors/UI
│ ├── agentctl/
│ │ └── server/ # agentctl HTTP server
│ │ ├── acp/ # ACP protocol implementation
│ │ ├── adapter/ # Protocol adapters + transport/ (ACP, Codex, OpenCode, Copilot, Amp)
│ │ ├── api/ # HTTP endpoints
│ │ ├── config/ # agentctl configuration
│ │ ├── instance/ # Multi-instance management
│ │ ├── mcp/ # MCP server integration
│ │ ├── process/ # Agent subprocess management
│ │ ├── shell/ # Shell session management
│ │ └── utility/ # agentctl utilities
│ ├── orchestrator/ # Task execution coordination
│ │ ├── dto/ # Orchestrator data transfer objects
│ │ ├── executor/ # Launches agents via lifecycle manager
│ │ ├── handlers/ # Orchestrator event handlers
│ │ ├── messagequeue/ # Message queue for agent prompts
│ │ ├── queue/ # Task queue
│ │ ├── scheduler/ # Task scheduling
│ │ └── watcher/ # Event handlers
│ ├── task/
│ │ ├── controller/ # Task HTTP/WS controllers
│ │ ├── dto/ # Task data transfer objects
│ │ ├── events/ # Task event types
│ │ ├── handlers/ # Task event handlers
│ │ ├── models/ # Task, Session, Executor, Message models
│ │ ├── repository/ # Database access (SQLite)
│ │ ├── service/ # Task business logic
│ │ └── share/ # Public share links: snapshot builder, gist backend, service, redaction
│ ├── office/ # Autonomous agent management (Office feature)
│ │ ├── agents/ # Agent instance CRUD + auth guards
│ │ ├── approvals/ # Approval requests and decisions
│ │ ├── channels/ # External integration channels (webhooks)
│ │ ├── config/ # Config sync (DB ↔ filesystem)
│ │ ├── configloader/ # Filesystem config reader/writer
│ │ ├── costs/ # Cost tracking and budget policies
│ │ ├── dashboard/ # Dashboard API, issues, activity, live runs
│ │ ├── infra/ # GC, reconciliation
│ │ ├── labels/ # Task labels
│ │ ├── onboarding/ # Workspace onboarding wizard API
│ │ ├── projects/ # Project management
│ │ ├── repository/ # Office SQLite persistence
│ │ ├── runtime/ # Agent run context, capabilities, and runtime action surface
│ │ ├── routines/ # Scheduled recurring tasks
│ │ ├── routing/ # Provider routing: resolver, validators, catalogue, backoff, agent-overrides
│ │ ├── scheduler/ # Wakeup scheduler (duplicate of service scheduler features)
│ │ ├── service/ # Core office service (wakeups, event subscribers, execution policy)
│ │ ├── shared/ # Shared interfaces and activity logging
│ │ ├── skills/ # Skill injection and materialization
│ │ └── workspaces/ # Workspace deletion handler
│ ├── events/ # Event bus for internal pub/sub
│ ├── gateway/ # WebSocket gateway
│ ├── github/ # GitHub API integration (PRs, reviews, webhooks)
│ ├── gitlab/ # GitLab API integration (MRs, discussions, pipelines)
│ ├── common/ # Shared utilities, config, logger
│ ├── integration/ # External integrations
│ ├── integrations/ # Shared shapes for third-party integrations
│ │ ├── healthpoll/ # Reusable 90s auth-health Poller (used by jira, linear)
│ │ └── secretadapter/ # Upsert-style adapter over secrets.SecretStore
│ ├── jira/ # Jira/Atlassian Cloud integration (config, REST client, poller)
│ ├── linear/ # Linear integration (config, GraphQL client, poller)
│ ├── lsp/ # LSP server
│ ├── mcp/ # MCP protocol support
│ ├── health/ # Health check endpoints
│ ├── notifications/ # Notification system
│ ├── persistence/ # Persistence layer
│ ├── prompts/ # Prompt management
│ ├── repoclone/ # Repository cloning for remote executors
│ ├── scriptengine/ # Script placeholder resolution and interpolation
│ ├── secrets/ # Secret management
│ ├── sprites/ # Sprites AI integration
│ ├── sysprompt/ # System prompt injection
│ ├── task/
│ │ └── ...
│ ├── tools/ # Tool integrations
│ ├── user/ # User management
│ ├── utility/ # Shared utility functions
│ ├── workflow/ # Workflow engine
│ │ ├── engine/ # Typed state-machine engine
│ │ ├── models/ # Workflow step, template, and history models
│ │ ├── repository/ # Workflow persistence (SQLite)
│ │ └── service/ # Workflow CRUD and step resolution
│ └── worktree/ # Git worktree management for workspace isolation
```
### Key Concepts
**Orchestrator** coordinates task execution:
- Receives task start/stop/resume requests via WebSocket
- Delegates to lifecycle manager for agent operations
- Handles event-driven state transitions via workflow engine
- Located in `internal/orchestrator/`
**Workflow Engine** (`internal/workflow/engine/`) provides typed state-machine evaluation:
- `Engine.HandleTrigger()` evaluates step actions for triggers (on_enter, on_turn_start, on_turn_complete, on_exit)
- `TransitionStore` interface abstracts persistence (implemented by `orchestrator.workflowStore`)
- `CallbackRegistry` maps action kinds to callbacks (plan mode, auto-start, context reset)
- First-transition-wins: multiple transition actions in one trigger, first eligible wins
- `EvaluateOnly` mode: engine evaluates without persisting, caller orchestrates on_exit → DB → on_enter
- `RequiresApproval` on actions: transitions requiring review gating are skipped
- Idempotent by `OperationID`; session-scoped data bag via `MachineState.Data`
**Agent Runtime** (`internal/agent/runtime/`) is the single seam for launching, resuming, stopping, and observing agent executions. ADR 0004 introduced this in Phase 1 of task-model-unification. The public surface is `runtime.Runtime` (`runtime.go`); a thin facade (`facade.go`) delegates to a `Backend` (satisfied by `*lifecycle.Manager`).
**Convention:** only `internal/agent/runtime/` (and code that pre-dates Phase 1 migration) may import `runtime/lifecycle` or `runtime/agentctl` directly. New consumers — workflow engine actions, cron-driven trigger handlers, future task-tier callers — should depend on `runtime.Runtime`. Existing call sites are migrated through later phases of task-model-unification.
**Lifecycle Manager** (`internal/agent/runtime/lifecycle/`) manages agent instances under the runtime:
- `Manager` (`manager.go`, `manager_*.go`) - central coordinator for agent lifecycle
- `ExecutorBackend` interface (`executor_backend.go`) - abstracts execution environment (Docker, Standalone, Sprites, Remote Docker)
- `ExecutionStore` (`execution_store.go`) - thread-safe in-memory execution tracking
- `session.go` - ACP session initialization and resume
- `streams.go` - WebSocket stream connections to agentctl
- `process_runner.go` - agent process launch and management
- `profile_resolver.go` - resolves agent profiles/settings
**agentctl client** (`internal/agent/runtime/agentctl/`) is the HTTP/WS client used by the lifecycle manager to talk to a running agentctl instance. It is a runtime-tier package and should not be imported outside `internal/agent/runtime/`.
**agentctl** is an HTTP server that:
- Runs inside Docker containers or as standalone process
- Manages agent subprocess via stdin/stdout (ACP protocol)
- Exposes workspace operations (shell, git, files)
- Supports multiple concurrent instances on different ports
**Executor Types** (database model):
- `local_pc` - Standalone process on host ✅
- `local_docker` - Docker container on host ✅
- `sprites` - Sprites cloud environment ✅
- `remote_docker`, `remote_vps`, `k8s` - Planned
### Execution Flow
```
Client (WS) → Orchestrator → Lifecycle Manager → ExecutorBackend (container/process) → agentctl
↓
Client (WS) ← Orchestrator ← Lifecycle Manager ←──── stream updates (WS) ──────── agent subprocess
```
1. Orchestrator receives `session.launch` via WS
2. Lifecycle Manager creates executor instance (container or process)
3. agentctl starts inside the instance, agent subprocess is configured and started
4. Agent events stream back via WS through the chain
**Session Resume:** `TaskSession.ACPSessionID` stored for resume; `ExecutorRunning` tracks active state; on restart `RecoverInstances()` reconnects.
**Provider Pattern:** Packages expose `Provide(cfg, log) (*impl, cleanup, error)` for DI. Returns implementation, cleanup function, and error. Cleanup called during graceful shutdown.
**Worktrees:** `internal/worktree/Manager` provides workspace isolation. Each session can have its own worktree (branch) to prevent conflicts between concurrent agents.
**Executor default scripts:** Default prepare scripts are in `internal/agent/runtime/lifecycle/default_scripts.go`; `internal/scriptengine/` handles placeholder resolution.
---
## agentctl Server
### API Groups
agentctl exposes these route groups (see `internal/agentctl/server/api/`):
- `/health`, `/info`, `/status` - Health and status
- `/instances/*` - Multi-instance management
- `/processes/*` - Agent subprocess management (start/stop)
- `/agent/configure`, `/agent/stream` - Agent configuration and event streaming
- `/git/*` - Git operations (status, commit, push, pull, rebase, stage, etc.)
- `/shell/*` - Shell session management
- `/workspace/*` - File operations, search, tree
- `/vscode/*` - VS Code integration proxy
### Adapter Model
Protocol adapters in `adapter/transport/` normalize different agent CLIs:
- `AgentAdapter` interface defines `Start()`, `Stop()`, `Prompt()`, `Cancel()`
- Transports: `acp` (Claude Code), `codex` (OpenAI Codex), `opencode`, `shared`, `streamjson`
- Top-level adapters: `CopilotAdapter` (GitHub Copilot SDK), `AmpAdapter` (Sourcegraph Amp)
- `process.Manager` owns subprocess, wires stdio to adapter
- Factory pattern in `adapter/factory.go` selects adapter by agent type
### ACP Protocol
JSON-RPC 2.0 over stdin/stdout between agentctl and agent process. Requests: `initialize`, `session/new`, `session/load`, `session/prompt`, `session/cancel`. Notifications: `session/update` with types `message_chunk`, `tool_call`, `tool_update`, `complete`, `error`, `permission_request`, `context_window`.
---
## Frontend Architecture
### UI Components
**Shadcn Components:** Import from `@kandev/ui` package:
```typescript
import { Badge } from '@kandev/ui/badge';
import { Button } from '@kandev/ui/button';
import { Dialog } from '@kandev/ui/dialog';
// etc...
```
**Do NOT** import from `@/components/ui/*` - always use `@kandev/ui` package.
### Data Flow Pattern (Critical)
```
SSR Fetch -> Hydrate Store -> Components Read Store -> Hooks Subscribe
```
**Never fetch data directly in components.**
### Store Structure (Domain Slices)
```
lib/state/
├── store.ts # Root composition
├── default-state.ts # Default state + initial state merge
├── slices/ # Domain slices
│ ├── kanban/ # boards, tasks, columns
│ ├── session/ # sessions, messages, turns, worktrees
│ ├── session-runtime/ # shell, processes, git, context
│ ├── workspace/ # workspaces, repos, branches
│ ├── settings/ # executors, agents, editors, prompts (incl. userSettings)
│ ├── office/ # office agents, skills, projects, dashboard, issues
│ ├── comments/ # code review diff comments
│ ├── github/ # GitHub PRs, reviews
│ └── ui/ # preview, connection, active state, sidebar views
├── hydration/ # SSR merge strategies
hooks/domains/{kanban,session,workspace,settings,comments,github}/ # Domain-organized hooks
lib/api/domains/ # API clients
├── kanban-api, session-api, workspace-api, settings-api, process-api
├── plan-api, queue-api, workflow-api, stats-api, github-api
├── office-api, office-extended-api, tree-api
├── user-shell-api, debug-api, secrets-api, sprites-api, vscode-api
├── health-api, utility-api
```
**Key State Paths:**
- `messages.bySession[sessionId]`, `shell.outputs[sessionId]`, `gitStatus.bySessionId[sessionId]`
- `tasks.activeTaskId`, `tasks.activeSessionId`, `workspaces.activeId`
- `repositories.byWorkspace`, `repositoryBranches.byRepository`
**Hydration:** `lib/state/hydration/merge-strategies.ts` has `deepMerge()`, `mergeSessionMap()`, `mergeLoadingState()` to avoid overwriting live client state. Pass `activeSessionId` to protect active sessions.
**Hooks Pattern:** Hooks in `hooks/domains/` encapsulate WS subscription + store selection. WS client deduplicates subscriptions automatically.
### WS
**Format:** `{id, type, action, payload, timestamp}`.
- `apps/backend/AGENTS.md` — Go backend: package structure (incl. `internal/office/` and `internal/agent/runtime/`), key concepts (orchestrator, workflow engine, agent runtime, lifecycle manager), execution flow, provider pattern, backups, testing conventions, Go lint limits.
- `apps/backend/internal/agentctl/AGENTS.md` — agentctl HTTP server: route groups, adapter model, ACP protocol.
- `apps/backend/internal/agentctl/server/api/AGENTS.md` — reverse-proxy body rewriting (`Accept-Encoding`), iframe-blocking header stripping.
- `apps/backend/internal/integrations/AGENTS.md` — adding a new third-party integration (Jira/Linear pattern, both backend and frontend halves). The `/add-integration` skill mirrors this for scaffolding new integrations.
- `apps/web/AGENTS.md` — Next.js frontend: shadcn imports, SSR-hydrate-store data flow, store slice structure (incl. `office`), WS format, component conventions, TS lint limits.
---
@@ -305,30 +44,22 @@ lib/api/domains/ # API clients
### Commit Conventions (enforced by CI)
Commits to `main` **must** follow [Conventional Commits](https://www.conventionalcommits.org/) (`type: description`). PRs are squash-merged — the PR title becomes the commit, validated by CI. Changelog is auto-generated from these via git-cliff (`cliff.toml`). See `.agents/skills/commit/SKILL.md` for allowed types and examples.
Commits to `main` **must** follow [Conventional Commits](https://www.conventionalcommits.org/) (`type: description`). PRs are squash-merged - the PR title becomes the commit, validated by CI. Changelog is auto-generated from these via git-cliff (`cliff.toml`). See `.agents/skills/commit/SKILL.md` for allowed types and examples.
The commitlint hook caps the header at **100 characters** (`type(scope): description`). The pre-commit prettier hook also reformats staged TS/TSX files - if it does, the commit fails. Re-stage the reformatted files and create a new commit (don't `--amend`).
### Release & Versioning
Kandev uses a **single SemVer** `X.Y.Z` across npm, Homebrew, and GitHub release; release flow runs entirely in CI via `.github/workflows/release.yml`. Full details in the `/release` skill — load it when cutting a release or debugging release artifacts.
### Code Quality (enforced by linters)
### Code Quality
Static analysis runs in CI and pre-commit. New code **must** stay within these limits:
Static analysis runs in CI and pre-commit. Each subtree has its own thresholds:
**Go** (`apps/backend/.golangci.yml` - errors on new code only):
- Functions: **≤80 lines**, **≤50 statements**
- Cyclomatic complexity: **≤15** · Cognitive complexity: **≤30**
- Nesting depth: **≤5** · Naked returns only in functions **≤30 lines**
- No duplicated blocks (**≥150 tokens**) · Repeated strings → constants (**≥3 occurrences**)
- Go limits: see `apps/backend/AGENTS.md` (and `apps/backend/.golangci.yml`).
- TypeScript limits: see `apps/web/AGENTS.md` (and `apps/web/eslint.config.mjs`).
**TypeScript** (`apps/web/eslint.config.mjs` - warnings, will become errors):
- Files: **≤600 lines** · Functions: **≤100 lines**
- Cyclomatic complexity: **≤15** · Cognitive complexity: **≤20**
- Nesting depth: **≤4** · Parameters: **≤5**
- No duplicated strings (**≥4 occurrences**) · No identical functions · No unused imports
- No nested ternaries
**When you hit a limit:** extract a helper function, custom hook, or sub-component. Prefer composition over growing a single function.
When you hit a limit: extract a helper function, custom hook, or sub-component. Prefer composition over growing a single function.
### Testing
@@ -339,34 +70,6 @@ Every code change must include tests for new or changed logic. Backend: `*_test.
- **Decisions:** Architecture decisions are recorded in `docs/decisions/`. Read `docs/decisions/INDEX.md` for an overview. When making significant architectural choices, create a new ADR via `/record decision`.
- **Plans:** Implementation plans are generated from specs via `/plan` and saved to `docs/specs/<slug>/plan.md`. Plans are gitignored (working files, not committed) — only specs and ADRs are tracked.
### Backend
- Provider pattern for DI; stderr for logs, stdout for ACP only
- Pass context through chains; event bus for cross-component comm
- **Execution access:** Workspace-oriented handlers (files, shell, inference, ports, vscode, LSP) MUST use `GetOrEnsureExecution(ctx, sessionID)` — it recovers from backend restarts by creating executions on-demand. Only use `GetExecutionBySessionID` for operations that require a running agent process (prompt, cancel, mode).
- **Task lifecycle events:** Any code path that mutates a task row must publish via the event bus (`task.created` / `task.updated` / `task.deleted`) — either by going through `Service.CreateTask` / `UpdateTask` / `DeleteTask` / `ArchiveTask`, or by calling `publishTaskEvent` (or one of the `Publish*` helpers in `service_events.go`) directly. Walking `repository.TaskRepository` straight bypasses event publishing and breaks WS-driven UI like the All-Workflows kanban view. `HandoffService`'s cascade methods learned this the hard way — they now require a `TaskEventPublisher` wired via `SetTaskEventPublisher`. New cascade / bulk / cleanup paths must follow the same pattern.
- **Testing:** Prefer `testing/synctest` (Go 1.24+) over `time.Sleep` for time-dependent tests. Use `synctest.Test` to wrap tests with tickers or timeouts — it advances fake time instantly when all goroutines are idle. When `synctest` is not feasible (e.g., tests spawning external processes like `git`), use channel-based synchronization (`<-started`, non-blocking `select`) instead of sleep-based waits. Reserve `time.Sleep` only for integration tests that need real subprocess execution time.
### Backups
- On every SQLite boot, `persistence.Provide` reads `kandev_meta.kandev_version`. If the stored version differs from the binary version (or any user tables exist but no version is recorded), it takes a `VACUUM INTO` snapshot into `<data-dir>/backups/` before running migrations.
- Retention: 2 backups kept (newest two by mtime); older ones are pruned after the snapshot succeeds.
- Postgres: backup is skipped with a log line. Use `pg_dump` for Postgres backups.
- Boot aborts if the backup fails -- the pool is closed and `Provide` returns an error.
- After all repos complete `initSchema`, `cmd/kandev/storage.go:recordSchemaVersion` writes the current binary version into `kandev_meta` (non-fatal; a failure just means the next boot will take a fresh snapshot).
- Migration logging: `db.MigrateLogger.Apply(name, stmt)` -- success logs Info, "already exists"/"duplicate column name" is silently swallowed, anything else logs Warn but never returns an error (preserving the existing swallow-error contract).
### Frontend
- **Data:** SSR fetch → hydrate → read store. Never fetch in components
- **UI Components:**
- Import shadcn components from `@kandev/ui`, NOT `@/components/ui/*`
- **Always prefer native shadcn components** over custom implementations
- Check `apps/packages/ui/src/` for available components (pagination, table, dialog, etc.)
- For data tables, use `@kandev/ui/table` with TanStack Table; use shadcn Pagination components
- Only create custom components when shadcn doesn't provide what's needed
- **Components:** <200 lines, extract to domain components, composition over props
- **Hooks:** Domain-organized in `hooks/domains/`, encapsulate subscription + selection
- **WS:** Use subscription hooks only; client auto-deduplicates
- **Interactivity:** All buttons and links with actions must have `cursor-pointer` class
### Plan Implementation
- After implementing a plan, run `make fmt` first to format code, then run `make typecheck test lint` to verify the changes. Formatting must come first because formatters may split lines, which can trigger complexity linter warnings.
@@ -378,7 +81,7 @@ Skills use `gh` CLI by default. If a `gh` command fails (not installed, not auth
### Third-party integrations
Jira and Linear are the model (per-workspace credentials, 90s auth-health poller via `internal/integrations/healthpoll`, settings page with status banner). New integrations should **reuse the shared shapes** rather than copying either. Full layout, file conventions, and Jira-vs-Linear divergence notes in the `/add-integration` skill — load it when scaffolding a new integration.
Jira and Linear are the model (per-workspace credentials, 90s auth-health poller via `internal/integrations/healthpoll`, settings page with status banner). New integrations should **reuse the shared shapes** rather than copying either. Full layout, file conventions, and Jira-vs-Linear divergence notes in `apps/backend/internal/integrations/AGENTS.md` and the `/add-integration` skill — load either when scaffolding a new integration.
### Runtime profiles (prod / dev / e2e)
@@ -394,6 +97,8 @@ To flip a feature on for every user: change its `prod:` to `"true"` in `profiles
This file is read by AI coding agents (Claude Code via `CLAUDE.md` symlink, Codex via `AGENTS.md`). If your changes make any section of this file outdated or inaccurate - e.g., you add/remove/rename packages, change architectural patterns, add new adapters, modify store slices, or change conventions - **update the relevant sections of this file as part of the same PR**. Keep descriptions concise and factual. Do not add speculative or aspirational content.
When a change is scoped to a single subtree, update the scoped `AGENTS.md` instead of (or in addition to) this root file. See the "Scoped guidance" pointers at the top.
---
**Last Updated**: 2026-05-16