fix(templates): publish agent.upsert with the hasStream flag on launch - #1038
Merged
Merged
Conversation
The template launch route was the only one of ~31 `agent.upsert` publish sites that sent the raw AgentRecord instead of wrapping it in `withStreamFlag`, so its payload omitted `hasStream` entirely. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
selfcontained
deleted the
agt_c29292e62569/job-debt-collector-cf0194d2
branch
September 2, 2026 09:14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
apps/server/src/routes/templates.tswas the onlyagent.upsertpublish site in the repo that sent the rawAgentRecordinstead ofwithStreamFlag(...). This threadswithStreamFlagintoTemplateRouteDepsand wraps the launch publish, plus a colocated test.Why it's tech debt
There are 31
agent.upsertpublish sites (server.ts,server/mcp-handlers.tsx7,server/mcp-review-handlers.tsx2,server/agent-lifecycle-runtime.tsx7,routes/release.ts,routes/agents/{terminal,crud,lifecycle}-routes.ts,routes/templates.ts). Thirty wrapped the agent; this one didn't. Verified withgrep -rn --text -A3 'type: "agent.upsert"' apps/server/src/.Is it a behavior change? Yes, but an unobservable one — and it closes a latent hole
applyAgentUpsert(apps/web/src/hooks/use-sse.ts) replaces the cached agent wholesale, preserving onlysubmittedReviewId. So the unflagged payload droppedhasStreamfrom the cached row.agents-view.tsx:275—focusedAgent?.hasStream ?? false— soundefinedandfalsewere already indistinguishable.templateService.launchTemplatealways callsagentManager.createAgent, so the record is always brand new.createAgentfiresonAgentCreatedearly (agents/manager.ts:369, which publishes a flagged upsert withhasStream: false), then runslaunchWithSetupScript, then re-fetches and returns the final record — which is what this route publishes. A stream only exists once the agent's CLI drives a CDP screencast viaStreamManager.startStream, which cannot happen inside that window. So there is no user-visible symptom today; the fix makes the payload correct by construction rather than correct by luck, and stops the second publish from being able to clobber the first one's flag.Deliberately NOT changed (near-misses)
return { agent: result.agent }stays unflagged.crud-routes.ts:333does the same (return reply.code(201).send({ agent })after a flagged publish);lifecycle-routes.ts:63is the one that flags its response. That inconsistency is pre-existing and is about the REST payload, not the SSE payload this entry is about — folding it in would widen the blast radius to the launch endpoint's public response shape for no benefit.server.ts:417'sonAgentCreatedpublish. It looks like this route's publish is redundant with it, but it isn't: the listener fires with the record as of insert, before media seeding, pin/prompt assembly and process launch, and the route publishes the re-fetched final record. Removing either one loses information.TemplateRouteDeps.publishUiEvent: (event: unknown) => void. Eight route dep types declare it asunknownandserver.tscastsevent as UiEventat 13 wiring sites; onlyroutes/reviews.ts:18uses the precise(event: UiEvent) => void. That looseness is exactly why an unflaggedagent.upserttype-checked, but tightening it touches 9 modules and belongs in its own PR. Backlogged.What the included set has in common
Exactly one line changed at a publish site: the payload's
agentvalue. The other 30 sites are untouched; the deps type gains one field with the same signature already used byroutes/agents/shared.ts:32,routes/release.ts:247,server/mcp-handlers.ts:101,server/mcp-review-handlers.ts:97andserver/agent-lifecycle-runtime.ts:15.Test
New
apps/server/test/template-launch-publish.test.ts— bare Fastify +registerTemplateRouteswith a stubbedtemplateServiceand a spypublishUiEvent(thequick-phrases-routes.test.tsshape; the existingtemplate-routes.test.tsboots the full app and can't observe published events). Confirmed non-vacuous: reverting the one-line fix fails the first test and leaves the second passing.Checks
pnpm run checkclean ·pnpm run test2986 passed / 9 skipped, plus 1490 and 60 in the other two suites ·pnpm run test:e2e186 passed / 12 skipped. Server-only change, sofinalize:weband web vitest do not apply.Next run
Backlog entry for
AgentRecord(server) vsAgent(web) divergence — the last big server↔web wire-type gap. This PR unblocks it:hasStreamis now attached at everyagent.upsertpublish site, so a shared agent wire type no longer has to keep it optional on that account.🤖 Generated with Claude Code