Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/api/src/handlers/tasks/manageSourceControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export async function manageSourceControl(
);
case 'get_pull_request':
case 'list_pull_request_comments':
case 'list_pull_requests':
return c.json(
await readSourceControlPullRequestForTaskRun({
taskRun,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 22 additions & 3 deletions apps/worker/src/mcp/roomote-mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,8 @@ roomoteMcpServer.registerTool(
'Provider-neutral pull request/merge request operations for the current task. ' +
'Use action "create_or_update_pull_request" after committing and pushing a branch; ' +
'when an open PR/MR already exists for sourceBranch, targetBranch may be omitted and defaults to its current base. ' +
'Use action "get_pull_request" to read PR/MR details (state, branches, head/base SHAs) and ' +
'Use action "get_pull_request" to read PR/MR details (state, branches, head/base SHAs), ' +
'"list_pull_requests" to list open PRs/MRs in a repository (summaries with branches, labels, and mergeability where the provider exposes it), and ' +
'"list_pull_request_comments" to read review threads (with resolution state) and issue comments. ' +
'Use "reply_to_pull_request_comment" to answer a review thread, "create_pull_request_comment" for a top-level comment, ' +
'"resolve_pull_request_thread" to resolve or reopen a thread, and "submit_pull_request_review" to approve, request changes, or leave a review comment. ' +
Expand All @@ -605,6 +606,7 @@ roomoteMcpServer.registerTool(
.enum([
'create_or_update_pull_request',
'get_pull_request',
'list_pull_requests',
'list_pull_request_comments',
'reply_to_pull_request_comment',
'create_pull_request_comment',
Expand All @@ -613,7 +615,7 @@ roomoteMcpServer.registerTool(
'update_pull_request_comment',
])
.describe(
'create_or_update_pull_request creates or refreshes the PR/MR for a branch; get_pull_request reads PR/MR details; list_pull_request_comments reads review threads and issue comments; reply_to_pull_request_comment answers a review thread; create_pull_request_comment posts a top-level comment; resolve_pull_request_thread resolves or reopens a thread; submit_pull_request_review approves, requests changes, or leaves a review comment; update_pull_request_comment edits an existing comment in place.',
'create_or_update_pull_request creates or refreshes the PR/MR for a branch; get_pull_request reads PR/MR details; list_pull_requests lists open PRs/MRs in the repository; list_pull_request_comments reads review threads and issue comments; reply_to_pull_request_comment answers a review thread; create_pull_request_comment posts a top-level comment; resolve_pull_request_thread resolves or reopens a thread; submit_pull_request_review approves, requests changes, or leaves a review comment; update_pull_request_comment edits an existing comment in place.',
),
repositoryFullName: z
.string()
Expand All @@ -626,7 +628,22 @@ roomoteMcpServer.registerTool(
.positive()
.optional()
.describe(
'Required for every action except create_or_update_pull_request: the PR/MR number (GitLab iid).',
'Required for every action except create_or_update_pull_request and list_pull_requests: the PR/MR number (GitLab iid).',
),
state: z
.literal('open')
.optional()
.describe(
'Optional filter for list_pull_requests. Only "open" is supported; open pull requests are listed either way.',
),
limit: z
.number()
.int()
.positive()
.max(200)
.optional()
.describe(
'Optional cap for list_pull_requests on how many pull requests to return (default 100, max 200).',
),
threadId: z
.string()
Expand Down Expand Up @@ -715,6 +732,8 @@ roomoteMcpServer.registerTool(
action: params.action,
repositoryFullName: params.repositoryFullName,
prNumber: params.prNumber,
state: params.state,
limit: params.limit,
threadId: params.threadId,
commentId: params.commentId,
resolved: params.resolved,
Expand Down
15 changes: 15 additions & 0 deletions apps/worker/src/mcp/roomote-mcp-server/source-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type ManageSourceControlParams = {
action:
| 'create_or_update_pull_request'
| 'get_pull_request'
| 'list_pull_requests'
| 'list_pull_request_comments'
| 'reply_to_pull_request_comment'
| 'create_pull_request_comment'
Expand All @@ -20,6 +21,8 @@ type ManageSourceControlParams = {
| 'update_pull_request_comment';
repositoryFullName: string;
prNumber?: number;
state?: 'open';
limit?: number;
threadId?: string;
commentId?: string;
resolved?: boolean;
Expand Down Expand Up @@ -92,6 +95,18 @@ export async function handleManageSourceControl(
});
}

if (params.action === 'list_pull_requests') {
return jsonResult(
await readSourceControl(config, taskId, {
action: params.action,
repositoryFullName: params.repositoryFullName,
state: params.state,
limit: params.limit,
sourceControlProvider: params.sourceControlProvider,
}),
);
}

if (
typeof params.prNumber !== 'number' ||
!Number.isInteger(params.prNumber) ||
Expand Down
10 changes: 8 additions & 2 deletions apps/worker/src/mcp/roomote-mcp-server/tasks-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,15 @@ export async function readSourceControl(
config: RoomoteConfig,
taskId: string,
params: {
action: 'get_pull_request' | 'list_pull_request_comments';
action:
| 'get_pull_request'
| 'list_pull_request_comments'
| 'list_pull_requests';
repositoryFullName: string;
prNumber: number;
// Required for the single-PR actions; unused by list_pull_requests.
prNumber?: number;
state?: 'open';
limit?: number;
sourceControlProvider?: SourceControlProvider;
},
): Promise<SourceControlPullRequestReadResponse> {
Expand Down
3 changes: 2 additions & 1 deletion apps/worker/src/mcp/roomote-mcp-server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export interface SourceControlPullRequestReadResponse {
success: true;
provider: SourceControlProvider;
repositoryFullName: string;
number: number;
/** Present for single-PR reads; list_pull_requests returns pullRequests instead. */
number?: number;
warnings: string[];
[key: string]: unknown;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type GithubPrConflictResolveTask,
getSkillCommandDelimiter,
resolveSourceControlProviderFromPayload,
} from '@roomote/types';
import type { ResolvedTaskCommitAuthor } from '../commit-author';

Expand All @@ -23,12 +24,16 @@ export function githubPrConflictResolve({
const {
payload: { repo, prNumber, prTitle, prUrl, headRef, baseRef },
} = taskSpec;
// The payload kind stays GithubPrConflictResolve for every provider; the
// stamped payload provider decides which surface the task reports under.
const provider = resolveSourceControlProviderFromPayload(taskSpec.payload);
const delimiter = getSkillCommandDelimiter(taskSpec.harness);
const description = buildStructuredTaskRequest({
command: `${delimiter}resolve-github-pr-merge-conflicts`,
activeAppendixPath: 'resolve-github-pr-merge-conflicts',
taskContext: {
repository: repo,
source_control_provider: provider,
pull_request_number: prNumber,
workflow: 'pr_conflict_resolve',
task_link_follow: `[Follow](${taskRunUrl})`,
Expand All @@ -45,7 +50,7 @@ export function githubPrConflictResolve({
return standardTask({
description,
repo,
taskSurface: 'github',
taskSurface: provider,
taskRunUrl,
attribution,
requestFormat: 'structured',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ You are a PR merge-conflict resolver. Merge the base branch into the target PR b
<title>Fetch PR context and prepare the merge</title>
<description>Read the PR title, body, and branch names before starting the merge flow.</description>
<actions>
<action>Fetch PR info with `gh pr view <PR_NUMBER> --json title,body,headRefName,baseRefName`.</action>
<action>Check out the PR and merge the base branch into it with `gh pr checkout <PR_NUMBER> --force`, `git fetch origin <baseRefName>`, and `GIT_EDITOR=true git merge --no-ff --no-edit origin/<baseRefName>`.</action>
<action>On GitHub, fetch PR info with `gh pr view <PR_NUMBER> --json title,body,headRefName,baseRefName` and check out the PR with `gh pr checkout <PR_NUMBER> --force`.</action>
<action>On any other provider (`source_control_provider` in the task context is not `github`), do not use `gh`: the task context already carries the PR title, URL, and branch names, so run `git fetch origin <headRefName>` and `git checkout -B <headRefName> origin/<headRefName>` instead.</action>
<action>Merge the base branch into the checked-out PR branch with `git fetch origin <baseRefName>` and `GIT_EDITOR=true git merge --no-ff --no-edit origin/<baseRefName>`.</action>
<action>If a merge is already in progress, inspect `git status` first and continue the existing merge when it is already resolving this PR; abort only when the in-progress merge is stale or unrelated to the requested PR.</action>
<action>If a stale rebase from an older run is in progress, abort it with `git rebase --abort` before starting the merge flow.</action>
<action>Identify conflicts with `git diff --name-only --diff-filter=U`.</action>
Expand Down Expand Up @@ -182,8 +183,9 @@ You are a PR merge-conflict resolver. Merge the base branch into the target PR b
</workflow>

<git_command_reference>
<command purpose="Get PR info">`gh pr view <N> --json title,body,headRefName,baseRefName`</command>
<command purpose="Checkout PR">`gh pr checkout <N> --force`</command>
<command purpose="Get PR info (GitHub only)">`gh pr view <N> --json title,body,headRefName,baseRefName`</command>
<command purpose="Checkout PR (GitHub only)">`gh pr checkout <N> --force`</command>
<command purpose="Checkout PR (non-GitHub providers)">`git fetch origin <headRefName>` then `git checkout -B <headRefName> origin/<headRefName>`</command>
<command purpose="Fetch base branch">`git fetch origin <baseRefName>`</command>
<command purpose="Merge base into PR branch">`GIT_EDITOR=true git merge --no-ff --no-edit origin/<baseRefName>`</command>
<command purpose="List unmerged files">`git diff --name-only --diff-filter=U`</command>
Expand Down Expand Up @@ -277,8 +279,8 @@ You are a PR merge-conflict resolver. Merge the base branch into the target PR b

<final_response_format>
<rule>When the run succeeds, the final response must stay machine-parseable for the conflict-resolution callback and PR success comment flow.</rule>
<rule>Start the response with `Resolved merge conflicts in:` followed by one `- \`path/to/file\`` item per resolved file. If no files required manual resolution, start with `Resolved merge conflicts.` instead.</rule>
<rule>If there are controversial decisions, include a `Decisions I'm not 100% sure:` section with one `- ` bullet per decision.</rule>
<rule>If there are warnings, include a `Warnings:` section with one `- ` bullet per warning.</rule>
<rule>Start the response with `Resolved merge conflicts in:` followed by one `- \`path/to/file\``item per resolved file. If no files required manual resolution, start with`Resolved merge conflicts.`instead.</rule>
<rule>If there are controversial decisions, include a`Decisions I'm not 100% sure:`section with one`- `bullet per decision.</rule>
<rule>If there are warnings, include a`Warnings:`section with one`- ` bullet per warning.</rule>
<rule>Keep the response concise and do not add extra prose before or after these sections.</rule>
</final_response_format>
94 changes: 94 additions & 0 deletions packages/db/src/lib/__tests__/github-branch-activity.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions packages/db/src/lib/github-branch-activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export async function findActiveGitHubBranchWork({
.where(
and(
...baseConditions,
payloadProviderCondition(sourceControlProvider),
sql`${taskRuns.payload}->>'repo' = ${repoFullName}`,
sql`(
${taskRuns.payload}->>'branch' = ${branchName}
Expand All @@ -178,6 +179,20 @@ export async function findActiveGitHubBranchWork({
return pickActiveWork(branchRows, 'branch');
}

/**
* Payload-side provider scoping for the branch fallback lookups. Payloads
* written before provider-neutral support omit `sourceControlProvider` and
* mean GitHub at runtime (see `normalizeSourceControlProvider`), so missing
* or empty values are treated as `'github'`. This keeps a same-named
* repo/branch on another provider from cross-matching GitHub work and vice
* versa.
*/
function payloadProviderCondition(
sourceControlProvider: SourceControlProvider,
) {
return sql`COALESCE(NULLIF(${taskRuns.payload}->>'sourceControlProvider', ''), 'github') = ${sourceControlProvider}`;
}

/**
* Returns the newest active Roomote run that can safely continue follow-up
* work on the same PR branch without introducing a second writer or second
Expand Down
Loading
Loading