Commit Graph

40 Commits

Author SHA1 Message Date
José Almeida 538251d995 refactor(e2e): extract docker + session helpers from docker-launch.spec
Pull the inline docker CLI helpers (`dockerInspectExists`, `dockerStop`,
`dockerCurrentBranch`, etc.) and the generic session-state pollers
(`waitForLatestSessionDone`, `waitForSessionDone`,
`waitForSessionEnvironment`) into `e2e/helpers/docker.ts` and
`e2e/helpers/session.ts`. The spec drops from 763 to 647 lines and the
helpers can be reused by future docker / session tests.
2026-05-07 00:52:25 +01:00
José Almeida d35e601d42 test(docker): cover stop + resume → same container, branch preserved
Adds a Docker e2e test that exercises the user-visible stop-then-resume
flow: launch session, stop it via WS session.stop, launch a fresh
session on the same task, and assert the same task environment +
container ID are reused and the kandev feature branch is preserved.

Also nudge the executor popover padding: when the prepare-status
section is rendered above the Sprites/Docker info block, the cumulative
top+bottom paddings looked too generous around "Sprites sandbox" /
"Local Docker". Tighten both sections vertically without affecting the
horizontal rhythm or the bottom action row.
2026-05-05 23:05:22 +01:00
José Almeida b1f57cec6b test(docker-e2e): add real-Docker Playwright project + first spec
A new Playwright project — \`docker\` — runs container-bound E2E specs
against a backend with KANDEV_E2E_DOCKER=1. The fixture probes for a
Docker daemon and skips the worker entirely when none is reachable, so
contributors without Docker can still run the rest of the suite.

The fixture builds a tiny kandev-agent:e2e image once per machine
(node:22-slim + git/curl), pre-seeds a local_docker executor profile,
and seeds a local repo with a file:// origin so the executor's
clone-inside-container path resolves without network access. A small
WS-based session.launch helper is added to the api-client for tests
that need to launch additional sessions on an existing task.

First spec — docker-launch.spec.ts — covers the smoke path: a session
launches in a real container, the task environment records its
container_id, and \`docker inspect\` confirms the container exists.
Two follow-up specs (multi-session env reuse + recovery from external
container removal) are wired up but marked test.fixme with comments
pinpointing the backend gaps that block them: persistTaskEnvironment
isn't called for workflow-prepared sessions (so task_environment_id
stays empty on those rows), and DockerExecutor.reconnectToContainer
loses the agentctl handshake auth token across launches (so reconnect
401s and falls back to a fresh container every time).

Run locally with: \`pnpm --filter @kandev/web exec playwright test
--config e2e/playwright.config.ts --project=docker\`.
2026-05-04 21:09:39 +01:00
José Almeida 8cbeb32b7b test(docker): cover container config build + Docker profile persistence
Adds two regression tests for the recent Docker fixes:

- container_config_test.go (Go unit): asserts buildContainerConfig
  expands `{workspace}` in WorkingDir (defaulting to /workspace when no
  host WorkspacePath is set) and that ContainerConfig.ImageTagOverride
  takes precedence over the agent runtime's hardcoded `rt.Image`. No
  Docker daemon required.

- docker-profile-persistence.spec.ts (Playwright E2E): creates a
  local_docker executor + profile, opens the profile edit page, clicks
  "Use defaults" to populate the Image Tag input + Dockerfile editor,
  hits Save, then refetches the profile via API and reloads the page to
  verify both fields round-trip through `profile.config`. Catches the
  prior regression where DockerSections owned the form state locally
  and never wrote it back. Runs against the existing E2E backend that
  has KANDEV_DOCKER_ENABLED=false — we only test the persistence path,
  not container creation.

Adds an api-client helper (`getExecutorProfile`) for the round-trip
assertion.
2026-05-04 21:08:51 +01:00
José Almeida a4e0fc0452 feat: attribute cross-task agent messages with a sender badge (#819)
* feat(orchestrator): carry sender_task metadata through user message paths

Add WithSenderTask builder to UserMessageMeta and a metadata-aware
QueueMessageWithMetadata variant on the message queue. The drain path
in event_handlers_agent merges queued metadata into the recorded user
message so attribution survives the queue → DB transition.

* feat(mcp): attribute message_task_kandev calls to the sending agent

The MCP server inside agentctl now injects sender_task_id and
sender_session_id from its own server context. The backend handler
validates the sender (rejecting missing IDs, self-messages, and unknown
tasks), looks up the sender title, wraps the prompt in a <kandev-system>
attribution block, and propagates structured metadata across all three
delivery paths (queue, created-session, record+prompt). The wrapped
content lets the receiving agent identify the peer both live and on ACP
session resume; the existing sysprompt strip pipeline hides the wrapper
from the UI.

* feat(web): render sender-task badge on cross-task agent messages

User messages whose metadata carries sender_task_id now render a
clickable purple badge above the bubble, linking to the source task.
The badge live-resolves the sender's title from the kanban store and
falls back to a greyed snapshot when the task isn't loaded
(cross-workspace, deleted, etc.). Adds a non-subscribing useTaskById
selector so badges don't fan out WS subscriptions per message.

* test(e2e): cover cross-task agent message attribution

Eight scenarios driven through the mock-agent's MCP script directive:
queue path, prompt path, self-message rejection, unknown-target error,
badge link target, live title re-resolution after rename, kandev-system
wrapper preservation in raw_content, and prompt bodies containing their
own kandev-system tags. Adds an updateTaskTitle helper to ApiClient.

* fix: address CodeRabbit + Greptile P1 findings on PR #819

- requeueMessage now forwards queuedMsg.Metadata via QueueMessageWithMetadata
  so sender attribution survives transient delivery failures and retries.
- CREATED-session branch in handleMessageTask records the user message AFTER
  StartCreatedSession succeeds, so a launch failure no longer leaves an
  orphaned attributed chat row.
- SenderTaskBadge: collapse identical displayTitle/fullTitle locals.
- E2E: extract waitForTargetIdle helper; replace the 1.5s raw sleep in the
  collision-check test with the same poll the rest of the suite uses.

* fix: address Claude review suggestions on PR #819

- wrapAgentMessage strips </kandev-system> from the embedded sender title
  before formatting. Without this, a title containing the closing tag
  would short-circuit the non-greedy strip regex and leak the attribution
  tail into the visible UI bubble. Metadata snapshot keeps the original
  title so the badge still displays it.
- Add a regression test covering the malicious-title case.
- Document the deliberate cross-workspace messaging policy on the sender
  lookup so future readers don't add a workspace-scope check (Q13 of the
  feature plan locked this as intentional).
2026-05-04 19:30:10 +01:00
João Salavisa bb36cb9e01 refactor: move integrations to top-level settings with install-wide configs (#787)
* refactor: move integrations to top-level settings with install-wide configs

Jira and Linear configurations are now install-wide singletons rather than
per-workspace duplicates, and the GitHub/Jira/Linear settings pages live
under /settings/integrations instead of being nested inside each workspace.
Issue and PR watchers stay per-workspace via a workspace switcher inside
each integration page. Backend ships one-shot migrations that promote the
most-recently-updated row and copy its secret onto the new global key.

* fix: address review feedback on integrations refactor

Fix migration crash on installs whose jira_configs predate the auth-health
columns by selecting only guaranteed-present legacy columns and falling back
to defaults for the rest. Cover the empty-legacy-table and pre-health-columns
upgrade paths with new store tests.

On the frontend, refactor useIntegrationEnabled to take string parameters
instead of an inline options object so getSnapshot stays referentially stable
across renders, and stop dispatching a misleading no-op sync event when
localStorage.setItem fails in private mode.

* fix: hydrate workspace list on integration settings pages

The integration pages wrapped their content in a fresh StateProvider, which
created a nested store scope with an empty initial state and shadowed the
settings layout's hydrated workspace list. WorkspaceScopedSection then saw
zero workspaces and rendered the "Create a workspace to configure this
integration" empty state even when the user had workspaces.

Drop the redundant StateProvider from the jira and linear pages so they share
the layout's store. The github page switches to StateHydrator so it can still
seed the GitHub OAuth status without resetting the rest of the store.

* refactor(integrations): flatten watcher tables, pick workspace in create dialog

Hiding watchers behind a workspace switcher made the list invisible at a
glance — a user with multiple workspaces had to flip between each one to
see what was configured. Replace the switcher with a flat table that lists
every watch across every workspace, with a Workspace column for context, and
move workspace selection into the create dialog so the user picks once when
the watcher is born.

Backend: jira list-issue-watches and github list-review-watches /
list-issue-watches accept the workspace_id query param as optional; when
omitted, the corresponding store ListAll* method returns rows across every
workspace. Per-row mutations still require workspace_id as the IDOR guard.

Frontend: useReviewWatches / useIssueWatches / useJiraIssueWatches gain a
three-state contract — string for one workspace, undefined for all
workspaces, null to skip fetching. Watch tables grow an optional
showWorkspace column. Watch dialogs accept an optional workspaceId prop;
when absent, the dialog renders a workspace dropdown for the create flow
(disabled when editing existing rows so refs aren't orphaned). Github
review-watch-dialog crossed the 600-line lint cap so the prompt editor
moved to review-watch-prompt-field.tsx.

* fix(integrations): make watcher step dropdown reactive across workspace switches

Switching to a workspace whose workflows had to be fetched left the workflow
step dropdown stuck — the user could pick a workflow but the step Select
either showed the previous workflow's steps or appeared inert. The cause was
the per-dialog useWorkflowSteps hook deferring its empty-state reset via
Promise.resolve().then(() => setSteps([])) to dodge a setState-in-effect
lint rule, which let stale steps survive into the next render and gave Radix
nothing to anchor the popover to during the in-flight fetch.

Hoist the hook into a shared useWorkflowSteps that resets via React's
official setState-during-render pattern, exposes a loading flag, and returns
a stepPlaceholder helper so the Select can render "Loading steps…" or "No
steps in this workflow" instead of an empty popover. The Step Select is now
also disabled while loading or when there are no steps, making the state of
the dropdown unambiguous.

* fix: address PR review feedback on integrations refactor

Backend:
- linear migration: add column-presence guard mirroring the Jira fix so
  installs that upgraded from the original Linear schema (without org_slug
  / last_checked_at / last_ok / last_error) don't crash startup with
  "no such column"; add TestStore_MigrateLegacyPerWorkspaceTable_PreHealthColumns
- jira/handlers.go: drop the now-unused errMsgWorkspaceIDRequired constant
  that was failing golangci-lint
- jira/mock_controller_test.go (new): cover setAuthHealth happy-path and
  invalid-JSON-rejection so the singleton-shaped payload is locked in

Frontend:
- useIntegrationAvailable: gate the auth-health probe on loaded && enabled
  so disabled integrations don't poll /config every 90s
- use-workflow-steps: initialize loading to !!workflowId so edit-mode opens
  show "Loading steps…" instead of "No steps in this workflow" before the
  first fetch lands
- use-workflow-steps.test.ts (new): cover the empty/non-empty workflowId,
  workflow switch, fetch error, and stepPlaceholder branches
- use-linear-enabled.test: tighten the legacy-key migration assertion to
  pin the migrated value rather than just verifying key cleanup

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-03 10:22:57 +01:00
João Salavisa 5936bb6808 test: add e2e coverage and mock backends for jira and linear integrations (#771)
* test: add e2e coverage and mock backends for jira and linear integrations

Adds in-memory MockClient + MockController for both integrations gated by
KANDEV_MOCK_{JIRA,LINEAR}=true, plus apiClient mock helpers and Playwright
specs covering settings and import flows.

Also fixes ValidatedPopover's TriggerButton silently dropping radix Slot's
onClick handler — the popover wouldn't open under any synthetic-click path
(including Playwright). Spreads `...rest` onto the inner Button.

Reworks the integration cleanup hook in test-base as an auto fixture so it
runs for every test using the imported `test` object, not just tests in the
fixture file itself.

* fix: address PR review feedback on jira/linear mocks and e2e specs

- Fix SetAuthResult docstrings: nil simulates an unconfigured state, not the default success.
- Drop unused JSON tags from doneTransitionCall and setStateCall (Go-only test fixtures).
- Track Linear issue insertion order so SearchIssues returns deterministic results instead of map-iteration shuffle.
- Document filterByJQL precedence at the SearchTickets call site so the all-hits-on-no-match fallback is visible.
- Wait for the post-save auth-health probe before forcing a failure in the seeded-failure tests, eliminating a goroutine race.
- Reorder authMethod precedence in setJiraConfig/setLinearConfig to put the spread first so 'caller wins' reads naturally.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-05-02 14:41:36 +01:00
José Almeida aa70b7dfa4 feat(web): mobile terminal key-bar — Ctrl/Shift modify OS-keyboard input + iOS keyboard fixes (#741)
* feat(web): add mobile terminal key-bar with sticky Ctrl modifier

Mobile software keyboards lack Ctrl, Esc, Tab, arrows, and shell symbols,
so users had no way to interrupt a running command (Ctrl+C), escape an
editor, or navigate the shell. Adds a horizontally scrolling key-bar
above the virtual keyboard — sticky Ctrl modifier, dedicated Ctrl+C /
Ctrl+D buttons, and visualViewport-based docking so the bar tracks the
OS keyboard as it opens and closes.

* test(web): add E2E coverage for mobile terminal key-bar

Nine Playwright tests on the mobile-chrome project cover visibility
gating, key emission verified via captured WS frames, the sticky Ctrl
state machine, dedicated Ctrl+C / Ctrl+D buttons, keyboard-aware
docking, accessibility, and an end-to-end ^C echo to prove the shell
path works. Adds a reusable shell.input frame capture helper and a
small page-object for the key-bar.

* feat(web): shared shell input helpers for the mobile terminal

Introduces a small toolkit the next commit will wire into the mobile
key-bar:

- `refocusXtermTextarea` — re-focuses xterm's hidden textarea after a
  virtual-key tap so iOS Safari doesn't dismiss the soft keyboard.
- `suppressIOSKeyboardAssists` — strips name/autocomplete/data-* hints
  on the textarea so iOS / 1Password / LastPass / Bitwarden skip the
  AutoFill suggestion bar.
- `useShellModifiersStore` (Zustand) — module-level Ctrl/Shift latch
  state with toggle (off → latched → sticky → off) and consume actions.
- `applyShellModifiers` — pure transform: ctrl + a-z → control byte;
  shift + tab → CSI Z (reverse tab); single-char only.
- `sendShellInput` — single entry point that reads the store, applies
  the transform, sends over WS, and consumes latched modifiers.

`useShellKeySender` and the xterm `onData` path in `shell-terminal.tsx`
now route through `sendShellInput` so any future modifier state
propagates uniformly. `suppressIOSKeyboardAssists` is wired into
`useTerminalInit` immediately after `terminal.open()`.

* feat(web): mobile key-bar pivots to OS-keyboard input + pins to keyboard

Reworks the mobile terminal key-bar around two insights:

1. The OS soft keyboard already provides letters — duplicating them
   in-bar was confusing and didn't address the real problem (Ctrl
   couldn't modify what the user typed via the OS keyboard, because
   that input went straight to xterm and bypassed React state).

2. iOS Safari `position: fixed; bottom:` drifts during visual-viewport
   scroll while the keyboard is open. Anchoring via `top` tied to the
   visual viewport's bottom edge stays glued.

Changes:

- `MobileTerminalKeybar` reads Ctrl/Shift state from the shared
  `useShellModifiersStore` and toggles it on tap. The actual chord
  transform now happens in `sendShellInput`, which both the bar and
  xterm's `onData` path call — so a Ctrl latch set from the bar
  modifies the next character typed on the OS keyboard.
- Drops `LETTER_KEYS` and `chordChar` from the bar; adds a Shift
  toggle (Shift+Tab → reverse-tab from the bar; pairs with the
  modifier transform for OS-keyboard taps).
- Latched state now shows the same ring as sticky (with sticky using
  a stronger `ring-primary`), so the first tap is visibly active.
- `touch-action: manipulation` on each button so taps in the
  `overflow-x-auto` row don't get reinterpreted as scroll.
- `useVisualViewportOffset` exports `viewportBottom = offsetTop +
  height`. When the keyboard is open the bar uses `top: viewportBottom
  - KEYBAR_HEIGHT_PX` and `bottom: auto`; closed, it keeps the
  `bottom`-based positioning so it sits above the bottom nav.
- `SessionMobileLayout` computes the terminal panel's
  `paddingBottom` so terminal content's bottom edge sits exactly at
  the bar's top edge whether the keyboard is up or not.

Tests cover the new modifier flow, Shift+Tab, the absence of
in-bar letter buttons, and the `top`-anchored positioning when the
keyboard is reported as open.

* test(web): rewrite mobile key-bar e2e around real user flows

The prior e2e leaned on in-bar letter buttons that no longer exist.
Rewrites the spec around the journeys a mobile user actually performs:
tap ^C to cancel, latch Ctrl + type a letter on the OS keyboard, sticky
Ctrl across multiple chords, Shift+Tab from the bar, history nav with
the arrow button, Esc, symbol keys, and the iOS positioning fixes
(top-anchored when the keyboard opens, tracks visual viewport scroll,
terminal panel reserves space for the bar). Adds a `shift` getter to
the page object.

* style(web): apply prettier formatting

* fix(web): keep modifiers armed on dropped sends + tighten input handler

Addresses bot review on PR #741:

- `sendShellInput` no longer consumes Ctrl/Shift when the WS client
  isn't available — the chord stays armed so the user can retry once
  reconnected instead of seeing the latch silently clear.
- Adds `send-shell-input.test.ts` with the matrix the helper needs:
  empty data, ctrl chord + auto-release, sticky retention, shift+tab
  → CSI Z, shift passthrough on non-tab, and the WS-disconnected
  no-send/no-consume path.
- Removes dead `ctrlChord` export and its tests — the only consumer
  was its own test, and `applyShellModifiers` covers the same
  transform end-to-end.
- Drops the unused `send` parameter from `useShellInputHandler` (the
  effect now goes through `sendShellInput` directly), removing a
  spurious dep that would otherwise re-fire the effect on identity
  changes.
- Documents that `KEY_SEQUENCES.home`/`end` are intentionally readline
  shortcuts (Ctrl+A/E), not VT Home/End sequences, so future readers
  don't mistake it for a bug.

* refactor(web): split mobile key-bar helpers, tighten null-viewport test

Addresses two non-blocking suggestions from PR #741 review:

- Move `KeyDef`, `KEYS`, `ModifierButton`, and `KeybarButton` out of
  `mobile-terminal-keybar.tsx` into a sibling helpers file. The main
  component drops from 213 to 115 lines, comfortably under the 200-line
  component convention.
- Assert `viewportBottom === 0` in the null-viewport branch of
  `use-visual-viewport-offset.test.ts` so all three return-shape fields
  are covered there.
2026-04-30 14:05:35 +01:00
Carlos Florêncio 7a2160f1f9 feat: show repository scripts in dockview "+" menu (#703) 2026-04-25 18:40:31 +01:00
Carlos Florêncio 1fc3544625 fix: subtask sessions inherit agent profile from parent task (#692) 2026-04-24 21:31:36 +01:00
José Almeida 046ac5c1b6 feat(web): add Ctrl+F search to session, plan, and terminal panels (#686)
* feat(backend): add session-scoped message search endpoint

Adds SearchMessages across repository/service/handler layers and a new
WebSocket action "message.search". Returns lightweight hits with a
60-char snippet around the first match. Session-scoped only: results
never leak across sessions. Uses dialect-aware LIKE/ILIKE so it works
on SQLite today and Postgres later.

* feat(web): add shared panel search bar and Ctrl+F hook

Introduces the reusable PanelSearchBar overlay and usePanelSearch hook
used by the per-panel search features. The hook listens on the window
and fires only when focus is within the panel root, preventDefaults the
browser find-in-page, and wires Esc to close. PanelRoot becomes a
forwardRef so panel components can expose their root element to the
hook. Adds SHORTCUTS.FIND_IN_PANEL plus shared highlight/flash styles.

* feat(web): add Ctrl+F search to terminal panels

Mounts the xterm addon-search on both passthrough and shell terminals,
exposing next/prev plus case-sensitivity and regex toggles. Invalid
regex surfaces an inline error without firing the search. Because xterm
consumes keystrokes before React, the Ctrl/Cmd+F intercept is added to
attachCustomKeyEventHandler so the shortcut opens the bar from inside
the terminal. Results counter comes from onDidChangeResults; decorations
clear on close.

* feat(web): add Ctrl+F search to plan panel

Adds a custom TipTap extension that builds a ProseMirror DecorationSet
over the document and exposes setPlanSearchQuery / planSearchNext /
planSearchPrev / clearPlanSearch commands. The current match gets an
extra class and scrolls into view. Match count is read from the plugin
state via transaction subscription, so edits to the document update the
counter automatically. Find-only — no replace.

* feat(web): add Ctrl+F search to session chat panel

Wires the new backend message.search action into a debounced client
hook and renders a hits dropdown under the search bar, with the query
highlighted in each snippet. Clicking a hit scrolls the message into
view and flashes it. When the target message is older than what is
currently loaded, the hook loops through loadOlder pages until the
message renders, making search work across lazy-loaded history without
needing to scroll-and-wait manually.

* fix(web): lazy-load xterm addon-search to avoid SSR crash

addon-search is published as a UMD bundle that references `self` at
module load. Statically importing it breaks the Next.js static
prerender of /. Deferring the import to an effect keeps the module out
of the SSR graph without changing runtime behaviour.

* fix(web): propagate plan editor via state so plan-search hook re-runs

The plan-search hook subscribed to editor transactions inside a
useEffect that depended on a ref object. Because ref.current changes do
not trigger effect re-runs, if the Editor instance was not yet ready
when the hook first mounted, the transaction handler never attached and
the match counter stayed at 0 / 0. Tracking the editor via state (in
addition to the ref) causes the deps to change when the Editor becomes
available, so the handler attaches and the counter updates.

* feat(web): allow overriding message list renderer via ?renderer= query

Exposes the "native" / "virtuoso" choice at runtime so E2E coverage can
exercise both paths without a redeploy or code change. Defaults to the
existing "native" strategy when no override is present.

* test(web): add Playwright coverage for panel-scoped Ctrl+F search

Adds an e2e/tests/search/ suite with five specs:

- panel-search-shared: cross-panel contract (Ctrl+F opens, autofocus,
  Esc closes, close button, state reset on reopen) parametrised over
  session, plan, and terminal.
- terminal-search: xterm addon-search integration — key intercept
  without leaking "f" to the PTY, case + regex toggles (incl. invalid
  regex error), Esc does not reach the shell, backspace adjusts count.
- plan-search: TipTap plugin — highlight count, exactly one current
  match, counter, Enter / Shift+Enter wrapping, close clears
  decorations, no case/regex toggles in plan bar.
- session-search: WebSocket-backed hits list, "No matches" state,
  click-to-flash, cross-session isolation, close, and virtuoso renderer
  coverage via the new ?renderer= override.
- panel-focus-routing: bar mounts inside the focused panel only.

Three tests capture GIFs for the PR description via prCapture when
CAPTURE_PR_ASSETS is set (one per panel). Helpers live in
e2e/helpers/panel-search.ts and e2e/helpers/seed-session-messages.ts;
SessionPage gains search-bar locators.

* fix(web): prevent plan-search update loop on spurious editor transactions

TipTap fires `transaction` on every cursor move/click. The match-info
handler allocated a fresh `{current, total}` object each time, so React
always saw new identity, re-rendered, and could cascade with downstream
effects (e.g. `rehydrateCommentMarks`) into a "Maximum update depth
exceeded" loop. Use a functional setState with equality bail-out so the
handler only schedules a render when the counter actually changes.

* fix(web): retry terminal search once xterm addon-search finishes loading

The SearchAddon is dynamic-imported and was stored in a ref. The
query→findNext effect called `addonRef.current?.findNext(...)` — a
silent no-op if the addon wasn't loaded yet — and its deps didn't
include addon readiness, so a query typed before the import resolved
was dropped forever. Track the addon via state so the search effect
re-runs when it becomes available (and re-binds cleanly if the terminal
is ever re-created). Also deduplicate match-info updates via functional
setState + equality check.

* fix(web): route Ctrl+F to the last-clicked panel when focus is ambient

The chat panel's message list has no focus-capturing child, so clicking
on a message left `document.activeElement === <body>` and the existing
`isFocusWithin` gate silently fell through to the browser's native find.
Fix with two complementary mechanisms:

- `usePanelSearch`: track the most-recently-clicked panel via a
  window-level pointerdown capture listener. When focus is ambient
  (body/html), that panel claims Ctrl+F. Window-level capture dodges
  any descendant `stopPropagation` that would skip a container-level
  listener.
- Chat panel root: tabIndex=-1 + onMouseDown that focuses the root when
  the click target isn't already focusable. Normal path matches how
  plan/terminal already work (TipTap/xterm capture focus on click).

* feat(web): show agent profile name and logo in session search hits

The hit list showed uppercase "AGENT"/"USER" with vivid blue/green
colors that clashed with the app's muted metadata aesthetic. Replace
with the actual author identity:

- Agent hits render the profile name (falling back to "Agent") with
  the profile's <AgentLogo> inline — same mechanism session tabs use.
- User hits render "You" with a subtle `text-primary/80` tint that
  echoes the user bubble's `bg-primary/30`, keeping a light color
  differentiator without reintroducing loud blue/green.

Added `useSessionAgentIdentity` (plus a `useSessionAgentProfile` helper)
that reads from the store via two primitive selectors — the id, then
the profile — and composes the final object inside `useMemo`. A naive
single selector returning `{label, name}` triggers the "getSnapshot
should be cached" infinite loop in `useSyncExternalStore`.

* style: apply prettier formatting to search files

* fix(web): inline resolveStrategy in useMemo to satisfy react-hooks/use-memo

* fix(backend): escape LIKE metacharacters in message search

User query of `%` previously built LIKE pattern `%%` which matched every
message in the session; `file_name` also matched `filename`, `file name`,
etc. Escape the three LIKE metacharacters (`\`, `%`, `_`) before wrapping
the pattern, and declare `ESCAPE '\'` on the clause. Add regression tests
covering wildcard literals and the literal-percent happy path.

* fix(backend): slice search snippet in rune space to keep UTF-8 valid

`buildSnippet` indexed and sliced on byte offsets, so content containing
multi-byte characters (emoji, CJK, accented letters) could be cut
mid-rune — `encoding/json` then replaced the invalid bytes with `\ufffd`
in the websocket response. Switch to rune slicing and per-rune
case-folding via `unicode.ToLower`; this also avoids the
`strings.ToLower` byte-length-change pitfall (e.g. Turkish İ, German ß).
Add a handler test covering emoji, CJK, and large multi-byte content.

* fix(web): cancel backfill loop when user clicks a newer session hit

`setActiveHit` awaits up to 40 `loadOlder()` iterations when the target
message isn't in the DOM. If the user clicked a second hit while those
awaits were in flight, the in-progress loop kept running and eventually
scrolled to the original target, overriding the user's intent. Gate the
loop on a generation counter so only the most recent call's backfill
drives the scroll.

* fix(web): gate plan-search scroll side-effect on dispatch

TipTap invokes commands with `dispatch === undefined` during `can()`
checks to test applicability (e.g. before enabling a menu item). The
next/prev commands called `scrollToMatch` outside the `if (dispatch)`
guard, so a dry-run invocation would scroll the editor to a match the
user didn't navigate to. Move the scroll inside the dispatch block.

* fix(web): address review nits on search UX

- formatTime now checks Number.isNaN(d.getTime()) before formatting;
  Invalid Date doesn't throw, so the catch branch was unreachable and
  hits with a malformed created_at rendered the literal "Invalid Date".
- prefers-reduced-motion now silences the .search-flash animation
  alongside the other custom keyframe animations.
- planScript escapes `title` symmetrically with `content` so callers
  passing arbitrary strings don't produce malformed JSON.

* fix(web): fold description into opts to keep seedTask under param limit

`seedTask` had 6 parameters, violating the repo's 5-parameter cap and
failing CI lint. Fold `description` into the existing `opts` object and
update all callsites.

* test(web): add unit tests for useSessionSearch + remove redundant ternary

Cover the non-trivial hook logic called out in review: debounced fetch
coalescing, request-ID cancellation for superseded queries, null
sessionId short-circuit, and close() resets. Also simplify the
`matches.length ? 0 : 0` ternary flagged as a dead branch — both sides
were already 0.

* fix(web): exclude PanelRoot (tabindex=-1) from the focus short-circuit

`target.closest("[tabindex]")` was matching PanelRoot itself (which has
`tabIndex={-1}` so it can receive programmatic focus), causing the guard
to early-return and the `panelRef.focus()` call to never fire — defeating
the Ctrl+F focus-routing that this handler was added for.

Narrow the selector to `[tabindex]:not([tabindex='-1'])` so it only
short-circuits on genuinely interactive elements.

* fix(web): read isReadOnlyMode from live ref in shell-terminal key handler

`attachCustomKeyEventHandler` is called once and has no dispose hook, so
the inner handler keeps its initial closure forever. Other values
(sessionId, send, onFindInPanel) are read via `stateRef` so they stay
current, but `isReadOnlyMode` was captured by closure — if the prop ever
toggled mid-session, the Cmd+Arrow→Home/End path would continue firing
against a read-only terminal. Include it in the state ref for
consistency.

* fix(web): flatten doubly-nested seedTask opts in virtuoso spec; drop unused MODIFIER

The regex-based transform in 7152b936 over-nested one call in
session-search.spec.ts — `opts.description` received an object rather
than a string, so the virtuoso renderer test never received a proper
task description. Flatten it.

Also remove MODIFIER imports/declarations in terminal-search.spec.ts and
panel-search-shared.spec.ts that were left dangling behind `void` suppressors.

* fix(web): abort session-search backfill loop on close/unmount

Move reset logic into `close()` so the lifecycle is explicit (and avoids
the set-state-in-effect lint path the previous reset effect triggered).
Bump the `activeHitGenRef` generation counter on both `close()` and
unmount so an in-flight `setActiveHit` backfill can no longer issue
`loadOlder()` calls for a hidden UI.

Also extract `useDebouncedSearch`, `useSetActiveHit`, and a
`focusMessageElement` helper to keep `useSessionSearch` under the
100-line function cap.

* perf(web): skip PanelSearchBar debounce for session search

Session search already debounces the WS call inside `useDebouncedSearch`
(180ms), but the shared bar adds its own 150ms debounce on top — giving
a 330ms keystroke→request latency instead of the intended ~180ms.
Pass `debounceMs={0}` so the bar forwards queries immediately and the
hook-level debounce is the single source of timing truth.
2026-04-24 17:14:03 +01:00
Carlos Florêncio 6ffb157d7e fix: user workflow deadlock (#677) 2026-04-23 09:54:06 +01:00
Carlos Florêncio 2d1a868107 feat: configurable CLI flags per agent profile (#653) 2026-04-20 23:42:14 +01:00
Carlos Florêncio 8d407eab02 test(web): add e2e tests for tasks list view filters (#612) (#635) 2026-04-18 12:00:17 +01:00
Carlos Florêncio dc827a25d6 fix(github): treat skipped checks as passing and add ready-to-merge status (#616) 2026-04-17 16:33:43 +01:00
Carlos Florêncio a222854422 feat: associate agent profiles with workflows and steps (#597) 2026-04-16 10:14:45 +01:00
Carlos Florêncio 204fac1cca feat: acp-first profiles, models, and modes (#566) 2026-04-15 13:22:32 +01:00
José Almeida 2b8513f902 feat: add multi-select and drag-to-move for file tree and changes panel (#490)
* feat: add multi-select and drag-to-move for file tree and changes panel

Add Shift-click range and Ctrl/Cmd-click toggle selection to the file
tree and staged/unstaged changes sections. Includes bulk actions
(delete, stage, unstage, discard), drag-and-drop file moving into
directories, keyboard shortcuts (Escape, Ctrl+A), and E2E tests.

* fix: move bulk action components to file-row to fix max-lines lint

* style: apply prettier formatting to all changed files

* fix: bulk delete all selected items and update anchor on stale shift-click

* fix: address CodeRabbit review comments

- Keep original file-row-* data-testid for backwards compat
- Remove eager onClearSelection from bulk actions (auto-pruned by hook)
- Variant-scope bulk-discard/bulk-unstage test ids
- Remove index.lock deletion from E2E GitHelper retry loop

* fix: fix E2E tests — create files after session open, remove DnD test

- Create files AFTER opening session so WS detects git status changes
- Add proper git env vars (GIT_AUTHOR_NAME, etc.) matching existing tests
- Use element.press("Escape") instead of page keyboard for proper focus
- Remove drag-to-directory test (Playwright dragTo doesn't support HTML5
  dataTransfer events needed by our DnD implementation)

* fix: rename E2E task titles to avoid ambiguous 'Changes' tab selector

* fix: make file list focusable for Escape key handler in E2E tests

* fix: improve selection styling and ignore right-clicks in selection handler

- Use text-accent-foreground on selected rows for dark mode readability
- Override child span colors with [&_span]:text-accent-foreground
- Use hover:bg-accent/80 instead of hover:bg-muted on selected items
- Skip selection handling on right-click (button === 2) so context menu
  works without clearing multi-selection

* fix: plain click should not select — only Ctrl/Cmd+click and Shift+click

Plain click now clears any selection and performs the original action
(open file / toggle folder / open diff). Selection only happens with
modifier keys. handleClick returns boolean to indicate whether the
click was consumed by selection logic.

* fix: fix multi-select bugs and expand E2E test coverage

Bug fixes:
- Change isSelected from boolean to function prop so children compute
  their own selection state (fixes nested file selection)
- Inline TreeNodeRow JSX into TreeNodeItem so ContextMenuTrigger asChild
  can attach refs directly (fixes right-click context menu)
- Set lastClickedRef on plain click so shift-click has a valid anchor
- Add click-outside handler to clear selection

E2E tests expanded to 14 file-tree tests + 3 changes-panel tests:
- Ctrl-click select, toggle, multi-select
- Plain click clears selection
- Shift-click range after plain click and ctrl-click anchors
- Directory selection and nested file selection
- Right-click context menu (single + bulk delete)
- Click outside clears selection
- Escape clears selection

* fix: move AlertDialog outside ContextMenu to fix delete action

The AlertDialog wrapping the ContextMenu caused Radix focus management
to intercept context menu item selection, making Delete trigger Rename.
Moving AlertDialog to a sibling of ContextMenu fixes the conflict.

* fix: reorder context menu — Delete first, Rename second

Radix ContextMenu auto-focuses the first item on open, which caused
accidental Rename triggers. Putting Delete first (the more common
action for selection) avoids this.

* fix: don't clear selection when clicking context menu or dialog

The click-outside handler was clearing selection when clicking on
context menu items or confirmation dialog buttons (rendered in portals
outside the tree container). Now skips clearing for elements with
role=menu, role=alertdialog, or role=dialog.

Also extended the bulk delete E2E test to verify both files are
actually removed after confirming the dialog.

* fix: hide file count in delete dialog when folder children not loaded

When a folder hasn't been expanded, its children aren't loaded yet so
countFilesInTree returns 0. Instead of showing "0 files", hide the
count entirely and just show "delete <folder name>".

* fix: show confirmation dialog for bulk discard in changes panel

Bulk discard was calling git.discard() directly, bypassing the
confirmation dialog. Now routes through handleBulkDiscardClick which
opens the DiscardDialog with a bulk message ("discard changes to N
files") before executing.

* fix: address remaining CodeRabbit review comments

- Deduplicate filenames on move: when moving files with identical names
  to the same directory, append "(1)", "(2)" etc. to avoid collisions.
  Both tree update and rename API calls use the same deduplication.
- Replace void operator with .catch(() => {}) for bulk stage/unstage
  to prevent unhandled promise rejections.
- Add document-level dragend listener as safety net to clear stuck drag
  state if the element's onDragEnd doesn't fire (Escape, out-of-window).

* test: add unit tests for file tree move deduplication and visible paths

* fix: address all remaining PR review comments

- Move onSelectionChange out of React state updater (side effect fix)
- Stabilize keyboard listener deps (pass callbacks not object)
- Remove unnecessary useMemo in BulkActionBar
- Fix ghost-restore on partial bulk delete (remove all at once)
- Fix blank filename on single-file bulk discard
- Add aria-selected and role=treeitem for accessibility
- Extract computeNextSelection params into object (max-params lint)

* fix: add missing bulk action props to WorkingTreeProps type

WorkingTreeSections referenced onBulkStage, onBulkUnstage, and
onBulkDiscard but WorkingTreeProps Pick didn't include them, causing
a TypeScript build failure.

* fix: address final review comments

- Add unit tests for computeNextSelection (8 tests covering ctrl-click,
  shift-click range, stale anchor, reverse range, shift+ctrl extend)
- Extract shared E2E helpers (GitHelper, makeGitEnv, openTaskSession,
  createStandardProfile) to e2e/helpers/git-helper.ts
- Fix misleading comment in bulk delete (says "re-insert only failed"
  but implementation restores full snapshot)

* fix: use Promise.all for bulk delete to prevent partial rollback

Individual .catch() handlers could restore already-deleted files
if a later delete failed. Now uses Promise.all so rollback only
happens if any delete rejects.

* test: add renderHook tests for useMultiSelect hook behaviors

- Stale-path pruning: items shrink → selected paths auto-pruned
- Plain click: returns false, clears existing selection
- selectAll/clearSelection: work correctly, fire onSelectionChange
- Install @testing-library/react for renderHook support

* fix: handle false returns from onDeleteFile in bulk delete Promise.all

* chore: remove unused @testing-library/react-hooks dependency
2026-04-11 17:43:21 +01:00
Carlos Florêncio 41b405c64f fix(backend): prevent task reorder during silent session resume (#555) 2026-04-08 07:55:24 +01:00
Carlos Florêncio 75aad972a7 fix: increment CI checks elapsed time for in-progress runs in PR panel (#529) 2026-04-03 14:29:02 +01:00
Carlos Florêncio 1c6506911e feat: add workflow sorting with drag-and-drop reordering (#520) 2026-04-01 23:58:43 +01:00
Carlos Florêncio 447da7edb2 fix: re-inject plan mode instructions on follow-up prompts (#507) 2026-03-31 22:43:22 +01:00
Carlos Florêncio c56dfe8537 feat(github): move PR monitoring to backend with lightweight polling (#502) 2026-03-30 23:26:01 +01:00
Carlos Florêncio 81341263fd feat(terminal): fix bottom padding and add font family setting (#489) 2026-03-29 22:27:37 +01:00
José Almeida d1a028fdb6 feat: introduce subtasks, allow sessions to task reuse executor (#419) 2026-03-29 15:33:40 +01:00
Carlos Florêncio 82d5168b67 refactor: centralize default prompts and fix PR review scoping (#476) 2026-03-28 16:51:24 +00:00
Carlos Florêncio 7da6550093 fix(orchestrator): fail session with guidance when PR branch is missing (#466) 2026-03-26 19:35:17 +00:00
Carlos Florêncio ec01a55354 fix(web): prevent dockview layout from not filling viewport after session switch (#471) 2026-03-26 10:04:04 +00:00
Carlos Florêncio 1212326c39 fix(web): add queued message indicator to quick chat and e2e tests (#470) 2026-03-26 10:01:57 +00:00
Carlos Florêncio fb5886600a feat(web): add markdown preview mode and PR screenshot capture (#461) 2026-03-24 23:38:02 +00:00
Carlos Florêncio 74bba464b3 fix(settings): show confirmation dialog when deleting agent profile with active sessions (#441) 2026-03-14 22:06:58 +00:00
José Almeida f1da9644f4 feat: agent-native config mode (#396)
* 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
2026-03-13 00:44:06 +00:00
José Almeida 06e5114174 feat: add clickable terminal links with configurable open behavior (#401)
* feat: add clickable terminal links with configurable open behavior

Add @xterm/addon-web-links to both passthrough and shell terminals,
enabling Cmd+Click on URLs. Users can configure whether links open
in a new browser tab (default) or the built-in browser panel via a
new setting in the Settings page.

* style: fix prettier formatting

* fix: address CodeRabbit review comments

- Extract applyTerminalLinkBehavior helper to reduce complexity
- Set default terminalLinkBehavior for first-time users in empty settings path
- Use storeApi.getState() for optimistic rollback to avoid stale closures
- Add beforeEach settings reset in E2E tests
- Use 4xx range assertion instead of hardcoded 500

* fix: return 400 for user settings validation errors

Add ErrValidation sentinel to user service and wrap validation errors
from apply* functions. Handler now returns 400 Bad Request instead of
500 Internal Server Error for invalid input. Update E2E test assertion
to enforce client error status (< 500).

* fix: remove cmd+click mention from terminal links description

* fix: mirror validation error handling in WS update path

* style: fix prettier formatting
2026-03-10 15:37:51 +00:00
Carlos Florêncio ddcd101762 fix: persist template step edits on workflow save and add step delete confirmation (#394) 2026-03-08 20:41:11 +00:00
Carlos Florêncio 0da44478d1 feat: add port proxy, symlink file save fix, and remote executor improvements (#358) 2026-03-07 14:53:21 +00:00
José Almeida 123b2f6eed feat: start task from GitHub URL (#365)
* 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
2026-03-05 00:47:58 +00:00
Carlos Florêncio 124fe588f8 feat: improve TUI agents session resume on restart (#367) 2026-03-04 23:34:26 +00:00
Carlos Florêncio c7978a4ef3 fix: resolve stale PR data on task switch and deduplicate lifecycle code (#361) 2026-03-03 23:40:44 +00:00
Carlos Florêncio ee59ccdfbb feat: tui agents with workflows and code quality improvements (#360) 2026-03-03 22:59:22 +00:00
Carlos Florêncio 44811da702 feat: web e2e tests (#304) 2026-03-02 08:54:58 +00:00