Commit Graph

187 Commits

Author SHA1 Message Date
Carlos Florêncio 27e7e0776a fix: preserve managed GitHub tools across login shells (#2141) 2026-08-02 11:18:32 +01:00
Carlos Florêncio e466bda55f fix: preserve service identity and resume credentials (#2121) 2026-08-01 11:19:24 +01:00
Carlos Florêncio 3760f12939 feat(editors): scope embedded VS Code by executor (#2059) 2026-07-30 22:24:16 +01:00
Clément Fiere 04d2806645 fix: resume archive-cancelled sessions without a running row (#2041) 2026-07-30 11:22:53 +01:00
Carlos Florêncio adecad7f30 fix(github): honor executor clone transport (#1998) 2026-07-28 15:46:28 +01:00
Carlos Florêncio 35d1a85d74 fix: recover failed agent resumes and managed runtimes (#1991) 2026-07-28 09:23:30 +01:00
Carlos Florêncio b39a668e99 feat(github): add task git credential policy (#1985) 2026-07-27 18:10:12 +01:00
José Almeida 5df06ad138 fix(orchestrator): resume manually stopped sessions (#1979)
* fix(orchestrator): resume manually stopped sessions

* fix(orchestrator): preserve pre-lock cancellations
2026-07-27 16:24:41 +01:00
Carlos Florêncio 49e00447a5 fix: restore legacy GitHub launch compatibility (#1962) 2026-07-26 19:07:46 +01:00
João Salavisa bb72d70586 refactor(backend): remove dead code and deprecated shims (#1965)
Delete unreachable/unused backend symbols confirmed by staticcheck U1000
and golang.org/x/tools/deadcode, plus deprecated back-compat aliases kept
alive only by their own tests.

Removed (unused):
- parseGitHubRepoURL, syncTaskStateForBulkWorkflowMove (task/service)
- resolvePrimaryRepoInfo (orchestrator/executor)
- resolveWorkflowAgentProfile, workflowDefaultAgentProfile wrappers (mcp/handlers)
- cleanupExitedContainer (runtime/lifecycle) + orphaned fmt import
- VscodeManager.setEnv (agentctl/server/process)
- repoRootDir const (cmd/preview)

Removed (deprecated shims):
- Canceller.CancelSessionAndNotify alias -> callers use DetachSessionAndNotify
- ScriptPlaceholder type alias (live scriptPlaceholders var kept)
- Placeholder{QueueRun,ClearDecisions,QueueRunForEachParticipant}Callback
  no-op structs and their self-referential test

No behavior change; build, vet, and affected-package tests pass.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 17:54:12 +01:00
Carlos Florêncio 75abf779ed feat(github): support per-workspace GitHub App registrations (#1810) 2026-07-26 10:39:08 +01:00
Carlos Florêncio bc218dfbbe feat: add workspace sources from files panel (#1900)
* feat: add workspace sources from files panel

* fix: preserve worktree identity during recovery

* feat: unify workspace source actions

* fix: validate recovered workspace repositories

* fix: harden workspace source filesystem boundaries

* test: split workspace repository chip coverage

* fix: restrict repository rescan recovery paths

* fix: stabilize workspace source CI checks

* test: canonicalize workspace source paths

* test: align workspace source container paths

* fix: populate direct workspace repository names

* test: preserve git fixture rewrites across restarts

* test: isolate backend git fixture config

* fix: resume remote workspace executions before creation

* fix: preserve ssh agent identity during resume

* fix: preserve SSH workspace across backend restart

* fix: simplify workspace actions trigger

* fix: refine add workspace repositories UX

* docs: narrow workspace source UX updates

* fix: avoid phantom turn on session resume

* test(web): normalize workspace source request headers

* feat(web): streamline workspace source picker

* docs: remove trailing spaces from workspace source plan

* fix: prevent resume status from creating phantom turns

* fix(web): stabilize workspace source attachment

* refactor(web): extract workspace source store contract

* fix: avoid phantom turns on idle agent resume

* fix: address backend static analysis findings

* fix: preserve agentctl readiness status value

* fix: preserve resume messages without active turns

* fix: resume agents from promoted workspace root

* chore: merge origin/main into feature/add-new-repo-folder-d97

* feat(web): explain workspace source consequences

* test(websocket): count session activity subscription
2026-07-25 21:29:53 +01:00
point-source dba2057590 feat: accept input while a session's foreground turn waits on background work (#1668) 2026-07-25 18:08:35 +01:00
Clément Fiere 3d7b2f32a7 fix: resume an unarchived task's archive-cancelled and multi-repo sessions (#1905) 2026-07-23 21:25:42 +01:00
Carlos Florêncio 73bf0fb028 fix: improve session failure recovery UX (#1866) 2026-07-23 20:02:20 +01:00
Clément Fiere 7a044b7e02 fix(task): dedupe repository listing and close local-path create race (#1897) 2026-07-23 17:46:25 +01:00
Carlos Florêncio d475cdf597 fix: scope Office agent tools and task mutations (#1867) 2026-07-23 16:31:37 +01:00
Carlos Florêncio edee5a03cf fix: scope PR branch failure guidance (#1873) 2026-07-23 14:45:01 +01:00
Carlos Florêncio c041dbee0e fix(orchestrator): separate office task session ownership (#1893) 2026-07-23 14:12:58 +01:00
Clément Fiere 26c38b02e1 fix(backend): publish session state_changed when archiving cancels sessions (#1891) 2026-07-23 14:08:41 +01:00
João Salavisa 0f277457e9 fix(executor): re-clone provider repos when local path is missing or not a git repo (#1876)
* fix(executor): re-clone provider repos when local path is missing or not a git repo

Previously the re-clone guard only fired when local_path was empty, so a
set-but-invalid path (e.g. deleted checkout, or relocated by the provider
clone-path/identity change) was fed straight to the worktree preparer,
which hard-failed with "repository is not a git repository" instead of
recovering.

Two-part fix:
- resolveTaskRepoInfo now also re-clones when local_path exists but isn't
  a valid git checkout (new isLocalGitRepo helper), self-healing on next
  launch.
- validateWorktreeRequest now fails the "Validate repository" step with an
  honest error naming the offending path when it truly isn't a git repo,
  instead of passing and cascading into a misleading "sync interrupted"
  failure downstream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(executor): gate stale-path reclone to provider repos and cover resume + multi-repo

Restrict the stale-local-path re-clone to genuinely provider-backed repos
(SourceType != "local") so a local checkout that's temporarily missing is
never silently overwritten. Centralize the re-clone guard into a shared
ensureRepoLocalPath helper and call it from the single-repo resume path
(applyResumeRepoConfig) so resume self-heals like a fresh launch. Apply the
same non-git-path validation used by the single-repo prepare path to the
multi-repo prepareOneRepo path so a stale secondary repo fails fast with a
clear message instead of surfacing worktree.Manager's ErrRepoNotGit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:13:09 +01:00
João Salavisa 78cae96bb1 feat(plugins): host-side conversation reads + utility-agent invoke (#1852)
* feat(plugins): add capability-gated Host reader for conversation content

Plugins can now read historical user/agent message content over the Host
data API, gated by a new `api_read:messages` capability (ADR 0047). This
unblocks "summarize yesterday"-style plugins that the live `message.added`
bus event alone cannot serve.

- proto: `ListMessages` RPC + `Message`/`MessageFilter` on `service Host`.
- SDK: `Host.Messages() MessageReader`, Go-native `Message`/`MessageFilter`.
- host: `api_read:messages` accessor + denied reader; content sanitized via
  `sysprompt.StripSystemContent` (no raw system prompts); RFC3339 `since`/
  `until` → `InvalidArgument` on bad input.
- data: new `ListMessagesForPlugin` filtered query (session/task ids, type,
  created_at range) through the task service, never the DB file.
- tests: in-process + wire capability gating, content stripping, filter/time
  passthrough, pagination, and the SQLite query.
- docs: ADR 0047, spec, plugins-manifest, plugins-authoring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugins): add Host.InvokeUtilityAgent for settings-selectable utility agent

Plugins can now delegate a one-shot LLM step to an operator-configured agent,
gated by a new `agent_invoke` capability (ADR 0048), so a plugin needs no
provider API key of its own.

- setting: new `utility_agent_profile_id` user setting (Settings > System >
  Utility Agent), a profile dropdown cloned from the existing utility-agent
  path; selectable in the UI.
- capability: boolean `agent_invoke`, enforced like `state`/`secrets`.
- proto/SDK: `InvokeUtilityAgent(prompt) -> text` RPC and
  `Host.InvokeUtilityAgent(ctx, prompt)`.
- backend: reuses the sessionless host-utility inference tier (ADR 0002) via a
  thin adapter — no agent loop, no task/session/workspace; resolves the
  configured profile to (agent type, model). Unconfigured / deleted profile ->
  gRPC FailedPrecondition.
- tests: capability gating, completion path (asserts the configured profile is
  used and text returned), no-utility-agent and stale-profile error paths,
  SDK/wire round-trips, and the settings apply/trim behavior.
- docs: ADR 0048, spec, plugins-manifest, plugins-authoring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): resolve utility-agent deps live and bound message filter size

Address CI + review feedback on the host plugin capabilities:

- docs: register the new /settings/system/utility-agent route in
  docs/public/coverage.json (fixes the "Validate public docs" CI failure).
- utility agent (ADR 0048): hostUtilityMgr is wired late in boot — after
  StartActivePlugins has already spawned boot-active plugins — so snapshotting
  the utility deps in hostForPlugin stranded those hosts with nil deps and made
  InvokeUtilityAgent return Unimplemented for their lifetime. Read the deps live
  from the Service (mutex-guarded) at call time instead; add a regression test
  for late wiring reaching an already-spawned host.
- messages (ADR 0047): cap the combined session/task/type filter values at 400
  so an oversized filter fails fast with InvalidArgument instead of hitting
  SQLite's host-parameter limit at query execution; add a test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 00:20:09 +01:00
Carlos Florêncio 7fd99c3f33 feat: complete GitLab integration parity (#1832) 2026-07-21 23:24:22 +01:00
Carlos Florêncio be56eff1a2 fix: reset office session state when execution profile changes (#1846) 2026-07-21 21:37:08 +01:00
Carlos Florêncio c2a678b023 feat: add Azure DevOps integration (#1778) 2026-07-21 08:40:26 +01:00
José Almeida 3479fb601e fix(web): refresh walkthrough ranges on Monaco model changes (#1822)
* fix(web): refresh walkthrough ranges on Monaco model changes

* fix(web): avoid recentering walkthrough on line edits

* test(backend): isolate executor session snapshots

* test(backend): deep-copy executor session snapshots
2026-07-20 17:34:15 +01:00
José Almeida f7f1d10996 feat: add parent-controlled task stopping via MCP (#1803)
* feat: add parent-controlled task stopping via MCP

* fix: harden parent task stop lifecycle

* fix: preserve rejected task rollback state
2026-07-19 20:23:46 +01:00
Carlos Florêncio 4fdd679269 fix: attribute model configuration to turns (#1784) 2026-07-19 12:46:40 +01:00
Carlos Florêncio 21f811d5ec feat: add idle storage maintenance (#1699) 2026-07-18 19:08:44 +01:00
Clément Fiere 69972b4f79 fix: freeze archived task state against late REVIEW writes (#1706) 2026-07-18 17:30:24 +01:00
Carlos Florêncio 16da501fc4 feat(office): route agents through execution profiles (#1725) 2026-07-17 18:47:09 +01:00
Carlos Florêncio 30373ea40c fix: promote commandless workspace executions (#1692) 2026-07-14 23:14:37 +01:00
Carlos Florêncio ff2647bf13 feat: add configurable branch names (#1615) 2026-07-09 19:59:29 +01:00
Carl Sue 9722a39639 fix(ssh-executor): forward agent creds, fix terminal host config, restore linux/arm64 (#1559)
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-07-09 09:20:07 +01:00
point-source e1cf99f821 fix(executors): truthful executor rows, startup reconciliation, resume-safe cleanup (#1597) (#1608)
Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
2026-07-08 23:37:02 +01:00
Carlos Florêncio c18a13c361 feat(tasks): adopt Office-style /tasks list UX (#1593) 2026-07-03 22:56:13 +01:00
Carlos Florêncio 85950a4b69 fix: persist live executor runtime state (#1585) (#1587) 2026-07-03 15:39:06 +01:00
Carlos Florêncio 657b92eb69 fix: restore workspace deletion from settings (#1547)
* fix: restore workspace deletion from settings

* fix: validate workspace delete in service

* fix: clarify workspace delete errors

* fix: address workspace delete review feedback

* fix: guard confirmed workspace delete before cascade

* fix: simplify workspace delete cascade guard

* fix: make confirmed workspace delete atomic

* fix: preserve confirmed workspace delete side effects

* fix: snapshot confirmed workspace cascade rows

* fix: clarify confirmed workspace delete cleanup

* fix: preserve office workspace deletion cleanup

* fix: preserve workspace delete error mapping

* fix: streamline workspace delete cascade

* fix: harden workspace delete cleanup

* fix: keep workspace delete failure logs

* fix: fail workspace delete on missing session inventory

---------

Co-authored-by: root <root@kandev.ts.net>
2026-06-30 08:24:43 +01:00
Carlos Florêncio e51e0ebf3c fix: keep tasks active while sibling sessions run (#1551)
* fix: keep tasks active while sibling sessions run

* fix: avoid duplicate task review writes

* fix: clear completed sessions before review guard

* fix: serialize session launch state

* test: cover completed sibling session states

* fix: bound completed execution markers

* fix: amortize completed execution marker cleanup

* fix: fail closed on stale session starts

* fix: ignore stale completed execution events

* test: cover stale completed tool calls

* fix: harden stale session state guards

* fix: handle terminal execution races

* fix: avoid duplicate terminal complete side effects

* fix: harden terminal execution race guards

* fix: guard task review reconciliation

* fix: handle late terminal complete side effects

* fix: align review-state test guards

* fix: fail closed on stale review bridges

* fix: reduce review reconcile complexity

---------

Co-authored-by: root <root@kandev.ts.net>
2026-06-30 02:56:46 +01:00
Carlos Florêncio d0a032fa60 fix: prefer main for worktree base branches (#1532) 2026-06-29 14:17:14 +01:00
Carlos Florêncio 211e738ec0 fix: reuse worktrees for same-task handoffs (#1510) 2026-06-27 15:00:38 +01:00
Carlos Florêncio aee1846cd5 feat: remove nextjs production runtime (#1389) 2026-06-18 18:36:48 +01:00
Carlos Florêncio d8436dfd8f feat: improve passthrough composer and session recovery (#1411) 2026-06-17 19:01:42 +01:00
Carlos Florêncio 8d0493d1a4 fix: persist session runtime config (#1346) 2026-06-12 08:56:14 +01:00
Carlos Florêncio 64140ae942 fix(acpdbg): read claude agent models and modes from configOptions (#1310) 2026-06-08 22:44:02 +01:00
Carlos Florêncio 3e92d3abc6 fix: allow office mcp mode for existing tasks (#1315) 2026-06-08 21:22:30 +01:00
José Almeida 4601dbc2d2 feat(tasks): search tasks by PR number in command panel (#1268)
* feat(tasks): search tasks by PR number in command panel

Typing a PR number (e.g. 1243 or #1243) in the Cmd+K panel now surfaces
the task that PR is associated with, alongside the existing
title/description/repo search.

The github store gains a workspace-scoped ListTaskIDsByPRNumber lookup
over github_task_prs; the task service resolves it through an injected
PRTaskResolver interface (github service wired in cmd/kandev) and merges
PR matches into search results, deduped and respecting the
archived/ephemeral/config filters. Keeping the lookup at the service
layer avoids coupling the task repository to the github schema, so
existing search queries and tests are untouched.

* fix(tasks): scope PR-number search to page 1 and unfiltered queries

Address review feedback on PR-number search:

- Only augment results on the first page of an unscoped search. Running
  on every page re-prepended the PR match and truncated a real result
  off each subsequent page; running under a workflow/repository filter
  leaked tasks that don't satisfy the filter.
- Add idx_github_task_prs_pr_number so the lookup is index-backed
  instead of a full scan (pr_number is the 3rd column of the existing
  UNIQUE constraint, unusable as a leading key).
- Log instead of silently dropping a task when GetTask fails in the
  PR-match loop.

* test(github): dedup PR-lookup test setup into shared newTestStore

Add workspace_id to newTestStore's tasks table and drop the near-identical
newTestStoreWithWorkspaces helper, per review feedback.

* perf(tasks): batch-fetch PR-matched tasks instead of N+1 GetTask

Add a repository GetTasksByIDs and use it in augmentWithPRMatches so PR-number
search loads all matched tasks in one query instead of one GetTask per id,
addressing the N+1 noted in review. Fetch+filter is extracted into a
fetchPRMatchedTasks helper that preserves resolver order and dedup.
2026-06-05 16:20:30 +01:00
Carlos Florêncio a8db7478ac fix(backend): cap gh/git fork concurrency via shared subproc throttles (#1216) 2026-06-01 20:35:43 +01:00
João Salavisa fd79d6170f feat(tasks): multi-branch tasks — N (repo, branch) pairs per task (#1226)
* feat(tasks): multi-branch tasks — N (repo, branch) pairs per task

Extends multi-repo into multi-branch so a single task can hold multiple
worktrees on the same repo (one per branch). Sibling worktrees layout at
<task>/<repo>/ + <task>/<repo>-<slug>/, dedicated PRWatch per branch,
live agentctl tracker rescan + frontend store updates so the UI splits
PRs / files / commits per branch without a session restart. MCP
add_branch_to_task_kandev tool is shielded to the worktree executor.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(web): unify multi-branch repo label as <repo> · <branch> across panels

PR Changes and Commits panels rendered repo labels differently for multi-branch
tasks — Commits showed "kandev · feature/x" while PR Changes leaked the raw
"kandev-feature-x" agentctl tracker tag. Reformat tracker-tagged repo names via
formatRepoLabel using the longest known-repo prefix so kandev-cli-* doesn't
collapse to kandev · cli-*.

* fix(tasks): address multi-branch PR review — data races, slug collisions, MCP error codes

Backend
- agentctl process.Manager: add repoTrackersMu RWMutex guarding repoTrackers
  + workspaceTracker. Reads snapshot under RLock; RescanRepositories builds
  trackers first, takes Lock for swap/append. Validates newWorkDir via
  os.Stat before publishing cfg.WorkDir.
- worktree.Manager: reject invalid explicit BranchSlug up-front; cache key
  uses sanitized wt.BranchSlug so read/write paths are symmetric.
- worktree.store: narrow isDuplicateColumnError to "duplicate column name".
- task service AddBranchToTask: slug-collision guard rejects feat/a vs feat-a.
- orchestrator resolvePushRepo: two-pass exact-name match before subdir
  prefix fallback so "backend" doesn't swallow pushes for "backend-admin".
- executor buildRepoSpecs: duplicate repos always get a non-empty BranchSlug
  (falls back to branch-N when both branch fields sanitize to empty).
- MCP handler: classifyAddBranchError maps service errors to NOT_FOUND /
  CONFLICT / VALIDATION_ERROR; add ErrorCodeConflict constant.
- MCP server: extract registerAddBranchToTaskTool, register in task mode
  only (external mode contract: config tools + create_task_kandev).
- workspace_rescan handler: reject malformed JSON with 400 instead of
  silently rescanning the current root.
- branch_materializer: split MaterializeBranch into prepare + finalize
  helpers to satisfy the 80-line cap.

Frontend
- existingPrUrl returns undefined when a task has >1 PR so per-repo View PR
  buttons take over (no silent linking to a sibling).
- formatRepoLabel checks exact known-repo match before prefix splitting so
  "kandev-cli" isn't mislabeled as "kandev · cli".
- Repo chip key now includes base_branch so siblings differing only on
  base render as distinct chips.

Docs
- multi-branch spec uniqueness wording aligned with the canonical 4-column
  key (task_id, repository_id, base_branch, checkout_branch).
- worktree.CreateRequest.BranchSlug doc describes the sibling layout
  (<repo>-<slug>) instead of the obsolete nested form.

* fix(tasks): scope slug-collision check to repo only + use errors.Is for ErrTaskNotFound

- service_branches: drop base_branch from the slug-collision check. Worktree
  path is (repo, slug); two rows on the same repo cut from different bases
  still collide on disk.
- service_branches: prepareBranchAdd wraps taskrepo.ErrTaskNotFound via
  fmt.Errorf with %w so classifiers don't have to substring-match the
  formatted task UUID.
- mcp handlers: classifyAddBranchError uses errors.Is(err, ErrTaskNotFound).

* fix(agentctl): sanitize rescan work_dir before os.Stat to clear CodeQL path-injection

CodeQL flagged the rescan endpoint as a path-injection sink (the
HTTP-supplied work_dir flowed straight into os.Stat and downstream
scanRepositorySubdirs). Add validateRescanPath which:
- requires the input to be a clean absolute path with no ".." segments
- rejects anything that isn't equal to or an ancestor of the current
  cfg.WorkDir — the only legitimate use case is the branch materializer
  promoting <task>/<repo>/ to <task>/

Defense in depth: agentctl binds to localhost, but any colocated process
could otherwise direct it at /etc or the user's home by POSTing
/api/v1/workspace/rescan.

* fix(agentctl): tighten validateRescanPath, close TOCTOU on existingTrackers, add tests

- Drop the strings.Contains(clean, "..") guard. filepath.Clean already
  resolves traversal; the substring check rejects legitimate names like
  /home/u/..hidden without adding defense (the ancestor check via
  filepath.Rel is the real boundary).
- Drop the rel == "." branch in the ancestor check — early return for
  clean == currentClean already covers identity.
- Read existingTrackers inside the write-lock section (not under RLock
  then re-Lock) so two concurrent rescans can't both see 0 trackers and
  double-bootstrap the multi-repo set.
- Add table-driven TestValidateRescanPath covering rejection paths
  (relative, descendant, sibling, traversal-cleaned, empty input),
  legitimate ancestor promotion, exact match, no-anchor first-launch,
  and a /home/u/..hidden dotted-segment case.

* fix(agentctl): break taint flow into os.Stat — derive rescan path from cfg, not HTTP body

CodeQL's go/path-injection trace still flagged manager_rescan.go after the
prior sanitizer because the user-supplied newWorkDir flowed (after
validation) into os.Stat and then into cfg.WorkDir → tracker paths →
workspace_monitor os.Stat sinks.

Replace validateRescanPath with resolveRescanPath: it returns a path
DERIVED from currentWorkDir (no-op = currentClean, promotion =
filepath.Dir(currentClean)) and uses newPath only for comparison, never
as a source for the returned string. os.Stat now receives a path whose
provenance is trusted manager config.

Functionally the legitimate caller (branch materializer promoting
<task>/<repo>/ to <task>/) is unchanged.

* test(agentctl): make TestResolveRescanPath windows-portable

The Linux Backend Tests job passed but the Backend (windows) job rejected
every "absolute" case because filepath.IsAbs("/task/repo") is false on
Windows (no volume letter).

Build inputs via filepath.Join against absRoot() (filepath.Abs of "/" /
"\\") so the same table covers POSIX (`/...`) and Windows (`C:\...`)
without per-OS branches. Keep the traversal-after-Clean case POSIX-only
since filepath.Clean is the shared primitive and that input form needs a
volume to be considered absolute on Windows anyway.

* fix(agentctl): serialize RescanRepositories + re-check trackers under write lock

Two concurrent RescanRepositories calls could both observe an empty
tracker set (between the snapshot read and the bootstrap branch) and both
enter transitionToMultiRepoMode, leaking a duplicate bare-root tracker
and duplicate per-repo trackers. Same race in appendNewRepoTrackers would
double-append the same new child across both callers.

Fix:
- Add rescanMu sync.Mutex on Manager. Acquired for the full body of
  RescanRepositories so the snapshot → branch → mutate sequence is
  serialized. repoTrackersMu still guards individual field reads from
  unrelated goroutines (subscribe/unsubscribe/poll-mode).
- appendNewRepoTrackers re-checks membership inside the write-lock
  before appending and drops already-present trackers (Stop + detach
  outside the lock). Defense in depth.
- Test: add `dotted segment is legal as promotion` case so the
  ..hidden-segment promotion path stays exercised.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 19:36:20 +01:00
Carlos Florêncio 86f756e280 feat(workflow): add set_session_mode action and preserve mode across reset (#1188) 2026-05-31 14:36:12 +01:00