Phase 2: single per-user box container for every thread
Every thread (agent turns + terminal + project commands) now execs into one
persistent per-user container (spoon-box-{username}) instead of ephemeral
docker run --rm — so the agent and terminal share the exact same running
environment, filesystem, and in-session installs.
- docker.ts: ensureUserContainer (persistent box) + streamExecInContainer/
runExecInContainer (docker exec, streaming) sharing a factored streamSubprocess
- user-container.ts: reference-counted box lifecycle (held while any thread
workspace is active or a terminal is connected; idle-reaped after
SPOON_AGENT_BOX_IDLE_MS, default 30m)
- worker.ts: runClaim acquires the box; codex turn + runProjectCommand exec into
it; release on stop/PR/failure
- terminal.ts: execs into the shared box (dockerode TTY) instead of a per-job
container; materializeUserHome runs the dotfiles setup in the box
- Verified: agent + terminal run in the same box, share fs, dotfiles + tmux load
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { env } from './env';
|
||||
import {
|
||||
ensureUserContainer,
|
||||
stopWorkspaceContainer,
|
||||
userContainerName,
|
||||
} from './runtime/docker';
|
||||
|
||||
// Phase 2: one persistent "box" container per user that all of their threads
|
||||
// (agent turns + terminal + commands) exec into. Reference-counted so it stays
|
||||
// up while any thread workspace is active or a terminal is connected, and is
|
||||
// reaped after an idle period once nothing holds it.
|
||||
type Box = { refs: number; idleTimer?: NodeJS.Timeout };
|
||||
const boxes = new Map<string, Box>();
|
||||
|
||||
export const acquireUserBox = async (args: {
|
||||
username: string;
|
||||
workdir: string;
|
||||
containerHome: string;
|
||||
}): Promise<string> => {
|
||||
const name = await ensureUserContainer(args);
|
||||
const box = boxes.get(args.username) ?? { refs: 0 };
|
||||
if (box.idleTimer) {
|
||||
clearTimeout(box.idleTimer);
|
||||
box.idleTimer = undefined;
|
||||
}
|
||||
box.refs += 1;
|
||||
boxes.set(args.username, box);
|
||||
return name;
|
||||
};
|
||||
|
||||
export const releaseUserBox = (username: string) => {
|
||||
const box = boxes.get(username);
|
||||
if (!box) return;
|
||||
box.refs = Math.max(0, box.refs - 1);
|
||||
if (box.refs > 0) return;
|
||||
box.idleTimer = setTimeout(() => {
|
||||
void stopWorkspaceContainer(userContainerName(username));
|
||||
boxes.delete(username);
|
||||
}, env.boxIdleMs);
|
||||
};
|
||||
Reference in New Issue
Block a user