mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-15 06:04:43 +00:00
1174 lines
46 KiB
TypeScript
1174 lines
46 KiB
TypeScript
import { createClient } from "rivetkit/client";
|
|
import type {
|
|
AgentType,
|
|
AddRepoInput,
|
|
AppConfig,
|
|
FoundryAppSnapshot,
|
|
FoundryBillingPlanId,
|
|
CreateTaskInput,
|
|
AppEvent,
|
|
SessionEvent,
|
|
SandboxProcessesEvent,
|
|
TaskRecord,
|
|
TaskSummary,
|
|
TaskWorkbenchChangeModelInput,
|
|
TaskWorkbenchCreateTaskInput,
|
|
TaskWorkbenchCreateTaskResponse,
|
|
TaskWorkbenchDiffInput,
|
|
TaskWorkbenchRenameInput,
|
|
TaskWorkbenchRenameSessionInput,
|
|
TaskWorkbenchSelectInput,
|
|
TaskWorkbenchSetSessionUnreadInput,
|
|
TaskWorkbenchSendMessageInput,
|
|
TaskWorkbenchSnapshot,
|
|
TaskWorkbenchTabInput,
|
|
TaskWorkbenchUpdateDraftInput,
|
|
TaskEvent,
|
|
WorkbenchTaskDetail,
|
|
WorkbenchTaskSummary,
|
|
WorkbenchSessionDetail,
|
|
WorkspaceEvent,
|
|
WorkspaceSummarySnapshot,
|
|
HistoryEvent,
|
|
HistoryQueryInput,
|
|
ProviderId,
|
|
RepoOverview,
|
|
RepoStackActionInput,
|
|
RepoStackActionResult,
|
|
RepoRecord,
|
|
StarSandboxAgentRepoInput,
|
|
StarSandboxAgentRepoResult,
|
|
SwitchResult,
|
|
UpdateFoundryOrganizationProfileInput,
|
|
} from "@sandbox-agent/foundry-shared";
|
|
import type { ProcessCreateRequest, ProcessInfo, ProcessLogFollowQuery, ProcessLogsResponse, ProcessSignalQuery } from "sandbox-agent";
|
|
import { createMockBackendClient } from "./mock/backend-client.js";
|
|
import { sandboxInstanceKey, taskKey, workspaceKey } from "./keys.js";
|
|
|
|
export type TaskAction = "push" | "sync" | "merge" | "archive" | "kill";
|
|
|
|
export interface SandboxSessionRecord {
|
|
id: string;
|
|
agent: string;
|
|
agentSessionId: string;
|
|
lastConnectionId: string;
|
|
createdAt: number;
|
|
destroyedAt?: number;
|
|
status?: "running" | "idle" | "error";
|
|
}
|
|
|
|
export interface SandboxSessionEventRecord {
|
|
id: string;
|
|
eventIndex: number;
|
|
sessionId: string;
|
|
createdAt: number;
|
|
connectionId: string;
|
|
sender: "client" | "agent";
|
|
payload: unknown;
|
|
}
|
|
|
|
export type SandboxProcessRecord = ProcessInfo;
|
|
|
|
export interface ActorConn {
|
|
on(event: string, listener: (payload: any) => void): () => void;
|
|
onError(listener: (error: unknown) => void): () => void;
|
|
dispose(): Promise<void>;
|
|
}
|
|
|
|
interface WorkspaceHandle {
|
|
connect(): ActorConn;
|
|
addRepo(input: AddRepoInput): Promise<RepoRecord>;
|
|
listRepos(input: { workspaceId: string }): Promise<RepoRecord[]>;
|
|
createTask(input: CreateTaskInput): Promise<TaskRecord>;
|
|
listTasks(input: { workspaceId: string; repoId?: string }): Promise<TaskSummary[]>;
|
|
getRepoOverview(input: { workspaceId: string; repoId: string }): Promise<RepoOverview>;
|
|
runRepoStackAction(input: RepoStackActionInput): Promise<RepoStackActionResult>;
|
|
history(input: HistoryQueryInput): Promise<HistoryEvent[]>;
|
|
switchTask(taskId: string): Promise<SwitchResult>;
|
|
getTask(input: { workspaceId: string; taskId: string }): Promise<TaskRecord>;
|
|
attachTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<{ target: string; sessionId: string | null }>;
|
|
pushTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<void>;
|
|
syncTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<void>;
|
|
mergeTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<void>;
|
|
archiveTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<void>;
|
|
killTask(input: { workspaceId: string; taskId: string; reason?: string }): Promise<void>;
|
|
useWorkspace(input: { workspaceId: string }): Promise<{ workspaceId: string }>;
|
|
starSandboxAgentRepo(input: StarSandboxAgentRepoInput): Promise<StarSandboxAgentRepoResult>;
|
|
getWorkspaceSummary(input: { workspaceId: string }): Promise<WorkspaceSummarySnapshot>;
|
|
applyTaskSummaryUpdate(input: { taskSummary: WorkbenchTaskSummary }): Promise<void>;
|
|
removeTaskSummary(input: { taskId: string }): Promise<void>;
|
|
reconcileWorkbenchState(input: { workspaceId: string }): Promise<WorkspaceSummarySnapshot>;
|
|
createWorkbenchTask(input: TaskWorkbenchCreateTaskInput): Promise<TaskWorkbenchCreateTaskResponse>;
|
|
markWorkbenchUnread(input: TaskWorkbenchSelectInput): Promise<void>;
|
|
renameWorkbenchTask(input: TaskWorkbenchRenameInput): Promise<void>;
|
|
renameWorkbenchBranch(input: TaskWorkbenchRenameInput): Promise<void>;
|
|
createWorkbenchSession(input: TaskWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }>;
|
|
renameWorkbenchSession(input: TaskWorkbenchRenameSessionInput): Promise<void>;
|
|
setWorkbenchSessionUnread(input: TaskWorkbenchSetSessionUnreadInput): Promise<void>;
|
|
updateWorkbenchDraft(input: TaskWorkbenchUpdateDraftInput): Promise<void>;
|
|
changeWorkbenchModel(input: TaskWorkbenchChangeModelInput): Promise<void>;
|
|
sendWorkbenchMessage(input: TaskWorkbenchSendMessageInput): Promise<void>;
|
|
stopWorkbenchSession(input: TaskWorkbenchTabInput): Promise<void>;
|
|
closeWorkbenchSession(input: TaskWorkbenchTabInput): Promise<void>;
|
|
publishWorkbenchPr(input: TaskWorkbenchSelectInput): Promise<void>;
|
|
revertWorkbenchFile(input: TaskWorkbenchDiffInput): Promise<void>;
|
|
}
|
|
|
|
interface AppWorkspaceHandle {
|
|
connect(): ActorConn;
|
|
getAppSnapshot(input: { sessionId: string }): Promise<FoundryAppSnapshot>;
|
|
skipAppStarterRepo(input: { sessionId: string }): Promise<FoundryAppSnapshot>;
|
|
starAppStarterRepo(input: { sessionId: string; organizationId: string }): Promise<FoundryAppSnapshot>;
|
|
selectAppOrganization(input: { sessionId: string; organizationId: string }): Promise<FoundryAppSnapshot>;
|
|
updateAppOrganizationProfile(input: UpdateFoundryOrganizationProfileInput & { sessionId: string }): Promise<FoundryAppSnapshot>;
|
|
triggerAppRepoImport(input: { sessionId: string; organizationId: string }): Promise<FoundryAppSnapshot>;
|
|
beginAppGithubInstall(input: { sessionId: string; organizationId: string }): Promise<{ url: string }>;
|
|
createAppCheckoutSession(input: { sessionId: string; organizationId: string; planId: FoundryBillingPlanId }): Promise<{ url: string }>;
|
|
createAppBillingPortalSession(input: { sessionId: string; organizationId: string }): Promise<{ url: string }>;
|
|
cancelAppScheduledRenewal(input: { sessionId: string; organizationId: string }): Promise<FoundryAppSnapshot>;
|
|
resumeAppSubscription(input: { sessionId: string; organizationId: string }): Promise<FoundryAppSnapshot>;
|
|
recordAppSeatUsage(input: { sessionId: string; workspaceId: string }): Promise<FoundryAppSnapshot>;
|
|
}
|
|
|
|
interface TaskHandle {
|
|
getTaskSummary(): Promise<WorkbenchTaskSummary>;
|
|
getTaskDetail(): Promise<WorkbenchTaskDetail>;
|
|
getSessionDetail(input: { sessionId: string }): Promise<WorkbenchSessionDetail>;
|
|
connect(): ActorConn;
|
|
}
|
|
|
|
interface SandboxInstanceHandle {
|
|
connect(): ActorConn;
|
|
createSession(input: {
|
|
prompt: string;
|
|
cwd?: string;
|
|
agent?: AgentType | "opencode";
|
|
}): Promise<{ id: string | null; status: "running" | "idle" | "error"; error?: string }>;
|
|
listSessions(input?: { cursor?: string; limit?: number }): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }>;
|
|
listSessionEvents(input: { sessionId: string; cursor?: string; limit?: number }): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }>;
|
|
createProcess(input: ProcessCreateRequest): Promise<SandboxProcessRecord>;
|
|
listProcesses(): Promise<{ processes: SandboxProcessRecord[] }>;
|
|
getProcessLogs(input: { processId: string; query?: ProcessLogFollowQuery }): Promise<ProcessLogsResponse>;
|
|
stopProcess(input: { processId: string; query?: ProcessSignalQuery }): Promise<SandboxProcessRecord>;
|
|
killProcess(input: { processId: string; query?: ProcessSignalQuery }): Promise<SandboxProcessRecord>;
|
|
deleteProcess(input: { processId: string }): Promise<void>;
|
|
sendPrompt(input: { sessionId: string; prompt: string; notification?: boolean }): Promise<void>;
|
|
sessionStatus(input: { sessionId: string }): Promise<{ id: string; status: "running" | "idle" | "error" }>;
|
|
sandboxAgentConnection(): Promise<{ endpoint: string; token?: string }>;
|
|
providerState(): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }>;
|
|
}
|
|
|
|
interface RivetClient {
|
|
workspace: {
|
|
getOrCreate(key?: string | string[], opts?: { createWithInput?: unknown }): WorkspaceHandle;
|
|
};
|
|
task: {
|
|
get(key?: string | string[]): TaskHandle;
|
|
getOrCreate(key?: string | string[], opts?: { createWithInput?: unknown }): TaskHandle;
|
|
};
|
|
sandboxInstance: {
|
|
getOrCreate(key?: string | string[], opts?: { createWithInput?: unknown }): SandboxInstanceHandle;
|
|
};
|
|
}
|
|
|
|
export interface BackendClientOptions {
|
|
endpoint: string;
|
|
defaultWorkspaceId?: string;
|
|
mode?: "remote" | "mock";
|
|
}
|
|
|
|
export interface BackendClient {
|
|
getAppSnapshot(): Promise<FoundryAppSnapshot>;
|
|
connectWorkspace(workspaceId: string): Promise<ActorConn>;
|
|
connectTask(workspaceId: string, repoId: string, taskId: string): Promise<ActorConn>;
|
|
connectSandbox(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<ActorConn>;
|
|
subscribeApp(listener: () => void): () => void;
|
|
signInWithGithub(): Promise<void>;
|
|
signOutApp(): Promise<FoundryAppSnapshot>;
|
|
skipAppStarterRepo(): Promise<FoundryAppSnapshot>;
|
|
starAppStarterRepo(organizationId: string): Promise<FoundryAppSnapshot>;
|
|
selectAppOrganization(organizationId: string): Promise<FoundryAppSnapshot>;
|
|
updateAppOrganizationProfile(input: UpdateFoundryOrganizationProfileInput): Promise<FoundryAppSnapshot>;
|
|
triggerAppRepoImport(organizationId: string): Promise<FoundryAppSnapshot>;
|
|
reconnectAppGithub(organizationId: string): Promise<void>;
|
|
completeAppHostedCheckout(organizationId: string, planId: FoundryBillingPlanId): Promise<void>;
|
|
openAppBillingPortal(organizationId: string): Promise<void>;
|
|
cancelAppScheduledRenewal(organizationId: string): Promise<FoundryAppSnapshot>;
|
|
resumeAppSubscription(organizationId: string): Promise<FoundryAppSnapshot>;
|
|
recordAppSeatUsage(workspaceId: string): Promise<FoundryAppSnapshot>;
|
|
addRepo(workspaceId: string, remoteUrl: string): Promise<RepoRecord>;
|
|
listRepos(workspaceId: string): Promise<RepoRecord[]>;
|
|
createTask(input: CreateTaskInput): Promise<TaskRecord>;
|
|
listTasks(workspaceId: string, repoId?: string): Promise<TaskSummary[]>;
|
|
getRepoOverview(workspaceId: string, repoId: string): Promise<RepoOverview>;
|
|
runRepoStackAction(input: RepoStackActionInput): Promise<RepoStackActionResult>;
|
|
getTask(workspaceId: string, taskId: string): Promise<TaskRecord>;
|
|
listHistory(input: HistoryQueryInput): Promise<HistoryEvent[]>;
|
|
switchTask(workspaceId: string, taskId: string): Promise<SwitchResult>;
|
|
attachTask(workspaceId: string, taskId: string): Promise<{ target: string; sessionId: string | null }>;
|
|
runAction(workspaceId: string, taskId: string, action: TaskAction): Promise<void>;
|
|
createSandboxSession(input: {
|
|
workspaceId: string;
|
|
providerId: ProviderId;
|
|
sandboxId: string;
|
|
prompt: string;
|
|
cwd?: string;
|
|
agent?: AgentType | "opencode";
|
|
}): Promise<{ id: string; status: "running" | "idle" | "error" }>;
|
|
listSandboxSessions(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
input?: { cursor?: string; limit?: number },
|
|
): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }>;
|
|
listSandboxSessionEvents(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
input: { sessionId: string; cursor?: string; limit?: number },
|
|
): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }>;
|
|
createSandboxProcess(input: { workspaceId: string; providerId: ProviderId; sandboxId: string; request: ProcessCreateRequest }): Promise<SandboxProcessRecord>;
|
|
listSandboxProcesses(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<{ processes: SandboxProcessRecord[] }>;
|
|
getSandboxProcessLogs(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessLogFollowQuery,
|
|
): Promise<ProcessLogsResponse>;
|
|
stopSandboxProcess(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessSignalQuery,
|
|
): Promise<SandboxProcessRecord>;
|
|
killSandboxProcess(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessSignalQuery,
|
|
): Promise<SandboxProcessRecord>;
|
|
deleteSandboxProcess(workspaceId: string, providerId: ProviderId, sandboxId: string, processId: string): Promise<void>;
|
|
subscribeSandboxProcesses(workspaceId: string, providerId: ProviderId, sandboxId: string, listener: () => void): () => void;
|
|
sendSandboxPrompt(input: {
|
|
workspaceId: string;
|
|
providerId: ProviderId;
|
|
sandboxId: string;
|
|
sessionId: string;
|
|
prompt: string;
|
|
notification?: boolean;
|
|
}): Promise<void>;
|
|
sandboxSessionStatus(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
sessionId: string,
|
|
): Promise<{ id: string; status: "running" | "idle" | "error" }>;
|
|
sandboxProviderState(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }>;
|
|
getSandboxAgentConnection(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<{ endpoint: string; token?: string }>;
|
|
getWorkspaceSummary(workspaceId: string): Promise<WorkspaceSummarySnapshot>;
|
|
getTaskDetail(workspaceId: string, repoId: string, taskId: string): Promise<WorkbenchTaskDetail>;
|
|
getSessionDetail(workspaceId: string, repoId: string, taskId: string, sessionId: string): Promise<WorkbenchSessionDetail>;
|
|
getWorkbench(workspaceId: string): Promise<TaskWorkbenchSnapshot>;
|
|
subscribeWorkbench(workspaceId: string, listener: () => void): () => void;
|
|
createWorkbenchTask(workspaceId: string, input: TaskWorkbenchCreateTaskInput): Promise<TaskWorkbenchCreateTaskResponse>;
|
|
markWorkbenchUnread(workspaceId: string, input: TaskWorkbenchSelectInput): Promise<void>;
|
|
renameWorkbenchTask(workspaceId: string, input: TaskWorkbenchRenameInput): Promise<void>;
|
|
renameWorkbenchBranch(workspaceId: string, input: TaskWorkbenchRenameInput): Promise<void>;
|
|
createWorkbenchSession(workspaceId: string, input: TaskWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }>;
|
|
renameWorkbenchSession(workspaceId: string, input: TaskWorkbenchRenameSessionInput): Promise<void>;
|
|
setWorkbenchSessionUnread(workspaceId: string, input: TaskWorkbenchSetSessionUnreadInput): Promise<void>;
|
|
updateWorkbenchDraft(workspaceId: string, input: TaskWorkbenchUpdateDraftInput): Promise<void>;
|
|
changeWorkbenchModel(workspaceId: string, input: TaskWorkbenchChangeModelInput): Promise<void>;
|
|
sendWorkbenchMessage(workspaceId: string, input: TaskWorkbenchSendMessageInput): Promise<void>;
|
|
stopWorkbenchSession(workspaceId: string, input: TaskWorkbenchTabInput): Promise<void>;
|
|
closeWorkbenchSession(workspaceId: string, input: TaskWorkbenchTabInput): Promise<void>;
|
|
publishWorkbenchPr(workspaceId: string, input: TaskWorkbenchSelectInput): Promise<void>;
|
|
revertWorkbenchFile(workspaceId: string, input: TaskWorkbenchDiffInput): Promise<void>;
|
|
health(): Promise<{ ok: true }>;
|
|
useWorkspace(workspaceId: string): Promise<{ workspaceId: string }>;
|
|
starSandboxAgentRepo(workspaceId: string): Promise<StarSandboxAgentRepoResult>;
|
|
}
|
|
|
|
export function rivetEndpoint(config: AppConfig): string {
|
|
return `http://${config.backend.host}:${config.backend.port}/v1/rivet`;
|
|
}
|
|
|
|
export function createBackendClientFromConfig(config: AppConfig): BackendClient {
|
|
return createBackendClient({
|
|
endpoint: rivetEndpoint(config),
|
|
defaultWorkspaceId: config.workspace.default,
|
|
});
|
|
}
|
|
|
|
function stripTrailingSlash(value: string): string {
|
|
return value.replace(/\/$/, "");
|
|
}
|
|
|
|
function normalizeLegacyBackendEndpoint(endpoint: string): string {
|
|
const normalized = stripTrailingSlash(endpoint);
|
|
if (normalized.endsWith("/api/rivet")) {
|
|
return `${normalized.slice(0, -"/api/rivet".length)}/v1/rivet`;
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function deriveBackendEndpoints(endpoint: string): { appEndpoint: string; rivetEndpoint: string } {
|
|
const normalized = normalizeLegacyBackendEndpoint(endpoint);
|
|
if (normalized.endsWith("/rivet")) {
|
|
return {
|
|
appEndpoint: normalized.slice(0, -"/rivet".length),
|
|
rivetEndpoint: normalized,
|
|
};
|
|
}
|
|
return {
|
|
appEndpoint: normalized,
|
|
rivetEndpoint: `${normalized}/rivet`,
|
|
};
|
|
}
|
|
|
|
function signedOutAppSnapshot(): FoundryAppSnapshot {
|
|
return {
|
|
auth: { status: "signed_out", currentUserId: null },
|
|
activeOrganizationId: null,
|
|
onboarding: {
|
|
starterRepo: {
|
|
repoFullName: "rivet-dev/sandbox-agent",
|
|
repoUrl: "https://github.com/rivet-dev/sandbox-agent",
|
|
status: "pending",
|
|
starredAt: null,
|
|
skippedAt: null,
|
|
},
|
|
},
|
|
users: [],
|
|
organizations: [],
|
|
};
|
|
}
|
|
|
|
export function createBackendClient(options: BackendClientOptions): BackendClient {
|
|
if (options.mode === "mock") {
|
|
return createMockBackendClient(options.defaultWorkspaceId);
|
|
}
|
|
|
|
const endpoints = deriveBackendEndpoints(options.endpoint);
|
|
const rivetApiEndpoint = endpoints.rivetEndpoint;
|
|
const appApiEndpoint = endpoints.appEndpoint;
|
|
const client = createClient({ endpoint: rivetApiEndpoint }) as unknown as RivetClient;
|
|
const workbenchSubscriptions = new Map<
|
|
string,
|
|
{
|
|
listeners: Set<() => void>;
|
|
disposeConnPromise: Promise<(() => Promise<void>) | null> | null;
|
|
}
|
|
>();
|
|
const sandboxProcessSubscriptions = new Map<
|
|
string,
|
|
{
|
|
listeners: Set<() => void>;
|
|
disposeConnPromise: Promise<(() => Promise<void>) | null> | null;
|
|
}
|
|
>();
|
|
const appSubscriptions = {
|
|
listeners: new Set<() => void>(),
|
|
disposeConnPromise: null as Promise<(() => Promise<void>) | null> | null,
|
|
};
|
|
|
|
const appRequest = async <T>(path: string, init?: RequestInit): Promise<T> => {
|
|
const headers = new Headers(init?.headers);
|
|
if (init?.body && !headers.has("Content-Type")) {
|
|
headers.set("Content-Type", "application/json");
|
|
}
|
|
|
|
const res = await fetch(`${appApiEndpoint}${path}`, {
|
|
...init,
|
|
headers,
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error(`app request failed: ${res.status} ${res.statusText}`);
|
|
}
|
|
return (await res.json()) as T;
|
|
};
|
|
|
|
const getSessionId = async (): Promise<string | null> => {
|
|
const res = await fetch(`${appApiEndpoint}/auth/get-session`, {
|
|
credentials: "include",
|
|
});
|
|
if (res.status === 401) {
|
|
return null;
|
|
}
|
|
if (!res.ok) {
|
|
throw new Error(`auth session request failed: ${res.status} ${res.statusText}`);
|
|
}
|
|
const data = (await res.json().catch(() => null)) as { session?: { id?: string | null } | null } | null;
|
|
const sessionId = data?.session?.id;
|
|
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
|
|
};
|
|
|
|
const workspace = async (workspaceId: string): Promise<WorkspaceHandle> =>
|
|
client.workspace.getOrCreate(workspaceKey(workspaceId), {
|
|
createWithInput: workspaceId,
|
|
});
|
|
|
|
const appWorkspace = async (): Promise<AppWorkspaceHandle> =>
|
|
client.workspace.getOrCreate(workspaceKey("app"), {
|
|
createWithInput: "app",
|
|
}) as unknown as AppWorkspaceHandle;
|
|
|
|
const task = async (workspaceId: string, repoId: string, taskId: string): Promise<TaskHandle> => client.task.get(taskKey(workspaceId, repoId, taskId));
|
|
|
|
const sandboxByKey = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<SandboxInstanceHandle> => {
|
|
return (client as any).sandboxInstance.get(sandboxInstanceKey(workspaceId, providerId, sandboxId));
|
|
};
|
|
|
|
function isActorNotFoundError(error: unknown): boolean {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return message.includes("Actor not found");
|
|
}
|
|
|
|
const sandboxByActorIdFromTask = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<SandboxInstanceHandle | null> => {
|
|
const ws = await workspace(workspaceId);
|
|
const rows = await ws.listTasks({ workspaceId });
|
|
const candidates = [...rows].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
|
|
for (const row of candidates) {
|
|
try {
|
|
const detail = await ws.getTask({ workspaceId, taskId: row.taskId });
|
|
if (detail.providerId !== providerId) {
|
|
continue;
|
|
}
|
|
const sandbox = detail.sandboxes.find(
|
|
(sb) =>
|
|
sb.sandboxId === sandboxId &&
|
|
sb.providerId === providerId &&
|
|
typeof (sb as any).sandboxActorId === "string" &&
|
|
(sb as any).sandboxActorId.length > 0,
|
|
) as { sandboxActorId?: string } | undefined;
|
|
if (sandbox?.sandboxActorId) {
|
|
return (client as any).sandboxInstance.getForId(sandbox.sandboxActorId);
|
|
}
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
if (!isActorNotFoundError(error) && !message.includes("Unknown task")) {
|
|
throw error;
|
|
}
|
|
// Best effort fallback path; ignore missing task actors here.
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const withSandboxHandle = async <T>(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
run: (handle: SandboxInstanceHandle) => Promise<T>,
|
|
): Promise<T> => {
|
|
const handle = await sandboxByKey(workspaceId, providerId, sandboxId);
|
|
try {
|
|
return await run(handle);
|
|
} catch (error) {
|
|
if (!isActorNotFoundError(error)) {
|
|
throw error;
|
|
}
|
|
const fallback = await sandboxByActorIdFromTask(workspaceId, providerId, sandboxId);
|
|
if (!fallback) {
|
|
throw error;
|
|
}
|
|
return await run(fallback);
|
|
}
|
|
};
|
|
|
|
const connectWorkspace = async (workspaceId: string): Promise<ActorConn> => {
|
|
return (await workspace(workspaceId)).connect() as ActorConn;
|
|
};
|
|
|
|
const connectTask = async (workspaceId: string, repoId: string, taskIdValue: string): Promise<ActorConn> => {
|
|
return (await task(workspaceId, repoId, taskIdValue)).connect() as ActorConn;
|
|
};
|
|
|
|
const connectSandbox = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<ActorConn> => {
|
|
try {
|
|
return (await sandboxByKey(workspaceId, providerId, sandboxId)).connect() as ActorConn;
|
|
} catch (error) {
|
|
if (!isActorNotFoundError(error)) {
|
|
throw error;
|
|
}
|
|
const fallback = await sandboxByActorIdFromTask(workspaceId, providerId, sandboxId);
|
|
if (!fallback) {
|
|
throw error;
|
|
}
|
|
return fallback.connect() as ActorConn;
|
|
}
|
|
};
|
|
|
|
const getWorkbenchCompat = async (workspaceId: string): Promise<TaskWorkbenchSnapshot> => {
|
|
const summary = await (await workspace(workspaceId)).getWorkspaceSummary({ workspaceId });
|
|
const tasks = await Promise.all(
|
|
summary.taskSummaries.map(async (taskSummary) => {
|
|
const detail = await (await task(workspaceId, taskSummary.repoId, taskSummary.id)).getTaskDetail();
|
|
const sessionDetails = await Promise.all(
|
|
detail.sessionsSummary.map(async (session) => {
|
|
const full = await (await task(workspaceId, detail.repoId, detail.id)).getSessionDetail({ sessionId: session.id });
|
|
return [session.id, full] as const;
|
|
}),
|
|
);
|
|
const sessionDetailsById = new Map(sessionDetails);
|
|
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,
|
|
tabs: 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,
|
|
};
|
|
}),
|
|
);
|
|
|
|
const projects = summary.repos
|
|
.map((repo) => ({
|
|
id: repo.id,
|
|
label: repo.label,
|
|
updatedAtMs: tasks.filter((task) => task.repoId === repo.id).reduce((latest, task) => Math.max(latest, task.updatedAtMs), repo.latestActivityMs),
|
|
tasks: tasks.filter((task) => task.repoId === repo.id).sort((left, right) => right.updatedAtMs - left.updatedAtMs),
|
|
}))
|
|
.filter((repo) => repo.tasks.length > 0);
|
|
|
|
return {
|
|
workspaceId,
|
|
repos: summary.repos.map((repo) => ({ id: repo.id, label: repo.label })),
|
|
projects,
|
|
tasks: tasks.sort((left, right) => right.updatedAtMs - left.updatedAtMs),
|
|
};
|
|
};
|
|
|
|
const subscribeWorkbench = (workspaceId: string, listener: () => void): (() => void) => {
|
|
let entry = workbenchSubscriptions.get(workspaceId);
|
|
if (!entry) {
|
|
entry = {
|
|
listeners: new Set(),
|
|
disposeConnPromise: null,
|
|
};
|
|
workbenchSubscriptions.set(workspaceId, entry);
|
|
}
|
|
|
|
entry.listeners.add(listener);
|
|
|
|
if (!entry.disposeConnPromise) {
|
|
entry.disposeConnPromise = (async () => {
|
|
const handle = await workspace(workspaceId);
|
|
const conn = (handle as any).connect();
|
|
const unsubscribeEvent = conn.on("workbenchUpdated", () => {
|
|
const current = workbenchSubscriptions.get(workspaceId);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
for (const currentListener of [...current.listeners]) {
|
|
currentListener();
|
|
}
|
|
});
|
|
const unsubscribeError = conn.onError(() => {});
|
|
return async () => {
|
|
unsubscribeEvent();
|
|
unsubscribeError();
|
|
await conn.dispose();
|
|
};
|
|
})().catch(() => null);
|
|
}
|
|
|
|
return () => {
|
|
const current = workbenchSubscriptions.get(workspaceId);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
current.listeners.delete(listener);
|
|
if (current.listeners.size > 0) {
|
|
return;
|
|
}
|
|
|
|
workbenchSubscriptions.delete(workspaceId);
|
|
void current.disposeConnPromise?.then(async (disposeConn) => {
|
|
await disposeConn?.();
|
|
});
|
|
};
|
|
};
|
|
|
|
const sandboxProcessSubscriptionKey = (workspaceId: string, providerId: ProviderId, sandboxId: string): string => `${workspaceId}:${providerId}:${sandboxId}`;
|
|
|
|
const subscribeSandboxProcesses = (workspaceId: string, providerId: ProviderId, sandboxId: string, listener: () => void): (() => void) => {
|
|
const key = sandboxProcessSubscriptionKey(workspaceId, providerId, sandboxId);
|
|
let entry = sandboxProcessSubscriptions.get(key);
|
|
if (!entry) {
|
|
entry = {
|
|
listeners: new Set(),
|
|
disposeConnPromise: null,
|
|
};
|
|
sandboxProcessSubscriptions.set(key, entry);
|
|
}
|
|
|
|
entry.listeners.add(listener);
|
|
|
|
if (!entry.disposeConnPromise) {
|
|
entry.disposeConnPromise = (async () => {
|
|
const handle = await sandboxByKey(workspaceId, providerId, sandboxId);
|
|
const conn = (handle as any).connect();
|
|
const unsubscribeEvent = conn.on("processesUpdated", () => {
|
|
const current = sandboxProcessSubscriptions.get(key);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
for (const currentListener of [...current.listeners]) {
|
|
currentListener();
|
|
}
|
|
});
|
|
const unsubscribeError = conn.onError(() => {});
|
|
return async () => {
|
|
unsubscribeEvent();
|
|
unsubscribeError();
|
|
await conn.dispose();
|
|
};
|
|
})().catch(() => null);
|
|
}
|
|
|
|
return () => {
|
|
const current = sandboxProcessSubscriptions.get(key);
|
|
if (!current) {
|
|
return;
|
|
}
|
|
current.listeners.delete(listener);
|
|
if (current.listeners.size > 0) {
|
|
return;
|
|
}
|
|
|
|
sandboxProcessSubscriptions.delete(key);
|
|
void current.disposeConnPromise?.then(async (disposeConn) => {
|
|
await disposeConn?.();
|
|
});
|
|
};
|
|
};
|
|
|
|
const subscribeApp = (listener: () => void): (() => void) => {
|
|
appSubscriptions.listeners.add(listener);
|
|
|
|
if (!appSubscriptions.disposeConnPromise) {
|
|
appSubscriptions.disposeConnPromise = (async () => {
|
|
const handle = await appWorkspace();
|
|
const conn = (handle as any).connect();
|
|
const unsubscribeEvent = conn.on("appUpdated", () => {
|
|
for (const currentListener of [...appSubscriptions.listeners]) {
|
|
currentListener();
|
|
}
|
|
});
|
|
const unsubscribeError = conn.onError(() => {});
|
|
return async () => {
|
|
unsubscribeEvent();
|
|
unsubscribeError();
|
|
await conn.dispose();
|
|
};
|
|
})().catch(() => null);
|
|
}
|
|
|
|
return () => {
|
|
appSubscriptions.listeners.delete(listener);
|
|
if (appSubscriptions.listeners.size > 0) {
|
|
return;
|
|
}
|
|
|
|
void appSubscriptions.disposeConnPromise?.then(async (disposeConn) => {
|
|
await disposeConn?.();
|
|
});
|
|
appSubscriptions.disposeConnPromise = null;
|
|
};
|
|
};
|
|
|
|
return {
|
|
async getAppSnapshot(): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
return signedOutAppSnapshot();
|
|
}
|
|
return await (await appWorkspace()).getAppSnapshot({ sessionId });
|
|
},
|
|
|
|
async connectWorkspace(workspaceId: string): Promise<ActorConn> {
|
|
return await connectWorkspace(workspaceId);
|
|
},
|
|
|
|
async connectTask(workspaceId: string, repoId: string, taskIdValue: string): Promise<ActorConn> {
|
|
return await connectTask(workspaceId, repoId, taskIdValue);
|
|
},
|
|
|
|
async connectSandbox(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<ActorConn> {
|
|
return await connectSandbox(workspaceId, providerId, sandboxId);
|
|
},
|
|
|
|
subscribeApp(listener: () => void): () => void {
|
|
return subscribeApp(listener);
|
|
},
|
|
|
|
async signInWithGithub(): Promise<void> {
|
|
const callbackURL = typeof window !== "undefined" ? `${window.location.origin}/organizations` : `${appApiEndpoint.replace(/\/$/, "")}/organizations`;
|
|
const response = await appRequest<{ url: string; redirect?: boolean }>("/auth/sign-in/social", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
provider: "github",
|
|
callbackURL,
|
|
disableRedirect: true,
|
|
}),
|
|
});
|
|
if (typeof window !== "undefined") {
|
|
window.location.assign(response.url);
|
|
}
|
|
},
|
|
|
|
async signOutApp(): Promise<FoundryAppSnapshot> {
|
|
return await appRequest<FoundryAppSnapshot>("/app/sign-out", { method: "POST" });
|
|
},
|
|
|
|
async skipAppStarterRepo(): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).skipAppStarterRepo({ sessionId });
|
|
},
|
|
|
|
async starAppStarterRepo(organizationId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).starAppStarterRepo({ sessionId, organizationId });
|
|
},
|
|
|
|
async selectAppOrganization(organizationId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).selectAppOrganization({ sessionId, organizationId });
|
|
},
|
|
|
|
async updateAppOrganizationProfile(input: UpdateFoundryOrganizationProfileInput): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).updateAppOrganizationProfile({
|
|
sessionId,
|
|
organizationId: input.organizationId,
|
|
displayName: input.displayName,
|
|
slug: input.slug,
|
|
primaryDomain: input.primaryDomain,
|
|
});
|
|
},
|
|
|
|
async triggerAppRepoImport(organizationId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).triggerAppRepoImport({ sessionId, organizationId });
|
|
},
|
|
|
|
async reconnectAppGithub(organizationId: string): Promise<void> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
const response = await (await appWorkspace()).beginAppGithubInstall({ sessionId, organizationId });
|
|
if (typeof window !== "undefined") {
|
|
window.location.assign(response.url);
|
|
}
|
|
},
|
|
|
|
async completeAppHostedCheckout(organizationId: string, planId: FoundryBillingPlanId): Promise<void> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
const response = await (await appWorkspace()).createAppCheckoutSession({ sessionId, organizationId, planId });
|
|
if (typeof window !== "undefined") {
|
|
window.location.assign(response.url);
|
|
}
|
|
},
|
|
|
|
async openAppBillingPortal(organizationId: string): Promise<void> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
const response = await (await appWorkspace()).createAppBillingPortalSession({ sessionId, organizationId });
|
|
if (typeof window !== "undefined") {
|
|
window.location.assign(response.url);
|
|
}
|
|
},
|
|
|
|
async cancelAppScheduledRenewal(organizationId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).cancelAppScheduledRenewal({ sessionId, organizationId });
|
|
},
|
|
|
|
async resumeAppSubscription(organizationId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).resumeAppSubscription({ sessionId, organizationId });
|
|
},
|
|
|
|
async recordAppSeatUsage(workspaceId: string): Promise<FoundryAppSnapshot> {
|
|
const sessionId = await getSessionId();
|
|
if (!sessionId) {
|
|
throw new Error("No active auth session");
|
|
}
|
|
return await (await appWorkspace()).recordAppSeatUsage({ sessionId, workspaceId });
|
|
},
|
|
|
|
async addRepo(workspaceId: string, remoteUrl: string): Promise<RepoRecord> {
|
|
return (await workspace(workspaceId)).addRepo({ workspaceId, remoteUrl });
|
|
},
|
|
|
|
async listRepos(workspaceId: string): Promise<RepoRecord[]> {
|
|
return (await workspace(workspaceId)).listRepos({ workspaceId });
|
|
},
|
|
|
|
async createTask(input: CreateTaskInput): Promise<TaskRecord> {
|
|
return (await workspace(input.workspaceId)).createTask(input);
|
|
},
|
|
|
|
async starSandboxAgentRepo(workspaceId: string): Promise<StarSandboxAgentRepoResult> {
|
|
return (await workspace(workspaceId)).starSandboxAgentRepo({ workspaceId });
|
|
},
|
|
|
|
async listTasks(workspaceId: string, repoId?: string): Promise<TaskSummary[]> {
|
|
return (await workspace(workspaceId)).listTasks({ workspaceId, repoId });
|
|
},
|
|
|
|
async getRepoOverview(workspaceId: string, repoId: string): Promise<RepoOverview> {
|
|
return (await workspace(workspaceId)).getRepoOverview({ workspaceId, repoId });
|
|
},
|
|
|
|
async runRepoStackAction(input: RepoStackActionInput): Promise<RepoStackActionResult> {
|
|
return (await workspace(input.workspaceId)).runRepoStackAction(input);
|
|
},
|
|
|
|
async getTask(workspaceId: string, taskId: string): Promise<TaskRecord> {
|
|
return (await workspace(workspaceId)).getTask({
|
|
workspaceId,
|
|
taskId,
|
|
});
|
|
},
|
|
|
|
async listHistory(input: HistoryQueryInput): Promise<HistoryEvent[]> {
|
|
return (await workspace(input.workspaceId)).history(input);
|
|
},
|
|
|
|
async switchTask(workspaceId: string, taskId: string): Promise<SwitchResult> {
|
|
return (await workspace(workspaceId)).switchTask(taskId);
|
|
},
|
|
|
|
async attachTask(workspaceId: string, taskId: string): Promise<{ target: string; sessionId: string | null }> {
|
|
return (await workspace(workspaceId)).attachTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.attach",
|
|
});
|
|
},
|
|
|
|
async runAction(workspaceId: string, taskId: string, action: TaskAction): Promise<void> {
|
|
if (action === "push") {
|
|
await (await workspace(workspaceId)).pushTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.push",
|
|
});
|
|
return;
|
|
}
|
|
if (action === "sync") {
|
|
await (await workspace(workspaceId)).syncTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.sync",
|
|
});
|
|
return;
|
|
}
|
|
if (action === "merge") {
|
|
await (await workspace(workspaceId)).mergeTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.merge",
|
|
});
|
|
return;
|
|
}
|
|
if (action === "archive") {
|
|
await (await workspace(workspaceId)).archiveTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.archive",
|
|
});
|
|
return;
|
|
}
|
|
await (await workspace(workspaceId)).killTask({
|
|
workspaceId,
|
|
taskId,
|
|
reason: "cli.kill",
|
|
});
|
|
},
|
|
|
|
async createSandboxSession(input: {
|
|
workspaceId: string;
|
|
providerId: ProviderId;
|
|
sandboxId: string;
|
|
prompt: string;
|
|
cwd?: string;
|
|
agent?: AgentType | "opencode";
|
|
}): Promise<{ id: string; status: "running" | "idle" | "error" }> {
|
|
const created = await withSandboxHandle(input.workspaceId, input.providerId, input.sandboxId, async (handle) =>
|
|
handle.createSession({
|
|
prompt: input.prompt,
|
|
cwd: input.cwd,
|
|
agent: input.agent,
|
|
}),
|
|
);
|
|
if (!created.id) {
|
|
throw new Error(created.error ?? "sandbox session creation failed");
|
|
}
|
|
return {
|
|
id: created.id,
|
|
status: created.status,
|
|
};
|
|
},
|
|
|
|
async listSandboxSessions(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
input?: { cursor?: string; limit?: number },
|
|
): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.listSessions(input ?? {}));
|
|
},
|
|
|
|
async listSandboxSessionEvents(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
input: { sessionId: string; cursor?: string; limit?: number },
|
|
): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.listSessionEvents(input));
|
|
},
|
|
|
|
async createSandboxProcess(input: {
|
|
workspaceId: string;
|
|
providerId: ProviderId;
|
|
sandboxId: string;
|
|
request: ProcessCreateRequest;
|
|
}): Promise<SandboxProcessRecord> {
|
|
return await withSandboxHandle(input.workspaceId, input.providerId, input.sandboxId, async (handle) => handle.createProcess(input.request));
|
|
},
|
|
|
|
async listSandboxProcesses(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<{ processes: SandboxProcessRecord[] }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.listProcesses());
|
|
},
|
|
|
|
async getSandboxProcessLogs(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessLogFollowQuery,
|
|
): Promise<ProcessLogsResponse> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.getProcessLogs({ processId, query }));
|
|
},
|
|
|
|
async stopSandboxProcess(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessSignalQuery,
|
|
): Promise<SandboxProcessRecord> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.stopProcess({ processId, query }));
|
|
},
|
|
|
|
async killSandboxProcess(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
processId: string,
|
|
query?: ProcessSignalQuery,
|
|
): Promise<SandboxProcessRecord> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.killProcess({ processId, query }));
|
|
},
|
|
|
|
async deleteSandboxProcess(workspaceId: string, providerId: ProviderId, sandboxId: string, processId: string): Promise<void> {
|
|
await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.deleteProcess({ processId }));
|
|
},
|
|
|
|
subscribeSandboxProcesses(workspaceId: string, providerId: ProviderId, sandboxId: string, listener: () => void): () => void {
|
|
return subscribeSandboxProcesses(workspaceId, providerId, sandboxId, listener);
|
|
},
|
|
|
|
async sendSandboxPrompt(input: {
|
|
workspaceId: string;
|
|
providerId: ProviderId;
|
|
sandboxId: string;
|
|
sessionId: string;
|
|
prompt: string;
|
|
notification?: boolean;
|
|
}): Promise<void> {
|
|
await withSandboxHandle(input.workspaceId, input.providerId, input.sandboxId, async (handle) =>
|
|
handle.sendPrompt({
|
|
sessionId: input.sessionId,
|
|
prompt: input.prompt,
|
|
notification: input.notification,
|
|
}),
|
|
);
|
|
},
|
|
|
|
async sandboxSessionStatus(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
sessionId: string,
|
|
): Promise<{ id: string; status: "running" | "idle" | "error" }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.sessionStatus({ sessionId }));
|
|
},
|
|
|
|
async sandboxProviderState(
|
|
workspaceId: string,
|
|
providerId: ProviderId,
|
|
sandboxId: string,
|
|
): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.providerState());
|
|
},
|
|
|
|
async getSandboxAgentConnection(workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<{ endpoint: string; token?: string }> {
|
|
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.sandboxAgentConnection());
|
|
},
|
|
|
|
async getWorkspaceSummary(workspaceId: string): Promise<WorkspaceSummarySnapshot> {
|
|
return (await workspace(workspaceId)).getWorkspaceSummary({ workspaceId });
|
|
},
|
|
|
|
async getTaskDetail(workspaceId: string, repoId: string, taskIdValue: string): Promise<WorkbenchTaskDetail> {
|
|
return (await task(workspaceId, repoId, taskIdValue)).getTaskDetail();
|
|
},
|
|
|
|
async getSessionDetail(workspaceId: string, repoId: string, taskIdValue: string, sessionId: string): Promise<WorkbenchSessionDetail> {
|
|
return (await task(workspaceId, repoId, taskIdValue)).getSessionDetail({ sessionId });
|
|
},
|
|
|
|
async getWorkbench(workspaceId: string): Promise<TaskWorkbenchSnapshot> {
|
|
return await getWorkbenchCompat(workspaceId);
|
|
},
|
|
|
|
subscribeWorkbench(workspaceId: string, listener: () => void): () => void {
|
|
return subscribeWorkbench(workspaceId, listener);
|
|
},
|
|
|
|
async createWorkbenchTask(workspaceId: string, input: TaskWorkbenchCreateTaskInput): Promise<TaskWorkbenchCreateTaskResponse> {
|
|
return (await workspace(workspaceId)).createWorkbenchTask(input);
|
|
},
|
|
|
|
async markWorkbenchUnread(workspaceId: string, input: TaskWorkbenchSelectInput): Promise<void> {
|
|
await (await workspace(workspaceId)).markWorkbenchUnread(input);
|
|
},
|
|
|
|
async renameWorkbenchTask(workspaceId: string, input: TaskWorkbenchRenameInput): Promise<void> {
|
|
await (await workspace(workspaceId)).renameWorkbenchTask(input);
|
|
},
|
|
|
|
async renameWorkbenchBranch(workspaceId: string, input: TaskWorkbenchRenameInput): Promise<void> {
|
|
await (await workspace(workspaceId)).renameWorkbenchBranch(input);
|
|
},
|
|
|
|
async createWorkbenchSession(workspaceId: string, input: TaskWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }> {
|
|
return await (await workspace(workspaceId)).createWorkbenchSession(input);
|
|
},
|
|
|
|
async renameWorkbenchSession(workspaceId: string, input: TaskWorkbenchRenameSessionInput): Promise<void> {
|
|
await (await workspace(workspaceId)).renameWorkbenchSession(input);
|
|
},
|
|
|
|
async setWorkbenchSessionUnread(workspaceId: string, input: TaskWorkbenchSetSessionUnreadInput): Promise<void> {
|
|
await (await workspace(workspaceId)).setWorkbenchSessionUnread(input);
|
|
},
|
|
|
|
async updateWorkbenchDraft(workspaceId: string, input: TaskWorkbenchUpdateDraftInput): Promise<void> {
|
|
await (await workspace(workspaceId)).updateWorkbenchDraft(input);
|
|
},
|
|
|
|
async changeWorkbenchModel(workspaceId: string, input: TaskWorkbenchChangeModelInput): Promise<void> {
|
|
await (await workspace(workspaceId)).changeWorkbenchModel(input);
|
|
},
|
|
|
|
async sendWorkbenchMessage(workspaceId: string, input: TaskWorkbenchSendMessageInput): Promise<void> {
|
|
await (await workspace(workspaceId)).sendWorkbenchMessage(input);
|
|
},
|
|
|
|
async stopWorkbenchSession(workspaceId: string, input: TaskWorkbenchTabInput): Promise<void> {
|
|
await (await workspace(workspaceId)).stopWorkbenchSession(input);
|
|
},
|
|
|
|
async closeWorkbenchSession(workspaceId: string, input: TaskWorkbenchTabInput): Promise<void> {
|
|
await (await workspace(workspaceId)).closeWorkbenchSession(input);
|
|
},
|
|
|
|
async publishWorkbenchPr(workspaceId: string, input: TaskWorkbenchSelectInput): Promise<void> {
|
|
await (await workspace(workspaceId)).publishWorkbenchPr(input);
|
|
},
|
|
|
|
async revertWorkbenchFile(workspaceId: string, input: TaskWorkbenchDiffInput): Promise<void> {
|
|
await (await workspace(workspaceId)).revertWorkbenchFile(input);
|
|
},
|
|
|
|
async health(): Promise<{ ok: true }> {
|
|
const workspaceId = options.defaultWorkspaceId;
|
|
if (!workspaceId) {
|
|
throw new Error("Backend client default workspace is required for health checks");
|
|
}
|
|
|
|
await (await workspace(workspaceId)).useWorkspace({
|
|
workspaceId,
|
|
});
|
|
return { ok: true };
|
|
},
|
|
|
|
async useWorkspace(workspaceId: string): Promise<{ workspaceId: string }> {
|
|
return (await workspace(workspaceId)).useWorkspace({ workspaceId });
|
|
},
|
|
};
|
|
}
|