/** * Conscience module: broad-consideration coach that recommends or loads * skills and tools before the agent acts. * * Pre-measurement: all selections are trace-only. No steers, no auto-loading. */ import type { Skill } from "@earendil-works/pi-coding-agent"; import type { Questions } from "pi-typesafe"; import type { ConscienceConfig } from "./config.js"; export type Disposition = "advance" | "awaiting_user" | "no_gap" | "unclear"; /** One candidate for assessment: a skill or a tool. */ export interface Candidate { /** "skill" or "tool". */ kind: "skill" | "tool"; /** Stable id: skill name or tool name. */ id: string; /** Human-readable description (sanitized: no absolute paths or URLs). */ description: string; /** True when this candidate belongs to a category that was truncated (catalog incomplete). */ incomplete?: boolean; /** Full skill metadata, present for kind=skill. */ skill?: Skill; } /** The result of one assessment. */ export interface AssessmentResult { /** The disposition chosen by the shared question. */ disposition: Disposition; /** The selected candidate, if any. */ selected: Candidate | null; /** P(useful now) = P(direct_useful) + P(prerequisite). */ usefulness: number; /** P(advance) from the disposition answer. */ pAdvance: number; /** Question hash for deduplication. */ questionHash: string; /** Time spent on this assessment (ms). */ elapsedMs: number; /** Why no selection was made, if applicable. */ skipReason?: SkipReason | undefined; /** Error category when skipReason is "error": timeout, network, configuration, auth, or other. */ errorCategory?: string | undefined; /** Number of judge requests issued for this assessment. */ requestCount: number; } export type SkipReason = "disabled" | "no_consent" | "no_match" | "already_supplied" | "awaiting_user" | "timeout" | "error" | "stale" | "catalog_unavailable" | "catalog_limit" | "metadata_unsafe" | "budget" | "explicit_skill" | "origin_unknown" | "below_threshold"; /** Score levels for the usefulness question. Levels are ordered from least to most useful. */ export declare const SCORE_LEVELS: readonly ["No useful contribution to the current request, or conflicts with the supplied constraints.", "Related to the subject, but already covered, premature, or unable to resolve the current need.", "Addresses a concrete unmet need at the current step without displacing the active workflow.", "Supplies a missing prerequisite or directly applicable documented method needed for the current step."]; /** Strip absolute paths, URLs, and credentials from a description. */ export declare function sanitizeDescription(text: string): string; /** Opaque IDs: c1, c2, ... in stable order. */ export declare function opaqueId(index: number): string; /** Map candidates to opaque IDs, returning a stable ordered list. */ export declare function assignOpaqueIds(candidates: Candidate[]): Array<{ opaqueId: string; candidate: Candidate; }>; /** * Build assessment questions for a batch of candidates. * State carries structured data; each question names its candidate field. * Returns questions, the disposition key, and the opaqueId→candidate map. */ export declare function buildBatchQuestions(batch: Array<{ opaqueId: string; candidate: Candidate; }>, task: string, recentContext: string, activeSkills: string[], suppliedSkills: string[]): { questions: Questions; dispositionKey: string; idMap: Map; }; /** * Build the full state object for the judge request (spec §3 state contract). */ export declare function buildState(task: string, recentContext: string, activeSkills: string[], suppliedSkills: string[], batch: Array<{ opaqueId: string; candidate: Candidate; }>): Record; /** * Compute a deterministic hash of the assessment questions for deduplication. * Recursively sorts all object keys for canonical form. */ export declare function questionHash(questions: Questions): string; /** Filter candidates by eligibility rules, capped at MAX_ELIGIBLE per category. */ export declare function eligibleCandidates(skills: Skill[], tools: { name: string; description: string; }[], config: ConscienceConfig, activeSkills: string[], suppliedSkills: string[]): { candidates: Candidate[]; skillOverflow: boolean; toolOverflow: boolean; }; export interface Judge { evaluate(request: { state: unknown; questions: Questions; }, options?: { timeoutMs?: number; }): Promise<{ answers: Record; }>; } export interface ConscienceDeps { judge: Judge | undefined; config: ConscienceConfig; /** Shared timeout from WardenConfig. The effective deadline is min(conscience.timeoutMs, sharedTimeoutMs). */ sharedTimeoutMs: number; /** Current wall-clock time (ms). Injected for testability. */ now?: () => number; } /** * Run assessment for the given prompt revision. * Pre-measurement: returns trace-only results. No steers, no auto-loading. * Issues sequential requests: skills first, then tools in the remaining envelope. */ export declare function assess(prompt: string, recentContext: string, skills: Skill[], tools: { name: string; description: string; }[], activeSkills: string[], suppliedSkills: string[], deps: ConscienceDeps): Promise;