This commit is contained in:
Nathan Flurry 2026-03-14 14:38:29 -07:00 committed by GitHub
parent 70d31f819c
commit 5ea9ec5e2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 2605 additions and 669 deletions

View file

@ -112,6 +112,10 @@ interface WorkspaceHandle {
closeWorkbenchSession(input: TaskWorkbenchTabInput): Promise<void>;
publishWorkbenchPr(input: TaskWorkbenchSelectInput): Promise<void>;
revertWorkbenchFile(input: TaskWorkbenchDiffInput): Promise<void>;
reloadGithubOrganization(): Promise<void>;
reloadGithubPullRequests(): Promise<void>;
reloadGithubRepository(input: { repoId: string }): Promise<void>;
reloadGithubPullRequest(input: { repoId: string; prNumber: number }): Promise<void>;
}
interface AppWorkspaceHandle {
@ -296,6 +300,10 @@ export interface BackendClient {
closeWorkbenchSession(workspaceId: string, input: TaskWorkbenchTabInput): Promise<void>;
publishWorkbenchPr(workspaceId: string, input: TaskWorkbenchSelectInput): Promise<void>;
revertWorkbenchFile(workspaceId: string, input: TaskWorkbenchDiffInput): Promise<void>;
reloadGithubOrganization(workspaceId: string): Promise<void>;
reloadGithubPullRequests(workspaceId: string): Promise<void>;
reloadGithubRepository(workspaceId: string, repoId: string): Promise<void>;
reloadGithubPullRequest(workspaceId: string, repoId: string, prNumber: number): Promise<void>;
health(): Promise<{ ok: true }>;
useWorkspace(workspaceId: string): Promise<{ workspaceId: string }>;
starSandboxAgentRepo(workspaceId: string): Promise<StarSandboxAgentRepoResult>;
@ -1182,6 +1190,22 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
await (await workspace(workspaceId)).revertWorkbenchFile(input);
},
async reloadGithubOrganization(workspaceId: string): Promise<void> {
await (await workspace(workspaceId)).reloadGithubOrganization();
},
async reloadGithubPullRequests(workspaceId: string): Promise<void> {
await (await workspace(workspaceId)).reloadGithubPullRequests();
},
async reloadGithubRepository(workspaceId: string, repoId: string): Promise<void> {
await (await workspace(workspaceId)).reloadGithubRepository({ repoId });
},
async reloadGithubPullRequest(workspaceId: string, repoId: string, prNumber: number): Promise<void> {
await (await workspace(workspaceId)).reloadGithubPullRequest({ repoId, prNumber });
},
async health(): Promise<{ ok: true }> {
const workspaceId = options.defaultWorkspaceId;
if (!workspaceId) {

View file

@ -53,6 +53,11 @@ function upsertById<T extends { id: string }>(items: T[], nextItem: T, sort: (le
return [...filtered, nextItem].sort(sort);
}
function upsertByPrId<T extends { prId: string }>(items: T[], nextItem: T, sort: (left: T, right: T) => number): T[] {
const filtered = items.filter((item) => item.prId !== nextItem.prId);
return [...filtered, nextItem].sort(sort);
}
export const topicDefinitions = {
app: {
key: () => "app",
@ -90,6 +95,16 @@ export const topicDefinitions = {
...current,
repos: current.repos.filter((repo) => repo.id !== event.repoId),
};
case "pullRequestUpdated":
return {
...current,
openPullRequests: upsertByPrId(current.openPullRequests, event.pullRequest, (left, right) => right.updatedAtMs - left.updatedAtMs),
};
case "pullRequestRemoved":
return {
...current,
openPullRequests: current.openPullRequests.filter((pullRequest) => pullRequest.prId !== event.prId),
};
}
},
} satisfies TopicDefinition<WorkspaceSummarySnapshot, WorkspaceTopicParams, WorkspaceEvent>,

View file

@ -52,6 +52,8 @@ export interface MockFoundryGithubState {
importedRepoCount: number;
lastSyncLabel: string;
lastSyncAt: number | null;
lastWebhookAt: number | null;
lastWebhookEvent: string;
}
export interface MockFoundryOrganizationSettings {
@ -188,6 +190,8 @@ function buildRivetOrganization(): MockFoundryOrganization {
importedRepoCount: repos.length,
lastSyncLabel: "Synced just now",
lastSyncAt: Date.now() - 60_000,
lastWebhookAt: Date.now() - 30_000,
lastWebhookEvent: "push",
},
billing: {
planId: "team",
@ -267,6 +271,8 @@ function buildDefaultSnapshot(): MockFoundryAppSnapshot {
importedRepoCount: 1,
lastSyncLabel: "Synced just now",
lastSyncAt: Date.now() - 60_000,
lastWebhookAt: Date.now() - 120_000,
lastWebhookEvent: "pull_request.opened",
},
billing: {
planId: "free",
@ -301,6 +307,8 @@ function buildDefaultSnapshot(): MockFoundryAppSnapshot {
importedRepoCount: 3,
lastSyncLabel: "Waiting for first import",
lastSyncAt: null,
lastWebhookAt: null,
lastWebhookEvent: "",
},
billing: {
planId: "team",
@ -344,6 +352,8 @@ function buildDefaultSnapshot(): MockFoundryAppSnapshot {
importedRepoCount: 1,
lastSyncLabel: "Synced yesterday",
lastSyncAt: Date.now() - 24 * 60 * 60_000,
lastWebhookAt: Date.now() - 3_600_000,
lastWebhookEvent: "check_run.completed",
},
billing: {
planId: "free",
@ -397,6 +407,8 @@ function parseStoredSnapshot(): MockFoundryAppSnapshot | null {
...organization.github,
syncStatus: syncStatusFromLegacy(organization.github?.syncStatus ?? organization.repoImportStatus),
lastSyncAt: organization.github?.lastSyncAt ?? null,
lastWebhookAt: organization.github?.lastWebhookAt ?? null,
lastWebhookEvent: organization.github?.lastWebhookEvent ?? "",
},
})),
};
@ -567,6 +579,8 @@ class MockFoundryAppStore implements MockFoundryAppClient {
syncStatus: "synced",
lastSyncLabel: "Synced just now",
lastSyncAt: Date.now(),
lastWebhookAt: Date.now(),
lastWebhookEvent: "installation_repositories.added",
},
}));
this.importTimers.delete(organizationId);

View file

@ -249,6 +249,7 @@ export function createMockBackendClient(defaultWorkspaceId = "default"): Backend
};
}),
taskSummaries,
openPullRequests: [],
};
};
@ -763,6 +764,14 @@ export function createMockBackendClient(defaultWorkspaceId = "default"): Backend
emitTaskUpdate(input.taskId);
},
async reloadGithubOrganization(): Promise<void> {},
async reloadGithubPullRequests(): Promise<void> {},
async reloadGithubRepository(): Promise<void> {},
async reloadGithubPullRequest(): Promise<void> {},
async health(): Promise<{ ok: true }> {
return { ok: true };
},

View file

@ -100,7 +100,8 @@ class RemoteWorkbenchStore implements TaskWorkbenchClient {
async updateDraft(input: TaskWorkbenchUpdateDraftInput): Promise<void> {
await this.backend.updateWorkbenchDraft(this.workspaceId, input);
await this.refresh();
// Skip refresh — the server broadcast will trigger it, and the frontend
// holds local draft state to avoid the round-trip overwriting user input.
}
async sendMessage(input: TaskWorkbenchSendMessageInput): Promise<void> {