mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-01 15:35:12 +01:00
Reviewed and verified at f1c48c924d.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
100 lines
5.9 KiB
TypeScript
100 lines
5.9 KiB
TypeScript
import { notifyRunDispatch } from "./run-dispatch-notifications.js";
|
|
import { notifyNativeReviewResponse } from "./native-review-response-notifications.js";
|
|
import { isAgentStatusInvokable } from "@paperclipai/shared";
|
|
import { notifyNativeResponsePresentation } from "./native-response-presentation-notifications.js";
|
|
import { notifyChatInboundWakeNotices } from "./chat-inbound-wake-notice-notifications.js";
|
|
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),
|
|
async beforeStatusChange(tx, companyId, agentId, before, after) {
|
|
if (before !== after) {
|
|
await notifyRunDispatch(tx, { companyId });
|
|
await notifyDeliveryWork(tx, DELIVERY_QUEUES.agentTimers);
|
|
}
|
|
if (!isAgentStatusInvokable(before) && isAgentStatusInvokable(after)) {
|
|
await notifyNativeResponsePresentation(tx, { companyId, agentId });
|
|
await notifyNativeReviewResponse(tx, { companyId, agentId });
|
|
await notifyChatInboundWakeNotices(tx, { companyId });
|
|
}
|
|
},
|
|
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);
|
|
}
|