foundry: async app repo import on org select

This commit is contained in:
Nathan Flurry 2026-03-13 00:57:23 -07:00
parent fa4ed388d2
commit 6e216a9f86
2 changed files with 93 additions and 43 deletions

View file

@ -66,6 +66,7 @@ const WORKSPACE_QUEUE_NAMES = [
"workspace.command.addRepo",
"workspace.command.createTask",
"workspace.command.refreshProviderProfiles",
"workspace.command.syncGithubOrganizationRepos",
"workspace.command.syncGithubSession",
] as const;
const SANDBOX_AGENT_REPO = "rivet-dev/sandbox-agent";
@ -387,6 +388,19 @@ export async function runWorkspaceWorkflow(ctx: any): Promise<void> {
return Loop.continue(undefined);
}
if (msg.name === "workspace.command.syncGithubOrganizationRepos") {
await loopCtx.step({
name: "workspace-sync-github-organization-repos",
timeout: 60_000,
run: async () => {
const { syncGithubOrganizationRepos } = await import("./app-shell.js");
await syncGithubOrganizationRepos(loopCtx, msg.body as { sessionId: string; organizationId: string });
},
});
await msg.complete({ ok: true });
return Loop.continue(undefined);
}
return Loop.continue(undefined);
});
}

View file

@ -469,6 +469,58 @@ export async function syncGithubOrganizations(c: any, input: { sessionId: string
});
}
export async function syncGithubOrganizationRepos(c: any, input: { sessionId: string; organizationId: string }): Promise<void> {
assertAppWorkspace(c);
const session = await requireSignedInSession(c, input.sessionId);
requireEligibleOrganization(session, input.organizationId);
const { appShell } = getActorRuntimeContext();
const workspace = await getOrCreateWorkspace(c, input.organizationId);
const organization = await getOrganizationState(workspace);
try {
let repositories;
let installationStatus = organization.snapshot.github.installationStatus;
if (organization.snapshot.kind === "personal") {
repositories = await appShell.github.listUserRepositories(session.githubAccessToken);
installationStatus = "connected";
} else if (organization.githubInstallationId) {
try {
repositories = await appShell.github.listInstallationRepositories(organization.githubInstallationId);
} catch (error) {
if (!(error instanceof GitHubAppError) || (error.status !== 403 && error.status !== 404)) {
throw error;
}
repositories = (await appShell.github.listUserRepositories(session.githubAccessToken)).filter((repository) =>
repository.fullName.startsWith(`${organization.githubLogin}/`),
);
installationStatus = "reconnect_required";
}
} else {
repositories = (await appShell.github.listUserRepositories(session.githubAccessToken)).filter((repository) =>
repository.fullName.startsWith(`${organization.githubLogin}/`),
);
installationStatus = "reconnect_required";
}
await workspace.applyOrganizationSyncCompleted({
repositories,
installationStatus,
lastSyncLabel: repositories.length > 0 ? "Synced just now" : "No repositories available",
});
} catch (error) {
const installationStatus =
error instanceof GitHubAppError && (error.status === 403 || error.status === 404)
? "reconnect_required"
: organization.snapshot.github.installationStatus;
await workspace.markOrganizationSyncFailed({
message: error instanceof Error ? error.message : "GitHub import failed",
installationStatus,
});
}
}
/**
* Full synchronous sync: init session + sync orgs in one call.
* Used by bootstrapAppGithubSession (dev-only) where there is no proxy
@ -766,7 +818,22 @@ export const workspaceAppActions = {
const workspace = await getOrCreateWorkspace(c, input.organizationId);
const organization = await getOrganizationState(workspace);
if (organization.snapshot.github.syncStatus !== "synced") {
return await workspaceAppActions.triggerAppRepoImport(c, input);
if (organization.snapshot.github.syncStatus !== "syncing") {
await workspace.markOrganizationSyncStarted({
label: "Importing repository catalog...",
});
const self = selfWorkspace(c);
await self.send(
"workspace.command.syncGithubOrganizationRepos",
{ sessionId: input.sessionId, organizationId: input.organizationId },
{
wait: false,
},
);
}
return await buildAppSnapshot(c, input.sessionId);
}
return await buildAppSnapshot(c, input.sessionId);
},
@ -792,55 +859,24 @@ export const workspaceAppActions = {
const session = await requireSignedInSession(c, input.sessionId);
requireEligibleOrganization(session, input.organizationId);
const { appShell } = getActorRuntimeContext();
const workspace = await getOrCreateWorkspace(c, input.organizationId);
const organization = await getOrganizationState(workspace);
if (organization.snapshot.github.syncStatus === "syncing") {
return await buildAppSnapshot(c, input.sessionId);
}
await workspace.markOrganizationSyncStarted({
label: "Importing repository catalog...",
});
try {
let repositories;
let installationStatus = organization.snapshot.github.installationStatus;
if (organization.snapshot.kind === "personal") {
repositories = await appShell.github.listUserRepositories(session.githubAccessToken);
installationStatus = "connected";
} else if (organization.githubInstallationId) {
try {
repositories = await appShell.github.listInstallationRepositories(organization.githubInstallationId);
} catch (error) {
if (!(error instanceof GitHubAppError) || (error.status !== 403 && error.status !== 404)) {
throw error;
}
repositories = (await appShell.github.listUserRepositories(session.githubAccessToken)).filter((repository) =>
repository.fullName.startsWith(`${organization.githubLogin}/`),
);
installationStatus = "reconnect_required";
}
} else {
repositories = (await appShell.github.listUserRepositories(session.githubAccessToken)).filter((repository) =>
repository.fullName.startsWith(`${organization.githubLogin}/`),
);
installationStatus = "reconnect_required";
}
await workspace.applyOrganizationSyncCompleted({
repositories,
installationStatus,
lastSyncLabel: repositories.length > 0 ? "Synced just now" : "No repositories available",
});
} catch (error) {
const installationStatus =
error instanceof GitHubAppError && (error.status === 403 || error.status === 404)
? "reconnect_required"
: organization.snapshot.github.installationStatus;
await workspace.markOrganizationSyncFailed({
message: error instanceof Error ? error.message : "GitHub import failed",
installationStatus,
});
}
const self = selfWorkspace(c);
await self.send(
"workspace.command.syncGithubOrganizationRepos",
{ sessionId: input.sessionId, organizationId: input.organizationId },
{
wait: false,
},
);
return await buildAppSnapshot(c, input.sessionId);
},