import type { IntegrationErrorCode, Judge, Questions } from "pi-typesafe"; import type { ActionGuardConfig, ArmingRule, CommandRule, PathRule, SecurityConfig, SlopGuardConfig } from "./config.js"; export type Level = "allow" | "warn" | "confirm" | "deny"; export type Severity = "destructive" | "risky" | "sensitive" | "deny"; export interface PatternHit { id: string; severity: Severity; /** Short human label; never contains the matched text. */ label: string; /** For user-defined confirm rules: dialog prompts the user, hold uses steer semantics. */ action?: "dialog" | "hold"; /** Optional user-defined message, shown instead of the derived label. */ message?: string; } export interface ActionInput { tool: string; input: Record; cwd: string; /** Latest user request, used to judge whether the action is on task. */ task?: string | undefined; /** Prior conversation clarifies scope, but never grants approval for a held action. */ context?: readonly TaskMessage[] | undefined; /** The agent's own words in the message that makes this call (or its latest text under this prompt). Explains the step; never authorizes it. */ plan?: string | undefined; } export interface TaskMessage { role: "user" | "assistant"; text: string; } /** Redacted, truncated view of a tool call. This object is what leaves the machine. */ export interface ActionSummary { tool: string; command?: string; path?: string; location?: "inside_project" | "outside_project"; exists?: boolean; bytes?: number; excerpt?: string; editCount?: number; edits?: Array<{ oldText: string; newText: string; }>; input?: string; /** Present when part of the command is data (a heredoc body, a quoted message), so a destructive string inside it is payload. */ dataText?: string; } export type ScopeLabel = "expected_step" | "plausible_side_step" | "unrelated" | "unclear"; export interface Judgment { irreversible: number; offTask: number; scope: ScopeLabel; scopeConfidence: number; /** P(the latest user message approves this exact action); only asked when a previously held call is retried. */ approved?: number; /** P(the latest user message regrets an allowed call of the previous turn); asked once per prompt, on its first action request. */ regretted?: number; /** The id of the regretted previous action when several were offered. */ regretTarget?: string; securityRisk?: number; /** P(the action changes files, state, or external systems). Off-task alone holds only actions that can change something. */ mutates?: number; /** P(the action does something materially different from `plan`); only asked when the agent said something before the call. */ intentMismatch?: number; /** P(the effect is visible outside the working tree: commit, push, merge, publish, message, install, launched process); commands only. */ visible?: number; model: string; elapsedMs: number; } /** One probability per slop symptom; the steer names the symptoms above the threshold. */ export interface SlopJudgment { stub: number; comments: number; dead: number; hedging: number; } export type SlopSymptom = keyof SlopJudgment; export declare const SLOP_SYMPTOMS: readonly SlopSymptom[]; /** A call the guard allowed in the previous turn, as the regret question sees it: redacted summary fields only. */ export interface PreviousAction { id: string; tool: string; command?: string; path?: string; } export interface Verdict { level: Level; source: "skipped" | "read-only" | "pattern" | "typesafe" | "error"; summary: ActionSummary; patterns: PatternHit[]; /** Human-readable reasons without secrets or full commands. */ reasons: string[]; judgment?: Judgment; slop?: SlopJudgment; /** Symptoms at or above the slop threshold, strongest first. The level itself is never raised by slop. */ slopSymptoms?: SlopSymptom[]; slopReasons?: string[]; /** True when a previously held call was allowed because the user's latest message approves it. */ approvedByUser?: boolean; /** Redacted, truncated `plan` as sent to Jev and shown in the trace. */ plan?: string; /** True when Jev finds the call at odds with the agent's stated plan and the call can change something; the agent is told. */ intentMismatch?: boolean; /** True when Jev finds the call unrelated to the request on a call that can change something; the agent is steered back to the task. */ offTaskSteer?: boolean; /** Answers to the caller's own `questions`: P(yes) for a noul, the picked option for a choice, the level for a score. */ extra?: Record; /** Safe TypeSafe error message when the judge could not answer. */ error?: string; errorCode?: IntegrationErrorCode; } export type { Judge } from "pi-typesafe"; export interface EvaluateOptions { config: ActionGuardConfig; /** Omit to run offline pattern checks only (no consent, no network). */ judge?: Judge | undefined; signal?: AbortSignal | undefined; /** Adds quality questions for write/edit content to the same request. */ slop?: SlopGuardConfig | undefined; security?: SecurityConfig | undefined; /** This exact call was held earlier and the user has replied since: ask whether the reply approves it. */ retryAfterHold?: boolean | undefined; /** Calls allowed in the previous turn: ask whether the user's latest message regrets one of them (rides this request). */ previousActions?: readonly PreviousAction[] | undefined; /** * Extra questions over the same state (`task`, `context`, `plan`, `action`), answered in `verdict.extra` and never acted on. * How a candidate question is measured on recorded sessions before it earns an acting rule (scripts/calibrate-action.mjs). */ questions?: Questions | undefined; } export declare const higher: (a: Level, b: Level) => Level; export interface ScannedCommand { /** The command with data text blanked; what the pattern rules read. */ text: string; /** True when a heredoc body or quoted data was removed. */ stripped: boolean; } /** * Removes heredoc bodies that are not fed to a shell and quoted arguments of data commands. Interpreter heredocs * (`python3 - <, cwd?: string, options?: PatternOptions): PatternHit[]; export declare function writeSinkTargets(command: string): string[]; /** Shared glob/regex path matcher used by both path rules (guard.ts) and arming rules (arming.ts). * Normalises ~ expansion and path separators, then tries the pattern in both tilde-prefixed and bare forms. */ export declare function matchPathGlobs(patterns: readonly string[], useRegex: boolean, candidate: string): boolean; export declare function isReadOnlyCommand(command: string): boolean; export declare function describeAction(tool: string, input: Record, cwd: string): ActionSummary; export declare const questions: { irreversible: import("pi-typesafe").NoulQuestion; off_task: import("pi-typesafe").NoulQuestion; mutates: import("pi-typesafe").NoulQuestion; scope: import("pi-typesafe").ChoiceQuestion<{ readonly expected_step: "Required implementation, bug fix, regression test, or verification for the active task"; readonly plausible_side_step: "Reasonable supporting work whose necessity is not yet established"; readonly unrelated: "No useful connection to the active task, or contrary to the user's current direction"; readonly unclear: "The supplied conversation or action gives too little information to establish scope; this is not itself a violation"; }>; }; /** * Commands only (a write or edit never is). Alone it has no precision, but a visible action that departs from the agent's * plan is what users object to: on recorded sessions 18% of such calls sat in a turn the user rejected, four times the base rate. */ export declare const visibleQuestion: { visible: import("pi-typesafe").NoulQuestion; }; /** Asked only when the agent said something before the call; an empty plan cannot be contradicted. */ export declare const intentQuestion: { intent_mismatch: import("pi-typesafe").NoulQuestion; }; export declare const slopQuestions: { slop_stub: import("pi-typesafe").NoulQuestion; slop_comments: import("pi-typesafe").NoulQuestion; slop_dead: import("pi-typesafe").NoulQuestion; slop_hedging: import("pi-typesafe").NoulQuestion; }; export declare const SLOP_LABELS: Record; export declare const securityQuestion: { security_risk: import("pi-typesafe").NoulQuestion; }; export declare const approvalQuestion: { approved: import("pi-typesafe").NoulQuestion; }; /** * One yes/no on whether the user's reply regrets what the agent did last turn; with several candidates a Choice names the * one. Labels the allowed calls for hold calibration and never changes the verdict on the current call. */ export declare function regretQuestions(actions: readonly PreviousAction[]): { regretted: import("pi-typesafe").NoulQuestion; regret_target?: never; } | { regretted: import("pi-typesafe").NoulQuestion; regret_target: import("pi-typesafe").ChoiceQuestion<{ [k: string]: string; }>; }; /** Scores, counts, and whitespace removed; the fingerprint of what the notice actually says. */ export declare function steerFingerprint(content: string): string; export declare class SteerRepeatWindow { private readonly window; private readonly recent; constructor(window?: number); /** True when this normalised text was already sent inside the window; the text is recorded either way. */ seen(content: string): boolean; reset(): void; } /** The agent's words as they leave the machine: redacted and bounded. Undefined when the agent said nothing. */ export declare function describePlan(plan: string | undefined): string | undefined; export declare function buildRequest(summary: ActionSummary, task: string | undefined, extras?: { slop?: boolean; approval?: boolean; security?: boolean; context?: readonly TaskMessage[] | undefined; previousActions?: readonly PreviousAction[] | undefined; plan?: string | undefined; questions?: Questions | undefined; }): { state: { previous_actions?: { command?: string; id: string; tool: string; path?: string; }[]; plan?: string; task: string; action: Record; context: { role: "user" | "assistant"; text: string; }[]; }; questions: { regretted: import("pi-typesafe").NoulQuestion; regret_target?: never; security_risk?: import("pi-typesafe").NoulQuestion; approved?: import("pi-typesafe").NoulQuestion; slop_stub?: import("pi-typesafe").NoulQuestion; slop_comments?: import("pi-typesafe").NoulQuestion; slop_dead?: import("pi-typesafe").NoulQuestion; slop_hedging?: import("pi-typesafe").NoulQuestion; intent_mismatch?: import("pi-typesafe").NoulQuestion; visible?: import("pi-typesafe").NoulQuestion; irreversible: import("pi-typesafe").NoulQuestion; off_task: import("pi-typesafe").NoulQuestion; mutates: import("pi-typesafe").NoulQuestion; scope: import("pi-typesafe").ChoiceQuestion<{ readonly expected_step: "Required implementation, bug fix, regression test, or verification for the active task"; readonly plausible_side_step: "Reasonable supporting work whose necessity is not yet established"; readonly unrelated: "No useful connection to the active task, or contrary to the user's current direction"; readonly unclear: "The supplied conversation or action gives too little information to establish scope; this is not itself a violation"; }>; } | { regretted: import("pi-typesafe").NoulQuestion; regret_target: import("pi-typesafe").ChoiceQuestion<{ [k: string]: string; }>; security_risk?: import("pi-typesafe").NoulQuestion; approved?: import("pi-typesafe").NoulQuestion; slop_stub?: import("pi-typesafe").NoulQuestion; slop_comments?: import("pi-typesafe").NoulQuestion; slop_dead?: import("pi-typesafe").NoulQuestion; slop_hedging?: import("pi-typesafe").NoulQuestion; intent_mismatch?: import("pi-typesafe").NoulQuestion; visible?: import("pi-typesafe").NoulQuestion; irreversible: import("pi-typesafe").NoulQuestion; off_task: import("pi-typesafe").NoulQuestion; mutates: import("pi-typesafe").NoulQuestion; scope: import("pi-typesafe").ChoiceQuestion<{ readonly expected_step: "Required implementation, bug fix, regression test, or verification for the active task"; readonly plausible_side_step: "Reasonable supporting work whose necessity is not yet established"; readonly unrelated: "No useful connection to the active task, or contrary to the user's current direction"; readonly unclear: "The supplied conversation or action gives too little information to establish scope; this is not itself a violation"; }>; } | { security_risk?: import("pi-typesafe").NoulQuestion; approved?: import("pi-typesafe").NoulQuestion; slop_stub?: import("pi-typesafe").NoulQuestion; slop_comments?: import("pi-typesafe").NoulQuestion; slop_dead?: import("pi-typesafe").NoulQuestion; slop_hedging?: import("pi-typesafe").NoulQuestion; intent_mismatch?: import("pi-typesafe").NoulQuestion; visible?: import("pi-typesafe").NoulQuestion; irreversible: import("pi-typesafe").NoulQuestion; off_task: import("pi-typesafe").NoulQuestion; mutates: import("pi-typesafe").NoulQuestion; scope: import("pi-typesafe").ChoiceQuestion<{ readonly expected_step: "Required implementation, bug fix, regression test, or verification for the active task"; readonly plausible_side_step: "Reasonable supporting work whose necessity is not yet established"; readonly unrelated: "No useful connection to the active task, or contrary to the user's current direction"; readonly unclear: "The supplied conversation or action gives too little information to establish scope; this is not itself a violation"; }>; }; }; export declare function evaluateAction(action: ActionInput, options: EvaluateOptions): Promise; /** What the agent reads after a call that differs from its own plan ran: name the gap, bound the answer to one line. * Without the bound the model writes a full accounting of the notice at the end of every task, which is noise for the * user reading the transcript; the wording below caps the demanded reply at one short sentence. */ export declare function intentSteer(verdict: Verdict): string; /** What the agent reads after an unrelated change ran: the request it drifted from, the two acceptable moves, one line. */ export declare function offTaskSteer(verdict: Verdict): string; /** Offline stand-in for the approval question when TypeSafe is not available. */ export declare function textApproves(task: string | undefined): boolean; /** * The text the agent receives when a call is held. It explains the judgment and the two acceptable next moves, * so the model re-plans instead of retrying. Contains no command text (the model already has it) and no secrets. */ export declare function steerReason(verdict: Verdict, options: { canApprove: boolean; }): string; /** One-line rendering for widgets and logs. Includes no command text. Templates: see widget.ts. */ export declare function formatVerdict(verdict: Verdict, template?: string): string; /** Render a verdict and return both the line and raw tokens, for live-mode re-rendering. */ export declare function formatVerdictTokens(verdict: Verdict, template?: string): { line: string; tokens: Record; };