Files
AgentCompany/src/main/database/company-settings.ts
T
ZacharyZhang-NY 71791825ce chore(lint): remove unused vars and imports
- agents-crud: drop unused roleLevel local + inferRoleLevel import
- agents-hierarchy: prefix unused builder params with _
- company-settings: drop InboxItem import, prefix unused builder params
- worker-message-handler: drop unused WorkerServiceDependencies import
2026-04-17 01:44:40 -04:00

477 lines
19 KiB
TypeScript

import type { DatabaseSync } from "node:sqlite";
import type {
CompanyMetrics,
AgentMetrics,
ProfileSnapshot,
RunStatus,
StandupReport,
} from "@shared/types";
import {
BOARD_READER_ID,
type ReadAll,
type Transaction,
} from "./helpers";
export function buildCompanySettingsMethods(
getDb: () => DatabaseSync,
readAll: ReadAll,
_transaction: Transaction,
_deps: {
listSnapshot: (options: { companyId?: string | null }) => ProfileSnapshot;
},
) {
return {
/**
* Returns the raw inputs for the GET /api/companies/:id/dashboard
* endpoint: agent statuses, task statuses, and the last 20 activity rows.
* Route code aggregates counts by status on top of this raw data.
*/
getDashboardSnapshot(companyId: string): {
agents: Array<{ id: string; status: string }>;
tasks: Array<{ id: string; status: string }>;
recentActivity: Array<{
id: string;
companyId: string;
actor: string;
action: string;
entityType: string;
entityId: string;
detail: string | null;
createdAt: string;
}>;
} {
return {
agents: readAll<{ id: string; status: string }>(
"select id, status from agents where company_id = ?",
companyId,
),
tasks: readAll<{ id: string; status: string }>(
"select id, status from tasks where company_id = ?",
companyId,
),
recentActivity: readAll<{
id: string; companyId: string; actor: string; action: string;
entityType: string; entityId: string; detail: string | null; createdAt: string;
}>(
"select id, company_id as companyId, actor, action, entity_type as entityType, entity_id as entityId, detail, created_at as createdAt from activity where company_id = ? order by created_at desc limit 20",
companyId,
),
};
},
getCompanyMetrics(companyId: string, days: number = 30): CompanyMetrics {
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
const db = getDb();
const agentRows = readAll<{
id: string; name: string; status: string;
}>(
`select id, name, status from agents where company_id = ? order by created_at asc`,
companyId,
);
const activeAgentRows = agentRows.filter(
(a) => a.status !== "pending_approval" && a.status !== "terminated",
);
const totalAgents = activeAgentRows.length;
const activeAgents = agentRows.filter(
(a) => a.status === "active" || a.status === "running",
).length;
const totalTasks = (db.prepare(
"select count(*) as c from tasks where company_id = ?",
).get(companyId) as { c: number }).c;
const completedTasks = (db.prepare(
"select count(*) as c from tasks where company_id = ? and status = 'done'",
).get(companyId) as { c: number }).c;
const blockedTasks = (db.prepare(
"select count(*) as c from tasks where company_id = ? and status = 'blocked'",
).get(companyId) as { c: number }).c;
const recentRunStats = db.prepare(
`select
count(*) as totalRuns,
sum(case when status = 'succeeded' then 1 else 0 end) as successful,
sum(case when status = 'failed' or status = 'timed_out' then 1 else 0 end) as failed
from runs where company_id = ? and created_at > ?`,
).get(companyId, cutoff) as { totalRuns: number; successful: number | null; failed: number | null };
const totalRuns = recentRunStats.totalRuns ?? 0;
const successfulRuns = recentRunStats.successful ?? 0;
const failedRuns = recentRunStats.failed ?? 0;
const totalCost = (db.prepare(
"select coalesce(sum(amount_usd), 0) as s from cost_entries where company_id = ?",
).get(companyId) as { s: number }).s;
const recentTasksDone = (db.prepare(
"select count(*) as c from tasks where company_id = ? and status = 'done' and updated_at > ?",
).get(companyId, cutoff) as { c: number }).c;
const taskAggRows = readAll<{
agentId: string;
done: number;
assigned: number;
cancelled: number;
}>(
`select assignee_agent_id as agentId,
sum(case when status = 'done' then 1 else 0 end) as done,
sum(case when status != 'done' and status != 'cancelled' then 1 else 0 end) as assigned,
sum(case when status = 'cancelled' then 1 else 0 end) as cancelled
from tasks
where company_id = ? and assignee_agent_id is not null
group by assignee_agent_id`,
companyId,
);
const taskAggMap = new Map(taskAggRows.map((row) => [row.agentId, row]));
const runRecentAggRows = readAll<{
agentId: string;
total: number;
succeeded: number;
failed: number;
}>(
`select agent_id as agentId,
count(*) as total,
sum(case when status = 'succeeded' then 1 else 0 end) as succeeded,
sum(case when status = 'failed' or status = 'timed_out' then 1 else 0 end) as failed
from runs
where company_id = ? and created_at > ?
group by agent_id`,
companyId,
cutoff,
);
const runRecentAggMap = new Map(runRecentAggRows.map((row) => [row.agentId, row]));
const runDurationRows = readAll<{
agentId: string;
avgDurationSec: number | null;
lastActiveAt: string | null;
}>(
`select agent_id as agentId,
avg((julianday(finished_at) - julianday(started_at)) * 86400.0) as avgDurationSec,
max(created_at) as lastActiveAt
from runs
where company_id = ? and started_at is not null and finished_at is not null
group by agent_id`,
companyId,
);
const runDurationMap = new Map(runDurationRows.map((row) => [row.agentId, row]));
const lastActiveRows = readAll<{ agentId: string; lastActiveAt: string }>(
`select agent_id as agentId, max(created_at) as lastActiveAt
from runs where company_id = ? group by agent_id`,
companyId,
);
const lastActiveMap = new Map(lastActiveRows.map((row) => [row.agentId, row.lastActiveAt]));
const costAggRows = readAll<{ agentId: string; totalCost: number }>(
`select agent_id as agentId, coalesce(sum(amount_usd), 0) as totalCost
from cost_entries where company_id = ? and agent_id is not null
group by agent_id`,
companyId,
);
const costAggMap = new Map(costAggRows.map((row) => [row.agentId, row.totalCost]));
const reviewCycleRows = readAll<{ actor: string; cycles: number }>(
`select actor, count(*) as cycles
from activity
where company_id = ?
and action in ('task.status_changed', 'task.updated', 'task.auto_assigned')
group by actor`,
companyId,
);
const reviewCycleMap = new Map(reviewCycleRows.map((row) => [row.actor, row.cycles]));
const agentMetrics: AgentMetrics[] = activeAgentRows.map((agent) => {
const taskAgg = taskAggMap.get(agent.id);
const runRecent = runRecentAggMap.get(agent.id);
const runDur = runDurationMap.get(agent.id);
const succeeded = runRecent?.succeeded ?? 0;
const total = runRecent?.total ?? 0;
return {
agentId: agent.id,
agentName: agent.name,
tasksCompleted: taskAgg?.done ?? 0,
tasksAssigned: taskAgg?.assigned ?? 0,
tasksFailed: taskAgg?.cancelled ?? 0,
successRate: total > 0 ? Math.round((succeeded / total) * 100) : 0,
avgRunDurationSec: Math.round(runDur?.avgDurationSec ?? 0),
totalCostUsd: costAggMap.get(agent.id) ?? 0,
runsTotal: total,
runsSucceeded: succeeded,
runsFailed: runRecent?.failed ?? 0,
reviewCycles: reviewCycleMap.get(agent.id) ?? 0,
lastActiveAt: lastActiveMap.get(agent.id) ?? null,
};
});
return {
totalAgents,
activeAgents,
totalTasks,
completedTasks,
blockedTasks,
totalRuns,
successfulRuns,
failedRuns,
overallSuccessRate: totalRuns > 0 ? Math.round((successfulRuns / totalRuns) * 100) : 0,
totalCostUsd: totalCost,
avgCostPerTask: completedTasks > 0 ? totalCost / completedTasks : 0,
throughputTasksPerDay: days > 0 ? recentTasksDone / days : 0,
agentMetrics,
};
},
getStandupReport(companyId: string): StandupReport {
const db = getDb();
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
type AgentRow = {
id: string; name: string; role: string; status: StandupReport["agentReports"][number]["status"];
budgetMonthlyUsd: number; spentMonthlyUsd: number;
};
const agents = readAll<AgentRow>(
`select id, name, role, status,
budget_monthly_usd as budgetMonthlyUsd,
spent_monthly_usd as spentMonthlyUsd
from agents
where company_id = ? and status != 'terminated' and status != 'pending_approval'
order by created_at asc`,
companyId,
);
const recentCompletedTasksCount = (db.prepare(
"select count(*) as c from tasks where company_id = ? and status = 'done' and updated_at > ?",
).get(companyId, oneDayAgo) as { c: number }).c;
const recentRunStats = db.prepare(
`select
count(*) as total,
sum(case when status = 'succeeded' then 1 else 0 end) as succeeded,
sum(case when status = 'failed' or status = 'timed_out' then 1 else 0 end) as failed
from runs where company_id = ? and created_at > ?`,
).get(companyId, oneDayAgo) as { total: number; succeeded: number | null; failed: number | null };
const recentRunsTotal = recentRunStats.total ?? 0;
const successfulRuns = recentRunStats.succeeded ?? 0;
const failedRuns = recentRunStats.failed ?? 0;
const blockedTaskRows = readAll<{ id: string; title: string; assigneeAgentId: string | null }>(
`select id, title, assignee_agent_id as assigneeAgentId
from tasks where company_id = ? and status = 'blocked'`,
companyId,
);
const pendingApprovals = (db.prepare(
"select count(*) as c from approvals where company_id = ? and state = 'pending'",
).get(companyId) as { c: number }).c;
const activeSprint = db.prepare(
`select name, velocity_points as velocityPoints, completed_points as completedPoints
from sprints where company_id = ? and status = 'active' order by start_date desc limit 1`,
).get(companyId) as { name: string; velocityPoints: number; completedPoints: number } | undefined;
const urgentUnread = (db.prepare(
`select count(*) as c
from agent_messages messages
left join agent_message_reads reads
on reads.message_id = messages.id and reads.agent_id = '__board__'
where messages.company_id = ?
and reads.read_at is null
and (messages.priority = 'urgent' or messages.channel = 'incident')`,
).get(companyId) as { c: number }).c;
const tasksByAgentRows = readAll<{
agentId: string; inProgress: number; blocked: number; completed24h: number;
}>(
`select assignee_agent_id as agentId,
sum(case when status = 'in_progress' then 1 else 0 end) as inProgress,
sum(case when status = 'blocked' then 1 else 0 end) as blocked,
sum(case when status = 'done' and updated_at > ? then 1 else 0 end) as completed24h
from tasks
where company_id = ? and assignee_agent_id is not null
group by assignee_agent_id`,
oneDayAgo,
companyId,
);
const tasksByAgent = new Map(tasksByAgentRows.map((row) => [row.agentId, row]));
const recentRunsByAgent = readAll<{
agentId: string; runCount: number; lastStatus: string | null; lastAt: string | null;
}>(
`select agent_id as agentId,
count(*) as runCount,
(select status from runs r2 where r2.agent_id = runs.agent_id and r2.company_id = runs.company_id and r2.created_at > ? order by created_at desc limit 1) as lastStatus,
max(created_at) as lastAt
from runs
where company_id = ? and created_at > ?
group by agent_id`,
oneDayAgo,
companyId,
oneDayAgo,
);
const runsByAgent = new Map(recentRunsByAgent.map((row) => [row.agentId, row]));
const costByAgentRows = readAll<{ agentId: string; cost24h: number }>(
`select agent_id as agentId, coalesce(sum(amount_usd), 0) as cost24h
from cost_entries
where company_id = ? and agent_id is not null and created_at > ?
group by agent_id`,
companyId,
oneDayAgo,
);
const costByAgent = new Map(costByAgentRows.map((row) => [row.agentId, row.cost24h]));
const reviewByAgentRows = readAll<{ actor: string; cycles: number }>(
`select actor, count(*) as cycles
from activity
where company_id = ? and created_at > ?
and action in ('task.status_changed', 'task.updated', 'task.auto_assigned')
group by actor`,
companyId,
oneDayAgo,
);
const reviewByAgent = new Map(reviewByAgentRows.map((row) => [row.actor, row.cycles]));
const agentNameMap = new Map(agents.map((a) => [a.id, a.name]));
const companyHighlights: string[] = [];
const blockers: string[] = [];
if (recentCompletedTasksCount > 0) {
companyHighlights.push(`${recentCompletedTasksCount} task(s) completed in last 24h`);
}
if (recentRunsTotal > 0) {
companyHighlights.push(
`${recentRunsTotal} runs (${successfulRuns} succeeded, ${failedRuns} failed)`,
);
}
for (const task of blockedTaskRows) {
const assigneeName = task.assigneeAgentId ? agentNameMap.get(task.assigneeAgentId) ?? null : null;
blockers.push(`${task.title} — blocked (${assigneeName ?? "unassigned"})`);
}
if (activeSprint) {
const pct = activeSprint.velocityPoints > 0
? Math.round((activeSprint.completedPoints / activeSprint.velocityPoints) * 100)
: 0;
companyHighlights.push(
`Sprint "${activeSprint.name}": ${activeSprint.completedPoints}/${activeSprint.velocityPoints} pts (${pct}%)`,
);
}
if (urgentUnread > 0) {
blockers.push(`${urgentUnread} urgent unread message(s) need attention`);
}
const totalBudget = agents.reduce((sum, a) => sum + a.budgetMonthlyUsd, 0);
const totalSpent = agents.reduce((sum, a) => sum + a.spentMonthlyUsd, 0);
const budgetUtilization = totalBudget > 0 ? Math.round((totalSpent / totalBudget) * 100) : 0;
const agentReports = agents.map((agent) => {
const taskAgg = tasksByAgent.get(agent.id);
const runAgg = runsByAgent.get(agent.id);
const completed24h = taskAgg?.completed24h ?? 0;
const runs24h = runAgg?.runCount ?? 0;
const reviewedTasks = reviewByAgent.get(agent.id) ?? 0;
const highlights: string[] = [];
if (completed24h > 0) highlights.push(`Completed ${completed24h} task(s)`);
if (runs24h > 0) highlights.push(`${runs24h} run(s) in last 24h`);
if (reviewedTasks > 0) highlights.push(`${reviewedTasks} status update(s)`);
return {
agentId: agent.id,
agentName: agent.name,
role: agent.role,
status: agent.status,
tasksInProgress: taskAgg?.inProgress ?? 0,
tasksCompleted24h: completed24h,
tasksBlocked: taskAgg?.blocked ?? 0,
lastRunStatus: (runAgg?.lastStatus ?? null) as RunStatus | null,
lastRunAt: runAgg?.lastAt ?? null,
costLast24h: costByAgent.get(agent.id) ?? 0,
highlights,
};
});
const summaryParts: string[] = [];
if (agents.length > 0) summaryParts.push(`${agents.length} active agents`);
summaryParts.push(`${recentCompletedTasksCount} tasks done`);
if (blockedTaskRows.length > 0) summaryParts.push(`${blockedTaskRows.length} blocked`);
if (pendingApprovals > 0) summaryParts.push(`${pendingApprovals} pending approvals`);
return {
companyId,
generatedAt: new Date().toISOString(),
summary: summaryParts.join(" · "),
agentReports,
companyHighlights,
blockers,
pendingApprovals,
budgetUtilization,
};
},
getSidebarBadges(companyId: string): {
pendingApprovals: number;
activeRuns: number;
failedRuns: number;
todoTasks: number;
budgetWarnings: number;
unreadMessages: number;
} {
const pendingApprovals =
(
getDb()
.prepare(
"select count(*) as c from approvals where company_id = ? and state = 'pending'",
)
.get(companyId) as { c: number }
)?.c ?? 0;
const activeRuns =
(
getDb()
.prepare(
"select count(*) as c from runs where company_id = ? and status in ('queued', 'running')",
)
.get(companyId) as { c: number }
)?.c ?? 0;
const failedRuns =
(
getDb()
.prepare(
"select count(*) as c from runs where company_id = ? and status in ('failed', 'timed_out', 'interrupted')",
)
.get(companyId) as { c: number }
)?.c ?? 0;
const todoTasks =
(
getDb()
.prepare(
"select count(*) as c from tasks where company_id = ? and status in ('todo', 'in_progress', 'in_review')",
)
.get(companyId) as { c: number }
)?.c ?? 0;
const budgetWarnings =
(
getDb()
.prepare(
"select count(*) as c from agents where company_id = ? and budget_monthly_usd > 0 and (spent_monthly_usd * 1.0 / budget_monthly_usd) >= 0.8",
)
.get(companyId) as { c: number }
)?.c ?? 0;
const unreadMessages =
(
getDb()
.prepare(
`select count(*) as c
from agent_messages messages
left join agent_message_reads reads
on reads.message_id = messages.id
and reads.agent_id = ?
where messages.company_id = ?
and reads.read_at is null
and (messages.channel = 'company' or messages.channel = 'incident' or messages.priority = 'urgent')`,
)
.get(BOARD_READER_ID, companyId) as { c: number }
)?.c ?? 0;
return { pendingApprovals, activeRuns, failedRuns, todoTasks, budgetWarnings, unreadMessages };
},
};
}