feat(memory): add lifecycle hooks and vector memory plugin

Add plugin lifecycle hooks infrastructure:
- before_agent_start: inject context before agent loop
- agent_end: analyze conversation after completion
- 13 hook types total (message, tool, session, gateway hooks)

Memory plugin implementation:
- LanceDB vector storage with OpenAI embeddings
- kind: "memory" to integrate with upstream slot system
- Auto-recall: injects <relevant-memories> when context found
- Auto-capture: stores preferences, decisions, entities
- Rule-based capture filtering with 0.95 similarity dedup
- Tools: memory_recall, memory_store, memory_forget
- CLI: clawdbot ltm list|search|stats

Plugin infrastructure:
- api.on() method for hook registration
- Global hook runner singleton for cross-module access
- Priority ordering and error catching

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Radek Paclt
2026-01-18 05:40:58 +00:00
parent ac1b2d8c40
commit ebfeb7a6bf
11 changed files with 2073 additions and 2 deletions

View File

@@ -64,8 +64,9 @@ import { prepareSessionManagerForRun } from "../session-manager-init.js";
import { buildEmbeddedSystemPrompt, createSystemPromptOverride } from "../system-prompt.js";
import { splitSdkTools } from "../tool-split.js";
import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../../date-time.js";
import { mapThinkingLevel } from "../utils.js";
import { describeUnknownError, mapThinkingLevel } from "../utils.js";
import { resolveSandboxRuntimeStatus } from "../../sandbox/runtime-status.js";
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
import type { EmbeddedRunAttemptParams, EmbeddedRunAttemptResult } from "./types.js";
@@ -458,9 +459,40 @@ export async function runEmbeddedAttempt(
}
}
// Get hook runner once for both before_agent_start and agent_end hooks
const hookRunner = getGlobalHookRunner();
let promptError: unknown = null;
try {
const promptStartedAt = Date.now();
// Run before_agent_start hooks to allow plugins to inject context
let effectivePrompt = params.prompt;
if (hookRunner?.hasHooks("before_agent_start")) {
try {
const hookResult = await hookRunner.runBeforeAgentStart(
{
prompt: params.prompt,
messages: activeSession.messages,
},
{
agentId: params.sessionKey?.split(":")[0] ?? "main",
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,
},
);
if (hookResult?.prependContext) {
effectivePrompt = `${hookResult.prependContext}\n\n${params.prompt}`;
log.debug(
`hooks: prepended context to prompt (${hookResult.prependContext.length} chars)`,
);
}
} catch (hookErr) {
log.warn(`before_agent_start hook failed: ${String(hookErr)}`);
}
}
log.debug(`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`);
// Repair orphaned trailing user messages so new prompts don't violate role ordering.
@@ -480,7 +512,7 @@ export async function runEmbeddedAttempt(
}
try {
await abortable(activeSession.prompt(params.prompt, { images: params.images }));
await abortable(activeSession.prompt(effectivePrompt, { images: params.images }));
} catch (err) {
promptError = err;
} finally {
@@ -501,6 +533,29 @@ export async function runEmbeddedAttempt(
messagesSnapshot = activeSession.messages.slice();
sessionIdUsed = activeSession.sessionId;
// Run agent_end hooks to allow plugins to analyze the conversation
// This is fire-and-forget, so we don't await
if (hookRunner?.hasHooks("agent_end")) {
hookRunner
.runAgentEnd(
{
messages: messagesSnapshot,
success: !aborted && !promptError,
error: promptError ? describeUnknownError(promptError) : undefined,
durationMs: Date.now() - promptStartedAt,
},
{
agentId: params.sessionKey?.split(":")[0] ?? "main",
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,
},
)
.catch((err) => {
log.warn(`agent_end hook failed: ${err}`);
});
}
} finally {
clearTimeout(abortTimer);
if (abortWarnTimer) clearTimeout(abortWarnTimer);

View File

@@ -5,6 +5,7 @@ export const createTestRegistry = (overrides: Partial<PluginRegistry> = {}): Plu
plugins: [],
tools: [],
hooks: [],
typedHooks: [],
channels: [],
providers: [],
gatewayHandlers: {},

View File

@@ -0,0 +1,67 @@
/**
* Global Plugin Hook Runner
*
* Singleton hook runner that's initialized when plugins are loaded
* and can be called from anywhere in the codebase.
*/
import { createSubsystemLogger } from "../logging.js";
import { createHookRunner, type HookRunner } from "./hooks.js";
import type { PluginRegistry } from "./registry.js";
const log = createSubsystemLogger("plugins");
let globalHookRunner: HookRunner | null = null;
let globalRegistry: PluginRegistry | null = null;
/**
* Initialize the global hook runner with a plugin registry.
* Called once when plugins are loaded during gateway startup.
*/
export function initializeGlobalHookRunner(registry: PluginRegistry): void {
globalRegistry = registry;
globalHookRunner = createHookRunner(registry, {
logger: {
debug: (msg) => log.debug(msg),
warn: (msg) => log.warn(msg),
error: (msg) => log.error(msg),
},
catchErrors: true,
});
const hookCount = registry.hooks.length;
if (hookCount > 0) {
log.info(`hook runner initialized with ${hookCount} registered hooks`);
}
}
/**
* Get the global hook runner.
* Returns null if plugins haven't been loaded yet.
*/
export function getGlobalHookRunner(): HookRunner | null {
return globalHookRunner;
}
/**
* Get the global plugin registry.
* Returns null if plugins haven't been loaded yet.
*/
export function getGlobalPluginRegistry(): PluginRegistry | null {
return globalRegistry;
}
/**
* Check if any hooks are registered for a given hook name.
*/
export function hasGlobalHooks(hookName: Parameters<HookRunner["hasHooks"]>[0]): boolean {
return globalHookRunner?.hasHooks(hookName) ?? false;
}
/**
* Reset the global hook runner (for testing).
*/
export function resetGlobalHookRunner(): void {
globalHookRunner = null;
globalRegistry = null;
}

400
src/plugins/hooks.ts Normal file
View File

@@ -0,0 +1,400 @@
/**
* Plugin Hook Runner
*
* Provides utilities for executing plugin lifecycle hooks with proper
* error handling, priority ordering, and async support.
*/
import type { PluginRegistry } from "./registry.js";
import type {
PluginHookAfterCompactionEvent,
PluginHookAfterToolCallEvent,
PluginHookAgentContext,
PluginHookAgentEndEvent,
PluginHookBeforeAgentStartEvent,
PluginHookBeforeAgentStartResult,
PluginHookBeforeCompactionEvent,
PluginHookBeforeToolCallEvent,
PluginHookBeforeToolCallResult,
PluginHookGatewayContext,
PluginHookGatewayStartEvent,
PluginHookGatewayStopEvent,
PluginHookMessageContext,
PluginHookMessageReceivedEvent,
PluginHookMessageSendingEvent,
PluginHookMessageSendingResult,
PluginHookMessageSentEvent,
PluginHookName,
PluginHookRegistration,
PluginHookSessionContext,
PluginHookSessionEndEvent,
PluginHookSessionStartEvent,
PluginHookToolContext,
} from "./types.js";
// Re-export types for consumers
export type {
PluginHookAgentContext,
PluginHookBeforeAgentStartEvent,
PluginHookBeforeAgentStartResult,
PluginHookAgentEndEvent,
PluginHookBeforeCompactionEvent,
PluginHookAfterCompactionEvent,
PluginHookMessageContext,
PluginHookMessageReceivedEvent,
PluginHookMessageSendingEvent,
PluginHookMessageSendingResult,
PluginHookMessageSentEvent,
PluginHookToolContext,
PluginHookBeforeToolCallEvent,
PluginHookBeforeToolCallResult,
PluginHookAfterToolCallEvent,
PluginHookSessionContext,
PluginHookSessionStartEvent,
PluginHookSessionEndEvent,
PluginHookGatewayContext,
PluginHookGatewayStartEvent,
PluginHookGatewayStopEvent,
};
export type HookRunnerLogger = {
debug?: (message: string) => void;
warn: (message: string) => void;
error: (message: string) => void;
};
export type HookRunnerOptions = {
logger?: HookRunnerLogger;
/** If true, errors in hooks will be caught and logged instead of thrown */
catchErrors?: boolean;
};
/**
* Get hooks for a specific hook name, sorted by priority (higher first).
*/
function getHooksForName<K extends PluginHookName>(
registry: PluginRegistry,
hookName: K,
): PluginHookRegistration<K>[] {
return (registry.typedHooks as PluginHookRegistration<K>[])
.filter((h) => h.hookName === hookName)
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
}
/**
* Create a hook runner for a specific registry.
*/
export function createHookRunner(registry: PluginRegistry, options: HookRunnerOptions = {}) {
const logger = options.logger;
const catchErrors = options.catchErrors ?? true;
/**
* Run a hook that doesn't return a value (fire-and-forget style).
* All handlers are executed in parallel for performance.
*/
async function runVoidHook<K extends PluginHookName>(
hookName: K,
event: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[0],
ctx: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[1],
): Promise<void> {
const hooks = getHooksForName(registry, hookName);
if (hooks.length === 0) return;
logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers)`);
const promises = hooks.map(async (hook) => {
try {
await (hook.handler as (event: unknown, ctx: unknown) => Promise<void>)(event, ctx);
} catch (err) {
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
if (catchErrors) {
logger?.error(msg);
} else {
throw new Error(msg);
}
}
});
await Promise.all(promises);
}
/**
* Run a hook that can return a modifying result.
* Handlers are executed sequentially in priority order, and results are merged.
*/
async function runModifyingHook<K extends PluginHookName, TResult>(
hookName: K,
event: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[0],
ctx: Parameters<NonNullable<PluginHookRegistration<K>["handler"]>>[1],
mergeResults?: (accumulated: TResult | undefined, next: TResult) => TResult,
): Promise<TResult | undefined> {
const hooks = getHooksForName(registry, hookName);
if (hooks.length === 0) return undefined;
logger?.debug?.(`[hooks] running ${hookName} (${hooks.length} handlers, sequential)`);
let result: TResult | undefined;
for (const hook of hooks) {
try {
const handlerResult = await (
hook.handler as (event: unknown, ctx: unknown) => Promise<TResult>
)(event, ctx);
if (handlerResult !== undefined && handlerResult !== null) {
if (mergeResults && result !== undefined) {
result = mergeResults(result, handlerResult);
} else {
result = handlerResult;
}
}
} catch (err) {
const msg = `[hooks] ${hookName} handler from ${hook.pluginId} failed: ${String(err)}`;
if (catchErrors) {
logger?.error(msg);
} else {
throw new Error(msg);
}
}
}
return result;
}
// =========================================================================
// Agent Hooks
// =========================================================================
/**
* Run before_agent_start hook.
* Allows plugins to inject context into the system prompt.
* Runs sequentially, merging systemPrompt and prependContext from all handlers.
*/
async function runBeforeAgentStart(
event: PluginHookBeforeAgentStartEvent,
ctx: PluginHookAgentContext,
): Promise<PluginHookBeforeAgentStartResult | undefined> {
return runModifyingHook<"before_agent_start", PluginHookBeforeAgentStartResult>(
"before_agent_start",
event,
ctx,
(acc, next) => ({
systemPrompt: next.systemPrompt ?? acc?.systemPrompt,
prependContext:
acc?.prependContext && next.prependContext
? `${acc.prependContext}\n\n${next.prependContext}`
: (next.prependContext ?? acc?.prependContext),
}),
);
}
/**
* Run agent_end hook.
* Allows plugins to analyze completed conversations.
* Runs in parallel (fire-and-forget).
*/
async function runAgentEnd(
event: PluginHookAgentEndEvent,
ctx: PluginHookAgentContext,
): Promise<void> {
return runVoidHook("agent_end", event, ctx);
}
/**
* Run before_compaction hook.
*/
async function runBeforeCompaction(
event: PluginHookBeforeCompactionEvent,
ctx: PluginHookAgentContext,
): Promise<void> {
return runVoidHook("before_compaction", event, ctx);
}
/**
* Run after_compaction hook.
*/
async function runAfterCompaction(
event: PluginHookAfterCompactionEvent,
ctx: PluginHookAgentContext,
): Promise<void> {
return runVoidHook("after_compaction", event, ctx);
}
// =========================================================================
// Message Hooks
// =========================================================================
/**
* Run message_received hook.
* Runs in parallel (fire-and-forget).
*/
async function runMessageReceived(
event: PluginHookMessageReceivedEvent,
ctx: PluginHookMessageContext,
): Promise<void> {
return runVoidHook("message_received", event, ctx);
}
/**
* Run message_sending hook.
* Allows plugins to modify or cancel outgoing messages.
* Runs sequentially.
*/
async function runMessageSending(
event: PluginHookMessageSendingEvent,
ctx: PluginHookMessageContext,
): Promise<PluginHookMessageSendingResult | undefined> {
return runModifyingHook<"message_sending", PluginHookMessageSendingResult>(
"message_sending",
event,
ctx,
(acc, next) => ({
content: next.content ?? acc?.content,
cancel: next.cancel ?? acc?.cancel,
}),
);
}
/**
* Run message_sent hook.
* Runs in parallel (fire-and-forget).
*/
async function runMessageSent(
event: PluginHookMessageSentEvent,
ctx: PluginHookMessageContext,
): Promise<void> {
return runVoidHook("message_sent", event, ctx);
}
// =========================================================================
// Tool Hooks
// =========================================================================
/**
* Run before_tool_call hook.
* Allows plugins to modify or block tool calls.
* Runs sequentially.
*/
async function runBeforeToolCall(
event: PluginHookBeforeToolCallEvent,
ctx: PluginHookToolContext,
): Promise<PluginHookBeforeToolCallResult | undefined> {
return runModifyingHook<"before_tool_call", PluginHookBeforeToolCallResult>(
"before_tool_call",
event,
ctx,
(acc, next) => ({
params: next.params ?? acc?.params,
block: next.block ?? acc?.block,
blockReason: next.blockReason ?? acc?.blockReason,
}),
);
}
/**
* Run after_tool_call hook.
* Runs in parallel (fire-and-forget).
*/
async function runAfterToolCall(
event: PluginHookAfterToolCallEvent,
ctx: PluginHookToolContext,
): Promise<void> {
return runVoidHook("after_tool_call", event, ctx);
}
// =========================================================================
// Session Hooks
// =========================================================================
/**
* Run session_start hook.
* Runs in parallel (fire-and-forget).
*/
async function runSessionStart(
event: PluginHookSessionStartEvent,
ctx: PluginHookSessionContext,
): Promise<void> {
return runVoidHook("session_start", event, ctx);
}
/**
* Run session_end hook.
* Runs in parallel (fire-and-forget).
*/
async function runSessionEnd(
event: PluginHookSessionEndEvent,
ctx: PluginHookSessionContext,
): Promise<void> {
return runVoidHook("session_end", event, ctx);
}
// =========================================================================
// Gateway Hooks
// =========================================================================
/**
* Run gateway_start hook.
* Runs in parallel (fire-and-forget).
*/
async function runGatewayStart(
event: PluginHookGatewayStartEvent,
ctx: PluginHookGatewayContext,
): Promise<void> {
return runVoidHook("gateway_start", event, ctx);
}
/**
* Run gateway_stop hook.
* Runs in parallel (fire-and-forget).
*/
async function runGatewayStop(
event: PluginHookGatewayStopEvent,
ctx: PluginHookGatewayContext,
): Promise<void> {
return runVoidHook("gateway_stop", event, ctx);
}
// =========================================================================
// Utility
// =========================================================================
/**
* Check if any hooks are registered for a given hook name.
*/
function hasHooks(hookName: PluginHookName): boolean {
return registry.typedHooks.some((h) => h.hookName === hookName);
}
/**
* Get count of registered hooks for a given hook name.
*/
function getHookCount(hookName: PluginHookName): number {
return registry.typedHooks.filter((h) => h.hookName === hookName).length;
}
return {
// Agent hooks
runBeforeAgentStart,
runAgentEnd,
runBeforeCompaction,
runAfterCompaction,
// Message hooks
runMessageReceived,
runMessageSending,
runMessageSent,
// Tool hooks
runBeforeToolCall,
runAfterToolCall,
// Session hooks
runSessionStart,
runSessionEnd,
// Gateway hooks
runGatewayStart,
runGatewayStop,
// Utility
hasHooks,
getHookCount,
};
}
export type HookRunner = ReturnType<typeof createHookRunner>;

View File

@@ -8,6 +8,7 @@ import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
import { createSubsystemLogger } from "../logging.js";
import { resolveUserPath } from "../utils.js";
import { discoverClawdbotPlugins } from "./discovery.js";
import { initializeGlobalHookRunner } from "./hook-runner-global.js";
import { createPluginRegistry, type PluginRecord, type PluginRegistry } from "./registry.js";
import { createPluginRuntime } from "./runtime/index.js";
import { setActivePluginRegistry } from "./runtime.js";
@@ -271,6 +272,7 @@ function createPluginRecord(params: {
cliCommands: [],
services: [],
httpHandlers: 0,
hookCount: 0,
configSchema: params.configSchema,
configUiHints: undefined,
configJsonSchema: undefined,
@@ -521,5 +523,6 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi
registryCache.set(cacheKey, registry);
}
setActivePluginRegistry(registry, cacheKey);
initializeGlobalHookRunner(registry);
return registry;
}

View File

@@ -22,6 +22,9 @@ import type {
PluginLogger,
PluginOrigin,
PluginKind,
PluginHookName,
PluginHookHandlerMap,
PluginHookRegistration as TypedPluginHookRegistration,
} from "./types.js";
import type { PluginRuntime } from "./runtime/types.js";
import type { HookEntry } from "../hooks/types.js";
@@ -94,6 +97,7 @@ export type PluginRecord = {
cliCommands: string[];
services: string[];
httpHandlers: number;
hookCount: number;
configSchema: boolean;
configUiHints?: Record<string, PluginConfigUiHint>;
configJsonSchema?: Record<string, unknown>;
@@ -103,6 +107,7 @@ export type PluginRegistry = {
plugins: PluginRecord[];
tools: PluginToolRegistration[];
hooks: PluginHookRegistration[];
typedHooks: TypedPluginHookRegistration[];
channels: PluginChannelRegistration[];
providers: PluginProviderRegistration[];
gatewayHandlers: GatewayRequestHandlers;
@@ -123,6 +128,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
plugins: [],
tools: [],
hooks: [],
typedHooks: [],
channels: [],
providers: [],
gatewayHandlers: {},
@@ -346,6 +352,22 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
});
};
const registerTypedHook = <K extends PluginHookName>(
record: PluginRecord,
hookName: K,
handler: PluginHookHandlerMap[K],
opts?: { priority?: number },
) => {
record.hookCount += 1;
registry.typedHooks.push({
pluginId: record.id,
hookName,
handler,
priority: opts?.priority,
source: record.source,
} as TypedPluginHookRegistration);
};
const normalizeLogger = (logger: PluginLogger): PluginLogger => ({
info: logger.info,
warn: logger.warn,
@@ -380,6 +402,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
registerCli: (registrar, opts) => registerCli(record, registrar, opts),
registerService: (service) => registerService(record, service),
resolvePath: (input: string) => resolveUserPath(input),
on: (hookName, handler, opts) => registerTypedHook(record, hookName, handler, opts),
};
};
@@ -393,5 +416,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
registerGatewayMethod,
registerCli,
registerService,
registerHook,
registerTypedHook,
};
}

View File

@@ -200,6 +200,12 @@ export type ClawdbotPluginApi = {
registerService: (service: ClawdbotPluginService) => void;
registerProvider: (provider: ProviderPlugin) => void;
resolvePath: (input: string) => string;
/** Register a lifecycle hook handler */
on: <K extends PluginHookName>(
hookName: K,
handler: PluginHookHandlerMap[K],
opts?: { priority?: number },
) => void;
};
export type PluginOrigin = "bundled" | "global" | "workspace" | "config";
@@ -210,3 +216,219 @@ export type PluginDiagnostic = {
pluginId?: string;
source?: string;
};
// ============================================================================
// Plugin Hooks
// ============================================================================
export type PluginHookName =
| "before_agent_start"
| "agent_end"
| "before_compaction"
| "after_compaction"
| "message_received"
| "message_sending"
| "message_sent"
| "before_tool_call"
| "after_tool_call"
| "session_start"
| "session_end"
| "gateway_start"
| "gateway_stop";
// Agent context shared across agent hooks
export type PluginHookAgentContext = {
agentId?: string;
sessionKey?: string;
workspaceDir?: string;
messageProvider?: string;
};
// before_agent_start hook
export type PluginHookBeforeAgentStartEvent = {
prompt: string;
messages?: unknown[];
};
export type PluginHookBeforeAgentStartResult = {
systemPrompt?: string;
prependContext?: string;
};
// agent_end hook
export type PluginHookAgentEndEvent = {
messages: unknown[];
success: boolean;
error?: string;
durationMs?: number;
};
// Compaction hooks
export type PluginHookBeforeCompactionEvent = {
messageCount: number;
tokenCount?: number;
};
export type PluginHookAfterCompactionEvent = {
messageCount: number;
tokenCount?: number;
compactedCount: number;
};
// Message context
export type PluginHookMessageContext = {
channelId: string;
accountId?: string;
conversationId?: string;
};
// message_received hook
export type PluginHookMessageReceivedEvent = {
from: string;
content: string;
timestamp?: number;
metadata?: Record<string, unknown>;
};
// message_sending hook
export type PluginHookMessageSendingEvent = {
to: string;
content: string;
metadata?: Record<string, unknown>;
};
export type PluginHookMessageSendingResult = {
content?: string;
cancel?: boolean;
};
// message_sent hook
export type PluginHookMessageSentEvent = {
to: string;
content: string;
success: boolean;
error?: string;
};
// Tool context
export type PluginHookToolContext = {
agentId?: string;
sessionKey?: string;
toolName: string;
};
// before_tool_call hook
export type PluginHookBeforeToolCallEvent = {
toolName: string;
params: Record<string, unknown>;
};
export type PluginHookBeforeToolCallResult = {
params?: Record<string, unknown>;
block?: boolean;
blockReason?: string;
};
// after_tool_call hook
export type PluginHookAfterToolCallEvent = {
toolName: string;
params: Record<string, unknown>;
result?: unknown;
error?: string;
durationMs?: number;
};
// Session context
export type PluginHookSessionContext = {
agentId?: string;
sessionId: string;
};
// session_start hook
export type PluginHookSessionStartEvent = {
sessionId: string;
resumedFrom?: string;
};
// session_end hook
export type PluginHookSessionEndEvent = {
sessionId: string;
messageCount: number;
durationMs?: number;
};
// Gateway context
export type PluginHookGatewayContext = {
port?: number;
};
// gateway_start hook
export type PluginHookGatewayStartEvent = {
port: number;
};
// gateway_stop hook
export type PluginHookGatewayStopEvent = {
reason?: string;
};
// Hook handler types mapped by hook name
export type PluginHookHandlerMap = {
before_agent_start: (
event: PluginHookBeforeAgentStartEvent,
ctx: PluginHookAgentContext,
) => Promise<PluginHookBeforeAgentStartResult | void> | PluginHookBeforeAgentStartResult | void;
agent_end: (event: PluginHookAgentEndEvent, ctx: PluginHookAgentContext) => Promise<void> | void;
before_compaction: (
event: PluginHookBeforeCompactionEvent,
ctx: PluginHookAgentContext,
) => Promise<void> | void;
after_compaction: (
event: PluginHookAfterCompactionEvent,
ctx: PluginHookAgentContext,
) => Promise<void> | void;
message_received: (
event: PluginHookMessageReceivedEvent,
ctx: PluginHookMessageContext,
) => Promise<void> | void;
message_sending: (
event: PluginHookMessageSendingEvent,
ctx: PluginHookMessageContext,
) => Promise<PluginHookMessageSendingResult | void> | PluginHookMessageSendingResult | void;
message_sent: (
event: PluginHookMessageSentEvent,
ctx: PluginHookMessageContext,
) => Promise<void> | void;
before_tool_call: (
event: PluginHookBeforeToolCallEvent,
ctx: PluginHookToolContext,
) => Promise<PluginHookBeforeToolCallResult | void> | PluginHookBeforeToolCallResult | void;
after_tool_call: (
event: PluginHookAfterToolCallEvent,
ctx: PluginHookToolContext,
) => Promise<void> | void;
session_start: (
event: PluginHookSessionStartEvent,
ctx: PluginHookSessionContext,
) => Promise<void> | void;
session_end: (
event: PluginHookSessionEndEvent,
ctx: PluginHookSessionContext,
) => Promise<void> | void;
gateway_start: (
event: PluginHookGatewayStartEvent,
ctx: PluginHookGatewayContext,
) => Promise<void> | void;
gateway_stop: (
event: PluginHookGatewayStopEvent,
ctx: PluginHookGatewayContext,
) => Promise<void> | void;
};
export type PluginHookRegistration<K extends PluginHookName = PluginHookName> = {
pluginId: string;
hookName: K;
handler: PluginHookHandlerMap[K];
priority?: number;
source: string;
};