FL-06 — Session git & actions · Task breakdown
Ordered, executable tasks derived from
plan.md. Each carries explicit file paths and a test. Every task ships with its tests (Constitution VI). Work top to bottom; tasks marked(parallel)may run alongside the task immediately above them.
Feature ID: FL-06-session-git-actions
Spec: ./spec.md · Plan: ./plan.md
Status: Draft
Last updated: 2026-10-18
How to use
- Paths are repo-relative to the monorepo root; run with
pnpmfrom the root. - Runners:
packages/agent,apps/api,packages/tasks→ Jest;apps/node,packages/contracts,apps/webunit → Vitest;apps/webe2e → Playwright. - No migration in this epic (CONTRACTS §13 has no FL-06 slot). Any task that seems to need a column is wrong — stop and re-read plan §4.
- Blocked by FL-04 (session channel, daemon, start dialog, session detail page) and FL-14
(Checkout rows, git line,
checkouts.*handler). Tasks name what they extend. - One task = one PR. Add new tasks at the bottom; never renumber.
- Commit style:
feat(fleet-sessions): …,feat(node): …,test(fleet): …,chore(i18n): ….
P1.A — Contracts and branch rule
- T-01. Add
packages/contracts/src/fleet/fleet-session-actions.types.ts(action ids, defaults, context, availability, ledger entry, send/changes/PR request shapes, rate and cap constants,renderActionPrompt); export frompackages/contracts/src/fleet/index.ts. Test:packages/contracts/src/fleet/__tests__/fleet-session-actions.spec.ts— placeholder matrix, unknown placeholders left verbatim, every default names the placeholders it needs. - T-02 (parallel). Add the optional
fleet { branchPrefix?, actions? }block to theagent.ymlschema inpackages/agent/src/agents/types.tsand its parse/serialise path inpackages/agent/src/agents/agent-file.service.ts(unknown keys still rejected; block optional). Test: extendpackages/agent/src/agents/__tests__/agent-file.service.spec.ts— parses, round-trips, an invalid action id is refused with the field named. - T-03. Create pure
packages/agent/src/fleet/branch-proposal.ts(proposeBranch(ctx): Task branch reuse viataskBranchNamefrompackages/agent/src/tasks-domain/task-isolation.ts, else<prefix>/<slug>from first message / title / generated name; suffixing against a supplied set of taken names). Test:packages/agent/src/fleet/__tests__/branch-proposal.spec.ts— Task reuse, slug rules (≤ 40 chars, kebab), prefix defaults,-2/-3suffixing, Unicode input. - T-04. Add
GET /api/fleet/sessions/branch-proposalto a newapps/api/src/fleet/fleet-session-actions.controller.ts(taken names =fleet_checkouts.branchfor the remote incl. archived + nodegit ls-remote --headsthrough an optionalremoteHeads: truerequest flag this task adds to FL-14'scheckouts.listinapps/node/src/core/link/modules/checkouts.ts, answered in a new optional reply fieldremoteBranches— additive, FL-14's callers unchanged), DTO inapps/api/src/fleet/dto/fleet-session-actions.dto.ts. The path is one of the §11.2 additions raised in spec §8b. Test:apps/api/src/fleet/fleet-session-actions.controller.spec.ts— proposal shape,unreachablewhen the node cannot list remote heads, cross-user 404. - T-05. Extend FL-04's start dialog (
apps/web/src/components/fleet/sessions/SessionStartDialog.tsx) Where step:apps/web/src/components/fleet/sessions/BranchProposalField.tsx, preselect New worktree for Agent sessions when the folder is a checkout; recordcheckoutIdon the session start request. Test:BranchProposalField.unit.spec.tsx— default selection, suffix note, Task branch read-only.
P1.B — Git line with PR, cadence, changes
- T-06. Create
packages/agent/src/fleet/fleet-pr-enrichment.service.ts(PR by head branch throughGitFacadeService, state, checks roll-up, unresolved review comments, mergeable; writes only the CONTRACTS §4.7 fields{ number, state, checks, url }tofleet_checkouts.gitLine.prand keepsunresolvedComments/mergeablein the 60 s per-checkout cache that the relevance rules read) andpackages/tasks/src/tasks/trigger/fleet-pr-enrichment.task.ts(every minute + ingest nudge ongithub.prevents fromapps/api/src/ingest/github/github-pr-review-bridge.service.ts), dispatcher symbol bound inapps/api/src/fleet/fleet.module.ts. Test:packages/agent/src/fleet/__tests__/fleet-pr-enrichment.service.spec.ts(cache, state mapping, unknown on provider error with age),packages/tasks/src/__tests__/fleet-pr-enrichment.task.spec.ts. - T-07 (parallel). Drive the git-line cadence from session
session.statuschanges: in FL-04'spackages/agent/src/fleet/sessions/fleet-session.service.tscall FL-14's refresh every 60 s whileworkingand once ondone/ended(checkouts.list { refresh: true, checkoutIds }). Test: extendpackages/agent/src/fleet/sessions/__tests__/fleet-session.service.spec.ts— timer starts/stops with state, stop refresh happens exactly once. - T-08. Add
checkouts.changestoapps/node/src/core/link/modules/checkouts.tsandapps/node/src/core/checkouts/checkout-changes.ts(porcelain v2 +git diff HEAD, caps, binary without patch,.ever-works/and.mounts/excluded). The verb is in the CONTRACTS §3.3machinevocabulary (rev 2). Test:apps/node/src/core/checkouts/checkout-changes.spec.ts— caps per file and total, omitted counts, binary, excluded dirs, no HTML in output. - T-09. Add
GET /api/fleet/checkouts/:id/changestofleet-session-actions.controller.ts(familycheckouts, consent check,maxBytesclamp). Test: extend the controller spec —consent_missing,unreachable, clamp. - T-10. Web:
apps/web/src/components/fleet/sessions/PrPill.tsx(reusessafePrUrlfromapps/web/src/components/tasks/TaskPrPill.tsx), add theprslot toapps/web/src/components/fleet/checkouts/GitLine.tsx, wire the git line + pill into FL-05'sapps/web/src/components/fleet/sessions/SessionCard.tsxfooter slots and FL-04's session detail header;ChangesPanel.tsxwith the text-only rules ofTaskDiffSheet.tsx; BFF routesapps/web/src/app/api/fleet/checkouts/[id]/changes/route.ts,apps/web/src/app/api/fleet/sessions/branch-proposal/route.ts. Test:PrPill.unit.spec.tsx(tones, invalid URL not rendered),ChangesPanel.unit.spec.tsx(text node only, omission footer),GitLine.unit.spec.tsxextended (pr slot, staleness). - T-11. i18n
fleet.sessionGit.{gitLine,prPill,changes,branch}inapps/web/messages/en.json; writeapps/web/e2e/flow-fleet-session-git.spec.tsP1 flows (Where default, suffix note, git line on card and detail, Changes panel). Test: e2e green in CI; no literal strings incomponents/fleet/sessions/.
P2.A — Actions service and delivery
- T-12. Create pure
packages/agent/src/fleet/session-action-rules.ts(availableActions(ctx)per spec FR-15, incl. merge-policyprotectedBranchesfrompackages/agent/src/policy/merge-policy.service.ts). Test:packages/agent/src/fleet/__tests__/session-action-rules.spec.ts— full truth table; shell provider → none; outside checkout → recap only. - T-13. Create
packages/agent/src/fleet/fleet-session-action.service.ts(list,send: render with the Agent'sagent.ymloverride or default →session.input { source:'action', actionId }via FL-04's link service → for an Agent session a ledger row inagent_run_logs(step: 'fleet.action',level: 'INFO') → for both kinds an auditnode.session.inputwithdetails.actionand a redacted 200-charpromptPreview; owner-session ledger read from those audit rows (spec §8b default); 10/min and 2 s refusals; echo →deliveredAt;resend). Test:packages/agent/src/fleet/__tests__/fleet-session-action.service.spec.ts— every refusal code, ledger + audit per Agent-session send, audit-only ledger for an owner session with "session total only" cost, override precedence, delivered/undelivered, resend only when undelivered, kill switch. - T-14. Extend FL-04's daemon input path to accept
sourceandactionIdonsession.input(apps/node/src/core/sessions/daemon/daemon-protocol.ts), logging the action id, writing prompt + newline to the PTY. Test: extendapps/node/src/core/sessions/daemon/__tests__/daemon-protocol.spec.ts— field optional, bytes identical to typed input. - T-15. Add
GET/POST /api/fleet/sessions/:id/actions,GET …/actions/ledger,POST …/actions/:ledgerId/resendtofleet-session-actions.controller.ts; DTOs. Test: extend the controller spec —invalidwith the hidden rule named,busy,limit_exceeded,permission_denied(audiencesessions),consent_missingper kind. - T-16. Create
packages/tasks/src/tasks/trigger/fleet-action-undelivered-sweep.task.ts(every 2 min; Agent-session ledger rows without echo after 5 s →undelivered). Test:packages/tasks/src/__tests__/fleet-action-undelivered-sweep.task.spec.ts.
P2.B — Pull requests, credential in session
- T-17. Create
packages/agent/src/fleet/fleet-pull-request.service.ts: Task-bound session →TaskWorkspaceService.finalizeRemotePush(packages/agent/src/tasks-domain/task-workspace.service.ts:1842); free session → newopenForBranchbeside it; update when a PR exists; refuse on default branch or unpushed head;redactSecretson title/body; AgentcanOpenPullRequestsand the Node'scheckoutsaudience checked; never calls merge. Test:packages/agent/src/fleet/__tests__/fleet-pull-request.service.spec.ts— both paths, update vs open, each refusal, Task →in_review+prUrl, body redacted, no merge call (spy). - T-18. Add
POST/GET /api/fleet/checkouts/:id/pull-requestaccepting a person session or the run-scopedew_run_…bearer (apps/api/src/auth/guards/auth-session.guard.tspath) scoped to the token's own session checkout; whitelist the route for the node MCP bridge inapps/mcp/src/openapi-tools/whitelist.tsasfleet_checkouts_open_pull_request. Test: extend the controller spec — run token on a foreign checkout →permission_denied; person path 202;apps/mcpwhitelist spec extended. - T-19. Node:
apps/node/src/core/sessions/session-credential-helper.ts— per-sessionGIT_CONFIG_COUNT/GIT_CONFIG_KEY_n=credential.helperpointing at a node-local helper that answersgit credential fillwith the run-scoped push credential (apps/node/src/core/workspaces/push-credential.ts) while the lease is alive; Agent sessions only. Test:apps/node/src/core/sessions/session-credential-helper.spec.ts— answers only for the covered host/repo and only while leased; owner sessions get no helper; nothing written to any git config file. - T-20. Web:
SessionActionsMenu.tsx,SessionActionSheet.tsx,SessionActionsLedger.tsx,OpenPullRequestDialog.tsx,session-git.shared.tsunderapps/web/src/components/fleet/sessions/; wire Actions ▾ beside End on the session detail; keyboardA/D/G/1–7; BFF routes underapps/web/src/app/api/fleet/sessions/[id]/actions/**and.../checkouts/[id]/pull-request/route.ts. Test:SessionActionsMenu.unit.spec.tsx(hidden reasons, shell → none, protected warning),SessionActionSheet.unit.spec.tsx(editable, cost note, needs-you warning),session-git.shared.unit.spec.ts. - T-21. Cost: FL-20's scan (
packages/agent/src/fleet/fleet-usage-attribution.service.ts) patchestokens/costCentsontofleet.actionledger rows between consecutive marks; the ledger shows "unknown" until then. Test: extend FL-20's attribution spec — two marks split a session's tokens; no marks → untouched. - T-22. i18n
fleet.sessionGit.{actions,sheet,pullRequestDialog,ledger}; extendapps/web/e2e/flow-fleet-session-git.spec.ts(menu relevance, send → echo → ledger, PR dialog) and writeapps/web/e2e/flow-fleet-session-actions-contract.spec.ts. Test: green in CI.
P3 — Clean up, tools, editor
- T-23. Clean up over FL-14's delete (no new route): add the
cleanUp=1gate (worktree, PR merged/closed, no live session → elseinvalidwith the rule named) to FL-14'sDELETE /api/fleet/checkouts/:idinapps/api/src/fleet/fleet-checkouts.controller.ts, andCleanUpButton.tsxon FL-05's card and FL-14'sCheckoutRow.tsx(FL-14's delete preview → confirmation → delete,mode: 'delete'). Test: extendapps/api/src/fleet/fleet-checkouts.controller.spec.ts(gating codes; no gate withoutcleanUp=1),CleanUpButton.unit.spec.tsx(visibility matrix), FL-14 handler spec — branch kept. - T-24. Tools
fleet_sessions_actions_list|action,fleet_checkouts_changesinpackages/agent/src/fleet/agent-fleet-tools.tswith §11.3 annotations (Agent Clean up is FL-14'sfleet_checkouts_deletewithcleanUp); MCP whitelist entries; CLIapps/cli/src/commands/fleet/sessions.ts(action,changes,pull-request) and a--clean-upflag on FL-14'sapps/cli/src/commands/fleet/checkouts.tsdelete. Test:agent-fleet-tools.spec.tsextended (annotations, audience refusals), CLI command specapps/cli/src/commands/fleet/__tests__/sessions.spec.ts(--json, exit codes). - T-25.
apps/web/src/components/agents/AgentInstructionsEditor.tsx— Fleet actions section over theagent.ymlpill: seven template fields with the default beside each, branch prefix; writes through the existing agent-file save path. Test:AgentInstructionsEditor.unit.spec.tsxextended — override round-trip, default shown, invalid placeholder warning.
P4 — Docs and rollout
- T-26. Write
docs/features/fleet-session-git.md(git line, worktree default and naming, actions and templates, pull requests via the GitHub App, clean up, cost); add toapps/docs/sidebarsPlatform.tsbeside'features/fleet'(:183); cross-link fromdocs/features/sessions-and-steering.mdanddocs/features/task-isolation.md. Test: docs build has no broken-link warning. - T-27. Update
docs/specs/features/fleet/TRACKER.md; add the §11.2 paths and the owner-session ledger resolution raised in spec §8b to CONTRACTS in the same PR (the §3.3 and §10 entries already landed in CONTRACTS rev 2); run the banned-terms grep over the folder. Test: grep prints nothing. - T-28.
pnpm format && pnpm lint && pnpm test && pnpm build; boot the API withFLEET_SESSIONS_ENABLED=trueand send one action against a stubbed node in the e2e stack. Test: all green; the ledger row exists withdeliveredAt.
Definition of done
- Every checkbox ticked; ACC-FL06-01 … ACC-FL06-19 verified on
developwith the flag on. - No merge call exists in any FL-06 code path (asserted by test); no provider credential reaches a session; no prompt text in ledger metadata.
pnpm format:check,pnpm lint, docs build clean; Constitution gates in spec §10 confirmed.