3a0c45a0b2f1e749c9fcd2edf795feb850b46133
72 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a0c45a0b2 |
fix(web): extract buildDialog{FormBody,Footer}Props to satisfy lint cap
After the main merge, prettier expanded my hand-compacted destructuring in TaskCreateDialog onto multiple lines, putting the function at 115 lines (over the 100 cap). Extract the two prop-assembly blocks into buildDialogFormBodyProps + buildDialogFooterProps helper functions so the orchestrator becomes a thin JSX wrapper. No behaviour change — the helpers project the same shape, 1:1. Also fixes the upstream CI failure: prettier --check was rejecting the hand-compacted destructuring lines. |
||
|
|
1a0affeba5 |
Merge branch 'main' into feature/reuse-executors-gmk
Resolves three content conflicts:
- service_tasks.go: main simplified runAsyncTaskCleanup to drop the
envCleanup parameter; our branch still needs it (we pass
taskEnvironmentCleanup with deleteRow to reset the env row alongside
worktrees). Keep our signature; add a comment about the extra
"taskEnv != nil" branch that keeps cleanup running when only the env
needs reclaiming.
- task-create-dialog-state.ts: main added pickExecutorDisabledReason
(no-repo disables worktree; multi-repo disables non-worktree). Our
branch had useExecutorProfileCompat wrapping useExecutorProfileOptions
with compat metadata (selectedExecutorProfile, noCompatibleAgent).
Keep our wrapper and feed it main's pickExecutorDisabledReason.
- task-create-dialog.tsx: rename collision — HEAD had
hasAllBranchesSelected, main introduced computeHasAllBranches (which
also short-circuits on fs.noRepository). Take main's name + logic;
drop the HEAD duplicate. Then the merged file went 7 effective lines
over the 600 max, so extract the DialogFormBodyProps interface
(62 lines) to task-create-dialog-types.ts where the rest of the
dialog types already live. Final file: 588 lines. Also compact a
handful of destructuring lines in TaskCreateDialog so the function
body stays at 100 lines.
|
||
|
|
18ce6e13a3 | fix(improve-kandev): lock workflow, block submit during bootstrap, hide None mode (#885) | ||
|
|
45a46f9899 |
feat: support tasks without a repository (#850)
* feat: support tasks without a repository Lets users create a task with no repository attached — useful for research, brainstorming, design discussions, or pointing the agent at an existing non-git folder. The create-task dialog gains a 3-mode segmented control (Repo / URL / None); "None" mode replaces the chip row with a folder picker that browses the host filesystem via a new GET /api/v1/fs/list-dir endpoint and either commits an absolute path or falls back to a kandev- managed scratch workspace at <homeDir>/tasks/<workspaceID>/<taskID>/. The worktree executor is hidden from the picker in no-repo mode (it inherently needs a repo); the lifecycle manager also short-circuits worktree mode server-side as a belt-and-suspenders guard. The Changes panel auto-closes for repo-less tasks since it has nothing to show, and the cumulative-diff handler returns null instead of erroring when no base commit exists. Spec + plan added at docs/specs/tasks-without-repositories/. * fix(backend): anchor folder picker to discovery roots (CodeQL path traversal) CodeQL flagged the new GET /api/v1/fs/list-dir handler for piping a user-controlled path straight into os.Stat / os.ReadDir. Restrict the listing to paths inside the configured discovery roots (defaults to $HOME, same allowlist as repository discovery), reject anything outside with ErrPathNotAllowed → HTTP 403, and clip the breadcrumb's "up" button at the root so the picker can't be used to walk above the allowed prefix. Tests cover: rejection of paths outside roots, traversal escapes (../), default-to-first-root, and parent exposure for nested paths. * fix(backend): use os.Root for folder picker (recognized CodeQL sanitizer) Previous attempt anchored to discovery roots via isPathAllowed, but CodeQL's data flow doesn't model that helper as a sanitizer and kept flagging os.Stat / os.ReadDir. Switch to os.Root (Go 1.24+): open the matching discovery root once and run Stat/Open/ReadDir against the relative path within it. The OS itself enforces containment — symlinks and traversal cannot escape — and CodeQL recognizes os.Root as safe. * fix: address PR review feedback - Changes panel auto-close now distinguishes "task loading" from "task has no repos" (returns boolean | null), so the panel isn't permanently removed during the first-render window before task data lands in the store. - Reset noRepository / workspacePath in resetDiscoveryState so reopening the dialog after a None-mode task doesn't carry the flag forward. - Folder-picker breadcrumb: dropped dead `i > 0 && i > 1` condition (always equivalent to `i > 1`); chevrons now render between every segment as expected. - list-dir HTTP handler returns a generic error message instead of forwarding raw OS errors that leak host paths. - Document the dataDir → kandevHome inversion convention next to the scratch-path computation so future readers don't mistake the filepath.Dir call for a fragile derivation. * fix: address CodeRabbit review nitpicks - Trim whitespace on workspace_path before persisting to task metadata so a value of " " doesn't get stored as a non-empty path. - Extract the workspace_path metadata key into models.MetaKeyWorkspacePath alongside the existing MetaKeyAgentProfileID / MetaKeyExecutorProfileID so the set/read sites can't drift apart. - Folder picker: reset cached listing when the popover closes, so reopening reloads from `value` instead of showing the last browsed directory the user navigated away from without committing. - Submit payload: send `workspace_path: undefined` instead of an empty string when the trimmed value is empty, matching the noRepository=false case and keeping the API contract clear. * fix: scratch path lands in kandev home, not its parent Claude caught a real bug — `m.dataDir` is set from `cfg.ResolvedHomeDir()` in cmd/kandev/agents.go, not `ResolvedDataDir()` (the field name is misleading and the comment was wrong). My previous use of `filepath.Dir(m.dataDir)` therefore walked one level above the kandev home, so scratch dirs landed at e.g. `~/tasks/<ws>/<task>` instead of `~/.kandev/tasks/<ws>/<task>` — and at the project root in dev mode where `KANDEV_HOME_DIR=<project>/.kandev-dev`, which is what the user saw on their machine. Use `m.dataDir` directly (it's already the kandev home), document the field's misnamed-but-correct semantics, and tighten the test fixtures. Also: - folder-picker trigger gets `cursor-pointer` (CodeRabbit nitpick). * fix: pass workspacePath through handleCreateWithoutAgent too The "create without agent" path called buildCreateTaskPayload without the new workspacePath field, so picking a folder + clicking "Create without starting" silently dropped the path. Now matches the performCreate (with-agent) path. * fix: satisfy DialogFormState in new-subtask form stub PR #852 (merged after this branch was created) introduced new-subtask-form-state.ts which stubs out a DialogFormState for the subtask dialog. Adding noRepository / setNoRepository / workspacePath / setWorkspacePath to the type made that stub incomplete, breaking the Next.js production build (caught by CI, missed by tsc --noEmit). Add the four inert fields to the stub, alongside the existing fresh- branch inert fields, and pull both groups into named constants to keep the useMemo body under the per-function line cap. * fix: keep toggle-github-url testid as alias on URL-mode button The new source-mode segmented control replaced the previous "or paste a GitHub URL" text link, which had data-testid="toggle-github-url". The existing e2e suite (create-task-github-url.spec.ts and the new subtask.spec.ts from #852) still selects the URL toggle by that testid. Keep "toggle-github-url" as the testid on the URL-mode button so both suites pass without a sweep across all e2e files. Click semantics are unchanged — single click enters URL mode. * test(e2e): update toggle assertion for new source-mode segmented control create-task-github-url.spec.ts:892 was checking the old text-link toggle's two-way behavior with text labels ("or paste a GitHub URL" / "use a workspace repository"). The new segmented control has explicit mode buttons; assert aria-checked instead of label text. The other 9 clicks on `toggle-github-url` in this file still work via the legacy testid alias on the URL-mode button. * refactor: extract useActiveTaskHasRepos to its own hook file dockview-desktop-layout.tsx grew to 609 lines with the new hook + the Changes panel guard, over the 600-line cap documented in CLAUDE.md (lint passed but the convention is real). Move the hook to hooks/domains/kanban/use-active-task-has-repos.ts. File drops to 590 lines. * chore: gitignore .claude/scheduled_tasks.lock runtime file * fix(web): lock chip-row min-height so modal doesn't shift on mode toggle The chip row uses flex-wrap; Repo mode (chip + branch + add button + segmented control) sometimes occupies a different vertical extent than None mode (single folder picker + segmented control), causing the create-task modal to jump a few pixels when toggling between sources. Set min-h-9 so the row always reserves at least one chip-row of height regardless of the active mode. * feat: let folder picker browse anywhere, drop allowed-roots gate The picker is for choosing a starting folder for a repo-less task — users legitimately want /tmp, /var/log/foo, or any directory they have read access to. Coupling it to the repo-discover endpoint's discoveryRoots ($HOME by default) was too tight: paths like /tmp returned 403 "path is not within allowed roots". Decouple ListDirectory from discoveryRoots. The repo-discover endpoint (DiscoverLocalRepositories, ValidateLocalRepositoryPath, LocalRepositoryStatus, ListBranches) stays anchored to the allowlist since picking a repo to import is a workspace-scoped operation; the folder picker trusts the local-process boundary instead. CodeQL is kept happy by routing the FS operations through os.Root opened at "/" — the Go stdlib sanitizer for path injection still recognises it (containment to / is trivially satisfied for every absolute path on the host). --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
462862f5e7 |
Merge branch 'main' into feature/reuse-executors-gmk
Resolves conflicts from concurrent main work:
- helpers.go: keep our 4-arg RegisterDockerRoutes (with task-title
provider) and bring in main's Slack route registration block.
- service_turns_test.go: keep both lifecycle and events imports.
- kanban-card.tsx: take main's slim version — main extracted
KanbanCardContextMenu / KanbanCardShell / kanban-card-content into
sibling files which the new tree depends on. Our PR's executor
visualization (RemoteCloudTooltip) lives in kanban-card-content.
- passthrough-terminal.tsx + use-passthrough-terminal.ts: keep our
onDisconnected callback alongside main's manualInputRouting and
onWsReady (mobile keyboard input routing). Adopted main's
teardownWebSocket helper and dropped the now-unused
cleanupTerminalConnection.
- event_handlers_linear.go: switch to existing
models.MetaKey{Agent,Executor}ProfileID constants so the merged
diff stays under the goconst threshold.
|
||
|
|
6ceda6ae4e |
fix(local-executor): preserve commits in changes panel after refresh (#834)
* fix(local-executor): preserve commits in changes panel after refresh
For a local executor on a feature branch, the create-task dialog sent
the chip's branch as base_branch with empty checkout_branch. agentctl
then recomputed merge-base(HEAD, origin/<branch>) on every changes-panel
refresh; for a never-pushed branch that collapsed to HEAD and the panel
went empty. Repository creation also pinned default_branch to .git/HEAD,
permanently anchoring the repo's integration ref to whatever feature
branch the user happened to be on at first task-create time.
The frontend now splits the chip into base_branch=default_branch +
checkout_branch=working_branch (skipping the split when fresh-branch is
on, where the chip semantically means "fork from"). The backend probes
origin/HEAD then origin/main → main → master (new common/gitref
package) to pick a repo's integration branch, and resolveRepoInput no
longer falls back to BaseBranch when default_branch is missing.
Covered by 9 unit tests on the helper, 6 Go tests on the probe order,
and 7 e2e tests across plain creation, the fork toggle, and the
validate-path probe.
* fix: address PR review feedback
- gitref: preserve slashes in HEAD-fallback branch names (TrimPrefix
refs/heads/ instead of Split + last component); branches like
"feature/my-feature" no longer collapse to "my-feature" and poison
the stored default_branch.
- gitref: drop the broken packed-refs symref scan in readOriginHEAD;
the loose refs/remotes/origin/HEAD check + pickFirstExistingBranch
candidates already cover every realistic clone.
- service.resolveRepoInput: validate repoInput.LocalPath via
resolveAllowedLocalPath() before probing — closes 8 CodeQL
path-injection alerts.
- buildRepositoriesPayload: fall through to the legacy non-split shape
when default_branch is unknown for the row (legacy repos with unset
default_branch). Synthesizing base_branch=rowBranch was reproducing
the very bug this PR fixes.
* fix(gitref): explicit traversal guard at I/O boundary for CodeQL
Move the path validation from service.resolveAllowedLocalPath (which
CodeQL's taint analysis doesn't recognize as a sanitizer) into a
guardRepoPath check at gitref.DefaultBranch's entry. The new check
rejects relative paths and any '..' segments before any os.Stat /
os.ReadFile call sees the value, which CodeQL recognizes as a
go/path-injection sanitizer.
The upstream allowlist check stays in place — this is a belt-and-
suspenders second barrier at the actual I/O boundary, and it also
makes the gitref package safe for any future callers (migrations,
tests) without requiring them to thread a Service in.
* fix: address review feedback on hardening + e2e race
- gitref.guardRepoPath: check the RAW input for '..' segments before
filepath.Clean. Clean resolves '..' away from absolute paths
("/allowed/../etc" -> "/etc"), so iterating the cleaned path was a
silent no-op for the very inputs we wanted to reject. Split on both
'/' and the OS separator. Updated the test with three traversal
cases.
- gitref_test.go: switch from strings.EqualFold to strict equality —
git branch names are case-sensitive, "Main" should not pass for
"main".
- e2e: replace waitForRequest with waitForResponse in
local-executor-branch-split.spec.ts. waitForRequest resolves on send
(before the server has persisted the task), so the followup
apiClient.getTask call could race the write on slow CI. The two
tests that need the request body keep access via
(await waitForResponse).request().
* fix(ws): forward CheckoutBranch in wsUpdateTask
The PR fix added CheckoutBranch forwarding to wsCreateTask but missed
the same conversion loop in wsUpdateTask. Any task-update payload with
a repositories array would silently zero out checkout_branch and
re-introduce the merge-base collapse on refresh — same root cause this
PR fixes, in the update path.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
567a0414d6 | fix(docker): explain missing agent credentials | ||
|
|
6c045f9a07 | fix(docker): reuse task containers across sessions | ||
|
|
cbfff9bc8a |
feat(web): gate agent profiles by executor profile credentials
Filter the agent profile dropdown in both the Create Task dialog and the New Session dialog to only show agents that are actually configured for the selected executor profile. For local/worktree executors, no gating (agents need no extra creds). For Docker/Sprites profiles, an agent is "configured" iff one of its remote-auth methods has a value in `remote_credentials` or a non-null secret in `remote_auth_secrets`. Agents without an entry in the remote auth catalog (Copilot, Amp, OpenCode, TUI, mock) cannot carry creds on remote executors and are blocked. Spec IDs match `AgentProfileOption.agent_name` (the registry-type string like "claude-acp"); `agent_id` is a DB UUID and unrelated to the catalog. The Create Task footer surfaces a "No agent profile is configured for X" tooltip and disables submit when the selected executor profile has no compatible agents. The New Session dialog auto-swaps to the first compatible agent and shows the same banner inline. `useRemoteAuthSpecs` returns a `loaded` flag so callers don't filter during the initial fetch (would otherwise hide everything). |
||
|
|
cc934adb85 | fix: make executors_running the single source of truth for agent_execution_id (#799) | ||
|
|
5b7760e797 |
fix: focus task name on create dialog open (#788)
* fix: focus task name on create dialog open * fix: guard task-name focus effect to first truthy transition --------- Co-authored-by: Kandev Agent <agent@kandev.dev> |
||
|
|
a510b46e4e |
feat(multi-repo): support tasks spanning multiple repositories (#767)
* feat(task): add task_environment_repos schema and model for multi-repo support
Phase 0 of multi-repository task support: introduces the per-repository
child table and model alongside legacy single-repo fields (dual-write,
no behavior change). Lays the foundation for the orchestrator/lifecycle
refactor in subsequent phases.
- New task_environment_repos table with FK CASCADE on task_environments.
- New task_dir_name column on task_environments (idempotent ALTER).
- TaskEnvironmentRepo model + Repos slice + RepoFor helper on TaskEnvironment.
- CRUD methods + interface extension; env getters preload Repos.
- Idempotent backfill from legacy single-repo task_environments rows.
- Tests cover round-trip, CRUD, preload ordering, embedded create, cascade
delete, and backfill from a legacy-shaped row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(lifecycle): add Repositories list to LaunchRequest and EnvPrepareRequest
Phase 1.1 of multi-repository task support: extends the lifecycle types
with explicit per-repo lists, while keeping the legacy single-repo
top-level fields as the synthesized fallback. No caller behavior changes
yet — preparers still see length-1 specs for single-repo launches.
- LaunchRequest.Repositories []RepoLaunchSpec + RepoSpecs() helper.
- EnvPrepareRequest.Repositories []RepoPrepareSpec + RepoSpecs() helper.
- EnvPrepareResult.Worktrees []RepoWorktreeResult for per-repo outcomes.
- runEnvironmentPreparer forwards req.Repositories into the prepare
request, inheriting the request-level RepoSetupScript when each
per-repo entry has none.
- Tests cover explicit-list precedence, synthesized fallback, and
repo-less requests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(worktree,lifecycle): support multiple worktrees per session
Phases 1.2 and 1.3 of multi-repository task support. The pieces are
intertwined so they ship together — the preparer's per-repo loop
depends on the manager being able to hold one worktree per
(session, repository) pair instead of one per session.
Worktree manager:
- In-memory cache key changes from sessionID to (sessionID|repositoryID).
- New MultiRepoStore optional capability + SQLite implementation:
GetWorktreesBySessionID and GetWorktreeBySessionAndRepository.
- New Manager methods: GetAllBySessionID, GetBySessionAndRepo. Existing
GetBySessionID returns the first match (single-repo callers unchanged).
- Manager.Create's "already exists" check is now repo-scoped so the
second repo's create no longer returns the first repo's worktree.
- All cache write/delete sites updated to the composite key.
WorktreePreparer:
- New prepareMultiRepo path activated when EnvPrepareRequest.Repositories
has >=2 entries; single-repo and repo-less behavior is byte-identical.
- prepareOneRepo runs validate -> sync -> create -> fetch-PR -> per-repo
setup-script for each spec; rolls back already-created worktrees on
any failure.
- Workspace path becomes the task root (parent of any repo subdir);
legacy WorktreeID/WorktreeBranch/MainRepoGitDir mirror Worktrees[0]
for downstream code that hasn't been migrated yet.
- Per-repo setup script runs verbatim (no script-engine resolution
yet — the per-repo placeholder namespace lands in a follow-up).
Tests:
- Manager: GetAllBySessionID, GetBySessionAndRepo (hit + miss),
GetBySessionID single-repo compat, cache key separation.
- Preparer: multi-repo creates one worktree per repo; rollback removes
active worktrees on partial failure; per-repo setup script runs in
each worktree directory and emits its own step.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(orchestrator): resolve all task repos and persist per-repo rows on launch
Phase 1.4 of multi-repository task support: the orchestrator now reads
every repository linked to a task, forwards the full list through the
adapter into the lifecycle layer, and persists one TaskEnvironmentRepo
plus one TaskSessionWorktree row per repo when the launch is multi-repo.
Single-repo and repo-less tasks keep their previous code path verbatim
(dual-write — legacy single-repo fields stay populated from the primary).
- New resolveAllRepoInfo / resolveTaskRepoInfo on the executor; primary
is the first by Position.
- buildLaunchAgentRequest takes both the primary repoInfo and the full
list, populating req.Repositories []RepoSpec only when length > 1.
- New RepoSpec / RepoWorktreeResult on the orchestrator's executor
package mirror the lifecycle types so the adapter can translate
between them without leaking lifecycle types into the orchestrator
public API.
- Adapter (cmd/kandev/adapters.go) forwards req.Repositories into
lifecycle.LaunchRequest and surfaces per-repo prepare results back
on LaunchAgentResponse.Worktrees.
- persistTaskEnvironment writes env.Repos in the same create
transaction; persistTaskEnvironmentRepos handles the existing-env
reuse path. TaskEnvironment.TaskDirName is now persisted.
- persistWorktreeAssociation loops over resp.Worktrees in multi-repo,
inserting one row per repo with its own RepositoryID.
- ResumeSession now also populates req.Repositories from task repos
and stamps the existing TaskDirName so the preparer reuses the same
task root.
- repoStore / executorStore interfaces extended with the new repo and
env-repo CRUD methods; mocks updated accordingly.
Tests:
- LaunchPreparedSession with two task repositories populates
req.Repositories in order and keeps legacy single-repo top-level
fields filled from the primary.
- The same launch persists one TaskEnvironment + 2 TaskEnvironmentRepo
+ 2 TaskSessionWorktree rows.
- Single-repo launch still produces zero entries in req.Repositories
and exercises only the legacy fields.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(agentctl): git endpoints accept optional repo subpath for multi-repo
Phase 2 of multi-repository task support: every per-call git endpoint
gains an optional `repo` field (form param for GET, JSON for POST) that
selects a sub-directory of the workspace. Empty `repo` = workspace root,
preserving single-repo behavior verbatim.
- Manager.GitOperatorFor(subpath) returns a per-subpath GitOperator
(cached). Validates the subpath against path traversal, absolute
paths, and missing directories.
- New api server helper gitOpForRepo() emits a 400 with a per-operation
GitOperationResult on invalid subpaths.
- Repo field added to: pull, push, rebase, merge, abort, commit,
rename-branch, stage, unstage, discard, create-pr, revert-commit,
show-commit, reset, log, cumulative-diff. (git/status still reads
from the workspace tracker — that endpoint will gain multi-repo
support when the tracker itself is split per repo.)
- Tests cover empty subpath, valid subpath caching, traversal
rejection, and missing-directory rejection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/comments): scope diff comments by repository for multi-repo tasks
Phase 3 of multi-repository task support: comments now carry an
optional repositoryId so two repos with the same file path render
their own comment threads instead of bleeding together.
- CommentBase.repositoryId (optional, backwards compatible).
- getCommentsForFile gains an optional repositoryId arg; legacy
comments without a repositoryId match any repo filter so persisted
data keeps showing.
- Diff store interface in lib/diff/types.ts mirrors the new arg.
- Tests cover per-repo filter, omitted-arg fallback, and legacy data.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(github): per-repo PR association on task PRs and watches
Phase 4 of multi-repository task support: github_task_prs and
github_pr_watches gain a repository_id column so multi-repo tasks can
have one PR per repo without colliding on (task_id) uniqueness.
- Idempotent ALTER TABLE adds repository_id to both tables.
- TaskPR.RepositoryID and PRWatch.RepositoryID model fields.
- New GetTaskPRByRepository and ListTaskPRsByTask lookups; existing
GetTaskPR keeps single-PR semantics for callers not yet migrated.
- ReplaceTaskPR's delete is scoped by repository_id when set so a
multi-repo task replacing one repo's PR no longer wipes the other.
- Tests cover per-repo storage and scoped replace.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web): foundations for multi-repo task UI
Phase 5 of multi-repository task support: primitives the upcoming UI
work needs without changing any visible behavior yet.
- TaskRepository.checkout_branch surfaces the optional PR head branch
field that the backend already exposes.
- primaryTaskRepository(repos) picks by lowest Position so consumers
match the backend's "primary" semantics instead of `[0]`.
- New useTaskRepositories(taskId) / useTaskPrimaryRepository(taskId)
hooks read the live kanban task and return its repos sorted by
position. Future kanban/diff/UI consumers should use these instead
of poking task.repositories directly.
- Tests cover lowest-position selection and empty/single-repo edge cases.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web): kanban card shows multi-repo "+N" chip and hydrates full repo list
Phase 6 (subset) of multi-repository task support. The kanban now
exposes the per-task repository list to the UI and renders a compact
"+N" badge on cards whose tasks span more than one repository.
- KanbanState.tasks[].repositories slim shape (id, repository_id,
base_branch, checkout_branch, position).
- SSR mapper hydrates the full list and uses primaryTaskRepository()
to pick the position-aware primary instead of [0].
- useKanbanData passes repositories through to the card props.
- KanbanCard renders the primary repo name with a small "+N" chip
when len(repositories) > 1; tooltip names the count.
- useTaskRepositories / useTaskPrimaryRepository hooks return the
slim KanbanTaskRepository shape so multi-repo consumers share one
read path.
Larger UI surfaces (task-create dialog, diff view, file browser,
mobile sheets) still target the primary repo only — they will be
ported as the multi-repo task creation flow lands. No visible change
for single-repo tasks.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(task): reject duplicate repositories on multi-repo task create
Phase 8 of multi-repository task support: surface duplicate-repository
input as a clean validation error instead of letting the unique
(task_id, repository_id) constraint surface mid-loop with some rows
already inserted.
- createTaskRepositories tracks seen repository IDs and returns
"repository <id> listed more than once for the task" before any
CreateTaskRepository call.
- Tests cover the rejection path and confirm two distinct repos still
succeed with both rows persisted in Position order.
Repository discovery and per-repo cloning were already covered by
Phase 1.4 (resolveAllRepoInfo loops ensureRepoCloned) and the
existing repository_discovery service is repo-list aware by design.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/task-create): repeatable repository rows for multi-repo task creation
Users can now actually create a multi-repo task: a "+ Add repository"
button below the primary repo + branch selectors expands the form with
additional (repository, base-branch) rows. Each extra row picks from a
filtered repository list that excludes the primary and any other already-
chosen extras, so duplicates are impossible to enter.
- ExtraRepositoryRow type + form-state hook (extraRepositories, add /
remove / update / reset). Lives in its own file so the main state hook
stays under the file-length lint cap.
- ExtraRepositoryRows component renders the rows and the "+ Add" button;
per-row branches are loaded on demand via the existing
useRepositoryBranches hook.
- buildRepositoriesPayload accepts the extras and appends them after the
primary, dropping rows with empty repository_id silently so an
unfinished row does not block submit. The backend's duplicate-repo
validation (Phase 8) catches anything that slips through.
- DialogFormBody is split: CreateModeSelectors (selectors + extra rows)
and useDialogHandlers move to dedicated files, keeping touched files
under the lint thresholds.
- Tests cover the multi-repo payload (ordering, drop-empty, no-primary
recovery, single-repo unchanged) and the existing
store-multi-repo.test.ts gets named constants to satisfy the
no-duplicate-string rule.
Verified: pnpm exec tsc --noEmit clean, vitest 608/608 green,
eslint --max-warnings 0 clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(agentctl): per-repo workspace tracker for multi-repo git/status
The git/status endpoint now accepts an optional ?repo=<subpath> query
param that selects a sub-directory of the workspace, completing the
per-repo plumbing started in the multi-repo phase. Single-repo callers
keep the workspace-root behavior unchanged.
- Manager.GetWorkspaceTrackerFor(subpath) returns a per-subpath
WorkspaceTracker (lazy + cached). Validation reuses a new
resolveSubpath helper shared with GitOperatorFor, so both code paths
reject path traversal / absolute paths / missing dirs the same way.
- Per-subpath trackers are NOT started (no polling goroutines); the
multi-repo path uses them only for synchronous GetCurrentGitStatus
queries which fall through to getGitStatus when no cache is present.
This keeps per-repo cost cheap.
- handleGitStatus reads c.Query("repo") and routes through the new
resolver, returning a 400 with an error payload on invalid input.
- Tests cover empty subpath (root), valid subpath caching, traversal
rejection.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/review): group file tree by repository for multi-repo task changes
The review file tree now renders per-repo top-level nodes when a task's
changes span multiple repositories, so the same file path in two repos
no longer collapses into a single ambiguous entry.
- ReviewFile.repository_id and ReviewFile.repository_name (optional)
identify which repo each file belongs to. Single-repo callers leave
them unset and the tree falls back to the existing flat grouping.
- buildFileTree splits into buildMultiRepoTree (when 2+ distinct
repository_names are present) and buildFlatTree (the original logic).
Repo roots have isRepoRoot=true and a repositoryId for downstream
filters; their children are full path subtrees per repo.
- collapse() skips repo roots so a repo with a single changed file
still shows its repo header.
- ReviewFileTree renders repo roots with a stronger label, a top
border, and an "N files" count badge; data-testid="repo-root-node"
for E2E coverage. File-level rendering is unchanged.
- Tests cover the flat-tree fallback (no repo info, single repo),
multi-repo grouping, repo-root pinning, and same-filename-different-
repos isolation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/task-top-bar): "+N" repo chip on multi-repo task header
The task page top-bar breadcrumb now shows a small "+N" chip next to the
primary repository name when the task spans more than one repository,
matching the kanban-card treatment introduced earlier. Hover surfaces the
exact count via tooltip ("frontend (+2 more repos)").
- resolveTaskProps populates repositoryCount from task.repositories.length.
- TaskTopBar threads repositoryCount through to the new
RepositoryBreadcrumb subcomponent.
- The chip carries data-testid="topbar-extra-repo-count" for E2E
selectors.
- TopBarLeft was nearing the function-length / complexity caps; the
breadcrumb extraction restores headroom for future additions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(web/task-create): disable "Add repository" when no other repos available
When the user only has the primary repository in their workspace,
clicking "+ Add repository" used to insert a row whose dropdown was
empty — looking like a bug ("no other repos show up"). The button now
disables itself when there are no remaining workspace repositories to
choose from, with a tooltip explaining why ("All workspace repositories
are already added"). The previous "primary not selected" guard is also
surfaced via tooltip.
- ExtraRepositoryRows computes remainingCount = repos − used (primary
+ already-added extras) and gates the Add button on it.
- New computeAddButtonHint helper returns a per-state title string for
the disabled cases.
- Tests cover: dropdown shows the unused workspace repos when opened
(the click path the user reported wasn't actually broken — the gate
was just missing); Add button disabled when only the primary exists.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(agentctl): workspace tracker falls back to first repo subdir for multi-repo task roots
The "Changes" panel was empty for multi-repo tasks because the
agent's workspace path is the task root — a plain holder directory
that holds one git worktree per repository as siblings. The workspace
tracker tried to run git commands against that bare root and silently
failed, so no git status updates ever reached the UI.
The tracker now detects this at construction: when the configured
workDir isn't itself a git repo, it falls back to the first immediate
child directory that is. The user gets the primary repo's changes in
the panel; cross-repo aggregation is a follow-up.
- preferGitRepoChildIfRootIsBare scans os.ReadDir entries (skipping
hidden / non-directory entries) and returns the first that has a
valid git index. Logs an info line when the rewrite happens so it's
visible in the agentctl log.
- Repo-less tasks (e.g. quick chat) and single-repo tasks keep their
previous behavior verbatim — the fallback only fires when the root
itself fails resolveGitIndexPath.
- Tests cover: fallback when the workdir is a multi-repo holder,
no-fallback for a real git repo, no-fallback when no git child
exists.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(multi-repo): per-repo workspace tracking — Changes panel shows all repos
The agent's workspace tracker is now split per-repository for multi-repo
task roots. Each repo gets its own tracker that polls git independently
and stamps its repository name onto every emitted event. The Changes
panel aggregates files from every repo and groups them under per-repo
headers using the file-tree machinery from Phase 6.
Backend (agentctl):
- GitStatusUpdate / FileListUpdate gain a RepositoryName field. Empty
for single-repo workspaces (legacy clients see no change).
- WorkspaceTracker carries repositoryName and stamps it onto every
emitted status/file-list. Two constructors: NewWorkspaceTracker for
the legacy single-repo path (with the bare-root fallback) and
NewWorkspaceTrackerForRepo for explicit per-repo trackers.
- Manager scans direct child directories for git repos at construction.
When 2+ repos are found, builds one tracker per repo subdir on top of
the root tracker (which stays bound to the bare task root and emits
nothing useful). Single-repo workspaces are unchanged.
- Manager.SubscribeWorkspaceStream / UnsubscribeWorkspaceStream fan out
one client subscription across the root + all per-repo trackers via
AttachWorkspaceStreamSubscriber / DetachWorkspaceStreamSubscriber on
the tracker. The WS handler switched from the single-tracker subscribe
to the manager fan-out.
- Tests cover: per-repo trackers spawn for multi-repo task roots,
single-repo doesn't, both repos emit GitStatusUpdates tagged with
RepositoryName.
Frontend:
- GitStatusEntry / GitStatusData carry repository_name (optional).
- session-runtime gitStatus.byEnvironmentRepo new map keyed by env id
then repository name; setGitStatus routes per-repo updates into it
while still updating the legacy byEnvironmentId for single-repo
consumers. clearGitStatus drops both maps.
- migrateEnvKeyedData migrates the new per-repo map alongside the
existing keys when sessionId resolves to an environmentId.
- WS git-status handler forwards repository_name from event.status.
- New useSessionGitStatusByRepo hook returns sorted [{repo, status}]
pairs for the active session.
- task-changes-panel mergeReviewFiles iterates statusByRepo when
available, stamping repository_name onto every uncommitted file so
the Changes panel groups them under per-repo headers (using the
ReviewFile.repository_name plumbing from Phase 6).
- Tests: per-repo routing, no overwrite on sibling repo update,
legacy single-repo fallback, clear-on-cleanup.
Verified: pnpm exec vitest run 623/623, eslint --max-warnings 0 clean,
tsc --noEmit clean, make test (backend) clean, make lint 0 issues.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/changes-panel): per-repo group headers above each repo's files
The Changes panel now renders a sticky "frontend / N files" header above
the diffs from each repository so the user can visually distinguish
where each change lives. Single-repo tasks render unchanged (no header
shown when there's only one unnamed group).
- ReviewDiffList groups its file list by repository_name (preserving the
caller's order within each group), then renders one RepoGroupHeader
per group followed by the existing FileDiffSection list. Header gating
rule: show when len(groups) > 1 OR the single group has a non-empty
repository name.
- Group key uses "repo:path" so a file with the same path in two repos
no longer collides on React's reconciliation.
- groupFilesByRepository + RepoGroupHeader live in a sibling
review-diff-list-groups.tsx file to keep review-diff-list.tsx under
the 600-line lint cap.
- Tests cover: no header for plain (no repo_name) files, one header per
repo for 2+ groups, header shown even for a single named repo, file
paths land under the right group's section.
Verified: vitest 627/627, eslint clean, tsc clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(lifecycle): propagate RepositoryName through PublishGitStatus
The agentctl WorkspaceTracker stamps every per-repo GitStatusUpdate
with RepositoryName, but the lifecycle PublishGitStatus translation
into GitEventPayload was dropping that field. The orchestrator then
forwarded a status with empty repository_name, the frontend store
routed everything into the empty-key bucket, and the Changes panel's
multi-repo grouping never triggered — no header, no per-repo split.
- GitStatusData gains RepositoryName (omitempty so single-repo
payloads stay byte-identical on the wire).
- PublishGitStatus copies update.RepositoryName onto the payload.
- Test asserts the field survives the publish → subscribe round trip
via the in-memory event bus on the GitEvent.<sessionID> subject.
Restart the agentctl process for any running multi-repo task to pick
up the new field; new launches get it automatically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(web/changes-panel): per-repo group headers in the dockview Changes tab
The Changes tab in the dockview panel (the Files / Changes (N) tab the
user actually interacts with — not the standalone Review dialog) was
still rendering a flat file list, so multi-repo task changes either
collided onto the legacy single-status (showing only one repo's files)
or rendered without grouping.
- FileInfo gains repository_name (optional, single-repo unchanged).
- useSessionGit aggregates files from useSessionGitStatusByRepo when
multi-repo data is present, stamping each FileInfo with its
repository_name. Falls back to the legacy single status for
single-repo workspaces. Aggregation extracted into
aggregateFilesAcrossRepos to keep useSessionGit under the 100-line
function lint cap.
- ChangedFile (changes-panel.tsx + changes-panel-timeline.tsx) carries
repositoryName through to the renderer.
- FileListSection delegates to a new FileListBody that groups rows by
repositoryName; renders a small per-repo subheader when 2+ repos OR
any single named repo is present, otherwise falls back to the
pre-multi-repo flat list (same DOM shape).
- Tests cover: no header for plain (no repo_name) files, headers
per repo for 2+ groups, single named repo still gets a label.
Restart the backend so the per-repo trackers spawn for in-flight
multi-repo tasks; new launches are unaffected.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(multi-repo): per-repo git ops, unified branch endpoint, chip-style task create
Bundles a string of multi-repo fixes and refactors that came up while
testing the feature end-to-end:
- workspace_path stored on multi-repo TaskEnvironment was one level too
high, so agentctl booted with the wrong WorkDir on cold start; fixed
in resolveTaskEnvWorkspacePath + GetWorkspaceInfoForSession.
- Stage/Unstage/Discard/Commit/Revert/Reset/Amend now thread `repo`
through agentctl client → WS handler → frontend hook → SessionGit
fan-out, so the right git repo is targeted (was running at the task
root, which isn't a git repo for multi-repo tasks).
- agentctl GitLog is multi-repo aware: when no `repo` is specified and
per-repo trackers exist, fans out one log per repo and merges sorted
newest-first with RepositoryName stamped on each commit.
- Per-repo grouping for files (Changes panel) and commits, with
collapsible group headers; commits section defaults to collapsed.
Algorithm extracted into one generic groupByRepositoryName helper
(was copy-pasted three times).
- Task creation dialog rewritten around a unified `fs.repositories[]`
list — no more "primary vs extras" split. Chip-style row with one
pill per repo + branch, "+" to add, x to remove, GitHub URL toggle
inline. Discovered (on-machine) repos appear in any chip's dropdown.
Removes ~6 fields and 3 helpers from the dialog state.
- Branch listing endpoints collapsed to one:
GET /workspaces/:id/branches?repository_id=X | path=Y
Frontend collapses to a single useBranches hook with a tagged-union
source. Drops two HTTP routes, two service methods, two API client
funcs, and one whole hook file.
Tests added across the chain: workspace_path resolver, sort-commits-
by-date, manager poll-mode propagation, RepoSubpaths, chip grouping
+ collapse + per-repo isLatest, group-by-repo helper, dialog payload
builder + chip wiring + discovered-repo flow.
* feat(multi-repo): unified per-repo git controls for all VCS operations
Changes panel and top-bar VCS button render per-repo Stage all, Commit,
Unstage all, Push, Pull, Rebase, Merge, and Create PR uniformly across
single-repo and multi-repo workspaces. Operations route to the correct
git repo subpath; fan-outs collect per-repo results and surface partial
success in the toast layer.
Backend:
- Stamp RepositoryName on Reset/BranchSwitch/FileChange notifications
and forward through orchestrator handlers
- Surface per-repo errors from handleGitLogMultiRepo via PerRepoErrors
Frontend:
- pull/push/rebase/merge/abort/commit fan out via PerRepoOperationResult
- Per-repo group headers always render with action buttons
- Top-bar dropdown lists per-repo Pull/Rebase/Merge entries
- pendingStageFiles keyed per-repo to prevent cross-repo clears
- useRepoDisplayName distinguishes loading from multi-repo
- Commit dialog file summary respects stageAll
- New helpers: changes-panel-helpers.ts, changes-panel-repo-groups.tsx,
use-repo-display-name.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(multi-repo): plumb repo through file/commit/diff endpoints; per-repo PRs and review UI
- backend: thread `repo` subpath through pull/push/rebase/merge/abort, file
content/update, cumulative-diff, and commit-show endpoints; per-repo
fan-out for log + cumulative-diff with each repo's own merge-base; emit
full `repositories` array on task.updated WS payload
- github: store/list TaskPRs as []*TaskPR (per-repo); orchestrator/web store
rekey to `Record<taskId, TaskPR[]>`
- web: aggregate PR icon (count badge + worst-of color, multi-PR tooltip);
branch hover card renders one row per repo with each repo's task base
branch; review dialog merges per-repo file maps with composite keys and
propagates repository_name through diff/expand/revert; commit-detail
panel scopes to the owning repo
- kanban card: chip row of repo names (up to 2 + "+N" overflow, tooltip
lists all)
- task-create dialog: gate non-worktree executors to multi-repo only; move
fresh-branch toggle inline next to the repo chips (icon-only, with
title/aria-label)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(multi-repo): scope PR watches/associations and review UI per repository
Schema: add repository_id to UNIQUE constraints on github_pr_watches and
github_task_prs (with idempotent copy-and-rename migration) so one task
spanning N repos can hold one watch/PR per repo without collisions.
Plumb repository_id through CreatePRWatch/EnsurePRWatch/AssociatePRWithTask
/AssociatePRByURL, fan TriggerPRSync across all per-repo watches, and
disambiguate status sync by (task, owner, repo, pr_number) instead of
"first match". Add Repo to wsCreateFile/Delete/Rename and wsCreatePR/
RenameBranch so file and git ops target the right working tree.
Frontend review dialog: switch reviewedFiles, staleFiles, fileRefs,
selectedFile and comment counts to NUL-separated `${repo}\x00${path}`
keys so same-named files in different repos no longer share state.
useTaskPR consumes the new {prs: TaskPR[]} envelope.
Also: snapshot repoTrackers slice before background refresh in
SetWorkspacePollMode to prevent races with Stop(), and key
mergeCumulativeFiles by NUL-separated (repo, path) to match.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: apply prettier formatting
Unblock CI prettier check on files modified earlier on this branch
that pre-date the current prettier config.
* fix: address PR review feedback and unblock E2E
Backend:
- ListWorkspaceTaskPRs background sweep now uses TriggerPRSyncAll so
multi-repo tasks refresh every per-repo PR (was only the most recently
updated watch). Caught by claude review on #767.
Frontend (no behaviour change):
- Replace literal NUL byte (0x00) in FILE_KEY_SEP and the inline key
builder in use-review-dialog with the explicit escape so the
source is plain ASCII. The runtime separator is unchanged; this just
removes a footgun that GitHub's diff renders as a visible space.
- Show the auto-resolved branch as a small read-only pill next to the
GitHub URL input so the URL-mode dialog surfaces the branch the task
will run on (previously only the URL was visible).
E2E:
- changes-panel-focus / pr-detection / pr-switcher-changes: expand the
commits-section before asserting on commit text -- the section starts
collapsed since the chip-style refactor.
- repository-selector-scroll: open the popover via repo-chip-trigger
instead of the legacy single repository-selector testid.
* test(e2e): align task-create dialog tests with chip refactor
Updates three more tests that broke when the dialog migrated from a
single repository/branch combobox to per-row repo/branch chips and the
URL-mode toggle relabel:
- create-task.spec.ts (pre-select + remembers-selections): switch
repository-selector / branch-selector lookups to repo-chip-trigger /
branch-chip-trigger.
- create-task-github-url.spec.ts (toggle test): the inverse-state label
is now "use a workspace repository", not the legacy
"or select a repository".
No code changes — purely test alignment with the existing UI.
* fix(web): re-apply workbench-chrome topbar + restore branch-selector behaviour
Two regressions surfaced by the multi-repo merge into main:
1. task-top-bar.tsx had been resolved in our favour at the workbench-chrome
merge (#761), losing the TopbarActionOverflow wiring that the new test
asserts on. Re-applies main's topbar structure (TopbarCluster groups,
TopbarActionOverflow, IntegrationsMenu, MoreToolsMenu, SettingsButton)
on top of our multi-repo additions: repositoryCount + RepositoryBreadcrumb
"+N" chip, gitStatus + isMultiRepo plumbing, and the GitAheadBehindBadges
suppression for multi-repo. GitAheadBehindBadges moved to its own file to
stay under the file-length cap.
2. The chip-style task-create dialog dropped the legacy branch-selector
semantics. Restored:
- Local-executor branch lock: the per-row branch chip is now disabled
when isLocalExecutor and fresh-branch is off. The user's checkout
dictates the branch for local execution; fresh-branch unlocks it.
- Branch override: when locked, the chip shows currentLocalBranch
instead of the value the user picked under a different executor
(so worktree -> local doesn't display a stale "develop" pick).
- Branch refresh: useBranches now exposes a refresh() callback for
id-based sources (calls listRepositoryBranches with refresh=true),
and Pill renders BranchRefreshButton in its popover header when
onRefresh is wired.
- URL-mode branch picker: GitHubUrlSection now renders a real
branch-chip-trigger Pill backed by fs.githubBranches instead of a
read-only span, restoring the "branch selectable in URL mode"
behaviour (URL mode is always remote-clone, so the local-executor
lock does not apply).
E2E spec updated: bulk-rename legacy branch-selector / repository-selector
testids to branch-chip-trigger / repo-chip-trigger.first(). All 13
create-task-branch-selector tests pass locally.
* ci: force workflow re-run
* fix(web): unblock E2E shard 2
Three issues showed up only after the merge with main, which brought back
the E2E shard 2 tests touching git/changes-panel and improve-kandev:
1. Wire the TaskCreateDialog slot props (extraFormSlot, aboveDescriptionSlot,
bottomSlot, descriptionPlaceholder) through to DialogPromptSection. They
were declared on the public API but dropped on the floor before this
change, so Improve Kandev's contributor banner ("@octocat" /
"@kandev-maint") never rendered. Also gate the "or paste a GitHub URL"
toggle on lockedFields.repository so the locked-repo dialog hides it
(matches main's behaviour and the existing test expectation).
2. Revert "commits section starts collapsed" (introduced in
|
||
|
|
1eae869054 | feat: add improve kandev in-app contribution flow (#740) | ||
|
|
9f1e372967 | feat: refresh + smarter filter for new-task branch selector (#750) | ||
|
|
7b5b3f329d |
feat: add Linear integration (#736)
* feat: add Linear integration
Mirrors the existing Jira integration so a workspace can connect to Linear
with a personal API key, browse and search issues, and start a Kandev task
from any issue. Backend uses Linear's GraphQL API with a workspace-scoped
config + secret store and a 90s auth-health poller; the frontend adds a
settings page, an issue dialog with state transitions, link/import buttons
in the task UI, and a paginated /linear browse page with a Start task flow.
* fix(linear): address PR review feedback
- Route httpListStates through Service.ListStates instead of clientFor
- Extract repeated workspace_id required string into a constant
- Log GetConfig errors during credential resolution instead of dropping them
- Clear popover error state on close in import + link buttons
- Pick the first workflow with steps in the quick task launcher (don't hard-pin to workflows[0])
- Reset useLinearEnabled state on workspace change via tag-based pattern
- Add cursor-pointer to anchor + Link elements per project convention
- Add tests: handlers_test.go, linear-api.test.ts, use-linear-enabled.test.ts, linear-issue-common.test.tsx
- Document internal/jira and internal/linear in CLAUDE.md/AGENTS.md
* fix(linear): address second-round PR review feedback
- Round Linear's fractional state position instead of truncating it via
int(math.Round(s.Position)) so adjacent fractional positions don't collapse
- Force LinearSettings to remount on workspace change with key={id} so the
previous workspace's config / form / testResult / teams cannot bleed into
the next workspace before the new fetches settle
- Broaden the legacy-value useLinearEnabled test to cover "true", "1", "yes",
and "legacy" so a regression to strict value === "true" would fail
* fix(linear): address third-round PR review feedback
- Propagate context.Canceled / context.DeadlineExceeded from ListStates in
GetIssue instead of swallowing them — a caller cancellation must not return
a partial-success response
- Drop the one-off contains() test helper in handlers_test.go and use
strings.Contains directly so the assertion is self-documenting
- Add getLinearIssue URL-shape test that locks identifier-as-path-segment
vs workspaceId-as-query-param so accidentally swapping them would fail
* test(linear): extract repeated literals in linear-api.test.ts
Pulls "ws-1", the API base URL, and the auth-method literal into module-
level constants so the assertions read as the route shape they care about
and the duplicate-strings ESLint rule (currently warn, threshold 4)
stays quiet as the file grows.
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
|
||
|
|
ec9637c517 | feat: opt-in fresh-branch checkout for local executor task creation (#695) | ||
|
|
54b98ed9eb |
feat(jira): add Jira integration for ticket browsing, import, and task linking (#705)
* feat(jira): add Jira integration for ticket browsing, import, and task linking Adds a workspace-scoped Atlassian Cloud integration: per-workspace settings (site URL, email, API token or session cookie), encrypted secret storage, and a REST client for tickets, projects, search, and transitions. A background poller probes credentials on a 90s cadence and the keepalive runs immediately on save so the UI's connected indicator updates without delay. The /jira page provides ticket browsing with filter pills, custom JQL, saved views (including JQL), and configurable task-launch presets that prefill TaskCreateDialog. JiraLinkButton on the task top bar links an existing task to a ticket by prepending its key to the title; the ticket popover surfaces details, transitions, and re-auth prompts on session expiry. Topbar shows separate icon buttons for GitHub and Jira when each is configured. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): address PR review feedback - Token-based pagination (pageToken/isLast) replaces broken offset/total pagination - Race-fix in service.clientFor: re-check cache after acquiring write lock - sync.Mutex guards poller started/cancel state against concurrent Start/Stop - Channel-based + atomic.Int32 test sync replaces time.Sleep polling in poller_test - Async auth probe on SetConfig with detached context.Background goroutine - Distinguish 4xx vs 5xx in httpSetConfig via ErrInvalidConfig sentinel - Propagate 4xx APIErrors from ListTransitions in GetTicket - Distinguish "not found" from real errors in jiraSecretAdapter.Exists - Log secret Exists errors instead of swallowing - walkADF: handle softBreak (was only hardBreak despite the doc-comment) - Consolidate JIRA_KEY_RE in jira-ticket-common; remove duplicates - ticket-row imports formatRelative/statusBadgeClass from common - isFilterState type guard validates SavedView.filters shape - task-presets readStorage preserves user's "cleared all" intent (returns []) - task-presets-section gates draft sync on `loaded` to avoid wiping in-progress edits - jira-api: caller `init` no longer overrides method/body via spread order - jira-settings: disable Test button when no saved/typed secret Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): record auth probe failure even when probe ctx expires - RecordAuthHealth now uses a fresh context.Background-derived ctx with a 5s timeout for the UpdateAuthHealth DB write, so a probe that consumes its full 15s deadline still persists the failure (greptile P1). - SetConfig goroutine drops its redundant outer timeout — RecordAuthHealth manages its own probe timeout, so context.Background is enough. - Add Service.SetProbeHook so tests can synchronize on probe completion; service_test waitForAuthProbe now blocks on the hook channel instead of time.Sleep polling. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): address coderabbit review feedback (round 3) - url.PathEscape ticketKey in GetTicket/ListTransitions/DoTransition paths to harden against malicious keys ('../../foo', 'KEY?override=x'). - factoryHit on the test fixture is now sync/atomic.Int32; the async probe goroutine spawned by SetConfig races the test thread under -race. - GetConfig logs Reveal failures (was silently dropped, leaving the UI to show "no expiry info" indistinguishably from a freshly-saved cookie). - DeleteConfig logs secret-delete errors instead of swallowing them. - clientFor distinguishes a secret-store failure from "not configured" so the UI doesn't render the configure-CTA for transient backend errors. - Unify ErrNotConfigured wire code: HTTP and WebSocket both return "JIRA_NOT_CONFIGURED" via a shared constant. - JiraLinkButton strips a leading "PROJ-123: " before re-prefixing so re-linking a task replaces the prefix instead of stacking it. - jira-settings clears testResult on save/delete so the alert reflects only the currently-saved credentials. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(jira): cover Reveal error path in clientFor Reveal-error case is distinct from "no config row" — the former wraps the underlying error, the latter returns ErrNotConfigured. Without this test the changed contract has no regression coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): route secret adapter Set through Exists Treating every Get error as "not found" turned a transient DB error on an existing row into a constraint-violation Create that masked the real cause. Reuse Exists so the "secret not found:" prefix is the only path that falls through to Create. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test(jira): cover jiraSecretAdapter.Set branching Routing Set through Exists changed the contract: a transient DB error in Get now propagates instead of cascading into a constraint-violation Create. Add three tests so a future refactor cannot regress this without CI catching it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(jira): split Reveal errors and guard Save for new configs - resolveCredentials now mirrors the clientFor split: a Reveal error surfaces as `read jira secret: %w` with a warn log instead of collapsing into "no token stored — paste one to test". - disableSave gains the same missing-secret guard as disableTest so a brand-new config can no longer POST an empty secret and leave the workspace stuck with hasSecret=false. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5f1c4a13e4 | fix(web): enable Start task button when workflow provides agent override (#665) | ||
|
|
31140c8b9f | fix(web): follow workflow step session switches in chat UI (#625) | ||
|
|
a222854422 | feat: associate agent profiles with workflows and steps (#597) | ||
|
|
204fac1cca | feat: acp-first profiles, models, and modes (#566) | ||
|
|
afed5a8f85 |
fix(web): stop discarding branch selection for local executor (#558)
Commit
|
||
|
|
348ee25e6f | feat: disable branch selector for local executor and implement base branch checkout (#515) | ||
|
|
d1a028fdb6 | feat: introduce subtasks, allow sessions to task reuse executor (#419) | ||
|
|
b1c848ed80 | fix(ui): persist task creation draft when modal closes (#455) | ||
|
|
21cb909ef4 | feat: improve git checkout with error classification and warning propagation (#386) | ||
|
|
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 |
||
|
|
44811da702 | feat: web e2e tests (#304) | ||
|
|
32f7ceb319 | feat: several UX improvements (#272) | ||
|
|
a23ab5be3a | feat: remote executors + secrets (#257) | ||
|
|
61ef224ec7 | style: fix format issues (#255) | ||
|
|
53ae8d57e3 | fix: resolve all web app ESLint linter warnings (#246) | ||
|
|
90bec9aaa0 | feat: update readme & agent TUI reliability (#239) | ||
|
|
2b05ed0735 | feat: improved passthrough and workflow (#237) | ||
|
|
658bd349b5 | feat: improve workflows (#228) | ||
|
|
81dc7ad827 |
feat: auggie dynamic model list (#223)
feat: agent logos |
||
|
|
f4d1dfd3a7 |
feat: better context files (#222)
feat: context files |
||
|
|
ea751d3ecc | feat: improve task creation (#220) | ||
|
|
813bfd1215 | feat: dockview new ui (#219) | ||
|
|
0a2ed53327 |
feat: improve plan mode (#212)
feat: plan mode |
||
|
|
a70de7da11 | feat: local executor and worktree + new task dialog (#211) | ||
|
|
3196a02220 | feat: file icons (#207) | ||
|
|
0de3c31ddc | feat: mobile design (#160) | ||
|
|
14a7601360 | feat: improve kanban board ui (#155) | ||
|
|
c163b61b7d | feat: all tasks page plus search (#140) | ||
|
|
15e33515d2 | feat: improve task creation dialog (#135) | ||
|
|
6d23d68633 | feat: improve chat ui messages (#132) | ||
|
|
3f7154e186 | feat: chat messages normalized (#121) | ||
|
|
0b573bbdda | feat(workflow): implement workflow system with steps terminology (#102) | ||
|
|
187d002717 | feat: per-task executor selection with multi-runtime support (#92) |