import type { JudgmentBackend } from "./backend.js"; import type { WidgetConfig } from "./widget.js"; export interface Threshold { /** P(yes) at or above this shows a warning and continues. */ warn: number; /** P(yes) at or above this asks the user before the tool runs. */ confirm: number; } export interface OffTaskThreshold { /** P(off-task) at or above this, with a scope other than unclear, shows a warning. */ warn: number; /** P(off-task) at or above this, with scope unrelated on a call that can change something, also steers the agent back to the task. Never holds: on 17k recorded calls off-task holds caught nothing the user regretted. */ steer: number; } /** A user-defined command rule, matched against the stripDataText-processed command. */ export interface CommandRule { /** Stable id; same namespace as built-in rule ids, so exemptRules can reference either. */ id: string; /** Regex source string; compiled case-insensitively unless caseSensitive is true. */ pattern: string; /** warn: notice to the agent; confirm: hold (action dialog steers); deny: block with no dialog. */ severity: "warn" | "confirm" | "deny"; /** When severity is confirm, dialog (default) prompts the user; hold uses steer semantics. */ action?: "dialog" | "hold"; /** Optional human label shown instead of the derived one. */ message?: string; /** Match case-sensitively. */ caseSensitive?: boolean; } export interface ActionGuardConfig { enabled: boolean; /** Tool names inspected before execution. Read-only tools are skipped to keep latency low. */ tools: string[]; /** When TypeSafe cannot answer (timeout, outage, budget), allow the call with a warning instead of asking. */ failOpen: boolean; /** Per-request TypeSafe timeout. The call is judged as an error after this. */ timeoutMs: number; irreversible: Threshold; offTask: OffTaskThreshold; /** P(the call differs from the agent's own stated plan) at or above this warns and tells the agent; never holds on its own. */ intentMismatch: number; /** The same, for a command whose effect is visible outside the working tree (commit, push, merge, publish, launch): less mismatch is enough. */ visibleMismatch: number; /** Write each judged call and what the user did next (approved, declined, re-planned, regretted) to an owner-only per-session file under the agent directory; redacted, never the command. */ feedbackLog: boolean; /** User-defined command rules (user file only; project files cannot set severity above warn). */ commandRules: CommandRule[]; /** User-defined deny rules, shorthand for commandRules with severity deny (user file only). */ commandDenyRules: CommandRule[]; /** Built-in or user rule ids to exempt (user file only). */ exemptRules: string[]; /** User-defined path rules with an access dimension (user file only; project files cannot act on them). */ pathRules: PathRule[]; /** User-defined arming rules: editing files matching globs arms a command pattern for a window (user file only). */ armingRules: ArmingRule[]; } /** A user-defined path rule: which paths, which side of the access is held, which tools, what happens on a hit. */ export interface PathRule { /** Stable id; same namespace as rule ids, so exemptRules can silence a user path rule too. */ id: string; /** Path globs (`**` any depth, `*` one segment, `?` one character, `~` expands) or regex sources with regex: true. */ paths: string[]; /** Regex instead of glob for shapes globs cannot express. */ regex?: boolean; /** Which touches match: "none" any touch; "read" writes only (reads flow); "write" reads only (writes flow). */ access: "none" | "read" | "write"; /** Which surfaces check the rule: file tools by name ("write", "edit", "read"), or "*" to also match bash commands. */ tools: string[]; /** note: tell the agent after the fact (the default, today's sensitive-path behavior); warn: notice; confirm: dialog; block: deny. */ action: "note" | "warn" | "confirm" | "block"; /** Optional human label shown instead of the derived one. */ message?: string; /** Skip when the path does not exist (default true): phantom paths do not fire. */ onlyIfExists?: boolean; } /** A user-defined arming rule: a preparation (editing files matching globs) arms a command pattern for a window. * While armed, matching commands fire the rule's action. This is the fix for the class of incident where each * individual call was harmless (edit a config, then run the reconciler that applies it) but the composition was * destructive — no single-call rule can catch it. */ export interface ArmingRule { /** Stable id; same namespace as rule ids, so exemptRules can silence an arming rule too. */ id: string; /** The preparation: editing files matching these globs arms the rule. */ when: { /** Path globs (`**` any depth, `*` one segment, `?` one character, `~` expands) or regex sources with regex: true. */ edited: string[]; /** Regex instead of glob for shapes globs cannot express. */ regex?: boolean; /** Which file tools arm: default ["write", "edit"]. */ tools?: string[]; }; /** The armed command: while armed, commands matching this regex fire the rule's action. */ arms: { /** Regex source string; compiled case-insensitively unless caseSensitive is true. */ command: string; /** How long the rule stays armed after the last matching edit; default "10m". */ for?: string | number; /** Match the command case-sensitively. */ caseSensitive?: boolean; }; /** confirm: dialog (user-invoked prompt); hold: steer hold; block: deny. */ action: "confirm" | "hold" | "block"; /** Optional human label shown in the dialog/status. */ message?: string; } export interface StuckGuardConfig { enabled: boolean; /** Tool results remembered per user prompt. */ window: number; /** Failures in the window before Jev is asked. */ minFailures: number; /** Tool results between two Jev checks. */ cooldown: number; /** P(same strategy) at or above this reports the agent as stuck. */ sameStrategy: number; /** Calls to the same target (same tool + input key) that trigger churn detection. */ churnThreshold: number; /** Also steer the agent with a short message, not only the user. */ nudge: boolean; } export interface DoneGuardConfig { enabled: boolean; /** P(final message claims completion) at or above this warns when no check passed in the run. */ claimsDone: number; /** Also send the agent a follow-up asking it to verify. Triggers one more LLM turn. */ nudge: boolean; } export interface ProseConfig { enabled: boolean; /** Who reads the agent's replies: "technical", "plain", or a free-text description. Drives the jargon question. */ audience: string; /** P(symptom) at or above this counts as a hit. */ threshold: number; /** A symptom must hit in this many of the last three replies before the agent is nudged. */ trend: number; /** Replies with fewer characters are not judged. */ minChars: number; } export interface SlopGuardConfig { enabled: boolean; /** P(symptom) at or above this is reported for written code: stub, comments, dead, hedging. */ threshold: number; prose: ProseConfig; } export interface SecurityConfig { enabled: boolean; /** P(injection or exfiltration) at or above this adds an untrusted-output notice. */ threshold: number; } export interface RulesConfig { /** Judge each write and edit against the project's Markdown rules on its own Jev request; steer, never hold. */ enabled: boolean; /** P(violation) at or above this names the rule to the agent. */ threshold: number; /** Project-relative Markdown rule files, used when the root pi-warden.md is absent. All are sent in one request. */ files: string[]; /** With no rules file, README.md, CLAUDE.md, or AGENTS.md (first found) is judged as one document. */ fallback: boolean; /** Characters of a fallback document sent per request; every heading and the head of each section are kept within it. */ maxChars: number; /** Globs of files whose content is never sent to Jev for rules (secrets, generated, vendored). */ exclude: string[]; /** Globs of files the rules do not apply to, for example tests or docs. */ skip: string[]; /** Glob → note. A write or edit under a matching path steers the agent with the note once per path; code only. */ sensitivePaths: Record; } export interface ContextConfig { enabled: boolean; /** Only new tool output is compressed; warm history and system prompts are never changed. */ tailMinChars: number; /** Minimum P(the full output is not needed), 1 - P(all), before code removes output. */ confidence: number; /** A text result at least this long that repeats an earlier result of this session is replaced by a short note (code only). */ duplicateMinChars: number; /** Search command named in the recall footer; `auto` detects one at load. */ recallTool: RecallTool; /** Minimum P(format) before a format-specific parser builds the excerpt instead of the generic head/tail one. */ formatConfidence: number; } export interface RunawayConfig { enabled: boolean; /** Identical text paragraphs in one streaming reply before the run is stopped. Ordinary replies repeat a paragraph twice at most. */ repeats: number; /** The same limit for thinking, where code drafting repeats paragraphs legitimately. */ thinkingRepeats: number; /** Characters streamed before the first check. */ minChars: number; /** After stopping, start one follow-up turn that names the repeat and asks for the one next step; once per user prompt. */ recover: boolean; } export interface NotifyConfig { /** Desktop notification when the agent needs you: a held call it will ask about, a confirm dialog, a stopped runaway. Off by default; opt in per user or project. */ enabled: boolean; /** Sibling holds in one turn produce one notification; a second within this many milliseconds is skipped. */ cooldownMs: number; /** * Your own notifier as an argv (no shell), for ssh sessions or a phone relay: `{title}` and `{body}` in an argument are * replaced, and both are in PI_WARDEN_TITLE / PI_WARDEN_BODY. Empty: detect the desktop's own tool. User file only. */ command: string[]; } export interface SubagentConfig { /** Read async subagent reports at all. Off: warden ignores them, as before 0.14. */ enabled: boolean; /** Ask Jev whether a report that names trouble deserves a wake. Off keeps the offline layer, which never wakes. */ wake: boolean; /** P(this report needs the agent awake) at or above this value wakes it. Conservative on purpose. */ threshold: number; /** At most one wake per this window, so several children finishing together cost one interruption. */ cooldownMs: number; } export type RecallTool = "auto" | "rg" | "ag" | "ugrep" | "git-grep" | "grep" | "select-string" | "findstr" | "none"; export declare function isRecallTool(value: unknown): value is RecallTool; export type WardenMode = "steer" | "confirm" | "advise"; export interface LearningConfig { /** Enable adaptive thresholds based on learning data. */ adaptiveThresholds: boolean; /** Enable pattern analysis and recommendations. */ patternAnalysis: boolean; /** Minimum number of holds before adaptive thresholds kick in. */ minHoldsForAdaptive: number; /** How aggressively to adjust thresholds (0-1). Higher values mean faster adaptation. */ adaptationRate: number; /** Days to keep hold records in SQLite before pruning. 0 disables pruning. */ retentionDays: number; } export interface WardenConfig { /** Master switch. false disables every guard, including offline pattern checks. */ enabled: boolean; /** Consent to send task and action summaries to api.typesafe.ai. Set by /warden enable; never by a project file. */ typesafe: boolean; /** The decisions service the judgments go to. User file only: a project must not redirect judgments to another vendor. */ typesafeBackend: JudgmentBackend; /** * steer (default): a confirm-level call is held and the agent receives the judgment as its tool result, so it re-plans or asks * the user in chat. confirm: open a dialog and let the user decide (falls back to steer without a UI). advise: never hold; report only. * PI_WARDEN_MODE overrides it. */ mode: WardenMode; /** Per-request TypeSafe timeout for every guard. */ timeoutMs: number; /** Maximum TypeSafe requests per session across all guards. */ maxRequests: number; action: ActionGuardConfig; stuck: StuckGuardConfig; done: DoneGuardConfig; slop: SlopGuardConfig; security: SecurityConfig; rules: RulesConfig; context: ContextConfig; runaway: RunawayConfig; notify: NotifyConfig; /** Triage of async subagent reports: Jev separates what needs the agent awake from what is only context. */ subagent: SubagentConfig; /** The status line above the editor and the trace panel. */ widget: WidgetConfig; /** Show steer messages in the transcript. They are always visible in the trace panel. */ steerVisible: boolean; /** Per-call warning notices ("warden · …") in the transcript. Off by default; the widget and trace panel always show them. */ notices: boolean; /** Steers delivered to the agent per run before further non-critical ones are recorded in the trace only. Every delivered * steer costs at least one LLM turn, and a closing run that collects six notices collects six restatements of the final * status. 0 disables the budget. Critical guards (stuck, done, runaway, subagent wake) always deliver. */ steerBudget: number; /** Learning and adaptation settings. */ learning: LearningConfig; } export declare const PACKAGE_NAME = "pi-warden"; /** Bumped when WardenConfig gains a section; extension.ts checks it so a half-updated module graph is reported, not crashed on. */ export declare const CONFIG_SCHEMA = 6; export declare const PROJECT_CONFIG_FILE = "pi-warden.json"; export declare function defaultConfig(): WardenConfig; /** Mirrors Pi's agent directory rule so the file sits next to pi-typesafe's auth.json. */ export declare function userConfigPath(): string; export declare function projectConfigPath(cwd: string): string; type Json = Record; export declare function isMode(value: unknown): value is WardenMode; /** Unknown keys and invalid values fall back to the base; nothing throws on a malformed file. */ export declare function applyUserOverrides(base: WardenConfig, raw: unknown): WardenConfig; /** Project files may tune the guards but cannot grant TypeSafe consent, change the mode, or raise budgets. */ export declare function applyProjectOverrides(base: WardenConfig, raw: unknown): WardenConfig; export interface LoadOptions { cwd?: string; /** Project overrides are applied only when the caller vouches for the project (Pi's trust decision). */ projectTrusted?: boolean; } export declare function loadConfig(options?: LoadOptions): WardenConfig; /** Reads only the user file, for editing and persisting consent. */ export declare function readUserConfig(): Json; export declare function writeUserConfig(raw: Json): string; /** Persists one top-level user setting without disturbing the rest of the file. */ export declare function setUserSetting(key: "typesafe" | "enabled" | "mode", value: boolean | WardenMode): string; export declare function setNestedValue(obj: Record, path: string, value: unknown): Record; export declare function getNestedValue(obj: Record, path: string): unknown; /** Coerce a CLI string into a JSON primitive so `/warden config set` stays ergonomic. */ export declare function parseConfigValue(value: string): unknown; export {};