import { logger } from "../middleware/logger.js"; import { companies, type Db } from "@paperclipai/db"; import { eq } from "drizzle-orm"; import * as lifecycle from "../modules/agent-lifecycle/index.js"; import { withAccountingTransaction } from "./accounting-transaction.js"; import { budgetServiceInTransaction, policyBlocks, type BudgetServiceHooks } from "./budgets.js"; import { recordAgentStatusEvent, recordResourceCreationEvent } from "./resource-lifecycle-events.js"; import { clearPrimaryAgent } from "./primary-agent.js"; import { deleteAgentDependencies } from "./agent-deletion.js"; import { prepareAgentConfiguration, completeAgentConfiguration } from "./agent-configuration.js"; import { prepareAgentHire, initializeAgentHire, prepareAgentHireApproval, completeAgentHireApproval } from "./agent-hiring.js"; import { DELIVERY_QUEUES, notifyDeliveryWork } from "./delivery-work-notifications.js"; import { fenceActiveCompanyAgentStopsInTransaction as fenceActiveCompanyStops } from "../modules/agent-lifecycle/index.js"; import { applyAgentPolicyHoldsInTransaction } from "../modules/agent-lifecycle/index.js"; import { trackIdleWork } from "./task-admission.js"; import { environmentService } from "./environments.js"; import { instanceSettingsService } from "./instance-settings.js"; import { pluginEnvironmentRequiresAgent } from "./plugin-environment-driver.js"; export { scheduleAgentLifecycle, processAgentLifecycle } from "../modules/agent-lifecycle/index.js"; export type { LifecycleDriver, LifecycleAgent } from "../modules/agent-lifecycle/index.js"; export function createAgentLifecycleEffects(_hooks: BudgetServiceHooks = {}): lifecycle.LifecycleEffects { return { async setupRequiresPreparation(db, companyId, environmentId) { const id = environmentId ?? (await instanceSettingsService(db).get()).defaultEnvironmentId; const environment = id ? await environmentService(db).getById(id) : null; if (!environment || !await pluginEnvironmentRequiresAgent(db, environment)) return false; const [company] = await db.select({ requiresApproval: companies.requireBoardApprovalForNewAgents }).from(companies).where(eq(companies.id, companyId)); if (!company || company.requiresApproval) return false; const bound = await environmentService(db).listBoundCompanyIds(environment.id); if (bound.length && !bound.includes(companyId)) throw new lifecycle.AgentLifecycleConflict("The environment belongs to another company"); return true; }, notifyWork: tx => notifyDeliveryWork(tx, DELIVERY_QUEUES.agentLifecycle), reportFailure(context, error) { const code = lifecycle.lifecycleFailureCode(error); logger.warn({ ...context, failureCode: code ?? "unclassified" }, "Agent lifecycle operation failed"); }, transaction: withAccountingTransaction, deleteDependencies: deleteAgentDependencies, policyBlocks, setAgentBudget: (db, publications, companyId, agentId, amount, userId, isActive) => budgetServiceInTransaction(db, publications).upsertPolicy(companyId, { scopeType: "agent", scopeId: agentId, amount, isActive, windowKind: "calendar_month_utc", }, userId), recordCreation: (db, companyId, agentId) => recordResourceCreationEvent(db, companyId, "agent", agentId), recordStatus: recordAgentStatusEvent, clearPrimary: clearPrimaryAgent, prepareConfiguration: prepareAgentConfiguration, completeConfiguration: completeAgentConfiguration, prepareHire: prepareAgentHire, initializeHire: initializeAgentHire, prepareHireApproval: prepareAgentHireApproval, completeHireApproval: completeAgentHireApproval, }; } export function createAgentLifecycle(db: Db, hooks: BudgetServiceHooks = {}) { return lifecycle.createAgentLifecycle(db, createAgentLifecycleEffects(hooks)); } function trackedDriver(driver: lifecycle.LifecycleDriver): lifecycle.LifecycleDriver { return { ...driver, runHost: agent => trackIdleWork(driver.runHost(agent)), runPlugin: (agent, pluginId) => trackIdleWork(driver.runPlugin(agent, pluginId)) }; } export function configureAgentLifecycle(db: Db, driver: lifecycle.LifecycleDriver, canRun = () => true) { return lifecycle.configureAgentLifecycle(db, createAgentLifecycleEffects(), trackedDriver(driver), canRun); } export function startAgentLifecycle(db: Db, driver: lifecycle.LifecycleDriver, canRun: () => boolean) { return lifecycle.startAgentLifecycle(db, createAgentLifecycleEffects(), trackedDriver(driver), canRun); } /** Budget evaluation owns the transaction and supplies the aggregate agent decision. */ export function applyAgentBudgetHoldInTransaction(tx: Db, companyId: string, agentId: string, blocked: boolean) { return applyAgentPolicyHoldsInTransaction(tx, createAgentLifecycleEffects(), companyId, agentId, { budget: blocked }); } export function fenceActiveCompanyAgentStopsInTransaction(tx: Db, companyId: string, status: "paused" | "archived") { return fenceActiveCompanyStops(tx, createAgentLifecycleEffects(), companyId, status); }