258 lines
13 KiB
Markdown
258 lines
13 KiB
Markdown
---
|
|
spec: docs/specs/agents/requirements/runtime-updates.md
|
|
decision: docs/decisions/2026-07-26-user-managed-agent-runtime-updates.md
|
|
created: 2026-07-26
|
|
status: completed
|
|
---
|
|
|
|
# Implementation Plan: Managed Agent Runtime Updates
|
|
|
|
## Overview
|
|
|
|
Replace PR 1950's repository-maintained npm pins and scheduled updater with
|
|
unversioned, cache-preferring managed ACP launch commands. Then add a
|
|
host-scoped, user-triggered update pipeline that refreshes npm's execution
|
|
cache, re-probes ACP capabilities, and streams the result into the responsive
|
|
Settings > Agents UI. The implementation preserves active sessions and treats
|
|
ACP negotiation plus advertised capabilities as the compatibility boundary.
|
|
|
|
---
|
|
|
|
## Backend
|
|
|
|
### Managed npm runtime contract
|
|
|
|
- Add `ManagedNPMRuntimeSpec` and the optional `ManagedNPMRuntimeAgent`
|
|
capability to `apps/backend/internal/agent/agents/agent.go`.
|
|
`ManagedNPMRuntimeSpec` owns the hard-coded npm package and ACP argv suffix;
|
|
request data never selects either value.
|
|
- Add command helpers in
|
|
`apps/backend/internal/agent/agents/managed_npm_runtime.go`:
|
|
`CachedACPCommand()` emits
|
|
`npx --yes --prefer-offline <unversioned-package> <acp-args>`, and
|
|
`CacheUpdateCommand()` emits a direct argv command equivalent to
|
|
`npm exec --yes --prefer-online --package=<unversioned-package> -- node -e ""`.
|
|
The update command primes the same unversioned npm execution-cache key
|
|
without relying on each package exposing a version flag.
|
|
- Implement the capability for Claude, Codex, OpenCode, Copilot, and Gemini in
|
|
their existing files. Use `CachedACPCommand()` from `BuildCommand`,
|
|
`Runtime().Cmd`, and `InferenceConfig().Command` so sessions and capability
|
|
probes resolve the same runtime. Keep passthrough/authentication helpers
|
|
separate where they intentionally use another command.
|
|
- Remove all exact version constants/specs from the five agents. Keep remote
|
|
install scripts unversioned. Copilot no longer opts its managed ACP launch
|
|
into native-binary preference; its native CLI remains available for
|
|
discovery and login.
|
|
|
|
### Runtime update and capability refresh
|
|
|
|
- Extend `apps/backend/internal/agent/hostutility/public.go` and
|
|
`manager.go` with `RefreshWithCommand(ctx, agentType, command)`. It reuses the
|
|
existing bounded ACP probe and cache normalization but overrides the
|
|
inference command for that probe. The normal `Refresh` path remains
|
|
unchanged.
|
|
- Add `apps/backend/internal/agent/settings/controller/agent_update.go` with an
|
|
`agentRuntimeUpdater` seam for tests and a production implementation that:
|
|
reads current version from `hostutility.Manager.Get`, resolves the target
|
|
with direct argv `npm view <package> dist-tags.latest --json`, runs only the
|
|
built-in `CacheUpdateCommand`, and calls `RefreshWithCommand` with the
|
|
cache-preferring ACP command.
|
|
- Add `apps/backend/internal/agent/settings/controller/agent_update_job.go`.
|
|
`AgentUpdateJobStore` uses the existing 64 KiB ring-buffer, five-minute
|
|
timeout/retention, four-worker bound, batched output, and broadcaster
|
|
patterns. It transitions through `queued`, `resolving`, `updating`,
|
|
`refreshing`, and a terminal state while retaining current/target/refreshed
|
|
versions and refresh errors.
|
|
- Add a shared same-agent maintenance coordinator in
|
|
`apps/backend/internal/agent/settings/controller/maintenance_jobs.go`.
|
|
Repeated update requests reuse the active update. An install/update conflict
|
|
returns `409 Conflict` with the active maintenance job identity and kind;
|
|
neither command is started.
|
|
- Classify an authentication-required post-update probe as package success with
|
|
`refresh_error`. Registry, npm-exec, timeout, unsupported-protocol, and
|
|
non-recoverable initialization failures are terminal failures and retain the
|
|
prior capability cache.
|
|
- After a successful refresh, call `BroadcastAvailableAgents` so the new
|
|
version, models, modes, commands, and configuration options reach open
|
|
Settings pages without reload.
|
|
|
|
### HTTP, DTO, and WebSocket contracts
|
|
|
|
- Extend `apps/backend/internal/agent/settings/dto/dto.go` with
|
|
`RuntimeUpdateDTO`, `AgentUpdateJobStatus`, `AgentUpdateJobDTO`,
|
|
`ListAgentUpdateJobsResponse`, and the optional
|
|
`AvailableAgentDTO.RuntimeUpdate`.
|
|
- Populate runtime metadata in
|
|
`apps/backend/internal/agent/settings/controller/agent_discovery.go` only
|
|
when an available built-in agent implements `ManagedNPMRuntimeAgent`.
|
|
`current_version` comes from the latest successful host capability record.
|
|
- Register the three update endpoints in
|
|
`apps/backend/internal/agent/settings/handlers/handlers.go`, protected by the
|
|
existing Settings interlock for POST. Preserve `202 Accepted` for a newly
|
|
queued or deduplicated update and return typed 404/409/503 errors.
|
|
- Add `ActionAgentUpdateStarted`, `ActionAgentUpdateOutput`, and
|
|
`ActionAgentUpdateFinished` to
|
|
`apps/backend/pkg/websocket/actions.go`.
|
|
|
|
## Frontend
|
|
|
|
### API, state, and WebSocket hydration
|
|
|
|
- Extend `apps/web/lib/types/http-agents.ts` with `RuntimeUpdate` metadata.
|
|
- Add `AgentUpdateJob`, `updateAgent`, `listAgentUpdateJobs`, and
|
|
`getAgentUpdateJob` to `apps/web/lib/api/domains/settings-api.ts` and export
|
|
them through the existing API barrels.
|
|
- Add `updateJobs` plus set/upsert/append/clear actions to
|
|
`apps/web/lib/state/slices/settings/types.ts`,
|
|
`settings-slice.ts`, `apps/web/lib/state/store.ts`, and their re-export
|
|
surfaces.
|
|
- Add typed update payloads/actions to
|
|
`apps/web/lib/types/backend.ts` and consume the three notifications in
|
|
`apps/web/lib/ws/handlers/agents.ts`. Appended output chunks must not replace
|
|
the retained job log.
|
|
- Add `useAgentRuntimeUpdates` beside the existing install hook in
|
|
`apps/web/app/settings/agents/page.tsx`. It rehydrates retained jobs on
|
|
mount, submits updates, handles maintenance conflicts, and relies on
|
|
`agent.available.updated` for the refreshed catalogue.
|
|
|
|
### Installed-agent update control
|
|
|
|
- Add
|
|
`apps/web/components/settings/agent-runtime-update-control.tsx` and compose it
|
|
into `installed-agent-card.tsx` only when `runtime_update.supported` is true.
|
|
- Show current version (or `Unknown`), target when resolved, the current job
|
|
phase, bounded streamed output, refresh-only warnings, retryable failures,
|
|
and terminal success. Disable the action while install or update maintenance
|
|
is active for the same agent.
|
|
- Preserve the existing single-column phone card layout. The labeled update
|
|
button has at least a 44 px touch target, version/progress content wraps
|
|
vertically, the document remains the primary scroller, and only the bounded
|
|
output log gets internal vertical scrolling. No horizontal page scrolling or
|
|
hover-only affordance is introduced.
|
|
|
|
## Documentation and PR replacement
|
|
|
|
- Delete the scheduled updater workflow, updater scripts/tests, obsolete
|
|
pin-oriented plan, and the rejected scheduled-pin ADR added by the current
|
|
branch.
|
|
- Restore `.github/workflows/lint-action-pinning.yml` and the unrelated GitLab
|
|
poller test to their `origin/main` behavior using explicit patches.
|
|
- Rewrite `apps/backend/internal/agent/agents/ACP_BRIDGE_VERSIONS.md`,
|
|
`README.md`, and
|
|
`docs/decisions/0034-agentclientprotocol-codex-acp.md` so they describe
|
|
unversioned managed ACP resolution rather than numeric pins.
|
|
- Document the Settings update action, host-only scope, active-session
|
|
behavior, model refresh, and best-effort npm cache in
|
|
`docs/public/agents-and-profiles.md`.
|
|
|
|
## Tests
|
|
|
|
- **What:** all five managed agents use the unversioned cache-preferring command
|
|
on ACP build/runtime/inference surfaces and expose the correct hard-coded
|
|
package metadata.
|
|
**File:** tests beside
|
|
`apps/backend/internal/agent/agents/{claude_acp,codex_acp,opencode_acp,copilot_acp,gemini}.go`
|
|
and `apps/backend/internal/agent/runtime/lifecycle/manager_launch_test.go`.
|
|
**How:** table-driven argv assertions; reject `@<version>` and explicit
|
|
`latest`.
|
|
- **What:** cache update commands use direct argv, online freshness, a
|
|
hard-coded package, and no user-controlled shell.
|
|
**File:**
|
|
`apps/backend/internal/agent/agents/managed_npm_runtime_test.go`.
|
|
**How:** unit tests over every managed runtime spec.
|
|
- **What:** force refresh probes with the override command and replaces the
|
|
host capability cache.
|
|
**File:** `apps/backend/internal/agent/hostutility/manager_test.go`.
|
|
**How:** fake agentctl probe request/response assertions.
|
|
- **What:** target resolution, job states, output bounds, deduplication,
|
|
install/update mutual exclusion, refresh success, auth-required refresh, and
|
|
hard failures follow the spec.
|
|
**File:**
|
|
`apps/backend/internal/agent/settings/controller/agent_update_test.go` and
|
|
`agent_install_test.go`.
|
|
**How:** fake updater/host utility plus real in-memory job scheduling.
|
|
- **What:** handler → controller → job-store endpoints return accepted,
|
|
retained, not-found, conflict, and interlock responses and broadcast update
|
|
events.
|
|
**File:**
|
|
`apps/backend/internal/agent/settings/handlers/agent_update_handlers_test.go`.
|
|
**How:** Gin HTTP integration tests with injected controller seams.
|
|
- **What:** the catalogue exposes managed runtime metadata and omits it for
|
|
unmanaged/native-only agents.
|
|
**File:**
|
|
`apps/backend/internal/agent/settings/controller/agent_discovery_test.go`.
|
|
**How:** registry and capability-cache fixtures.
|
|
- **What:** frontend state merges snapshots and output chunks without
|
|
duplication, and the installed card renders idle/running/success/failure
|
|
states accessibly.
|
|
**File:**
|
|
`apps/web/lib/state/slices/settings/settings-slice.test.ts`,
|
|
`apps/web/lib/ws/handlers/agents.test.ts`, and
|
|
`apps/web/components/settings/agent-runtime-update-control.test.tsx`.
|
|
**How:** Vitest store/handler tests and Testing Library component tests.
|
|
|
|
## E2E Tests
|
|
|
|
- **Scenario:** given an installed managed agent, starting Update shows
|
|
current-to-target versions, reachable progress/output, and terminal success.
|
|
**File:**
|
|
`apps/web/e2e/tests/settings/agent-runtime-update.spec.ts`.
|
|
**What to verify:** stable test IDs, POST submission, retained-job
|
|
rehydration, progress text, terminal state, and retry after failure using
|
|
deterministic network fixtures while backend integration is covered in Go.
|
|
- **Scenario:** given a successful update whose refreshed catalogue adds a
|
|
model, the new model appears without a document reload.
|
|
**File:** same spec.
|
|
**What to verify:** an `agent.available.updated` catalogue replacement is
|
|
reflected in the installed agent/profile model surface.
|
|
- **Scenario:** given a phone viewport and a running update, all version,
|
|
progress, output, and retry controls are touch-reachable without horizontal
|
|
overflow.
|
|
**File:**
|
|
`apps/web/e2e/tests/settings/mobile-agent-runtime-update.spec.ts`.
|
|
**What to verify:** labeled 44 px action, wrapped content, bounded log scroll,
|
|
page scroll, and `document.scrollWidth <= document.clientWidth`.
|
|
|
|
## Implementation Waves
|
|
|
|
Wave 1 (parallel):
|
|
|
|
- [x] [Task 01: Define unpinned managed runtimes](task-01-unpinned-managed-runtimes.md)
|
|
- [x] [Task 02: Remove pin automation and update documentation](task-02-remove-pin-automation.md)
|
|
|
|
Wave 2:
|
|
|
|
- [x] [Task 03: Build the backend update pipeline](task-03-backend-update-pipeline.md)
|
|
|
|
Wave 3:
|
|
|
|
- [x] [Task 04: Add the Settings update UI](task-04-settings-update-ui.md)
|
|
|
|
Wave 4:
|
|
|
|
- [x] [Task 05: Verify the runtime update user flows](task-05-runtime-update-e2e.md)
|
|
|
|
## Verification
|
|
|
|
- `cd apps/backend && go test ./internal/agent/agents ./internal/agent/runtime/lifecycle`
|
|
- `cd apps/backend && go test ./internal/agent/hostutility ./internal/agent/settings/controller ./internal/agent/settings/handlers`
|
|
- `cd apps && pnpm --filter @kandev/web test -- --run lib/state/slices/settings/settings-slice.test.ts lib/ws/handlers/agents.test.ts components/settings/agent-runtime-update-control.test.tsx`
|
|
- `cd apps/web && pnpm e2e:run tests/settings/agent-runtime-update.spec.ts`
|
|
- `cd apps/web && pnpm e2e:run tests/settings/mobile-agent-runtime-update.spec.ts -- --project=mobile-chrome`
|
|
- `node --test scripts/validate-public-docs.test.mjs`
|
|
- `node scripts/validate-public-docs.mjs`
|
|
- `git diff --check`
|
|
- Commit through active hooks, then delegate `/verify` in full mode before
|
|
pushing the replacement PR branch.
|
|
|
|
## Risks
|
|
|
|
- npm's execution cache is explicitly best-effort. Tests assert command intent,
|
|
not durable npm cache internals.
|
|
- A same-protocol upstream runtime can still regress. The UI must preserve
|
|
prior capability data and expose the failure; rollback remains out of scope.
|
|
- Capability probes can require authentication after package installation.
|
|
Package success and capability refresh status must remain distinct.
|
|
- Existing sessions retain their already-started process. No lifecycle restart
|
|
or process replacement belongs in these tasks.
|