mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-15 18:56:05 +01:00
feat: add individual agent interaction permissions (#20411)
Add eleven sparse incoming and outgoing restrictions, human CLI/API administration, shared mutation enforcement, and pending-delivery checks. Preserve existing defaults, agent settings, and existing authorization boundaries. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
4158799d1b
commit
5bad074baa
97 files changed
+3590
-433
No files matched your search
@@ -12,6 +12,7 @@ import type { LifecycleEffects } from "./effects.js";
|
||||
export async function updateAgentConfiguration(tx: Db, effects: LifecycleEffects, id: string, data: AgentConfigurationPatch, options?: UpdateAgentOptions, publications: ActivityPublication[] = []) {
|
||||
if (!("nestedIndex" in tx)) throw new AgentLifecycleConflict("Configuration integration requires a database transaction");
|
||||
assertConfigurationFields(data);
|
||||
options = await effects.lockConfigurationInteractions(tx, id, data, options);
|
||||
const [current] = await tx.select().from(agents).where(eq(agents.id, id)).for("no key update");
|
||||
if (!current) return null;
|
||||
if (options?.expectedConfigurationRevision && options.expectedConfigurationRevision !== agentConfigurationRevision(current)) {
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface LifecycleEffects {
|
||||
beforeConnectionEligibilityRestore?(db: Db, companyId: string, agentId: string): Promise<void>;
|
||||
reportFailure(context: LifecycleFailure, error: unknown): void;
|
||||
deleteDependencies(db: Db, companyId: string, agentId: string): Promise<void>;
|
||||
lockConfigurationInteractions(db: Db, id: string, data: AgentConfigurationPatch, options?: UpdateAgentOptions): Promise<UpdateAgentOptions | undefined>;
|
||||
prepareConfiguration(db: Db, current: AgentConfigurationRecord, data: AgentConfigurationPatch, options?: UpdateAgentOptions): Promise<AgentConfigurationPatch>;
|
||||
completeConfiguration(db: Db, before: AgentConfigurationRecord, after: AgentConfigurationRecord, patch: AgentConfigurationPatch, options: UpdateAgentOptions | undefined, publications: ActivityPublication[]): Promise<void>;
|
||||
transaction<T>(db: Db, companyId: string, work: (tx: Db, publications: ActivityPublication[]) => Promise<T>): Promise<T>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { preserveAgentInteractionPermissions } from "../../../lib/agent-interaction-permissions.js";
|
||||
import type { LifecycleEffects } from "./effects.js";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
@@ -43,6 +44,7 @@ export function agentRecords(db: Db, effects: LifecycleEffects) {
|
||||
const createdStatus=status === "pending_approval" ? "pending_approval" : status === "terminated" ? "terminated" : "paused";
|
||||
const values: typeof agents.$inferInsert = {
|
||||
...prepared,
|
||||
...(setup ? { permissions: preserveAgentInteractionPermissions(setup.permissions, prepared.permissions) } : {}),
|
||||
createdByUserId: setup?.createdByUserId ?? options?.createdByUserId ?? null,
|
||||
lifecycleState,
|
||||
lifecycleVersion: setup ? setup.lifecycleVersion + 1 : 1,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ActivityPublication } from "../../types/activity-publication.js";
|
||||
import { AgentLifecycleConflict } from "./domain/policy.js";
|
||||
import { hireApprovalService as approvalRecords, type HireDecisionTarget } from "./adapters/approvals.js";
|
||||
import { agents, companies } from "@paperclipai/db";
|
||||
import { and, asc, desc, eq, gt, inArray, lte, sql } from "drizzle-orm";
|
||||
@@ -43,6 +45,24 @@ export function processAgentLifecycle(db: Db, id: string): Promise<void> {
|
||||
return workers.get(db)?.process(id) ?? Promise.resolve();
|
||||
}
|
||||
|
||||
export function initializeImportedAgentConfigurationInTransaction(tx: Db, effects: LifecycleEffects, publications: ActivityPublication[],
|
||||
id: string, data: Pick<AgentConfigurationPatch, "adapterConfig" | "reportsTo">) {
|
||||
if (!("nestedIndex" in tx)) throw new AgentLifecycleConflict("Imported configuration requires a database transaction");
|
||||
if (Object.keys(data).some(key => key !== "adapterConfig" && key !== "reportsTo")) throw new AgentLifecycleConflict("Only initial imported instructions and reporting links can be initialized");
|
||||
return updateAgentConfiguration(tx, effects, id, data, undefined, publications);
|
||||
}
|
||||
|
||||
export async function requestAgentHireInTransaction(tx: Db, effects: LifecycleEffects, publications: ActivityPublication[],
|
||||
...args: Parameters<ReturnType<typeof agentRecords>["create"]>) {
|
||||
if (!("nestedIndex" in tx)) throw new AgentLifecycleConflict("Imported hires require a database transaction");
|
||||
const records = agentRecords(tx, { ...effects, transaction: async (database, companyId, work) => {
|
||||
const [company] = await database.select({ id: companies.id }).from(companies).where(eq(companies.id, companyId)).for("no key update");
|
||||
if (!company) throw new AgentLifecycleConflict("Import company is unavailable");
|
||||
return work(database, publications);
|
||||
} });
|
||||
return records.create(...args);
|
||||
}
|
||||
|
||||
export function createAgentLifecycle(db: Db, effects: LifecycleEffects) {
|
||||
assertRootDatabase(db);
|
||||
const records = agentRecords(db, effects);
|
||||
|
||||
Reference in new issue
Block a user