Add foundry terminal and process pane

This commit is contained in:
Nathan Flurry 2026-03-10 23:55:43 -07:00
parent 0471214d65
commit 28c4ac22ff
16 changed files with 2412 additions and 36 deletions

View file

@ -3,7 +3,15 @@ import { eq } from "drizzle-orm";
import { actor, queue } from "rivetkit";
import { Loop, workflow } from "rivetkit/workflow";
import type { ProviderId } from "@openhandoff/shared";
import type { SessionEvent, SessionRecord } from "sandbox-agent";
import type {
ProcessCreateRequest,
ProcessInfo,
ProcessLogFollowQuery,
ProcessLogsResponse,
ProcessSignalQuery,
SessionEvent,
SessionRecord,
} from "sandbox-agent";
import { sandboxInstanceDb } from "./db/db.js";
import { sandboxInstance as sandboxInstanceTable } from "./db/schema.js";
import { SandboxInstancePersistDriver } from "./persist.js";
@ -18,6 +26,11 @@ export interface SandboxInstanceInput {
sandboxId: string;
}
interface SandboxAgentConnection {
endpoint: string;
token?: string;
}
const SANDBOX_ROW_ID = 1;
const CREATE_SESSION_MAX_ATTEMPTS = 3;
const CREATE_SESSION_RETRY_BASE_MS = 1_000;
@ -79,7 +92,7 @@ function parseMetadata(metadataJson: string): Record<string, unknown> {
}
}
async function loadPersistedAgentConfig(c: any): Promise<{ endpoint: string; token?: string } | null> {
async function loadPersistedAgentConfig(c: any): Promise<SandboxAgentConnection | null> {
try {
const row = await c.db
.select({ metadataJson: sandboxInstanceTable.metadataJson })
@ -101,7 +114,7 @@ async function loadPersistedAgentConfig(c: any): Promise<{ endpoint: string; tok
return null;
}
async function loadFreshDaytonaAgentConfig(c: any): Promise<{ endpoint: string; token?: string }> {
async function loadFreshDaytonaAgentConfig(c: any): Promise<SandboxAgentConnection> {
const { config, driver } = getActorRuntimeContext();
const daytona = driver.daytona.createClient({
apiUrl: config.providers.daytona.endpoint,
@ -116,7 +129,7 @@ async function loadFreshDaytonaAgentConfig(c: any): Promise<{ endpoint: string;
return preview.token ? { endpoint: preview.url, token: preview.token } : { endpoint: preview.url };
}
async function loadFreshProviderAgentConfig(c: any): Promise<{ endpoint: string; token?: string }> {
async function loadFreshProviderAgentConfig(c: any): Promise<SandboxAgentConnection> {
const { providers } = getActorRuntimeContext();
const provider = providers.get(c.state.providerId);
return await provider.ensureSandboxAgent({
@ -125,7 +138,7 @@ async function loadFreshProviderAgentConfig(c: any): Promise<{ endpoint: string;
});
}
async function loadAgentConfig(c: any): Promise<{ endpoint: string; token?: string }> {
async function loadAgentConfig(c: any): Promise<SandboxAgentConnection> {
const persisted = await loadPersistedAgentConfig(c);
if (c.state.providerId === "daytona") {
// Keep one stable signed preview endpoint per sandbox-instance actor.
@ -282,6 +295,13 @@ async function getSandboxAgentClient(c: any) {
});
}
function broadcastProcessesUpdated(c: any): void {
c.broadcast("processesUpdated", {
sandboxId: c.state.sandboxId,
at: Date.now(),
});
}
async function ensureSandboxMutation(c: any, command: EnsureSandboxCommand): Promise<void> {
const now = Date.now();
const metadata = {
@ -478,6 +498,56 @@ export const sandboxInstance = actor({
sandboxId: input.sandboxId,
}),
actions: {
async sandboxAgentConnection(c: any): Promise<SandboxAgentConnection> {
return await loadAgentConfig(c);
},
async createProcess(c: any, request: ProcessCreateRequest): Promise<ProcessInfo> {
const client = await getSandboxAgentClient(c);
const created = await client.createProcess(request);
broadcastProcessesUpdated(c);
return created;
},
async listProcesses(c: any): Promise<{ processes: ProcessInfo[] }> {
const client = await getSandboxAgentClient(c);
return await client.listProcesses();
},
async getProcessLogs(
c: any,
request: { processId: string; query?: ProcessLogFollowQuery }
): Promise<ProcessLogsResponse> {
const client = await getSandboxAgentClient(c);
return await client.getProcessLogs(request.processId, request.query);
},
async stopProcess(
c: any,
request: { processId: string; query?: ProcessSignalQuery }
): Promise<ProcessInfo> {
const client = await getSandboxAgentClient(c);
const stopped = await client.stopProcess(request.processId, request.query);
broadcastProcessesUpdated(c);
return stopped;
},
async killProcess(
c: any,
request: { processId: string; query?: ProcessSignalQuery }
): Promise<ProcessInfo> {
const client = await getSandboxAgentClient(c);
const killed = await client.killProcess(request.processId, request.query);
broadcastProcessesUpdated(c);
return killed;
},
async deleteProcess(c: any, request: { processId: string }): Promise<void> {
const client = await getSandboxAgentClient(c);
await client.deleteProcess(request.processId);
broadcastProcessesUpdated(c);
},
async providerState(c: any): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }> {
const at = Date.now();
const { config, driver } = getActorRuntimeContext();