Files
nntrivi2001 57dc91585d 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.
2026-07-30 14:39:03 +07:00

635 lines
19 KiB
JavaScript

#!/usr/bin/env node
/**
* Seeds the database with sample data for development and demo purposes.
*
* Default behavior is ADDITIVE and IDEMPOTENT:
* node scripts/seed.js Insert the three stable test fixtures
* (single-agent, deeply-nested, and a
* waiting-on-input session that exercises
* the Waiting badge / reason chip /
* banner UI). Re-runs are no-ops if
* fixtures already exist.
*
* node scripts/seed.js --full Also insert the random/demo sessions
* (old behavior; produces unbounded data
* on repeat runs — use intentionally).
*
* node scripts/seed.js --reset Remove existing fixture rows before
* re-inserting them (e.g. to refresh
* timestamps). Only deletes fixture
* sessions, never user data.
*
* This script NEVER deletes non-fixture data. To wipe the DB, use
* scripts/clear-data.js (which now requires --yes).
*
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
const { v4: uuidv4 } = require("uuid");
const { db, stmts } = require("../server/db");
// ── Stable fixture IDs ─────────────────────────────────────────────────────
// These IDs are intentionally non-UUID-shaped strings prefixed with `demo-`
// so they are easy to recognize, never collide with real Claude Code session
// UUIDs, and stay stable across seed runs.
const FIXTURES = {
solo: {
sessionId: "demo-solo-0001-0001-0001-000000000001",
mainAgentId: "demo-solo-0001-main",
},
waiting: {
sessionId: "demo-waiting-0001-0001-0001-000000000001",
mainAgentId: "demo-waiting-0001-main",
},
nested: {
sessionId: "demo-nested-0001-0001-0001-000000000001",
mainAgentId: "demo-nested-0001-main",
agents: {
l1Explorer: "demo-nested-0001-l1-explorer",
l2Researcher: "demo-nested-0001-l2-researcher",
l3TestWriter: "demo-nested-0001-l3-testwriter",
l4Debugger: "demo-nested-0001-l4-debugger",
l2Reviewer: "demo-nested-0001-l2-reviewer",
l1Architect: "demo-nested-0001-l1-architect",
l1DocWriter: "demo-nested-0001-l1-docwriter",
l2ExampleGen: "demo-nested-0001-l2-examplegen",
},
},
};
const FIXTURE_SESSION_IDS = [
FIXTURES.solo.sessionId,
FIXTURES.waiting.sessionId,
FIXTURES.nested.sessionId,
];
const args = new Set(process.argv.slice(2));
const FULL = args.has("--full");
const RESET = args.has("--reset");
function randomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
function minutesAgo(minutes) {
return new Date(Date.now() - minutes * 60000).toISOString();
}
const AGENT_NAMES = [
"Main Agent",
"Code Explorer",
"Test Runner",
"Code Reviewer",
"Security Auditor",
"Doc Writer",
"Debugger",
"Knowledge Base",
"TDD Assistant",
"UI Engineer",
];
const SUBAGENT_TYPES = [
"Explore",
"general-purpose",
"Plan",
"code-reviewer",
"tdd-assistant",
"debugger",
"security-auditor",
"doc-writer",
"knowledge-base",
"ui-engineer",
];
const TOOL_NAMES = [
"Read",
"Write",
"Edit",
"Bash",
"Grep",
"Glob",
"Agent",
"WebSearch",
"WebFetch",
];
const TASKS = [
"Searching for authentication middleware patterns",
"Running test suite for user service",
"Reviewing PR #42 for security vulnerabilities",
"Analyzing database schema for optimization",
"Exploring component structure in src/components",
"Writing unit tests for payment processor",
"Debugging failing integration test",
"Documenting API endpoints",
"Scanning for OWASP Top 10 vulnerabilities",
"Refactoring utility functions",
];
function sessionExists(id) {
return !!db.prepare("SELECT 1 FROM sessions WHERE id = ?").get(id);
}
function deleteFixtureRows() {
const placeholders = FIXTURE_SESSION_IDS.map(() => "?").join(",");
const tx = db.transaction(() => {
db.prepare(`DELETE FROM events WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM agents WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM token_usage WHERE session_id IN (${placeholders})`).run(
...FIXTURE_SESSION_IDS
);
db.prepare(`DELETE FROM sessions WHERE id IN (${placeholders})`).run(...FIXTURE_SESSION_IDS);
});
tx();
}
// ── Stable fixtures (AgentCard click behavior + the Waiting overlay demo) ──
function seedFixtures() {
const result = { inserted: [], skipped: [] };
const tx = db.transaction(() => {
// 1. Single-agent session (no subagents — leaf-only; click should NAVIGATE)
if (sessionExists(FIXTURES.solo.sessionId)) {
result.skipped.push("Single Agent: Quick Hotfix");
} else {
stmts.insertSession.run(
FIXTURES.solo.sessionId,
"Single Agent: Quick Hotfix",
"active",
"/home/dev/hotfix",
"claude-sonnet-4-6",
null
);
stmts.insertAgent.run(
FIXTURES.solo.mainAgentId,
FIXTURES.solo.sessionId,
"Main Agent",
"main",
null,
"working",
"Patching null-pointer in checkout handler",
null,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run(
"Edit",
FIXTURES.solo.mainAgentId
);
result.inserted.push("Single Agent: Quick Hotfix");
}
// 2. Waiting-on-input session — exercises the yellow Waiting overlay end
// to end: awaiting_input_since + awaiting_reason drive the Waiting
// badge, the reason chip/tooltip (urgent "notification" → amber), the
// Kanban Waiting column, and SessionDetail's waiting-for-input banner.
if (sessionExists(FIXTURES.waiting.sessionId)) {
result.skipped.push("Waiting Demo: Permission Prompt");
} else {
stmts.insertSession.run(
FIXTURES.waiting.sessionId,
"Waiting Demo: Permission Prompt",
"active",
"/home/dev/waiting-demo",
"claude-opus-4-6",
null
);
stmts.insertAgent.run(
FIXTURES.waiting.mainAgentId,
FIXTURES.waiting.sessionId,
"Main Agent",
"main",
null,
"waiting",
"Blocked on a permission prompt (Bash: npm publish)",
null,
null
);
// Stamp the awaiting overlay a few minutes in the past so the banner's
// "how long" readout shows something real on first render.
const awaitingTs = new Date(Date.now() - 4 * 60 * 1000).toISOString();
stmts.setSessionAwaitingInput.run(awaitingTs, "notification", FIXTURES.waiting.sessionId);
stmts.setAgentAwaitingInput.run(awaitingTs, "notification", FIXTURES.waiting.mainAgentId);
result.inserted.push("Waiting Demo: Permission Prompt");
}
// 3. Deeply-nested session (depth 4, branching — click PARENT toggles, LEAF navigates)
if (sessionExists(FIXTURES.nested.sessionId)) {
result.skipped.push("Deep Nesting: Multi-Agent Research Pipeline");
} else {
const ids = FIXTURES.nested.agents;
stmts.insertSession.run(
FIXTURES.nested.sessionId,
"Deep Nesting: Multi-Agent Research Pipeline",
"active",
"/home/dev/research-pipeline",
"claude-opus-4-6",
null
);
stmts.insertAgent.run(
FIXTURES.nested.mainAgentId,
FIXTURES.nested.sessionId,
"Main Agent",
"main",
null,
"waiting",
"Orchestrating multi-agent research pipeline",
null,
null
);
// Depth 1: Main → Codebase Explorer (working)
stmts.insertAgent.run(
ids.l1Explorer,
FIXTURES.nested.sessionId,
"Codebase Explorer",
"subagent",
"Explore",
"working",
"Mapping authentication module dependencies",
FIXTURES.nested.mainAgentId,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Glob", ids.l1Explorer);
// Depth 2: Explorer → Security Researcher (working)
stmts.insertAgent.run(
ids.l2Researcher,
FIXTURES.nested.sessionId,
"Security Researcher",
"subagent",
"general-purpose",
"working",
"Analyzing OAuth2 token validation patterns",
ids.l1Explorer,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run(
"WebSearch",
ids.l2Researcher
);
// Depth 3: Researcher → Test Engineer (working)
stmts.insertAgent.run(
ids.l3TestWriter,
FIXTURES.nested.sessionId,
"Test Engineer",
"subagent",
"test-engineer",
"working",
"Writing integration tests for token refresh flow",
ids.l2Researcher,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Write", ids.l3TestWriter);
// Depth 4: Test Engineer → Test Debugger (deepest leaf)
stmts.insertAgent.run(
ids.l4Debugger,
FIXTURES.nested.sessionId,
"Test Debugger",
"subagent",
"debugger",
"working",
"Investigating flaky assertion in token expiry test",
ids.l3TestWriter,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Bash", ids.l4Debugger);
// Depth 2 branch (sibling of Researcher): Code Reviewer (completed leaf)
stmts.insertAgent.run(
ids.l2Reviewer,
FIXTURES.nested.sessionId,
"Code Reviewer",
"subagent",
"code-reviewer",
"completed",
"Reviewed middleware chain for injection risks",
ids.l1Explorer,
null
);
db.prepare("UPDATE agents SET ended_at = ? WHERE id = ?").run(minutesAgo(5), ids.l2Reviewer);
// Depth 1 sibling: Architecture Planner (completed leaf)
stmts.insertAgent.run(
ids.l1Architect,
FIXTURES.nested.sessionId,
"Architecture Planner",
"subagent",
"Plan",
"completed",
"Designed auth service boundary and API contracts",
FIXTURES.nested.mainAgentId,
null
);
db.prepare("UPDATE agents SET ended_at = ? WHERE id = ?").run(
minutesAgo(12),
ids.l1Architect
);
// Depth 1 sibling: Documentation Writer (working — has its own child)
stmts.insertAgent.run(
ids.l1DocWriter,
FIXTURES.nested.sessionId,
"Documentation Writer",
"subagent",
"doc-writer",
"working",
"Writing API docs for /auth/* endpoints",
FIXTURES.nested.mainAgentId,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Edit", ids.l1DocWriter);
// Depth 2: Doc Writer → Example Generator (connected leaf)
stmts.insertAgent.run(
ids.l2ExampleGen,
FIXTURES.nested.sessionId,
"Example Generator",
"subagent",
"general-purpose",
"working",
"Generating cURL examples for auth endpoints",
ids.l1DocWriter,
null
);
result.inserted.push("Deep Nesting: Multi-Agent Research Pipeline (9 agents, depth 4)");
}
// Sprinkle a few events on freshly inserted fixtures only
for (const sid of FIXTURE_SESSION_IDS) {
const hasEvents =
db.prepare("SELECT 1 FROM events WHERE session_id = ? LIMIT 1").get(sid) !== undefined;
if (hasEvents) continue;
const agents = stmts.listAgentsBySession.all(sid);
const eventCount = Math.floor(Math.random() * 8) + 3;
for (let i = 0; i < eventCount; i++) {
const agent = randomItem(agents);
const eventType = randomItem(["PreToolUse", "PostToolUse", "Notification"]);
const tool = randomItem(TOOL_NAMES);
stmts.insertEvent.run(
sid,
agent?.id ?? null,
eventType,
eventType.includes("Tool") ? tool : null,
eventType === "PreToolUse"
? `Using tool: ${tool}`
: eventType === "PostToolUse"
? `Tool completed: ${tool}`
: `Agent ${agent?.name || "unknown"} notification`,
JSON.stringify({ tool_name: tool })
);
}
}
});
tx();
return result;
}
// ── Random demo data (old behavior — opt-in with --full) ───────────────────
function seedFullDemo() {
console.log("⚠️ --full mode: inserting random demo sessions on top of existing data.");
console.log(" These get fresh UUIDs each run, so re-runs accumulate. Use intentionally.\n");
const tx = db.transaction(() => {
const sessions = [];
const activeSessionId = uuidv4();
stmts.insertSession.run(
activeSessionId,
"Feature: User Authentication",
"active",
"/home/dev/my-app",
"claude-opus-4-6",
null
);
sessions.push(activeSessionId);
const activeSessionId2 = uuidv4();
stmts.insertSession.run(
activeSessionId2,
"Bug Fix: Payment Processing",
"active",
"/home/dev/payment-service",
"claude-sonnet-4-6",
null
);
sessions.push(activeSessionId2);
for (let i = 0; i < 5; i++) {
const id = uuidv4();
stmts.insertSession.run(
id,
randomItem([
"Refactor: Database Layer",
"Feature: Email Notifications",
"Fix: Memory Leak in Worker",
"Test: API Integration Suite",
"Docs: README Update",
]),
"completed",
randomItem(["/home/dev/api", "/home/dev/frontend", "/home/dev/worker"]),
randomItem(["claude-opus-4-6", "claude-sonnet-4-6"]),
null
);
db.prepare("UPDATE sessions SET ended_at = ? WHERE id = ?").run(
minutesAgo(Math.floor(Math.random() * 120)),
id
);
sessions.push(id);
}
const errSessionId = uuidv4();
stmts.insertSession.run(
errSessionId,
"Deploy: Production Release",
"error",
"/home/dev/infra",
"claude-opus-4-6",
null
);
db.prepare("UPDATE sessions SET ended_at = ? WHERE id = ?").run(minutesAgo(45), errSessionId);
sessions.push(errSessionId);
const mainAgent1 = `${activeSessionId}-main`;
stmts.insertAgent.run(
mainAgent1,
activeSessionId,
"Main Agent",
"main",
null,
"working",
"Implementing JWT authentication middleware",
null,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Edit", mainAgent1);
for (let i = 0; i < 3; i++) {
const subId = uuidv4();
const status = randomItem(["working", "working", "working"]);
stmts.insertAgent.run(
subId,
activeSessionId,
AGENT_NAMES[i + 1],
"subagent",
SUBAGENT_TYPES[i + 1],
status,
TASKS[i],
mainAgent1,
null
);
if (status === "working") {
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run(
randomItem(TOOL_NAMES),
subId
);
}
}
const mainAgent2 = `${activeSessionId2}-main`;
stmts.insertAgent.run(
mainAgent2,
activeSessionId2,
"Main Agent",
"main",
null,
"working",
"Investigating payment webhook failures",
null,
null
);
const sub2 = uuidv4();
stmts.insertAgent.run(
sub2,
activeSessionId2,
"Debugger",
"subagent",
"debugger",
"working",
"Tracing webhook request flow",
mainAgent2,
null
);
db.prepare("UPDATE agents SET current_tool = ? WHERE id = ?").run("Grep", sub2);
for (const sid of sessions.slice(2)) {
const mainId = `${sid}-main`;
stmts.insertAgent.run(mainId, sid, "Main Agent", "main", null, "completed", null, null, null);
db.prepare("UPDATE agents SET ended_at = ? WHERE id = ?").run(
minutesAgo(Math.floor(Math.random() * 60)),
mainId
);
const subCount = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < subCount; i++) {
const subId = uuidv4();
const name = randomItem(AGENT_NAMES.slice(1));
stmts.insertAgent.run(
subId,
sid,
name,
"subagent",
randomItem(SUBAGENT_TYPES.slice(1)),
sid === sessions[sessions.length - 1] ? "error" : "completed",
randomItem(TASKS),
mainId,
null
);
db.prepare("UPDATE agents SET ended_at = ? WHERE id = ?").run(
minutesAgo(Math.floor(Math.random() * 60)),
subId
);
}
}
for (const sid of sessions) {
const eventCount = Math.floor(Math.random() * 15) + 5;
const agents = stmts.listAgentsBySession.all(sid);
for (let i = 0; i < eventCount; i++) {
const agent = randomItem(agents);
const eventType = randomItem([
"PreToolUse",
"PostToolUse",
"PreToolUse",
"PostToolUse",
"Notification",
]);
const tool = randomItem(TOOL_NAMES);
const summary =
eventType === "PreToolUse"
? `Using tool: ${tool}`
: eventType === "PostToolUse"
? `Tool completed: ${tool}`
: `Agent ${agent?.name || "unknown"} notification`;
stmts.insertEvent.run(
sid,
agent?.id ?? null,
eventType,
eventType.includes("Tool") ? tool : null,
summary,
JSON.stringify({ tool_name: tool })
);
}
const session = stmts.getSession.get(sid);
if (session && session.status !== "active") {
stmts.insertEvent.run(
sid,
null,
"Stop",
null,
`Session ended: ${session.status}`,
JSON.stringify({ stop_reason: session.status })
);
}
}
});
tx();
}
function main() {
console.log("Seeding database (additive — existing data is preserved)...\n");
if (RESET) {
console.log("--reset: removing existing fixture rows before re-inserting.");
deleteFixtureRows();
}
const fixtureResult = seedFixtures();
if (fixtureResult.inserted.length > 0) {
console.log("Inserted fixtures:");
for (const name of fixtureResult.inserted) console.log(` + ${name}`);
}
if (fixtureResult.skipped.length > 0) {
console.log("Skipped (already present — pass --reset to recreate):");
for (const name of fixtureResult.skipped) console.log(` · ${name}`);
}
if (FULL) {
console.log("");
seedFullDemo();
}
const stats = stmts.stats.get();
console.log("");
console.log(
`Total in DB: ${stats.total_sessions} sessions, ${stats.total_agents} agents, ${stats.total_events} events.`
);
console.log("");
console.log("Test URLs:");
console.log(` Single-agent: /sessions/${FIXTURES.solo.sessionId}`);
console.log(` Waiting (reason): /sessions/${FIXTURES.waiting.sessionId}`);
console.log(` Nested (depth 4): /sessions/${FIXTURES.nested.sessionId}`);
}
main();