mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-19 00:25:23 +00:00
Rename Foundry handoffs to tasks (#239)
* Restore foundry onboarding stack * Consolidate foundry rename * Create foundry tasks without prompts * Rename Foundry handoffs to tasks
This commit is contained in:
parent
d30cc0bcc8
commit
d75e8c31d1
281 changed files with 9242 additions and 4356 deletions
672
foundry/packages/backend/src/actors/workspace/actions.ts
Normal file
672
foundry/packages/backend/src/actors/workspace/actions.ts
Normal file
|
|
@ -0,0 +1,672 @@
|
|||
// @ts-nocheck
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { Loop } from "rivetkit/workflow";
|
||||
import type {
|
||||
AddRepoInput,
|
||||
CreateTaskInput,
|
||||
TaskRecord,
|
||||
TaskSummary,
|
||||
TaskWorkbenchChangeModelInput,
|
||||
TaskWorkbenchCreateTaskInput,
|
||||
TaskWorkbenchDiffInput,
|
||||
TaskWorkbenchRenameInput,
|
||||
TaskWorkbenchRenameSessionInput,
|
||||
TaskWorkbenchSelectInput,
|
||||
TaskWorkbenchSetSessionUnreadInput,
|
||||
TaskWorkbenchSendMessageInput,
|
||||
TaskWorkbenchSnapshot,
|
||||
TaskWorkbenchTabInput,
|
||||
TaskWorkbenchUpdateDraftInput,
|
||||
HistoryEvent,
|
||||
HistoryQueryInput,
|
||||
ListTasksInput,
|
||||
ProviderId,
|
||||
RepoOverview,
|
||||
RepoStackActionInput,
|
||||
RepoStackActionResult,
|
||||
RepoRecord,
|
||||
StarSandboxAgentRepoInput,
|
||||
StarSandboxAgentRepoResult,
|
||||
SwitchResult,
|
||||
WorkspaceUseInput,
|
||||
} from "@sandbox-agent/foundry-shared";
|
||||
import { getActorRuntimeContext } from "../context.js";
|
||||
import { getTask, getOrCreateHistory, getOrCreateProject, selfWorkspace } from "../handles.js";
|
||||
import { logActorWarning, resolveErrorMessage } from "../logging.js";
|
||||
import { normalizeRemoteUrl, repoIdFromRemote } from "../../services/repo.js";
|
||||
import { taskLookup, repos, providerProfiles } from "./db/schema.js";
|
||||
import { agentTypeForModel } from "../task/workbench.js";
|
||||
import { expectQueueResponse } from "../../services/queue.js";
|
||||
import { workspaceAppActions } from "./app-shell.js";
|
||||
|
||||
interface WorkspaceState {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
interface RefreshProviderProfilesCommand {
|
||||
providerId?: ProviderId;
|
||||
}
|
||||
|
||||
interface GetTaskInput {
|
||||
workspaceId: string;
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
interface TaskProxyActionInput extends GetTaskInput {
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
interface RepoOverviewInput {
|
||||
workspaceId: string;
|
||||
repoId: string;
|
||||
}
|
||||
|
||||
const WORKSPACE_QUEUE_NAMES = ["workspace.command.addRepo", "workspace.command.createTask", "workspace.command.refreshProviderProfiles"] as const;
|
||||
const SANDBOX_AGENT_REPO = "rivet-dev/sandbox-agent";
|
||||
|
||||
type WorkspaceQueueName = (typeof WORKSPACE_QUEUE_NAMES)[number];
|
||||
|
||||
export { WORKSPACE_QUEUE_NAMES };
|
||||
|
||||
export function workspaceWorkflowQueueName(name: WorkspaceQueueName): WorkspaceQueueName {
|
||||
return name;
|
||||
}
|
||||
|
||||
function assertWorkspace(c: { state: WorkspaceState }, workspaceId: string): void {
|
||||
if (workspaceId !== c.state.workspaceId) {
|
||||
throw new Error(`Workspace actor mismatch: actor=${c.state.workspaceId} command=${workspaceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveRepoId(c: any, taskId: string): Promise<string> {
|
||||
const row = await c.db.select({ repoId: taskLookup.repoId }).from(taskLookup).where(eq(taskLookup.taskId, taskId)).get();
|
||||
|
||||
if (!row) {
|
||||
throw new Error(`Unknown task: ${taskId} (not in lookup)`);
|
||||
}
|
||||
|
||||
return row.repoId;
|
||||
}
|
||||
|
||||
async function upsertTaskLookupRow(c: any, taskId: string, repoId: string): Promise<void> {
|
||||
await c.db
|
||||
.insert(taskLookup)
|
||||
.values({
|
||||
taskId,
|
||||
repoId,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: taskLookup.taskId,
|
||||
set: { repoId },
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
async function collectAllTaskSummaries(c: any): Promise<TaskSummary[]> {
|
||||
const repoRows = await c.db.select({ repoId: repos.repoId, remoteUrl: repos.remoteUrl }).from(repos).orderBy(desc(repos.updatedAt)).all();
|
||||
|
||||
const all: TaskSummary[] = [];
|
||||
for (const row of repoRows) {
|
||||
try {
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, row.repoId, row.remoteUrl);
|
||||
const snapshot = await project.listTaskSummaries({ includeArchived: true });
|
||||
all.push(...snapshot);
|
||||
} catch (error) {
|
||||
logActorWarning("workspace", "failed collecting tasks for repo", {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: row.repoId,
|
||||
error: resolveErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
all.sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
return all;
|
||||
}
|
||||
|
||||
function repoLabelFromRemote(remoteUrl: string): string {
|
||||
try {
|
||||
const url = new URL(remoteUrl.startsWith("http") ? remoteUrl : `https://${remoteUrl}`);
|
||||
const parts = url.pathname.replace(/\/+$/, "").split("/").filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]}/${(parts[1] ?? "").replace(/\.git$/, "")}`;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return remoteUrl;
|
||||
}
|
||||
|
||||
async function buildWorkbenchSnapshot(c: any): Promise<TaskWorkbenchSnapshot> {
|
||||
const repoRows = await c.db
|
||||
.select({ repoId: repos.repoId, remoteUrl: repos.remoteUrl, updatedAt: repos.updatedAt })
|
||||
.from(repos)
|
||||
.orderBy(desc(repos.updatedAt))
|
||||
.all();
|
||||
|
||||
const tasks: Array<any> = [];
|
||||
const projects: Array<any> = [];
|
||||
for (const row of repoRows) {
|
||||
const projectTasks: Array<any> = [];
|
||||
try {
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, row.repoId, row.remoteUrl);
|
||||
const summaries = await project.listTaskSummaries({ includeArchived: true });
|
||||
for (const summary of summaries) {
|
||||
try {
|
||||
await upsertTaskLookupRow(c, summary.taskId, row.repoId);
|
||||
const task = getTask(c, c.state.workspaceId, row.repoId, summary.taskId);
|
||||
const snapshot = await task.getWorkbench({});
|
||||
tasks.push(snapshot);
|
||||
projectTasks.push(snapshot);
|
||||
} catch (error) {
|
||||
logActorWarning("workspace", "failed collecting workbench task", {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: row.repoId,
|
||||
taskId: summary.taskId,
|
||||
error: resolveErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (projectTasks.length > 0) {
|
||||
projects.push({
|
||||
id: row.repoId,
|
||||
label: repoLabelFromRemote(row.remoteUrl),
|
||||
updatedAtMs: projectTasks[0]?.updatedAtMs ?? row.updatedAt,
|
||||
tasks: projectTasks.sort((left, right) => right.updatedAtMs - left.updatedAtMs),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logActorWarning("workspace", "failed collecting workbench repo snapshot", {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: row.repoId,
|
||||
error: resolveErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tasks.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
||||
projects.sort((left, right) => right.updatedAtMs - left.updatedAtMs);
|
||||
return {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repos: repoRows.map((row) => ({
|
||||
id: row.repoId,
|
||||
label: repoLabelFromRemote(row.remoteUrl),
|
||||
})),
|
||||
projects,
|
||||
tasks,
|
||||
};
|
||||
}
|
||||
|
||||
async function requireWorkbenchTask(c: any, taskId: string) {
|
||||
const repoId = await resolveRepoId(c, taskId);
|
||||
return getTask(c, c.state.workspaceId, repoId, taskId);
|
||||
}
|
||||
|
||||
async function addRepoMutation(c: any, input: AddRepoInput): Promise<RepoRecord> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const remoteUrl = normalizeRemoteUrl(input.remoteUrl);
|
||||
if (!remoteUrl) {
|
||||
throw new Error("remoteUrl is required");
|
||||
}
|
||||
|
||||
const { driver } = getActorRuntimeContext();
|
||||
await driver.git.validateRemote(remoteUrl);
|
||||
|
||||
const repoId = repoIdFromRemote(remoteUrl);
|
||||
const now = Date.now();
|
||||
|
||||
await c.db
|
||||
.insert(repos)
|
||||
.values({
|
||||
repoId,
|
||||
remoteUrl,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: repos.repoId,
|
||||
set: {
|
||||
remoteUrl,
|
||||
updatedAt: now,
|
||||
},
|
||||
})
|
||||
.run();
|
||||
|
||||
await workspaceActions.notifyWorkbenchUpdated(c);
|
||||
return {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId,
|
||||
remoteUrl,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
}
|
||||
|
||||
async function createTaskMutation(c: any, input: CreateTaskInput): Promise<TaskRecord> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const { providers } = getActorRuntimeContext();
|
||||
const providerId = input.providerId ?? providers.defaultProviderId();
|
||||
|
||||
const repoId = input.repoId;
|
||||
const repoRow = await c.db.select({ remoteUrl: repos.remoteUrl }).from(repos).where(eq(repos.repoId, repoId)).get();
|
||||
if (!repoRow) {
|
||||
throw new Error(`Unknown repo: ${repoId}`);
|
||||
}
|
||||
const remoteUrl = repoRow.remoteUrl;
|
||||
|
||||
await c.db
|
||||
.insert(providerProfiles)
|
||||
.values({
|
||||
providerId,
|
||||
profileJson: JSON.stringify({ providerId }),
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: providerProfiles.providerId,
|
||||
set: {
|
||||
profileJson: JSON.stringify({ providerId }),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
})
|
||||
.run();
|
||||
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, repoId, remoteUrl);
|
||||
await project.ensure({ remoteUrl });
|
||||
|
||||
const created = await project.createTask({
|
||||
task: input.task,
|
||||
providerId,
|
||||
agentType: input.agentType ?? null,
|
||||
explicitTitle: input.explicitTitle ?? null,
|
||||
explicitBranchName: input.explicitBranchName ?? null,
|
||||
onBranch: input.onBranch ?? null,
|
||||
});
|
||||
|
||||
await c.db
|
||||
.insert(taskLookup)
|
||||
.values({
|
||||
taskId: created.taskId,
|
||||
repoId,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: taskLookup.taskId,
|
||||
set: { repoId },
|
||||
})
|
||||
.run();
|
||||
|
||||
const task = getTask(c, c.state.workspaceId, repoId, created.taskId);
|
||||
await task.provision({ providerId });
|
||||
|
||||
await workspaceActions.notifyWorkbenchUpdated(c);
|
||||
return created;
|
||||
}
|
||||
|
||||
async function refreshProviderProfilesMutation(c: any, command?: RefreshProviderProfilesCommand): Promise<void> {
|
||||
const body = command ?? {};
|
||||
const { providers } = getActorRuntimeContext();
|
||||
const providerIds: ProviderId[] = body.providerId ? [body.providerId] : providers.availableProviderIds();
|
||||
|
||||
for (const providerId of providerIds) {
|
||||
await c.db
|
||||
.insert(providerProfiles)
|
||||
.values({
|
||||
providerId,
|
||||
profileJson: JSON.stringify({ providerId }),
|
||||
updatedAt: Date.now(),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: providerProfiles.providerId,
|
||||
set: {
|
||||
profileJson: JSON.stringify({ providerId }),
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
})
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
export async function runWorkspaceWorkflow(ctx: any): Promise<void> {
|
||||
await ctx.loop("workspace-command-loop", async (loopCtx: any) => {
|
||||
const msg = await loopCtx.queue.next("next-workspace-command", {
|
||||
names: [...WORKSPACE_QUEUE_NAMES],
|
||||
completable: true,
|
||||
});
|
||||
if (!msg) {
|
||||
return Loop.continue(undefined);
|
||||
}
|
||||
|
||||
if (msg.name === "workspace.command.addRepo") {
|
||||
const result = await loopCtx.step({
|
||||
name: "workspace-add-repo",
|
||||
timeout: 60_000,
|
||||
run: async () => addRepoMutation(loopCtx, msg.body as AddRepoInput),
|
||||
});
|
||||
await msg.complete(result);
|
||||
return Loop.continue(undefined);
|
||||
}
|
||||
|
||||
if (msg.name === "workspace.command.createTask") {
|
||||
const result = await loopCtx.step({
|
||||
name: "workspace-create-task",
|
||||
timeout: 12 * 60_000,
|
||||
run: async () => createTaskMutation(loopCtx, msg.body as CreateTaskInput),
|
||||
});
|
||||
await msg.complete(result);
|
||||
return Loop.continue(undefined);
|
||||
}
|
||||
|
||||
if (msg.name === "workspace.command.refreshProviderProfiles") {
|
||||
await loopCtx.step("workspace-refresh-provider-profiles", async () =>
|
||||
refreshProviderProfilesMutation(loopCtx, msg.body as RefreshProviderProfilesCommand),
|
||||
);
|
||||
await msg.complete({ ok: true });
|
||||
}
|
||||
|
||||
return Loop.continue(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
export const workspaceActions = {
|
||||
...workspaceAppActions,
|
||||
async useWorkspace(c: any, input: WorkspaceUseInput): Promise<{ workspaceId: string }> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
return { workspaceId: c.state.workspaceId };
|
||||
},
|
||||
|
||||
async addRepo(c: any, input: AddRepoInput): Promise<RepoRecord> {
|
||||
const self = selfWorkspace(c);
|
||||
return expectQueueResponse<RepoRecord>(
|
||||
await self.send(workspaceWorkflowQueueName("workspace.command.addRepo"), input, {
|
||||
wait: true,
|
||||
timeout: 60_000,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
async listRepos(c: any, input: WorkspaceUseInput): Promise<RepoRecord[]> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const rows = await c.db
|
||||
.select({
|
||||
repoId: repos.repoId,
|
||||
remoteUrl: repos.remoteUrl,
|
||||
createdAt: repos.createdAt,
|
||||
updatedAt: repos.updatedAt,
|
||||
})
|
||||
.from(repos)
|
||||
.orderBy(desc(repos.updatedAt))
|
||||
.all();
|
||||
|
||||
return rows.map((row) => ({
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: row.repoId,
|
||||
remoteUrl: row.remoteUrl,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
}));
|
||||
},
|
||||
|
||||
async createTask(c: any, input: CreateTaskInput): Promise<TaskRecord> {
|
||||
const self = selfWorkspace(c);
|
||||
return expectQueueResponse<TaskRecord>(
|
||||
await self.send(workspaceWorkflowQueueName("workspace.command.createTask"), input, {
|
||||
wait: true,
|
||||
timeout: 12 * 60_000,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
async starSandboxAgentRepo(c: any, input: StarSandboxAgentRepoInput): Promise<StarSandboxAgentRepoResult> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const { driver } = getActorRuntimeContext();
|
||||
await driver.github.starRepository(SANDBOX_AGENT_REPO);
|
||||
return {
|
||||
repo: SANDBOX_AGENT_REPO,
|
||||
starredAt: Date.now(),
|
||||
};
|
||||
},
|
||||
|
||||
async getWorkbench(c: any, input: WorkspaceUseInput): Promise<TaskWorkbenchSnapshot> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
return await buildWorkbenchSnapshot(c);
|
||||
},
|
||||
|
||||
async notifyWorkbenchUpdated(c: any): Promise<void> {
|
||||
c.broadcast("workbenchUpdated", { at: Date.now() });
|
||||
},
|
||||
|
||||
async createWorkbenchTask(c: any, input: TaskWorkbenchCreateTaskInput): Promise<{ taskId: string }> {
|
||||
const created = await workspaceActions.createTask(c, {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: input.repoId,
|
||||
task: input.task,
|
||||
...(input.title ? { explicitTitle: input.title } : {}),
|
||||
...(input.branch ? { explicitBranchName: input.branch } : {}),
|
||||
...(input.model ? { agentType: agentTypeForModel(input.model) } : {}),
|
||||
});
|
||||
return { taskId: created.taskId };
|
||||
},
|
||||
|
||||
async markWorkbenchUnread(c: any, input: TaskWorkbenchSelectInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.markWorkbenchUnread({});
|
||||
},
|
||||
|
||||
async renameWorkbenchTask(c: any, input: TaskWorkbenchRenameInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.renameWorkbenchTask(input);
|
||||
},
|
||||
|
||||
async renameWorkbenchBranch(c: any, input: TaskWorkbenchRenameInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.renameWorkbenchBranch(input);
|
||||
},
|
||||
|
||||
async createWorkbenchSession(c: any, input: TaskWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
return await task.createWorkbenchSession({ ...(input.model ? { model: input.model } : {}) });
|
||||
},
|
||||
|
||||
async renameWorkbenchSession(c: any, input: TaskWorkbenchRenameSessionInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.renameWorkbenchSession(input);
|
||||
},
|
||||
|
||||
async setWorkbenchSessionUnread(c: any, input: TaskWorkbenchSetSessionUnreadInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.setWorkbenchSessionUnread(input);
|
||||
},
|
||||
|
||||
async updateWorkbenchDraft(c: any, input: TaskWorkbenchUpdateDraftInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.updateWorkbenchDraft(input);
|
||||
},
|
||||
|
||||
async changeWorkbenchModel(c: any, input: TaskWorkbenchChangeModelInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.changeWorkbenchModel(input);
|
||||
},
|
||||
|
||||
async sendWorkbenchMessage(c: any, input: TaskWorkbenchSendMessageInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.sendWorkbenchMessage(input);
|
||||
},
|
||||
|
||||
async stopWorkbenchSession(c: any, input: TaskWorkbenchTabInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.stopWorkbenchSession(input);
|
||||
},
|
||||
|
||||
async closeWorkbenchSession(c: any, input: TaskWorkbenchTabInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.closeWorkbenchSession(input);
|
||||
},
|
||||
|
||||
async publishWorkbenchPr(c: any, input: TaskWorkbenchSelectInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.publishWorkbenchPr({});
|
||||
},
|
||||
|
||||
async revertWorkbenchFile(c: any, input: TaskWorkbenchDiffInput): Promise<void> {
|
||||
const task = await requireWorkbenchTask(c, input.taskId);
|
||||
await task.revertWorkbenchFile(input);
|
||||
},
|
||||
|
||||
async listTasks(c: any, input: ListTasksInput): Promise<TaskSummary[]> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
if (input.repoId) {
|
||||
const repoRow = await c.db.select({ remoteUrl: repos.remoteUrl }).from(repos).where(eq(repos.repoId, input.repoId)).get();
|
||||
if (!repoRow) {
|
||||
throw new Error(`Unknown repo: ${input.repoId}`);
|
||||
}
|
||||
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, input.repoId, repoRow.remoteUrl);
|
||||
return await project.listTaskSummaries({ includeArchived: true });
|
||||
}
|
||||
|
||||
return await collectAllTaskSummaries(c);
|
||||
},
|
||||
|
||||
async getRepoOverview(c: any, input: RepoOverviewInput): Promise<RepoOverview> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const repoRow = await c.db.select({ remoteUrl: repos.remoteUrl }).from(repos).where(eq(repos.repoId, input.repoId)).get();
|
||||
if (!repoRow) {
|
||||
throw new Error(`Unknown repo: ${input.repoId}`);
|
||||
}
|
||||
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, input.repoId, repoRow.remoteUrl);
|
||||
await project.ensure({ remoteUrl: repoRow.remoteUrl });
|
||||
return await project.getRepoOverview({});
|
||||
},
|
||||
|
||||
async runRepoStackAction(c: any, input: RepoStackActionInput): Promise<RepoStackActionResult> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const repoRow = await c.db.select({ remoteUrl: repos.remoteUrl }).from(repos).where(eq(repos.repoId, input.repoId)).get();
|
||||
if (!repoRow) {
|
||||
throw new Error(`Unknown repo: ${input.repoId}`);
|
||||
}
|
||||
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, input.repoId, repoRow.remoteUrl);
|
||||
await project.ensure({ remoteUrl: repoRow.remoteUrl });
|
||||
return await project.runRepoStackAction({
|
||||
action: input.action,
|
||||
branchName: input.branchName,
|
||||
parentBranch: input.parentBranch,
|
||||
});
|
||||
},
|
||||
|
||||
async switchTask(c: any, taskId: string): Promise<SwitchResult> {
|
||||
const repoId = await resolveRepoId(c, taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, taskId);
|
||||
const record = await h.get();
|
||||
const switched = await h.switch();
|
||||
|
||||
return {
|
||||
workspaceId: c.state.workspaceId,
|
||||
taskId,
|
||||
providerId: record.providerId,
|
||||
switchTarget: switched.switchTarget,
|
||||
};
|
||||
},
|
||||
|
||||
async refreshProviderProfiles(c: any, command?: RefreshProviderProfilesCommand): Promise<void> {
|
||||
const self = selfWorkspace(c);
|
||||
await self.send(workspaceWorkflowQueueName("workspace.command.refreshProviderProfiles"), command ?? {}, {
|
||||
wait: true,
|
||||
timeout: 60_000,
|
||||
});
|
||||
},
|
||||
|
||||
async history(c: any, input: HistoryQueryInput): Promise<HistoryEvent[]> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const limit = input.limit ?? 20;
|
||||
const repoRows = await c.db.select({ repoId: repos.repoId }).from(repos).all();
|
||||
|
||||
const allEvents: HistoryEvent[] = [];
|
||||
|
||||
for (const row of repoRows) {
|
||||
try {
|
||||
const hist = await getOrCreateHistory(c, c.state.workspaceId, row.repoId);
|
||||
const items = await hist.list({
|
||||
branch: input.branch,
|
||||
taskId: input.taskId,
|
||||
limit,
|
||||
});
|
||||
allEvents.push(...items);
|
||||
} catch (error) {
|
||||
logActorWarning("workspace", "history lookup failed for repo", {
|
||||
workspaceId: c.state.workspaceId,
|
||||
repoId: row.repoId,
|
||||
error: resolveErrorMessage(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
allEvents.sort((a, b) => b.createdAt - a.createdAt);
|
||||
return allEvents.slice(0, limit);
|
||||
},
|
||||
|
||||
async getTask(c: any, input: GetTaskInput): Promise<TaskRecord> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
|
||||
const repoRow = await c.db.select({ remoteUrl: repos.remoteUrl }).from(repos).where(eq(repos.repoId, repoId)).get();
|
||||
if (!repoRow) {
|
||||
throw new Error(`Unknown repo: ${repoId}`);
|
||||
}
|
||||
|
||||
const project = await getOrCreateProject(c, c.state.workspaceId, repoId, repoRow.remoteUrl);
|
||||
return await project.getTaskEnriched({ taskId: input.taskId });
|
||||
},
|
||||
|
||||
async attachTask(c: any, input: TaskProxyActionInput): Promise<{ target: string; sessionId: string | null }> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
return await h.attach({ reason: input.reason });
|
||||
},
|
||||
|
||||
async pushTask(c: any, input: TaskProxyActionInput): Promise<void> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
await h.push({ reason: input.reason });
|
||||
},
|
||||
|
||||
async syncTask(c: any, input: TaskProxyActionInput): Promise<void> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
await h.sync({ reason: input.reason });
|
||||
},
|
||||
|
||||
async mergeTask(c: any, input: TaskProxyActionInput): Promise<void> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
await h.merge({ reason: input.reason });
|
||||
},
|
||||
|
||||
async archiveTask(c: any, input: TaskProxyActionInput): Promise<void> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
await h.archive({ reason: input.reason });
|
||||
},
|
||||
|
||||
async killTask(c: any, input: TaskProxyActionInput): Promise<void> {
|
||||
assertWorkspace(c, input.workspaceId);
|
||||
const repoId = await resolveRepoId(c, input.taskId);
|
||||
const h = getTask(c, c.state.workspaceId, repoId, input.taskId);
|
||||
await h.kill({ reason: input.reason });
|
||||
},
|
||||
};
|
||||
1442
foundry/packages/backend/src/actors/workspace/app-shell.ts
Normal file
1442
foundry/packages/backend/src/actors/workspace/app-shell.ts
Normal file
File diff suppressed because it is too large
Load diff
5
foundry/packages/backend/src/actors/workspace/db/db.ts
Normal file
5
foundry/packages/backend/src/actors/workspace/db/db.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { db } from "rivetkit/db/drizzle";
|
||||
import * as schema from "./schema.js";
|
||||
import migrations from "./migrations.js";
|
||||
|
||||
export const workspaceDb = db({ schema, migrations });
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { defineConfig } from "rivetkit/db/drizzle";
|
||||
|
||||
export default defineConfig({
|
||||
out: "./src/actors/workspace/db/drizzle",
|
||||
schema: "./src/actors/workspace/db/schema.ts",
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE `provider_profiles` (
|
||||
`provider_id` text PRIMARY KEY NOT NULL,
|
||||
`profile_json` text NOT NULL,
|
||||
`updated_at` integer NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE `repos` (
|
||||
`repo_id` text PRIMARY KEY NOT NULL,
|
||||
`remote_url` text NOT NULL,
|
||||
`created_at` integer NOT NULL,
|
||||
`updated_at` integer NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE `task_lookup` (
|
||||
`task_id` text PRIMARY KEY NOT NULL,
|
||||
`repo_id` text NOT NULL
|
||||
);
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "a85809c0-65c2-4f99-92ed-34357c9f83d7",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"provider_profiles": {
|
||||
"name": "provider_profiles",
|
||||
"columns": {
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"profile_json": {
|
||||
"name": "profile_json",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "450e2fdf-6349-482f-8a68-5bc0f0a9718a",
|
||||
"prevId": "a85809c0-65c2-4f99-92ed-34357c9f83d7",
|
||||
"tables": {
|
||||
"provider_profiles": {
|
||||
"name": "provider_profiles",
|
||||
"columns": {
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"profile_json": {
|
||||
"name": "profile_json",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"repos": {
|
||||
"name": "repos",
|
||||
"columns": {
|
||||
"repo_id": {
|
||||
"name": "repo_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"remote_url": {
|
||||
"name": "remote_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1770924376525,
|
||||
"tag": "0000_rare_iron_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "6",
|
||||
"when": 1770947252912,
|
||||
"tag": "0001_sleepy_lady_deathstrike",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "6",
|
||||
"when": 1772668800000,
|
||||
"tag": "0002_tiny_silver_surfer",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
189
foundry/packages/backend/src/actors/workspace/db/migrations.ts
Normal file
189
foundry/packages/backend/src/actors/workspace/db/migrations.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
// This file is generated by src/actors/_scripts/generate-actor-migrations.ts.
|
||||
// Source of truth is drizzle-kit output under ./drizzle (meta/_journal.json + *.sql).
|
||||
// Do not hand-edit this file.
|
||||
|
||||
const journal = {
|
||||
entries: [
|
||||
{
|
||||
idx: 0,
|
||||
when: 1770924376525,
|
||||
tag: "0000_rare_iron_man",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 1,
|
||||
when: 1770947252912,
|
||||
tag: "0001_sleepy_lady_deathstrike",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 2,
|
||||
when: 1772668800000,
|
||||
tag: "0002_tiny_silver_surfer",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 3,
|
||||
when: 1773100800000,
|
||||
tag: "0003_app_shell_organization_profile",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 4,
|
||||
when: 1773100800001,
|
||||
tag: "0004_app_shell_organization_members",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 5,
|
||||
when: 1773100800002,
|
||||
tag: "0005_app_shell_seat_assignments",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 6,
|
||||
when: 1773100800003,
|
||||
tag: "0006_app_shell_invoices",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 7,
|
||||
when: 1773100800004,
|
||||
tag: "0007_app_shell_sessions",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 8,
|
||||
when: 1773100800005,
|
||||
tag: "0008_app_shell_stripe_lookup",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 9,
|
||||
when: 1773100800006,
|
||||
tag: "0009_github_sync_status",
|
||||
breakpoints: true,
|
||||
},
|
||||
{
|
||||
idx: 10,
|
||||
when: 1772928000000,
|
||||
tag: "0010_app_session_starter_repo",
|
||||
breakpoints: true,
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
|
||||
export default {
|
||||
journal,
|
||||
migrations: {
|
||||
m0000: `CREATE TABLE \`provider_profiles\` (
|
||||
\`provider_id\` text PRIMARY KEY NOT NULL,
|
||||
\`profile_json\` text NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0001: `CREATE TABLE \`repos\` (
|
||||
\`repo_id\` text PRIMARY KEY NOT NULL,
|
||||
\`remote_url\` text NOT NULL,
|
||||
\`created_at\` integer NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0002: `CREATE TABLE \`task_lookup\` (
|
||||
\`task_id\` text PRIMARY KEY NOT NULL,
|
||||
\`repo_id\` text NOT NULL
|
||||
);
|
||||
`,
|
||||
m0003: `CREATE TABLE \`organization_profile\` (
|
||||
\`id\` text PRIMARY KEY NOT NULL,
|
||||
\`kind\` text NOT NULL,
|
||||
\`github_account_id\` text NOT NULL,
|
||||
\`github_login\` text NOT NULL,
|
||||
\`github_account_type\` text NOT NULL,
|
||||
\`display_name\` text NOT NULL,
|
||||
\`slug\` text NOT NULL,
|
||||
\`primary_domain\` text NOT NULL,
|
||||
\`default_model\` text NOT NULL,
|
||||
\`auto_import_repos\` integer NOT NULL,
|
||||
\`repo_import_status\` text NOT NULL,
|
||||
\`github_connected_account\` text NOT NULL,
|
||||
\`github_installation_status\` text NOT NULL,
|
||||
\`github_installation_id\` integer,
|
||||
\`github_last_sync_label\` text NOT NULL,
|
||||
\`stripe_customer_id\` text,
|
||||
\`stripe_subscription_id\` text,
|
||||
\`stripe_price_id\` text,
|
||||
\`billing_plan_id\` text NOT NULL,
|
||||
\`billing_status\` text NOT NULL,
|
||||
\`billing_seats_included\` integer NOT NULL,
|
||||
\`billing_trial_ends_at\` text,
|
||||
\`billing_renewal_at\` text,
|
||||
\`billing_payment_method_label\` text NOT NULL,
|
||||
\`created_at\` integer NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0004: `CREATE TABLE \`organization_members\` (
|
||||
\`id\` text PRIMARY KEY NOT NULL,
|
||||
\`name\` text NOT NULL,
|
||||
\`email\` text NOT NULL,
|
||||
\`role\` text NOT NULL,
|
||||
\`state\` text NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0005: `CREATE TABLE \`seat_assignments\` (
|
||||
\`email\` text PRIMARY KEY NOT NULL,
|
||||
\`created_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0006: `CREATE TABLE \`invoices\` (
|
||||
\`id\` text PRIMARY KEY NOT NULL,
|
||||
\`label\` text NOT NULL,
|
||||
\`issued_at\` text NOT NULL,
|
||||
\`amount_usd\` integer NOT NULL,
|
||||
\`status\` text NOT NULL,
|
||||
\`created_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0007: `CREATE TABLE \`app_sessions\` (
|
||||
\`id\` text PRIMARY KEY NOT NULL,
|
||||
\`current_user_id\` text,
|
||||
\`current_user_name\` text,
|
||||
\`current_user_email\` text,
|
||||
\`current_user_github_login\` text,
|
||||
\`current_user_role_label\` text,
|
||||
\`eligible_organization_ids_json\` text NOT NULL,
|
||||
\`active_organization_id\` text,
|
||||
\`github_access_token\` text,
|
||||
\`github_scope\` text NOT NULL,
|
||||
\`starter_repo_status\` text NOT NULL,
|
||||
\`starter_repo_starred_at\` integer,
|
||||
\`starter_repo_skipped_at\` integer,
|
||||
\`oauth_state\` text,
|
||||
\`oauth_state_expires_at\` integer,
|
||||
\`created_at\` integer NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0008: `CREATE TABLE \`stripe_lookup\` (
|
||||
\`lookup_key\` text PRIMARY KEY NOT NULL,
|
||||
\`organization_id\` text NOT NULL,
|
||||
\`updated_at\` integer NOT NULL
|
||||
);
|
||||
`,
|
||||
m0009: `ALTER TABLE \`organization_profile\` ADD COLUMN \`github_sync_status\` text NOT NULL DEFAULT 'pending';
|
||||
ALTER TABLE \`organization_profile\` ADD COLUMN \`github_last_sync_at\` integer;
|
||||
UPDATE \`organization_profile\`
|
||||
SET \`github_sync_status\` = CASE
|
||||
WHEN \`repo_import_status\` = 'ready' THEN 'synced'
|
||||
WHEN \`repo_import_status\` = 'importing' THEN 'syncing'
|
||||
ELSE 'pending'
|
||||
END;
|
||||
`,
|
||||
m0010: `ALTER TABLE \`app_sessions\` ADD COLUMN \`starter_repo_status\` text NOT NULL DEFAULT 'pending';
|
||||
ALTER TABLE \`app_sessions\` ADD COLUMN \`starter_repo_starred_at\` integer;
|
||||
ALTER TABLE \`app_sessions\` ADD COLUMN \`starter_repo_skipped_at\` integer;
|
||||
`,
|
||||
} as const,
|
||||
};
|
||||
100
foundry/packages/backend/src/actors/workspace/db/schema.ts
Normal file
100
foundry/packages/backend/src/actors/workspace/db/schema.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { integer, sqliteTable, text } from "rivetkit/db/drizzle";
|
||||
|
||||
// SQLite is per workspace actor instance, so no workspaceId column needed.
|
||||
export const providerProfiles = sqliteTable("provider_profiles", {
|
||||
providerId: text("provider_id").notNull().primaryKey(),
|
||||
profileJson: text("profile_json").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const repos = sqliteTable("repos", {
|
||||
repoId: text("repo_id").notNull().primaryKey(),
|
||||
remoteUrl: text("remote_url").notNull(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const taskLookup = sqliteTable("task_lookup", {
|
||||
taskId: text("task_id").notNull().primaryKey(),
|
||||
repoId: text("repo_id").notNull(),
|
||||
});
|
||||
|
||||
export const organizationProfile = sqliteTable("organization_profile", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
kind: text("kind").notNull(),
|
||||
githubAccountId: text("github_account_id").notNull(),
|
||||
githubLogin: text("github_login").notNull(),
|
||||
githubAccountType: text("github_account_type").notNull(),
|
||||
displayName: text("display_name").notNull(),
|
||||
slug: text("slug").notNull(),
|
||||
primaryDomain: text("primary_domain").notNull(),
|
||||
defaultModel: text("default_model").notNull(),
|
||||
autoImportRepos: integer("auto_import_repos").notNull(),
|
||||
repoImportStatus: text("repo_import_status").notNull(),
|
||||
githubConnectedAccount: text("github_connected_account").notNull(),
|
||||
githubInstallationStatus: text("github_installation_status").notNull(),
|
||||
githubSyncStatus: text("github_sync_status").notNull(),
|
||||
githubInstallationId: integer("github_installation_id"),
|
||||
githubLastSyncLabel: text("github_last_sync_label").notNull(),
|
||||
githubLastSyncAt: integer("github_last_sync_at"),
|
||||
stripeCustomerId: text("stripe_customer_id"),
|
||||
stripeSubscriptionId: text("stripe_subscription_id"),
|
||||
stripePriceId: text("stripe_price_id"),
|
||||
billingPlanId: text("billing_plan_id").notNull(),
|
||||
billingStatus: text("billing_status").notNull(),
|
||||
billingSeatsIncluded: integer("billing_seats_included").notNull(),
|
||||
billingTrialEndsAt: text("billing_trial_ends_at"),
|
||||
billingRenewalAt: text("billing_renewal_at"),
|
||||
billingPaymentMethodLabel: text("billing_payment_method_label").notNull(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const organizationMembers = sqliteTable("organization_members", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
email: text("email").notNull(),
|
||||
role: text("role").notNull(),
|
||||
state: text("state").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const seatAssignments = sqliteTable("seat_assignments", {
|
||||
email: text("email").notNull().primaryKey(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const invoices = sqliteTable("invoices", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
label: text("label").notNull(),
|
||||
issuedAt: text("issued_at").notNull(),
|
||||
amountUsd: integer("amount_usd").notNull(),
|
||||
status: text("status").notNull(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
});
|
||||
|
||||
export const appSessions = sqliteTable("app_sessions", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
currentUserId: text("current_user_id"),
|
||||
currentUserName: text("current_user_name"),
|
||||
currentUserEmail: text("current_user_email"),
|
||||
currentUserGithubLogin: text("current_user_github_login"),
|
||||
currentUserRoleLabel: text("current_user_role_label"),
|
||||
eligibleOrganizationIdsJson: text("eligible_organization_ids_json").notNull(),
|
||||
activeOrganizationId: text("active_organization_id"),
|
||||
githubAccessToken: text("github_access_token"),
|
||||
githubScope: text("github_scope").notNull(),
|
||||
starterRepoStatus: text("starter_repo_status").notNull(),
|
||||
starterRepoStarredAt: integer("starter_repo_starred_at"),
|
||||
starterRepoSkippedAt: integer("starter_repo_skipped_at"),
|
||||
oauthState: text("oauth_state"),
|
||||
oauthStateExpiresAt: integer("oauth_state_expires_at"),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
|
||||
export const stripeLookup = sqliteTable("stripe_lookup", {
|
||||
lookupKey: text("lookup_key").notNull().primaryKey(),
|
||||
organizationId: text("organization_id").notNull(),
|
||||
updatedAt: integer("updated_at").notNull(),
|
||||
});
|
||||
17
foundry/packages/backend/src/actors/workspace/index.ts
Normal file
17
foundry/packages/backend/src/actors/workspace/index.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { actor, queue } from "rivetkit";
|
||||
import { workflow } from "rivetkit/workflow";
|
||||
import { workspaceDb } from "./db/db.js";
|
||||
import { runWorkspaceWorkflow, WORKSPACE_QUEUE_NAMES, workspaceActions } from "./actions.js";
|
||||
|
||||
export const workspace = actor({
|
||||
db: workspaceDb,
|
||||
queues: Object.fromEntries(WORKSPACE_QUEUE_NAMES.map((name) => [name, queue()])),
|
||||
options: {
|
||||
actionTimeout: 5 * 60_000,
|
||||
},
|
||||
createState: (_c, workspaceId: string) => ({
|
||||
workspaceId,
|
||||
}),
|
||||
actions: workspaceActions,
|
||||
run: workflow(runWorkspaceWorkflow),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue