mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 03:03:18 +00:00
chore(foundry): migrate to actions (#262)
* feat(foundry): checkpoint actor and workspace refactor
* docs(foundry): add agent handoff context
* wip(foundry): continue actor refactor
* wip(foundry): capture remaining local changes
* Complete Foundry refactor checklist
* Fix Foundry validation fallout
* wip
* wip: convert all actors from workflow to plain run handlers
Workaround for RivetKit bug where c.queue.iter() never yields messages
for actors created via getOrCreate from another actor's context. The
queue accepts messages (visible in inspector) but the iterator hangs.
Sleep/wake fixes it, but actors with active connections never sleep.
Converted organization, github-data, task, and user actors from
run: workflow(...) to plain run: async (c) => { for await ... }.
Also fixes:
- Missing auth tables in org migration (auth_verification etc)
- default_model NOT NULL constraint on org profile upsert
- Nested workflow step in github-data (HistoryDivergedError)
- Removed --force from frontend Dockerfile pnpm install
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Convert all actors from queues/workflows to direct actions, lazy task creation
Major refactor replacing all queue-based workflow communication with direct
RivetKit action calls across all actors. This works around a RivetKit bug
where c.queue.iter() deadlocks for actors created from another actor's context.
Key changes:
- All actors (organization, task, user, audit-log, github-data) converted
from run: workflow(...) to actions-only (no run handler, no queues)
- PR sync creates virtual task entries in org local DB instead of spawning
task actors — prevents OOM from 200+ actors created simultaneously
- Task actors created lazily on first user interaction via getOrCreate,
self-initialize from org's getTaskIndexEntry data
- Removed requireRepoExists cross-actor call (caused 500s), replaced with
local resolveTaskRepoId from org's taskIndex table
- Fixed getOrganizationContext to thread overrides through all sync phases
- Fixed sandbox repo path (/home/user/repo for E2B compatibility)
- Fixed buildSessionDetail to skip transcript fetch for pending sessions
- Added process crash protection (uncaughtException/unhandledRejection)
- Fixed React infinite render loop in mock-layout useEffect dependencies
- Added sandbox listProcesses error handling for expired E2B sandboxes
- Set E2B sandbox timeout to 1 hour (was 5 min default)
- Updated CLAUDE.md with lazy task creation rules, no-silent-catch policy,
React hook dependency safety rules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix E2B sandbox timeout comment, frontend stability, and create-flow improvements
- Add TEMPORARY comment on E2B timeoutMs with pointer to rivetkit sandbox
resilience proposal for when autoPause lands
- Fix React useEffect dependency stability in mock-layout and
organization-dashboard to prevent infinite re-render loops
- Fix terminal-pane ref handling
- Improve create-flow service and tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
32f3c6c3bc
commit
f45a467484
139 changed files with 9768 additions and 7204 deletions
|
|
@ -1,8 +1,11 @@
|
|||
import { betterAuth } from "better-auth";
|
||||
import { createAdapterFactory } from "better-auth/adapters";
|
||||
import { APP_SHELL_ORGANIZATION_ID } from "../actors/organization/app-shell.js";
|
||||
import { authUserKey, organizationKey } from "../actors/keys.js";
|
||||
import { APP_SHELL_ORGANIZATION_ID } from "../actors/organization/constants.js";
|
||||
// organization actions are called directly (no queue)
|
||||
// user actor actions are called directly (no queue)
|
||||
import { organizationKey, userKey } from "../actors/keys.js";
|
||||
import { logger } from "../logging.js";
|
||||
// expectQueueResponse removed — actions return values directly
|
||||
|
||||
const AUTH_BASE_PATH = "/v1/auth";
|
||||
const SESSION_COOKIE = "better-auth.session_token";
|
||||
|
|
@ -59,6 +62,8 @@ function resolveRouteUserId(organization: any, resolved: any): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
// sendOrganizationCommand removed — org actions are called directly
|
||||
|
||||
export interface BetterAuthService {
|
||||
auth: any;
|
||||
resolveSession(headers: Headers): Promise<{ session: any; user: any } | null>;
|
||||
|
|
@ -75,7 +80,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
}
|
||||
|
||||
// getOrCreate is intentional here: the adapter runs during Better Auth callbacks
|
||||
// which can fire before any explicit create path. The app organization and auth user
|
||||
// which can fire before any explicit create path. The app organization and user
|
||||
// actors must exist by the time the adapter needs them.
|
||||
const appOrganization = () =>
|
||||
actorClient.organization.getOrCreate(organizationKey(APP_SHELL_ORGANIZATION_ID), {
|
||||
|
|
@ -83,9 +88,9 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
});
|
||||
|
||||
// getOrCreate is intentional: Better Auth creates user records during OAuth
|
||||
// callbacks, so the auth-user actor must be lazily provisioned on first access.
|
||||
const getAuthUser = async (userId: string) =>
|
||||
await actorClient.authUser.getOrCreate(authUserKey(userId), {
|
||||
// callbacks, so the user actor must be lazily provisioned on first access.
|
||||
const getUser = async (userId: string) =>
|
||||
await actorClient.user.getOrCreate(userKey(userId), {
|
||||
createWithInput: { userId },
|
||||
});
|
||||
|
||||
|
|
@ -110,7 +115,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const email = direct("email");
|
||||
if (typeof email === "string" && email.length > 0) {
|
||||
const organization = await appOrganization();
|
||||
const resolved = await organization.authFindEmailIndex({ email: email.toLowerCase() });
|
||||
const resolved = await organization.betterAuthFindEmailIndex({ email: email.toLowerCase() });
|
||||
return resolveRouteUserId(organization, resolved);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -125,7 +130,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const sessionToken = direct("token") ?? data?.token;
|
||||
if (typeof sessionId === "string" || typeof sessionToken === "string") {
|
||||
const organization = await appOrganization();
|
||||
const resolved = await organization.authFindSessionIndex({
|
||||
const resolved = await organization.betterAuthFindSessionIndex({
|
||||
...(typeof sessionId === "string" ? { sessionId } : {}),
|
||||
...(typeof sessionToken === "string" ? { sessionToken } : {}),
|
||||
});
|
||||
|
|
@ -144,11 +149,11 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const accountId = direct("accountId") ?? data?.accountId;
|
||||
const organization = await appOrganization();
|
||||
if (typeof accountRecordId === "string" && accountRecordId.length > 0) {
|
||||
const resolved = await organization.authFindAccountIndex({ id: accountRecordId });
|
||||
const resolved = await organization.betterAuthFindAccountIndex({ id: accountRecordId });
|
||||
return resolveRouteUserId(organization, resolved);
|
||||
}
|
||||
if (typeof providerId === "string" && providerId.length > 0 && typeof accountId === "string" && accountId.length > 0) {
|
||||
const resolved = await organization.authFindAccountIndex({ providerId, accountId });
|
||||
const resolved = await organization.betterAuthFindAccountIndex({ providerId, accountId });
|
||||
return resolveRouteUserId(organization, resolved);
|
||||
}
|
||||
return null;
|
||||
|
|
@ -157,9 +162,9 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return null;
|
||||
};
|
||||
|
||||
const ensureOrganizationVerification = async (method: string, payload: Record<string, unknown>) => {
|
||||
const ensureOrganizationVerification = async (actionName: string, payload: Record<string, unknown>) => {
|
||||
const organization = await appOrganization();
|
||||
return await organization[method](payload);
|
||||
return await (organization as any)[actionName](payload);
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
@ -170,7 +175,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
create: async ({ model, data }) => {
|
||||
const transformed = await transformInput(data, model, "create", true);
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authCreateVerification", { data: transformed });
|
||||
return await ensureOrganizationVerification("commandBetterAuthVerificationCreate", { data: transformed });
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForQuery(model, undefined, transformed);
|
||||
|
|
@ -178,19 +183,19 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
throw new Error(`Unable to resolve auth actor for create(${model})`);
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const created = await userActor.createAuthRecord({ model, data: transformed });
|
||||
const userActor = await getUser(userId);
|
||||
const created = await userActor.authCreate({ model, data: transformed });
|
||||
const organization = await appOrganization();
|
||||
|
||||
if (model === "user" && typeof transformed.email === "string" && transformed.email.length > 0) {
|
||||
await organization.authUpsertEmailIndex({
|
||||
await organization.commandBetterAuthEmailIndexUpsert({
|
||||
email: transformed.email.toLowerCase(),
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
if (model === "session") {
|
||||
await organization.authUpsertSessionIndex({
|
||||
await organization.commandBetterAuthSessionIndexUpsert({
|
||||
sessionId: String(created.id),
|
||||
sessionToken: String(created.token),
|
||||
userId,
|
||||
|
|
@ -198,7 +203,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
}
|
||||
|
||||
if (model === "account") {
|
||||
await organization.authUpsertAccountIndex({
|
||||
await organization.commandBetterAuthAccountIndexUpsert({
|
||||
id: String(created.id),
|
||||
providerId: String(created.providerId),
|
||||
accountId: String(created.accountId),
|
||||
|
|
@ -212,7 +217,8 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
findOne: async ({ model, where, join }) => {
|
||||
const transformedWhere = transformWhereClause({ model, where, action: "findOne" });
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authFindOneVerification", { where: transformedWhere, join });
|
||||
const organization = await appOrganization();
|
||||
return await organization.betterAuthFindOneVerification({ where: transformedWhere, join });
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForQuery(model, transformedWhere);
|
||||
|
|
@ -220,15 +226,16 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return null;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const found = await userActor.findOneAuthRecord({ model, where: transformedWhere, join });
|
||||
const userActor = await getUser(userId);
|
||||
const found = await userActor.betterAuthFindOneRecord({ model, where: transformedWhere, join });
|
||||
return found ? ((await transformOutput(found, model, undefined, join)) as any) : null;
|
||||
},
|
||||
|
||||
findMany: async ({ model, where, limit, sortBy, offset, join }) => {
|
||||
const transformedWhere = transformWhereClause({ model, where, action: "findMany" });
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authFindManyVerification", {
|
||||
const organization = await appOrganization();
|
||||
return await organization.betterAuthFindManyVerification({
|
||||
where: transformedWhere,
|
||||
limit,
|
||||
sortBy,
|
||||
|
|
@ -244,7 +251,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const resolved = await Promise.all(
|
||||
(tokenClause.value as string[]).map(async (sessionToken: string) => ({
|
||||
sessionToken,
|
||||
route: await organization.authFindSessionIndex({ sessionToken }),
|
||||
route: await organization.betterAuthFindSessionIndex({ sessionToken }),
|
||||
})),
|
||||
);
|
||||
const byUser = new Map<string, string[]>();
|
||||
|
|
@ -259,11 +266,11 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
|
||||
const rows = [];
|
||||
for (const [userId, tokens] of byUser) {
|
||||
const userActor = await getAuthUser(userId);
|
||||
const userActor = await getUser(userId);
|
||||
const scopedWhere = transformedWhere.map((entry: any) =>
|
||||
entry.field === "token" && entry.operator === "in" ? { ...entry, value: tokens } : entry,
|
||||
);
|
||||
const found = await userActor.findManyAuthRecords({ model, where: scopedWhere, limit, sortBy, offset, join });
|
||||
const found = await userActor.betterAuthFindManyRecords({ model, where: scopedWhere, limit, sortBy, offset, join });
|
||||
rows.push(...found);
|
||||
}
|
||||
return await Promise.all(rows.map(async (row: any) => await transformOutput(row, model, undefined, join)));
|
||||
|
|
@ -275,8 +282,8 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return [];
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const found = await userActor.findManyAuthRecords({ model, where: transformedWhere, limit, sortBy, offset, join });
|
||||
const userActor = await getUser(userId);
|
||||
const found = await userActor.betterAuthFindManyRecords({ model, where: transformedWhere, limit, sortBy, offset, join });
|
||||
return await Promise.all(found.map(async (row: any) => await transformOutput(row, model, undefined, join)));
|
||||
},
|
||||
|
||||
|
|
@ -284,7 +291,10 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const transformedWhere = transformWhereClause({ model, where, action: "update" });
|
||||
const transformedUpdate = (await transformInput(update as Record<string, unknown>, model, "update", true)) as Record<string, unknown>;
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authUpdateVerification", { where: transformedWhere, update: transformedUpdate });
|
||||
return await ensureOrganizationVerification("commandBetterAuthVerificationUpdate", {
|
||||
where: transformedWhere,
|
||||
update: transformedUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForQuery(model, transformedWhere, transformedUpdate);
|
||||
|
|
@ -292,29 +302,34 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return null;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const userActor = await getUser(userId);
|
||||
const before =
|
||||
model === "user"
|
||||
? await userActor.findOneAuthRecord({ model, where: transformedWhere })
|
||||
? await userActor.betterAuthFindOneRecord({ model, where: transformedWhere })
|
||||
: model === "account"
|
||||
? await userActor.findOneAuthRecord({ model, where: transformedWhere })
|
||||
? await userActor.betterAuthFindOneRecord({ model, where: transformedWhere })
|
||||
: model === "session"
|
||||
? await userActor.findOneAuthRecord({ model, where: transformedWhere })
|
||||
? await userActor.betterAuthFindOneRecord({ model, where: transformedWhere })
|
||||
: null;
|
||||
const updated = await userActor.updateAuthRecord({ model, where: transformedWhere, update: transformedUpdate });
|
||||
const updated = await userActor.authUpdate({ model, where: transformedWhere, update: transformedUpdate });
|
||||
const organization = await appOrganization();
|
||||
|
||||
if (model === "user" && updated) {
|
||||
if (before?.email && before.email !== updated.email) {
|
||||
await organization.authDeleteEmailIndex({ email: before.email.toLowerCase() });
|
||||
await organization.commandBetterAuthEmailIndexDelete({
|
||||
email: before.email.toLowerCase(),
|
||||
});
|
||||
}
|
||||
if (updated.email) {
|
||||
await organization.authUpsertEmailIndex({ email: updated.email.toLowerCase(), userId });
|
||||
await organization.commandBetterAuthEmailIndexUpsert({
|
||||
email: updated.email.toLowerCase(),
|
||||
userId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (model === "session" && updated) {
|
||||
await organization.authUpsertSessionIndex({
|
||||
await organization.commandBetterAuthSessionIndexUpsert({
|
||||
sessionId: String(updated.id),
|
||||
sessionToken: String(updated.token),
|
||||
userId,
|
||||
|
|
@ -322,7 +337,7 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
}
|
||||
|
||||
if (model === "account" && updated) {
|
||||
await organization.authUpsertAccountIndex({
|
||||
await organization.commandBetterAuthAccountIndexUpsert({
|
||||
id: String(updated.id),
|
||||
providerId: String(updated.providerId),
|
||||
accountId: String(updated.accountId),
|
||||
|
|
@ -337,7 +352,10 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
const transformedWhere = transformWhereClause({ model, where, action: "updateMany" });
|
||||
const transformedUpdate = (await transformInput(update as Record<string, unknown>, model, "update", true)) as Record<string, unknown>;
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authUpdateManyVerification", { where: transformedWhere, update: transformedUpdate });
|
||||
return await ensureOrganizationVerification("commandBetterAuthVerificationUpdateMany", {
|
||||
where: transformedWhere,
|
||||
update: transformedUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForQuery(model, transformedWhere, transformedUpdate);
|
||||
|
|
@ -345,14 +363,15 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return 0;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
return await userActor.updateManyAuthRecords({ model, where: transformedWhere, update: transformedUpdate });
|
||||
const userActor = await getUser(userId);
|
||||
return await userActor.authUpdateMany({ model, where: transformedWhere, update: transformedUpdate });
|
||||
},
|
||||
|
||||
delete: async ({ model, where }) => {
|
||||
const transformedWhere = transformWhereClause({ model, where, action: "delete" });
|
||||
if (model === "verification") {
|
||||
await ensureOrganizationVerification("authDeleteVerification", { where: transformedWhere });
|
||||
const organization = await appOrganization();
|
||||
await organization.commandBetterAuthVerificationDelete({ where: transformedWhere });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -361,20 +380,20 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const userActor = await getUser(userId);
|
||||
const organization = await appOrganization();
|
||||
const before = await userActor.findOneAuthRecord({ model, where: transformedWhere });
|
||||
await userActor.deleteAuthRecord({ model, where: transformedWhere });
|
||||
const before = await userActor.betterAuthFindOneRecord({ model, where: transformedWhere });
|
||||
await userActor.authDelete({ model, where: transformedWhere });
|
||||
|
||||
if (model === "session" && before) {
|
||||
await organization.authDeleteSessionIndex({
|
||||
await organization.commandBetterAuthSessionIndexDelete({
|
||||
sessionId: before.id,
|
||||
sessionToken: before.token,
|
||||
});
|
||||
}
|
||||
|
||||
if (model === "account" && before) {
|
||||
await organization.authDeleteAccountIndex({
|
||||
await organization.commandBetterAuthAccountIndexDelete({
|
||||
id: before.id,
|
||||
providerId: before.providerId,
|
||||
accountId: before.accountId,
|
||||
|
|
@ -382,14 +401,16 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
}
|
||||
|
||||
if (model === "user" && before?.email) {
|
||||
await organization.authDeleteEmailIndex({ email: before.email.toLowerCase() });
|
||||
await organization.commandBetterAuthEmailIndexDelete({
|
||||
email: before.email.toLowerCase(),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteMany: async ({ model, where }) => {
|
||||
const transformedWhere = transformWhereClause({ model, where, action: "deleteMany" });
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authDeleteManyVerification", { where: transformedWhere });
|
||||
return await ensureOrganizationVerification("commandBetterAuthVerificationDeleteMany", { where: transformedWhere });
|
||||
}
|
||||
|
||||
if (model === "session") {
|
||||
|
|
@ -397,12 +418,12 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
if (!userId) {
|
||||
return 0;
|
||||
}
|
||||
const userActor = await getAuthUser(userId);
|
||||
const userActor = await getUser(userId);
|
||||
const organization = await appOrganization();
|
||||
const sessions = await userActor.findManyAuthRecords({ model, where: transformedWhere, limit: 5000 });
|
||||
const deleted = await userActor.deleteManyAuthRecords({ model, where: transformedWhere });
|
||||
const sessions = await userActor.betterAuthFindManyRecords({ model, where: transformedWhere, limit: 5000 });
|
||||
const deleted = await userActor.authDeleteMany({ model, where: transformedWhere });
|
||||
for (const session of sessions) {
|
||||
await organization.authDeleteSessionIndex({
|
||||
await organization.commandBetterAuthSessionIndexDelete({
|
||||
sessionId: session.id,
|
||||
sessionToken: session.token,
|
||||
});
|
||||
|
|
@ -415,15 +436,16 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return 0;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
const deleted = await userActor.deleteManyAuthRecords({ model, where: transformedWhere });
|
||||
const userActor = await getUser(userId);
|
||||
const deleted = await userActor.authDeleteMany({ model, where: transformedWhere });
|
||||
return deleted;
|
||||
},
|
||||
|
||||
count: async ({ model, where }) => {
|
||||
const transformedWhere = transformWhereClause({ model, where, action: "count" });
|
||||
if (model === "verification") {
|
||||
return await ensureOrganizationVerification("authCountVerification", { where: transformedWhere });
|
||||
const organization = await appOrganization();
|
||||
return await organization.betterAuthCountVerification({ where: transformedWhere });
|
||||
}
|
||||
|
||||
const userId = await resolveUserIdForQuery(model, transformedWhere);
|
||||
|
|
@ -431,8 +453,8 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
return 0;
|
||||
}
|
||||
|
||||
const userActor = await getAuthUser(userId);
|
||||
return await userActor.countAuthRecords({ model, where: transformedWhere });
|
||||
const userActor = await getUser(userId);
|
||||
return await userActor.betterAuthCountRecords({ model, where: transformedWhere });
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
@ -477,17 +499,17 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
|
||||
async getAuthState(sessionId: string) {
|
||||
const organization = await appOrganization();
|
||||
const route = await organization.authFindSessionIndex({ sessionId });
|
||||
const route = await organization.betterAuthFindSessionIndex({ sessionId });
|
||||
if (!route?.userId) {
|
||||
return null;
|
||||
}
|
||||
const userActor = await getAuthUser(route.userId);
|
||||
const userActor = await getUser(route.userId);
|
||||
return await userActor.getAppAuthState({ sessionId });
|
||||
},
|
||||
|
||||
async upsertUserProfile(userId: string, patch: Record<string, unknown>) {
|
||||
const userActor = await getAuthUser(userId);
|
||||
return await userActor.upsertUserProfile({ userId, patch });
|
||||
const userActor = await getUser(userId);
|
||||
return await userActor.profileUpsert({ userId, patch });
|
||||
},
|
||||
|
||||
async setActiveOrganization(sessionId: string, activeOrganizationId: string | null) {
|
||||
|
|
@ -495,8 +517,8 @@ export function initBetterAuthService(actorClient: any, options: { apiUrl: strin
|
|||
if (!authState?.user?.id) {
|
||||
throw new Error(`Unknown auth session ${sessionId}`);
|
||||
}
|
||||
const userActor = await getAuthUser(authState.user.id);
|
||||
return await userActor.upsertSessionState({ sessionId, activeOrganizationId });
|
||||
const userActor = await getUser(authState.user.id);
|
||||
return await userActor.sessionStateUpsert({ sessionId, activeOrganizationId });
|
||||
},
|
||||
|
||||
async getAccessTokenForSession(sessionId: string) {
|
||||
|
|
|
|||
584
foundry/packages/backend/src/services/branch-name-prefixes.ts
Normal file
584
foundry/packages/backend/src/services/branch-name-prefixes.ts
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
// Auto-generated list of branch name prefixes.
|
||||
// Source: McMaster-Carr product catalog.
|
||||
export const BRANCH_NAME_PREFIXES: readonly string[] = [
|
||||
"abrasive-blasters",
|
||||
"ac-motors",
|
||||
"access-doors",
|
||||
"adjustable-handles",
|
||||
"aerosol-paint",
|
||||
"air-cleaners",
|
||||
"air-cylinders",
|
||||
"air-filters",
|
||||
"air-hose",
|
||||
"air-knives",
|
||||
"air-nozzles",
|
||||
"air-regulators",
|
||||
"air-ride-wheels",
|
||||
"air-slides",
|
||||
"alligator-clips",
|
||||
"alloy-steel",
|
||||
"aluminum-honeycomb",
|
||||
"angle-indicators",
|
||||
"antiseize-lubricants",
|
||||
"antislip-fluid",
|
||||
"backlight-panel-kits",
|
||||
"ball-bearings",
|
||||
"ball-end-mills",
|
||||
"ball-joint-linkages",
|
||||
"ball-transfers",
|
||||
"band-clamps",
|
||||
"band-saw-blades",
|
||||
"bar-clamps",
|
||||
"bar-grating",
|
||||
"barbed-hose-fittings",
|
||||
"barbed-tube-fittings",
|
||||
"basket-strainers",
|
||||
"batch-cans",
|
||||
"battery-chargers",
|
||||
"battery-holders",
|
||||
"bead-chain",
|
||||
"beam-clamps",
|
||||
"belt-conveyors",
|
||||
"bench-scales",
|
||||
"bench-vises",
|
||||
"bin-boxes",
|
||||
"bin-storage",
|
||||
"binding-posts",
|
||||
"blank-tags",
|
||||
"blasting-cabinets",
|
||||
"blind-rivets",
|
||||
"bluetooth-padlocks",
|
||||
"boring-lathe-tools",
|
||||
"box-reducers",
|
||||
"box-wrenches",
|
||||
"braided-hose",
|
||||
"brass-pipe-fittings",
|
||||
"breather-vents",
|
||||
"butt-splices",
|
||||
"c-clamps",
|
||||
"cable-cutters",
|
||||
"cable-holders",
|
||||
"cable-tie-mounts",
|
||||
"cable-ties",
|
||||
"cam-handles",
|
||||
"cam-latches",
|
||||
"cam-locks",
|
||||
"cap-nuts",
|
||||
"captive-panel-screws",
|
||||
"carbide-burs",
|
||||
"carbide-inserts",
|
||||
"carbon-fiber",
|
||||
"carbon-steel",
|
||||
"cardstock-tags",
|
||||
"carriage-bolts",
|
||||
"cast-acrylic",
|
||||
"cast-iron",
|
||||
"cast-nylon",
|
||||
"casting-compounds",
|
||||
"ceiling-lights",
|
||||
"ceramic-adhesives",
|
||||
"chain-slings",
|
||||
"check-valves",
|
||||
"chemical-hose",
|
||||
"chemistry-meters",
|
||||
"chemistry-testing",
|
||||
"chip-clearing-tools",
|
||||
"chucking-reamers",
|
||||
"cinching-straps",
|
||||
"circuit-breakers",
|
||||
"circular-saw-blades",
|
||||
"circular-saws",
|
||||
"clamping-hangers",
|
||||
"clevis-pins",
|
||||
"clevis-rod-ends",
|
||||
"clip-on-nuts",
|
||||
"coaxial-connectors",
|
||||
"coaxial-cords",
|
||||
"coiled-spring-pins",
|
||||
"compact-connectors",
|
||||
"computer-adapters",
|
||||
"concrete-adhesives",
|
||||
"concrete-repair",
|
||||
"contour-transfers",
|
||||
"conveyor-belt-lacing",
|
||||
"conveyor-belting",
|
||||
"conveyor-brushes",
|
||||
"conveyor-rollers",
|
||||
"coolant-hose",
|
||||
"copper-tube-fittings",
|
||||
"copper-tubing",
|
||||
"cord-grips",
|
||||
"cord-reels",
|
||||
"cotter-pins",
|
||||
"coupling-nuts",
|
||||
"cpvc-pipe-fittings",
|
||||
"cup-brushes",
|
||||
"cutoff-wheels",
|
||||
"cylinder-hones",
|
||||
"cylinder-racks",
|
||||
"cylinder-trucks",
|
||||
"data-cable",
|
||||
"data-connectors",
|
||||
"dc-motors",
|
||||
"dead-blow-hammers",
|
||||
"delrin-acetal-resin",
|
||||
"desiccant-air-dryers",
|
||||
"desktop-cranes",
|
||||
"dial-calipers",
|
||||
"dial-indicators",
|
||||
"die-springs",
|
||||
"direct-heaters",
|
||||
"disconnect-switches",
|
||||
"dispensing-needles",
|
||||
"dispensing-pumps",
|
||||
"disposable-clothing",
|
||||
"disposable-gloves",
|
||||
"document-protectors",
|
||||
"door-closers",
|
||||
"door-handles",
|
||||
"door-holders",
|
||||
"dowel-pins",
|
||||
"drafting-equipment",
|
||||
"drain-cleaners",
|
||||
"drainage-mats",
|
||||
"draw-latches",
|
||||
"drawer-cabinets",
|
||||
"drawer-slides",
|
||||
"drill-bit-sets",
|
||||
"drill-bits",
|
||||
"drill-bushings",
|
||||
"drill-chucks",
|
||||
"drill-presses",
|
||||
"drilling-screws",
|
||||
"drinking-fountains",
|
||||
"drive-anchors",
|
||||
"drive-rollers",
|
||||
"drive-shafts",
|
||||
"drum-faucets",
|
||||
"drum-pumps",
|
||||
"drum-top-vacuums",
|
||||
"drum-trucks",
|
||||
"dry-box-gloves",
|
||||
"dry-erase-boards",
|
||||
"dry-film-lubricants",
|
||||
"duct-fans",
|
||||
"duct-hose",
|
||||
"duct-tape",
|
||||
"dust-collectors",
|
||||
"dustless-chalk",
|
||||
"edge-trim",
|
||||
"electric-actuators",
|
||||
"electric-drills",
|
||||
"electric-drum-pumps",
|
||||
"electric-mixers",
|
||||
"electrical-switches",
|
||||
"electrical-tape",
|
||||
"electronic-calipers",
|
||||
"enclosure-heaters",
|
||||
"enclosure-panels",
|
||||
"ethernet-cords",
|
||||
"exhaust-fans",
|
||||
"exit-lights",
|
||||
"expansion-joints",
|
||||
"expansion-plugs",
|
||||
"extension-cords",
|
||||
"extension-springs",
|
||||
"fabric-snaps",
|
||||
"fan-blades",
|
||||
"fep-tubing",
|
||||
"fiberglass-grating",
|
||||
"file-holders",
|
||||
"filter-bag-housings",
|
||||
"filter-bags",
|
||||
"filter-cartridges",
|
||||
"fire-fighting-hose",
|
||||
"first-aid-supplies",
|
||||
"fixture-clamps",
|
||||
"flange-locknuts",
|
||||
"flange-mount-seals",
|
||||
"flap-sanding-discs",
|
||||
"flap-sanding-wheels",
|
||||
"flared-tube-fittings",
|
||||
"flashing-lights",
|
||||
"flat-washers",
|
||||
"flexible-shafts",
|
||||
"flexible-shank-burs",
|
||||
"flexible-trays",
|
||||
"float-valves",
|
||||
"floor-locks",
|
||||
"floor-marking-tape",
|
||||
"floor-scales",
|
||||
"floor-squeegees",
|
||||
"flow-sights",
|
||||
"flow-switches",
|
||||
"flowmeter-totalizers",
|
||||
"foot-switches",
|
||||
"force-gauges",
|
||||
"fume-exhausters",
|
||||
"garbage-bags",
|
||||
"garden-hose",
|
||||
"gas-hose",
|
||||
"gas-regulators",
|
||||
"gas-springs",
|
||||
"gauge-blocks",
|
||||
"glass-sights",
|
||||
"gold-wire",
|
||||
"grab-latches",
|
||||
"grease-fittings",
|
||||
"grinding-bits",
|
||||
"grinding-wheels",
|
||||
"hand-brushes",
|
||||
"hand-chain-hoists",
|
||||
"hand-reamers",
|
||||
"hand-trucks",
|
||||
"hand-wheels",
|
||||
"hand-winches",
|
||||
"hanging-scales",
|
||||
"hard-hats",
|
||||
"hardened-shafts",
|
||||
"hardness-testers",
|
||||
"heat-exchangers",
|
||||
"heat-guns",
|
||||
"heat-lamps",
|
||||
"heat-sealable-bags",
|
||||
"heat-set-inserts",
|
||||
"heat-shrink-tubing",
|
||||
"heat-sinks",
|
||||
"heated-scrapers",
|
||||
"helical-inserts",
|
||||
"hex-bit-sockets",
|
||||
"hex-head-screws",
|
||||
"hex-nuts",
|
||||
"high-accuracy-rulers",
|
||||
"high-amp-relays",
|
||||
"high-vacuum-filters",
|
||||
"high-vacuum-sights",
|
||||
"hinge-adjusters",
|
||||
"hoist-rings",
|
||||
"hole-saws",
|
||||
"hose-couplings",
|
||||
"hose-reels",
|
||||
"hot-melt-glue",
|
||||
"hydraulic-cylinders",
|
||||
"hydraulic-hose",
|
||||
"hydraulic-jacks",
|
||||
"iec-connectors",
|
||||
"immersion-heaters",
|
||||
"impression-foam",
|
||||
"indicating-lights",
|
||||
"inflatable-wedges",
|
||||
"ink-markers",
|
||||
"insertion-heaters",
|
||||
"inspection-mirrors",
|
||||
"instrument-carts",
|
||||
"insulation-jacketing",
|
||||
"jam-removers",
|
||||
"jigsaw-blades",
|
||||
"key-cabinets",
|
||||
"key-locking-inserts",
|
||||
"key-stock",
|
||||
"keyed-drive-shafts",
|
||||
"keyseat-end-mills",
|
||||
"l-key-sets",
|
||||
"l-keys",
|
||||
"label-holders",
|
||||
"latching-connectors",
|
||||
"lathe-tools",
|
||||
"lavatory-partitions",
|
||||
"lead-screws",
|
||||
"leveling-lasers",
|
||||
"leveling-mounts",
|
||||
"lid-supports",
|
||||
"lift-off-hinges",
|
||||
"lift-trucks",
|
||||
"light-bulbs",
|
||||
"limit-switches",
|
||||
"linear-ball-bearings",
|
||||
"liquid-level-gauges",
|
||||
"lock-washers",
|
||||
"lockout-devices",
|
||||
"loop-clamps",
|
||||
"loop-hangers",
|
||||
"machine-brackets",
|
||||
"machine-handles",
|
||||
"machine-keys",
|
||||
"magnetic-base-drills",
|
||||
"magnetic-bumpers",
|
||||
"masking-tape",
|
||||
"masonry-drill-bits",
|
||||
"medium-amp-relays",
|
||||
"metal-cable-ties",
|
||||
"metal-panels",
|
||||
"metal-plates",
|
||||
"metal-tags",
|
||||
"metering-pumps",
|
||||
"metric-o-rings",
|
||||
"mil-spec-connectors",
|
||||
"mobile-lift-tables",
|
||||
"motor-controls",
|
||||
"motor-starters",
|
||||
"mountable-cable-ties",
|
||||
"mounting-tape",
|
||||
"neoprene-foam",
|
||||
"nickel-titanium",
|
||||
"nonmarring-hammers",
|
||||
"nonslip-bumpers",
|
||||
"nylon-rivets",
|
||||
"nylon-tubing",
|
||||
"o-rings",
|
||||
"oil-level-indicators",
|
||||
"oil-reservoirs",
|
||||
"oil-skimmers",
|
||||
"on-off-valves",
|
||||
"open-end-wrenches",
|
||||
"outlet-boxes",
|
||||
"outlet-strips",
|
||||
"packaging-tape",
|
||||
"paint-brushes",
|
||||
"paint-markers",
|
||||
"paint-sprayers",
|
||||
"pallet-racks",
|
||||
"pallet-trucks",
|
||||
"panel-air-filters",
|
||||
"parts-baskets",
|
||||
"pendant-switches",
|
||||
"perforated-sheets",
|
||||
"pest-control",
|
||||
"petroleum-hose",
|
||||
"piano-hinges",
|
||||
"pipe-couplings",
|
||||
"pipe-gaskets",
|
||||
"pipe-markers",
|
||||
"pipe-wrenches",
|
||||
"plank-grating",
|
||||
"plastic-clamps",
|
||||
"plastic-mesh",
|
||||
"plate-lifting-clamps",
|
||||
"platinum-wire",
|
||||
"plier-clamps",
|
||||
"plug-gauges",
|
||||
"portable-lights",
|
||||
"power-cords",
|
||||
"power-supplied",
|
||||
"power-supplies",
|
||||
"precision-knives",
|
||||
"press-fit-nuts",
|
||||
"press-in-nuts",
|
||||
"protecting-tape",
|
||||
"protective-coatings",
|
||||
"protective-curtains",
|
||||
"protective-panels",
|
||||
"protective-wrap",
|
||||
"proximity-switches",
|
||||
"pull-handles",
|
||||
"push-brooms",
|
||||
"push-nuts",
|
||||
"push-on-seals",
|
||||
"pvc-pipe-fittings",
|
||||
"pvc-tubing",
|
||||
"quick-release-pins",
|
||||
"ratchet-pullers",
|
||||
"recycled-plastics",
|
||||
"repair-adhesives",
|
||||
"repair-clamps",
|
||||
"reusable-cable-ties",
|
||||
"ring-terminals",
|
||||
"rivet-nuts",
|
||||
"robot-base-mounts",
|
||||
"robot-bases",
|
||||
"rocker-switches",
|
||||
"rod-wipers",
|
||||
"roller-bearings",
|
||||
"roller-chain",
|
||||
"roller-conveyors",
|
||||
"roof-exhaust-fans",
|
||||
"roof-repair",
|
||||
"rotary-broaches",
|
||||
"rotary-hammers",
|
||||
"rotary-shaft-seals",
|
||||
"rotating-cranes",
|
||||
"rotating-joints",
|
||||
"router-bits",
|
||||
"rtd-probes",
|
||||
"rubber-edge-seals",
|
||||
"rubber-tread-wheels",
|
||||
"rubber-tubing",
|
||||
"safety-cabinets",
|
||||
"safety-glasses",
|
||||
"safety-mirrors",
|
||||
"sanding-belts",
|
||||
"sanding-discs",
|
||||
"sanding-guides",
|
||||
"sanding-rolls",
|
||||
"sanding-sheets",
|
||||
"screw-extractors",
|
||||
"screw-jacks",
|
||||
"scrub-brushes",
|
||||
"sealing-washers",
|
||||
"security-lights",
|
||||
"sensor-connectors",
|
||||
"set-screws",
|
||||
"setup-clamps",
|
||||
"shaft-collars",
|
||||
"shaft-couplings",
|
||||
"shaft-repair-sleeves",
|
||||
"shaft-supports",
|
||||
"sharpening-stones",
|
||||
"sheet-metal-cutters",
|
||||
"shelf-cabinets",
|
||||
"shim-stock",
|
||||
"shim-tape",
|
||||
"shipping-pails",
|
||||
"shock-absorbers",
|
||||
"shoulder-screws",
|
||||
"shower-stations",
|
||||
"silicone-foam",
|
||||
"sleeve-bearings",
|
||||
"slide-bolts",
|
||||
"slitting-saws",
|
||||
"slotted-spring-pins",
|
||||
"sludge-samplers",
|
||||
"small-parts-storage",
|
||||
"snap-acting-switches",
|
||||
"soap-dispensers",
|
||||
"socket-head-screws",
|
||||
"socket-organizers",
|
||||
"socket-wrenches",
|
||||
"soldering-irons",
|
||||
"solid-rivets",
|
||||
"solid-rod-ends",
|
||||
"sound-insulation",
|
||||
"space-heaters",
|
||||
"spacing-beads",
|
||||
"spanner-wrenches",
|
||||
"specialty-pliers",
|
||||
"specialty-vises",
|
||||
"specialty-washers",
|
||||
"speed-reducers",
|
||||
"splicing-connectors",
|
||||
"spray-bottles",
|
||||
"spray-nozzles",
|
||||
"spring-clamps",
|
||||
"spring-plungers",
|
||||
"spring-steel",
|
||||
"square-drive-sockets",
|
||||
"square-end-mills",
|
||||
"square-nuts",
|
||||
"squeeze-bottles",
|
||||
"stack-lights",
|
||||
"stainless-steel",
|
||||
"stair-treads",
|
||||
"static-control-mats",
|
||||
"steel-carts",
|
||||
"steel-pipe-fittings",
|
||||
"steel-pipe-flanges",
|
||||
"steel-stamps",
|
||||
"steel-tubing",
|
||||
"step-ladders",
|
||||
"stepper-motors",
|
||||
"storage-bags",
|
||||
"storage-boxes",
|
||||
"storage-chests",
|
||||
"straight-ladders",
|
||||
"strap-hinges",
|
||||
"stretch-wrap",
|
||||
"strip-doors",
|
||||
"strip-springs",
|
||||
"strobe-lights",
|
||||
"structural-adhesives",
|
||||
"strut-channel",
|
||||
"strut-channel-nuts",
|
||||
"strut-mount-clamps",
|
||||
"suction-cup-lifters",
|
||||
"suction-strainers",
|
||||
"super-absorbent-foam",
|
||||
"super-flexible-glass",
|
||||
"surface-fillers",
|
||||
"surface-mount-hinges",
|
||||
"t-handle-keys",
|
||||
"t-slotted-framing",
|
||||
"tamper-seals",
|
||||
"tank-level-measurers",
|
||||
"tape-dispensers",
|
||||
"tape-measures",
|
||||
"taper-pins",
|
||||
"tapping-screws",
|
||||
"teflon-ptfe",
|
||||
"terminal-blocks",
|
||||
"test-indicators",
|
||||
"test-leads",
|
||||
"test-weights",
|
||||
"tethered-knobs",
|
||||
"thermal-insulation",
|
||||
"thread-adapters",
|
||||
"thread-sealant-tape",
|
||||
"thread-sealants",
|
||||
"threaded-inserts",
|
||||
"threaded-standoffs",
|
||||
"threaded-studs",
|
||||
"thrust-ball-bearings",
|
||||
"thrust-bearings",
|
||||
"thumb-nuts",
|
||||
"thumb-screws",
|
||||
"tie-down-rings",
|
||||
"time-clocks",
|
||||
"timer-relays",
|
||||
"timer-switches",
|
||||
"toggle-clamps",
|
||||
"toggle-switches",
|
||||
"tool-holders",
|
||||
"tool-sets",
|
||||
"tool-steel",
|
||||
"torque-wrenches",
|
||||
"torsion-springs",
|
||||
"tote-boxes",
|
||||
"touch-bars",
|
||||
"track-casters",
|
||||
"track-rollers",
|
||||
"track-wheels",
|
||||
"traction-mats",
|
||||
"trolley-systems",
|
||||
"tube-brushes",
|
||||
"tube-fittings",
|
||||
"tubular-light-bulbs",
|
||||
"turn-lock-connectors",
|
||||
"twist-ties",
|
||||
"u-bolts",
|
||||
"u-joints",
|
||||
"ul-class-fuses",
|
||||
"unthreaded-spacers",
|
||||
"usb-adapters",
|
||||
"usb-cords",
|
||||
"utility-knives",
|
||||
"v-belts",
|
||||
"vacuum-cups",
|
||||
"vacuum-pumps",
|
||||
"wall-louvers",
|
||||
"wash-fountains",
|
||||
"wash-guns",
|
||||
"waste-containers",
|
||||
"water-deionizers",
|
||||
"water-filters",
|
||||
"water-hose",
|
||||
"water-removal-pumps",
|
||||
"weather-stations",
|
||||
"web-slings",
|
||||
"weld-nuts",
|
||||
"welding-clothing",
|
||||
"welding-helmets",
|
||||
"wet-dry-vacuums",
|
||||
"wet-mops",
|
||||
"wheel-brushes",
|
||||
"wing-nuts",
|
||||
"wire-cloth",
|
||||
"wire-connectors",
|
||||
"wire-cutting-pliers",
|
||||
"wire-partitions",
|
||||
"wire-rope",
|
||||
"wire-rope-clamps",
|
||||
"wire-wrap",
|
||||
"wool-felt",
|
||||
"work-platforms",
|
||||
"workbench-legs",
|
||||
"woven-wire-cloth",
|
||||
] as const;
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { BRANCH_NAME_PREFIXES } from "./branch-name-prefixes.js";
|
||||
|
||||
export interface ResolveCreateFlowDecisionInput {
|
||||
task: string;
|
||||
explicitTitle?: string;
|
||||
|
|
@ -89,30 +91,42 @@ export function sanitizeBranchName(input: string): string {
|
|||
return trimmed.slice(0, 50).replace(/-+$/g, "");
|
||||
}
|
||||
|
||||
function generateRandomSuffix(length: number): string {
|
||||
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function generateBranchName(): string {
|
||||
const prefix = BRANCH_NAME_PREFIXES[Math.floor(Math.random() * BRANCH_NAME_PREFIXES.length)]!;
|
||||
const suffix = generateRandomSuffix(4);
|
||||
return `${prefix}-${suffix}`;
|
||||
}
|
||||
|
||||
export function resolveCreateFlowDecision(input: ResolveCreateFlowDecisionInput): ResolveCreateFlowDecisionResult {
|
||||
const explicitBranch = input.explicitBranchName?.trim();
|
||||
const title = deriveFallbackTitle(input.task, input.explicitTitle);
|
||||
const generatedBase = sanitizeBranchName(title) || "task";
|
||||
|
||||
const branchBase = explicitBranch && explicitBranch.length > 0 ? explicitBranch : generatedBase;
|
||||
|
||||
const existingBranches = new Set(input.localBranches.map((value) => value.trim()).filter((value) => value.length > 0));
|
||||
const existingTaskBranches = new Set(input.taskBranches.map((value) => value.trim()).filter((value) => value.length > 0));
|
||||
const conflicts = (name: string): boolean => existingBranches.has(name) || existingTaskBranches.has(name);
|
||||
|
||||
if (explicitBranch && conflicts(branchBase)) {
|
||||
throw new Error(`Branch '${branchBase}' already exists. Choose a different --name/--branch value.`);
|
||||
if (explicitBranch && explicitBranch.length > 0) {
|
||||
if (conflicts(explicitBranch)) {
|
||||
throw new Error(`Branch '${explicitBranch}' already exists. Choose a different --name/--branch value.`);
|
||||
}
|
||||
return { title, branchName: explicitBranch };
|
||||
}
|
||||
|
||||
if (explicitBranch) {
|
||||
return { title, branchName: branchBase };
|
||||
}
|
||||
|
||||
let candidate = branchBase;
|
||||
let index = 2;
|
||||
while (conflicts(candidate)) {
|
||||
candidate = `${branchBase}-${index}`;
|
||||
index += 1;
|
||||
// Generate a random McMaster-Carr-style branch name, retrying on conflicts
|
||||
let candidate = generateBranchName();
|
||||
let attempts = 0;
|
||||
while (conflicts(candidate) && attempts < 100) {
|
||||
candidate = generateBranchName();
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getOrCreateOrganization } from "../actors/handles.js";
|
||||
import { APP_SHELL_ORGANIZATION_ID } from "../actors/organization/app-shell.js";
|
||||
import { APP_SHELL_ORGANIZATION_ID } from "../actors/organization/constants.js";
|
||||
|
||||
export interface ResolvedGithubAuth {
|
||||
githubToken: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue