* fix(mcp): keep spawner attribution on a spawned session's first turn
A session started through spawn_session_kandev never learned who spawned
it, so it had no way to report back. The MCP handler did prepend a
<kandev-system> attribution block, but the first-turn canonicalizer
(sysprompt.canonicalizeKandevContext) drops every system block it cannot
attribute to server state — and the spawn block was not on the trusted
list, so it was deleted before the agent ever saw it. Spawns are always a
first turn, so this happened every time.
Generate the block at the launch site instead, from server-resolved
identity, and whitelist that exact content for the injector — the same
pattern entity references already use:
- config/prompts/spawned-session.md holds the attribution template.
- sysprompt.SpawnedSessionContext builds it; sysprompt.StripTags (lifted
out of the handlers' stripSystemTag) keeps embedded IDs from closing
the block early.
- LaunchSessionRequest.SpawnOrigin carries the spawner identity and is
kept off the wire (json:"-") so a WS client cannot forge a trusted
system block.
- startTask prepends the block and passes the identical content as
trusted content to both the task and Office injectors. Its trailing
env/route parameters move into a startTaskOptions struct rather than
growing the positional list again.
- The MCP handler now only fills SpawnOrigin (including the spawner's
session name) and passes the prompt through untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(mcp): derive spawn attribution from the verified spawner session
Addresses both PR review findings.
CodeRabbit: spawnOrigin copied sender_task_id / sender_session_id straight
out of the request, so an unknown or cross-task sender_session_id became
trusted launch attribution — the spawned agent would be told to report to
a session that never asked for it. Resolve the spawner session once, up
front, require it to belong to the claimed sender task, and derive the
origin (and the inherited agent profile) from that row. An unverifiable
claim now yields no attribution instead of a forged one. This also drops
a duplicate session lookup.
Codex: passthrough profiles skip the Kandev MCP wrap, so the new
attribution never reached a spawned passthrough session even though it
gets MCP configured and can reply. Give that path plain-text attribution
(no <kandev-system> markup, which would show up raw in the TTY the user
is watching). The wrap branch moves into applyLaunchPromptContext, which
keeps startTask under the nesting limit.
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>
* 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>
* fix(scripts): launch dev-isolated backend via __backend subcommand
The kandev binary became a bundle launcher with the restart supervisor
(ADR 0019) and exits without KANDEV_BUNDLE_DIR when invoked bare; the
isolated dev instance must run the backend entrypoint directly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp): session spawning, sibling messaging, and renameable tabs
- spawn_session_kandev spawns an additional agent session on an existing
task (default: the caller's own) via LaunchSession(IntentStart), with
optional agent_profile_id (defaults to the spawner's profile) and a
session name; the initial prompt carries spawner attribution and reply
instructions.
- message_task_kandev accepts an optional session_id to target a specific
session, enabling same-task sibling messaging. Explicit targets are
pinned through dispatch so the idle-path primary-session re-resolution
cannot reroute a sibling message back to its sender (regression-tested).
- task_sessions gains a name column (migration ordered after the
agent_execution_id-drop rebuild), a session.rename WS action, and inline
tab rename UI; custom names take precedence over derived agent/model
labels across the tab, agents dropdown, mobile list, chat header, and
dockview panel titles.
- Inter-session messages stamp sender_session_name into metadata so the
sender badge shows which session sent the message.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(mcp): address review findings on session spawn/rename/messaging
- include name in task-session DTOs (list + full, no omitempty) so
persisted tab names survive list hydration and clears propagate
- sanitize sender task/session ids embedded in <kandev-system> blocks
- reject cross-workspace spawn targets; require sender_task_id
- truncate session names by runes, not bytes
- distinguish not-found from internal errors in message target lookup
- guard TabRenameInput blur-commit after Enter/Escape resolution
- cap optimistic rename length client-side and toast on rename failure
- add tests: spawn launch-failure and cross-workspace paths, renameSession
API client
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(mcp): round-two review fixes for spawn/rename lint and hardening
- extract spawn validation/authorization helpers (cyclop/nestif limits)
- make stripSystemTag replace until stable to block nested tag evasion
- load session before rename so a durable rename can never skip its
broadcast; surface lookup failures to the caller instead
- drop redundant sessionLauncher nil check (guarded registration)
- add maxLength to the rename input matching the server cap
- add tests: nested sanitizer evasion, spawn rename-failure tolerance
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>