Full-codebase ts-prune + manual audit sweep. Every change below is a pure
deletion (or a 2-line re-wire where a dead function was supposed to be
alive). No scope additions.
Dead wrappers / phantom features
- shouldAutoApproveApproval: zero-value wrapper over shouldAutoApprove.
Four call sites now import shouldAutoApprove from execution-policy
directly.
- evaluateRotation(session, connectorId, thresholdsOverride): the
connectorId arg was never read (underscored), and thresholdsOverride
was only exercised by the test suite — no production caller. Signature
collapses to evaluateRotation(session). DEFAULT_THRESHOLDS /
RotationThresholds exports and their tests go with it.
- installLogRedactor: defensive console wrapper that was never wired
from worker-entry. buildRedactor handles the known threat (env-value
leaks); the pattern-based redactTokens stays for tests and future
composition.
Dead files
- resume-interrupted-tasks.ts: helper function never imported.
- service-graph-orchestration-deps.ts: three factory closures
superseded by the inline closures in create-orchestration-service.
Dead exports
- listConnectorsByAuthKind (shared/connector-catalog)
- getLocalePromptInstruction + whole localization.ts file
- ImportantDocType
- main-side ConnectorAuthKind (duplicated the shared one; not referenced)
- CheckpointEngineService, RunHousekeepingService types
- WorkspaceResolution type
- cmdOrCtrlSymbol
- AgentEventPush, CredentialPayload type aliases
- parseApprovalPayloadJson, parseEnvValue, ProviderCard
- getSOP — buildSOPSection reads the SOPS map directly; the individual
getter had no callers. Deduped to a flat array.
- renderWakeReason in wake-reasons/en.ts: byte-for-byte duplicate of the
one in wake-reasons/index.ts.
Forgotten wiring (not cleanup, but adjacent)
- registerAgentStandupRoute: the GET /api/agents/me/standup endpoint is
advertised in every system prompt and wake-reason doc, but the route
handler was never registered in api-server/index.ts. Agents have been
hitting 404 since Pi Mono. Registered.
Dead DB column
- v44 migration drops runs.context_snapshot_json. Column was added by
v16-v25, never written or read, and v43's deliberate preservation
was an interim measure. Same idempotent table-rebuild pattern as
v41/v43.
Test tree
- Phantom-feature threshold override tests removed (3). The rest of
the rotation suite re-expressed against the real maxRuns=20 floor.
495/495 unit tests pass. typecheck + build clean.
388 lines
15 KiB
TypeScript
388 lines
15 KiB
TypeScript
import log from "electron-log/main.js";
|
|
import { hireRequestSchema } from "@shared/contracts";
|
|
import { getConnectorExecutionReadinessIssue } from "@shared/connector-policy";
|
|
import {
|
|
canManageOrganization,
|
|
canRequestHire,
|
|
getHireNameValidationError,
|
|
getHireRoutingValidationError,
|
|
resolveHireReportsTo,
|
|
} from "../agent-permissions";
|
|
import { resolveApprovalDecision } from "../approval-policy";
|
|
import { shouldAutoApprove } from "../execution-policy";
|
|
import { requireParam, type AgentApiServer } from "./server-core";
|
|
|
|
export function registerAgentRoutes(server: AgentApiServer) {
|
|
const db = server.db;
|
|
const callbacks = server.callbacks;
|
|
const onDomainChanged = server.onDomainChanged;
|
|
server.addRoute("GET", "/api/agents/me", (_req, res, _params, token) => {
|
|
const agent = db.getAgent(token.agentId);
|
|
const chainOfCommand = db.getChainOfCommand(token.agentId);
|
|
const directReports = db.getDirectReports(token.agentId, token.companyId);
|
|
server.json(res, 200, { agent, chainOfCommand, directReports });
|
|
});
|
|
|
|
server.addRoute("GET", "/api/agents/me/standup", (_req, res, _params, token) => {
|
|
const agent = db.getAgent(token.agentId);
|
|
if (!agent || agent.companyId !== token.companyId) {
|
|
server.json(res, 404, { error: "Agent not found" });
|
|
return;
|
|
}
|
|
|
|
const companyAgents = db.listAgentsForStandup(token.companyId);
|
|
const companyTasks = db.listTasksForStandup(token.companyId);
|
|
const companyApprovals = db.listApprovalsForStandup(token.companyId);
|
|
|
|
const myTasks = companyTasks.filter((t) => t.assigneeAgentId === token.agentId);
|
|
const directReports = companyAgents.filter((a) => a.reportsTo === token.agentId);
|
|
const reportIds = new Set(directReports.map((a) => a.id));
|
|
const reportTasks = companyTasks.filter((t) => t.assigneeAgentId && reportIds.has(t.assigneeAgentId));
|
|
|
|
const tasksAwaitingMyReview = reportTasks.filter((t) => t.status === "in_review");
|
|
const blockedReportTasks = reportTasks.filter((t) => t.status === "blocked");
|
|
const myBlockedTasks = myTasks.filter((t) => t.status === "blocked");
|
|
const myInProgressTasks = myTasks.filter((t) => t.status === "in_progress");
|
|
const myTodoTasks = myTasks.filter((t) => t.status === "todo");
|
|
|
|
const allRelevantTaskIds = new Set([...myTasks.map((t) => t.id), ...reportTasks.map((t) => t.id)]);
|
|
const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString();
|
|
const recentComments = db.listRecentTaskComments([...allRelevantTaskIds], twoHoursAgo, 20);
|
|
|
|
const myPendingApprovals = companyApprovals.filter(
|
|
(a) => a.requestedByAgentId === token.agentId && a.state === "pending",
|
|
);
|
|
const myResolvedApprovals = companyApprovals.filter(
|
|
(a) => a.requestedByAgentId === token.agentId && a.state !== "pending",
|
|
).slice(0, 5);
|
|
|
|
const agentNameMap = new Map(companyAgents.map((a) => [a.id, a.name]));
|
|
const taskTitleMap = new Map(companyTasks.map((t) => [t.id, t.title]));
|
|
const socialAccounts = db.listSocialAccounts(token.companyId);
|
|
|
|
server.json(res, 200, {
|
|
agent: { id: agent.id, name: agent.name, role: agent.role, status: agent.status },
|
|
summary: {
|
|
tasksAwaitingReview: tasksAwaitingMyReview.length,
|
|
blockedReportTasks: blockedReportTasks.length,
|
|
myBlockedTasks: myBlockedTasks.length,
|
|
myInProgress: myInProgressTasks.length,
|
|
myTodo: myTodoTasks.length,
|
|
pendingApprovals: myPendingApprovals.length,
|
|
directReports: directReports.length,
|
|
},
|
|
tasksAwaitingReview: tasksAwaitingMyReview.map((t) => ({
|
|
id: t.id,
|
|
title: t.title,
|
|
assignee: agentNameMap.get(t.assigneeAgentId ?? "") ?? "Unknown",
|
|
priority: t.priority,
|
|
})),
|
|
blockedTasks: [...blockedReportTasks, ...myBlockedTasks].map((t) => ({
|
|
id: t.id,
|
|
title: t.title,
|
|
assignee: agentNameMap.get(t.assigneeAgentId ?? "") ?? "Unknown",
|
|
priority: t.priority,
|
|
})),
|
|
recentComments: recentComments.map((c) => ({
|
|
taskId: c.taskId,
|
|
taskTitle: taskTitleMap.get(c.taskId) ?? "",
|
|
author: c.authorName,
|
|
body: c.body.slice(0, 200),
|
|
createdAt: c.createdAt,
|
|
})),
|
|
pendingApprovals: myPendingApprovals.map((a) => ({
|
|
id: a.id,
|
|
type: a.type,
|
|
summary: a.payloadSummary,
|
|
state: a.state,
|
|
})),
|
|
resolvedApprovals: myResolvedApprovals.map((a) => ({
|
|
id: a.id,
|
|
type: a.type,
|
|
summary: a.payloadSummary,
|
|
state: a.state,
|
|
decisionNote: a.decisionNote,
|
|
})),
|
|
reportStatuses: directReports.map((r) => {
|
|
const rTasks = companyTasks.filter((t) => t.assigneeAgentId === r.id);
|
|
return {
|
|
id: r.id,
|
|
name: r.name,
|
|
role: r.role,
|
|
status: r.status,
|
|
tasksInProgress: rTasks.filter((t) => t.status === "in_progress").length,
|
|
tasksInReview: rTasks.filter((t) => t.status === "in_review").length,
|
|
tasksBlocked: rTasks.filter((t) => t.status === "blocked").length,
|
|
tasksDone: rTasks.filter((t) => t.status === "done").length,
|
|
};
|
|
}),
|
|
socialAccounts: socialAccounts.map((a) => ({
|
|
id: a.id,
|
|
platform: a.platform,
|
|
accountName: a.accountName,
|
|
displayName: a.displayName,
|
|
status: a.status,
|
|
requireApproval: a.requireApproval,
|
|
})),
|
|
});
|
|
});
|
|
|
|
server.addRoute("GET", "/api/agents/me/metrics", (_req, res, _params, token) => {
|
|
const companyMetrics = db.getCompanyMetrics(token.companyId, 30);
|
|
const agentMetric = companyMetrics.agentMetrics.find(m => m.agentId === token.agentId);
|
|
if (!agentMetric) {
|
|
server.json(res, 404, { error: "Metrics not found" });
|
|
return;
|
|
}
|
|
server.json(res, 200, agentMetric);
|
|
});
|
|
|
|
server.addRoute("GET", "/api/agents/:agentId/metrics", (_req, res, params, token) => {
|
|
const agentId = requireParam(params, "agentId");
|
|
const agent = db.getAgent(agentId);
|
|
if (!agent || agent.companyId !== token.companyId) {
|
|
server.json(res, 404, { error: "Agent not found" });
|
|
return;
|
|
}
|
|
const companyMetrics = db.getCompanyMetrics(token.companyId, 30);
|
|
const agentMetric = companyMetrics.agentMetrics.find((metric) => metric.agentId === agentId);
|
|
if (!agentMetric) {
|
|
server.json(res, 404, { error: "Metrics not found" });
|
|
return;
|
|
}
|
|
server.json(res, 200, agentMetric);
|
|
});
|
|
|
|
server.addRoute("GET", "/api/agents/:agentId", (_req, res, params, token) => {
|
|
let agent;
|
|
try {
|
|
agent = db.getAgent(requireParam(params, "agentId"));
|
|
} catch (err) {
|
|
log.warn(`[api/agents] getAgent failed:`, err instanceof Error ? err.message : String(err));
|
|
server.json(res, 404, { error: "Agent not found" });
|
|
return;
|
|
}
|
|
if (agent.companyId !== token.companyId) {
|
|
server.json(res, 404, { error: "Agent not found" });
|
|
return;
|
|
}
|
|
const directReports = db.getDirectReports(agent.id, token.companyId);
|
|
const reportsTo = agent.reportsTo;
|
|
const manager = reportsTo
|
|
? (() => {
|
|
try {
|
|
return db.getAgent(reportsTo);
|
|
} catch (error) {
|
|
log.warn(`[api/agents] manager lookup failed for ${reportsTo}: ${error instanceof Error ? error.message : String(error)}`);
|
|
return null;
|
|
}
|
|
})()
|
|
: null;
|
|
server.json(res, 200, {
|
|
...agent,
|
|
manager: manager ? { id: manager.id, name: manager.name, role: manager.role } : null,
|
|
directReports: directReports.map(r => ({ id: r.id, name: r.name, role: r.role, status: r.status })),
|
|
});
|
|
});
|
|
|
|
server.addRoute("GET", "/api/companies/:companyId/agents", (_req, res, params, token) => {
|
|
if (params.companyId !== token.companyId) {
|
|
server.json(res, 403, { error: "Company mismatch" });
|
|
return;
|
|
}
|
|
const agents = db.listAgentsForApi(params.companyId);
|
|
server.json(res, 200, agents);
|
|
});
|
|
|
|
server.addRoute("POST", "/api/companies/:companyId/agents", (_req, res, params, token, body) => {
|
|
const companyId = requireParam(params, "companyId");
|
|
if (companyId !== token.companyId) {
|
|
server.json(res, 403, { error: "Company mismatch" });
|
|
return;
|
|
}
|
|
if (!canRequestHire(db, companyId, token.agentId)) {
|
|
server.json(res, 403, { error: "Current agent is not allowed to request hires" });
|
|
return;
|
|
}
|
|
const reportsToProvided =
|
|
typeof body === "object" &&
|
|
body !== null &&
|
|
Object.prototype.hasOwnProperty.call(body, "reportsTo");
|
|
const parsed = hireRequestSchema.safeParse({
|
|
companyId,
|
|
requestedByAgentId: token.agentId,
|
|
...(typeof body === "object" && body ? body : {}),
|
|
});
|
|
if (!parsed.success) {
|
|
server.json(res, 400, { error: "Invalid hire payload" });
|
|
return;
|
|
}
|
|
const input = parsed.data;
|
|
const resolvedReportsTo = resolveHireReportsTo(
|
|
db,
|
|
companyId,
|
|
token.agentId,
|
|
reportsToProvided ? input.reportsTo : undefined,
|
|
input.department ?? null,
|
|
);
|
|
const nameError = getHireNameValidationError({
|
|
name: input.name,
|
|
role: input.role,
|
|
title: input.title,
|
|
});
|
|
if (nameError) {
|
|
server.json(res, 422, { error: nameError });
|
|
return;
|
|
}
|
|
const routingError = getHireRoutingValidationError(
|
|
db,
|
|
companyId,
|
|
token.agentId,
|
|
resolvedReportsTo,
|
|
input.department ?? null,
|
|
);
|
|
if (routingError) {
|
|
server.json(res, 422, { error: routingError });
|
|
return;
|
|
}
|
|
if (server.companyRefError(res, companyId, [
|
|
{ id: input.requestedByAgentId, table: "agents", label: "requesting agent" },
|
|
{ id: resolvedReportsTo, table: "agents", label: "manager" },
|
|
{ id: input.workspaceId, table: "workspaces", label: "workspace" },
|
|
])) {
|
|
return;
|
|
}
|
|
if (
|
|
resolvedReportsTo &&
|
|
resolvedReportsTo !== token.agentId &&
|
|
!canManageOrganization(db, companyId, token.agentId) &&
|
|
!db.getChainOfCommand(resolvedReportsTo).some((agent) => agent.id === token.agentId)
|
|
) {
|
|
server.json(res, 403, { error: "Requested hire must report to you or a manager in your reporting chain." });
|
|
return;
|
|
}
|
|
const connectorRecord = (() => {
|
|
try {
|
|
return db.getConnector(input.connectorId);
|
|
} catch (err) {
|
|
log.warn(`[api/agents] getConnector failed for ${input.connectorId}:`, err instanceof Error ? err.message : String(err));
|
|
return null;
|
|
}
|
|
})();
|
|
if (!connectorRecord) {
|
|
server.json(res, 422, { error: "The selected connector does not exist." });
|
|
return;
|
|
}
|
|
const connectorIssue = getConnectorExecutionReadinessIssue(connectorRecord);
|
|
if (connectorIssue) {
|
|
server.json(res, 422, { error: connectorIssue });
|
|
return;
|
|
}
|
|
|
|
// Prevent duplicate agents: reject if a non-terminated agent with the same name already exists
|
|
const existingAgents = db.listExistingAgentsForHireCheck(companyId);
|
|
const duplicateByName = existingAgents.find(
|
|
(a) => a.name.toLowerCase() === input.name.toLowerCase(),
|
|
);
|
|
if (duplicateByName) {
|
|
server.json(res, 409, { error: `Agent "${input.name}" already exists in this company (id: ${duplicateByName.id}, role: ${duplicateByName.role}). Use the existing agent instead of creating a duplicate.` });
|
|
return;
|
|
}
|
|
// Also check pending hire approvals with the same name to prevent concurrent duplicate requests
|
|
const pendingHires = db.listPendingHireApprovalPayloads(companyId);
|
|
for (const pending of pendingHires) {
|
|
try {
|
|
const payload = pending.payloadJson ? JSON.parse(pending.payloadJson) as Record<string, unknown> : {};
|
|
if (typeof payload.name === "string" && payload.name.toLowerCase() === input.name.toLowerCase()) {
|
|
server.json(res, 409, { error: `A hire request for "${input.name}" is already pending approval.` });
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
log.warn(
|
|
`[api/agents] invalid pending-hire payloadJson for company ${companyId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const managerLabel = resolvedReportsTo
|
|
? (() => {
|
|
try {
|
|
return db.getAgent(resolvedReportsTo).name;
|
|
} catch (err) {
|
|
log.warn(`[api/agents] getAgent failed for manager ${resolvedReportsTo}:`, err instanceof Error ? err.message : String(err));
|
|
return resolvedReportsTo;
|
|
}
|
|
})()
|
|
: "the company board";
|
|
|
|
let result: { agentId: string; approvalId: string };
|
|
try {
|
|
result = db.createHireRequest({
|
|
companyId,
|
|
requestedByAgentId: input.requestedByAgentId ?? token.agentId,
|
|
name: input.name,
|
|
role: input.role,
|
|
title: input.title,
|
|
department: input.department ?? null,
|
|
reportsTo: resolvedReportsTo,
|
|
connectorId: input.connectorId,
|
|
workspaceId: input.workspaceId ?? null,
|
|
model: input.model ?? null,
|
|
metadataJson: input.metadataJson ?? "{}",
|
|
capabilities: input.capabilities,
|
|
budgetMonthlyUsd: input.budgetMonthlyUsd,
|
|
payloadSummary: `Hire ${input.name} as ${input.role}`,
|
|
impactSummary: input.capabilities || `${input.name} will report to ${managerLabel}.`,
|
|
payloadJson: JSON.stringify({
|
|
name: input.name,
|
|
role: input.role,
|
|
title: input.title,
|
|
requestedByAgentId: input.requestedByAgentId ?? token.agentId,
|
|
requestedConfigurationSnapshot: {
|
|
connectorId: input.connectorId,
|
|
workspaceId: input.workspaceId ?? null,
|
|
model: input.model ?? null,
|
|
metadataJson: input.metadataJson ?? "{}",
|
|
capabilities: input.capabilities,
|
|
budgetMonthlyUsd: input.budgetMonthlyUsd,
|
|
reportsTo: resolvedReportsTo,
|
|
department: input.department ?? null,
|
|
title: input.title,
|
|
},
|
|
}),
|
|
});
|
|
} catch (error) {
|
|
server.json(res, 422, { error: error instanceof Error ? error.message : "Hire could not be created" });
|
|
return;
|
|
}
|
|
|
|
db.addActivity({
|
|
companyId,
|
|
actor: token.agentId,
|
|
action: "agent.hire_requested",
|
|
entityType: "agent",
|
|
entityId: result.agentId,
|
|
detail: input.name,
|
|
});
|
|
|
|
let autoApproved = false;
|
|
if (shouldAutoApprove(db, companyId, "hire_agent")) {
|
|
resolveApprovalDecision({
|
|
db,
|
|
handleApprovalResolved: callbacks.onApprovalResolved,
|
|
companyId,
|
|
approvalId: result.approvalId,
|
|
state: "approved",
|
|
decisionNote: "Auto-approved by company policy",
|
|
detail: `Auto-approved hire: ${input.name}`,
|
|
});
|
|
autoApproved = true;
|
|
log.info(`[auto-approve] Auto-approved hire of ${input.name} via agent API`);
|
|
}
|
|
|
|
onDomainChanged();
|
|
server.json(res, 201, { ...result, autoApproved });
|
|
});
|
|
|
|
}
|