Commit Graph

2062 Commits

Author SHA1 Message Date
Carlos Florêncio 6ba9f1b0fd feat: add quarantine retention lifecycle (#2049) 2026-07-30 10:44:51 +01:00
Carlos Florêncio d05ba00b9f feat(web): preview markdown from review diffs (#2036) 2026-07-30 10:44:30 +01:00
João Salavisa d3bdad06c2 chore(plugin-registry): add kandev-plugin-kandy (#2042)
Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-07-30 10:12:58 +01:00
João Salavisa d93b06005d refactor(backend): drop dead req param from resolveGHToken (#2013)
PR #1810 retired the host-global gh token detection that injected GITHUB_TOKEN
into the request env, leaving both remote executors with a `_ = req` placeholder
to silence the unused parameter. resolveGHToken is now just a filter over the
selected credential IDs, so the parameter is gone from the Sprites and SSH
flavors alike. Test asserts the pass-through selection value, not just length.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 23:47:56 +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
João Salavisa 744aabee96 fix(mcp): keep spawner attribution on a spawned session's first turn (#2047)
* 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>
2026-07-29 22:51:40 +01:00
Clément Fiere 69b3340653 fix(github): stop the PR panel from showing the previous task's stale feedback (#2048) 2026-07-29 22:10:30 +01:00
Carlos Florêncio b4c537fa11 feat(web): hide embedded VS Code on Windows hosts (#2045) 2026-07-29 22:10:19 +01:00
Carlos Florêncio 6c9e0c2917 docs: clarify power-user positioning and roadmap (#2044) 2026-07-29 19:08:18 +01:00
Carlos Florêncio 18bf81819e fix(web): show only model in agent tab title (#2043) 2026-07-29 19:07:58 +01:00
Carlos Florêncio e77788706e fix(backend): cover agent launch readiness timeout (#2040) 2026-07-29 19:07:47 +01:00
Clément Fiere 3e794ba903 fix(github): stop SELECT * scans from breaking on TaskPR schema drift (#1988) 2026-07-29 18:52:00 +01:00
Clément Fiere f0b816a89f feat: add transcript auto-scroll toggle (#2039) 2026-07-29 18:51:38 +01:00
Alassane 3c5578285e fix: backfill remote_url from local checkout for legacy GitLab repos (#2030) 2026-07-29 18:14:01 +01:00
Luan Martins 04a5c4e4ee feat(github): add task PR lifecycle notifications (#2038) 2026-07-29 18:13:02 +01:00
João Manso 6317bf9426 fix(backend): cover the whole launch chain in waitForSessionReady (#2032) 2026-07-29 18:07:03 +01:00
Carlos Florêncio b734d3f8a5 fix: add stalled agent recovery (#2035) 2026-07-29 14:57:05 +01:00
Carlos Florêncio 1f19dbe6d5 fix: keep watcher save state visible (#2037) 2026-07-29 14:05:38 +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
João Salavisa 6868410d80 fix(plugins): match per-session-suffixed event subjects in plugin delivery (#2029)
Per-session events are published on a suffixed bus subject
("shell.output.<sessionId>") but stamped with the bare event-type constant
("shell.output"). The plugin deliverer subscribed with the manifest pattern and
then re-checked that pattern against event.Type, which made every suffixed
subject undeliverable: a 3-segment pattern matched the bus subscription but
failed the re-check on segment count, while a 2-segment pattern passed the
re-check but never subscribed to the 3-segment subject.

Carry the concrete publish subject on bus.Event (stamped in place by Publish in
both the memory and NATS implementations; the NATS receive side prefers the
delivering msg.Subject) and re-check the manifest pattern against it via a new
Event.EffectiveSubject helper that falls back to Type. The concrete subject is
also what plugins receive as pluginsdk.Event.EventType -- already documented in
the proto as "bus subject" -- so a plugin subscribed to "shell.output.*" can
read the session id off it.

Audited all 109 EventBus.Publish call sites: 17 subject families were affected;
office.run.event_appended, the office tree-hold subjects and the cross-plugin
plugin.<id>.<name> already stamped Type with the full subject. Three git.ws
sites in the orchestrator publish on git.ws.<sessionId> while stamping
events.GitEvent, so the subject there is not a suffixed form of its own type at
all -- left as-is, since the gateway routes off the subject and never reads
event.Type. Unsuffixed subjects publish with subject == type, so existing
2-segment subscriptions are unaffected, and the re-check still enforces manifest
segment-count strictness over the bus's looser NATS ">" semantics.
2026-07-29 13:30:30 +01:00
Carlos Florêncio d162b11051 fix(web): prevent hidden workflow task inheritance (#2031) 2026-07-29 12:13:14 +01:00
José Almeida 7ec82dcaec feat(search): add task workspace search palette (#2006) 2026-07-29 09:50:29 +01:00
Carlos Florêncio e8d4b7d69e fix: restore coarse running busy signal (#2023) 2026-07-29 08:11:51 +01:00
João Salavisa 5fb7575e60 fix(backend): return bad request on malformed sprites WS payloads (#2014)
wsStatus, wsListInstances and wsTest discarded the ParsePayload error and
carried on with a zero-valued SecretID, so a malformed frame surfaced as a
confusing downstream failure (or a lookup against an empty secret ID) instead
of a clear bad-request. Match the sibling handlers in the same file and return
ws.NewError(..., ErrorCodeBadRequest, "invalid payload", nil).

Absent payloads keep working: Message.ParsePayload treats a nil payload as a
no-op, so only genuinely malformed JSON now errors.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:53:50 +01:00
Carlos Florêncio d340512cc6 fix(web): keep agent tab title synced with model (#2021) 2026-07-28 23:44:19 +01:00
Carlos Florêncio abf10d7814 fix(web): reconcile responsive task layouts (#2011) 2026-07-28 23:29:32 +01:00
João Salavisa 7b796b1eaa fix(agents): set confirmed SessionDirTemplate for Pi, Qoder, Kiro and Trae (#2019)
All four templates were confirmed against the shipped CLI, not inferred
from the agent name:

- pi-acp     -> {home}/.pi              (pi-acp@0.0.32 package source)
- qoder-acp  -> {home}/.qoder           (qodercli 1.1.7, --config-dir proof)
- kiro-acp   -> {home}/.kiro            (Kiro CLI 2.15.0, ACP session/new)
- trae-acp   -> {home}/.cache/trae-cli  (Trae CLI 0.120.48, ACP session/new)

Kiro and Trae deliberately break the "{home}/.<agent>" shape: Kiro keeps
its login token in ~/.local/share/kiro-cli/data.sqlite3 while sessions
live in ~/.kiro, and Trae keeps config in ~/.trae but session state in
the cache dir with auth in the OS keyring. Each Runtime() records the
evidence and the residual gaps.

Extends the shared ACP agent spec table with sessionDirTemplate so every
agent's session root is pinned, including the ones already set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 23:17:47 +01:00
João Manso ca328f750f fix(worktree): stop planting self-referential links in local repositories (#2007) 2026-07-28 23:11:18 +01:00
Carlos Florêncio 2f6079fe0b perf(web): hydrate empty message favorites once (#2025) 2026-07-28 23:04:55 +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
João Salavisa fa309d0627 chore(web): delete dead tracked store.ts.backup (#2012)
`apps/web/lib/state/store.ts.backup` was a 1202-line snapshot of the
pre-slice-refactor monolithic Zustand store, accidentally committed in
b090a0157 (#1049). It never compiled and never ran — `tsconfig.json`
includes `**/*.ts`, which does not match a `.backup` suffix — so its only
effect was polluting grep/search results for anyone working on the real
store at `apps/web/lib/state/store.ts`.

No runtime code referenced it: `grep -rn "store.ts.backup" apps/` returns
nothing, and no build, lint, or tsconfig glob picks up `*.backup`.

Verified no logic was dropped. Of the 177 state keys and actions in the
backup, 172 exist in the live slice tree under `lib/state/slices/`. The
five that don't belong to two concepts that were deliberately renamed
after the snapshot was taken:

- `boardId` / `setBoards` / `setActiveBoard` — boards became workflows in
  658bd349b (#228); the live kanban slice uses `workflowId` /
  `setWorkflows` / `setActiveWorkflow`.
- `environmentsLoaded` / `setEnvironments` — environments became
  executors; the live settings slice has `ExecutorsState` /
  `setExecutors`.

Also add `*.backup`, `*.bak`, and `*.orig` to `.gitignore` so editor and
merge scratch copies cannot be committed again.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 22:42:59 +01:00
João Salavisa ec297626cd fix(backend): bound the launcher health probe with a timeout (#2017)
http.Get uses http.DefaultClient, which has no timeout and carries no
context. A backend that accepted the TCP connection but never responded
blocked waitForHealth forever, hanging the launcher with no way to
cancel it -- exactly what the suppressed noctx lint was warning about.

Probe through a package-level client with an explicit 2s timeout using
http.NewRequestWithContext, thread a context down from launcher.Run, and
drain each response body before closing so the connection is reused
across poll iterations. The overall wait now derives a deadline from the
caller's context, so cancellation ends it too.

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 22:42:00 +01:00
Carlos Florêncio 211d5b5408 fix: auto-select the sole visible workflow (#2020) 2026-07-28 22:41:31 +01:00
João Salavisa 029f485e57 fix(backend): bound tool installer downloads with timeouts (#2016)
* fix(backend): bound tool installer downloads with timeouts

The GitHub release and tarball install strategies downloaded archives with
http.DefaultClient, which has no timeout: a slow or stalled mirror left an
install hanging indefinitely with no way out.

Both strategies now share a package-level client whose transport bounds each
phase that can stall silently (connect, TLS handshake, response headers), and
whose response bodies are wrapped in a stall guard that aborts the transfer
when it stops making progress. There is deliberately no overall
http.Client.Timeout — these are tarballs, and one deadline for the whole
transfer would kill a large-but-healthy download on a slow link.

Requests keep using http.NewRequestWithContext, so an install remains
cancellable; the guard derives its own cancellable context from the caller's.

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

* fix(backend): bound only blocked reads in the download stall guard

The guard armed its timer at construction and rearmed it after any read that
returned bytes, so the window also covered whatever the caller did with those
bytes downstream. Expanding one read's worth of a highly compressed archive
onto a slow disk can outlast the window without the mirror having gone quiet
at all, which would surface a false "download stalled" error on a perfectly
healthy install.

Arm the timer around the blocking read alone and stop it as soon as the read
returns, so the bound is on how long a read blocks rather than on wall-clock
time spent downstream.

Reported by Codex review on #2016.

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-28 22:39:21 +01:00
Clément Fiere 61731c7340 feat(web): add favorite star toggle to chat messages (#2008) 2026-07-28 21:52:34 +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 09b5c4df7a feat: propagate managed git environment to terminals (#2010) 2026-07-28 21:19:27 +01:00
Carlos Florêncio e5237b26c2 fix: keep file attachments compact beside image previews (#1997) 2026-07-28 17:32:33 +01:00
José Almeida 2281f60a42 fix(kanban): adapt lanes to available board width (#2004)
* fix(kanban): adapt lanes to available board width

* refactor(kanban): remove compact stage rail

* docs(kanban): align adaptive lane records

* fix(kanban): stabilize adaptive lane rendering

* refactor(kanban): remove unused board grid
2026-07-28 17:32:09 +01:00
Carlos Florêncio e9bb4e20e2 feat(walkthrough): stabilize navigation footer (#2002) 2026-07-28 16:38:56 +01:00
Carlos Florêncio adecad7f30 fix(github): honor executor clone transport (#1998) 2026-07-28 15:46:28 +01:00
Carlos Florêncio f501346d15 feat: add visible WIP overflow queues (#2001) 2026-07-28 15:46:16 +01:00
José Almeida 5b2cd59545 feat(web): streamline mobile review file headers (#1995)
* feat(web): streamline mobile review file headers

* fix(web): toggle mobile review file menu

* test(web): select mobile review header in grouping test
2026-07-28 10:31:03 +01:00
Clément Fiere 018241f301 fix(sentry): make issue-watch StatsPeriod actually filter by issue age (#1977) 2026-07-28 09:29:55 +01:00
Clément Fiere a303ef7d6f feat(sentry): support polling multiple projects per issue watcher (#1978) 2026-07-28 09:29:33 +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 7250cdbf61 feat: explain busy storage cleanup and allow override (#1993) 2026-07-28 09:23:42 +01:00
Carlos Florêncio 35d1a85d74 fix: recover failed agent resumes and managed runtimes (#1991) 2026-07-28 09:23:30 +01:00
João Salavisa 16495a6dcc fix(terminal): expose executor-profile env vars to terminal shells (#1992)
* fix(terminal): expose executor-profile env vars to terminal shells

PR #1971 got the profile env into the agent subprocess and the repository
setup script, but the terminal panel was still a gap: the local user shell
was started with `InteractiveStartRequest.Env` unset, so it inherited only
the backend process environment, and agentctl's per-terminal shells were
started from `shell.DefaultConfig` with no env overrides at all.

Local/worktree path: resolve the task environment's executor profile at
terminal start (revealing secret-backed entries) and pass it through
UserShellOptions.Env into the deferred PTY start. Resolution happens per
terminal rather than being cached at launch, so a terminal opened before
the agent ever runs still gets the vars.

Remote path (Docker/Sprites/SSH): StartTerminalShell now folds the agent
instance's env into the shell config, so a terminal inside the workspace
sees what the agent sees. SSH is unchanged in scope — its instance env is
still only the credential allowlist, not arbitrary profile variables.

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

* fix(terminal): resolve terminal env from the session's executor profile

Address PR review. The resolver read executor_profile_id off the
task_environments row, but persistTaskEnvironment's reuse branch never
refreshes that column — it keeps whatever the first session stamped on it.
buildLaunchAgentRequest resolves the agent's env from
session.ExecutorProfileID instead, so a task environment reused by a later
session that picked a different profile of the same executor type would give
the terminal stale secrets and omit the ones the agent just received.

Prefer the session's profile and fall back to the environment row only when
the session recorded none. Also document why mergeAgentEnvIntoShellConfig
needs no defensive copy and why StartEnvForTesting is exported.

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

* test(terminal): give env-row fallback subtests distinct session IDs

Address PR review nit: both subtests shared the hardcoded "session-3" at
the call site, so the error-path case read as a repeat of the first.
Move the session ID into the table as session-no-profile / session-error.

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-28 00:08:57 +01:00