feat: improve agent runtime update flow (#1982)

This commit is contained in:
Carlos Florêncio
2026-07-27 19:34:13 +01:00
committed by GitHub
parent b118a4fe98
commit 2bd2bae2ea
27 changed files with 1543 additions and 435 deletions
@@ -0,0 +1,173 @@
---
spec: docs/specs/agents/runtime-updates.md
created: 2026-07-27
status: complete
---
# Implementation Plan: Agent Runtime Update Dialog
## Overview
PR #1950 placed retained update state, versions, output, and results directly
inside installed-agent cards. It also configured PR previews with
`KANDEV_MOCK_AGENT=only`, which intentionally removes every real agent from the
registry and therefore leaves the install catalogue empty. This repair restores
the preview catalogue, adds a read-only update-preview contract, and moves the
approval and ephemeral streaming lifecycle into a responsive dialog.
## Confirmed root causes
- `apps/backend/cmd/preview/sprite_ops.go` exports
`KANDEV_MOCK_AGENT=only`. `registry.Provide` treats `only` as an isolated E2E
mode and registers only `mock-agent`; `ListAvailableAgents` can only return
registry entries, so Settings receives no real install candidates.
- `AgentRuntimeUpdateControl` is rendered inside `InstalledAgentCard`, so all
versions, output, and terminal state expand the card.
- `useAgentRuntimeUpdates` calls `listAgentUpdateJobs` on mount and upserts
retained backend jobs into the global Settings store, explicitly restoring
output and terminal state after a page restart.
- The only preparation path is `POST /agent-update/:agentName`; target-version
resolution happens after enqueue, so the UI cannot show the next version and
exact command before mutation.
## Backend
### Preview environment catalogue
Change `buildExtractScript` in
`apps/backend/cmd/preview/sprite_ops.go` to use
`KANDEV_MOCK_AGENT=true`. That mode retains the enabled mock agent while loading
the built-in real-agent catalogue, allowing unavailable agents with install
scripts to appear in Settings. Update the focused script assertion in
`apps/backend/cmd/preview/sprite_ops_test.go`; keep the registry mode test as
supporting evidence that `true` loads defaults and `only` does not.
### Read-only update preview
Add `AgentUpdatePreviewDTO` in
`apps/backend/internal/agent/settings/dto/dto.go` with agent, package, current
version, target version, argv, and display command fields.
Add `Controller.PreviewAgentUpdate(ctx, name)` in
`apps/backend/internal/agent/settings/controller/agent_update.go`. It validates
the same built-in `ManagedNPMRuntimeAgent` boundary as enqueue, reads the current
capability version, resolves the upstream target through `RuntimeUpdater`, and
serializes `CacheUpdateCommand()` with the existing `buildCommandString`
helper. Extend that helper to preserve empty argv entries as `""`, so the
displayed recipe remains an exact representation of the direct argv command.
It never claims maintenance state, creates a job, or executes the update command.
Register `GET /api/v1/agent-update/:agentName/preview` and a focused handler in
`apps/backend/internal/agent/settings/handlers/handlers.go`. Reuse the existing
update error classification for not-found, unsupported, unavailable, and
version-resolution failures.
## Frontend
### API and page-local update lifecycle
Add `AgentUpdatePreview` and `previewAgentUpdate` to
`apps/web/lib/api/domains/agent-update-api.ts`.
Refactor `useAgentRuntimeUpdates` so it does not list or hydrate retained update
jobs on mount. Starting an update returns or binds the accepted job identity for
the currently open dialog; WebSocket snapshots and chunks may continue using
the existing bounded Settings store, but the dialog displays only the job
started or recovered during its current page-local interaction.
### Installed-agent card and responsive update surface
Replace the inline `AgentRuntimeUpdateControl` composition with a compact
icon-only trigger in `apps/web/components/settings/installed-agent-card.tsx`.
The icon remains visually small while its standalone hitbox is at least 44px
and has an agent-specific accessible label and tooltip. Unmanaged agents omit
the trigger.
Replace `agent-runtime-update-control.tsx` with a responsive update surface:
- Desktop uses a centered `Dialog`.
- Phone uses an inset bottom `Drawer`, following
`components/kanban/mobile-menu-sheet.tsx`, because this is a temporary,
single-purpose approval flow rather than a primary route.
- Header and approval/dismiss actions remain fixed; one `min-h-0 flex-1
overflow-y-auto` body owns scrolling and clears the bottom safe area.
- Opening fetches the read-only preview and shows current/target versions,
host-only scope, automatic capability refresh, unchanged active sessions,
and the exact wrapping command.
- Approval is disabled until preview succeeds and is the only action that
sends the update POST.
- After approval, the same surface shows phase, bounded stdout/stderr, terminal
result, and retry. Closing or restarting the page discards the UI selection
and presentation state.
- No update information is rendered in the card.
### Public documentation
Update `docs/public/agents-and-profiles.md` to describe the icon-triggered
preview and approval flow, the exact-command disclosure, dialog-only streaming,
and the intentionally ephemeral browser presentation. The preview launcher
environment change is internal and does not need public environment-variable
documentation.
## Tests
- **Preview catalogue mode:** change `TestBuildExtractScript` first and confirm
it fails while the script still exports `only`, then make it pass with
`true`. File: `apps/backend/cmd/preview/sprite_ops_test.go`.
- **Read-only preview:** controller tests prove current/target/command output,
empty-argument display, unmanaged rejection, registry failure, and absence
of update execution.
File:
`apps/backend/internal/agent/settings/controller/agent_update_test.go`.
- **Exact display command:** extend the existing table test for
`buildCommandString` with an empty argument. File:
`apps/backend/internal/agent/settings/controller/controller_test.go`.
- **Preview handler:** handler tests prove the GET response and errors while
preserving POST enqueue behavior. File:
`apps/backend/internal/agent/settings/handlers/agent_update_handlers_test.go`.
- **Ephemeral state helper behavior:** retain focused Settings slice/WS tests
for bounded same-job output merging, and remove the retained-job hydration
expectation from the hook path. Files:
`apps/web/lib/state/slices/settings/settings-slice.test.ts` and
`apps/web/lib/ws/handlers/agents.test.ts`.
- **Public docs:** run `node --test scripts/validate-public-docs.test.mjs` and
`node scripts/validate-public-docs.mjs`.
## E2E Tests
- **Desktop approval:** opening the card icon shows versions, explanation, and
exact command while POST count remains zero; approving starts one job and
streams output/result inside the dialog.
- **Restart reset:** after progress or a terminal result, restarting the page
leaves only the icon on the card; a newly opened dialog contains a fresh
preview and no prior output/result.
- **Mobile parity:** the Pixel 5 flow taps the 44px icon, uses the bottom
drawer, reads the preview before approval, streams a long output without
document horizontal overflow, and reaches the fixed final actions.
Files:
- `apps/web/e2e/tests/settings/agent-runtime-update.spec.ts`
- `apps/web/e2e/tests/settings/mobile-agent-runtime-update.spec.ts`
- `apps/web/e2e/tests/settings/agent-runtime-update-helpers.ts`
## Implementation Waves And Parallel Candidates
Execution is sequential in the primary conversation. No subagents are
authorized.
Wave 1:
- [x] [Task 01: Restore the preview install catalogue](task-01-preview-catalogue.md)
- [x] [Task 02: Add the read-only update preview API](task-02-update-preview-api.md)
Wave 2:
- [x] [Task 03: Move updates into an ephemeral responsive dialog](task-03-update-dialog-ui.md)
Wave 3:
- [x] [Task 04: Verify desktop and mobile update flows](task-04-update-dialog-e2e.md)
Tasks 01 and 02 touch disjoint files, but remain sequential unless the user
explicitly authorizes subagents after the model checkpoint.
@@ -0,0 +1,51 @@
---
id: "01-preview-catalogue"
title: "Restore the preview install catalogue"
status: done
wave: 1
depends_on: []
plan: "plan.md"
spec: "../../specs/agents/runtime-updates.md"
---
# Task 01: Restore the preview install catalogue
## Acceptance
- PR previews enable `mock-agent` while retaining the built-in real-agent
catalogue.
- Settings can derive unavailable built-in agents and their install scripts
from the preview registry.
- E2E-only `KANDEV_MOCK_AGENT=only` semantics remain unchanged elsewhere.
## Verification
- RED/GREEN:
`cd apps/backend && go test -run TestBuildExtractScript ./cmd/preview`
- Supporting:
`cd apps/backend && go test -run TestProvide_MockAgentModes ./internal/agent/registry`
## Files likely touched
- `apps/backend/cmd/preview/sprite_ops.go`
- `apps/backend/cmd/preview/sprite_ops_test.go`
## Dependencies
None.
## Parallelism
Sequential by default. It is file-disjoint from Task 02 and may run in
parallel only with explicit user authorization.
## Inputs
- Spec agent-catalogue and Settings scenarios
- `registry.Provide` mock-mode contract
- Confirmed preview script root cause in `plan.md`
## Output contract
Report RED and GREEN results, changed files, preview-mode risk, and update this
task plus `plan.md` status in the primary conversation.
@@ -0,0 +1,60 @@
---
id: "02-update-preview-api"
title: "Add the read-only update preview API"
status: done
wave: 1
depends_on: []
plan: "plan.md"
spec: "../../specs/agents/runtime-updates.md"
---
# Task 02: Add the read-only update preview API
## Acceptance
- A managed installed agent exposes current version, upstream target version,
trusted update argv, and a display command through a read-only endpoint.
- The display command quotes the update recipe's empty final argv entry as
`""`, preserving the exact direct-execution command.
- Preview never creates a job, claims maintenance state, or executes the
update command.
- Unmanaged, missing, unavailable-updater, and registry-resolution failures
return explicit errors and do not mutate runtime state.
## Verification
- RED/GREEN controller:
`cd apps/backend && go test -run 'TestAgentUpdatePreview' ./internal/agent/settings/controller`
- RED/GREEN handler:
`cd apps/backend && go test -run 'TestAgentUpdatePreviewEndpoint' ./internal/agent/settings/handlers`
## Files likely touched
- `apps/backend/internal/agent/settings/dto/dto.go`
- `apps/backend/internal/agent/settings/controller/controller.go`
- `apps/backend/internal/agent/settings/controller/controller_test.go`
- `apps/backend/internal/agent/settings/controller/agent_update.go`
- `apps/backend/internal/agent/settings/controller/agent_update_test.go`
- `apps/backend/internal/agent/settings/handlers/handlers.go`
- `apps/backend/internal/agent/settings/handlers/agent_update_handlers_test.go`
## Dependencies
None.
## Parallelism
Sequential by default. It is file-disjoint from Task 01 and may run in
parallel only with explicit user authorization.
## Inputs
- Spec update-preview API and approval scenarios
- Existing `RuntimeUpdater`, `ManagedNPMRuntimeSpec.CacheUpdateCommand`, and
`buildCommandString`
- Existing update handler error mapping and direct-argv security boundary
## Output contract
Report RED and GREEN results, response/error contracts, changed files, command
trust-boundary risk, and update this task plus `plan.md` status.
@@ -0,0 +1,70 @@
---
id: "03-update-dialog-ui"
title: "Move updates into an ephemeral responsive dialog"
status: done
wave: 2
depends_on: ["02-update-preview-api"]
plan: "plan.md"
spec: "../../specs/agents/runtime-updates.md"
---
# Task 03: Move updates into an ephemeral responsive dialog
## Acceptance
- Managed cards show only a compact, accessible update icon; versions, command,
output, progress, and results appear only in the opened surface.
- Opening is read-only and approval is unavailable until current version,
target version, explanation, and exact command are visible.
- The approved job streams stdout/stderr and terminal state in the current
surface, while closing or restarting the page discards its presentation.
- Desktop uses a centered dialog; phone uses an inset bottom drawer with a
44px trigger, fixed header/footer, one safe-area-aware scrolling body, and no
document horizontal overflow.
- Public Agents documentation describes the preview/approval dialog and notes
that its visible progress and results reset on page restart.
## Verification
- Targeted state/WS tests:
`cd apps && pnpm --filter @kandev/web test -- --run lib/state/slices/settings/settings-slice.test.ts lib/ws/handlers/agents.test.ts`
- Typecheck:
`cd apps/web && pnpm run typecheck`
- Lint:
`cd apps && pnpm --filter @kandev/web lint`
- Public docs:
`node --test scripts/validate-public-docs.test.mjs && node scripts/validate-public-docs.mjs`
## Files likely touched
- `apps/web/lib/api/domains/agent-update-api.ts`
- `apps/web/lib/api/index.ts`
- `apps/web/hooks/domains/settings/use-agent-runtime-updates.ts`
- `apps/web/app/settings/agents/page.tsx`
- `apps/web/components/settings/installed-agent-card.tsx`
- `apps/web/components/settings/agent-runtime-update-control.tsx`
- `apps/web/components/settings/use-agent-update-dialog-state.ts`
- `apps/web/lib/state/slices/settings/settings-slice.test.ts`
- `docs/public/agents-and-profiles.md`
## Dependencies
Task 02 defines the read-only preview contract.
## Parallelism
Sequential.
## Inputs
- Spec approval, non-persistence, failure, and mobile scenarios
- `components/kanban/mobile-menu-sheet.tsx` mobile surface exemplar
- Existing Settings WS job merging and install/update conflict handling
- Mobile contract in `plan.md`
- `/docs-maintainer` task-oriented documentation requirements
## Output contract
Report RED/GREEN evidence where non-visual logic changes, rendered desktop and
phone evidence, changed files, accessibility/mobile/state risks, and update
this task plus `plan.md` status.
@@ -0,0 +1,55 @@
---
id: "04-update-dialog-e2e"
title: "Verify desktop and mobile update dialog flows"
status: done
wave: 3
depends_on: ["01-preview-catalogue", "02-update-preview-api", "03-update-dialog-ui"]
plan: "plan.md"
spec: "../../specs/agents/runtime-updates.md"
---
# Task 04: Verify desktop and mobile update dialog flows
## Acceptance
- Desktop Playwright proves the icon opens a read-only preview, approval starts
one job, and stdout/stderr plus terminal state remain inside the dialog.
- Restart coverage proves prior progress/output/result is absent from the card
and a newly opened dialog.
- Mobile Chrome proves the 44px icon and bottom drawer deliver the same value,
keep long content internally reachable, clear safe areas, and do not create
document horizontal overflow.
## Verification
- Desktop:
`cd apps/web && pnpm e2e:run tests/settings/agent-runtime-update.spec.ts`
- Mobile:
`cd apps/web && pnpm e2e:run tests/settings/mobile-agent-runtime-update.spec.ts -- --project=mobile-chrome`
## Files likely touched
- `apps/web/e2e/tests/settings/agent-runtime-update-helpers.ts`
- `apps/web/e2e/tests/settings/agent-runtime-update.spec.ts`
- `apps/web/e2e/tests/settings/mobile-agent-runtime-update.spec.ts`
- Runtime-update UI files only for test IDs or regressions exposed by RED
## Dependencies
Tasks 01-03.
## Parallelism
Sequential.
## Inputs
- Every revised user-visible spec scenario
- `/e2e` production-build and mobile input-modality requirements
- Mobile geometry contract in `plan.md`
## Output contract
Report exact RED and GREEN commands/results, rendered artifacts inspected,
desktop/mobile geometry evidence, uncertainties, and update this task plus
`plan.md` status.
+16 -13
View File
@@ -24,19 +24,22 @@ The status shown on this page is authoritative for the current host. A CLI that
### Update a managed agent runtime
Installed Claude, Codex, OpenCode, Copilot, and Gemini cards provide an
**Update agent** action. Kandev invokes these managed npm runtimes without an
Installed Claude, Codex, OpenCode, Copilot, and Gemini cards provide an update
icon. Kandev invokes these managed npm runtimes without an
exact version or `latest` tag during ordinary launches, so npm can reuse its
best-effort execution cache. The version reported by the agent is the
authoritative current version; Kandev does not infer it from the application
release.
Select **Update agent** to deliberately check npm and refresh the managed
runtime on the Kandev host. The card shows the current and upstream target
versions, streams progress, and remains disabled until the update finishes.
After the package update, Kandev automatically starts a fresh ACP capability
probe. A successful probe replaces the advertised models, modes, configuration
options, commands, and runtime version without a page reload.
Select the update icon to deliberately check npm and refresh the managed
runtime on the Kandev host. Before anything changes, the update dialog shows
the current and upstream target versions, the exact command Kandev will run,
and how the update affects sessions. Select **Approve update** to start it.
The dialog streams progress and stdout/stderr until the update finishes; those
details do not appear on the agent card and are cleared when you restart the
page. After the package update, Kandev automatically starts a fresh ACP
capability probe. A successful probe replaces the advertised models, modes,
configuration options, commands, and runtime version without a page reload.
The action changes later host probes and sessions only. It does not restart an
active session, update a separately configured passthrough or authentication
@@ -44,11 +47,11 @@ helper, or update remote executors and running containers. Those environments
resolve their own unversioned runtime when they launch.
If registry lookup or package execution fails, Kandev keeps the previous
capability catalogue and shows the captured failure. If the package updates but
the follow-up probe requires authentication, the card reports the update plus
the refresh error; authenticate the host agent and refresh again. Update job
history is short-lived and does not survive a backend restart, and npm cache
contents are not a durable Kandev installation record.
capability catalogue and shows the captured failure in the dialog. If the
package updates but the follow-up probe requires authentication, the dialog
reports the update plus the refresh error; authenticate the host agent and
refresh again. Update job history is short-lived and does not survive a backend
restart, and npm cache contents are not a durable Kandev installation record.
### Add a custom terminal agent
+66 -12
View File
@@ -20,10 +20,16 @@ cache is already present.
tag.
- Normal launches may reuse npm's execution cache. Cache reuse is best-effort;
Kandev does not present it as a durable installed-version guarantee.
- Each supported installed agent has a visible **Update agent** action on the
Settings > Agents page on desktop and mobile.
- Starting an update shows the current runtime version, the upstream target
version, live progress, and the terminal success or failure state.
- Each supported installed agent has a compact, accessible update icon action
on its Settings > Agents card on desktop and mobile. Update versions,
command details, progress, output, and results are not rendered in the card.
- Opening the update action is read-only. It opens an update dialog that
resolves and shows the current runtime version, upstream target version,
exact built-in command, host-only scope, capability refresh, and active
session behavior before an update can start.
- The update starts only after the operator approves it in the dialog. The
dialog then shows live stdout/stderr, progress, and the terminal success or
failure state.
- A successful package update automatically starts a fresh host capability
probe. The returned runtime version, models, modes, configuration options,
commands, and capability status replace the previous cached values.
@@ -74,6 +80,9 @@ runtime version. Unmanaged agents omit `runtime_update`.
### Update jobs
- `GET /api/v1/agent-update/:agentName/preview` resolves the current and
upstream target versions and returns the exact trusted built-in update
command without starting an update.
- `POST /api/v1/agent-update/:agentName` starts or returns the active update
job for a built-in managed agent.
- `GET /api/v1/agent-update/jobs` returns active and recently completed update
@@ -105,6 +114,32 @@ An update job contains:
notifications with the same job identity and state. Output notifications carry
only the appended output chunk.
An update preview contains:
```json
{
"agent_name": "claude-acp",
"package": "@agentclientprotocol/claude-agent-acp",
"current_version": "0.62.0",
"target_version": "0.63.0",
"command": [
"npm",
"exec",
"--yes",
"--prefer-online",
"--package=@agentclientprotocol/claude-agent-acp",
"--",
"node",
"-e",
""
],
"command_string": "npm exec --yes --prefer-online --package=@agentclientprotocol/claude-agent-acp -- node -e \"\""
}
```
The preview endpoint accepts only the built-in agent name. Package names,
versions, registry URLs, and command arguments are not caller-controlled.
## State machine
| State | Trigger | Observable behavior |
@@ -122,6 +157,8 @@ Jobs are terminal after `succeeded` or `failed`. Retrying creates a new job.
- If npm registry metadata cannot be resolved, the job fails before changing
the runtime and retains the prior capability data.
- If preview version resolution fails, the dialog shows the error and keeps
approval disabled. No update job or package command starts.
- If the package update command fails or times out, the job fails and retains
the prior capability data.
- If the package update succeeds but the capability probe fails because
@@ -142,6 +179,9 @@ Jobs are terminal after `succeeded` or `failed`. Retrying creates a new job.
- Update jobs and capability data are process-local and do not survive a Kandev
backend restart.
- Completed jobs remain queryable for the existing short job-retention window.
- Settings does not rehydrate retained update jobs after a browser page
restart. Update dialog state, output, and results are intentionally
page-local and disappear when the page is restarted.
- npm's host cache may survive Kandev restarts, but Kandev does not own or
guarantee that cache.
- After a backend restart, normal host capability probing reports whichever
@@ -153,8 +193,12 @@ Jobs are terminal after `succeeded` or `failed`. Retrying creates a new job.
without an explicit update, **THEN** Kandev invokes the unversioned package
spec and does not require a repository-maintained version pin.
- **GIVEN** Claude reports runtime version `0.62.0` and npm reports `0.63.0`,
**WHEN** the operator selects **Update agent**, **THEN** the card shows
`0.62.0 → 0.63.0` and streams progress until the job is terminal.
**WHEN** the operator opens its update action, **THEN** the dialog shows
`0.62.0 → 0.63.0`, the exact built-in command, and how the host update
affects capabilities and active sessions without starting the command.
- **GIVEN** the update dialog has a resolved preview, **WHEN** the operator
approves the update, **THEN** Kandev starts exactly one update and the dialog
streams stdout/stderr until the job is terminal.
- **GIVEN** an update succeeds and the new runtime advertises an additional
model, **WHEN** the automatic capability probe completes, **THEN** the new
model appears without a page reload or manual Rescan.
@@ -164,16 +208,25 @@ Jobs are terminal after `succeeded` or `failed`. Retrying creates a new job.
- **GIVEN** an install is active for an agent, **WHEN** an update is requested
for that agent, **THEN** Kandev returns the active maintenance job rather
than running install and update concurrently.
- **GIVEN** npm registry lookup fails, **WHEN** an update is requested,
**THEN** the card shows a retryable failure and retains the previous models.
- **GIVEN** npm registry lookup fails while preparing the dialog, **WHEN** the
update action is opened, **THEN** the dialog shows a retryable preview failure,
keeps approval disabled, and starts no update job.
- **GIVEN** npm registry lookup succeeds for the preview but fails after
approval because the registry changed, **WHEN** the update job resolves its
target again, **THEN** the dialog shows a retryable update failure and retains
the previous models.
- **GIVEN** the package update succeeds but the fresh probe requires
authentication, **WHEN** the job finishes, **THEN** the card reports the new
package version, shows the refresh error, retains the previous models, and
keeps the existing authentication recovery action available.
authentication, **WHEN** the job finishes, **THEN** the dialog reports the new
package version and refresh error, the previous models remain available, and
the card keeps its existing authentication recovery action.
- **GIVEN** an agent is unmanaged or native-only, **WHEN** Settings renders its
installed card, **THEN** no update action is shown.
- **GIVEN** an update dialog has shown progress or a terminal result, **WHEN**
the operator restarts the page, **THEN** the dialog is closed and no prior
update details, output, or result appear on the agent card or in a newly
opened dialog.
- **GIVEN** an update is running on a phone viewport, **WHEN** the operator
views the installed-agent card, **THEN** current and target versions,
views the update surface, **THEN** current and target versions, the command,
progress, output, and retry state are reachable by touch without horizontal
page scrolling.
- **GIVEN** an agent session is already running, **WHEN** its host runtime is
@@ -191,3 +244,4 @@ Jobs are terminal after `succeeded` or `failed`. Retrying creates a new job.
- Managing native-only update channels such as Cursor.
- Updating separately distributed passthrough or authentication helper
packages when they are not the managed ACP runtime.
- Resuming or recovering update dialog state after a browser page restart.