Files
PaperClipAI/server/src/services/plugin-agent-runtime-state.ts
T
Devin FoleyandPaperclip a09ef344b3 perf(agents): avoid duplicate configuration reads (#17612)
Reuse one complete company roster for list responses and narrow other-agent eligibility checks to their five consumed fields. Preserve full selected-agent configuration, terminated-manager evidence, current spend, authorization, and fresh reads.

Verified with 170 affected tests, four rejected regression mutations, build and typecheck, exact-head Greptile 5/5, and green required CI.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 07:38:18 -07:00

74 lines
5.6 KiB
TypeScript

import { and, desc, eq } from "drizzle-orm";
import { agents, plugins, pluginCompanySettings, resourceLifecycleEvents, type Db } from "@paperclipai/db";
import type { PluginAgentRuntimeState, PluginAgentRuntimeIdentity } from "@paperclipai/plugin-sdk";
import { agentRecordQueries } from "../lib/agent-records.js";
import { conflict, forbidden } from "../errors.js";
import { agentInstructionsService } from "./agent-instructions.js";
import { pluginAgentConnectionAssignments } from "./plugin-agent-connections.js";
import { companySkillService } from "./company-skills.js";
import { withSkillActor } from "./skill-access.js";
import { readPaperclipSkillSyncPreference } from "@paperclipai/adapter-utils/server-utils";
import { skillVersionSelectionMap } from "./runtime-skill-selections.js";
import { instanceSettingsService } from "./instance-settings.js";
import { agentInstanceSecretService } from "./agent-instance-secrets.js";
async function readState(db: Db, pluginId: string, companyId: string, agentId: string, identityOnly: true): Promise<PluginAgentRuntimeIdentity | null>;
async function readState(db: Db, pluginId: string, companyId: string, agentId: string, identityOnly?: false): Promise<Omit<PluginAgentRuntimeState, "skills" | "connectionAssignments"> | null>;
async function readState(db: Db, pluginId: string, companyId: string, agentId: string, identityOnly = false): Promise<PluginAgentRuntimeIdentity | Omit<PluginAgentRuntimeState, "skills" | "connectionAssignments"> | null> {
return db.transaction(async tx => {
const [agent] = await tx.select().from(agents)
.where(and(eq(agents.companyId, companyId), eq(agents.id, agentId))).for("share");
if (!agent) return null;
const [plugin] = await tx.select().from(plugins).where(eq(plugins.id, pluginId));
const [settings] = await tx.select().from(pluginCompanySettings).where(and(eq(pluginCompanySettings.companyId, companyId), eq(pluginCompanySettings.pluginId, pluginId)));
if (plugin?.status !== "ready" || settings?.enabled === false || !plugin?.manifestJson.capabilities?.includes("agents.lifecycle.manage")
|| !plugin.manifestJson.resourceLifecycle?.includes("agent_runtime") || !agent.lifecycleRequiredPluginIds?.includes(pluginId)) {
throw forbidden("Agent runtime state requires the owning lifecycle plugin");
}
const [latest] = await tx.select({ id: resourceLifecycleEvents.id }).from(resourceLifecycleEvents)
.where(and(eq(resourceLifecycleEvents.companyId, companyId), eq(resourceLifecycleEvents.resourceType, "agent_runtime"), eq(resourceLifecycleEvents.resourceId, agentId)))
.orderBy(desc(resourceLifecycleEvents.id)).limit(1);
if (identityOnly) return {
revision: String(latest?.id ?? 0),
agent: { id: agent.id, companyId: agent.companyId, status: agent.status as PluginAgentRuntimeIdentity["agent"]["status"],
lifecycleState: agent.lifecycleState, lifecycleVersion: agent.lifecycleVersion },
instanceSecrets: await agentInstanceSecretService(tx as unknown as Db).read({ companyId, agentId, pluginId }),
};
const instructions = await agentInstructionsService().getBundleLocation(agent);
const records = agentRecordQueries(tx as unknown as Db);
const companyAgents = await records.listCompanyAgentEligibilityRows(companyId);
return {
revision: String(latest?.id ?? 0), agent: records.normalizeAgentRow(agent, companyAgents) as PluginAgentRuntimeState["agent"],
instructionsRootPath: instructions.rootPath, instructionsEntryFile: instructions.entryFile,
instanceSecrets: await agentInstanceSecretService(tx as unknown as Db).read({ companyId, agentId, pluginId }),
};
});
}
export async function pluginAgentRuntimeIdentity(db: Db, pluginId: string, companyId: string, agentId: string): Promise<PluginAgentRuntimeIdentity | null> {
return readState(db, pluginId, companyId, agentId, true);
}
export async function pluginAgentRuntimeState(db: Db, pluginId: string, companyId: string, agentId: string): Promise<PluginAgentRuntimeState | null> {
for (let attempt = 0; attempt < 3; attempt++) {
const state = await readState(db, pluginId, companyId, agentId);
if (!state) return null;
const preference = readPaperclipSkillSyncPreference(state.agent.adapterConfig);
const settings = await instanceSettingsService(db).getExperimental();
const skills = await withSkillActor({ type: "agent", agentId, companyId, source: "agent_key" }, () =>
companySkillService(db).listRuntimeSkillEntries(companyId, {
selectionConfig: state.agent.adapterConfig, includeLegacy: state.agent.adapterType !== "paperclip_runner", snapshotLatest: true,
versionSelections: skillVersionSelectionMap(preference.desiredSkillEntries, { versionPinsEnabled: settings.enableBetaSkills }),
}));
const connectionAssignments = await pluginAgentConnectionAssignments(db, companyId, agentId, { publicMcp: settings.enablePublicMcp, chatConnectors: settings.enableChatConnectors });
const fallback = !state.instructionsRootPath ? await agentInstructionsService().exportFiles(state.agent, { rejectSymlinks: true }) : null;
const entryFile = fallback?.entryFile ?? state.instructionsEntryFile;
const entryContent = fallback?.files[entryFile];
const current = await readState(db, pluginId, companyId, agentId);
if (!current) return null;
if (current.revision === state.revision) return { ...current, instructionsEntryFile: entryFile, ...(entryContent === undefined ? {} : { instructionsEntryContent: entryContent }), skills, connectionAssignments };
}
throw conflict("Agent runtime changed while resolving synchronization inputs; retry");
}