feat: Claude Code Monitor — lanes, pipelines and a merged workspace

Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
This commit is contained in:
2026-07-29 17:07:45 +07:00
commit 57dc91585d
783 changed files with 221743 additions and 0 deletions
+150
View File
@@ -0,0 +1,150 @@
/**
* @file Desktop smoke test.
*
* Boots the compiled main process under Electron, then probes the embedded
* dashboard server's /api/health endpoint. This is intentionally minimal:
* it does not exercise the BrowserWindow (which requires a display) so it
* runs on headless CI without xvfb. The window itself is covered by manual
* QA in the PR description.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { once } from "node:events";
import http from "node:http";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DESKTOP_ROOT = path.resolve(__dirname, "..");
const MAIN_JS = path.join(DESKTOP_ROOT, "out", "main.js");
// Resolve the actual Electron executable (electron.exe on Windows, the binary
// under Electron.app on macOS). The `.bin/electron` shim is extension-less and
// cannot be spawned without a shell on Windows; `require("electron")` returns
// the real binary path on every platform.
const ELECTRON_BIN = createRequire(import.meta.url)("electron");
const HEALTH_TIMEOUT_MS = 60_000;
const POLL_INTERVAL_MS = 500;
/** Resolve when GET /api/health on any of these ports answers ok. */
async function waitForHealth(ports, deadline) {
while (Date.now() < deadline) {
for (const port of ports) {
const ok = await probeHealth(port);
if (ok) return port;
}
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
}
throw new Error(`No port answered /api/health within ${HEALTH_TIMEOUT_MS}ms (tried ${ports})`);
}
function probeHealth(port) {
return new Promise((resolve) => {
const req = http.get({ host: "127.0.0.1", port, path: "/api/health", timeout: 1500 }, (res) => {
let buf = "";
res.setEncoding("utf8");
res.on("data", (chunk) => (buf += chunk));
res.on("end", () => {
try {
resolve(JSON.parse(buf)?.status === "ok");
} catch {
resolve(false);
}
});
});
req.on("error", () => resolve(false));
req.on("timeout", () => {
req.destroy();
resolve(false);
});
});
}
let electronProc;
// Pick a unique high port for each test run so we never accidentally probe an
// unrelated server (e.g. the user's own `npm start` on 4820). The env var
// `CCAM_DESKTOP_BIND_PORT` tells the desktop process to bind exactly this port,
// skipping the "adopt an existing healthy server" code path.
const TEST_PORT = 50000 + Math.floor(Math.random() * 5000);
// On POSIX, spawn the Electron parent as a process-group leader so we can
// signal the whole tree (helpers, embedded server) with one kill(-pid).
// Without this, SIGTERM only hits the parent and leaves helpers alive,
// keeping the stdio pipes open and hanging `node --test` indefinitely.
const IS_POSIX = process.platform !== "win32";
/** Kill the Electron process tree and resolve when it's actually gone. */
async function killElectronTree(proc, { timeoutMs = 5_000 } = {}) {
if (!proc || proc.exitCode !== null || proc.signalCode !== null) return;
proc.killedByTest = true;
const signalGroup = (sig) => {
try {
if (IS_POSIX && proc.pid) process.kill(-proc.pid, sig);
else proc.kill(sig);
} catch {
/* group may already be gone */
}
};
const exited = once(proc, "exit");
signalGroup("SIGTERM");
const timer = new Promise((resolve) => setTimeout(resolve, timeoutMs, "timeout"));
const winner = await Promise.race([exited.then(() => "exit"), timer]);
if (winner === "timeout") {
signalGroup("SIGKILL");
await Promise.race([exited, new Promise((r) => setTimeout(r, 2_000))]);
}
}
describe("desktop smoke", () => {
before(async () => {
electronProc = spawn(ELECTRON_BIN, [MAIN_JS], {
cwd: DESKTOP_ROOT,
detached: IS_POSIX,
env: {
...process.env,
// Suppress the BrowserWindow on the test runner; we only care that
// the server boots cleanly.
ELECTRON_DISABLE_GPU: "1",
ELECTRON_ENABLE_LOGGING: "1",
CCAM_DESKTOP_VERBOSE: "1",
CCAM_DESKTOP_BIND_PORT: String(TEST_PORT),
},
stdio: ["ignore", "pipe", "pipe"],
});
electronProc.stdout.on("data", (b) => process.stdout.write(`[electron] ${b}`));
electronProc.stderr.on("data", (b) => process.stderr.write(`[electron] ${b}`));
electronProc.on("exit", (code, signal) => {
if (!electronProc.killedByTest) {
// eslint-disable-next-line no-console
console.error(`electron exited unexpectedly: code=${code} signal=${signal}`);
}
});
});
after(async () => {
await killElectronTree(electronProc);
});
it("brings up the embedded server and serves /api/health on the bound port", async () => {
const deadline = Date.now() + HEALTH_TIMEOUT_MS;
const port = await waitForHealth([TEST_PORT], deadline);
assert.equal(
port,
TEST_PORT,
`desktop process should have bound CCAM_DESKTOP_BIND_PORT=${TEST_PORT}`
);
assert.ok(
electronProc && !electronProc.killed && electronProc.exitCode === null,
"electron process should still be alive when /api/health answers"
);
});
});