feat(foundry): add foundry base sandbox image with sudo, chromium, and dev tooling

Add a custom Docker image (foundry-base.Dockerfile) that builds sandbox-agent
from source and layers sudo, git, neovim, gh, node, bun, chromium, and
agent-browser. Includes publish script for timestamped + latest tags to
rivetdev/sandbox-agent on Docker Hub.

Update local sandbox provider default to use foundry-base-latest and wire
HF_LOCAL_SANDBOX_IMAGE env var through compose.dev.yaml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Flurry 2026-03-17 02:09:12 -07:00
parent eafe0f9fe4
commit 3895e34bdb
36 changed files with 800 additions and 1126 deletions

View file

@ -3,7 +3,22 @@ import { workflow } from "rivetkit/workflow";
import type { TaskRecord } from "@sandbox-agent/foundry-shared";
import { taskDb } from "./db/db.js";
import { getCurrentRecord } from "./workflow/common.js";
import { getSessionDetail, getTaskDetail, getTaskSummary } from "./workspace.js";
import {
changeWorkspaceModel,
getSessionDetail,
getTaskDetail,
getTaskSummary,
markWorkspaceUnread,
refreshWorkspaceDerivedState,
refreshWorkspaceSessionTranscript,
renameWorkspaceSession,
renameWorkspaceTask,
selectWorkspaceSession,
setWorkspaceSessionUnread,
syncTaskPullRequest,
syncWorkspaceSessionStatus,
updateWorkspaceDraft,
} from "./workspace.js";
import { runTaskWorkflow } from "./workflow/index.js";
import { TASK_QUEUE_NAMES } from "./workflow/queue.js";
@ -42,6 +57,41 @@ export const task = actor({
async getSessionDetail(c, input: { sessionId: string; authSessionId?: string }) {
return await getSessionDetail(c, input.sessionId, input.authSessionId);
},
// Direct actions migrated from queue:
async markUnread(c, input: { authSessionId?: string }) {
await markWorkspaceUnread(c, input?.authSessionId);
},
async renameTask(c, input: { value: string }) {
await renameWorkspaceTask(c, input.value);
},
async renameSession(c, input: { sessionId: string; title: string }) {
await renameWorkspaceSession(c, input.sessionId, input.title);
},
async selectSession(c, input: { sessionId: string; authSessionId?: string }) {
await selectWorkspaceSession(c, input.sessionId, input?.authSessionId);
},
async setSessionUnread(c, input: { sessionId: string; unread: boolean; authSessionId?: string }) {
await setWorkspaceSessionUnread(c, input.sessionId, input.unread, input?.authSessionId);
},
async updateDraft(c, input: { sessionId: string; text: string; attachments: any[]; authSessionId?: string }) {
await updateWorkspaceDraft(c, input.sessionId, input.text, input.attachments, input?.authSessionId);
},
async changeModel(c, input: { sessionId: string; model: string; authSessionId?: string }) {
await changeWorkspaceModel(c, input.sessionId, input.model, input?.authSessionId);
},
async refreshSessionTranscript(c, input: { sessionId: string }) {
await refreshWorkspaceSessionTranscript(c, input.sessionId);
},
async refreshDerived(c) {
await refreshWorkspaceDerivedState(c);
},
async syncSessionStatus(c, input: { sessionId: string; status: "running" | "idle" | "error"; at: number }) {
await syncWorkspaceSessionStatus(c, input.sessionId, input.status, input.at);
},
async syncPullRequest(c, input: { pullRequest: any }) {
await syncTaskPullRequest(c, input?.pullRequest ?? null);
},
},
run: workflow(runTaskWorkflow),
});