* feat(github): add issue watcher with task creation and auto-cleanup
Add a GitHub Issue Watcher feature that polls for new issues matching
configurable filters and automatically creates tasks for them. This
complements the existing PR Review Watcher with the same patterns.
Backend:
- Issue watch CRUD (models, store, service, controller, WS handlers)
- Poller integration with configurable intervals
- Event-driven task creation via orchestrator
- Repository resolution and worktree support for issue tasks
- Auto-cleanup of tasks for closed issues (CleanupClosedIssueTasks)
- GetIssueState client method across all implementations
- Default branch auto-detection from cloned repo HEAD
Frontend:
- Issue watch settings UI (table, dialog, placeholders)
- Issue link icon on task cards (green circle-dot)
- API client, store slice, and hook for issue watches
- SSR and WS hydration of issue metadata
* fix: resolve golangci-lint errors (goconst, nestif, dupl) and prettier formatting
* fix(github): fix issue watch update shape, trigger key, and label quoting
- httpUpdateIssueWatch now returns the full IssueWatch object instead of
{updated:true} so the frontend store correctly refreshes after edits
- httpTriggerIssueWatch returns new_issues_found to match TriggerIssueResponse
type; was returning new_issues so the toast count was always 0
- buildIssueFilter uses conditional quoting instead of fmt.Sprintf %q to
avoid Go escape sequences in GitHub search qualifiers
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* fix: resolve eslint warnings (max-lines-per-function, no-nested-ternary)
* test(github): add issue watcher tests and fix review feedback
- Add TestInterpolateIssuePrompt (4 cases: empty/all/none/partial)
- Add TestCreateIssueTask (3 cases: skip/reserve+assign/release on fail)
- Add TestShouldDeleteIssueTask (4 cases: open/closed+sessions/closed/error)
- Fix WS response key new_issues -> new_issues_found
- Fix fmt.Printf -> fmt.Fprintf(os.Stderr) in store.go
- Add CleanupClosedIssueTasks call in WS trigger handler
* fix: address PR review feedback and CI failures
- Split test files to fix revive file-length-limit (service_test.go,
event_handlers_github_test.go)
- Add session checker guard in CleanupClosedIssueTasks
- Normalize poll intervals on UpdateIssueWatch
- Add cleanup call in TriggerAllIssueChecks loop
- Log cleanup errors in controller and WS handler
- Prefer refs/remotes/origin/HEAD in detectGitDefaultBranch
- Add aria-label to issue task icon
- Fix TypeScript type errors: add linkedIssues to GitHubSlice,
fix hook exports, remove invalid imports
* fix: split test files exceeding revive limit and clean up minor issues
- Split service_test.go (883→779 lines): move TestTriggerPRSync_* to
service_pr_sync_test.go
- Split event_handlers_github_test.go (806→677 lines): move
TestCreateReviewTask_* and helpers to event_handlers_github_review_test.go
- Remove redundant s.taskSessionChecker nil-check inside shouldDeleteIssueTask
(early guard at CleanupClosedIssueTasks already ensures it is non-nil)
- Remove unreachable dead guard in detectAndPersistDefaultBranch
(repo.DefaultBranch is always "" at that call site)
- Fix bogus #0 issue badge in task-session-sidebar.tsx when issueNumber is absent
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* fix: address Claude review — poll clamp and prompt injection
- Handle v <= 0 in UpdateIssueWatch poll interval (fall back to default)
- Remove {{issue.body}} from default template to prevent prompt injection
- Update test expected output for new default template
* fix: remove {{issue.body}} from frontend DEFAULT_ISSUE_WATCH_PROMPT
Mirrors the backend fix — the UI pre-fills the prompt so the backend
fallback was never reached. The placeholder stays in
ISSUE_WATCH_PLACEHOLDERS for explicit opt-in.
* feat(github): add isIssueWatch sidebar filter dimension
Add isIssueWatch boolean to KanbanState, TaskSwitcherItem, and sidebar
filter registry — mirroring the isPRReview pattern. Issue-watch tasks
(created from issue_watch_id in metadata) can now be explicitly filtered
in the sidebar, just like PR review tasks.
Files changed:
- sidebar-view-types: add isIssueWatch dimension
- filter-dimension-registry: add Issue watch filter meta
- apply-view: add isIssueWatch extractor
- kanban types: add isIssueWatch field
- TaskSwitcherItem: add isIssueWatch field
- use-all-workflow-snapshots: derive isIssueWatch from metadata
- ssr/mapper: derive isIssueWatch from metadata
- task-session-sidebar: pass isIssueWatch, extract helpers to fix complexity
* fix: correct deleteIssueWatch response type to match backend
Backend httpDeleteIssueWatch returns {"deleted": true}; the frontend
type was { success: boolean } — a mismatch with no functional impact
(return value is ignored) but wrong per TypeScript contract.
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* refactor: extract metadata helpers to shared lib/metadata-utils.ts
- Move isPRReviewFromMetadata, isIssueWatchFromMetadata, and
issueFieldsFromMetadata to a shared module to eliminate duplicate
definitions (sonarjs/no-identical-functions).
- Fix deleteIssueWatch response type: { success: boolean } -> { deleted: boolean }
to match the actual backend response.
* style: fix prettier formatting
* test(web): add tests for isIssueWatch filter dimension and metadata-utils
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: fix prettier formatting in test files
* fix(github): cascade-delete issue watch tasks on watch deletion and add isPRReview to SSR mapper
- DeleteIssueWatch now wraps in a transaction and deletes related
github_issue_watch_tasks rows before removing the watch; prevents
orphaned dedup rows accumulating after a watch is deleted
- snapshotToState in mapper.ts now also sets isPRReview from metadata,
matching the mapping already done in use-all-workflow-snapshots.ts
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* style: extract duplicate string constant in metadata-utils test
* style: extract repeated WS error strings into constants in handlers.go
goconst min-occurrences: 3 — 'invalid payload' (6x) and 'id required' (4x)
both exceed the threshold. Extract to package-level constants.
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* style: extract repeated issue key strings into constants in event_handlers_github.go
goconst min-occurrences: 3 — 'issue_number' (9x) and 'issue_watch_id' (5x)
both newly introduced in this PR exceed the threshold.
Co-authored-by: João Salavisa <undefined@users.noreply.github.com>
* style: fix prettier formatting in metadata-utils test
---------
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>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* 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>
* fix: use dynamic merge-base for git commits to filter main branch commits
When a feature branch is rebased on main, the stored base_commit_sha
becomes outdated. Using git log base_commit..HEAD would include main
branch commits that were merged after the stored base commit.
This fix adds dynamic merge-base calculation using the session's
base_branch (target branch like origin/main). When fetching commits:
- The backend computes git merge-base HEAD target_branch
- This returns only commits not yet merged into the target branch
- Works correctly even after rebases
Changes:
- Extended SessionReader interface with GetSessionBaseBranch method
- Added GetMergeBase to GitOperator for computing merge-base
- Updated git log API to accept target_branch parameter
- Modified wsGitCommits handler to pass target branch for filtering
* fix: update executor_test mock to match new GetGitLog signature
* fix: update remaining mocks to match new GetGitLog signature
* fix: update integration test mock for GetGitLog signature
* fix: address code review feedback
- Use GetOrEnsureExecution in wsGitCommits to recover workspaces after backend restarts
- Fix archive snapshot to use baseBranch for merge-base when baseCommit is empty
- Extended ExecutionLookup interface with GetOrEnsureExecution method
* fix: distinguish not-ready errors from unexpected errors in wsGitCommits
Address CodeRabbit review feedback:
- Use errors.Is() to detect ErrSessionWorkspaceNotReady sentinel error
- Return ready:false gracefully for workspace-not-ready and session-not-ready errors
- Return actual errors for unexpected failures (database errors, etc.)
- Add comprehensive tests for error handling paths
* fix: include uncommitted changes in review dialog cumulative diff
The review dialog only showed uncommitted changes instead of the full
diff from the task's base commit. Two issues combined:
1. Backend used `git diff base..HEAD` (committed only); changed to
`git diff base` which compares against the working tree.
2. Frontend gave priority to git status over cumulative diff; reversed
the merge order so cumulative diff takes precedence.
Adds E2E test with a mock-agent scenario that commits changes then
leaves uncommitted modifications, verifying the review dialog shows
the complete base→working-tree diff.
* fix(e2e): remove incorrect BASE_CONTENT assertion in review cumulative diff test
The file is created after the base commit, so the entire file shows as
added in the cumulative diff. BASE_CONTENT was replaced and doesn't
appear. The key assertion is that COMMITTED_CHANGE is visible — proving
the diff includes committed changes (not just the uncommitted portion).
* fix: address CodeRabbit review feedback
- Update 3 docstrings to reflect working-tree diff (not HEAD)
- Generate scenario list dynamically in error message
- Use data-testid selector in E2E test for resilience
* style: format E2E test file
* fix(e2e): use toContainText and click file to ensure diff is scrollable
Pierre diff viewer content inside a scroll container may not be
considered "visible" by Playwright. Switch to toContainText which
checks text presence without element-level visibility. Also click
the file in the tree to ensure its diff section is scrolled into view.
* feat: add MCP mode pipeline for config vs task tool registration
Thread McpMode field through the entire instance creation chain:
CreateInstanceRequest → InstanceOverrides → InstanceConfig → MCP Server.
This allows config-mode MCP sessions to register a different set of
tools than the default task-mode sessions.
* feat: add config-mode MCP tools and agentctl handlers
Register config-mode tools (workflow steps, agents, MCP config, tasks)
when MCP server mode is "config". Add 16 new WS action constants and
agentctl-side handlers that forward tool calls to the backend via the
existing BackendClient channel.
* feat: add backend MCP handlers for config-mode operations
Add backend WebSocket handlers for workflow step CRUD, agent CRUD,
MCP config get/update, and task move/delete/archive/state operations.
Wire config dependencies via SetConfigDeps pattern to avoid breaking
the existing NewHandlers constructor.
* feat: add config-mode system prompt and unit tests
Add ConfigContext system prompt listing all config-mode MCP tools.
Add unit tests for mode-based tool registration, agentctl config
handler forwarding, backend handler validation, and system prompt
content.
* test: add exhaustive E2E tests for config-mode MCP handlers
38 end-to-end tests covering the full handler → service → DB pipeline
for all 16 config-mode operations: workflow step CRUD, agent CRUD,
agent profiles, MCP config, and task management (move, delete, archive,
update state). Includes handler registration verification and two
full pipeline integration tests.
* fix: remove E2E test that depends on agent CLIs installed in CI
TestE2E_Agent_CreateMultipleAndList tried to create claude-code and
codex agents, which aren't available in CI. Agents are unique by type
ID so creating duplicates also fails. Removed the test since
CreateAndList already validates the create+list flow.
* test: remove Go E2E tests for config-mode handlers
Replacing with Playwright E2E tests.
* test: add Playwright E2E tests for config-mode features
13 tests covering workflow steps, task operations, agent/profile
settings pages, MCP config, and workflow settings round-trips.
* feat: add dynamic MCP mode reconfiguration for agentctl instances
Add SetMode() to the MCP server that clears and re-registers tools for
the new mode. Expose it via PUT /api/v1/mcp/mode on per-instance servers,
a SetMcpMode client method, and a lifecycle Manager.SetMcpMode wrapper.
This enables switching from task to config MCP tools on workspaces that
were prepared before plan_mode was set on the session.
* feat: wire McpMode through executor launch and resume pipelines
Add McpMode to LaunchOptions so callers can set it at launch time.
StartTask and StartTaskWithSession now pass McpMode="config" when
plan mode is active. startAgentOnExistingWorkspace reconfigures the
MCP server via SetMcpMode before starting the agent subprocess.
The AgentManagerClient interface gains SetMcpMode; the lifecycle
adapter, executor, and all mock implementations are updated.
* fix: pass plan_mode from workflow step when auto-starting moved tasks
handleTaskMovedNoSession was hardcoding planMode=false when calling
StartTask. Now it loads the target step and checks for enable_plan_mode
in on_enter actions, passing the correct value so config-mode MCP tools
are registered for the agent.
* test: fix config-mode E2E tests and add SetMcpMode mock stubs
- Add SetMcpMode stubs to all AgentManager mock implementations
- Fix strict mode violations with { exact: true } on tool name assertions
- Expand collapsed tool call groups before asserting individual names
- Remove temporary diagnostic code
* fix: address CodeRabbit review comments
- Add servers parameter to update_mcp_config tool schema
- Add mutex protection to SetMode for thread safety
- Create session before building prompt so config context has session ID
- Fix inconsistent ask_user_question tool name reference (use _kandev suffix)
* refactor: use canonical ws.ActionMCP constants and handle marshal errors
- Import ActionMCP* constants from pkg/websocket instead of duplicating
them locally in config_handlers.go and handlers.go
- Handle json.MarshalIndent error in forwardToBackend instead of ignoring it
* fix: extract McpModeConfig constant and fix Prettier formatting
- Add McpModeConfig constant to avoid repeated "config" string (goconst)
- Fix Prettier formatting in config-management E2E spec
* fix: validate task state before update and strengthen sysprompt tests
- Add TaskState validation in handleUpdateTaskState to return proper
validation error instead of internal error for invalid states
- Strengthen format-contract assertions (exact %s counts, no %! markers)
- Extract repeated test data into constants per project guidelines
* refactor: decouple config MCP mode from plan_mode, use session metadata
Replace plan_mode-based config MCP activation with config_mode in
session metadata. Add isConfigModeSession() helper used across
executor_execute, executor_interaction, and executor_resume.
Propagate task.Metadata to session.Metadata in PrepareSession so
config_mode flows from task creation through to the agent launch.
* refactor: remove task tools from config MCP mode and update sysprompt
Remove task management tools (list_tasks, move_task, delete_task,
archive_task) from ModeConfig registration — they remain available in
ModeTask. Update ConfigContext system prompt to remove task tool docs
and add example requests section. Update MCP server tests for new tool
count (18→14) and remove deleted task handler tests.
* feat: add config-chat endpoint and workspace config agent profile
Add default_config_agent_profile_id column to workspaces with SQLite
migration. Add POST /workspaces/:id/config-chat endpoint that creates
an ephemeral task with config_mode metadata and starts a session with
config MCP tools. Extract resolveConfigChatDefaults helper to stay
within lint complexity limits.
* feat: add config chat UI with workspace settings integration
Add config chat modal component and hook for starting config sessions.
Add configChat state slice with open/close actions. Add workspace API
client for config-chat endpoint. Add configuration agent profile
selector to workspace settings form. Add config chat button to
settings layout sidebar.
* test: rewrite config-mode E2E tests to use metadata and session_id
Rewrite config-management E2E tests to use createTaskWithAgent with
config_mode metadata instead of a custom fetch helper. Fix navigation
to use task.session_id instead of task.id. Add metadata parameter
support to ApiClient.createTaskWithAgent.
* fix: use floating FAB for config chat and pass default agent profile
Move config chat button from header to a floating action button in the
bottom-right corner with a sparkles icon. Pass the workspace's default
agent profile ID when opening the config chat to avoid "no agent
profile configured" error. Add default_config_agent_profile_id to
workspace slice type.
* feat: async agent launch for config-chat and extract helpers
The config-chat endpoint now launches the agent asynchronously when a
prompt is provided, matching the start_agent flow used by task creation.
Extract deleteTaskOnError and launchConfigChatAgent helpers. Remove
LaunchWorkspace from prepare step to avoid blocking.
* feat: first-interaction agent profile selection for config chat
Open the config chat modal immediately on FAB click. If no default
config agent profile is set, the empty state now shows a profile
selector before starting. The selected profile is saved as the
workspace default_config_agent_profile_id for future sessions.
* test: fix config E2E tests — remove duplicate method, use real tools
Remove duplicate startConfigChat method in ApiClient that overrode the
correct 3-param signature. Replace invalid create_agent test (requires
registered agent type) with update_agent test. Remove fragile tool calls
group UI assertion from multi-tool test.
* fix: resolve 'no active session' error in config chat
Add openConfigChatModal action that only sets isOpen and workspaceId
without touching sessionId. Previously, open() called openConfigChat
with empty sessionId which was truthy, causing QuickChatContent to
render with an invalid session.
* feat: add minimal toolbar mode for config chat input
Thread minimalToolbar prop through ChatInputArea → ChatInputContainer →
ChatInputBody → ChatInputToolbar → QuickChatContent. When enabled,
renders only the submit/cancel button, hiding plan toggle, MCP
indicator, context popover, model selector, and other controls.
* feat: replace config chat dialog with floating popover panel
Replace Dialog with Popover from @kandev/ui so the config chat floats
up from the FAB button. Reduce FAB size to h-12/w-12 with larger h-7/w-7
icon. Rename tooltip to "Configuration Chat". Remove standalone
ConfigChatFAB component from settings layout.
* feat: filter config chat tasks from quick chat listing
Add exclude_config query parameter to ListTasksByWorkspace that filters
out tasks with metadata.config_mode=true. Apply it in the quick chat
sessions API call to prevent config chat tasks from appearing as tabs.
* refactor: move config agent profile setting to agents page
Remove the Configuration Agent Profile selector from workspace settings
and add a dedicated ConfigChatAgentSection to the agents settings page
with auto-save on change.
* feat: redesign config chat empty state with inline text input
Replace clickable template buttons with subtle suggestion hints that
fill the textarea on click and disappear once the user starts typing.
Add a persistent Textarea + send button at the bottom so users can
type directly without clicking templates. This prevents the "no active
session" error since QuickChatContent only renders after startSession
succeeds.
* fix: config chat UX polish — non-clickable suggestions, profile persistence
- Make suggestion prompts purely visual (non-interactive text hints)
- Update workspace store after saving config agent profile so the
profile selector doesn't reappear on subsequent opens
- Use useAppStoreApi for store access in callbacks
* fix: add inline error display and prevent accidental popover dismiss
- Show API errors inline below the input instead of via toast (which
can be hidden behind the popover)
- Prevent popover from closing on outside click via onInteractOutside,
so the user doesn't lose their typed input or active session
- Surface error state from useConfigChat hook to the empty state UI
* fix: use config-specific placeholder in config chat input
Add placeholderOverride prop through ChatInputArea and QuickChatContent
so the config chat shows "Ask anything about your configuration..."
instead of the default "Continue working on the task..." placeholder.
* feat: add default_config_agent_profile_id to workspace DTO and API
Wire the field through WorkspaceDTO, FromWorkspace(), HTTP/WS request
structs, service layer, and public API types so the config chat default
profile persists across page reloads and SSR hydration.
* feat: add config chat store actions and prompt delivery via WS
Add configChat slice actions (open, close, rename, tab management),
pendingPrompt state in useConfigChat hook, and initialPrompt support
in QuickChatContent so prompts are delivered via WS after subscription
is established rather than via HTTP (avoiding race condition).
* fix: resolve dual store issue and message fetch race condition
Replace StateProvider with StateHydrator in settings layout to prevent
a shadow Zustand store that intercepted WS events. Fix message fetch
race condition by merging WS-delivered messages with fetch results
instead of replacing them.
* feat: make config chat global with command panel integration
Add ConfigChatProvider at root layout so the popover is available on all
pages. FAB button only visible on settings pages; hidden anchor on other
pages for popover positioning. Add "Configuration Chat" command to the
command panel (Cmd+K). Add close button and header bar to the popover.
* test: add E2E tests for config chat popover and command panel
Add tests for opening config chat via FAB on settings page (with full
agent response flow) and opening via command panel on kanban page.
Fix api-client endpoint and add workspace update fields for E2E setup.
* fix: compress props destructuring to satisfy max-lines-per-function lint
Rebase merged both sides' additions into ChatInputEditorArea and
ChatInputToolbar, pushing them over the 100-line function limit.
Use object parameter with compact destructuring to reduce line count.
* fix: register config task tools and add exhaustive E2E coverage
Config-mode MCP task tools (list_tasks, move_task, delete_task,
archive_task) were defined but never registered — registerConfigTaskTools()
was never called in registerTools(). Fixed the registration and expanded
E2E tests from 5 to 15, covering all testable config-mode MCP tools
(workflow, agent, MCP config, and task management) with API-side
verification of side effects.
* feat: replace create/delete agent with create/delete agent profile in config mode
Agent creation/deletion via MCP isn't practical since agents map to
registered types. Replace with create_agent_profile and
delete_agent_profile tools that manage the per-agent configuration
presets users actually need to work with. Wires full chain: agentctl
MCP tools → WS actions → backend handlers → controller layer.
* chore: fix Prettier formatting
* fix: compress props destructuring to satisfy max-lines-per-function lint
* test: update MCP tests for agent profile tools and correct config tool count
* fix: address CodeRabbit review — normalize MCP mode, set default, consolidate tests
* fix: address CodeRabbit review — error leaks, search filter bug, stale prompt, sysprompt tools
* feat: expose all workflow step fields in config-mode MCP tools
* feat: add workflow CRUD to config-mode MCP tools
* fix: split workflow CRUD E2E test into separate tests
* fix: prevent cascade deletion of config session in delete workflow E2E test
The config-chat endpoint picks the newest workflow (ORDER BY created_at
DESC) for the ephemeral task. When the test created a deletable workflow
right before starting the config session, the session's own task landed
under that workflow. Deleting the workflow then cascaded and wiped the
running session's task, causing the MCP tool call to hang.
Add a "Config Anchor" bumper workflow so the config session uses it
instead of the deletable one.
* feat: add executor management to config-mode MCP tools
Add 8 new MCP tools for executor and executor profile CRUD in config
mode: list/create/update/delete for both executors and profiles.
Wired through the full stack (WS actions, backend handlers, agentctl
tools, sysprompt) with unit tests and E2E coverage.
* fix: auto-focus config chat input and rename popover title
* fix: remove executor CRUD MCP tools, keep executor profile management
Executors are pre-defined system entities (local, worktree, docker,
sprites). Only executor profiles should be managed via config chat.
Removes create_executor, update_executor, delete_executor MCP tools
while keeping list_executors and all executor profile tools.
* fix: add realtime UI sync for workflow steps and agent profiles via MCP
Workflow step and agent profile changes made through the config chat
MCP tools were not broadcasting events to the frontend. Executor
profile events worked because they went through the task service which
has event bus integration.
- Add WorkflowStep and AgentProfile event types to the event bus
- Add WS notification action constants for workflow step events
- Publish events from MCP handlers after step/profile CRUD operations
- Register gateway subscriptions for the new event types
- Add frontend WS handlers for workflow.step.created/updated/deleted
* fix: reuse existing config chat session when opened from command panel
The command panel always called openConfigChatModal() which reset the
session state, causing the profile picker to show again. Now it checks
for an existing active session first, matching the FAB behavior.
* fix: suppress FAB tooltip while config chat popover is open
When closing the popover via the X button, the mouse ends up over the
FAB area and the tooltip re-appears. Force the tooltip closed while
the popover is open so it cannot re-trigger on close.
* fix: thread default_config_agent_profile_id through all workspace data paths
The field was defined on the workspace type but never populated in:
- SSR mapper (page.tsx) — missing from initial hydration
- WS workspace handlers — wiped on workspace.created/updated events
- Backend publishWorkspaceEvent — not included in event data
This caused the config chat to always show the profile picker when
opened from the command panel, since the saved default was lost.
* fix: address PR review comments — workspace sync, type fixes, missing tool doc
- Sync configChat.workspaceId when promoting next tab in closeConfigChatSession
- Add update_task_state_kandev to ConfigContext system prompt
- Fix workflow step WS event types (workflow.step.* instead of step.*)
- Fix const reassignment in E2E tests (const → let)
* refactor: clean up dead code and register missing MCP tools
- Register update_task_state, delete_workflow_step, and
reorder_workflow_steps as MCP tools in agentctl config mode
- Remove dead handleCreateAgent/handleDeleteAgent WS handlers
and their ActionMCP constants (only profiles are managed)
- Rename openConfigChatModal to startNewConfigChat across frontend
- Update sysprompt, tests, and tool counts
* fix: remove extra next.config.ts machine
* fix: address CI failures — prettier formatting and funlen split
* fix: address coderabbit review comments across config-mode stack
- Include prompt field in workflow step broadcast payloads
- Align sysprompt task-state values with actual backend enum
- Return error instead of continuing when MCP mode switch fails
- Validate mode values in agentctl set-mcp-mode endpoint
- Use errors.Is() for sentinel error comparison
- Fix nullish coalescing to allow clearing config agent profile
- Expand StepPayload type with missing config-step fields
- Derive config agent Select value from store instead of local state
* fix: reuse persisted config chat session and reduce toolbar complexity
- Look up configChat.sessions for workspace before creating new session
- Extract MinimalToolbar and move defaults to module-level object to
reduce ChatInputToolbar cyclomatic complexity from 16 to 7
* fix: E2E executor test strict mode violation and invalid cleanup
- Use exact match for list_executors text to avoid matching 2 elements
- Remove cleanup that tried to delete pre-existing system executor
- Reuse persisted config chat session for workspace in global commands
- Reduce ChatInputToolbar cyclomatic complexity with module-level defaults
* 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
* feat(backend): add support for fetching file content at git ref
Add GetFileContentAtRef method to WorkspaceTracker for retrieving file content
at specific git refs (branches, commits, HEAD). This enables diff expansion by
fetching old file versions without checking out.
Changes:
- Extract maxFileSize constant to package level (10MB)
- Add GetFileContentAtRef to workspace_files.go using git show
- Add workspace.file.get_at_ref WebSocket action and handler
- Add agentctl client method for the new endpoint
- Register handler in workspace file handlers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(web): add diff expansion support for code review
Add lazy-loaded diff expansion to show more context in code reviews. When expansion
is enabled, users can expand hunks to see more surrounding code by clicking expand
buttons. Full file content is fetched on-demand via WebSocket.
Changes:
- Add useExpandableDiff hook for lazy-loading old/new file content
- Add requestFileContentAtRef to workspace-files.ts
- Integrate expansion into diff viewer components
- Add enableExpansion and baseRef props to DiffViewer
- Auto-load expansion content when enabled
- Pass expansion state through diff viewer hierarchy
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): gracefully handle expansion props for Monaco diff viewer
Monaco diff viewer has built-in expansion that doesn't require fetching
old file content. Strip pierre-diffs-only expansion props (enableExpansion,
baseRef) when Monaco is selected to avoid passing unsupported props.
This ensures the feature works correctly regardless of which diff viewer
the user selects in settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(backend): add diff expansion e2e scenario and fix unstaged diff base ref
- Add scenarioDiffExpansionSetup mock-agent scenario that creates a
200-line file, commits it, then modifies lines 50 and 150 to produce
a two-hunk uncommitted diff for testing diff expansion in the UI
- Add workspace_file_handlers for workspace.file.get_at_ref WS action
- Fix enrichWithDiffData to always diff against HEAD for unstaged/staged
content so locally committed files show correct diffs
- Add unit tests for GetFileContentAtRef (6 sub-tests)
* feat(web): add diff expansion UI and e2e test
- Wire useExpandableDiff hook into FileDiffViewer and ReviewDiffList
with enableExpansion and baseRef props
- Gracefully handle expansion props for Monaco diff viewer fallback
- Add e2e Playwright test for diff expansion between hunks
* fix(web): fix diff expansion by correcting hunk separators and line splitting
Three bugs prevented @pierre/diffs expand buttons from rendering:
- hunkSeparators was "simple" (no expand UI); changed to "line-info" when expansion enabled
- expandUnchanged:true showed all lines expanded (no separators); removed the option
- .split("\n") stripped trailing newlines; now uses SPLIT_WITH_NEWLINES regex to match library
* feat(web): add editor provider settings UI
Add an "Editor Providers" section to the Editors settings page with
Select dropdowns for each configurable context (Code Editor, Diff Viewer,
Chat Code Blocks, Chat Diff Blocks). Reads from and writes to the
existing localStorage-backed editor resolver store. Changes apply
immediately without requiring a save action.
* test(web): add e2e tests for editor provider settings
Verify that the editor provider dropdowns render with correct defaults,
update on selection, and persist changes across page reloads via
localStorage.
* feat(web): add expand/collapse unchanged regions toggle to diff toolbar
Refactor diff header toolbar into composable ToolbarButtons component and
add a toggle button to expand or collapse unchanged regions in both
Pierre Diffs and Monaco diff viewers. Pass enableExpansion through to
Monaco to control hideUnchangedRegions.
* fix(web): handle deleted files in diff expansion
Treat "file not found" / "no such file" errors as empty content instead
of throwing, so diff expansion works correctly for deleted files.
* chore(web): add diff expansion debug e2e helpers
* test(web): split diff expansion tests by provider (Pierre Diffs + Monaco)
Restructure diff-expansion e2e tests into two describe blocks, one per
diff viewer provider. Each block sets the provider via localStorage
before navigation and verifies the correct viewer renders:
- Pierre Diffs: checks for diffs-container element and shadow DOM
expand buttons between hunks
- Monaco: checks for .monaco-diff-viewer wrapper and the built-in
hideUnchangedRegions widget with "Show Unchanged Region" link
Both suites verify that clicking expand reveals collapsed middle lines.
* fix(web): fix Pierre Diffs expand button selectors in e2e tests
Use shadow DOM data attributes ([data-expand-button], [data-expand-up])
instead of XPath parent traversal which doesn't work inside shadow DOM.
Wait for expand buttons to load asynchronously before clicking.
* fix(e2e): make diff-expansion-setup scenario idempotent
The mock-agent scenario now removes and re-commits the test file before
each run, so subsequent tests in the same repo don't fail with "git
commit failed" when the file already exists. Also increase test timeouts
to match other heavy e2e tests.
* refactor(web): remove editor provider settings UI, enable expand-all in review
Remove the editor provider switching UI and its tests — Monaco's diff
expansion doesn't work and exposing provider switching adds confusion.
Show Pierre Diffs header toolbar in the review panel (remove hideHeader)
so the expand-all button is accessible. Add e2e test for expand-all.
* style(web): fix prettier formatting and lint warnings
Run prettier on files with formatting drift. Extract ToolbarBtn helper
in diff-header-toolbar to stay within max-lines-per-function limit.
Consolidate useMonacoDiffViewerState to reduce line count.
* chore(web): remove debug scaffolding, revert Monaco changes, drop stray console.log
Revert Monaco diff-viewer-toolbar and monaco-diff-viewer to main
(expansion not functional with Monaco). Remove debug-expansion
screenshot scripts and spec. Strip console.log from commit-detail-panel.
Strip enableExpansion from Monaco path in diff-viewer-resolver.
* fix(web): use commit-relative base ref for diff expansion
Use `${commitSha}^` instead of hardcoded `HEAD~1` so expansion fetches
the correct old content when viewing non-HEAD commits.
* fix(web,backend): address CodeRabbit review feedback
- Add aria-label to icon-only toolbar buttons for screen reader access
- Guard auto-load expansion effect against error state to prevent retry
loops when WebSocket fetch fails
- Invalidate expansion cache on file/ref change with request versioning
to prevent stale content
- Preflight blob size with git cat-file -s before git show to avoid
memory spikes on large files
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>