feat(forgejo): route triggers to one session per issue or pull request #34

Merged
lucas merged 1 commit from 31-forgejo-session-routing-per-issue-pull-request into main 2026-10-17 01:14:48 +02:00
Collaborator

Webhook triggers on the same (repo, number) now continue one deck session. A new forgejo_sessions table maps each artifact to its session. The dispatcher resumes the mapped session via ensureSessionLive and checks it lazily: if the session is archived, missing, fails to resume, has a different cwd, or its trigger is missing or for another artifact, a new session is created and the mapping is overwritten. That replacement is logged and nothing is posted to Forgejo. Dispatch is now split into gate (unclaimed, so held rows stay pending across restarts) and runReady, driven by a per-repo FIFO worker.

While any deck session in the repo's workspace is open, unattended turns wait. WsHub exposes openSessionIds, onPresenceChange and sendToSession. The new frames are forgejo_held and forgejo_activity (also replayed on subscribe) plus the client frame forgejo_inject. The web chat shows ForgejoBanners: the list of held triggers, an Inject button on the artifact's own session, and the "Forgejo agent is working in this workspace" warning.

Decisions

  • A mapped session is accepted under either trigger kind (forgejo_conversation or forgejo_pull_request) as long as owner/repo/number match. The plan only named forgejo_conversation, but PR triggers create forgejo_pull_request sessions since step 13.
  • A session the dispatcher brought live is disposed after its turn only if it isn't open in the deck at that moment. This applies to unattended and inject runs alike, so a user who opens the session mid-turn keeps it live.
  • The injected seed is sent with streamingBehavior: "followUp" for every trigger turn. On an idle session this makes no difference.
  • Inject failures reuse the existing error frame with sessionId (shown as the session's lastError), so there is no new forgejo_inject_failed frame.
  • inject() is synchronous and returns once the injected turns have started.
  • The migration is 014-forgejo-sessions.sql, because 013 was already taken. It adds an index on session_id for the reverse lookup.

Verification

  • bun test src/forgejo/dispatcher.test.ts src/db (apps/server): 66 pass, 0 fail. New tests cover: one session across triggers; replacement when the mapped session is archived, not found, missing its trigger row, has a different cwd, or has a trigger for another artifact; holding while another workspace session is open, then running and disposing after it closes; Inject running the artifact's triggers in order without disposing while another artifact stays held; the activity warning during an unattended turn; refused inject for unmapped or other-repo sessions.
  • Full bun test in apps/server: 771 pass, 0 fail.
  • bun run typecheck at the root (protocol, server, web): all exit 0.
  • Manual check against a live deck and Forgejo (Inject banner end to end): not run.

Closes #31

Webhook triggers on the same `(repo, number)` now continue one deck session. A new `forgejo_sessions` table maps each artifact to its session. The dispatcher resumes the mapped session via `ensureSessionLive` and checks it lazily: if the session is archived, missing, fails to resume, has a different cwd, or its trigger is missing or for another artifact, a new session is created and the mapping is overwritten. That replacement is logged and nothing is posted to Forgejo. Dispatch is now split into `gate` (unclaimed, so held rows stay `pending` across restarts) and `runReady`, driven by a per-repo FIFO worker. While any deck session in the repo's workspace is open, unattended turns wait. `WsHub` exposes `openSessionIds`, `onPresenceChange` and `sendToSession`. The new frames are `forgejo_held` and `forgejo_activity` (also replayed on subscribe) plus the client frame `forgejo_inject`. The web chat shows `ForgejoBanners`: the list of held triggers, an **Inject** button on the artifact's own session, and the "Forgejo agent is working in this workspace" warning. ## Decisions - A mapped session is accepted under either trigger kind (`forgejo_conversation` or `forgejo_pull_request`) as long as owner/repo/number match. The plan only named `forgejo_conversation`, but PR triggers create `forgejo_pull_request` sessions since step 13. - A session the dispatcher brought live is disposed after its turn only if it isn't open in the deck at that moment. This applies to unattended and inject runs alike, so a user who opens the session mid-turn keeps it live. - The injected seed is sent with `streamingBehavior: "followUp"` for every trigger turn. On an idle session this makes no difference. - Inject failures reuse the existing `error` frame with `sessionId` (shown as the session's `lastError`), so there is no new `forgejo_inject_failed` frame. - `inject()` is synchronous and returns once the injected turns have started. - The migration is `014-forgejo-sessions.sql`, because `013` was already taken. It adds an index on `session_id` for the reverse lookup. ## Verification - `bun test src/forgejo/dispatcher.test.ts src/db` (apps/server): 66 pass, 0 fail. New tests cover: one session across triggers; replacement when the mapped session is archived, not found, missing its trigger row, has a different cwd, or has a trigger for another artifact; holding while another workspace session is open, then running and disposing after it closes; Inject running the artifact's triggers in order without disposing while another artifact stays held; the activity warning during an unattended turn; refused inject for unmapped or other-repo sessions. - Full `bun test` in apps/server: 771 pass, 0 fail. - `bun run typecheck` at the root (protocol, server, web): all exit 0. - Manual check against a live deck and Forgejo (Inject banner end to end): not run. Closes #31
Persist an artifact-to-session mapping and resume it for every trigger on
the same repo#number, replacing it lazily when unusable. Hold unattended
turns while any deck session in the repo's workspace is open, with banners
listing held triggers, an Inject action on the artifact's own session, and
a warning while an unattended turn runs.

Co-Authored-By: Claude Opus 5.5 via omp-deck
lucas merged commit 50c2efc15f into main 2026-10-17 01:14:48 +02:00
lucas deleted branch 31-forgejo-session-routing-per-issue-pull-request 2026-10-17 01:14:49 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/omp-deck!34
No description provided.