Push-driven checkout diff subscriptions for changes sidebar (#24)
* Implement push-driven checkout diff subscriptions * Remove legacy checkout diff polling RPC * Fix checkout diff watcher coverage and fallback refresh
This commit is contained in:
@@ -19,7 +19,6 @@ import type {
|
||||
GitSetupOptions,
|
||||
HighlightedDiffResponse,
|
||||
CheckoutStatusResponse,
|
||||
CheckoutDiffResponse,
|
||||
CheckoutCommitResponse,
|
||||
CheckoutMergeResponse,
|
||||
CheckoutMergeFromBaseResponse,
|
||||
@@ -182,7 +181,11 @@ export type CreateAgentRequestOptions = {
|
||||
type GitDiffPayload = GitDiffResponse["payload"];
|
||||
type HighlightedDiffPayload = HighlightedDiffResponse["payload"];
|
||||
type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
|
||||
type CheckoutDiffPayload = CheckoutDiffResponse["payload"];
|
||||
type SubscribeCheckoutDiffPayload = Extract<
|
||||
SessionOutboundMessage,
|
||||
{ type: "subscribe_checkout_diff_response" }
|
||||
>["payload"];
|
||||
type CheckoutDiffPayload = Omit<SubscribeCheckoutDiffPayload, "subscriptionId">;
|
||||
type CheckoutCommitPayload = CheckoutCommitResponse["payload"];
|
||||
type CheckoutMergePayload = CheckoutMergeResponse["payload"];
|
||||
type CheckoutMergeFromBasePayload = CheckoutMergeFromBaseResponse["payload"];
|
||||
@@ -296,6 +299,10 @@ export class DaemonClient {
|
||||
string,
|
||||
{ labels?: Record<string, string>; agentId?: string } | undefined
|
||||
>();
|
||||
private checkoutDiffSubscriptions = new Map<
|
||||
string,
|
||||
{ cwd: string; compare: { mode: "uncommitted" | "base"; baseRef?: string } }
|
||||
>();
|
||||
private logger: Logger;
|
||||
private pendingSendQueue: PendingSend[] = [];
|
||||
private relayClientId: string | null = null;
|
||||
@@ -401,6 +408,7 @@ export class DaemonClient {
|
||||
this.reconnectAttempt = 0;
|
||||
this.updateConnectionState({ status: "connected" });
|
||||
this.resubscribeAgentUpdates();
|
||||
this.resubscribeCheckoutDiffSubscriptions();
|
||||
this.flushPendingSendQueue();
|
||||
this.resolveConnect();
|
||||
}),
|
||||
@@ -951,6 +959,22 @@ export class DaemonClient {
|
||||
}
|
||||
}
|
||||
|
||||
private resubscribeCheckoutDiffSubscriptions(): void {
|
||||
if (this.checkoutDiffSubscriptions.size === 0) {
|
||||
return;
|
||||
}
|
||||
for (const [subscriptionId, subscription] of this.checkoutDiffSubscriptions) {
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "subscribe_checkout_diff_request",
|
||||
subscriptionId,
|
||||
cwd: subscription.cwd,
|
||||
compare: subscription.compare,
|
||||
requestId: this.createRequestId(),
|
||||
});
|
||||
this.sendSessionMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Agent Lifecycle
|
||||
// ============================================================================
|
||||
@@ -1521,32 +1545,101 @@ export class DaemonClient {
|
||||
return responsePromise;
|
||||
}
|
||||
|
||||
private normalizeCheckoutDiffCompare(
|
||||
compare: { mode: "uncommitted" | "base"; baseRef?: string }
|
||||
): { mode: "uncommitted" | "base"; baseRef?: string } {
|
||||
if (compare.mode === "uncommitted") {
|
||||
return { mode: "uncommitted" };
|
||||
}
|
||||
const trimmedBaseRef = compare.baseRef?.trim();
|
||||
if (!trimmedBaseRef) {
|
||||
return { mode: "base" };
|
||||
}
|
||||
return { mode: "base", baseRef: trimmedBaseRef };
|
||||
}
|
||||
|
||||
async getCheckoutDiff(
|
||||
cwd: string,
|
||||
compare: { mode: "uncommitted" | "base"; baseRef?: string },
|
||||
requestId?: string
|
||||
): Promise<CheckoutDiffPayload> {
|
||||
const resolvedRequestId = this.createRequestId(requestId);
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "checkout_diff_request",
|
||||
const oneShotSubscriptionId = `oneshot-checkout-diff:${crypto.randomUUID()}`;
|
||||
try {
|
||||
const payload = await this.subscribeCheckoutDiff(cwd, compare, {
|
||||
subscriptionId: oneShotSubscriptionId,
|
||||
requestId,
|
||||
});
|
||||
return {
|
||||
cwd: payload.cwd,
|
||||
files: payload.files,
|
||||
error: payload.error,
|
||||
requestId: payload.requestId,
|
||||
};
|
||||
} finally {
|
||||
try {
|
||||
this.unsubscribeCheckoutDiff(oneShotSubscriptionId);
|
||||
} catch {
|
||||
// Ignore disconnect races during one-shot cleanup.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async subscribeCheckoutDiff(
|
||||
cwd: string,
|
||||
compare: { mode: "uncommitted" | "base"; baseRef?: string },
|
||||
options?: { subscriptionId?: string; requestId?: string }
|
||||
): Promise<SubscribeCheckoutDiffPayload> {
|
||||
const subscriptionId = options?.subscriptionId ?? crypto.randomUUID();
|
||||
const normalizedCompare = this.normalizeCheckoutDiffCompare(compare);
|
||||
const previousSubscription = this.checkoutDiffSubscriptions.get(subscriptionId) ?? null;
|
||||
this.checkoutDiffSubscriptions.set(subscriptionId, {
|
||||
cwd,
|
||||
compare,
|
||||
compare: normalizedCompare,
|
||||
});
|
||||
|
||||
const resolvedRequestId = this.createRequestId(options?.requestId);
|
||||
const message = SessionInboundMessageSchema.parse({
|
||||
type: "subscribe_checkout_diff_request",
|
||||
subscriptionId,
|
||||
cwd,
|
||||
compare: normalizedCompare,
|
||||
requestId: resolvedRequestId,
|
||||
});
|
||||
return this.sendRequest({
|
||||
requestId: resolvedRequestId,
|
||||
message,
|
||||
timeout: 60000,
|
||||
options: { skipQueue: true },
|
||||
select: (msg) => {
|
||||
if (msg.type !== "checkout_diff_response") {
|
||||
return null;
|
||||
}
|
||||
if (msg.payload.requestId !== resolvedRequestId) {
|
||||
return null;
|
||||
}
|
||||
return msg.payload;
|
||||
},
|
||||
|
||||
try {
|
||||
return await this.sendRequest({
|
||||
requestId: resolvedRequestId,
|
||||
message,
|
||||
timeout: 60000,
|
||||
options: { skipQueue: true },
|
||||
select: (msg) => {
|
||||
if (msg.type !== "subscribe_checkout_diff_response") {
|
||||
return null;
|
||||
}
|
||||
if (msg.payload.requestId !== resolvedRequestId) {
|
||||
return null;
|
||||
}
|
||||
if (msg.payload.subscriptionId !== subscriptionId) {
|
||||
return null;
|
||||
}
|
||||
return msg.payload;
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
if (previousSubscription) {
|
||||
this.checkoutDiffSubscriptions.set(subscriptionId, previousSubscription);
|
||||
} else {
|
||||
this.checkoutDiffSubscriptions.delete(subscriptionId);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeCheckoutDiff(subscriptionId: string): void {
|
||||
this.checkoutDiffSubscriptions.delete(subscriptionId);
|
||||
this.sendSessionMessage({
|
||||
type: "unsubscribe_checkout_diff_request",
|
||||
subscriptionId,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user