diff --git a/apps/server/src/mcp/toolkits/pullRequests/tools.ts b/apps/server/src/mcp/toolkits/pullRequests/tools.ts index 866580fc5e029..f00089ec96fa3 100644 --- a/apps/server/src/mcp/toolkits/pullRequests/tools.ts +++ b/apps/server/src/mcp/toolkits/pullRequests/tools.ts @@ -316,7 +316,7 @@ const ListThreadPullRequestsTool = Tool.make("list_thread_pull_requests", { const WatchPullRequestTool = Tool.make("watch_pull_request", { description: - "Watch an open PR for this thread, linking it first if needed. T3 wakes you on failed checks, passed required checks, new comments or reviews, or conflicts. Handle old comments first. Only later ones wake you. After this call, end your turn. The watch stays on after each wake, so do not call this again on a wake. A wake is not a merge decision. Check readiness yourself.", + "Watch an open PR for this thread, linking it first if needed. T3 wakes you on failed checks, passed required checks, new comments or reviews, or conflicts. Handle old comments first. Only later ones wake you. A merge or close wakes you once and ends the watch. After this call, end your turn. Other wakes leave the watch on, so do not call this again on a wake. A wake is not a merge decision. Check readiness yourself.", parameters: PullRequestTargetInput, success: WatchPullRequestResult, failure: PullRequestToolError, @@ -330,7 +330,8 @@ const WatchPullRequestTool = Tool.make("watch_pull_request", { .annotate(McpSchema.EnabledWhen, McpInvocationContext.hiddenFromSubagents); const UnwatchPullRequestTool = Tool.make("unwatch_pull_request", { - description: "Stop watching a PR for this thread. It stays linked.", + description: + "Stop watching a PR for this thread. It stays linked. wasWatching is false when the watch already ended, as it does when the PR merges or closes.", parameters: PullRequestTargetInput, success: WatchPullRequestResult, failure: PullRequestToolError, diff --git a/apps/server/src/orchestration-v2/PullRequestWatchReactor.ts b/apps/server/src/orchestration-v2/PullRequestWatchReactor.ts index 03a16e770efac..7d259d49b9aa0 100644 --- a/apps/server/src/orchestration-v2/PullRequestWatchReactor.ts +++ b/apps/server/src/orchestration-v2/PullRequestWatchReactor.ts @@ -4,6 +4,7 @@ import { type OrchestrationV2Notification, type PullRequestActivity, type PullRequestComment, + type PullRequestDetail, type PullRequestRef, type PullRequestThreadCommentsResult, type ThreadPullRequestLink, @@ -34,7 +35,11 @@ import * as PullRequestService from "../pullRequest/PullRequestService.ts"; import { forkParked } from "../serverActivation.ts"; import * as Orchestrator from "./Orchestrator.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; -import { evaluatePullRequestWatch, pullRequestWatchMessage } from "./pullRequestWatch.ts"; +import { + evaluatePullRequestWatch, + pullRequestWatchEndMessage, + pullRequestWatchMessage, +} from "./pullRequestWatch.ts"; /** Checks take minutes; faster sweeps waste the account's shared host quota. */ const SWEEP_MINUTES = 2; @@ -381,27 +386,23 @@ export const make = Effect.gen(function* () { ), ); - const closed = (target: WatchTarget) => - record(target, null, { - text: `Pull request #${target.link.number} (${target.link.url}) was closed, so T3 Code stopped watching it. Call watch_pull_request if it reopens.`, - notification: { - source: { kind: "monitor" }, - outcome: "updated", - summary: `#${target.link.number}: closed, stopped watching`, - }, - }).pipe( + const finished = (target: WatchTarget, detail: PullRequestDetail) => + record( + target, + null, + pullRequestWatchEndMessage({ number: target.link.number, url: target.link.url, detail }), + ).pipe( Effect.tap(() => woke(target)), - Effect.tap(() => ended(target, "closed")), + Effect.tap(() => ended(target, detail.state === "merged" ? "merged" : "closed")), ); - const endsWithoutRead = ({ thread, link }: WatchTarget): WatchEndReason | undefined => - link.snapshot?.state === "merged" - ? "merged" - : thread.settledOverride === "settled" || thread.settledAt !== null - ? "settled" - : thread.lineage.relationshipToParent === "subagent" - ? "subagent" - : undefined; + // A merged snapshot still gets one read, so the wake can name the merge commit. + const endsWithoutRead = ({ thread }: WatchTarget): WatchEndReason | undefined => + thread.settledOverride === "settled" || thread.settledAt !== null + ? "settled" + : thread.lineage.relationshipToParent === "subagent" + ? "subagent" + : undefined; const eachTarget = ( group: WatchGroup, @@ -498,11 +499,7 @@ export const make = Effect.gen(function* () { } if (detail.state !== "open") { lastReads.delete(group.key); - return yield* eachTarget(group, (target) => - detail.state === "closed" - ? closed(target) - : record(target, null).pipe(Effect.tap(() => ended(target, "merged"))), - ); + return yield* eachTarget(group, (target) => finished(target, detail)); } // Never advance the remark watermark past comments an incomplete read could have missed. diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts index 0b6070f364662..b1df9049caa6b 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.test.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.test.ts @@ -8,6 +8,7 @@ import { assert, describe, it } from "@effect/vitest"; import { PULL_REQUEST_WATCH_WAKE_LIMIT, evaluatePullRequestWatch as evaluate, + pullRequestWatchEndMessage, } from "./pullRequestWatch.ts"; // These cases read the head once or at one moment, so the no-checks notice never comes due. @@ -401,3 +402,42 @@ describe("evaluatePullRequestWatch", () => { assert.isTrue(anHourLater(watch(), silent).every((kinds) => kinds.length === 0)); }); }); + +describe("pullRequestWatchEndMessage", () => { + const url = "https://git.cafe/pingdotgg/t3code/pull/7"; + const end = (overrides: Partial[0]["detail"]>) => + pullRequestWatchEndMessage({ + number: 7, + url, + detail: { state: "merged", baseBranch: "main", ...overrides }, + }); + const mergeCommitSha = "3b2f958603a51d8c42c4ede02eee7b8cb310b2d9"; + + it("says how the pull request ended, with the merge commit and merger the host reports", () => { + const merged = end({ + mergeCommitSha, + mergedBy: { login: "juliusmarminge", name: null, avatarUrl: null }, + }); + assert.equal( + merged.text, + `Pull request #7 (${url}) was merged into main by juliusmarminge as 3b2f958, so T3 Code stopped watching it.`, + ); + assert.deepEqual(merged.notification, { + source: { kind: "monitor" }, + outcome: "completed", + summary: "#7: merged, stopped watching", + }); + // GitCafe reports a merge made on GitHub without who made it. + assert.equal( + end({ mergeCommitSha }).text, + `Pull request #7 (${url}) was merged into main as 3b2f958, so T3 Code stopped watching it.`, + ); + + const closed = end({ state: "closed" }); + assert.equal( + closed.text, + `Pull request #7 (${url}) was closed without merging, so T3 Code stopped watching it. If it is reopened, call watch_pull_request to watch it again.`, + ); + assert.equal(closed.notification.summary, "#7: closed, stopped watching"); + }); +}); diff --git a/apps/server/src/orchestration-v2/pullRequestWatch.ts b/apps/server/src/orchestration-v2/pullRequestWatch.ts index 1213948cc4d6c..d492132a1254b 100644 --- a/apps/server/src/orchestration-v2/pullRequestWatch.ts +++ b/apps/server/src/orchestration-v2/pullRequestWatch.ts @@ -214,6 +214,33 @@ const SUMMARY: Record = { conflicting: "merge conflict", }; +export function pullRequestWatchEndMessage(input: { + readonly number: number; + readonly url: string; + readonly detail: Pick; +}): { readonly text: string; readonly notification: OrchestrationV2Notification } { + const { detail } = input; + const merged = detail.state === "merged"; + const how = merged + ? [ + `was merged into ${detail.baseBranch}`, + detail.mergedBy ? ` by ${detail.mergedBy.login}` : "", + detail.mergeCommitSha ? ` as ${detail.mergeCommitSha.slice(0, 7)}` : "", + ].join("") + : "was closed without merging"; + return { + text: [ + `Pull request #${input.number} (${input.url}) ${how}, so T3 Code stopped watching it.`, + ...(merged ? [] : ["If it is reopened, call watch_pull_request to watch it again."]), + ].join(" "), + notification: { + source: { kind: "monitor" }, + outcome: merged ? "completed" : "updated", + summary: `#${input.number}: ${merged ? "merged" : "closed"}, stopped watching`, + }, + }; +} + export function pullRequestWatchMessage(input: { readonly number: number; readonly url: string; diff --git a/apps/server/src/orchestration-v2/runtimeLayer.test.ts b/apps/server/src/orchestration-v2/runtimeLayer.test.ts index c77837094b201..0f612e6028412 100644 --- a/apps/server/src/orchestration-v2/runtimeLayer.test.ts +++ b/apps/server/src/orchestration-v2/runtimeLayer.test.ts @@ -2893,6 +2893,108 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => { }), ); + // regression: watched #26248 merged and its agent kept saying it was still watching + it.effect.each(["the watch's read", "the link's sync"])( + "wakes once when a watched pull request merges, naming the merge commit: seen by %s", + (seenBy) => + Effect.gen(function* () { + const orchestrator = yield* Orchestrator.OrchestratorV2; + const threadId = ThreadId.make("runtime-pull-request-watch-merged"); + const projectId = ProjectId.make("pr-watch-merged-project"); + yield* seedProject({ + projectId, + title: "Watch merged", + workspaceRoot: "/workspace/watch-merged", + defaultModelSelection: null, + createdAt: "2026-10-01T00:00:00.000Z", + }); + yield* orchestrator.dispatch({ + type: "thread.create", + createdBy: "user", + creationSource: "web", + commandId: CommandId.make("pr-watch-merged-create"), + threadId, + projectId, + title: "Watch merged", + modelSelection, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + }); + const key = { host: "github.com", repository: "pingdotgg/t3code", number: 15 }; + yield* orchestrator.dispatch({ + type: "thread.pull-request.watch", + commandId: CommandId.make("pr-watch-merged-start"), + threadId, + ...key, + watching: true, + link: { url: "https://github.com/pingdotgg/t3code/pull/15", source: "agent" }, + }); + if (seenBy === "the link's sync") { + yield* orchestrator.dispatch({ + type: "thread.pull-request-link.sync", + commandId: CommandId.make("pr-watch-merged-synced"), + threadId, + ...key, + snapshot: { + state: "merged", + title: "Watched pull request", + headBranch: "feature", + baseBranch: "main", + isDraft: false, + updatedAt: null, + syncedAt: "2026-10-02T12:00:00.000Z", + mergedAt: "2026-10-02T12:00:00.000Z", + }, + stack: null, + }); + } + const reactor = yield* PullRequestWatchReactor.make.pipe( + Effect.provide( + Layer.mergeAll( + NodeServices.layer, + Layer.mock(PullRequestService.PullRequestService)({ + detail: () => + Effect.succeed({ + ...watchedPullRequestDetail({ + projectId, + number: 15, + at: "2026-10-02T12:00:00.000Z", + }), + state: "merged" as const, + mergedAt: "2026-10-02T12:00:00.000Z", + mergeCommitSha: "3b2f958603a51d8c42c4ede02eee7b8cb310b2d9", + mergedBy: { login: "juliusmarminge", name: null, avatarUrl: null }, + }), + activity: () => + Effect.succeed({ + comments: [], + commentCount: 0, + commentsTruncated: false, + reviewThreads: [], + commits: [], + }), + }), + ), + ), + ); + yield* reactor.sweep; + yield* TestClock.adjust("10 minutes"); + yield* reactor.sweep; + + const thread = yield* orchestrator.getThreadShell(threadId); + assert.isUndefined(thread?.pullRequests?.[0]?.watch); + const { messages } = yield* orchestrator.getThreadRecords(threadId, ["messages"]); + const wakes = messages.filter((message) => message.notification != null); + assert.deepEqual( + wakes.map((message) => message.notification?.summary), + ["#15: merged, stopped watching"], + ); + assert.include(wakes[0]!.text, "merged into main by juliusmarminge as 3b2f958"); + }), + ); + it.effect("wakes once for a state it reported, across a restart and a new watch", () => Effect.gen(function* () { const orchestrator = yield* Orchestrator.OrchestratorV2; diff --git a/apps/server/src/pullRequest/PullRequestService.ts b/apps/server/src/pullRequest/PullRequestService.ts index 5ee9b6274408a..490dbc25b43de 100644 --- a/apps/server/src/pullRequest/PullRequestService.ts +++ b/apps/server/src/pullRequest/PullRequestService.ts @@ -1736,6 +1736,10 @@ export const make = Effect.gen(function* () { updatedAt: changeRequest.updatedAt, observedAt, mergedAt: changeRequest.mergedAt, + ...(changeRequest.mergeCommitSha + ? { mergeCommitSha: changeRequest.mergeCommitSha } + : {}), + ...(changeRequest.mergedBy ? { mergedBy: changeRequest.mergedBy } : {}), closedAt: changeRequest.closedAt, reviewers: changeRequest.reviewers, labels: changeRequest.labels, diff --git a/docs/user/source-control.md b/docs/user/source-control.md index 815e5dd0668fa..8a1f2d9184396 100644 --- a/docs/user/source-control.md +++ b/docs/user/source-control.md @@ -289,8 +289,9 @@ the thread is active, the server checks the pull request every two minutes and w check fails, the required checks pass, someone else comments or reviews, or the branch starts to conflict. Threads in a project that watch the same pull request share one check. On GitHub, a check first asks whether anything changed and reads the pull request only when it did, which keeps -watching inside GitHub's rate limit. Comments from your own account do not wake it. Watching ends -when the pull request merges or closes, after 10 wakes in a row that bring only comments, after 8 +watching inside GitHub's rate limit. Comments from your own account do not wake it. When the pull +request merges or closes, the agent is woken once and watching ends; a reopened pull request needs a +new watch. Watching also ends after 10 wakes in a row that bring only comments, after 8 failed reads in a row, or when you press Stop on the thread. A rate limit only pauses watching. Settling or archiving a thread also ends all its watches. Unsettle the thread before starting a new watch. Watching a pull request again does not repeat check or conflict news its last diff --git a/packages/contracts/src/pullRequest.ts b/packages/contracts/src/pullRequest.ts index e38d5fa6cf95a..d8f1839c18deb 100644 --- a/packages/contracts/src/pullRequest.ts +++ b/packages/contracts/src/pullRequest.ts @@ -577,6 +577,8 @@ export const PullRequestDetail = Schema.Struct({ updatedAt: IsoDateTime, observedAt: Schema.optional(Schema.Finite), mergedAt: Schema.NullOr(IsoDateTime), + mergeCommitSha: Schema.optional(TrimmedNonEmptyString), + mergedBy: Schema.optional(PullRequestActor), closedAt: Schema.NullOr(IsoDateTime), reviewers: Schema.Array(PullRequestActor), labels: Schema.Array(PullRequestLabel), diff --git a/packages/source-control-core/src/server/PullRequestProvider.ts b/packages/source-control-core/src/server/PullRequestProvider.ts index b300f14afb0cb..9d8ee2e0f73f5 100644 --- a/packages/source-control-core/src/server/PullRequestProvider.ts +++ b/packages/source-control-core/src/server/PullRequestProvider.ts @@ -186,6 +186,8 @@ export interface ProviderChangeRequestDetail extends ProviderChangeRequest { readonly body: string; readonly changedFiles: number; readonly mergedAt: string | null; + readonly mergeCommitSha?: string | null; + readonly mergedBy?: PullRequestActor | null; readonly closedAt: string | null; readonly reviewers: ReadonlyArray; readonly checks: ReadonlyArray; diff --git a/packages/source-control-forgejo/src/server/ForgejoPullRequestProvider.ts b/packages/source-control-forgejo/src/server/ForgejoPullRequestProvider.ts index eba8329f4bdea..74bb5af5c8587 100644 --- a/packages/source-control-forgejo/src/server/ForgejoPullRequestProvider.ts +++ b/packages/source-control-forgejo/src/server/ForgejoPullRequestProvider.ts @@ -341,6 +341,8 @@ export const make = Effect.gen(function* () { return { ...forgejoChangeRequest(pr), headSha: pr.head.sha, + mergeCommitSha: pr.merge_commit_sha || null, + mergedBy: forgejoActor(pr.merged_by), body: pr.body ?? "", changedFiles: pr.changed_files ?? 0, reviewers: (pr.requested_reviewers ?? []).flatMap((user) => { diff --git a/packages/source-control-forgejo/src/server/forgejoPullRequestJson.ts b/packages/source-control-forgejo/src/server/forgejoPullRequestJson.ts index 246c1d55d9643..80bb9b53f6b3d 100644 --- a/packages/source-control-forgejo/src/server/forgejoPullRequestJson.ts +++ b/packages/source-control-forgejo/src/server/forgejoPullRequestJson.ts @@ -57,6 +57,8 @@ export const ForgejoPullRequest = Schema.Struct({ updated_at: Schema.String, closed_at: Schema.NullOr(Schema.String), merged_at: Schema.NullOr(Schema.String), + merge_commit_sha: Schema.optional(Schema.NullOr(Schema.String)), + merged_by: Schema.optional(Schema.NullOr(ForgejoUser)), additions: Schema.optional(Schema.NullOr(Schema.Int)), deletions: Schema.optional(Schema.NullOr(Schema.Int)), changed_files: Schema.optional(Schema.NullOr(Schema.Int)), diff --git a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts index 1d4b7338f3c12..f0caa7698d491 100644 --- a/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts +++ b/packages/source-control-gitcafe/src/server/GitCafePullRequestProvider.ts @@ -945,6 +945,7 @@ export const make = Effect.gen(function* () { changedFiles: changes?.changedFiles ?? 0, closedAt: pull.closedAt, mergedAt: pull.mergedAt, + mergeCommitSha: pull.mergeResultOid || pull.providerMerge?.mergeCommitOid || null, ...settledMerge(pull), reviewers: Json.toReviewers(listed, input.host), checks: checkList, diff --git a/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts b/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts index cc692637b82fe..a874a83a92c55 100644 --- a/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts +++ b/packages/source-control-gitcafe/src/server/gitCafePullRequestJson.ts @@ -88,9 +88,17 @@ export const GitCafePullDetail = Schema.Struct({ mergeRoute: Schema.optional(Schema.Literals(["native", "provider", "unsupported"])), lockedAt: Schema.optional(Schema.NullOr(IsoDateTime)), viewerActorId: Schema.optional(Schema.NullOr(Schema.String)), + mergeResultOid: Schema.optional(Schema.NullOr(Schema.String)), // Set once GitHub has merged a pull request GitCafe routed there, which can be before the // sync brings the merged state back. - providerMerge: Schema.optional(Schema.NullOr(Schema.Struct({ mergedAt: IsoDateTime }))), + providerMerge: Schema.optional( + Schema.NullOr( + Schema.Struct({ + mergedAt: IsoDateTime, + mergeCommitOid: Schema.optional(Schema.NullOr(Schema.String)), + }), + ), + ), capabilities: Schema.Struct({ comment: Schema.Boolean, review: Schema.Boolean, diff --git a/packages/source-control-github/src/server/GitHubPullRequestApi.test.ts b/packages/source-control-github/src/server/GitHubPullRequestApi.test.ts index 3fd74e08130ec..c6cbe430ee106 100644 --- a/packages/source-control-github/src/server/GitHubPullRequestApi.test.ts +++ b/packages/source-control-github/src/server/GitHubPullRequestApi.test.ts @@ -66,6 +66,8 @@ const coreResponse = (pullRequest: Readonly> = {}) => ({ deletions: 0, changedFiles: 0, mergedAt: null, + mergeCommit: null, + mergedBy: null, closedAt: null, isCrossRepository: false, headRepositoryOwner: null, diff --git a/packages/source-control-github/src/server/gitHubPullRequestJson.test.ts b/packages/source-control-github/src/server/gitHubPullRequestJson.test.ts index 4e6321aa3c7ac..7cf881ee893f5 100644 --- a/packages/source-control-github/src/server/gitHubPullRequestJson.test.ts +++ b/packages/source-control-github/src/server/gitHubPullRequestJson.test.ts @@ -357,6 +357,8 @@ describe("pull request detail decoding", () => { createdAt: "2026-07-01T00:00:00Z", updatedAt: "2026-07-05T00:00:00Z", mergedAt: null, + mergeCommit: null, + mergedBy: null, closedAt: null, headRefName: "feat/detail", baseRefName: "main", @@ -485,6 +487,26 @@ describe("pull request detail decoding", () => { expect(detail.checksState).toBe("pending"); }); + it("reads a merged pull request's merge commit and who merged it", () => { + expect( + detailOf([], { + state: "MERGED", + mergedAt: "2026-07-05T00:00:00Z", + mergeCommit: { oid: "3b2f958603a51d8c42c4ede02eee7b8cb310b2d9" }, + mergedBy: { + __typename: "User", + login: "octocat", + avatarUrl: "https://avatars.githubusercontent.com/u/1", + name: "Octo Cat", + }, + }), + ).toMatchObject({ + state: "merged", + mergeCommitSha: "3b2f958603a51d8c42c4ede02eee7b8cb310b2d9", + mergedBy: { login: "octocat", name: "Octo Cat" }, + }); + }); + it("reads an auto-merge request and strategy, and its null as off", () => { expect(detailOf([], { autoMergeRequest: { mergeMethod: "SQUASH" } })).toMatchObject({ autoMergeEnabled: true, diff --git a/packages/source-control-github/src/server/gitHubPullRequestJson.ts b/packages/source-control-github/src/server/gitHubPullRequestJson.ts index 965855a4350ac..95806ba05e1a3 100644 --- a/packages/source-control-github/src/server/gitHubPullRequestJson.ts +++ b/packages/source-control-github/src/server/gitHubPullRequestJson.ts @@ -978,6 +978,8 @@ export function toPullRequestWatchFingerprint( } export interface GitHubPullRequestCore extends GitHubPullRequestDetail { + readonly mergeCommitSha?: string | null; + readonly mergedBy?: PullRequestActor | null; readonly reviewRequestTeams: ReadonlyArray; readonly viewerAccess: GitHubViewerAccess & GitHubRepositoryAccess; readonly comparison: GitHubBaseComparison | null; @@ -1023,6 +1025,8 @@ export function toPullRequestCore(result: PullRequestCore.Result): GitHubPullReq statusCheckRollup: toCheckContexts(nodesOf(contexts)), }), headRepositoryNameWithOwner: trimmed(pr.headRepository?.nameWithOwner), + mergeCommitSha: trimmed(pr.mergeCommit?.oid), + mergedBy: toActor(pr.mergedBy), viewerAccess: { canWrite: toCanWrite(repository.viewerPermission), canDeleteBranch: toCanWrite(pr.headRepository?.viewerPermission) && pr.headRef !== null, diff --git a/packages/source-control-github/src/server/pullRequestReads.graphql b/packages/source-control-github/src/server/pullRequestReads.graphql index d5a95256fc1a3..8a6e451619f96 100644 --- a/packages/source-control-github/src/server/pullRequestReads.graphql +++ b/packages/source-control-github/src/server/pullRequestReads.graphql @@ -82,6 +82,16 @@ query PullRequestCore( createdAt updatedAt mergedAt + mergeCommit { + oid + } + mergedBy { + login + avatarUrl + ... on User { + name + } + } closedAt headRefName baseRefName diff --git a/packages/source-control-github/src/server/pullRequestReads.graphql.ts b/packages/source-control-github/src/server/pullRequestReads.graphql.ts index e261ec0240a73..d8f08f18591d9 100644 --- a/packages/source-control-github/src/server/pullRequestReads.graphql.ts +++ b/packages/source-control-github/src/server/pullRequestReads.graphql.ts @@ -164,6 +164,35 @@ export declare namespace ActorAvatars { export type Result = typeof ActorAvatars.result.Type } +/** Represents a Git commit. */ +export const MergeCommitCommit = Schema.Struct({ + /** The Git object ID */ + oid: Shared.GitObjectID +}) +export interface MergeCommitCommit extends Schema.Struct.Type {} + +/** A user is an individual's account on GitHub that owns repositories and can make new content. */ +export const MergedByUser = Schema.Struct({ + __typename: Schema.Literal("User"), + /** The username used to login. */ + login: Schema.String, + /** A URL pointing to the user's public avatar. */ + avatarUrl: Shared.URI, + /** The user's public profile name. */ + name: Schema.NullOr(Schema.String) +}) +export interface MergedByUser extends Schema.Struct.Type {} + +/** Represents an object which can take actions on GitHub. Typically a User or Bot. */ +export const ActorOther = Schema.Struct({ + __typename: GraphQL.otherTypename()(["User"]), + /** The username of the actor. */ + login: Schema.String, + /** A URL pointing to the actor's public avatar. */ + avatarUrl: Shared.URI +}) +export interface ActorOther extends Schema.Struct.Type {} + /** Represents an owner of a Repository. */ export const RepositoryOwner = Schema.Struct({ __typename: GraphQL.otherTypename()([]), @@ -189,7 +218,7 @@ export const HeadRefRef = Schema.Struct({ export interface HeadRefRef extends Schema.Struct.Type {} /** A user is an individual's account on GitHub that owns repositories and can make new content. */ -export const PullRequestCoreRepositoryPullRequestAuthorUser = Schema.Struct({ +export const AuthorUser = Schema.Struct({ __typename: Schema.Literal("User"), /** The username used to login. */ login: Schema.String, @@ -200,17 +229,7 @@ export const PullRequestCoreRepositoryPullRequestAuthorUser = Schema.Struct({ /** The user's public profile name. */ name: Schema.NullOr(Schema.String) }) -export interface PullRequestCoreRepositoryPullRequestAuthorUser extends Schema.Struct.Type {} - -/** Represents an object which can take actions on GitHub. Typically a User or Bot. */ -export const ActorOther = Schema.Struct({ - __typename: GraphQL.otherTypename()(["User"]), - /** The username of the actor. */ - login: Schema.String, - /** A URL pointing to the actor's public avatar. */ - avatarUrl: Shared.URI -}) -export interface ActorOther extends Schema.Struct.Type {} +export interface AuthorUser extends Schema.Struct.Type {} /** Represents an auto-merge request for a pull request */ export const AutoMergeRequest = Schema.Struct({ @@ -385,6 +404,13 @@ export const PullRequestCoreRepositoryPullRequestPullRequest = Schema.Struct({ updatedAt: Shared.DateTime, /** The date and time that the pull request was merged. */ mergedAt: Schema.NullOr(Shared.DateTime), + /** The commit that was created when this pull request was merged. */ + mergeCommit: Schema.NullOr(MergeCommitCommit), + /** The actor who merged the pull request. */ + mergedBy: Schema.NullOr(Schema.Union([ + MergedByUser, + ActorOther + ])), /** Identifies the date and time when the object was closed. */ closedAt: Schema.NullOr(Shared.DateTime), /** Identifies the name of the head Ref associated with the pull request, even if the ref has been deleted. */ @@ -403,7 +429,7 @@ export const PullRequestCoreRepositoryPullRequestPullRequest = Schema.Struct({ headRef: Schema.NullOr(HeadRefRef), /** The actor who authored the comment. */ author: Schema.NullOr(Schema.Union([ - PullRequestCoreRepositoryPullRequestAuthorUser, + AuthorUser, ActorOther ])), /** Returns the auto-merge request object if one exists for this pull request. */ @@ -457,7 +483,7 @@ export const PullRequestCore = GraphQL.query("PullRequestCore", { { name: "requestedReviewers", body: "Int!" } ], selections: [ - { name: "repository", body: ["(owner:$", "owner", " name:$", "name", "){mergeCommitAllowed squashMergeAllowed rebaseMergeAllowed autoMergeAllowed viewerPermission pullRequest(number:$", "number", "){number title url body state isDraft mergeable mergeStateStatus reviewDecision additions deletions changedFiles createdAt updatedAt mergedAt closedAt headRefName baseRefName headRefOid isCrossRepository headRepositoryOwner{__typename login}headRepository{nameWithOwner viewerPermission}headRef{id}author{__typename login avatarUrl...on User{id name}}autoMergeRequest{mergeMethod}viewerCanUpdate viewerDidAuthor viewerCanUpdateBranch baseRef{compare(headRef:$", "headRef", "){behindBy}}reviewRequests(first:$", "requestedReviewers", "){nodes{requestedReviewer{__typename...on User{login name}...on Bot{login}...on Team{slug combinedSlug teamName:name teamAvatarUrl:avatarUrl}}}}labels(first:100){nodes{name color}}commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){nodes{__typename...", "CheckContext", "}pageInfo{hasNextPage}}}}}}}}"] } + { name: "repository", body: ["(owner:$", "owner", " name:$", "name", "){mergeCommitAllowed squashMergeAllowed rebaseMergeAllowed autoMergeAllowed viewerPermission pullRequest(number:$", "number", "){number title url body state isDraft mergeable mergeStateStatus reviewDecision additions deletions changedFiles createdAt updatedAt mergedAt mergeCommit{oid}mergedBy{__typename login avatarUrl...on User{name}}closedAt headRefName baseRefName headRefOid isCrossRepository headRepositoryOwner{__typename login}headRepository{nameWithOwner viewerPermission}headRef{id}author{__typename login avatarUrl...on User{id name}}autoMergeRequest{mergeMethod}viewerCanUpdate viewerDidAuthor viewerCanUpdateBranch baseRef{compare(headRef:$", "headRef", "){behindBy}}reviewRequests(first:$", "requestedReviewers", "){nodes{requestedReviewer{__typename...on User{login name}...on Bot{login}...on Team{slug combinedSlug teamName:name teamAvatarUrl:avatarUrl}}}}labels(first:100){nodes{name color}}commits(last:1){nodes{commit{statusCheckRollup{contexts(first:100){nodes{__typename...", "CheckContext", "}pageInfo{hasNextPage}}}}}}}}"] } ], fragments: [ { name: "CheckContext", body: ["on StatusCheckRollupContext{__typename...on StatusContext{context state targetUrl createdAt description isRequired(pullRequestNumber:$", "number", ")@include(if:$", "required", ")}...on CheckRun{name status conclusion startedAt completedAt detailsUrl isRequired(pullRequestNumber:$", "number", ")@include(if:$", "required", ")checkSuite{workflowRun{workflow{name}}}}}"] } @@ -577,18 +603,6 @@ export declare namespace PullRequestCheckContexts { export type Result = typeof PullRequestCheckContexts.result.Type } -/** A user is an individual's account on GitHub that owns repositories and can make new content. */ -export const PullRequestPreviewRepositoryPullRequestAuthorUser = Schema.Struct({ - __typename: Schema.Literal("User"), - /** The username used to login. */ - login: Schema.String, - /** A URL pointing to the user's public avatar. */ - avatarUrl: Shared.URI, - /** The user's public profile name. */ - name: Schema.NullOr(Schema.String) -}) -export interface PullRequestPreviewRepositoryPullRequestAuthorUser extends Schema.Struct.Type {} - /** A repository pull request. */ export const PullRequestPreviewRepositoryPullRequestPullRequest = Schema.Struct({ /** Identifies the pull request number. */ @@ -605,7 +619,7 @@ export const PullRequestPreviewRepositoryPullRequestPullRequest = Schema.Struct( createdAt: Shared.DateTime, /** The actor who authored the comment. */ author: Schema.NullOr(Schema.Union([ - PullRequestPreviewRepositoryPullRequestAuthorUser, + MergedByUser, ActorOther ])) }) @@ -726,7 +740,7 @@ export const NodesIssueComment = Schema.Struct({ url: Shared.URI, /** The actor who authored the comment. */ author: Schema.NullOr(Schema.Union([ - PullRequestPreviewRepositoryPullRequestAuthorUser, + MergedByUser, ActorOther ])) }) @@ -757,7 +771,7 @@ export const NodesPullRequestReview = Schema.Struct({ url: Shared.URI, /** The actor who authored the comment. */ author: Schema.NullOr(Schema.Union([ - PullRequestPreviewRepositoryPullRequestAuthorUser, + MergedByUser, ActorOther ])) }) @@ -776,7 +790,7 @@ export interface PullRequestReviewConnection extends Schema.Struct.Type {} export const ReviewerCandidatesRepositoryPullRequestReviewRequestsNodesReviewRequest = Schema.Struct({ /** The reviewer that is requested. */ requestedReviewer: Schema.NullOr(Schema.Union([ - PullRequestPreviewRepositoryPullRequestAuthorUser, + MergedByUser, Team, NodesBot, RequestedReviewerOther