feat(web): a coding agent can be archived and brought back, stopping a running turn first

Merged
DA
7d ago
+415-84

What

The server side of archiving a coding agent and bringing it back (Dean: "a way to clean up agents within a plan, very helpful for failed agents"). This is PR 1 of 2; the desktop UI follows in a stacked PR.

  • Schema: coding_agent.archived_at, a nullable timestamptz (additive migration 0091_coding_agent_archived, folded into coding-agent-schema.ts). The release still serving never reads the column, so this ships in one step under the expand/contract rule.
  • Archive and restore: PATCH /api/agents/{id} takes { "archived": true | false }, next to the rename and model-choice bodies it already reads. The answer is the agent's summary, which now carries archivedAt while it is archived.
  • A running agent is stopped first. Archiving one with a turn under way goes through the same path a Stop takes: eve's cancel of that turn, then the row's stamp. stopEndpoint and the archive now share one stoppedTurn helper. If eve can't be reached the archive is refused as unavailable and nothing is stamped, so an agent is never archived while it is still working.
  • Nothing archived keeps working out of sight. An agent still starting, or with a message waiting on its turn, has no turn a Stop can name yet. Its archive is refused as agent_not_ready (409, the desktop says try again) until that turn exists or has ended. A failed agent (past the starting grace) archives normally. An archived agent also takes no message: the shared steer path refuses it as not found, which covers the route, the voice tool, and the inbox hand-off.
  • Lists hide archived agents by default. GET /api/plans/{id}/agents leaves them out unless ?archived=include asks; this is the same vocabulary the plan list uses for archived plans (CODING_AGENT_LIST_QUERY / CODING_AGENT_LIST_ARCHIVED). Every existing reader keeps today's view:
    • the sidebar's per-plan agent briefs (listPublishedCodingAgents), which drive the hover card and the working/PR marks;
    • the voice brain's list_coding_agents tool.
  • Reads by id still find an archived agent (transcript, pull request), so its transcript and PR are kept. No bulk archive and no permanent delete, as scoped.
  • Wire goldens: codingAgentArchiveRequestSchema is recorded, and the summary/answer/list goldens gain the optional archivedAt (regenerated with LUKE_UPDATE_FIXTURES=1). The only change is one added optional property.
  • tests/inbox-migration.test.ts used the current store's Start against a database migrated only to 0087, so any new coding_agent column breaks it (#2235 will hit the same thing). Its setup now writes the agent row naming only the columns that existed before 0088, the same way it already writes the old inbox rows.

Tests (PGlite, tests/coding-agents-app.test.ts)

  • An archived agent:
    • is left out of the plan's list and of the plans' briefs;
    • is listed with its stamp under archived=include;
    • still reads its transcript by id;
    • is listed again after archived: false;
    • is not found for another account;
    • refuses a message (404, eve asked nothing);
    • is refused as not ready while still starting.
  • Archiving an agent whose turn runs cancels that turn at eve first, so it reads cancelled and archived. When eve refuses the cancel, the archive is unavailable and the agent stays running and unarchived.
  • tests/drizzle-schema.test.ts checks the new column against information_schema.

./scripts/check.sh exits 0 locally. This PR touches no macOS UI, and verify.sh was not run (Linux VM).

šŸ¤– Generated with Claude Code