Add GitHub integration for PRs, issues and AI PR description (#205)

* feat: integrate GitHub OAuth device flow across runtimes

Add GitHub OAuth device flow endpoints across runtimes
Introduce GitHubSettings UI panel and sidebar entry
Persist GitHub auth state in per-runtime storage

* feat: add GitHub PR status and PR description generation

Show PR status for the current branch in the Git view
Generate a pull request description from the diff between base and head
Expose prStatus, prCreate, and prMerge APIs in web and desktop clients

* feat: add GitHub PR ready for review

Add API to mark pull requests as ready for review
Show a Ready button for draft PRs and reflect status in UI
Handle token expiration and GraphQL errors when marking ready
This commit is contained in:
Bohdan Triapitsyn
2026-01-23 16:08:58 +02:00
committed by GitHub
parent 0e715be7d6
commit 463e9ec4e3
43 changed files with 4999 additions and 106 deletions
+302
View File
@@ -0,0 +1,302 @@
# GitHub Features Plan (PRD-ish)
Goal: implement GitHub-powered workflows (PR panel + start sessions from Issue/PR) on top of the existing GitHub auth foundation.
Non-goals (for this phase)
- Rebuild auth/token storage (already implemented)
- Rebuild worktree lifecycle/cleanup (already implemented)
- Add hard context size caps/fallback logic (explicitly out of scope)
## Existing Primitives (MUST reuse)
These already exist; do not reinvent.
GitHub auth + connected user
- UI: `packages/ui/src/components/sections/openchamber/GitHubSettings.tsx`
- Runtime API: `GitHubAPI` in `packages/ui/src/lib/api/types.ts`
- Web endpoints: `packages/web/server/index.js` (`/api/github/*`)
- Desktop Tauri commands: `packages/desktop/src-tauri/src/commands/github.rs`
- VS Code bridge + storage: `packages/vscode/src/bridge.ts`, `packages/vscode/src/githubAuth.ts`
Projects and directories
- Projects store (one project == one repo path): `packages/ui/src/stores/useProjectsStore.ts`
- Active project selection is already used across the app; use it to scope all GitHub operations.
Worktree sessions
- Worktree creation and session wiring:
- `packages/ui/src/lib/worktreeSessionCreator.ts`
- Reuse `createWorktreeSession()` and `createWorktreeSessionForBranch(projectDirectory, branchName)`.
- Worktree cleanup/delete behavior is already present in session deletion flow:
- `packages/ui/src/components/session/SessionDialogs.tsx`
“Synthetic parts” / hidden context in chat
- SDK supports `TextPartInput.synthetic?: boolean`:
- `node_modules/@opencode-ai/sdk/dist/v2/gen/types.gen.d.ts`
- UI filters synthetic parts out of rendering:
- `packages/ui/src/lib/messages/synthetic.ts`
- Existing “seed new session from assistant answer” uses a hidden preface part:
- `packages/ui/src/stores/useSessionStore.ts` (`createSessionFromAssistantMessage`)
- `packages/ui/src/lib/opencode/client.ts` (`sendMessage({ prefaceText })`)
Git tab layout
- Git view where PR panel will be inserted:
- `packages/ui/src/components/views/GitView.tsx`
- Changes + Commit already exist; History is below.
## Guiding Principles
- One directory/project == one git repo. All GitHub actions are scoped to the active project.
- Do not show synthetic context to the user; only show the human prompt.
- If the user lacks permission for an operation (merge, create branch, etc), degrade to “Open in GitHub”.
- Prefer server-side Octokit for web runtime; for desktop/vscode, implement equivalent runtime commands/bridge handlers.
- Keep UI consistent with existing patterns (provider OAuth, worktree sessions, commit message generation).
## Feature A: Git Tab PR Panel (Create / Status / Merge)
### Intent
While working on a feature branch, show PR status and actions inside the Git tab, without leaving the app.
### Placement
Insert a new section between Commit and History in `packages/ui/src/components/views/GitView.tsx`.
### Visibility Rules
- Show only if:
- repo is detected (`isGitRepo === true`)
- branch is not the “base branch”
Base branch source (reuse existing config):
- `activeProject.worktreeDefaults.baseBranch` from `packages/ui/src/stores/useProjectsStore.ts`
- fallback to `status.tracking` remote HEAD logic later if needed (not required for v1).
### UI States
1) GitHub not connected
- Show CTA: “Connect GitHub” (link to Settings -> OpenChamber -> GitHub).
2) Connected but repo not resolvable to GitHub
- Show “Open remote in browser” if remote URL exists.
- Show error text explaining remote must be GitHub.
3) PR does not exist for current branch
- Show create form:
- base branch (default: baseBranch)
- title (default from branch name)
- draft toggle
- description textarea
- “Generate description” button (AI)
- “Create PR” button
4) PR exists
- Show summary:
- state (draft/open/merged)
- PR number + title
- checks summary
- mergeability (if available)
- “Open in GitHub”
- If user has merge permission and PR is mergeable:
- merge method dropdown (merge/squash/rebase)
- “Merge” button
- If cannot merge:
- disable merge button + show “Open in GitHub”
### AI “Generate description”
Mirror commit message generation approach.
Inputs:
- base branch
- branch name
- git diff for base...HEAD
- optionally selected files
Output:
- title suggestion (optional)
- body with sections:
- Summary
- Testing
- Notes
Reuse the same LLM infra used by commit generation in `packages/ui/src/components/views/GitView.tsx` (search for existing generation call and parallel it).
### Required GitHub API Calls
- Resolve repo from git remote URL (origin)
- Find PR by head branch
- Create PR
- Get PR details + checks
- Merge PR
### Implementation Notes
- Web runtime should use server endpoints + Octokit (token stays server-side).
- Desktop/vscode should use their runtime handlers (similar to GitHub auth) to avoid exposing token.
## Feature B: Start Session From GitHub Issue
### Intent
Create a new session seeded with issue context, without polluting chat with large issue bodies/comments.
### Entry Point
Session kebab menu in `packages/ui/src/components/session/SessionSidebar.tsx`.
Add new item:
- “New session from GitHub issue…”
### Modal UI
Issue picker modal:
- list issues for current repo (open by default)
- search by title/number
- direct input:
- full URL
- `#123` or `123`
- checkbox: “Create in worktree”
### Worktree option
If enabled:
- create a worktree session (reuse `createWorktreeSessionForBranch`)
- branch naming convention:
- `issue-<number>-<slug>` (slug derived from title)
- base branch:
- `activeProject.worktreeDefaults.baseBranch`
If disabled:
- create normal session in project root directory.
### Session Bootstrap (message)
Send a single user message with:
1) Visible text part: concise prompt, e.g.
- “Review the issue, clarify requirements, propose plan, then implement.”
2) Hidden synthetic parts: issue payload
- issue title/body
- labels, assignees, author
- comments (ordered)
- metadata (repo, number, url)
This must use SDK-supported `TextPartInput.synthetic = true` so it is not rendered.
Do not invent a new hidden-context mechanism.
### Required GitHub API Calls
- List issues
- Get issue by number
- List issue comments
## Feature C: Start Session From GitHub PR (with worktree checkout)
### Intent
Create a session seeded with PR context, with optional worktree checkout of PR branch (including forks).
### Entry Point
Session kebab menu in `packages/ui/src/components/session/SessionSidebar.tsx`.
Add new item:
- “New session from GitHub PR…”
### Modal UI
PR picker modal:
- list open PRs
- search by title/number
- direct input:
- full URL
- `#123` or `123`
- checkbox: “Create session in PR worktree”
### Worktree behavior
If enabled:
- if PR is from same repo:
- fetch head branch
- create worktree from that branch (reuse `createWorktreeSessionForBranch`)
- if PR is from fork:
- add temporary remote (derived from PR head repo clone URL)
- fetch `<head.ref>`
- create worktree
Fallbacks:
- if fetch/remote fails or permission denied:
- still create a normal session with PR context
- show toast with “Open in GitHub” action
### Session Bootstrap (message)
Same synthetic-parts approach as Issues.
Hidden parts should include:
- PR title/body
- PR comments + review comments
- changed files list
- optionally full diff (explicitly no caps)
- checks/status summary
Visible prompt text should instruct:
- review PR intent
- identify risks
- propose changes
- optionally implement changes in checked-out branch/worktree
### Required GitHub API Calls
- List PRs
- Get PR
- List issue comments for PR
- List review comments
- List files
- Get checks/status
## Cross-cutting: “Synthetic Parts” Sending API
Current behavior:
- `opencodeClient.sendMessage()` supports `prefaceText` which becomes a separate `TextPartInput`.
- There is no first-class way (yet) to mark arbitrary parts as `synthetic: true` from callsites.
Required change (shared for Features B/C):
- Extend `opencodeClient.sendMessage()` (in `packages/ui/src/lib/opencode/client.ts`) to support synthetic parts.
Recommended minimal API change:
- allow `prefaceTextSynthetic?: boolean` (default true when used for hidden context)
- allow `additionalParts?: Array<{ text: string; synthetic?: boolean; files?: ... }>`
- ensure generated `TextPartInput` includes `synthetic` when requested
This should reuse the existing filtering/rendering logic (no new UI hacks).
## Cross-cutting: Repo Resolution
Need a single helper to map current project repo -> GitHub owner/repo.
Inputs:
- project directory root
- git remote URL (origin)
Behavior:
- support common GitHub URL formats:
- `git@github.com:OWNER/REPO.git`
- `https://github.com/OWNER/REPO.git`
- `https://github.com/OWNER/REPO`
Output:
- `{ owner, repo }` or null
Use this for all GitHub feature endpoints.
## Cross-cutting: Permission / Fallback Rules
- Merge button enabled only if merge endpoint succeeds or mergeability indicates allowed.
- If not allowed:
- show “Open in GitHub” as primary action
- For PR worktrees from forks:
- if remote add/fetch fails => create normal session + “Open in GitHub”
## Work Breakdown (Suggested Order)
Phase 1: Shared plumbing
1) Repo resolution helper (remote URL -> owner/repo)
2) New message sending helper supporting `synthetic: true` parts
3) GitHub endpoints/commands for issue + PR fetch (read-only)
Phase 2: Session bootstrap flows
4) Issue picker modal + session bootstrap
5) PR picker modal + session bootstrap
6) PR worktree checkout (fork support)
Phase 3: Git tab PR panel
7) PR detect/status in Git tab
8) Create PR from branch
9) AI generate PR description
10) Merge (with fallback)
## Open Questions (for later)
- PR description generator prompt format: do we want the exact same “highlights” UI as commit gen, or a single-shot body generation?
- Worktree naming collision strategy for PR-based worktrees (owner/ref collisions) beyond current `sanitizeWorktreeSlug`.
+166
View File
@@ -0,0 +1,166 @@
# GitHub Integration (Auth Foundation)
This repo now has a GitHub auth foundation intended to be reused by all future GitHub features (PRs/issues/worktrees/etc).
It provides:
- GitHub OAuth Device Flow connect UX
- persistent token storage per runtime
- a small runtime API surface for UI
- server-side Octokit usage (web runtime)
## Scopes
Default scopes requested:
```
repo read:org workflow read:user user:email
```
Notes:
- Email is fetched from `/user` when available, otherwise `/user/emails` (requires `user:email`).
- Actions performed via this OAuth token are performed “as the user” (not a bot), but the OAuth App is visible under GitHub “Authorized OAuth Apps”.
## UI
Settings entry:
- `packages/ui/src/components/sections/openchamber/GitHubSettings.tsx`
Behavior:
- shows connected user card (avatar + name/email/login)
- Connect triggers Device Flow and polls until authorized
- Disconnect clears the stored token
## Runtime API (UI)
`RuntimeAPIs.github` is optional (some environments may not expose it).
Types:
- `packages/ui/src/lib/api/types.ts` (`GitHubAPI`, `GitHubAuthStatus`, `GitHubDeviceFlowStart`, `GitHubDeviceFlowComplete`)
Methods:
- `authStatus(): { connected, user?, scope? }`
- `authStart(): { deviceCode, userCode, verificationUri, verificationUriComplete?, expiresIn, interval, scope? }`
- `authComplete(deviceCode): { connected: true, user, scope? } | { connected: false, status?, error? }`
- `authDisconnect(): { removed: boolean }`
- `me?(): user` (optional, mostly for debugging)
Implementations:
- Web: `packages/web/src/api/github.ts`
- Desktop: `packages/desktop/src/api/github.ts` (calls Tauri commands)
- VS Code: `packages/vscode/webview/api/github.ts` (bridge messages)
## Web Runtime (Express server)
Endpoints (JSON):
- `GET /api/github/auth/status`
- returns `{ connected: false }` or `{ connected: true, user, scope }`
- `POST /api/github/auth/start`
- returns device flow payload:
- `{ deviceCode, userCode, verificationUri, verificationUriComplete?, expiresIn, interval, scope }`
- `POST /api/github/auth/complete`
- request: `{ deviceCode }`
- returns either pending or success:
- pending: `{ connected: false, status, error }`
- success: `{ connected: true, user, scope }`
- `DELETE /api/github/auth`
- clears stored token
- returns `{ success: true, removed: boolean }`
- `GET /api/github/me`
- returns the authenticated user summary
Code:
- endpoints: `packages/web/server/index.js`
- token store + config defaults: `packages/web/server/lib/github-auth.js`
- Octokit factory: `packages/web/server/lib/github-octokit.js`
- device flow helpers: `packages/web/server/lib/github-device-flow.js`
## Desktop Runtime (Tauri)
Tauri commands:
- `github_auth_status`
- `github_auth_start`
- `github_auth_complete` (param: `deviceCode`)
- `github_auth_disconnect`
- `github_me`
Code:
- `packages/desktop/src-tauri/src/commands/github.rs`
- wired in invoke handler: `packages/desktop/src-tauri/src/main.rs`
## VS Code Runtime
Bridge message types handled in extension:
- `api:github/auth:status`
- `api:github/auth:start`
- `api:github/auth:complete`
- `api:github/auth:disconnect`
- `api:github/me`
Code:
- storage + device flow + `/user` fetch: `packages/vscode/src/githubAuth.ts`
- bridge handlers: `packages/vscode/src/bridge.ts`
## Token Storage
- Web/server runtime: `~/.config/openchamber/github-auth.json`
- file mode `0600` best-effort
- Desktop runtime: `~/.config/openchamber/github-auth.json`
- file mode `0600` best-effort
- VS Code runtime: `${extensionGlobalStorage}/github-auth.json`
- file mode `0600` best-effort
Stored fields (current shape; can evolve):
```json
{
"accessToken": "…",
"scope": "…",
"tokenType": "bearer",
"createdAt": 1730000000000,
"user": {
"login": "…",
"id": 123,
"avatarUrl": "…",
"name": "…",
"email": "…"
}
}
```
## Official OAuth App
Default OAuth client id is baked in:
- `Ov23liNd8TxDcMXtAHHM`
Overrides:
- Web/server: `OPENCHAMBER_GITHUB_CLIENT_ID` (env)
- Scopes override (web/server): `OPENCHAMBER_GITHUB_SCOPES` (env)
Note: UI editing of client id/scopes was intentionally removed to reduce user confusion.
## How to Use in New Features
Preferred pattern:
- UI triggers new feature flows.
- Backend (web server or desktop/vscode runtime command/bridge) performs GitHub API calls using the stored token.
- Do not expose the token to the UI.
Web/server feature endpoints should:
- require `{ connected: true }` state (return 401 if not connected)
- use Octokit with `auth` set to stored token
- accept repo/issue/pr identifiers from UI and fetch needed context
Future “context bootstrap” idea:
- Add endpoints that take `{ owner, repo, number }` and return:
- issue/PR body
- comments
- changed files (PR)
- diff/patch summary
Then UI can start a session with a prefilled prompt.