Skip to content

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 a CardTranscript: the full prompt the agent received, the run's raw response, exit code, status, start/finish times, agent.
  • ExtractPrompt(DispatchJob): for run_agent/agent jobs, the full AgentRunRequest.Task (never the 200-char Title/Summary truncation 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's prompt field, or the raw free-text payload.
  • Response = DispatchResult.StdoutTail, falling back to StderrTail on a failed run with empty stdout (same fallback ToRunSummary already uses for Summary/Error).
  • FindJobAnywhere(cardId) — public sibling used by follow-up (below) to recover the parent's full AgentRunRequest (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 from ArtifactStore.FindArtifactByJob, same enrichment GET /api/runs already does.

Follow-up (write)

  • FollowUpComposer (pure, static) — builds the child AgentRunRequest: reuses the parent's Agent/Repo/Branch/Mode/Ticket/RepoId/WorktreePath (continuing the same worktree, like resuming a Claude Code session), stamps ParentCardId. ComposeDefaultTask builds 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 as POST /api/agent-runs). Locates the parent job, 400s if it isn't a run_agent card (design-lane follow-up is out of scope), composes the child request, validates/normalizes it through the existing AgentRunValidator, dispatches via the existing AgentRunDispatcher.SendAsync (the same file-queue write every other launch uses — no new write path), pushes the same RunUpdated{dispatched} hub bookend POST /api/agent-runs does.
  • No changes to CardRegistry/BuildTree — FLT-122 already threads any card carrying ParentCardId under its parent and tags it origin=spawn. This ticket only had to get ParentCardId set 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) emits openCard, bubbled through QueueListComponentStatusWallComponent.
  • TranscriptPanelComponent (new) — modal overlay: prompt block, response block (scrollable), artifact chips, exit/timing meta row, and a "Follow up" textarea + submit gated on CommandService.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 signal effect() whenever the selected card changes.
  • CommandService.getTranscript() (read, no session) / .followUp() (session-gated write) — same split as loadRepos()/launch().
  • StatusWallComponent holds selectedCard and 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 → CardRegistry actually nests the child under the parent, tagged origin=spawn), GetTranscript/ FindJobAnywhere cases added to FleetDispatchReaderTests (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-wall specs extended for the openCard bubble + 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.
  • Response is whatever StdoutTail fleet-dispatch published, not a guaranteed-full capture. This repo only reads the dispatch file queue; if fleet-dispatch's listener truncates stdout before writing DispatchResult.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 with Prompt = null — documented, not fabricated, matching the existing RepoId/ParentCardId precedent 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.csCardTranscript, FollowUpRequest, FollowUpAccepted.
  • src/FleetMissionControl.Api/Services/FleetDispatchReader.csGetTranscript, FindJobAnywhere, ExtractPrompt, BuildTranscript, FindResult.
  • src/FleetMissionControl.Api/Services/FollowUpComposer.cs — new.
  • src/FleetMissionControl.Api/Endpoints/CardEndpoints.cs — new.
  • src/FleetMissionControl.Api/Program.csapi.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) — openCard output.
  • frontend/src/app/components/queue-list.component.ts (+spec) — bubbles openCard.
  • 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.