feat(worker): per-user box status and lifecycle helpers

Centralize per-user box home-path derivation, status inspection, and
lifecycle (start/stop/restart) in a new box.ts so the HTTP routes and the
terminal WS bridge share one source of truth. Add inspectUserBoxStatus to
docker.ts (single `inspect --format` call, all-null/false on non-zero exit)
and resetBox to user-container.ts (clears the idle timer and drops the
registry entry so a stopped box isn't treated as held).

Manual verification (docker required, after Task 4 wires the routes): with
the worker running and a box up,
`curl -H "Authorization: Bearer $TOKEN" localhost:3921/box/status?user=<you>`
shows running:true + image + startedAt; `POST /box/lifecycle {"action":"stop"}`
flips status to running:false; `start` brings it back.
This commit is contained in:
Gabriel Brown
2026-07-11 11:57:57 -04:00
parent d4303e652e
commit 6439e5200b
4 changed files with 119 additions and 0 deletions
@@ -0,0 +1,29 @@
import { afterEach, describe, expect, test, vi } from 'vitest';
const load = async () => {
vi.resetModules();
process.env.SPOON_WORKER_TOKEN = 'test-worker-token';
process.env.GITHUB_APP_ID = '123';
process.env.GITHUB_APP_PRIVATE_KEY =
'-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----';
process.env.SPOON_AGENT_WORKDIR = '/work';
return await import('../../src/box');
};
describe('boxHomePaths', () => {
afterEach(() => vi.resetModules());
test('derives the per-user home dir and container home', async () => {
const { boxHomePaths } = await load();
const paths = boxHomePaths('gib');
expect(paths.homeDir.endsWith('homes/gib')).toBe(true);
expect(paths.containerHome).toBe('/home/gib');
});
test('distinct usernames produce distinct home dirs', async () => {
const { boxHomePaths } = await load();
expect(boxHomePaths('gib').homeDir).not.toBe(
boxHomePaths('other').homeDir,
);
});
});