import type { Agent } from "agents"; import * as Effect from "effect/Effect"; import { capabilityPolicyUpdate, capabilityReference, capabilitySourceKey, effectiveCapabilityPolicy, type CapabilityMetadata, type CapabilityPolicySnapshot, type CapabilityReference, } from "../shared/capability-policy"; import { AgentFailure, agentValidation } from "./agent-io"; import { diagnosticId, type DiagnosticStore } from "./diagnostics"; export class CapabilityPolicyStore { constructor( private readonly sql: Agent["sql"], private readonly storage: Pick, ) {} initialize() { this .sql`CREATE TABLE IF NOT EXISTS flarebot_capability_policy (singleton INTEGER PRIMARY KEY CHECK(singleton = 1), document TEXT NOT NULL)`; this .sql`INSERT OR IGNORE INTO flarebot_capability_policy VALUES (1, ${JSON.stringify({ revision: 0, defaultPolicy: "ask", sourceOverrides: [] })})`; } read(): CapabilityPolicySnapshot { const [row] = this.sql<{ document: string; }>`SELECT document FROM flarebot_capability_policy WHERE singleton = 1`; return capabilityPolicyUpdate.parse(JSON.parse(row.document)); } // Retiring a source also invalidates editors that have not yet saved its // first override. Read the current document inside the caller's transaction. removeSources(sources: readonly CapabilityReference["source"][]) { const keys = new Set(sources.map(capabilitySourceKey)); return this.storage.transactionSync(() => { const current = this.read(); return this.update({ ...current, sourceOverrides: current.sourceOverrides.filter( (entry) => !keys.has(capabilitySourceKey(entry.source)), ), }); }); } update(value: unknown): CapabilityPolicySnapshot { const input = capabilityPolicyUpdate.parse(value); return this.storage.transactionSync(() => { if (this.read().revision !== input.revision) throw new Error("Capability policy changed. Reload before saving."); const updated = capabilityPolicyUpdate.parse({ ...input, revision: input.revision + 1, }); this .sql`UPDATE flarebot_capability_policy SET document = ${JSON.stringify(updated)} WHERE singleton = 1`; return updated; }); } } export class CapabilityDenied extends AgentFailure { constructor(readonly decision: "ask" | "never" | "unavailable") { super({ message: decision === "ask" ? "Owner approval is required for this capability" : decision === "never" ? "This capability is prohibited by owner policy" : "This capability changed or is unavailable", }); } } // The resolver and operation are trusted server code. The caller supplies only // an identity/fingerprint, never policy, provenance, approval or executable code. export function runCapability( context: { policy: CapabilityPolicyStore; resolve: (reference: CapabilityReference) => CapabilityMetadata | undefined; diagnostics: DiagnosticStore; conversationId?: string; toolCallId?: string; approvedOnce?: boolean; }, value: unknown, operation: (metadata: CapabilityMetadata) => Effect.Effect, ): Effect.Effect { return Effect.gen(function* () { const reference = yield* agentValidation(() => capabilityReference.parse(value), ); const metadata = context.resolve(reference); const configuration = context.policy.read(); const current = metadata?.enabled && metadata.id === reference.id && metadata.fingerprint === reference.fingerprint && metadata.source.kind === reference.source.kind && metadata.source.id === reference.source.id; const effective = current ? effectiveCapabilityPolicy(metadata, configuration) : null; const decision = effective?.policy ?? "unavailable"; context.diagnostics.record({ kind: "approval", timestamp: Date.now(), id: diagnosticId(context.toolCallId), conversationId: diagnosticId(context.conversationId), requestId: null, phase: "transition", status: decision === "allow" || (decision === "ask" && context.approvedOnce === true) ? "succeeded" : decision === "ask" ? "pending" : "skipped", durationMs: null, details: { capabilityId: diagnosticId(reference.id), capabilitySourceId: diagnosticId(reference.source.id), capabilitySourceKind: reference.source.kind, approvalDecision: decision, policyRevision: configuration.revision, policySource: effective?.source, }, }); if ( (decision !== "allow" && !(decision === "ask" && context.approvedOnce === true)) || !metadata ) return yield* Effect.fail( new CapabilityDenied(decision === "allow" ? "unavailable" : decision), ); return yield* operation(metadata); }); }