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>
This commit is contained in:
Nathan Flurry 2026-03-16 02:00:31 -07:00
parent b372383cfd
commit 29e5821fef
23 changed files with 490 additions and 930 deletions

View file

@ -183,6 +183,7 @@ export interface BackendClientOptions {
endpoint: string;
defaultOrganizationId?: string;
mode?: "remote" | "mock";
encoding?: "json" | "cbor" | "bare";
}
export interface BackendClient {
@ -413,7 +414,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
const endpoints = deriveBackendEndpoints(options.endpoint);
const rivetApiEndpoint = endpoints.rivetEndpoint;
const appApiEndpoint = endpoints.appEndpoint;
const client = createClient({ endpoint: rivetApiEndpoint }) as unknown as RivetClient;
const client = createClient({ endpoint: rivetApiEndpoint, encoding: options.encoding }) as unknown as RivetClient;
const workspaceSubscriptions = new Map<
string,
{
@ -514,10 +515,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
const sandboxes = detail.sandboxes as Array<(typeof detail.sandboxes)[number] & { sandboxActorId?: string }>;
const sandbox = sandboxes.find(
(sb) =>
sb.sandboxId === sandboxId &&
sb.sandboxProviderId === sandboxProviderId &&
typeof sb.sandboxActorId === "string" &&
sb.sandboxActorId.length > 0,
sb.sandboxId === sandboxId && sb.sandboxProviderId === sandboxProviderId && typeof sb.sandboxActorId === "string" && sb.sandboxActorId.length > 0,
);
if (sandbox?.sandboxActorId) {
return (client as any).taskSandbox.getForId(sandbox.sandboxActorId);
@ -582,12 +580,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
return (await task(organizationId, repoId, taskIdValue)).getTaskDetail(await getAuthSessionInput());
};
const getSessionDetailWithAuth = async (
organizationId: string,
repoId: string,
taskIdValue: string,
sessionId: string,
): Promise<WorkspaceSessionDetail> => {
const getSessionDetailWithAuth = async (organizationId: string, repoId: string, taskIdValue: string, sessionId: string): Promise<WorkspaceSessionDetail> => {
return (await task(organizationId, repoId, taskIdValue)).getSessionDetail(await withAuthSessionInput({ sessionId }));
};
@ -596,67 +589,67 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
const summary = await (await organization(organizationId)).getOrganizationSummary({ organizationId });
const resolvedTasks = await Promise.all(
summary.taskSummaries.map(async (taskSummary) => {
let detail;
try {
const taskHandle = await task(organizationId, taskSummary.repoId, taskSummary.id);
detail = await taskHandle.getTaskDetail(authSessionInput);
} catch (error) {
if (isActorNotFoundError(error)) {
return null;
}
throw error;
let detail;
try {
const taskHandle = await task(organizationId, taskSummary.repoId, taskSummary.id);
detail = await taskHandle.getTaskDetail(authSessionInput);
} catch (error) {
if (isActorNotFoundError(error)) {
return null;
}
const sessionDetails = await Promise.all(
detail.sessionsSummary.map(async (session) => {
try {
const full = await (await task(organizationId, detail.repoId, detail.id)).getSessionDetail({
sessionId: session.id,
...(authSessionInput ?? {}),
});
return [session.id, full] as const;
} catch (error) {
if (isActorNotFoundError(error)) {
return null;
}
throw error;
throw error;
}
const sessionDetails = await Promise.all(
detail.sessionsSummary.map(async (session) => {
try {
const full = await (await task(organizationId, detail.repoId, detail.id)).getSessionDetail({
sessionId: session.id,
...(authSessionInput ?? {}),
});
return [session.id, full] as const;
} catch (error) {
if (isActorNotFoundError(error)) {
return null;
}
}),
);
const sessionDetailsById = new Map(sessionDetails.filter((entry): entry is readonly [string, WorkspaceSessionDetail] => entry !== null));
return {
id: detail.id,
repoId: detail.repoId,
title: detail.title,
status: detail.status,
repoName: detail.repoName,
updatedAtMs: detail.updatedAtMs,
branch: detail.branch,
pullRequest: detail.pullRequest,
activeSessionId: detail.activeSessionId ?? null,
sessions: detail.sessionsSummary.map((session) => {
const full = sessionDetailsById.get(session.id);
return {
id: session.id,
sessionId: session.sessionId,
sessionName: session.sessionName,
agent: session.agent,
model: session.model,
status: session.status,
thinkingSinceMs: session.thinkingSinceMs,
unread: session.unread,
created: session.created,
draft: full?.draft ?? { text: "", attachments: [], updatedAtMs: null },
transcript: full?.transcript ?? [],
};
}),
fileChanges: detail.fileChanges,
diffs: detail.diffs,
fileTree: detail.fileTree,
minutesUsed: detail.minutesUsed,
activeSandboxId: detail.activeSandboxId ?? null,
};
}),
);
throw error;
}
}),
);
const sessionDetailsById = new Map(sessionDetails.filter((entry): entry is readonly [string, WorkspaceSessionDetail] => entry !== null));
return {
id: detail.id,
repoId: detail.repoId,
title: detail.title,
status: detail.status,
repoName: detail.repoName,
updatedAtMs: detail.updatedAtMs,
branch: detail.branch,
pullRequest: detail.pullRequest,
activeSessionId: detail.activeSessionId ?? null,
sessions: detail.sessionsSummary.map((session) => {
const full = sessionDetailsById.get(session.id);
return {
id: session.id,
sessionId: session.sessionId,
sessionName: session.sessionName,
agent: session.agent,
model: session.model,
status: session.status,
thinkingSinceMs: session.thinkingSinceMs,
unread: session.unread,
created: session.created,
draft: full?.draft ?? { text: "", attachments: [], updatedAtMs: null },
transcript: full?.transcript ?? [],
};
}),
fileChanges: detail.fileChanges,
diffs: detail.diffs,
fileTree: detail.fileTree,
minutesUsed: detail.minutesUsed,
activeSandboxId: detail.activeSandboxId ?? null,
};
}),
);
const tasks = resolvedTasks.filter((task): task is Exclude<(typeof resolvedTasks)[number], null> => task !== null);
const repositories = summary.repos
@ -1205,11 +1198,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
return await withSandboxHandle(organizationId, sandboxProviderId, sandboxId, async (handle) => handle.sandboxAgentConnection());
},
async getSandboxWorkspaceModelGroups(
organizationId: string,
sandboxProviderId: SandboxProviderId,
sandboxId: string,
): Promise<WorkspaceModelGroup[]> {
async getSandboxWorkspaceModelGroups(organizationId: string, sandboxProviderId: SandboxProviderId, sandboxId: string): Promise<WorkspaceModelGroup[]> {
return await withSandboxHandle(organizationId, sandboxProviderId, sandboxId, async (handle) => handle.listWorkspaceModelGroups());
},