diff --git a/Dockerfile b/Dockerfile index 965cb8b..19d40be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,10 @@ RUN npm run build FROM node:22-alpine WORKDIR /app +# Runs Claude Code sessions inside a named tmux session per lane so both the +# dashboard (via node-pty attach) and a real terminal can share one live pane. +RUN apk add --no-cache tmux + COPY --from=server-deps /app/node_modules ./node_modules/ COPY package.json ./ COPY server/ ./server/ diff --git a/client/package-lock.json b/client/package-lock.json index 9a1b944..acef41a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@fontsource/inter": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "i18next": "^26.0.8", @@ -2100,6 +2102,21 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", diff --git a/client/package.json b/client/package.json index 62adb1d..82a30e3 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,8 @@ "dependencies": { "@fontsource/inter": "^5.2.8", "@fontsource/jetbrains-mono": "^5.2.8", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "i18next": "^26.0.8", diff --git a/package-lock.json b/package-lock.json index 125d804..16bcd97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cross-spawn": "^7.0.6", "express": "^4.21.2", "multer": "^2.0.0", + "node-pty": "^1.1.0", "redoc": "^2.5.3", "swagger-ui-express": "^5.0.1", "tar": "^7.4.3", @@ -1669,6 +1670,12 @@ "node": ">=10" } }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -1701,6 +1708,16 @@ "node": "4.x || >=6.0.0" } }, + "node_modules/node-pty": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz", + "integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0" + } + }, "node_modules/node-readfiles": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", diff --git a/package.json b/package.json index f5d86e7..e1c6c3a 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "cross-spawn": "^7.0.6", "express": "^4.21.2", "multer": "^2.0.0", + "node-pty": "^1.1.0", "redoc": "^2.5.3", "swagger-ui-express": "^5.0.1", "tar": "^7.4.3", diff --git a/server/db.js b/server/db.js index a209222..b9b3f08 100644 --- a/server/db.js +++ b/server/db.js @@ -248,7 +248,7 @@ db.exec(` CREATE TABLE IF NOT EXISTS dashboard_runs ( id TEXT PRIMARY KEY, session_id TEXT, - mode TEXT NOT NULL, + mode TEXT, cwd TEXT NOT NULL, model TEXT, permission_mode TEXT, @@ -496,6 +496,15 @@ try { } db.prepare("CREATE INDEX IF NOT EXISTS idx_dashboard_runs_lane ON dashboard_runs(lane_id)").run(); +// Migrate: tmux session name backing this run (tmux+PTY terminal design, +// 2026-08-11). Additive and nullable — historical rows spawned via the old +// stream-json mode have no tmux session and are display-only history now. +try { + db.prepare("SELECT tmux_session FROM dashboard_runs LIMIT 1").get(); +} catch { + db.prepare("ALTER TABLE dashboard_runs ADD COLUMN tmux_session TEXT").run(); +} + // Migrate: add stage-detection columns to lanes. Inference is never evidence — // these are additive columns separate from `stage` (the declared stage), so // `stage`'s meaning is untouched and turning the feature off loses nothing.