agent-orchestrator/backend/internal/httpd
Pritom Mazumdar 3986d488c8
feat(session): support multiple PRs per session (#230)
* feat(session): support multiple PRs per session

A session can now own several pull requests (a root plus stacked
children) instead of being capped at one. The SQLite schema was already
1-session->many-PR (pr.url PK, session_id a plain FK), so this is a
behavioural change across the observe -> persist -> derive -> react
pipeline, not a migration.

- observe: the SCM observer discovers every open PR whose source branch
  matches a session branch or descends from it ("branch/..." stacking),
  attributing each to the owning session; the longest matching branch
  wins so a child session claims its own stacked PRs.
- derive: session status is a worst-wins aggregate over all owned PRs,
  with a stack model (B is a child of A iff B.target == A.source and A is
  open) exposed via prs[] on every session read DTO.
- react: per-PR reactions; a stacked child blocked by an open parent is
  exempt from the rebase/merge-conflict nudge (only the bottom of the
  stack is eligible), and the session completes only when no PR is open
  and at least one merged.
- tests: unit coverage across stack/status/observer/lifecycle, a
  real-SQLite ListPRFactsForSession test for the stacked-PR read path,
  and a functional end-to-end integration test driving the real store +
  lifecycle + observer through attribution, completion, and stacked-child
  nudge suppression.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(scm): ignore fork heads in PR attribution and persist discovered siblings before completion

Branch-prefix attribution now requires a discovered PR's head branch to live
in the project repo. A fork PR can reuse a session's branch name while its
commits live in the fork, so the previous code could auto-claim foreign work.
Carry head repo full_name from the REST list response and skip any PR whose
head repo is not the base repo.

discoverNewPRs also writes each newly discovered PR as an open baseline row
before the refresh/lifecycle pass runs. A session can own several PRs, and a
terminal observation triggers a completion check that reads all of the
session's PRs from the store. Without the early write, an open sibling found
in the same poll was not yet durable and the session could terminate while
that PR was still open.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(session): surface actionable signals from blocked stacked children; clarify worker prompt

Status aggregation previously dropped any open PR blocked by an open parent,
hiding actionable child signals (failing CI, draft, requested changes,
unresolved comments) behind the parent's status. A blocked child still cannot
merge, so its readiness signals (mergeable/approved/review-pending/open) stay
suppressed, but its problem signals now contribute to the worst-wins aggregate.
The all-blocked fallback is preserved so a session never goes dark.

The worker multi-PR prompt said independent PRs could branch off the base
branch as usual, which conflicts with branch-prefix attribution. Clarify that
a PR may target the base branch, but its source branch must stay under the
session branch namespace for AO to track it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-17 17:31:28 +05:30
..
apierr refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96) 2026-06-03 18:09:02 +05:30
apispec feat(session): support multiple PRs per session (#230) 2026-06-17 17:31:28 +05:30
controllers feat(session): support multiple PRs per session (#230) 2026-06-17 17:31:28 +05:30
envelope fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
api.go feat: add notifications v1 (#181) 2026-06-14 20:02:32 +05:30
control_test.go fix(cli): harden daemon control surface and stop CLI from writing the store 2026-06-01 01:55:14 +05:30
cors.go feat(frontend): scaffold for frontend with complimentary backend changes (#168) 2026-06-10 11:40:17 +05:30
cors_test.go feat(frontend): scaffold for frontend with complimentary backend changes (#168) 2026-06-10 11:40:17 +05:30
events.go feat(cdc): add SSE event stream replay (#106) 2026-06-06 19:32:21 +05:30
events_test.go feat(cdc): add SSE event stream replay (#106) 2026-06-06 19:32:21 +05:30
log.go fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
log_test.go fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
logger.go refactor: simplify session lifecycle and zellij runtime (#62) 2026-06-01 08:42:49 +05:30
logger_test.go refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96) 2026-06-03 18:09:02 +05:30
router.go feat(frontend): scaffold for frontend with complimentary backend changes (#168) 2026-06-10 11:40:17 +05:30
server.go refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96) 2026-06-03 18:09:02 +05:30
server_test.go refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96) 2026-06-03 18:09:02 +05:30
terminal_mux.go refactor: simplify session lifecycle and zellij runtime (#62) 2026-06-01 08:42:49 +05:30
terminal_mux_test.go fix(terminal): prevent zellij session resurrection 2026-06-11 16:38:19 +05:30
testhelpers_test.go refactor(backend): LLD maintainability fixes in controllers/service layers (#95) (#96) 2026-06-03 18:09:02 +05:30