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

@ -10,6 +10,7 @@ import {
FilePlus,
FileX,
FolderOpen,
ExternalLink,
GitBranch,
GitPullRequest,
PanelRight,
@ -128,7 +129,7 @@ export const RightSidebar = memo(function RightSidebar({
}) {
const [css] = useStyletron();
const t = useFoundryTokens();
const [rightTab, setRightTab] = useState<"overview" | "changes" | "files">("changes");
const [rightTab, setRightTab] = useState<"overview" | "changes" | "files">("overview");
const contextMenu = useContextMenu();
const changedPaths = useMemo(() => new Set(task.fileChanges.map((file) => file.path)), [task.fileChanges]);
const isTerminal = task.status === "archived";
@ -645,6 +646,39 @@ export const RightSidebar = memo(function RightSidebar({
</div>
</div>
) : null}
{task.sandboxes?.find((s) => s.sandboxId === task.activeSandboxId)?.url ? (
<div className={css({ display: "flex", flexDirection: "column", gap: "8px" })}>
<LabelXSmall color={t.textTertiary} $style={{ textTransform: "uppercase", letterSpacing: "0.5px", fontWeight: 600 }}>
Sandbox
</LabelXSmall>
<a
href={task.sandboxes.find((s) => s.sandboxId === task.activeSandboxId)!.url!}
target="_blank"
rel="noopener noreferrer"
className={css({
display: "flex",
alignItems: "center",
gap: "8px",
color: t.textSecondary,
textDecoration: "none",
borderRadius: "6px",
paddingTop: "4px",
paddingRight: "8px",
paddingBottom: "4px",
paddingLeft: "4px",
":hover": { backgroundColor: t.interactiveHover, color: t.textPrimary },
})}
>
<ExternalLink size={14} color={t.textTertiary} style={{ flexShrink: 0 }} />
<LabelSmall
color="inherit"
$style={{ fontFamily: '"IBM Plex Mono", monospace', overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
>
{task.sandboxes.find((s) => s.sandboxId === task.activeSandboxId)!.url!}
</LabelSmall>
</a>
</div>
) : null}
</div>
) : rightTab === "changes" ? (
<div className={css({ padding: "10px 14px", display: "flex", flexDirection: "column", gap: "2px" })}>