FLT-140 — Session transcript + follow-up threading¶
Worktree: wt/WT-6022/FLT-140 (branch off origin/master @ 55529d3). Not merged/pushed.
What shipped¶
Transcript (read)¶
FleetDispatchReader.GetTranscript(cardId)— searches every live target's job + result dirs for the id (a card carries no target of its own) and returns aCardTranscript: the full prompt the agent received, the run's raw response, exit code, status, start/finish times, agent.ExtractPrompt(DispatchJob): forrun_agent/agentjobs, the fullAgentRunRequest.Task(never the 200-charTitle/Summarytruncation the Card list already used). For design-lane jobs (figma/stitch/claude-design/pptx/docx/xlsx/html/pdf via claude/codex/agy/grok/ollama), recovers the exact instructional text the agent received — either the{model,prompt}JSON envelope'spromptfield, or the raw free-text payload.Response=DispatchResult.StdoutTail, falling back toStderrTailon a failed run with empty stdout (same fallbackToRunSummaryalready uses forSummary/Error).FindJobAnywhere(cardId)— public sibling used by follow-up (below) to recover the parent's fullAgentRunRequest(Agent/Repo/Branch/Ticket) for re-dispatch.GET /api/cards/{cardId}/transcript(Endpoints/CardEndpoints.cs) — open read behind the tailnet perimeter like every other GET. 404 when the card is unknown/expired on every target. Artifacts merged in fromArtifactStore.FindArtifactByJob, same enrichmentGET /api/runsalready does.
Follow-up (write)¶
FollowUpComposer(pure, static) — builds the childAgentRunRequest: reuses the parent's Agent/Repo/Branch/Mode/Ticket/RepoId/WorktreePath (continuing the same worktree, like resuming a Claude Code session), stampsParentCardId.ComposeDefaultTaskbuilds the pre-loaded starting text (parent prompt + result summary) when the caller doesn't supply their own.POST /api/cards/{cardId}/follow-up— session-gated write (RequireFleetAuth, same posture asPOST /api/agent-runs). Locates the parent job, 400s if it isn't arun_agentcard (design-lane follow-up is out of scope), composes the child request, validates/normalizes it through the existingAgentRunValidator, dispatches via the existingAgentRunDispatcher.SendAsync(the same file-queue write every other launch uses — no new write path), pushes the sameRunUpdated{dispatched}hub bookendPOST /api/agent-runsdoes.- No changes to
CardRegistry/BuildTree— FLT-122 already threads any card carryingParentCardIdunder its parent and tags itorigin=spawn. This ticket only had to getParentCardIdset on the dispatched job; the session-tree grouping was already correct.
Frontend¶
SessionTreeRowComponent— new "▤" button (always visible, read-only viewers included — it's a read, not a write) emitsopenCard, bubbled throughQueueListComponent→StatusWallComponent.TranscriptPanelComponent(new) — modal overlay: prompt block, response block (scrollable), artifact chips, exit/timing meta row, and a "Follow up" textarea + submit gated onCommandService.state() === 'ready'(hidden with an honest note for read-only viewers, same Kerr-gate pattern every other write surface in this cockpit uses). Reloads via a signaleffect()whenever the selected card changes.CommandService.getTranscript()(read, no session) /.followUp()(session-gated write) — same split asloadRepos()/launch().StatusWallComponentholdsselectedCardand renders the panel as an overlay (not a 4th pane — works identically at every existing breakpoint).- NSwag client (
fleet-api.generated.ts) regenerated from the rebuilt OpenAPI doc —CardTranscript,FollowUpRequest,FollowUpAccepted,getCardTranscript(),followUpOnCard().
Test counts¶
- Backend: 442/442 passed (
dotnet test), up from 419 baseline. New:FollowUpComposerTests(6),FollowUpFlowTests(1 end-to-end: parent run → follow-up dispatch →CardRegistryactually nests the child under the parent, taggedorigin=spawn),GetTranscript/FindJobAnywherecases added toFleetDispatchReaderTests(7). - Frontend: 223/223 passed (
npx vitest run), up from 206 baseline. New/updated:transcript-panel.component.spec.ts(9),session-tree-row/queue-list/command.service/status-wallspecs extended for theopenCardbubble +getTranscript/followUp. dotnet build -warnaserror: clean.ng build: clean.ng lint: clean (one accessibility fix — backdrop dismiss is a real<button>sibling of the dialog, not a clickable<div>needing a stopPropagation/keyboard-handler workaround).
TODOs / known gaps (documented, not silently punted)¶
- Follow-up is
run_agent-only. A design-lane card (figma/stitch/pptx/…) 400s on/follow-up— "continuing a session" maps cleanly onto another CLI run; a design run's continuation semantics (re-plan? re-route?) belong to FLT-19/command-bar territory, not this ticket's scope. Responseis whateverStdoutTailfleet-dispatch published, not a guaranteed-full capture. This repo only reads the dispatch file queue; if fleet-dispatch's listener truncates stdout before writingDispatchResult.StdoutTail, the transcript inherits that truncation. No wiring change was made upstream (out of repo).- HTTP-dispatched runs with no recoverable job file (the pre-existing "job is null" branch in
FleetDispatchReader.ToRunSummary) get a transcript withPrompt = null— documented, not fabricated, matching the existingRepoId/ParentCardIdprecedent for that same case. - Follow-up reuses the parent's exact
Repo/Branch(same worktree) — there's no "follow up onto a fresh branch" option; that's a deliberate scope cut (worktree/branch features are explicitly out of scope per the ticket).
Files touched¶
src/FleetMissionControl.Api/Contracts/CardContracts.cs—CardTranscript,FollowUpRequest,FollowUpAccepted.src/FleetMissionControl.Api/Services/FleetDispatchReader.cs—GetTranscript,FindJobAnywhere,ExtractPrompt,BuildTranscript,FindResult.src/FleetMissionControl.Api/Services/FollowUpComposer.cs— new.src/FleetMissionControl.Api/Endpoints/CardEndpoints.cs— new.src/FleetMissionControl.Api/Program.cs—api.MapCardEndpoints();.tests/FleetMissionControl.Api.Tests/FleetDispatchReaderTests.cs,FollowUpComposerTests.cs(new),FollowUpFlowTests.cs(new).frontend/src/app/components/transcript-panel.component.ts(+spec, new).frontend/src/app/components/session-tree-row.component.ts(+spec) —openCardoutput.frontend/src/app/components/queue-list.component.ts(+spec) — bubblesopenCard.frontend/src/app/components/status-wall.component.ts(+spec) —selectedCard+ panel mount.frontend/src/app/core/command.service.ts(+spec) —getTranscript,followUp.frontend/src/app/api/fleet-api.generated.ts,openapi/FleetMissionControl.Api.json— regenerated.