/** * Pi Chat-Message Activation Adapter — `src/platform/adapters/pi/chat-activation.ts` * (subtask S8) * * Detects user messages on the Pi platform and runs the shared opencode * `handleChatMessage` pipeline (`src/hooks/chat-message.ts`) against them, so * function activation works on Pi exactly like the opencode `chat.message` * hook (hook-service.ts:207-213) without an opencode hook host: * * - `|fn|` parsing / activation (`parseFunctionActivation` + * `functionSessionState.activate`) * - auto-activation on the first user message * (`roleAutoActivateMap` / `roleLockedMap` → `activateDefaults`) * - wake-event unblocking for gated functions (dispatch notifications and * `[HITL APPROVAL REQUIRED]` messages clear `phase: "gated"`) * - `sessionAgentRegistry` recording * * User-message detection has two paths: * * 1. **Primary (live events):** `pi.on("message_start")` events whose * `message.role === "user"`. The user text is read from * `message.content` (a plain string for UserMessage, or a content * array of text entries). * 2. **Fallback (session restore):** when the event does not identify a * user message (legacy Pi versions without `message.role`, or a * resumed session that does not re-emit the current user message), * the LAST user message of the invoking session is read from the Pi * JSONL session file through `deps.session.messages(sessionID)` and * replayed through the pipeline — this restores function activation * state after an extension reload or `/resume`, when the in-memory * hook state was lost. * * Synthetic injections are skipped **exactly as chat-message.ts:26-29**: * * - On the live event path the shared pipeline applies that predicate * internally (the same behavior as opencode, where synthetic re-entries * DO pass through the `chat.message` hook so wake-event unblocking for * dispatch notifications still fires). * - On the JSONL fallback path the same predicate is applied here, so a * historical synthetic injection (e.g. a persisted loop-progress marker * or dispatch notification) is never replayed as a genuine user message. * * A per-session dedup map (keyed by the message id when available) prevents * a message from being fed through the pipeline twice — whether re-emitted * by Pi or re-read from the JSONL fallback. * * @module */ import type { HookDeps } from "../../../hooks/deps.ts"; import type { HookState } from "../../../hooks/state.ts"; /** * Clear the per-session dedup map (test/teardown helper). */ export declare function resetPiChatActivationDedup(): void; export interface PiChatActivationRunOptions { /** * Pi extension context — `sessionManager.getSessionId()` resolves the * session id; `ctx.agent` is an agent fallback (same chain as the S7 * system-transform adapter). */ ctx?: Record | undefined; /** S6 hook state (pendingCorrections, sessionAgentRegistry, …). */ state: HookState; /** S6 hook deps (session client, role maps, custom hooks, …). */ deps: HookDeps; /** Pi active-agent ref (role switcher) used as the agent fallback. */ activeAgent?: { get(): string | null; } | undefined; } export interface PiChatActivationResult { /** True when the message was fed through the chat-message pipeline. */ processed: boolean; } /** * Run the shared `handleChatMessage` pipeline against a Pi `message_start` * event (or the session's last JSONL user message when the event does not * identify one). Resolves session id / agent via the shared Pi helpers, * builds the `{ agent, sessionID }` input + `{ parts: [{ type: "text", * text }] }` output shape the opencode `chat.message` hook uses, and * delegates to the pipeline with the S6 `state` + `deps`. * * Returns `{ processed: true }` when the pipeline ran, `{ processed: false }` * when there was nothing to run (no session id, assistant message with no * replayable user message, dedup hit, or synthetic fallback). Errors are * swallowed and logged — a detection defect must never break the Pi runtime. */ export declare function runPiChatActivation(event: Record, options: PiChatActivationRunOptions): Promise; export interface WirePiChatActivationOptions { /** Pi ExtensionAPI instance (loosely typed optional peer dependency). */ pi: any; /** S6 hook state (the shared pipeline's `state`). */ state: HookState; /** S6 hook deps (the shared pipeline's `deps`). */ deps: HookDeps; /** Pi active-agent ref (role switcher) used as the agent fallback. */ activeAgent?: { get(): string | null; } | undefined; /** * Opt-out switch (child-process mode, subtask S2). When `false` the * wiring is skipped entirely — no `message_start` subscription is * created and `handleChatMessage` is never invoked, so a spawned Pi * subagent does not re-run the parent-side chat-activation machinery on * top of the `--append-system-prompt` it already received. Defaults to * `true`. */ enabled?: boolean; } export interface PiChatActivationWireResult { /** * Remove the subscription. Pi's `pi.on` has no unsubscribe API, so this is * a no-op kept for uniform shutdown — same convention as * `wirePiSessionStatusEvents`. */ unsubscribe: () => void; } /** * Subscribe `pi.on("message_start")` and route every event through * `runPiChatActivation` using the shared S6 pipeline state/deps. Degrades * gracefully when the Pi API lacks `.on()`. */ export declare function wirePiChatActivation(options: WirePiChatActivationOptions): PiChatActivationWireResult; //# sourceMappingURL=chat-activation.d.ts.map