feat(v2): migrate container runtime to Bun, improve image build surface
Container side: - agent-runner switches to Bun. Drops better-sqlite3 (native compile gone), drops tsc build step in-image AND the tsc-on-every-session-wake in the entrypoint — bun runs src/index.ts directly. bun:sqlite replaces better-sqlite3; cross-mount DB invariants (journal_mode=DELETE, busy_timeout) preserved. Named params converted from @name to $name because bun:sqlite does not auto-strip the prefix the way better-sqlite3 does. - Tests ported from vitest to bun:test (only describe/it/expect/before/afterEach used, API-compatible). vitest.config.ts excludes container/agent-runner/. - bun.lock replaces pnpm-lock.yaml + pnpm-workspace.yaml under container/agent-runner/. Host pnpm workspace does NOT include this tree. Dockerfile improvements (independent of Bun but bundled while touching the file): - tini as PID 1 for correct SIGTERM propagation (prevents half-written outbound.db on shutdown). - Extracted entrypoint.sh — readable and diffable vs the old inline printf. - BuildKit cache mounts for apt + bun install + pnpm install. - --no-install-recommends on apt, pinned CLAUDE_CODE_VERSION, AGENT_BROWSER, VERCEL, BUN_VERSION. - CJK fonts (~200MB) behind ARG INSTALL_CJK_FONTS=false; build.sh reads from .env; setup/container.ts reads the same .env so /setup and manual rebuild stay in sync. - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 in case any postinstall tries to pull a redundant Chromium. - /home/node 755 (was 777). Host side: - src/container-runner.ts dynamic spawn command collapses from `pnpm exec tsc --outDir /tmp/dist … && node /tmp/dist/index.js` to `exec bun run /app/src/index.ts` — cold start ~200-500ms faster per wake. CI: - oven-sh/setup-bun@v2 alongside Node/pnpm. Adds explicit container typecheck (was documented in CLAUDE.md, not enforced) and `bun test` for agent-runner tests.
This commit is contained in:
@@ -17,35 +17,35 @@
|
||||
* src/session-manager.ts for the full set of cross-mount invariants and
|
||||
* scripts/sanity-live-poll.ts for the empirical validation.
|
||||
*/
|
||||
import Database from 'better-sqlite3';
|
||||
import { Database } from 'bun:sqlite';
|
||||
import fs from 'fs';
|
||||
|
||||
const DEFAULT_INBOUND_PATH = '/workspace/inbound.db';
|
||||
const DEFAULT_OUTBOUND_PATH = '/workspace/outbound.db';
|
||||
const DEFAULT_HEARTBEAT_PATH = '/workspace/.heartbeat';
|
||||
|
||||
let _inbound: Database.Database | null = null;
|
||||
let _outbound: Database.Database | null = null;
|
||||
let _inbound: Database | null = null;
|
||||
let _outbound: Database | null = null;
|
||||
let _heartbeatPath: string = DEFAULT_HEARTBEAT_PATH;
|
||||
|
||||
/** Inbound DB — container opens read-only (host is the sole writer). */
|
||||
export function getInboundDb(): Database.Database {
|
||||
export function getInboundDb(): Database {
|
||||
if (!_inbound) {
|
||||
const dbPath = process.env.SESSION_INBOUND_DB_PATH || DEFAULT_INBOUND_PATH;
|
||||
_inbound = new Database(dbPath, { readonly: true });
|
||||
_inbound.pragma('busy_timeout = 5000');
|
||||
_inbound.exec('PRAGMA busy_timeout = 5000');
|
||||
}
|
||||
return _inbound;
|
||||
}
|
||||
|
||||
/** Outbound DB — container owns this file (sole writer). */
|
||||
export function getOutboundDb(): Database.Database {
|
||||
export function getOutboundDb(): Database {
|
||||
if (!_outbound) {
|
||||
const dbPath = process.env.SESSION_OUTBOUND_DB_PATH || DEFAULT_OUTBOUND_PATH;
|
||||
_outbound = new Database(dbPath);
|
||||
_outbound.pragma('journal_mode = DELETE');
|
||||
_outbound.pragma('busy_timeout = 5000');
|
||||
_outbound.pragma('foreign_keys = ON');
|
||||
_outbound.exec('PRAGMA journal_mode = DELETE');
|
||||
_outbound.exec('PRAGMA busy_timeout = 5000');
|
||||
_outbound.exec('PRAGMA foreign_keys = ON');
|
||||
// Lightweight forward-compat: session_state was added after the initial
|
||||
// v2 schema, so older session DBs don't have it. Create it on demand
|
||||
// instead of requiring a formal migration pass. Also handle the case
|
||||
@@ -97,9 +97,9 @@ export function clearStaleProcessingAcks(): void {
|
||||
}
|
||||
|
||||
/** For tests — creates in-memory DBs with the session schemas. */
|
||||
export function initTestSessionDb(): { inbound: Database.Database; outbound: Database.Database } {
|
||||
export function initTestSessionDb(): { inbound: Database; outbound: Database } {
|
||||
_inbound = new Database(':memory:');
|
||||
_inbound.pragma('foreign_keys = ON');
|
||||
_inbound.exec('PRAGMA foreign_keys = ON');
|
||||
_inbound.exec(`
|
||||
CREATE TABLE messages_in (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -132,7 +132,7 @@ export function initTestSessionDb(): { inbound: Database.Database; outbound: Dat
|
||||
`);
|
||||
|
||||
_outbound = new Database(':memory:');
|
||||
_outbound.pragma('foreign_keys = ON');
|
||||
_outbound.exec('PRAGMA foreign_keys = ON');
|
||||
_outbound.exec(`
|
||||
CREATE TABLE messages_out (
|
||||
id TEXT PRIMARY KEY,
|
||||
@@ -173,6 +173,6 @@ export function closeSessionDb(): void {
|
||||
* @deprecated Use getInboundDb() / getOutboundDb() instead.
|
||||
* Kept for backward compatibility during migration.
|
||||
*/
|
||||
export function getSessionDb(): Database.Database {
|
||||
export function getSessionDb(): Database {
|
||||
return getInboundDb();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user