Commit Graph

370 Commits

Author SHA1 Message Date
Carlos Florêncio 190eeddbc4 fix: default new workspaces to host GitHub access (#2156) 2026-08-02 14:13:04 +01:00
João Salavisa c7be985a29 feat(i18n): localize shared-task artifacts in the creator's locale (#2147)
Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-08-02 10:58:18 +02:00
Carlos Florêncio 1e353fbbbc fix: prevent scheduler database access during shutdown (#2135) 2026-08-01 20:11:04 +01:00
Carlos Florêncio 1d70a0df6d feat: enforce task title length limit (#2134) 2026-08-01 19:42:23 +01:00
Carlos Florêncio a1135aa8cf fix: hide false session question indicator (#2132) 2026-08-01 19:07:02 +01:00
Carlos Florêncio 1a999f201a fix: resolve chat file links from task workspace root (#2127) 2026-08-01 14:46:57 +01:00
Clément Fiere 59d1cc7277 feat: add Slack-style unread divider to session transcript (#1922) 2026-07-31 14:42:14 +01:00
João Salavisa 9be979827d fix: sync quick chats and their names across devices (#2085)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 09:01:22 +01:00
Carlos Florêncio 167ab6b778 feat(mcp): add session attachment diagnostics (#2061) 2026-07-30 20:49:07 +01:00
nova28 9b6d976f3b fix(backend): preserve local repository creation on macOS (#2052) 2026-07-30 16:23:39 +01:00
Carlos Florêncio 21674ec03c fix: bound task cleanup retries and ignore missing resources (#2027) (#2046) 2026-07-29 23:14:36 +01:00
Luan Martins 04a5c4e4ee feat(github): add task PR lifecycle notifications (#2038) 2026-07-29 18:13:02 +01:00
Carlos Florêncio c96b893174 fix: prevent duplicate review-watcher agents and resync task PRs on reconnect (#2034) 2026-07-29 14:05:03 +01:00
Carlos Florêncio e8d4b7d69e fix: restore coarse running busy signal (#2023) 2026-07-29 08:11:51 +01:00
João Salavisa 60040a71f1 fix(backend): return real results from Service.GetBlocking (#2015)
GetBlocking unconditionally returned an empty slice behind a TODO, so any
caller asking "which tasks does this task block?" silently got a wrong
answer. The reverse-lookup query the TODO asked for already exists on the
blocker repository as ListTasksBlockedBy (used by the office scheduler and
the handoff context builder) and is already part of the BlockerRepository
interface, so GetBlocking now delegates to it.

Like GetBlockers, it reports the raw blocker edges without filtering
archived tasks — every other ListTasksBlockedBy consumer does the same.

ListTasksBlockedBy also gains a deterministic ORDER BY (mirroring
ListTaskBlockers) and normalizes an empty result to a non-nil slice.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 23:03:43 +01:00
Carlos Florêncio a65087e062 fix: pull unstarted feeder tasks into available WIP steps (#2003) 2026-07-28 21:26:05 +01:00
Carlos Florêncio 7598880e67 feat: add issue-only Improve Kandev workflow (#1994) 2026-07-28 21:25:39 +01:00
Carlos Florêncio f501346d15 feat: add visible WIP overflow queues (#2001) 2026-07-28 15:46:16 +01:00
Alassane 4411f1c057 fix: match GitLab MR against repository remote_url identity (#1996) 2026-07-28 09:24:57 +01:00
Carlos Florêncio d705b3c2f3 fix(mcp): restore live add-branch worktrees (#1990)
* fix(mcp): restore live add-branch worktrees

* fix(mcp): reject deferred live branch additions

* docs(mcp): clarify live branch response contract
2026-07-27 22:08:18 +01:00
João Salavisa b118a4fe98 fix(mcp): honor explicit task_id in plan/walkthrough/review tools (#1984)
* fix(mcp): honor explicit task_id in plan/walkthrough/review tools

resolveTaskID silently discarded the caller-supplied task_id whenever the
MCP server was session-bound (every agent session) and substituted the
caller's own task. A parent reading a child's plan got the parent's empty
plan back ("No plan exists"), and a cross-task create/update would have
written to the wrong task — all with no error, despite the tool schemas
advertising task_id as a usable parameter.

Honor an explicitly provided task_id and fall back to the session-bound
task only when the argument is absent, matching how message_task_kandev /
stop_task_kandev already address other tasks. Cross-task access stays safe:
internal/mcp/scope attaches the stream owner's identity (from the agent's
own execution, never the payload), and the plan/walkthrough/review services
gate the target task_id through the same workspace-ownership check every
other cross-task surface uses — an out-of-reach task is rejected, not
served. The review service gains the SetTaskAuthorizer wiring the plan and
walkthrough services already had, so publish_review_findings is covered too.

Tool descriptions now document the default-to-current / cross-task-within-
reach behavior. Tests cover the resolution precedence (explicit wins /
fallback to bound / neither errors), a session-bound-to-A reads/creates a
plan for B regression, and the review authorizer allow/deny paths.

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

* fix(mcp): drop mcp.Required() from task_id on session-defaulting tools

The plan, walkthrough, and publish-review tools default task_id to the
session-bound task when it is omitted, so advertising it as required
contradicted that documented behavior (flagged by Codex and CodeRabbit on
the PR). Mark task_id optional on all eight; handler behavior is unchanged.
The genuinely cross-task tools (update/move/delete/archive_task,
message_task, get_task_conversation) keep task_id required.

Adds a schema test asserting task_id is present-but-optional on every
session-defaulting tool.

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

* test(mcp): cover review scoping + walkthrough/review tool-desc reach

Addresses two non-blocking review findings:

- Extend TestPlanAndWalkthroughScoping to exercise ReviewService through the
  assembled AuthorizeTaskAccess → SetTaskAuthorizer → PublishFindings path, so
  all three task-scoped services are covered uniformly (foreign owner denied
  with ErrTaskNotFound; real owner passes and the finding is stored).
- Document the default-to-current / cross-task-within-reach semantics in the
  top-level descriptions of the walkthrough and publish-review tools (was only
  in the task_id parameter description), since LLMs read the tool description
  first. Broaden the description test to assert all eight session-defaulting
  tools carry it.

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

* test(mcp): assert review finding persists through the auth gate

Follow-up to review: query repo.ListTaskReviewFindings after the owner
publish in TestPlanAndWalkthroughScoping and assert exactly one finding was
stored, so the test verifies the write landed in the repository rather than
only the service's returned slice.

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-27 18:34:55 +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 06d72a1215 fix: enforce workflow-step WIP limits on task creation (#1980) 2026-07-27 16:14:13 +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
João Salavisa 57adf1449c feat(review): native code-review agent with inline findings in the Changes panel (#1957)
* feat(review): persist and publish native code-review findings

Adds the backend core for reviewing a task's working diff before a PR
exists: task-scoped review runs and anchored, advisory findings that the
Changes/Review panel can render as inline comments.

- task_review_runs / task_review_findings schema, models, and repository,
  with supersede-on-re-review and a restart sweep that cancels in-flight
  runs rather than leaving them looking alive.
- ReviewService as the single write path, publishing bus events that the
  WS gateway forwards to the client.
- internal/review: reviewer-identity resolution (agent profile > code-review
  utility agent > user default, failing closed on passthrough-only profiles),
  change-set collection, prompt batching, response parsing, anchor
  extraction, and run orchestration.
- publish_review_findings_kandev MCP tool plus the task.review.* WS actions.
- Go djb2 that matches apps/web/lib/utils/hash.ts, asserted from shared
  fixtures on both sides so stale detection agrees across the wire.

Spec: docs/specs/native-code-review/spec.md
Plan: docs/plans/native-code-review/plan.md

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

* feat(review): add run_code_review workflow step and wire the review runner

Makes a review pass triggerable both ways and actually runnable end to end.

- run_code_review on_enter action across models, engine compile, and an
  orchestrator callback that logs rather than propagates failures, so a task
  never gets stuck in a step because no reviewer was configured.
- The step's optional agent_profile_id is what lets a step review with a
  different model than it implemented with; export/import swap it for a
  portable agent name/model/mode so it survives a cross-workspace round trip
  instead of carrying a dangling id.
- Concrete adapters for reviewer identity, changed-file collection, prompt
  rendering from the code-review utility agent's editable prompt, and
  inference (session-bound agentctl, falling back to the host instance so a
  review works on a task whose agent is not running).
- Boot sweep cancels review runs interrupted by a restart; the E2E reset
  clears review state before deleting the tasks that own it.

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

* feat(web): add review findings state, helpers, and live updates

Frontend plumbing for native code review, ahead of the UI that renders it.

- Review types, WS message map, and an API client that preserves the backend's
  machine-readable failure code so the surface can show an actionable message.
- A review store slice that merges findings by id (a re-publish refreshes an
  issue rather than listing it twice) and keeps a bounded run history.
- Findings helpers: composite file keys matching reviewFileKey, staleness via
  the same per-file diff hash that drives review marks, and best-effort
  re-anchoring from the stored anchor text so a moved finding follows the code
  it was written about instead of pointing at whatever now occupies its line.
- Hooks for backfill, resolve/dismiss/reopen with optimistic rollback, and
  sending a finding to the agent as follow-up context without changing its
  status — handing a finding to an agent is not the human accepting it.

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

* feat(web): render review findings inline in the Changes diff

Findings now appear where the code is, in the same annotation surface as
anchored review comments, with the run control that produces them.

- ReviewFindingCard: severity chip, category, Markdown body, and the three
  advisory actions. A suggestion is labelled "not applied automatically"
  because it is display-only in this iteration. Resolved and dismissed
  findings collapse to one muted line with an Undo, so a reviewed diff does
  not stay cluttered while the human can still change their mind.
- A review-finding annotation type threaded through the diff viewer, anchored
  from the same per-file diff hash the backend stamped. A finding whose diff
  moved is re-anchored from its stored text when that text survives, and
  otherwise reported as unanchored rather than placed against unrelated code.
- Review changes control with idle / running / cancel / failed states, and an
  inline Settings link for the one failure the user can actually fix.

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

* feat(web): make the review control reachable from the Review toolbar

Mounts the Review changes control next to the walkthrough action and lets an
inline finding wire its own actions.

The finding card container now reads the active task and session from the store
instead of taking four callbacks threaded through FileDiffViewer and DiffViewer;
an inline finding annotation only exists when the active task has findings, so
the store is always present and every other diff consumer is spared the
plumbing. Staleness likewise hashes the diff the viewer already holds, which is
already normalized, so it matches the hash the backend stamped without passing
it down.

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

* feat(web): surface stale review findings above their file's diff

A finding whose diff moved and whose anchor text is gone had nowhere to render,
so it was silently dropped. It now appears in a collapsed per-file banner above
the diff: the finding may still be valid, and placing it at its stored line
would point at whatever code now occupies that line instead.

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

* docs(review): mark the landed native code-review plan tasks done

Tasks 01-09 (backend persistence through inline diff rendering) are
implemented, tested, and committed. Tasks 10-13 — findings overview and step
editor, mobile parity, E2E, and public docs — remain pending.

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

* test(review): cover the on-demand review path end to end

Adds the mock-agent review responder and a Playwright spec, so both the happy
path and the no-reviewer path are verified against a real backend.

The mock agent answers any prompt carrying the review sentinel with fenced JSON,
deriving its anchors from the prompt's own diff rather than hardcoding them, so
findings land on files and lines the fixture really changed. It includes one
deliberately malformed entry to exercise the rejected-entry path.

Writing it caught an off-by-one shared by three diff walkers: splitting on "\n"
leaves an empty element after a trailing newline, which carries no diff marker
and was being counted as a context line, pushing new-side numbering one past the
end of a hunk. Interior blank lines still count, since a whitespace-stripped
blank context line legitimately appears as "".

Also adds a listInferenceAgents helper: default_utility_agent_id needs the
registered agent-type id, and an agent row UUID is rejected as not
inference-capable.

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

* fix(review): close review-run races and stale client findings

Addresses the three blockers and three suggestions from the PR #1957 automated
reviews. Two independent reviewers found each blocker separately.

- Launch claimed the task slot only after inserting the run row, so two
  concurrent launches both created a run and the loser returned a pending row
  with no goroutine behind it — a review that never finished until the next
  restart. The slot is now claimed before any DB write, and freed on every
  early return.
- Cancelling only marked the row. The goroutine ran on the shared background
  context, finished inference, and its completion overwrote the cancelled
  status and published findings the user had declined. Runs now carry a cancel
  handle, the WS action routes through the runner so the live context is
  cancelled, CompleteRun/FailRun refuse to resurrect a terminal run, and a
  cancelled pass neither publishes nor re-reports itself as failed.
- The backend deletes a superseded finding and inserts a replacement with a new
  id, but the publish event carried only the new one, so every open browser
  showed both at the same anchor until a reload. The event now names the
  superseded ids and the slice drops them.
- Run() waited on the shared WaitGroup, blocking on every other task's review;
  it now waits on its own pass only.
- createReviewSlice is typed over the `set` it actually uses, so call sites no
  longer pass superfluous arguments (CodeQL js/superfluous-trailing-arguments).
- A GetAgentProfile fault no longer collapses into "profile not found", which
  would have told the user to configure an agent they already have.

The cancel bug exposed a hole in the original tests: they asserted cancel could
not override a terminal run, but never the reverse. Both directions are covered
now, along with the concurrent-launch race, per-run waiting, and the supersede
path on both sides of the wire. Test fakes now mirror the service's terminal
guard rather than hiding it.

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

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:05:56 +01:00
Carlos Florêncio 6ffb23c187 fix: harden background work lifecycle tracking (#1955) 2026-07-26 14:18:35 +01:00
João Salavisa c3d235b83b fix(auth): close per-user session isolation gaps (service, WS, orchestrator) (#1939)
* fix(auth): scope session-keyed reads and mutations to the workspace owner

Session-keyed service entry points did not apply per-user scoping, so
knowing another user's session or task ID was enough to reach it:

- GetTaskSession, ListTaskSessions, GetPrimarySession returned rows for
  any ID. These feed GET /task-sessions/:id and GET /tasks/:id/sessions,
  whose DTO carries session Metadata, the host worktree path, the branch,
  and container/executor IDs.
- DismissLastAgentError mutated any session.
- ApproveSession approved a workflow review step on any session, which
  advances the task's workflow — the highest-impact of the set.

Authorize via authorizeTaskID off the row already read, rather than
AuthorizeSessionAccess, which would re-read the session on what is a hot
path. Internal identity-less callers (pollers, event bus, office
schedulers, office agent JWTs) and the synthetic identity used while auth
is disabled stay unscoped, and unowned pre-auth rows stay visible.

Batch helpers that take ID lists are deliberately left alone: their
callers derive IDs from an already-authorized task list, so per-ID checks
would add N queries to list views for no gain. Recorded in AGENTS.md.

Also route httpApproveSession through handleNotFound so a denial answers
404 like every other session route instead of a 500 that would confirm
the session exists.

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

* fix(auth): scope the session-keyed paths that bypass the task service

An adversarial sweep of the previous commit found session-keyed surfaces
reachable with a caller-supplied ID and no ownership check. They bypass
the task service, so scoping its methods did not cover them:

- POST /task-sessions/:id/{set-mode,set-model,set-config-option,
  authenticate} and .../processes/:processId/stop resolve their execution
  with a bare in-memory lookup, which skips the GetOrEnsure* chokepoint
  where the lifecycle per-user check runs. A non-owner could switch
  another user's model or mode, re-authenticate their agent, or kill
  their process. Guarded by a shared denySessionAccess helper.
- The orchestrator reads sessions through its own repo handle, so
  GetTaskSessionStatus and CheckSessionPR inherited nothing from the task
  service. CheckSessionPR also installs a PR watch as a side effect.
  Added a SetSessionAccessChecker hook mirroring the lifecycle manager's,
  wired to taskSvc.AuthorizeSessionAccess. The check runs before
  CheckSessionPR's unconfigured-GitHub early return so it applies
  regardless of setup, and denials reuse the "session not found" shape so
  they cannot be told apart from a missing session.
- GetMessage had no scoping while ListMessages beside it did, so a
  caller holding another user's (session_id, message_id) pair could read
  their shell output.

This does NOT close the whole surface. A second sweep found the
orchestrator's session-mutating WS actions (session.stop/delete/rename/
set_primary/set_plan_mode/reset_context/recover/launch/ensure,
agent.cancel, permission.respond) and agent.stdin / agent.resize also
unscoped — agent.stdin writes arbitrary input into another user's agent
process. Those touch live session lifecycle and need their own change;
recorded in AGENTS.md so the gap is not mistaken for covered.

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

* fix(auth): scope every session-keyed WS action, plus a gateway backstop

Completes the sweep. Dispatched WS actions were left to authorize
themselves at the service layer, and a dozen never did — a caller with a
guessed session ID could stop, delete, rename, relaunch or reset another
user's live agent session, cancel their in-flight turn, answer their
permission prompts, or write arbitrary bytes into their agent process.

Two layers, deliberately both:

1. Gateway backstop. Client.authorizeAction checks any dispatched payload
   carrying task_id or session_id against the client's identity before
   dispatch. Per-method opt-in across ~74 actions in four packages had
   been missed repeatedly; this makes a newly added action scoped by
   default. Verified safe to apply generically: session_id means a task
   session everywhere, and user-shell/terminal actions key off task_id /
   task_environment_id, so nothing is misread.

2. Explicit guards, for defense in depth and for non-WS callers:
   - orchestrator: StopSession, DeleteSession, SetPrimarySession,
     RenameSession, ResetAgentContext, CancelAgent, RespondToPermission,
     SetSessionPlanModeByID, RecoverSession, LaunchSession, EnsureSession,
     CancelTransientRetry. Added SetTaskAccessChecker for the task-keyed
     ones. LaunchSession guards every intent at the single funnel;
     SessionID is empty on create, so the task check carries that case.
   - agent.stdin / agent.resize now call lifecycleMgr.CheckSessionAccess,
     the helper already documented for bare-lookup handlers.

Guards go first in each method, before any repo/executor use.
TestSessionKeyedEntryPointsGuardBeforeDependencies runs all twelve with
nil dependencies and fails on a panic if one is placed too late.

Denials are indistinguishable from a missing resource: WS returns
not-found, CancelTransientRetry reports "nothing cancelled", and
GetTaskSessionStatus reuses its "session not found" shape.

Unwired checkers stay permissive throughout, so identity-less internal
callers (schedulers, event bus, workflow engine) and pre-auth single-user
instances are unaffected — pinned per entry point.

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

* fix(auth): scope environment-keyed user-shell actions

Third sweep finding, in surface the previous commit's own comment claimed
was covered. The user-shell actions key off task_environment_id and treat
task_id as optional, so a task_id-only backstop missed them:
user_shell.stop with an empty task_id and a foreign task_environment_id
fell through to the legacy runner path and tore down another user's
terminal PTY. That path never reaches
GetOrEnsureExecutionForEnvironment, where the environment check normally
runs, so neither layer caught it.

- Gateway backstop now also reads task_environment_id, gated by a new
  AuthPolicy.ActionEnvironment hook wired to
  taskSvc.AuthorizeEnvironmentAccess. This covers all three
  environment-keyed actions generically, not just the reported one.
- wsUserShellStop guards explicitly via a new
  lifecycleMgr.CheckEnvironmentAccess, mirroring CheckSessionAccess, so
  the fallback is safe independent of the gateway.

Corrected the comment that wrongly asserted task_environment_id was
already covered, and recorded in AGENTS.md both the exact field names the
backstop reads and why a permissive type on one of them would silently
opt out.

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

* fix(auth): authorize both IDs on task+session entry points

Review finding (P1). Entry points taking both a task and a session ID
only checked the session, so a caller could satisfy that check with one
of their own sessions while pointing taskID at another user's task:

- CheckSessionPR then used the unchecked taskID for GetTaskPR,
  resolveTaskRepo, resolvePrimaryTaskRepositoryID and EnsurePRWatch —
  cross-workspace PR disclosure plus a PR-watch write.
- CancelTransientRetry handed it to handleRecoverableFailure, which
  writes against that task.

Both now use a new authorizeTaskSessionPair, which authorizes each ID and
additionally requires the pair to be consistent, so the two arguments
cannot describe different tasks. GetTaskSessionStatus already validated
the pair after loading the session and is unchanged.

The consistency half is skipped when the session cannot be loaded: both
IDs are authorized by then, so a mismatch is a caller bug rather than a
cross-user leak, and failing there would change behavior for
identity-less internal callers.

Recorded the rule in AGENTS.md — a two-ID entry point must never use
authorizeSession alone.

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

* fix(auth): scope AttachWorkspaceSources

Found while rebasing onto main. POST /tasks/:id/workspace-sources and the
add_workspace_sources_kandev MCP tool landed on main during this PR's
review with no ownership check, so a caller could attach repositories and
folders to another user's task and have them materialized into that
task's workspace.

Same class as the rest of this PR, so fixed here rather than left for a
follow-up. The WS dispatch backstop added earlier already covered the
browser path (the payload carries task_id); this closes the HTTP route
and the MCP tool, which do not go through it.

ErrTaskNotFound already maps to 404 in workspaceSourceHTTPStatus, so the
denial surfaces correctly without touching the handler.

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

* fix(auth): map denied shell-output reads to 404

Review finding. GetMessage became per-user scoped earlier in this PR, so
a denial now arrives as ErrTaskNotFound. httpGetShellOutput only mapped
sql.ErrNoRows to 404, sending everything else to the 500 branch — so a
foreign message answered 500 while a missing one answered 404, which is
an existence signal and inconsistent with the 404-everywhere rule the
other session routes follow.

Kept the sql.ErrNoRows branch explicit rather than replacing it outright:
isNotFound classifies typed sentinels and the "not found" substring, and
"sql: no rows in result set" matches neither, so routing that case
through handleNotFound alone regressed the missing-message response to
500 (caught by TestHTTPShellOutputSnapshot).

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

* test(auth): make the shell-output scoping test actually exercise the guard

Both reviewers caught that TestHTTPGetShellOutputDeniesForeignMessageWith404
was vacuous. httpGetShellOutput has a second 404 gate after the service
call — `if !ok || message.TaskSessionID != c.Param("id")` — and
ExtractShellExecOutput returns ok=false for nil metadata. The fixture had
none, so the route answered 404 through that gate whether or not scoping
denied the read.

Confirmed by mutation rather than inspection: with the authorization
removed from GetMessage the test still passed. With valid shell-output
metadata in the fixture it returns 200 and the victim's stdout, so the
test fails as intended; restoring the guard makes it pass again.

Also mutation-tested the sibling denial tests, since this is a defect
class rather than a one-off:
- TestHTTPGetTaskSessionDeniesForeignSessionWith404 — sound, returns 200
  with the session DTO when the guard is removed.
- TestProcessRoutesDenyForeignSession — catches removal, though via the
  documented nil-lifecycleMgr panic rather than a status assertion.

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

* test(auth): add an in-test witness to the shell-output scoping test

Review suggestion. The fixture fix made the test exercise the guard, but
nothing in the test proved the 404 came from authorization rather than the
route's post-service `!ok` gate — which is exactly how it went vacuous in
the first place. The evidence lived in a commit message describing a
manual mutation run, not in the test.

Now asserts both directions, matching the approve-route test: the
non-owner gets 404, and the owner does NOT. If the assertion ever stops
proving authorization, the owner case fails and says so.

Re-verified by mutation: removing the guard from GetMessage fails the test
with 200 and the victim's stdout; restoring it passes.

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

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:09:52 +01:00
Carlos Florêncio 45ba6691c9 fix: isolate task state when switching workspaces (#1951) 2026-07-26 10:40:40 +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
João Salavisa 103878047c fix(auth): scope in-session agent MCP calls to the task owner (#1937)
* fix(auth): scope in-session agent MCP calls to the task owner

The MCP tools an agent gets automatically inside its own session are
relayed over the agent's WebSocket stream, which carries no credential.
The backend dispatched them on the raw stream context, so the task
service saw no identity — its signal for an internal caller (event bus,
pollers, office schedulers) — and served the request unscoped. An agent
that supplied another user's task_id or workflow_id was given their
data, violating the rule that knowing an ID must not grant access.

Resolve the owning user from the stream's own AgentExecution (task ->
workspace -> owner) and attach that real stored identity before
dispatch, so the existing authorize* checks apply exactly as they do on
the PAT-authenticated /mcp endpoint. The owning task never comes from
the agent-supplied payload: honoring a payload session_id would let an
agent name another user's session and inherit their identity.

Tool handlers are unchanged. Auth disabled stays unscoped, unclaimed
pre-auth workspaces stay visible to everyone, and a lookup failure
denies the dispatch rather than falling back to full access.

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

* fix(auth): never leave in-session MCP dispatch unscoped

Review follow-up. Three paths still returned an identity-free context
under enforced auth, and "no identity" is exactly what the task service
reads as an internal caller and serves unscoped:

- Unowned workspace. CreateWorkspace only stamps an owner for scoped
  callers, so internal callers keep producing owner_id='' rows after
  setup. Those streams got full cross-user access. Scope them to a
  sentinel user ID no account can hold: the service's "empty owner_id or
  my own ID" rule then limits them to the unowned rows that are public
  by the compatibility contract, and nothing else.
- Task with no workspace. Same treatment, same reasoning.
- Missing task or workspace row. A lookup that reports not-found as
  (nil, nil) fell through to unscoped; treat it as a resolution failure.

Also deny, rather than fabricate a member identity, when the owner's
account is gone or disabled. Disabling a user revokes their sessions and
PATs, so a still-running agent session was the one remaining way into
their workspace.

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

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:56:47 +01:00
João Salavisa 919a39e49a feat(auth): opt-in authentication & multi-user segregation (#1930)
* feat(auth): add auth core — identities, sessions, PATs, invites, mode state machine

Phase 1 of opt-in authentication + multi-user segregation:
- internal/auth: argon2id password hashing (PHC), opaque session/PAT tokens
  (SHA-256 at rest), login rate limiter, auth-mode state machine
  (disabled/setup/enabled), setup-wizard promotion of the pre-auth
  default-user row into the admin account, invites, admin user management
  with last-admin guard.
- internal/auth/store: auth_identities (OIDC-ready), auth_sessions,
  auth_api_tokens, auth_invites tables (SQLite+Postgres dialects).
- internal/auth/authn: request-identity plumbing (gin + plain contexts),
  RequireAdmin middleware.
- users table: display_name/role/status columns via new MigrateLogger
  wiring; AccountRepository surface (consumer-scoped, keeps existing
  Repository fakes untouched).
- config: AuthConfig.Required/SessionTTLHours/CookieName
  (KANDEV_AUTH_REQUIRED), FeaturesConfig.Auth; profiles.yaml + runtime
  flag registry entry for KANDEV_FEATURES_AUTH.

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

* feat(auth): enforce opt-in auth over HTTP — middleware, auth API, boot payload

Phase 2 of opt-in authentication + multi-user segregation:
- internal/auth/httpmw: global gin middleware after CORS. Disabled mode
  injects the synthetic single-user admin identity (behavior unchanged);
  enabled mode resolves session cookie then PAT bearer, with an explicit
  allowlist (health, SPA bootstrap reads, credential endpoints,
  self-authenticating webhooks), deferral for WS/proxy upgrades and
  office agent JWTs, and SPA shell availability for the login page.
- internal/auth/httpapi: full auth surface — setup wizard, login/logout,
  me, password change, sessions, PATs, invites (mint/preview/accept),
  auth settings (enable/disable), admin user management.
- Boot payload: initialState.auth block always present; unauthenticated
  visitors on auth-enabled instances get features+auth only (no data).
- External MCP: PAT required once auth is enabled (open while disabled).
- Admin guards on destructive system routes and runtime-flag overrides.
- Startup warning when bound non-loopback with auth disabled
  (ServerConfig.NonLoopbackBinds fail-closed nudge).
- Wiring: auth store in Repositories, auth service in Services,
  ClaimUnownedWorkspaces backfill on the task repository.

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

* feat(auth): per-user workspace scoping across HTTP services

Phase 3 of opt-in authentication + multi-user segregation:
- Task service: workspaces, workflows, repositories, and tasks are scoped
  to the request identity (owner-only visibility; pre-auth unowned rows
  stay shared). Denials use *NotFound sentinels - no existence leak.
  Internal callers and the disabled-mode synthetic identity remain
  unscoped, preserving behavior exactly when auth is off.
- CreateWorkspace stamps the authenticated caller as owner.
- Session chokepoint: lifecycle Manager.GetOrEnsureExecution runs a
  settable session-access check covering shell/files/ports/vscode/LSP.
- Secrets: per-user user_id column, scoped CRUD/list, ClaimUnowned wired
  into the setup-wizard backfills.
- Boot payload inherits scoping automatically.

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

* feat(auth): scope WebSocket gateway to authenticated users

Phase 4 of opt-in authentication + multi-user segregation:
- Connection auth: /ws, /terminal, /lsp, /vscode, /port-proxy routes are
  guarded when auth is enabled - session cookie (same-origin upgrade),
  resolved identity from the HTTP middleware, or ?token=<PAT> for
  headerless programmatic clients; JSON 401 before any upgrade.
- Client identity: every WS client carries its authn.Identity; dispatched
  RPC actions (workspace.list, task CRUD, ...) run under a context
  carrying it, so WS RPC gets the same per-user scoping as HTTP.
- Subscription checks: task/session subscribe+focus verify workspace
  visibility via the task service; user topic subscriptions are pinned to
  the client's own user.
- Broadcast routing: new Hub.BroadcastToWorkspace routes workspace-scoped
  events (task/workflow/repo/office/session-state) to the owner's clients
  only, with safe fallbacks (unowned/unresolvable -> broadcast) so events
  never vanish; instance-wide events (executors, environments, agent
  profiles, system jobs) stay global by design.
- Replaced the long-standing discarded-token TODO in HandleConnection.

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

* docs(auth): add feature spec and backend architecture notes for opt-in auth

- docs/specs/auth/spec.md: product behavior (opt-in, setup wizard,
  per-user workspaces, sessions/PATs, invites, public surface, v1 limits)
- apps/backend/AGENTS.md: internal/auth package layout + scoping rules
  for new service entry points and WS broadcast call sites

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

* docs(auth): public documentation for opt-in authentication

- New docs/public/authentication.md: enabling, users & invites, PATs,
  public endpoints, limitations, disabling
- run-as-a-service.md: network-security note now points at the real
  authentication boundary instead of reverse-proxy-only guidance
- meta.json: nav entry under Configure Kandev

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

* docs(auth): ADR for opt-in authentication architecture

Records the load-bearing choices: mode state machine + synthetic identity
in disabled mode, opaque DB-backed credentials over JWTs, default-user
promotion at setup, service-layer scoping with no-admin-bypass, and the
explicit public allowlist.

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

* feat(web): auth UI — login, setup wizard, invites, users & account settings

Adds the frontend half of opt-in authentication: a pre-auth gate in
main.tsx that renders the setup wizard or login/invite pages instead of
the app shell whenever the boot payload's auth block says so, a new
`auth` store slice + feature flag mirroring the backend's StatePayload,
credentialed fetch (`credentials: "include"`) with a 401 handler that
clears the session and redirects to /login, and a full auth-api.ts
client for the /api/v1/auth and /api/v1/users routes.

Settings additions: a System > Users page (list/create/disable/promote
users, invite-link dialog) and System > Authentication page (enable/
disable with confirm + env-locked banner), gated on the `auth` feature
flag and admin role. A new Account sidebar group (Profile & Password,
API Tokens) is shown to signed-in users once auth is enabled.

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

* test(auth): E2E lifecycle suite + fix workspace event WS leak it caught

New dedicated Playwright auth project (backend restarts with
KANDEV_AUTH_REQUIRED=true, isolated from the tokenless default suite):
setup-mode boot gating, admin setup wizard, login/logout + bad
credentials, PATs, admin-surface authorization, anonymous WS rejection,
UI login flow, and the acceptance test - two-user segregation across
HTTP, boot payload, and WebSocket.

The segregation spec immediately caught a real leak: workspace.* event
payloads carry their ID as 'id' (the payload IS the workspace DTO), not
'workspace_id', so BroadcastToWorkspace fell back to broadcast-to-all
for workspace create/update/delete. Fixed with action-aware extraction
plus a unit regression test.

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

* fix(auth): close cross-user access gaps found in security audit

Two independent security audits of the auth branch found several
authorization gaps where session/workspace-scoped surfaces were not wired
to the new per-user chokepoint. All fixed:

- IDOR on reverse-proxy/terminal routes (vscode, port-proxy, terminal):
  these resolve executions via GetExecutionBySessionID /
  GetOrEnsureExecutionForEnvironment / EnsurePassthroughExecution, which
  skipped the session-access check that only GetOrEnsureExecution ran. Added
  the check inside the env/passthrough resolvers (before the cache
  short-circuit) and an explicit CheckSessionAccess gate in the two bare-lookup
  proxy handlers. An authenticated non-owner can no longer reach another
  user's IDE, forwarded ports, or shell.
- Cross-user agent-conversation read: ListMessages / ListMessagesPaginated /
  SearchMessages now authorize the session.
- Cross-user repository-script read/inject: script CRUD now authorizes the
  parent repository's workspace.
- Office HTTP surface: a group middleware enforces workspace ownership on
  :wsId routes for real users (agent-JWT callers keep their claim scoping).
- Login rate-limit bypass: SetTrustedProxies(nil) so a spoofed
  X-Forwarded-For can't rotate the limiter key on a directly-reachable
  backend.
- Invite double-provisioning race: MarkInviteUsed is now the atomic
  serialization point (consumed before user creation).
- Setup wizard: serialized with a mutex so concurrent first-visitors can't
  corrupt the promoted admin profile.
- Log sanitizer: explicit kandev_pat_ redaction rule.

Regression tests added for message/script scoping and the
execution-access-before-cache invariant. Auth E2E (incl. two-user
segregation) still green.

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

* fix(auth): scope repository active-session-count endpoint

GET /api/v1/repositories/:id/active-session-count went through
CountActiveSessionsByRepository, which was not identity-scoped - a
cross-user info leak of another owner's active session counts. Added the
same authorizeRepositoryID gate the script routes use.

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

* fix(auth): add json tags to auth store models — omit digests, fix token UI

The auth store structs (APIToken, Invite, LoginIdentity, Session) had db:
tags but no json: tags, so the handlers that serialize them directly sent
Go-cased keys (CreatedAt, Name) AND leaked the SHA-256/password digests to
clients. Two consequences:
- The API-tokens UI showed 'Invalid Date' and an empty name (frontend reads
  created_at/name).
- token_sha256 / password_hash were serialized in list responses.

Added json tags with snake_case keys the frontend reads and json:"-" on
every credential digest. Regression test asserts digests never serialize and
the snake_case timestamp/name keys are present. Also adds the
auth-screenshots capture spec (artifacts gitignored).

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

* feat(web): move auth enable into feature toggles; show current user in sidebar

Remove the standalone Authentication settings page and its dedicated
settings/get-auth-settings API surface now that enabling auth lives in the
Feature Toggles system, and drop env_required now that /auth/me no longer
returns it. Add a current-user chip with a logout action to the sidebar
footer, shown only in enabled auth mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(auth): make features.auth the single on/off control

Per review feedback: authentication configuration now lives entirely in
the existing Feature Toggles system rather than a separate Authentication
settings section.

- The auth mode is derived from the features.auth runtime flag
  (KANDEV_FEATURES_AUTH): off -> disabled, on+no-admin -> setup,
  on+admin -> enabled. Removed the parallel auth.mode system setting,
  the SetEnabled service method, GET/PATCH /api/v1/auth/settings, the
  AuthConfig.Required field, KANDEV_AUTH_REQUIRED, and the auth service's
  Settings-store dependency and EnvRequired/env_required.
- The features.auth registry entry is re-described as the actual enable
  switch (high-risk, restart-required) and defaults off in every profile
  (dev was previously true — that only revealed UI before; now it would
  enforce, so it must be an explicit opt-in).
- Setup no longer persists a mode; completing the wizard creates the admin
  and the mode auto-derives to enabled.
- Startup 'exposed without auth' warning + all docs (public guide,
  run-as-a-service, spec, ADR, AGENTS.md) point at the feature toggle.
- E2E: auth specs use KANDEV_FEATURES_AUTH; screenshot spec captures the
  Feature Toggles page instead of the removed Authentication page; the
  member-restriction assertion targets an admin invite route.
- Tests updated across service/httpmw/httpapi/profiles for the derived
  mode; full suite green (only pre-existing sandbox-FS failures remain).

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

* fix(auth): complete cross-user isolation — integrations, automation, plan/walkthrough, session reads

A follow-up isolation audit found that per-user scoping had been wired into
the task service, secrets, office, and the WS gateway — but several
workspace/task/session-scoped surfaces still resolved a caller-supplied ID
with no ownership gate. Closed all of them; cross-user access now returns
404 across the board.

- Integrations (jira/linear/sentry/slack/azure-devops/gitlab/github/
  workflow-sync): a global middleware authorizes the workspace_id (query, or
  gitlab's /workspaces/:id/ path) on those route groups. Previously a member
  who knew another user's workspace ID could read/overwrite their integration
  connection config and watches.
- Automation: workspace-authorizer seam gates get/list/create/update/delete/
  enable/disable, triggers, run history, and — critically — the webhook
  secret. Was fully cross-tenant over WS before.
- Task plans (PlanService) and walkthroughs (WalkthroughService): task-
  authorizer seam on every read/write. These bypassed the task service.
- Session reads that hit the repo directly — session turns, task context,
  git snapshots, file reviews — now call AuthorizeSession/TaskAccess.

Internal callers (pollers, schedulers, MCP) carry no identity and stay
unscoped; disabled mode is unaffected. Regression tests added
(plan/walkthrough scoping, automation authorizer matrix, integration path
extraction) and the two-user E2E now asserts integration routes are shielded.
Docs (spec, ADR, public guide) updated to state what is isolated and that
filesystem/agent-credential isolation remains an OS-level limitation.

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

* fix(web): current-user chip uses border hover, not accent fill

The sidebar current-user chip used hover:bg-accent (a purpleish fill).
Switch to the border-hover idiom used elsewhere (border border-transparent
-> hover:border-border) with the neutral hover:bg-muted/50 the adjacent
workspace picker uses, so the hover reads as a subtle outline instead of a
colored background. Screenshot spec now captures the chip hover state.

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

* fix(web): right-align current-user chip in the sidebar footer

Push the user chip to the far right of the expanded footer (ml-auto) so it
sits opposite the left-aligned icon buttons; collapsed mode keeps the
centered vertical stack unchanged.

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

* fix(auth): reject synthetic identity on management routes and scope office run broadcasts

Address code-review findings on the opt-in authentication PR:

- P1: while auth is disabled the middleware injects a synthetic admin
  (RoleAdmin), and RequireAdmin alone let it through — so anyone hitting a
  not-yet-enabled instance could mint a PAT or plant an admin that survives
  enablement and hijacks first-run setup. Add authn.RequireRealIdentity and
  guard the credential/session/user-management routes with it (public
  bootstrap routes stay reachable). Responds 404 while disabled.
- P1: office run events carry task_id but no workspace_id, so they fell back
  to a global broadcast and leaked across users. Resolve the workspace from
  the task and route via the new Hub.BroadcastToWorkspaceOrDrop, which fails
  closed (drops rather than global-broadcasts) when the workspace is unknown
  and auth is enforced.
- nit: type createAuthSlice as a single-arg creator and call it with just set.

Also register the authentication docs page and its settings routes in
docs/public/coverage.json so the public-docs validator passes.

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-25 00:10:22 +01:00
Carlos Florêncio 1f4fabf30a fix: stabilize task and workspace creation (#1910) 2026-07-24 09:55:18 +01:00
Jérémy Coatelen cd332ef26b feat(task): nest a task under another as a sub-task from the sidebar (#1837) 2026-07-24 09:51:45 +01:00
Clément Fiere 62919efbdc fix: distinguish archived automation runs from cancelled ones (#1860) 2026-07-24 09:50:36 +01:00
Carlos Florêncio 29c86b7fa1 feat: create local repositories from new tasks (#1849) 2026-07-23 22:30:40 +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 5167bb9832 feat: add external vcs file links (#1883) 2026-07-23 15:18:10 +01:00
Carlos Florêncio e53fd038a8 fix(windows): list logical drives in folder picker (#1870) 2026-07-23 14:45:49 +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
José Almeida 7de788ceed feat: add cross-integration entity references (#1862)
* feat: add cross-integration entity references

* fix(web): anchor entity reference menu to composer

* fix: keep Kandev tasks under @ mentions

* fix: address entity reference review findings

* test(e2e): use option role for prompt menu

* test(e2e): update passthrough mention roles

* fix: preserve queued references and paginate Azure PRs
2026-07-23 09:03:56 +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