import type { RuntimeStateManager } from './store/runtime-state-manager.js'; import type { CandidateIntakeService } from './candidate-intake-service.js'; import type { LedgerAdapter } from './candidate-intake.js'; import type { RunnerResult, RunnerResultStatus } from './runner/runner-result.js'; import type { PDErrorCategory } from './error-categories.js'; import type { CandidateAdmissionResult, PainProvenance } from './admission-gate.js'; import type { DiagnosticianOutputV1 } from './diagnostician-output.js'; import type { PainIngressV1Payload } from './pain-ingress-payload.js'; import type { PainCorrectionEvidence } from './context-payload.js'; export type { PainProvenance }; /** * Minimal interface for a diagnostician runner. * Both DiagnosticianRunner and SplitDiagnosticianRunner satisfy this. */ export interface DiagnosticianRunnerLike { run(taskId: string): Promise; } /** PRI-359: Increased from 4 to 8 to accommodate failed tool_calls evidence */ export declare const MAX_EVIDENCE_ENTRIES = 8; export declare const MAX_EVIDENCE_NOTE_CHARS = 200; /** * PRI-844: defensive upper bound for persisted Owner correction text. * This is a storage safety limit, NOT semantic truncation — the original * correction should remain complete when under the limit, and the whole * point of the field is that the Owner's own words survive verbatim. */ export declare const MAX_CORRECTION_EVIDENCE_TEXT_CHARS = 2000; /** * PRI-844: sanitize (strip PD tags / redact tokens / redact absolute paths) * without the 200-char generic evidence cap, then apply the defensive * storage bound. Deliberately NOT `sanitizeString` — its MAX_EVIDENCE_VALUE_CHARS * (200) head-truncation would destroy the verbatim correction this field exists * to preserve. */ export declare function boundCorrectionEvidenceText(correctionEvidence: PainCorrectionEvidence, workspaceDir?: string): PainCorrectionEvidence; export interface PainEvidenceEntry { sourceRef: string; note: string; } /** * PRI-640 (Host Attribution SPEC §6): which agent host a pain originated from. * Observability metadata only — MUST NOT participate in pain identity, * admission, diagnosis, approval, or activation. NULL at the persistence * layer means legacy / manual / unknown (read models report `unknown`). */ export type GovernanceHostKind = 'openclaw' | 'codex'; export interface PainDetectedData { painId: string; painType: 'tool_failure' | 'subagent_error' | 'user_frustration'; source: string; reason: string; score?: number; sessionId?: string; agentId?: string; taskId?: string; traceId?: string; provenance?: PainProvenance; /** Codex Governance Closure SPEC §12: provenance `host_context_bound` names the host. */ hostKind?: GovernanceHostKind; evidence?: PainEvidenceEntry[]; /** * PRI-844: the Owner's verbatim correction, when this pain's context * contains one. Producers with no correction context leave it absent — * absence must never be replaced with fabricated correction text (rc-9). * Flows verbatim (defensively bounded) into diagnosticJson so the * diagnosis prompt sees the Owner's own words, not a trigger excerpt. */ correctionEvidence?: PainCorrectionEvidence; /** * PRI-642 SPEC §9: validated rev-2 ingress facts. When present, * buildDiagnosticJson writes them under the versioned `painIngress` * namespace next to the legacy top-level fields (one builder), and * re-entry validates the two against each other. */ painIngress?: PainIngressV1Payload; } export type PainSignalBridgeStatus = 'succeeded' | 'skipped' | 'failed' | 'retried' | 'degraded'; /** * PRI-539: A candidate that was admitted and ledgered but could not be * internalized (e.g. its mapped channel is MVP-disabled). Surfaced so the * Owner can see which candidates were dropped and why instead of silently * assuming every candidate was internalized (rc-9-no-silent-fallback / ERR-002). */ export interface NotInternalizableCandidate { candidateId: string; reason: string; } /** * PRI-642 SPEC §10: aggregate progress. `furthestStage` means AT LEAST ONE * item reached that stage — never that all candidates did; per-candidate * dispositions live in `candidateOutcomes`. */ export type PainFurthestStage = 'observed' | 'diagnosis_submitted' | 'diagnosis_completed' | 'candidate_processing' | 'internalization_seeded'; export interface PainProgressReport { furthestStage: PainFurthestStage; generatedCandidateIds: string[]; admittedCandidateIds: string[]; ledgerEntryIds: string[]; seededTaskIds: string[]; } /** PRI-642 SPEC §10: where each candidate stopped and why. */ export interface PainCandidateOutcome { candidateId: string; decision: 'admitted' | 'needs_evidence' | 'deferred'; ledgerEntryId?: string; seededTaskId?: string; reason: string; nextAction: string; } export interface PainSignalBridgeResult { status: PainSignalBridgeStatus; painId: string; taskId: string; runnerStatus?: RunnerResultStatus; runId?: string; artifactId?: string; candidateIds: string[]; ledgerEntryIds: string[]; admissionResults?: CandidateAdmissionResult[]; /** PRI-539: candidates admitted+ledgered but not internalizable (MVP-disabled channel). */ notInternalizable?: NotInternalizableCandidate[]; /** PRI-642 §10: aggregate progress (at-least-one semantics). */ progress?: PainProgressReport; /** PRI-642 §10: per-candidate disposition — the authority for mixed results. */ candidateOutcomes?: PainCandidateOutcome[]; errorCategory?: PDErrorCategory; message?: string; /** * PRI-638: recovery action surfaced by the runner. Only set when the failure * is an Owner capability decision (Diagnostician disabled), never for runtime * or provider faults — the two must remain distinguishable. */ nextAction?: string; } export interface PainSignalBridgeOptions { stateManager: RuntimeStateManager; runner: DiagnosticianRunnerLike; intakeService: CandidateIntakeService; ledgerAdapter: LedgerAdapter; owner?: string; autoIntakeEnabled?: boolean; /** Workspace directory — written into diagnosticJson so the diagnostician can locate files. */ workspaceDir?: string; /** * Pain Diagnosis Persistence: persist the diagnostician's root-cause * attribution into state.db pain_diagnoses (keyed by the canonical pain_id) * on diagnosis completion. Gated by the `pain_diagnosis_persistence` * feature flag — the factory resolves it from effectiveConfig; default * false keeps the pre-feature behavior (no writes). */ diagnosisPersistenceEnabled?: boolean; /** * PRI-720: seed NEW prompt/defer_archive chains with the explicit full-chain * topology override (pipelineMode='full_chain') instead of the channel-aware * short path. Resolved by the factory from the `prompt_full_pipeline` * feature flag (Owner switch: Console settings / .pd/config.yaml). Default * false = standard channel-aware topology. Affects only newly seeded chains. */ fullPipelinePromptSeeds?: boolean; eventEmitter?: { emitTelemetry: (event: { eventType: string; traceId: string; timestamp: string; payload: Record; }) => void; }; /** * PRI-624: resources the factory created for this bridge (its extra * SqliteConnection) that `dispose()` must release. Long-running workers * dispose bridges per cycle so file handles do not pin the workspace DB. */ ownedResources?: readonly { close: () => void | Promise; }[]; /** * PRI-638 P1-A: set ONLY by the factory's capability-disabled bridge. * While the Owner has switched the Diagnostician off, `onPainDetected` / * `executePendingDiagnosis` must ENSURE a durable task exists for new Pain * but must NEVER reset/re-trigger an existing task (that would erase * retry_wait/failed history and the retry budget). The runner is not * invoked, so provider calls stay 0. */ capabilityDisabled?: { readonly reason: string; readonly nextAction: string; }; } export declare function createDiagnosticianTaskId(painId: string): string; export declare class PainSignalBridge { private readonly stateManager; private readonly runner; private readonly intakeService; private readonly ledgerAdapter; private readonly owner; private readonly autoIntakeEnabled; private readonly diagnosisPersistenceEnabled; private readonly fullPipelinePromptSeeds; private readonly workspaceDir; private readonly eventEmitter?; private readonly ownedResources; private readonly capabilityDisabled; constructor(opts: PainSignalBridgeOptions); /** * PRI-638 P1-A: the unified disabled outcome. `status: 'failed'` is kept * (existing RunnerResult vocabulary) but carries `capability_missing` + * `nextAction`, so no caller can mistake it for a provider/runtime fault. */ private disabledResult; /** * PRI-624: release every handle this bridge holds (state manager + the * factory-owned connection). Callers that keep the cached bridge (plugin * host process) may skip this; per-cycle workers MUST dispose to avoid * pinning the workspace SQLite files. */ dispose(): Promise; private emitAdmissionEvent; /** * Submit a pain signal without running diagnosis. * Creates the task as 'pending' for later execution by orchestrator wakeOnce/recovery-sweep. * Returns the taskId for progress tracking. * PRI-369: async pain-record CLI — fire-and-forget submission. */ submitPainSignal(data: PainDetectedData): Promise<{ taskId: string; }>; onPainDetected(data: PainDetectedData): Promise; /** * PRI-624 (Codex Closure Slice C): execute one already-submitted * Diagnostician task — the async counterpart of `submitPainSignal`. A * worker (Companion) or CLI uses this to advance a task that Slice B * admission enqueued without running an LLM in the hook. * * Unlike `onPainDetected` this NEVER resets task state: a worker retry * loop must preserve the retry budget (attemptCount/maxAttempts) exactly * like the peer-runner consumers do. The lease is acquired inside * `runner.run` via the existing Runtime V2 lease manager. * * Eligibility: 'pending', or 'retry_wait' whose backoff deadline * (lease_expires_at) has elapsed (isRetryWaitBackoffElapsed — the same * guard wakeOnce applies). 'leased' is skipped — the existing lease wins; * expired leases are the recovery sweep's job, not ours. 'succeeded' * returns the existing result (idempotent). 'failed' / * 'needs_human_review' are skipped: terminal states need explicit * Owner/manual action, never silent worker retries. */ executePendingDiagnosis(input: { taskId: string; /** * @deprecated PRI-642 SPEC §9: re-entry reads the provenance from the * persisted task payload; a caller-supplied value no longer overrides * it (no host-binding defaults, single authority). */ provenance?: PainProvenance; }): Promise; /** * Pain Diagnosis Persistence: link the diagnostician's root-cause attribution * to the canonical pain_id in state.db pain_diagnoses. Runs BEFORE admission * so the attribution is durably recorded even when every candidate is later * rejected — the pain's diagnosis history must not depend on admission * outcome. Persistence is auxiliary to the admission→intake flow: failures * degrade observably via telemetry and never break the main pipeline * (rc-9-no-silent-fallback). */ private persistPainDiagnosis; /** * PRI-372 (T-G): Post-diagnosis processing extracted from onPainDetected(). * Handles admission → intake → seedDreamer after a successful diagnosis. * Also called by DiagRouterRunner's onDiagnosisComplete callback. */ onDiagnosisComplete(opts: { taskId: string; diagnosticianOutput: DiagnosticianOutputV1 | undefined; painId: string; provenance: PainProvenance; inputEvidenceCount?: number; }): Promise; private buildExistingResult; } //# sourceMappingURL=pain-signal-bridge.d.ts.map