export interface PluginPromptHookResult { prependContext?: string; prependSystemContext?: string; appendSystemContext?: string; } export interface MoltbotPluginAPI { config: MoltbotConfig; registerService(config: ServiceConfig): void; on(event: string, handler: (event: any, ctx?: any) => void | Promise): void; logger: { info(msg: string): void; warn(msg: string): void; error(msg: string): void; }; } export interface MoltbotConfig { agents?: { defaults?: { models?: { [modelName: string]: { alias?: string; }; }; }; }; plugins?: { entries?: { [pluginId: string]: { enabled?: boolean; config?: PluginConfig; }; }; }; } export interface PluginHookAgentContext { agentId?: string; sessionKey?: string; workspaceDir?: string; messageProvider?: string; channelId?: string; senderId?: string; /** Agent run id (SDK >= 2026.5) — REQUIRED by emitAgentEvent (provenance). */ runId?: string; } export interface PluginConfig { bankMission?: string; embedPort?: number; daemonIdleTimeout?: number; embedVersion?: string; embedPackagePath?: string; llmProvider?: string; llmModel?: string; llmApiKey?: string; llmBaseUrl?: string; apiPort?: number; hindsightApiUrl?: string; hindsightApiToken?: string; dynamicBankId?: boolean; bankId?: string; bankIdPrefix?: string; retainTags?: string[]; retainSource?: string; excludeProviders?: string[]; excludeSenders?: string[]; autoRecall?: boolean; dynamicBankGranularity?: Array<"agent" | "provider" | "channel" | "user">; autoRetain?: boolean; retainRoles?: Array<"user" | "assistant" | "system" | "tool">; retainFormat?: "json" | "text"; retainToolCalls?: boolean; recallBudget?: "low" | "mid" | "high"; recallMaxTokens?: number; recallTypes?: Array<"world" | "experience" | "observation">; recallRoles?: Array<"user" | "assistant" | "system" | "tool">; retainDocumentScope?: "session" | "turn"; retainEveryNTurns?: number; retainOverlapTurns?: number; recallTopK?: number; recallContextTurns?: number; recallTimeoutMs?: number; recallMaxQueryChars?: number; recallPromptPreamble?: string; recallInjectionPosition?: "prepend" | "append" | "user"; provenanceReport?: string; /** * Minimum cross-encoder score required for a recalled fact to be injected. * When set, the plugin activates `trace=true` on the recall request, reads * the raw `cross_encoder_score` from `response.trace.final_results`, and * filters out every fact below the threshold BEFORE injecting the memory * block. If no fact passes, no `` block is emitted. * * Range: [0.0, 1.0]. Default: 0.3 (active out-of-the-box once Hindsight * server uses a real neural reranker; harmless no-op when the server runs * in `rrf` passthrough mode because constant 0.5 ≥ 0.3 lets all results * through). * * Set to 0 to disable filtering while still requesting the trace * (useful to log scores empirically for tuning). Set to undefined or null * via `openclaw config set ... null` to disable both trace and filtering. */ recallMinRelevance?: number; /** * Recall router configuration. Decides whether (and what kinds of) memory * recall should be performed for a given user turn. See `src/router/` * for the heuristic + Jina-classifier pipeline. * * The default settings (`enabled=true`, `mode="heuristic"`) skip recall * deterministically on heartbeats, cron triggers, agent meta-questions, * and CLI test pings — zero Jina cost. Switch `mode` to `"jina-classifier"` * and provide `jinaApiKey` to enable semantic routing for ambiguous cases. */ recallRouter?: { /** Master switch. When false, recall is never gated by the router. */ enabled?: boolean; /** Engine for ambiguous cases. Default: "heuristic". */ mode?: "heuristic" | "jina-classifier"; /** * Jina API key (required when mode = "jina-classifier"). Configure as * a SecretRef: `openclaw config set ... .recallRouter.jinaApiKey * --ref-source env --ref-id JINA_API_KEY`. */ jinaApiKey?: string; /** * Optional few-shot classifier ID, obtained out-of-band via the Jina * Playground. When provided, the router calls /v1/classify with this * ID instead of running zero-shot. */ classifierId?: string; /** * Number of classes the zero-shot classifier should discriminate. * 2 = NONE/ALL (default — robust on short FR prompts). * 4 = NONE/WORLD_ONLY/EXPERIENCE_ONLY/ALL (enables finer routing, * less reliable on short or ambiguous prompts). */ routes?: 2 | 4; /** Custom label list — overrides `routes` when ≥ 2 labels. */ labels?: string[]; /** * Minimum classifier confidence required to trust a prediction. * Below this, the router fails open to ALL. Default: 0.35. */ minConfidence?: number; /** * When the router narrows recall types to WORLD_ONLY or * EXPERIENCE_ONLY, controls whether `observation` (consolidated * facts) is preserved alongside the chosen type. * * - `true` (default): WORLD_ONLY → ["world", "observation"], * EXPERIENCE_ONLY → ["experience", "observation"]. Consolidated * observations are derived from world/experience and are often * the highest-signal recall targets — keeping them avoids * silently dropping the most relevant fact when the router * picks a narrow route. * - `false`: strict narrowing — WORLD_ONLY → ["world"], * EXPERIENCE_ONLY → ["experience"]. Useful when the operator * wants exclusive type filtering with no compounding. * * Only applies when `observation` is already in the operator's * configured `recallTypes`; otherwise this option is a no-op. */ observationFollowsNarrow?: boolean; }; ignoreSessionPatterns?: string[]; statelessSessionPatterns?: string[]; skipStatelessSessions?: boolean; debug?: boolean; logLevel?: "off" | "error" | "warning" | "info" | "debug"; logSummaryIntervalMs?: number; retainQueuePath?: string; retainQueueMaxAgeMs?: number; retainQueueFlushIntervalMs?: number; } export interface ServiceConfig { id: string; start(): Promise; stop(): Promise; } export type { RecallResult as MemoryResult, RecallResponse, ReflectResponse, } from "@vectorize-io/hindsight-client"; /** * Internal retain payload shape built by `buildRetainRequest`. Not a * re-export from the generated client — the generated client's retain() * takes bankId + content + options as positional args, whereas we build up a * single object inside the plugin and translate it at the call site. Keeping * this type local means tests can assert the shape without pulling in * generated types. */ export interface RetainRequest { content: string; documentId?: string; metadata?: Record; tags?: string[]; /** * `'append'` concatenates this content to the existing document text * (Hindsight ≥ 0.5 only — older versions silently ignore the field and * overwrite). The plugin only sets this when capability detection at * service.start() confirmed support; otherwise it falls back to a * per-turn document id and leaves this unset. */ updateMode?: "replace" | "append"; } /** * Stats returned by `GET /v1/default/banks/{bank_id}/stats`. The generated * high-level client does not expose this endpoint yet; backfill calls it * directly via `fetch`. */ export interface BankStats { bank_id: string; total_nodes: number; total_links: number; total_documents: number; pending_operations: number; failed_operations: number; pending_consolidation: number; last_consolidated_at: string | null; total_observations: number; nodes_by_fact_type?: Record; links_by_link_type?: Record; links_by_fact_type?: Record; links_breakdown?: Record; }