feat(foundry): checkpoint actor and workspace refactor

This commit is contained in:
Nathan Flurry 2026-03-15 10:20:27 -07:00
parent 32f3c6c3bc
commit dbe57d45b9
81 changed files with 3441 additions and 2332 deletions

View file

@ -6,25 +6,25 @@ import type {
SessionEvent,
TaskRecord,
TaskSummary,
TaskWorkbenchChangeModelInput,
TaskWorkbenchCreateTaskInput,
TaskWorkbenchCreateTaskResponse,
TaskWorkbenchDiffInput,
TaskWorkbenchRenameInput,
TaskWorkbenchRenameSessionInput,
TaskWorkbenchSelectInput,
TaskWorkbenchSetSessionUnreadInput,
TaskWorkbenchSendMessageInput,
TaskWorkbenchSnapshot,
TaskWorkbenchSessionInput,
TaskWorkbenchUpdateDraftInput,
TaskWorkspaceChangeModelInput,
TaskWorkspaceCreateTaskInput,
TaskWorkspaceCreateTaskResponse,
TaskWorkspaceDiffInput,
TaskWorkspaceRenameInput,
TaskWorkspaceRenameSessionInput,
TaskWorkspaceSelectInput,
TaskWorkspaceSetSessionUnreadInput,
TaskWorkspaceSendMessageInput,
TaskWorkspaceSnapshot,
TaskWorkspaceSessionInput,
TaskWorkspaceUpdateDraftInput,
TaskEvent,
WorkbenchSessionDetail,
WorkbenchTaskDetail,
WorkbenchTaskSummary,
WorkspaceSessionDetail,
WorkspaceTaskDetail,
WorkspaceTaskSummary,
OrganizationEvent,
OrganizationSummarySnapshot,
HistoryEvent,
AuditLogEvent as HistoryEvent,
HistoryQueryInput,
SandboxProviderId,
RepoOverview,
@ -34,7 +34,7 @@ import type {
} 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 { getSharedMockWorkbenchClient } from "./workbench-client.js";
import { getSharedMockWorkspaceClient } from "./workspace-client.js";
interface MockProcessRecord extends SandboxProcessRecord {
logText: string;
@ -89,7 +89,7 @@ function toTaskStatus(status: TaskRecord["status"], archived: boolean): TaskReco
}
export function createMockBackendClient(defaultOrganizationId = "default"): BackendClient {
const workbench = getSharedMockWorkbenchClient();
const workspace = getSharedMockWorkspaceClient();
const listenersBySandboxId = new Map<string, Set<() => void>>();
const processesBySandboxId = new Map<string, MockProcessRecord[]>();
const connectionListeners = new Map<string, Set<(payload: any) => void>>();
@ -97,7 +97,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
let nextProcessId = 1;
const requireTask = (taskId: string) => {
const task = workbench.getSnapshot().tasks.find((candidate) => candidate.id === taskId);
const task = workspace.getSnapshot().tasks.find((candidate) => candidate.id === taskId);
if (!task) {
throw new Error(`Unknown mock task ${taskId}`);
}
@ -164,7 +164,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
async dispose(): Promise<void> {},
});
const buildTaskSummary = (task: TaskWorkbenchSnapshot["tasks"][number]): WorkbenchTaskSummary => ({
const buildTaskSummary = (task: TaskWorkspaceSnapshot["tasks"][number]): WorkspaceTaskSummary => ({
id: task.id,
repoId: task.repoId,
title: task.title,
@ -187,7 +187,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
})),
});
const buildTaskDetail = (task: TaskWorkbenchSnapshot["tasks"][number]): WorkbenchTaskDetail => ({
const buildTaskDetail = (task: TaskWorkspaceSnapshot["tasks"][number]): WorkspaceTaskDetail => ({
...buildTaskSummary(task),
task: task.title,
agentType: task.sessions[0]?.agent === "Codex" ? "codex" : "claude",
@ -211,7 +211,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
activeSandboxId: task.id,
});
const buildSessionDetail = (task: TaskWorkbenchSnapshot["tasks"][number], sessionId: string): WorkbenchSessionDetail => {
const buildSessionDetail = (task: TaskWorkspaceSnapshot["tasks"][number], sessionId: string): WorkspaceSessionDetail => {
const tab = task.sessions.find((candidate) => candidate.id === sessionId);
if (!tab) {
throw new Error(`Unknown mock session ${sessionId} for task ${task.id}`);
@ -232,7 +232,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
};
const buildOrganizationSummary = (): OrganizationSummarySnapshot => {
const snapshot = workbench.getSnapshot();
const snapshot = workspace.getSnapshot();
const taskSummaries = snapshot.tasks.map(buildTaskSummary);
return {
organizationId: defaultOrganizationId,
@ -256,20 +256,16 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
`sandbox:${organizationId}:${sandboxProviderId}:${sandboxId}`;
const emitOrganizationSnapshot = (): void => {
const summary = buildOrganizationSummary();
const latestTask = [...summary.taskSummaries].sort((left, right) => right.updatedAtMs - left.updatedAtMs)[0] ?? null;
if (latestTask) {
emitConnectionEvent(organizationScope(defaultOrganizationId), "organizationUpdated", {
type: "taskSummaryUpdated",
taskSummary: latestTask,
} satisfies OrganizationEvent);
}
emitConnectionEvent(organizationScope(defaultOrganizationId), "organizationUpdated", {
type: "organizationUpdated",
snapshot: buildOrganizationSummary(),
} satisfies OrganizationEvent);
};
const emitTaskUpdate = (taskId: string): void => {
const task = requireTask(taskId);
emitConnectionEvent(taskScope(defaultOrganizationId, task.repoId, task.id), "taskUpdated", {
type: "taskDetailUpdated",
type: "taskUpdated",
detail: buildTaskDetail(task),
} satisfies TaskEvent);
};
@ -400,6 +396,10 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
return unsupportedAppSnapshot();
},
async setAppDefaultModel(): Promise<FoundryAppSnapshot> {
return unsupportedAppSnapshot();
},
async updateAppOrganizationProfile(): Promise<FoundryAppSnapshot> {
return unsupportedAppSnapshot();
},
@ -433,7 +433,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
},
async listRepos(_organizationId: string): Promise<RepoRecord[]> {
return workbench.getSnapshot().repos.map((repo) => ({
return workspace.getSnapshot().repos.map((repo) => ({
organizationId: defaultOrganizationId,
repoId: repo.id,
remoteUrl: mockRepoRemote(repo.label),
@ -447,7 +447,7 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
},
async listTasks(_organizationId: string, repoId?: string): Promise<TaskSummary[]> {
return workbench
return workspace
.getSnapshot()
.tasks.filter((task) => !repoId || task.repoId === repoId)
.map((task) => ({
@ -641,24 +641,24 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
return buildOrganizationSummary();
},
async getTaskDetail(_organizationId: string, _repoId: string, taskId: string): Promise<WorkbenchTaskDetail> {
async getTaskDetail(_organizationId: string, _repoId: string, taskId: string): Promise<WorkspaceTaskDetail> {
return buildTaskDetail(requireTask(taskId));
},
async getSessionDetail(_organizationId: string, _repoId: string, taskId: string, sessionId: string): Promise<WorkbenchSessionDetail> {
async getSessionDetail(_organizationId: string, _repoId: string, taskId: string, sessionId: string): Promise<WorkspaceSessionDetail> {
return buildSessionDetail(requireTask(taskId), sessionId);
},
async getWorkbench(): Promise<TaskWorkbenchSnapshot> {
return workbench.getSnapshot();
async getWorkspace(): Promise<TaskWorkspaceSnapshot> {
return workspace.getSnapshot();
},
subscribeWorkbench(_organizationId: string, listener: () => void): () => void {
return workbench.subscribe(listener);
subscribeWorkspace(_organizationId: string, listener: () => void): () => void {
return workspace.subscribe(listener);
},
async createWorkbenchTask(_organizationId: string, input: TaskWorkbenchCreateTaskInput): Promise<TaskWorkbenchCreateTaskResponse> {
const created = await workbench.createTask(input);
async createWorkspaceTask(_organizationId: string, input: TaskWorkspaceCreateTaskInput): Promise<TaskWorkspaceCreateTaskResponse> {
const created = await workspace.createTask(input);
emitOrganizationSnapshot();
emitTaskUpdate(created.taskId);
if (created.sessionId) {
@ -667,99 +667,93 @@ export function createMockBackendClient(defaultOrganizationId = "default"): Back
return created;
},
async markWorkbenchUnread(_organizationId: string, input: TaskWorkbenchSelectInput): Promise<void> {
await workbench.markTaskUnread(input);
async markWorkspaceUnread(_organizationId: string, input: TaskWorkspaceSelectInput): Promise<void> {
await workspace.markTaskUnread(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async renameWorkbenchTask(_organizationId: string, input: TaskWorkbenchRenameInput): Promise<void> {
await workbench.renameTask(input);
async renameWorkspaceTask(_organizationId: string, input: TaskWorkspaceRenameInput): Promise<void> {
await workspace.renameTask(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async renameWorkbenchBranch(_organizationId: string, input: TaskWorkbenchRenameInput): Promise<void> {
await workbench.renameBranch(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async createWorkbenchSession(_organizationId: string, input: TaskWorkbenchSelectInput & { model?: string }): Promise<{ sessionId: string }> {
const created = await workbench.addSession(input);
async createWorkspaceSession(_organizationId: string, input: TaskWorkspaceSelectInput & { model?: string }): Promise<{ sessionId: string }> {
const created = await workspace.addSession(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, created.sessionId);
return created;
},
async renameWorkbenchSession(_organizationId: string, input: TaskWorkbenchRenameSessionInput): Promise<void> {
await workbench.renameSession(input);
async renameWorkspaceSession(_organizationId: string, input: TaskWorkspaceRenameSessionInput): Promise<void> {
await workspace.renameSession(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async setWorkbenchSessionUnread(_organizationId: string, input: TaskWorkbenchSetSessionUnreadInput): Promise<void> {
await workbench.setSessionUnread(input);
async setWorkspaceSessionUnread(_organizationId: string, input: TaskWorkspaceSetSessionUnreadInput): Promise<void> {
await workspace.setSessionUnread(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async updateWorkbenchDraft(_organizationId: string, input: TaskWorkbenchUpdateDraftInput): Promise<void> {
await workbench.updateDraft(input);
async updateWorkspaceDraft(_organizationId: string, input: TaskWorkspaceUpdateDraftInput): Promise<void> {
await workspace.updateDraft(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async changeWorkbenchModel(_organizationId: string, input: TaskWorkbenchChangeModelInput): Promise<void> {
await workbench.changeModel(input);
async changeWorkspaceModel(_organizationId: string, input: TaskWorkspaceChangeModelInput): Promise<void> {
await workspace.changeModel(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async sendWorkbenchMessage(_organizationId: string, input: TaskWorkbenchSendMessageInput): Promise<void> {
await workbench.sendMessage(input);
async sendWorkspaceMessage(_organizationId: string, input: TaskWorkspaceSendMessageInput): Promise<void> {
await workspace.sendMessage(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async stopWorkbenchSession(_organizationId: string, input: TaskWorkbenchSessionInput): Promise<void> {
await workbench.stopAgent(input);
async stopWorkspaceSession(_organizationId: string, input: TaskWorkspaceSessionInput): Promise<void> {
await workspace.stopAgent(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
emitSessionUpdate(input.taskId, input.sessionId);
},
async closeWorkbenchSession(_organizationId: string, input: TaskWorkbenchSessionInput): Promise<void> {
await workbench.closeSession(input);
async closeWorkspaceSession(_organizationId: string, input: TaskWorkspaceSessionInput): Promise<void> {
await workspace.closeSession(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async publishWorkbenchPr(_organizationId: string, input: TaskWorkbenchSelectInput): Promise<void> {
await workbench.publishPr(input);
async publishWorkspacePr(_organizationId: string, input: TaskWorkspaceSelectInput): Promise<void> {
await workspace.publishPr(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async revertWorkbenchFile(_organizationId: string, input: TaskWorkbenchDiffInput): Promise<void> {
await workbench.revertFile(input);
async revertWorkspaceFile(_organizationId: string, input: TaskWorkspaceDiffInput): Promise<void> {
await workspace.revertFile(input);
emitOrganizationSnapshot();
emitTaskUpdate(input.taskId);
},
async reloadGithubOrganization(): Promise<void> {},
async adminReloadGithubOrganization(): Promise<void> {},
async reloadGithubPullRequests(): Promise<void> {},
async adminReloadGithubPullRequests(): Promise<void> {},
async reloadGithubRepository(): Promise<void> {},
async adminReloadGithubRepository(): Promise<void> {},
async reloadGithubPullRequest(): Promise<void> {},
async adminReloadGithubPullRequest(): Promise<void> {},
async health(): Promise<{ ok: true }> {
return { ok: true };

View file

@ -1,33 +1,33 @@
import {
MODEL_GROUPS,
buildInitialMockLayoutViewModel,
groupWorkbenchRepositories,
groupWorkspaceRepositories,
nowMs,
providerAgent,
randomReply,
removeFileTreePath,
slugify,
uid,
} from "../workbench-model.js";
} from "../workspace-model.js";
import type {
TaskWorkbenchAddSessionResponse,
TaskWorkbenchChangeModelInput,
TaskWorkbenchCreateTaskInput,
TaskWorkbenchCreateTaskResponse,
TaskWorkbenchDiffInput,
TaskWorkbenchRenameInput,
TaskWorkbenchRenameSessionInput,
TaskWorkbenchSelectInput,
TaskWorkbenchSetSessionUnreadInput,
TaskWorkbenchSendMessageInput,
TaskWorkbenchSnapshot,
TaskWorkbenchSessionInput,
TaskWorkbenchUpdateDraftInput,
WorkbenchSession as AgentSession,
WorkbenchTask as Task,
WorkbenchTranscriptEvent as TranscriptEvent,
TaskWorkspaceAddSessionResponse,
TaskWorkspaceChangeModelInput,
TaskWorkspaceCreateTaskInput,
TaskWorkspaceCreateTaskResponse,
TaskWorkspaceDiffInput,
TaskWorkspaceRenameInput,
TaskWorkspaceRenameSessionInput,
TaskWorkspaceSelectInput,
TaskWorkspaceSetSessionUnreadInput,
TaskWorkspaceSendMessageInput,
TaskWorkspaceSnapshot,
TaskWorkspaceSessionInput,
TaskWorkspaceUpdateDraftInput,
WorkspaceSession as AgentSession,
WorkspaceTask as Task,
WorkspaceTranscriptEvent as TranscriptEvent,
} from "@sandbox-agent/foundry-shared";
import type { TaskWorkbenchClient } from "../workbench-client.js";
import type { TaskWorkspaceClient } from "../workspace-client.js";
function buildTranscriptEvent(params: {
sessionId: string;
@ -47,12 +47,12 @@ function buildTranscriptEvent(params: {
};
}
class MockWorkbenchStore implements TaskWorkbenchClient {
class MockWorkspaceStore implements TaskWorkspaceClient {
private snapshot = buildInitialMockLayoutViewModel();
private listeners = new Set<() => void>();
private pendingTimers = new Map<string, ReturnType<typeof setTimeout>>();
getSnapshot(): TaskWorkbenchSnapshot {
getSnapshot(): TaskWorkspaceSnapshot {
return this.snapshot;
}
@ -63,7 +63,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
};
}
async createTask(input: TaskWorkbenchCreateTaskInput): Promise<TaskWorkbenchCreateTaskResponse> {
async createTask(input: TaskWorkspaceCreateTaskInput): Promise<TaskWorkspaceCreateTaskResponse> {
const id = uid();
const sessionId = `session-${id}`;
const repo = this.snapshot.repos.find((candidate) => candidate.id === input.repoId);
@ -109,7 +109,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
return { taskId: id, sessionId };
}
async markTaskUnread(input: TaskWorkbenchSelectInput): Promise<void> {
async markTaskUnread(input: TaskWorkspaceSelectInput): Promise<void> {
this.updateTask(input.taskId, (task) => {
const targetSession = task.sessions[task.sessions.length - 1] ?? null;
if (!targetSession) {
@ -123,7 +123,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
});
}
async renameTask(input: TaskWorkbenchRenameInput): Promise<void> {
async renameTask(input: TaskWorkspaceRenameInput): Promise<void> {
const value = input.value.trim();
if (!value) {
throw new Error(`Cannot rename task ${input.taskId} to an empty title`);
@ -131,19 +131,11 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
this.updateTask(input.taskId, (task) => ({ ...task, title: value, updatedAtMs: nowMs() }));
}
async renameBranch(input: TaskWorkbenchRenameInput): Promise<void> {
const value = input.value.trim();
if (!value) {
throw new Error(`Cannot rename branch for task ${input.taskId} to an empty value`);
}
this.updateTask(input.taskId, (task) => ({ ...task, branch: value, updatedAtMs: nowMs() }));
}
async archiveTask(input: TaskWorkbenchSelectInput): Promise<void> {
async archiveTask(input: TaskWorkspaceSelectInput): Promise<void> {
this.updateTask(input.taskId, (task) => ({ ...task, status: "archived", updatedAtMs: nowMs() }));
}
async publishPr(input: TaskWorkbenchSelectInput): Promise<void> {
async publishPr(input: TaskWorkspaceSelectInput): Promise<void> {
const nextPrNumber = Math.max(0, ...this.snapshot.tasks.map((task) => task.pullRequest?.number ?? 0)) + 1;
this.updateTask(input.taskId, (task) => ({
...task,
@ -152,7 +144,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
}));
}
async revertFile(input: TaskWorkbenchDiffInput): Promise<void> {
async revertFile(input: TaskWorkspaceDiffInput): Promise<void> {
this.updateTask(input.taskId, (task) => {
const file = task.fileChanges.find((entry) => entry.path === input.path);
const nextDiffs = { ...task.diffs };
@ -167,7 +159,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
});
}
async updateDraft(input: TaskWorkbenchUpdateDraftInput): Promise<void> {
async updateDraft(input: TaskWorkspaceUpdateDraftInput): Promise<void> {
this.assertSession(input.taskId, input.sessionId);
this.updateTask(input.taskId, (task) => ({
...task,
@ -187,7 +179,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
}));
}
async sendMessage(input: TaskWorkbenchSendMessageInput): Promise<void> {
async sendMessage(input: TaskWorkspaceSendMessageInput): Promise<void> {
const text = input.text.trim();
if (!text) {
throw new Error(`Cannot send an empty mock prompt for task ${input.taskId}`);
@ -288,7 +280,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
this.pendingTimers.set(input.sessionId, timer);
}
async stopAgent(input: TaskWorkbenchSessionInput): Promise<void> {
async stopAgent(input: TaskWorkspaceSessionInput): Promise<void> {
this.assertSession(input.taskId, input.sessionId);
const existing = this.pendingTimers.get(input.sessionId);
if (existing) {
@ -311,14 +303,14 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
});
}
async setSessionUnread(input: TaskWorkbenchSetSessionUnreadInput): Promise<void> {
async setSessionUnread(input: TaskWorkspaceSetSessionUnreadInput): Promise<void> {
this.updateTask(input.taskId, (currentTask) => ({
...currentTask,
sessions: currentTask.sessions.map((candidate) => (candidate.id === input.sessionId ? { ...candidate, unread: input.unread } : candidate)),
}));
}
async renameSession(input: TaskWorkbenchRenameSessionInput): Promise<void> {
async renameSession(input: TaskWorkspaceRenameSessionInput): Promise<void> {
const title = input.title.trim();
if (!title) {
throw new Error(`Cannot rename session ${input.sessionId} to an empty title`);
@ -329,7 +321,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
}));
}
async closeSession(input: TaskWorkbenchSessionInput): Promise<void> {
async closeSession(input: TaskWorkspaceSessionInput): Promise<void> {
this.updateTask(input.taskId, (currentTask) => {
if (currentTask.sessions.length <= 1) {
return currentTask;
@ -342,7 +334,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
});
}
async addSession(input: TaskWorkbenchSelectInput): Promise<TaskWorkbenchAddSessionResponse> {
async addSession(input: TaskWorkspaceSelectInput): Promise<TaskWorkspaceAddSessionResponse> {
this.assertTask(input.taskId);
const nextSessionId = uid();
const nextSession: AgentSession = {
@ -368,7 +360,7 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
return { sessionId: nextSession.id };
}
async changeModel(input: TaskWorkbenchChangeModelInput): Promise<void> {
async changeModel(input: TaskWorkspaceChangeModelInput): Promise<void> {
const group = MODEL_GROUPS.find((candidate) => candidate.models.some((entry) => entry.id === input.model));
if (!group) {
throw new Error(`Unable to resolve model provider for ${input.model}`);
@ -382,11 +374,11 @@ class MockWorkbenchStore implements TaskWorkbenchClient {
}));
}
private updateState(updater: (current: TaskWorkbenchSnapshot) => TaskWorkbenchSnapshot): void {
private updateState(updater: (current: TaskWorkspaceSnapshot) => TaskWorkspaceSnapshot): void {
const nextSnapshot = updater(this.snapshot);
this.snapshot = {
...nextSnapshot,
repositories: groupWorkbenchRepositories(nextSnapshot.repos, nextSnapshot.tasks),
repositories: groupWorkspaceRepositories(nextSnapshot.repos, nextSnapshot.tasks),
};
this.notify();
}
@ -436,11 +428,11 @@ function candidateEventIndex(task: Task, sessionId: string): number {
return (session?.transcript.length ?? 0) + 1;
}
let sharedMockWorkbenchClient: TaskWorkbenchClient | null = null;
let sharedMockWorkspaceClient: TaskWorkspaceClient | null = null;
export function getSharedMockWorkbenchClient(): TaskWorkbenchClient {
if (!sharedMockWorkbenchClient) {
sharedMockWorkbenchClient = new MockWorkbenchStore();
export function getSharedMockWorkspaceClient(): TaskWorkspaceClient {
if (!sharedMockWorkspaceClient) {
sharedMockWorkspaceClient = new MockWorkspaceStore();
}
return sharedMockWorkbenchClient;
return sharedMockWorkspaceClient;
}