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:
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @file Verifies the legacy `agents_new` rebuild in db.js (triggered when the
|
||||
* agents table's stored CHECK constraint still contains the old 'idle'
|
||||
* status) preserves the workflow_run_id/workflow_phase columns added by the
|
||||
* earlier workflow migration, plus awaiting_reason, and recreates
|
||||
* idx_agents_workflow.
|
||||
*
|
||||
* Regression coverage for a bug flagged on PR #228: the rebuild's CREATE
|
||||
* TABLE agents_new / INSERT INTO agents_new SELECT / index-recreate block was
|
||||
* written before the workflow_run_id + workflow_phase columns existed, so it
|
||||
* silently dropped them (and idx_agents_workflow) for any DB old enough to
|
||||
* still carry the legacy CHECK. Because the workflow_run_id/workflow_phase
|
||||
* prepared statements are compiled at module load time, right after the
|
||||
* migrations run, this crashed startup for such legacy DBs.
|
||||
*
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { describe, it, before, after } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
const Database = require("better-sqlite3");
|
||||
|
||||
let TEST_DB;
|
||||
|
||||
before(() => {
|
||||
TEST_DB = path.join(os.tmpdir(), `dashboard-agents-legacy-${Date.now()}-${process.pid}.db`);
|
||||
process.env.DASHBOARD_DB_PATH = TEST_DB;
|
||||
|
||||
// Hand-build a pre-workflow, pre-awaiting-reason legacy DB: a sessions table
|
||||
// just rich enough to satisfy the agents FK, and an agents table using the
|
||||
// old 3/5-status CHECK (still containing 'idle') with none of the columns
|
||||
// added by later migrations (workflow_run_id, workflow_phase, updated_at,
|
||||
// awaiting_input_since, awaiting_reason). This is the exact shape the
|
||||
// 'idle' CHECK detector at db.js:~731 is looking for.
|
||||
const raw = new Database(TEST_DB);
|
||||
raw.pragma("foreign_keys = OFF");
|
||||
raw.exec(`
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('active','completed','error','abandoned')),
|
||||
cwd TEXT,
|
||||
model TEXT,
|
||||
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
ended_at TEXT,
|
||||
metadata TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE agents (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'main' CHECK(type IN ('main','subagent')),
|
||||
subagent_type TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'idle' CHECK(status IN ('idle','connected','working','completed','error')),
|
||||
task TEXT,
|
||||
current_tool TEXT,
|
||||
started_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
||||
ended_at TEXT,
|
||||
parent_agent_id TEXT,
|
||||
metadata TEXT,
|
||||
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (parent_agent_id) REFERENCES agents(id) ON DELETE SET NULL
|
||||
);
|
||||
`);
|
||||
raw
|
||||
.prepare("INSERT INTO sessions (id, name, status, cwd, model) VALUES (?, ?, ?, ?, ?)")
|
||||
.run("s-legacy-1", "legacy session", "active", "/tmp/legacy-proj", "claude");
|
||||
raw
|
||||
.prepare(
|
||||
"INSERT INTO agents (id, session_id, name, type, status, task) VALUES (?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
.run("a-legacy-1", "s-legacy-1", "legacy main agent", "main", "idle", "legacy task");
|
||||
raw.close();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
try {
|
||||
delete require.cache[require.resolve("../db")];
|
||||
} catch {}
|
||||
for (const suffix of ["", "-wal", "-shm"]) {
|
||||
try {
|
||||
fs.unlinkSync(TEST_DB + suffix);
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
|
||||
describe("legacy agents_new rebuild (pre-workflow, pre-awaiting-reason DB)", () => {
|
||||
it("loads db.js against the legacy DB without throwing", () => {
|
||||
delete require.cache[require.resolve("../db")];
|
||||
assert.doesNotThrow(() => require("../db"));
|
||||
});
|
||||
|
||||
it("adds workflow_run_id, workflow_phase and awaiting_reason to agents", () => {
|
||||
const { db } = require("../db");
|
||||
const cols = db.prepare("PRAGMA table_info(agents)").all();
|
||||
const names = cols.map((c) => c.name);
|
||||
for (const col of ["workflow_run_id", "workflow_phase", "awaiting_reason"]) {
|
||||
assert.ok(names.includes(col), `expected agents.${col}; got: ${names.join(",")}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("recreates idx_agents_workflow", () => {
|
||||
const { db } = require("../db");
|
||||
const idx = db
|
||||
.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'idx_agents_workflow'"
|
||||
)
|
||||
.get();
|
||||
assert.ok(idx, "expected idx_agents_workflow to exist after the rebuild");
|
||||
});
|
||||
|
||||
it("preserves the pre-existing agent row and remaps its status idle -> waiting", () => {
|
||||
const { db } = require("../db");
|
||||
const row = db.prepare("SELECT * FROM agents WHERE id = ?").get("a-legacy-1");
|
||||
assert.ok(row, "expected the legacy agent row to survive the rebuild");
|
||||
assert.equal(row.status, "waiting");
|
||||
assert.equal(row.session_id, "s-legacy-1");
|
||||
assert.equal(row.name, "legacy main agent");
|
||||
assert.equal(row.task, "legacy task");
|
||||
assert.equal(row.workflow_run_id, null);
|
||||
assert.equal(row.workflow_phase, null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user