import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import type { Action, Safety } from "../core/types"; export const GUARDRAILS_ACTION_BLOCKED_EVENT = "guardrails:action:blocked"; export const GUARDRAILS_RISK_DETECTED_EVENT = "guardrails:risk:detected"; export const GUARDRAILS_FEATURE_REQUEST_EVENT = "guardrails:feature:request"; export const GUARDRAILS_FEATURE_REGISTER_EVENT = "guardrails:feature:register"; export const GUARDRAILS_ACTION_PROMPTED_EVENT = "guardrails:action:prompted"; export const GUARDRAILS_WORKSPACE_ROOTS_CHANGED_EVENT = "guardrails:workspaceRoots:changed"; export const GUARDRAILS_CONFIG_RELOAD_EVENT = "guardrails:config:reload"; export const GUARDRAILS_YOLO_CHANGED_EVENT = "guardrails:yolo:changed"; export const GUARDRAILS_APPROVAL_BROKER_REGISTER_EVENT = "guardrails:approval-broker:register:v1"; export const GUARDRAILS_APPROVAL_REQUEST_EVENT = "guardrails:approval:request:v1"; export const GUARDRAILS_APPROVAL_REPLY_EVENT = "guardrails:approval:reply:v1"; export const GUARDRAILS_APPROVAL_SESSION_GRANT_EVENT = "guardrails:approval:session-grant:v1"; export type GuardrailsApprovalDecision = | "allow" | "allow-session" | "deny" | "stop"; export interface GuardrailsApprovalBrokerRegisterPayload { version: 1; brokerId: string; } export interface GuardrailsApprovalRequestPayload { version: 1; requestId: string; command: string; reason: string; metadata?: unknown; } export interface GuardrailsApprovalSessionGrantPayload { version: 1; command: string; } export interface GuardrailsApprovalReplyPayload { version: 1; requestId: string; decision: GuardrailsApprovalDecision; reason?: string; } export type GuardrailsFeatureId = | "policies" | "permissionGate" | "pathAccess" | "workspaceRoots"; export interface GuardrailsEventBase { source: "guardrails"; feature: GuardrailsFeatureId; timestamp: string; } export interface GuardrailsYoloChangedPayload { source: "guardrails"; timestamp: string; enabled: boolean; } export interface GuardrailsFeatureRequestPayload { source: "guardrails"; timestamp: string; } export interface GuardrailsFeatureRegisterPayload { source: "guardrails"; timestamp: string; feature: { id: GuardrailsFeatureId; }; } export type GuardrailsBlockSource = | "policy" | "permission" | "user" | "user-stop" | "nonInteractive"; export type GuardrailsActionBlockedPayload = GuardrailsEventBase & { action: Action; reason: string; block: { source: GuardrailsBlockSource; metadata?: TMeta; }; context?: { toolName?: string; input?: Record; }; }; export type GuardrailsActionPromptedPayload = GuardrailsEventBase & { action: Action; reason: string; prompt: { /** What kind of prompt was shown */ kind: "confirmation" | "permission"; /** The feature-specific metadata about the risk */ metadata?: TMeta; }; context?: { toolName?: string; input?: Record; }; }; export type GuardrailsRiskDetectedPayload = GuardrailsEventBase & { risk: Safety & { kind: "dangerous" }; context?: { toolName?: string; input?: Record; }; }; function timestamp(): string { return new Date().toISOString(); } export function emitYoloChanged(pi: ExtensionAPI, enabled: boolean): void { pi.events.emit(GUARDRAILS_YOLO_CHANGED_EVENT, { source: "guardrails", timestamp: timestamp(), enabled, } satisfies GuardrailsYoloChangedPayload); } export function createFeatureRequestPayload(): GuardrailsFeatureRequestPayload { return { source: "guardrails", timestamp: timestamp(), }; } export function createFeatureRegisterPayload( feature: GuardrailsFeatureId, ): GuardrailsFeatureRegisterPayload { return { source: "guardrails", timestamp: timestamp(), feature: { id: feature }, }; } export function emitActionBlocked( pi: ExtensionAPI, event: Omit, "source" | "timestamp">, ): void { pi.events.emit(GUARDRAILS_ACTION_BLOCKED_EVENT, { source: "guardrails", timestamp: timestamp(), ...event, }); } export function emitRiskDetected( pi: ExtensionAPI, event: Omit, "source" | "timestamp">, ): void { pi.events.emit(GUARDRAILS_RISK_DETECTED_EVENT, { source: "guardrails", timestamp: timestamp(), ...event, }); } export interface GuardrailsWorkspaceRootsChangedPayload { source: "guardrails"; timestamp: string; /** Absolute directory paths the agent may access as workspace roots. */ roots: string[]; } export function emitWorkspaceRootsChanged( pi: ExtensionAPI, roots: string[], ): void { pi.events.emit(GUARDRAILS_WORKSPACE_ROOTS_CHANGED_EVENT, { source: "guardrails", timestamp: timestamp(), roots, } satisfies GuardrailsWorkspaceRootsChangedPayload); } /** * Ask other guardrails extensions to reload their config from disk, after a * cross-feature edit (each extension entrypoint has its own loader instance). */ export function emitConfigReload(pi: ExtensionAPI): void { pi.events.emit(GUARDRAILS_CONFIG_RELOAD_EVENT, { source: "guardrails", timestamp: timestamp(), }); } export function emitActionPrompted( pi: ExtensionAPI, event: Omit, "source" | "timestamp">, ): void { pi.events.emit(GUARDRAILS_ACTION_PROMPTED_EVENT, { source: "guardrails", timestamp: timestamp(), ...event, }); }