feat(coder,desktop): coding agents get a generated title, renamed from the tab's right-click menu

Merged
DA
8d ago
+1005-109

Summary

Dean: "I like the chat title plan. I don't want double-click on the tab to rename, I want a right-click menu just like in the plans on the left side bar. Let's not do any retitling though." And: "retitling just seems like added complexity that we want to avoid. Do this in the cleanest way possible." So: one generation at start, a rename, nothing else. No retitle hook and no generated-vs-renamed flag: the generated title is written only where no title stands, which is what keeps a rename from ever being covered.

  • Storage: coding_agent.title, nullable, in migration 0069_coding_agent_title (renumbered as #1954, #1958, and #1965 took 0066–0068) and coding-agent-schema.ts. Null is "untitled", and the tab falls back to the model's name.
  • Generation, once: when the agent's first eve session starts (coder/hooks/store.ts, on session.started), the coder host generates a 3–6 word sentence-case title from the plan's name and the agent's first message (the plan text, or the developer's task since #1958), and writes it where the row's title is still null (fillCodingAgentTitle). A later session of the same conversation (a restart) generates nothing. It runs on a detached fiber beside the turn, so neither the Start request nor the turn waits for it. One call on claude-haiku-5-5 through the same @ai-sdk/anthropic door the steps use, with a fixed short prompt, 64 output tokens, low effort, and a 20 s bound; quotes and a trailing full stop are stripped. Any failure leaves the title null. Without an Anthropic key, agents stay untitled.
  • Rename: PATCH /api/agents/{id} takes { title } beside the existing { model, effort }. Owner-only, trimmed, bounded at 80 characters; an empty title clears it. The answer is the agent's summary, which now carries title.
  • Wire: codingAgentSummarySchema.title (null or string) for the voice-control PR to read, and the transcript page carries title too, because the list that opened the tab was read before the generated title landed; the tab's held read brings it in a moment later.
  • Desktop:
    • The agent tab's label is the title, or the model's name until it has one; the model is in the tab's hint.
    • Right-click on an agent tab opens the same ActionMenu the sidebar plan rows use, where the pointer is, with Rename. Rename turns the tab into the same inline name field the plan tab uses (PlanNameField, now taking its label and bound), and Return renames through a new codingAgents.rename act → gateway codingAgents.rename → PATCH. No double-click rename.
    • Notifications say the title where there is one ("Sign-out button in account menu finished").
  • Left out, on purpose: the PR chip's Open pull request and Copy branch name are not in the tab menu. The published read lives with the shown tab (useAgentPullRequest), so offering them from the strip would mean a GitHub read on right-click for every tab. The menu is Rename alone.

Evidence

  • Platform-independent checks: ./scripts/check.sh exits 0.
  • macOS Electron verification (./scripts/verify.sh): not run. CI could not verify this UI change. CI builds nothing for the Mac and this was done on a Linux VM; Dean waived verify.sh for this rollout. LUKE-159 is the manual Mac pass.

Visual evidence

Headless Chrome render of the real DesktopPlans page and side panel (stub controls, the renderer's built stylesheet). Three agents: two titled, one still on its model's name. Second row: right-click on the first agent's tab.

DarkLight

Physical-device evidence

  • Screenshot or screen recording: not attached (no Mac in this workspace)

Tests

Each fails on main and passes here:

  • coder-host.test.ts: the title is generated from the plan as the first session starts, tidied, and written once; a restarted session generates nothing; a rename is never covered by a generated title. A failing title call leaves the agent untitled.
  • coding-agents-app.test.ts: PATCH { title } sticks, the list reads it back, an empty title clears it, another account's agent is not found, and a title past the bound is refused.
  • desktop-plans.test.ts: agent tabs are named by title or else by model with the model in the hint; a right-click offers Rename where the pointer is, Rename turns the tab into a field, and Return renames the agent and hands focus back.
  • use-coding-agents.test.ts: a rename asks the service for that agent and takes its answer; a refusal changes nothing; a transcript page's title lands on its agent.
  • use-agent-transcript.test.ts, agent-notices.test.ts (the notification says the title, including one a transcript page carried just before the end), coding-agent-client.test.ts, compose-coding-agents.test.ts: the rename and the page's title through each layer.
  • After Devin's review: only the newest rename's answer stands when two answer out of order (as onChoose already did), and a transcript page's title reaches main's notice ledger. Left as is, with the reasoning on the threads: an emptied field renames nothing (the plan field's rule; clearing is PATCH { title: "" }), and a refused rename leaves the tab on its standing name with no note, since the strip has no note surface.

Test count: +6.

Invariants

  • ./scripts/check.sh green; verify.sh not run (Linux VM; see above).
  • JSON Schema goldens regenerated under LUKE_UPDATE_FIXTURES=1: title on the summary and the messages page, and the two new rename schemas.
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added.
  • No new as type assertion outside the allowlisted files.
  • No Effect.runPromise/runSync/runFork outside the runtime edges.
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch.
  • No Effect, Stream, or Layer written as a statement and dropped.
  • No CLAUDE.md/AGENTS.md sentence names a changed part.

šŸ¤– Generated with Claude Code