57dc91585d
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.
114 lines
3.6 KiB
JavaScript
114 lines
3.6 KiB
JavaScript
/**
|
|
* @file Express router for managing agents, providing endpoints to list, retrieve, create, and update agents. It interacts with the database using prepared statements and broadcasts changes to connected WebSocket clients for real-time updates.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { Router } = require("express");
|
|
const { stmts, db } = require("../db");
|
|
const { broadcast } = require("../websocket");
|
|
const { attachAgentCosts } = require("./pricing");
|
|
const { parseSources, sessionIdInSourcesClause } = require("../lib/source-filter");
|
|
|
|
const router = Router();
|
|
|
|
router.get("/", (req, res) => {
|
|
const rawLimit = parseInt(req.query.limit);
|
|
const limit = rawLimit > 0 ? rawLimit : 10000;
|
|
const offset = parseInt(req.query.offset) || 0;
|
|
const status = req.query.status;
|
|
const session_id = req.query.session_id;
|
|
const sources = parseSources(req);
|
|
|
|
let rows;
|
|
if (session_id) {
|
|
// A session belongs to exactly one source, so no extra source filter needed.
|
|
rows = stmts.listAgentsBySession.all(session_id);
|
|
} else if (sources) {
|
|
// Data-scope active: build a dynamic query restricting agents to sessions
|
|
// from the chosen machines. `agents` carries only session_id → subquery.
|
|
const scope = sessionIdInSourcesClause(sources, "session_id");
|
|
const clauses = [scope.clause];
|
|
const params = [...scope.params];
|
|
if (status) {
|
|
clauses.push("status = ?");
|
|
params.push(status);
|
|
}
|
|
rows = db
|
|
.prepare(
|
|
`SELECT * FROM agents WHERE ${clauses.join(" AND ")} ORDER BY started_at DESC LIMIT ? OFFSET ?`
|
|
)
|
|
.all(...params, limit, offset);
|
|
} else if (status) {
|
|
rows = stmts.listAgentsByStatus.all(status, limit, offset);
|
|
} else {
|
|
rows = stmts.listAgents.all(limit, offset);
|
|
}
|
|
|
|
// Attach each agent's OWN cost (from its metadata token buckets) so subagent
|
|
// cards can show their real cost instead of the session total.
|
|
res.json({ agents: attachAgentCosts(rows), limit, offset });
|
|
});
|
|
|
|
router.get("/:id", (req, res) => {
|
|
const agent = stmts.getAgent.get(req.params.id);
|
|
if (!agent) {
|
|
return res.status(404).json({ error: { code: "NOT_FOUND", message: "Agent not found" } });
|
|
}
|
|
res.json({ agent });
|
|
});
|
|
|
|
router.post("/", (req, res) => {
|
|
const { id, session_id, name, type, subagent_type, status, task, parent_agent_id, metadata } =
|
|
req.body;
|
|
if (!id || !session_id || !name) {
|
|
return res.status(400).json({
|
|
error: { code: "INVALID_INPUT", message: "id, session_id, and name are required" },
|
|
});
|
|
}
|
|
|
|
const existing = stmts.getAgent.get(id);
|
|
if (existing) {
|
|
return res.json({ agent: existing, created: false });
|
|
}
|
|
|
|
stmts.insertAgent.run(
|
|
id,
|
|
session_id,
|
|
name,
|
|
type || "main",
|
|
subagent_type || null,
|
|
status || "waiting",
|
|
task || null,
|
|
parent_agent_id || null,
|
|
metadata ? JSON.stringify(metadata) : null
|
|
);
|
|
|
|
const agent = stmts.getAgent.get(id);
|
|
broadcast("agent_created", agent);
|
|
res.status(201).json({ agent, created: true });
|
|
});
|
|
|
|
router.patch("/:id", (req, res) => {
|
|
const { name, status, task, current_tool, ended_at, metadata } = req.body;
|
|
const existing = stmts.getAgent.get(req.params.id);
|
|
if (!existing) {
|
|
return res.status(404).json({ error: { code: "NOT_FOUND", message: "Agent not found" } });
|
|
}
|
|
|
|
stmts.updateAgent.run(
|
|
name || null,
|
|
status || null,
|
|
task || null,
|
|
current_tool !== undefined ? current_tool : existing.current_tool,
|
|
ended_at || null,
|
|
metadata ? JSON.stringify(metadata) : null,
|
|
req.params.id
|
|
);
|
|
|
|
const agent = stmts.getAgent.get(req.params.id);
|
|
broadcast("agent_updated", agent);
|
|
res.json({ agent });
|
|
});
|
|
|
|
module.exports = router;
|