/** * SignalCollectorHost — plugin I/O 外壳 (design spec §4.2) * * 把 core 的纯逻辑 (collectSync / mapLlmResultToOutput) 接进 openclaw 运行时。 * * 同步路径 (prompt.ts before_prompt_build 钩子调用,绝不阻塞): * - trigger 门控 (user/api/undefined 视为用户交互,其余跳过) * - Stage1 关键词快扫 (零成本) * - 写 user_turns (复用 recordUserTurn) * - 高精度短语命中 (high) → 直接走 STRONG 分流 (同步,纯内存) * - 普通歧义词 / 未命中 → 入队异步 LLM 确认 (不阻塞) * * 异步路径 (fire-and-forget): * - Stage2 LLM 确认 (后台,不阻塞 prompt hook) * - LLM 不可用 → 降级:丢弃 ambiguous 候选 (不触发 STRONG,避免误判泛滥) * - 按 strength 分流:STRONG → emitPainDetectedEvent (修断裂 ③);WEAK → trackFriction 累积 GFI * * 降级不静默 (rc-9):所有降级路径走 SystemLogger。 */ import { type UnifiedKeywordStore, type SignalCollectorConfig } from '@principles/core/runtime-v2'; import type { PluginLogger } from '../openclaw-sdk.js'; import type { WorkspaceContext } from './workspace-context.js'; export declare const DEFAULT_SIGNAL_CONFIG: SignalCollectorConfig; /** * 判定 trigger 是否代表真实用户交互。 * * `user` / `api` / `undefined` 均视为用户交互(prompt.ts 与 detectSync 共享同一判定, * 避免两处门控不一致导致 api/undefined 触发的纠正信号丢失)。 * `heartbeat` / `cron` / `subagent` 等系统触发不视为用户交互。 */ export declare function isUserInteractionTrigger(trigger: string | undefined): boolean; export declare function buildDefaultKeywordStore(): UnifiedKeywordStore; /** * adapter 抽象 (Stage2 LLM 调用)。plugin 层可注入真实 adapter (LMStudio); * 为 null / 不可用时 host 走纯关键词降级。 * * 本文件不依赖具体的 PDRuntimeAdapter 实现,只依赖一个最小的分类函数, * 以保持 host 可单测 (测试可注入 mock classifier)。 */ export type SignalLlmClassifier = (text: string, promptTemplate: string) => Promise<{ is_feedback: boolean; type: 'correction' | 'empathy' | 'none'; confidence: number; reason: string; } | null>; export interface SignalCollectorHostOptions { keywordStore?: UnifiedKeywordStore; /** * Live store provider (P0-B Learn→Detect 闭环): 每次检测调用,mtime 变化时 * 重载 learned correction cues。设置后优先于 keywordStore 静态快照—— * optimizer 学到的词无需重启 OpenClaw 即进入下一次检测。 */ keywordStoreProvider?: () => UnifiedKeywordStore; config?: SignalCollectorConfig; /** Stage2 LLM 分类器。null/undefined → 降级纯关键词。 */ llmClassifier?: SignalLlmClassifier | null; /** * PRI-788 G3: 关键词确认反馈(earned precision 的证据源)。LLM 确认 verdict * 出现时以命中的词回调——wasCorrection=true 记 TP(LLM 确认是纠正), * false 记 FP(命中但不是纠正)。由 prompt.ts 接线到 CorrectionCueLearner; * 未注入时为 no-op(earned 永不升级,行为=G3 之前)。 */ cueFeedbackRecorder?: (terms: readonly string[], wasCorrection: boolean) => void; } export declare class SignalCollectorHost { private readonly wctx; private readonly storeProvider; private readonly config; private readonly llmClassifier; private readonly cueFeedbackRecorder?; /** rate limit 状态:sessionId → STRONG 计数桶 */ private readonly rateLimit; constructor(wctx: WorkspaceContext, options?: SignalCollectorHostOptions); /** * ★ 同步路径 (prompt.ts before_prompt_build 钩子里调用,绝不能阻塞)。 * * 只做:trigger 门控 + Stage1 关键词快扫 + 写 user_turns。 * 高精度短语命中 (high) → 直接走 STRONG 分流 (同步,纯内存)。 * 普通歧义词 / 未命中 → 入队异步 LLM 确认 (不阻塞)。 */ /** * 可选入参:lineage 字段(由调用方算好传入,host 不感知 trajectory 结构)。 * referencesAssistantTurnId 让诊断 evidence 能 JOIN 到前置 assistant turn。 */ detectSync(userMessage: string, sessionId: string, trigger: string, options?: { referencesAssistantTurnId?: number | null; turnIndex?: number; }): void; /** * ★ 异步路径 (fire-and-forget)。 * * 1. Stage2 LLM 确认 (后台,不阻塞用户) * 2. LLM 不可用 → 降级:empathy ambiguous 候选作为 WEAK 信号路由(保留旧版 GFI 累积); * correction ambiguous / 未命中候选丢弃(不触发 STRONG,避免误判泛滥) * 3. 按 strength 分流:STRONG → emitPainDetectedEvent;WEAK → trackFriction 累积 GFI;none → 仅记录 */ private detectAsyncAndRoute; /** * PRI-788 G3: 把 LLM 确认 verdict 转成关键词 TP/FP 反馈(earned precision 的 * 证据源)。recorder 未注入或抛错均不阻塞路由(rc-9:抛错记结构化日志)。 */ private emitCueFeedback; /** * PRI-788 G2: Stage2 不可用时的持久化兜底。LLM 不可用/超时/解析失败的丢弃 * 分支改为落库 signal_confirmations(pending),由 CorrectionObserverService * 每周期批量确认——通道恢复后信号可补确认,不再"通道死=信号丢"。 * * 只入队"词库命中过的歧义候选":零命中的普通消息体量与噪声不可控,其纠正 * 语义覆盖由 G3 的词库扩充/earned 解锁承担。 */ private queueUnconfirmedForBatch; /** PRI-788 G4: 未经确认就消失的候选计数(旁路,失败不阻塞)。 */ private countStage2Dropped; /** * PRI-788 G4: stage1Strong 计数(旁路观测),刻意离开同步检测路径。 * * `updateSignalHealth` 是同步 read-modify-write,直接放在 `detectSync` 里会让 * `before_prompt_build` 承担磁盘延迟。这里只触发计数,实际写入推迟到微任务 * 队列执行(写入本身仍在一个 tick 内同步完成,不会与其它写入交错)。 */ private countStage1Strong; /** * PRI-788 G2: 批量确认一条持久化的待确认信号(CorrectionObserverService 每 * 周期调用)。用调用方提供的 classifier(每 cycle 新鲜解析)重新分类: * correction → 回写标志 + 复用 realtime STRONG 分流(限流/身份派生一致); * WEAK → trackFriction;none → rejected;classifier 失败 → failed(调用方 * attempts++,达上限转 abandoned)。 */ confirmPendingSignal(item: { sessionId: string; userTurnRowid: number; occurrenceId: string; excerpt: string; terms?: readonly string[]; }, classifier: SignalLlmClassifier): Promise<{ disposition: 'confirmed' | 'rejected' | 'failed'; detail: string; }>; /** * PRI-788 G1: Stage2 确认为纠正后回写 user_turns.correction_detected(G1)。 * 以 Stage1 写入返回的 rowid 精确寻址(rc-7);rowid 缺失/行已不存在均为 * 可观测降级,不阻塞路由。 */ private writeBackConfirmedCorrection; /** * Stable occurrence identity for correction dedup (ADR-0020 §11.4): the real * per-session turn index when the caller supplies one (parity with * recordUserTurn), otherwise a content-derived legacy fallback. The fallback * is a degradation — never silent (rc-9). */ private resolveOccurrenceId; /** * STRONG 分流 → emitPainDetectedEvent (修断裂 ③)。 * 带 STRONG rate limit 门控 (spec §7.2):单 session 每小时上限 strongRateLimitPerHour。 * * Codex Governance Closure Slice B 收敛 (ADR-0020 §11.4):pain id 走 * production-pain-evidence 的内容派生 canonicalization(同一 pain identity * 权威),不再铸造随机 `correction_`;trace id 降级为 correlation 字段。 */ private routeStrong; /** * WEAK 分流 → trackFriction 累积 GFI + 写 evidence (维持现状,spec §3.2)。 * * WEAK 是 Layer 3 弱信号,用较小的摩擦分(20)累积,过 highGfi 阈值(70)才触发诊断。 * 不用 strongPainScore(70),因为 WEAK 单条不该直接顶满 GFI。 */ private routeWeak; /** * STRONG rate limit 门控:成功消耗一个名额返回 true;超限返回 false。 * 窗口满一小时自动重置。 */ private tryConsumeRateLimit; } /** 测试专用:清空告警去重状态。 */ export declare function _resetSignalClassifierWarnState(): void; export declare function createSignalLlmClassifierFromConfig(wctx: WorkspaceContext, logger?: Pick): SignalLlmClassifier | null;