Surface GitHub auto-merge actions (#1001)

* Surface GitHub auto-merge actions

* Namespace GitHub auto-merge RPC

* Update workspace git refresh test expectation

* Avoid redundant GitHub refresh queue
This commit is contained in:
Mohamed Boudra
2026-05-14 17:48:05 +08:00
committed by GitHub
parent 44292b2b69
commit eb7fa02ac8
27 changed files with 3029 additions and 105 deletions
@@ -1586,6 +1586,118 @@ test("requests checkout merge from base via RPC", async () => {
});
});
test("requests GitHub auto-merge enable via namespaced RPC", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
await connectPromise;
const promise = client.checkoutGithubSetAutoMerge(
"/tmp/project",
{ enabled: true, method: "squash" },
"req-enable-auto-merge",
);
expect(mock.sent).toHaveLength(1);
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("checkout.github.set_auto_merge.request");
expect(request.cwd).toBe("/tmp/project");
expect(request.enabled).toBe(true);
expect(request.mergeMethod).toBe("squash");
expect(request.requestId).toBe("req-enable-auto-merge");
mock.triggerMessage(
JSON.stringify({
type: "session",
message: {
type: "checkout.github.set_auto_merge.response",
payload: {
cwd: "/tmp/project",
enabled: true,
requestId: "req-enable-auto-merge",
success: true,
error: null,
},
},
}),
);
await expect(promise).resolves.toEqual({
cwd: "/tmp/project",
enabled: true,
requestId: "req-enable-auto-merge",
success: true,
error: null,
});
});
test("requests GitHub auto-merge disable via namespaced RPC", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "clsk_unit_test",
logger,
reconnect: { enabled: false },
transportFactory: () => mock.transport,
});
clients.push(client);
const connectPromise = client.connect();
mock.triggerOpen();
await connectPromise;
const promise = client.checkoutGithubSetAutoMerge(
"/tmp/project",
{ enabled: false },
"req-disable-auto-merge",
);
expect(mock.sent).toHaveLength(1);
const request = parseSentFrame(mock.sent[0]);
expect(request.type).toBe("checkout.github.set_auto_merge.request");
expect(request.cwd).toBe("/tmp/project");
expect(request.enabled).toBe(false);
expect(request.mergeMethod).toBeUndefined();
expect(request.requestId).toBe("req-disable-auto-merge");
mock.triggerMessage(
JSON.stringify({
type: "session",
message: {
type: "checkout.github.set_auto_merge.response",
payload: {
cwd: "/tmp/project",
enabled: false,
requestId: "req-disable-auto-merge",
success: true,
error: null,
},
},
}),
);
await expect(promise).resolves.toEqual({
cwd: "/tmp/project",
enabled: false,
requestId: "req-disable-auto-merge",
success: true,
error: null,
});
});
test("requests checkout pull via RPC", async () => {
const logger = createMockLogger();
const mock = createMockTransport();
@@ -32,6 +32,7 @@ import type {
CheckoutPrCreateResponse,
CheckoutPrMergeResponse,
CheckoutPrMergeMethod,
CheckoutGithubSetAutoMergeResponse,
CheckoutPrStatusResponse,
PullRequestTimelineResponse,
CheckoutSwitchBranchResponse,
@@ -274,6 +275,7 @@ type CheckoutPullPayload = CheckoutPullResponse["payload"];
type CheckoutPushPayload = CheckoutPushResponse["payload"];
type CheckoutPrCreatePayload = CheckoutPrCreateResponse["payload"];
type CheckoutPrMergePayload = CheckoutPrMergeResponse["payload"];
type CheckoutGithubSetAutoMergePayload = CheckoutGithubSetAutoMergeResponse["payload"];
type CheckoutPrStatusPayload = CheckoutPrStatusResponse["payload"];
type PullRequestTimelinePayload = PullRequestTimelineResponse["payload"];
type CheckoutSwitchBranchPayload = CheckoutSwitchBranchResponse["payload"];
@@ -1425,6 +1427,25 @@ export class DaemonClient {
});
}
private sendNamespacedCorrelatedSessionRequest<
TResponseType extends CorrelatedResponseType,
TResult = CorrelatedResponsePayload<TResponseType>,
>(params: {
requestId?: string;
message: { type: Extract<SessionInboundMessage["type"], `${string}.request`> } & Record<
string,
unknown
>;
timeout: number;
selectPayload?: (payload: CorrelatedResponsePayload<TResponseType>) => TResult | null;
}): Promise<TResult> {
const responseType = params.message.type.replace(/\.request$/, ".response") as TResponseType;
return this.sendCorrelatedSessionRequest({
...params,
responseType,
});
}
private sendSessionMessageStrict(message: SessionInboundMessage): void {
if (!this.transport || this.connectionState.status !== "connected") {
throw new Error("Transport not connected");
@@ -2817,6 +2838,23 @@ export class DaemonClient {
});
}
async checkoutGithubSetAutoMerge(
cwd: string,
input: { enabled: true; method: CheckoutPrMergeMethod } | { enabled: false },
requestId?: string,
): Promise<CheckoutGithubSetAutoMergePayload> {
return this.sendNamespacedCorrelatedSessionRequest<"checkout.github.set_auto_merge.response">({
requestId,
message: {
type: "checkout.github.set_auto_merge.request",
cwd,
enabled: input.enabled,
...(input.enabled ? { mergeMethod: input.method } : {}),
},
timeout: 60000,
});
}
async checkoutPrStatus(cwd: string, requestId?: string): Promise<CheckoutPrStatusPayload> {
return this.sendCorrelatedSessionRequest({
requestId,