import type { HookCredential } from '@mnemonik/credentials'; /** * Claude Code hook protocol — only the fields this dispatcher actually reads. * Spec: https://docs.claude.com/en/docs/claude-code/hooks * * We intentionally stay narrow: extra fields the host may ship are ignored * silently so newer Claude Code versions don't break the dispatcher. */ export type HookEventName = 'PreToolUse' | 'PostToolUse' | 'SessionStart' | 'Stop' | 'UserPromptSubmit' | 'SubagentStart' | 'Notification' | 'PreCompact' | 'SessionEnd'; export interface HookInput { session_id: string; transcript_path?: string; cwd: string; hook_event_name: HookEventName; source?: 'startup' | 'resume' | 'clear' | 'compact'; permission_mode?: string; stop_hook_active?: boolean; /** Stop / SubagentStop: the final assistant message text of the turn. */ last_assistant_message?: string | null; tool_name?: string; tool_input?: Record; tool_response?: unknown; tool_use_id?: string; /** * Present only when the hook fires inside a subagent (Claude Code docs: * "Absent for the main thread, even in --agent sessions"). The one field * that identifies a subagent; forwarded to the server as `agentId`. */ agent_id?: string; /** The subagent's type. Also set in --agent sessions, so never a subagent marker. */ agent_type?: string; } /** PreToolUse decision shape recognised by Claude Code. */ export type PermissionDecision = 'allow' | 'deny' | 'ask' | 'defer'; export interface PreToolUseDecision { hookSpecificOutput: { hookEventName: 'PreToolUse'; permissionDecision?: PermissionDecision; permissionDecisionReason?: string; additionalContext?: string; updatedInput?: Record; }; } export interface PostToolUseDecision { hookSpecificOutput: { hookEventName: 'PostToolUse'; additionalContext?: string; }; } /** Lifecycle decision shapes that can carry additionalContext. */ export interface AdditionalContextDecision { hookSpecificOutput: { hookEventName: 'SessionStart' | 'SubagentStart' | 'UserPromptSubmit' | 'PreCompact' | 'SessionEnd'; additionalContext?: string; }; } /** Stop decision — `decision:"block"` refuses to let the agent end the turn. */ export interface StopDecision { decision?: 'block'; reason?: string; hookSpecificOutput?: { hookEventName: 'Stop'; additionalContext?: string; }; } /** Bypass envelope wired through MNEMONIK_BYPASS_HOOKS=1. */ export type BypassType = 'typo' | 'formatting' | 'regenerated' | 'already_grounded' | 'other'; export interface BypassEnvelope { active: boolean; type: BypassType | null; detail: string | null; /** When non-null, the bypass envelope was malformed and the gate must NOT * honour it — we'd rather fail open silently than silently let a typo * in MNEMONIK_BYPASS_TYPE turn into a permanent escape hatch. */ invalidReason: string | null; } /** * Server response from /api/v1/hooks/policy-reminder. * * Each entry is a context-triggered policy whose trigger_keywords overlap * the action context the dispatcher posted. The dispatcher emits these as * additionalContext (Claude Code) / systemMessage (Codex). The `kind` * tells the agent whether the policy is forbidden / mustConfirm / etc., * the `description` is the human-readable rule text, and `replacement` * (when set) is suggested alternative phrasing. */ export interface PolicyReminderResponse { ok: boolean; reason?: 'unauthorized' | 'no_project' | 'no_context' | 'lookup_failed'; policies: Array<{ id: string; kind: string; pattern: string; description: string | null; replacement: string | null; }>; } /** * Server response from /api/v1/hooks/injections — server-driven * injection lookup. Each entry is an already-rendered envelope the * dispatcher should emit in the host-native surface indicated by * `deliveryHint`. The bus has already applied dedup + work-state hold * + per-signal kill switches, so the dispatcher is a thin renderer. */ export interface InjectionsResponse { conversationId?: string; ok: boolean; /** * `project_unavailable`: the folder's projectId names no project this * account can use. On SessionStart and UserPromptSubmit the reply then * carries the notice that says so (and the machine-health block), for the * host to render; see `renderableInjections`. */ reason?: 'unauthorized' | 'no_project' | 'project_unavailable'; injections: Array<{ signal: string; kind: 'directive' | 'nudge' | 'recall'; envelope: string; deliveryHint: 'additional_context' | 'system_message'; /** * For policy_check_required only: which kind of gate fired. `'deny'` * (forbiddenCommand — a hard line) or `'ask'` (mustConfirm — confirm then * proceed). Both block the call with `permissionDecision: 'deny'` and return * the reason to the agent; the value only selects telemetry + reason framing, * NOT the host decision shape. We never emit `permissionDecision: 'ask'`, * which would render an IDE permission dialog a remote user can't answer — * the agent asks the user in chat instead. Absent ⇒ default to `'deny'`, so * an old server that omits the field never downgrades a forbiddenCommand. */ hostDecision?: 'deny' | 'ask'; /** Private capture metadata. Never included in additionalContext text. */ creditCapture?: { deliveryRef: string; expectedFragmentHashes: string[]; rendererVersion: string; }; /** * JIT client-receipt ref on a recall-field `jit_required` entry. Persisted * only once the entry's rendered text is in the final hook output, then * acknowledged as `jitDeliveryAcks` on the next /hooks/injections request. */ deliveryRef?: string; }>; confirmedDeliveryRefs?: string[]; confirmedJitDeliveryRefs?: string[]; discardedJitDeliveryRefs?: string[]; } export interface DeliveryConfirmation { deliveryRef: string; emittedHashes: string[]; transcriptHashes: string[]; positions: Array<{ transcriptRef: string; byteOffset: number; contentIndex: number; fragmentIndex: number; }>; rendererVersion: string; /** Per-session fingerprint salt, disclosed only with the signed delivery ref. */ sessionSalt: string; } export interface TokenFingerprint { hash: string; length: number; } export interface ActionObservationPayload { event: 'pre_tool_use' | 'post_tool_use' | 'user_prompt' | 'compaction' | 'session_end'; nativeActionRef: string; seq?: number; transcriptRef?: string; actionClass: string; targetHashes: string[]; argHash?: string; resultHash?: string; exitStatus?: string; fingerprintSet: { args: TokenFingerprint[]; preImage: TokenFingerprint[]; }; } /** * Server response from /api/v1/hooks/jit — JIT Knowledge Injector gate * result. Per docs/development/jit-knowledge-injector.md §2.4, the envelope * is a `` block (or null when * the gate didn't fire), memoryIds lets the caller dedup against * already-surfaced memories on subsequent calls, and verdicts carries the * per-memory classification for telemetry. */ export interface JitResponse { envelope: string | null; memoryIds: string[]; verdicts: Array<{ id: string; verdict: string; reason: string; }>; deliveryRef?: string; } /** * Server response from /api/v1/hooks/ambient-recall. The envelope is a * `` block, or null when the gate surfaced nothing. * memoryIds lets the caller dedup against already-surfaced memories. * * Deliberately NOT JitResponse: ambient carries no `verdicts` array, because * there is no per-memory classification step — the gate is cosine similarity * plus truth state, not a ghost-disagreement verdict. */ export interface AmbientResponse { envelope: string | null; memoryIds: string[]; } /** Server response from /api/v1/hooks/file-context (auto-fetch path). */ export interface FileContextResponse { ok: boolean; reason?: 'unauthorized' | 'no_project' | 'no_file' | 'lookup_failed'; fileContext: { file: string; codeMemoriesCount: number; relatedMemoriesCount: number; summary: string; } | null; } /** * Server snapshot returned by /api/v1/hooks/snapshot. * * Phase 3 (0.5.0): the dispatcher no longer reads `policies` from the * snapshot — policy matching is fully server-side via * /api/v1/hooks/policy-reminder. The snapshot is still used for * coveredFiles + editedFiles + checkpoint/snapshot lifecycle fields. * The `policies` field is retained for backwards compatibility with * server versions that still populate it. */ export interface SnapshotResponse { ok: boolean; reason?: 'unauthorized' | 'no_project' | 'no_active_session'; coveredFiles: string[]; editedFiles: string[]; policies?: { forbiddenCommand: Array<{ pattern: string; description: string; }>; mustConfirm: Array<{ pattern: string; description: string; replacement: string | null; }>; }; sessionsMerged: number; now: number; lastCheckpointAt?: number | null; lastSnapshotAt?: number | null; lastSnapshotMemoryId?: string | null; hasRecentSnapshot?: boolean; } /** * Server response from /api/v1/hooks/mcp-precheck. * Cat 1-9 rule-engine verdict for an about-to-run Mnemonik MCP call. * See server migration 075 + cursor-hooks.plan.md §6 / §16. */ export interface McpPrecheckResponse { conversationId?: string; ok: boolean; /** Failure reason when ok=false (unauthorized | missing_required_field | no_project | ...) */ reason?: string; verdict?: 'allow' | 'deny' | 'ask'; /** Rule identifier when verdict is deny/ask. */ ruleId?: string; /** Rendered rule message_template when verdict is deny/ask. */ agentMessage?: string; cacheHint?: { ttlSeconds: number; } | null; } /** Server response from /api/v1/hooks/pre-compact (P5). */ export interface PreCompactResponse { ok: boolean; reason?: 'unauthorized' | 'no_project' | 'no_active_session' | 'save_failed'; memoryId: string | null; snapshotMemoryId: string | null; suppressedDuplicate?: boolean; } /** * Minimal config read from ~/.mnemonik/scanner.json (apiKey + server). * * Note: gate enforcement modes (preEdit / preBash / Stop / lifecycle * handlers) are deliberately NOT user-configurable. They are package-level * constants in `behavior.ts`, controlled by us as we mature each gate * across versions. Customers should not have to reason about which knob * to flip — the package version they install IS the contract. The `kind` * of any project policy they declare (forbiddenCommand / mustConfirm) * already encodes their intent and is honored independently. */ export interface HookConfig { apiKey: HookCredential | null; server: string; } export declare const DEFAULT_SERVER = "https://api.mnemonik.dev";