Complete Foundry refactor checklist

This commit is contained in:
Nathan Flurry 2026-03-15 13:38:51 -07:00 committed by Nathan Flurry
parent 40bed3b0a1
commit 13fc9cb318
91 changed files with 5091 additions and 4108 deletions

View file

@ -20,6 +20,7 @@ import type {
TaskWorkspaceUpdateDraftInput,
TaskEvent,
WorkspaceSessionDetail,
WorkspaceModelGroup,
WorkspaceTaskDetail,
WorkspaceTaskSummary,
OrganizationEvent,
@ -32,6 +33,7 @@ import type {
StarSandboxAgentRepoResult,
SwitchResult,
} from "@sandbox-agent/foundry-shared";
import { DEFAULT_WORKSPACE_MODEL_GROUPS } from "@sandbox-agent/foundry-shared";
import type { ProcessCreateRequest, ProcessLogFollowQuery, ProcessLogsResponse, ProcessSignalQuery } from "sandbox-agent";
import type { ActorConn, BackendClient, SandboxProcessRecord, SandboxSessionEventRecord, SandboxSessionRecord } from "../backend-client.js";
import { getSharedMockWorkspaceClient } from "./workspace-client.js";
@ -173,6 +175,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
updatedAtMs: task.updatedAtMs,
branch: task.branch,
pullRequest: task.pullRequest,
activeSessionId: task.activeSessionId ?? task.sessions[0]?.id ?? null,
sessionsSummary: task.sessions.map((tab) => ({
id: tab.id,
sessionId: tab.sessionId,
@ -190,13 +193,6 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
const buildTaskDetail = (task: TaskWorkspaceSnapshot["tasks"][number]): WorkspaceTaskDetail => ({
...buildTaskSummary(task),
task: task.title,
agentType: task.sessions[0]?.agent === "Codex" ? "codex" : "claude",
runtimeStatus: toTaskStatus(task.status === "archived" ? "archived" : "running", task.status === "archived"),
statusMessage: task.status === "archived" ? "archived" : "mock sandbox ready",
activeSessionId: task.sessions[0]?.sessionId ?? null,
diffStat: task.fileChanges.length > 0 ? `+${task.fileChanges.length}/-${task.fileChanges.length}` : "+0/-0",
prUrl: task.pullRequest ? `https://example.test/pr/${task.pullRequest.number}` : null,
reviewStatus: null,
fileChanges: task.fileChanges,
diffs: task.diffs,
fileTree: task.fileTree,
@ -236,6 +232,20 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
const taskSummaries = snapshot.tasks.map(buildTaskSummary);
return {
organizationId: defaultOrganizationId,
github: {
connectedAccount: "mock",
installationStatus: "connected",
syncStatus: "synced",
importedRepoCount: snapshot.repos.length,
lastSyncLabel: "Synced just now",
lastSyncAt: nowMs(),
lastWebhookAt: null,
lastWebhookEvent: "",
syncGeneration: 1,
syncPhase: null,
processedRepositoryCount: snapshot.repos.length,
totalRepositoryCount: snapshot.repos.length,
},
repos: snapshot.repos.map((repo) => {
const repoTasks = taskSummaries.filter((task) => task.repoId === repo.id);
return {
@ -298,9 +308,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
task: task.title,
sandboxProviderId: "local",
status: toTaskStatus(archived ? "archived" : "running", archived),
statusMessage: archived ? "archived" : "mock sandbox ready",
activeSandboxId: task.id,
activeSessionId: task.sessions[0]?.sessionId ?? null,
sandboxes: [
{
sandboxId: task.id,
@ -312,17 +320,6 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
updatedAt: task.updatedAtMs,
},
],
agentType: task.sessions[0]?.agent === "Codex" ? "codex" : "claude",
prSubmitted: Boolean(task.pullRequest),
diffStat: task.fileChanges.length > 0 ? `+${task.fileChanges.length}/-${task.fileChanges.length}` : "+0/-0",
prUrl: task.pullRequest ? `https://example.test/pr/${task.pullRequest.number}` : null,
prAuthor: task.pullRequest ? "mock" : null,
ciStatus: null,
reviewStatus: null,
reviewer: null,
conflictsWithMain: "0",
hasUnpushed: task.fileChanges.length > 0 ? "1" : "0",
parentBranch: null,
createdAt: task.updatedAtMs,
updatedAt: task.updatedAtMs,
};
@ -636,6 +633,14 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
return { endpoint: "mock://terminal-unavailable" };
},
async getSandboxWorkspaceModelGroups(
_organizationId: string,
_sandboxProviderId: SandboxProviderId,
_sandboxId: string,
): Promise<WorkspaceModelGroup[]> {
return DEFAULT_WORKSPACE_MODEL_GROUPS;
},
async getOrganizationSummary(): Promise<OrganizationSummarySnapshot> {
return buildOrganizationSummary();
},
@ -693,6 +698,13 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
emitSessionUpdate(input.taskId, input.sessionId);
},
async selectWorkspaceSession(_organizationId: string, input: TaskWorkspaceSessionInput): Promise<void> {
await workspace.selectSession(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async setWorkspaceSessionUnread(_organizationId: string, input: TaskWorkspaceSetSessionUnreadInput): Promise<void> {
await workspace.setSessionUnread(input);
emitOrganizationSnapshot();
@ -747,13 +759,8 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
},
async adminReloadGithubOrganization(): Promise<void> {},
async adminReloadGithubPullRequests(): Promise<void> {},
async adminReloadGithubRepository(): Promise<void> {},
async adminReloadGithubPullRequest(): Promise<void> {},
async health(): Promise<{ ok: true }> {
return { ok: true };
},

View file

@ -9,6 +9,7 @@ import {
slugify,
uid,
} from "../workspace-model.js";
import { DEFAULT_WORKSPACE_MODEL_ID, workspaceAgentForModel } from "@sandbox-agent/foundry-shared";
import type {
TaskWorkspaceAddSessionResponse,
TaskWorkspaceChangeModelInput,
@ -74,20 +75,19 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
id,
repoId: repo.id,
title: input.title?.trim() || "New Task",
status: "new",
status: "init_enqueue_provision",
repoName: repo.label,
updatedAtMs: nowMs(),
branch: input.branch?.trim() || null,
pullRequest: null,
activeSessionId: sessionId,
sessions: [
{
id: sessionId,
sessionId: sessionId,
sessionName: "Session 1",
agent: providerAgent(
MODEL_GROUPS.find((group) => group.models.some((model) => model.id === (input.model ?? "claude-sonnet-4")))?.provider ?? "Claude",
),
model: input.model ?? "claude-sonnet-4",
agent: workspaceAgentForModel(input.model ?? DEFAULT_WORKSPACE_MODEL_ID, MODEL_GROUPS),
model: input.model ?? DEFAULT_WORKSPACE_MODEL_ID,
status: "idle",
thinkingSinceMs: null,
unread: false,
@ -140,7 +140,18 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
this.updateTask(input.taskId, (task) => ({
...task,
updatedAtMs: nowMs(),
pullRequest: { number: nextPrNumber, status: "ready" },
pullRequest: {
number: nextPrNumber,
title: task.title,
state: "open",
url: `https://example.test/pr/${nextPrNumber}`,
headRefName: task.branch ?? `task/${task.id}`,
baseRefName: "main",
repoFullName: task.repoName,
authorLogin: "mock",
isDraft: false,
updatedAtMs: nowMs(),
},
}));
}
@ -189,7 +200,7 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
const startedAtMs = nowMs();
this.updateTask(input.taskId, (currentTask) => {
const isFirstOnTask = currentTask.status === "new";
const isFirstOnTask = String(currentTask.status).startsWith("init_");
const newTitle = isFirstOnTask ? (text.length > 50 ? `${text.slice(0, 47)}...` : text) : currentTask.title;
const newBranch = isFirstOnTask ? `feat/${slugify(newTitle)}` : currentTask.branch;
const userMessageLines = [text, ...input.attachments.map((attachment) => `@ ${attachment.filePath}:${attachment.lineNumber}`)];
@ -303,6 +314,14 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
});
}
async selectSession(input: TaskWorkspaceSessionInput): Promise<void> {
this.assertSession(input.taskId, input.sessionId);
this.updateTask(input.taskId, (currentTask) => ({
...currentTask,
activeSessionId: input.sessionId,
}));
}
async setSessionUnread(input: TaskWorkspaceSetSessionUnreadInput): Promise<void> {
this.updateTask(input.taskId, (currentTask) => ({
...currentTask,
@ -329,6 +348,7 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
return {
...currentTask,
activeSessionId: currentTask.activeSessionId === input.sessionId ? (currentTask.sessions.find((candidate) => candidate.id !== input.sessionId)?.id ?? null) : currentTask.activeSessionId,
sessions: currentTask.sessions.filter((candidate) => candidate.id !== input.sessionId),
};
});
@ -342,8 +362,8 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
sessionId: nextSessionId,
sandboxSessionId: null,
sessionName: `Session ${this.requireTask(input.taskId).sessions.length + 1}`,
agent: "Claude",
model: "claude-sonnet-4",
agent: workspaceAgentForModel(DEFAULT_WORKSPACE_MODEL_ID, MODEL_GROUPS),
model: DEFAULT_WORKSPACE_MODEL_ID,
status: "idle",
thinkingSinceMs: null,
unread: false,
@ -355,6 +375,7 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
this.updateTask(input.taskId, (currentTask) => ({
...currentTask,
updatedAtMs: nowMs(),
activeSessionId: nextSession.id,
sessions: [...currentTask.sessions, nextSession],
}));
return { sessionId: nextSession.id };
@ -369,7 +390,7 @@ class MockWorkspaceStore implements TaskWorkspaceClient {
this.updateTask(input.taskId, (currentTask) => ({
...currentTask,
sessions: currentTask.sessions.map((candidate) =>
candidate.id === input.sessionId ? { ...candidate, model: input.model, agent: providerAgent(group.provider) } : candidate,
candidate.id === input.sessionId ? { ...candidate, model: input.model, agent: workspaceAgentForModel(input.model, MODEL_GROUPS) } : candidate,
),
}));
}