66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
/**
|
|
* Decide which internal tools (`hire_agent`, `file_approval`) to attach
|
|
* to a given run's agent (C4 part 3/5).
|
|
*
|
|
* The selector runs on main at dispatch time — the worker never
|
|
* re-derives this. Rule set, in order:
|
|
*
|
|
* - An agent that `canRequestHire` (CEO, HR, dept lead with reports,
|
|
* anyone with org-management authority) receives `hire_agent`. A
|
|
* manager hiring an IC is a legitimate flow.
|
|
* - An agent that `canFileCeoStrategy` (genuine CEO: CEO-equivalent
|
|
* role + `reportsTo === null`) receives `file_approval` after the
|
|
* initial team-building phase has produced at least one hire.
|
|
*
|
|
* The two predicates are intentionally orthogonal: a VP with hiring
|
|
* authority receives hiring scope, while CEO-strategy approval remains
|
|
* pinned to the CEO role and the current org phase.
|
|
*
|
|
* No hedging, no sentinel values: if the DB lookup throws the caller
|
|
* is already in a broken state and should not silently fall through
|
|
* to an empty tool set.
|
|
*/
|
|
|
|
import type { AppDatabase } from "@main/database";
|
|
import type { AgentRecord } from "@shared/types";
|
|
import { canFileCeoStrategy, canRequestHire } from "@main/agent-permissions";
|
|
|
|
export type InternalToolName = "hire_agent" | "file_approval";
|
|
|
|
export function resolveInternalToolsForAgent(
|
|
db: AppDatabase,
|
|
agent: AgentRecord,
|
|
): InternalToolName[] {
|
|
const tools: InternalToolName[] = [];
|
|
if (canRequestHire(db, agent.companyId, agent.id)) {
|
|
tools.push("hire_agent");
|
|
}
|
|
if (canFileCeoStrategy(db, agent.companyId, agent.id) && !isInitialTeamBuildingPhase(db, agent)) {
|
|
tools.push("file_approval");
|
|
}
|
|
return tools;
|
|
}
|
|
|
|
function isInitialTeamBuildingPhase(db: AppDatabase, agent: AgentRecord): boolean {
|
|
if (!canFileCeoStrategy(db, agent.companyId, agent.id)) return false;
|
|
|
|
const directReports = db.agents.getDirectReports(agent.id, agent.companyId);
|
|
const activeReports = directReports.filter(
|
|
(report) => report.status !== "pending_approval" && report.status !== "terminated",
|
|
);
|
|
const pendingReportRows = directReports.filter((report) => report.status === "pending_approval");
|
|
const pendingHireApprovals = db.approvalsMod
|
|
.listCompanyApprovalsByType(agent.companyId, "hire_agent")
|
|
.filter((approval) => approval.state === "pending");
|
|
const pendingStrategyApprovals = db.approvalsMod
|
|
.listCompanyApprovalsByType(agent.companyId, "approve_ceo_strategy")
|
|
.filter((approval) => approval.state === "pending");
|
|
|
|
return (
|
|
activeReports.length === 0 &&
|
|
pendingReportRows.length === 0 &&
|
|
pendingHireApprovals.length === 0 &&
|
|
pendingStrategyApprovals.length === 0
|
|
);
|
|
}
|