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>
435 lines
11 KiB
TypeScript
435 lines
11 KiB
TypeScript
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
import type { Command } from "commander";
|
|
|
|
import type { AuthProfileCredential, OAuthCredential } from "../agents/auth-profiles/types.js";
|
|
import type { AnyAgentTool } from "../agents/tools/common.js";
|
|
import type { ChannelDock } from "../channels/dock.js";
|
|
import type { ChannelPlugin } from "../channels/plugins/types.js";
|
|
import type { ClawdbotConfig } from "../config/config.js";
|
|
import type { InternalHookHandler } from "../hooks/internal-hooks.js";
|
|
import type { HookEntry } from "../hooks/types.js";
|
|
import type { ModelProviderConfig } from "../config/types.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import type { WizardPrompter } from "../wizard/prompts.js";
|
|
import type { createVpsAwareOAuthHandlers } from "../commands/oauth-flow.js";
|
|
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
|
import type { PluginRuntime } from "./runtime/types.js";
|
|
|
|
export type { PluginRuntime } from "./runtime/types.js";
|
|
|
|
export type PluginLogger = {
|
|
debug?: (message: string) => void;
|
|
info: (message: string) => void;
|
|
warn: (message: string) => void;
|
|
error: (message: string) => void;
|
|
};
|
|
|
|
export type PluginConfigUiHint = {
|
|
label?: string;
|
|
help?: string;
|
|
advanced?: boolean;
|
|
sensitive?: boolean;
|
|
placeholder?: string;
|
|
};
|
|
|
|
export type PluginKind = "memory";
|
|
|
|
export type PluginConfigValidation =
|
|
| { ok: true; value?: unknown }
|
|
| { ok: false; errors: string[] };
|
|
|
|
export type ClawdbotPluginConfigSchema = {
|
|
safeParse?: (value: unknown) => {
|
|
success: boolean;
|
|
data?: unknown;
|
|
error?: {
|
|
issues?: Array<{ path: Array<string | number>; message: string }>;
|
|
};
|
|
};
|
|
parse?: (value: unknown) => unknown;
|
|
validate?: (value: unknown) => PluginConfigValidation;
|
|
uiHints?: Record<string, PluginConfigUiHint>;
|
|
jsonSchema?: Record<string, unknown>;
|
|
};
|
|
|
|
export type ClawdbotPluginToolContext = {
|
|
config?: ClawdbotConfig;
|
|
workspaceDir?: string;
|
|
agentDir?: string;
|
|
agentId?: string;
|
|
sessionKey?: string;
|
|
messageChannel?: string;
|
|
agentAccountId?: string;
|
|
sandboxed?: boolean;
|
|
};
|
|
|
|
export type ClawdbotPluginToolFactory = (
|
|
ctx: ClawdbotPluginToolContext,
|
|
) => AnyAgentTool | AnyAgentTool[] | null | undefined;
|
|
|
|
export type ClawdbotPluginToolOptions = {
|
|
name?: string;
|
|
names?: string[];
|
|
optional?: boolean;
|
|
};
|
|
|
|
export type ClawdbotPluginHookOptions = {
|
|
entry?: HookEntry;
|
|
name?: string;
|
|
description?: string;
|
|
register?: boolean;
|
|
};
|
|
|
|
export type ProviderAuthKind = "oauth" | "api_key" | "token" | "device_code" | "custom";
|
|
|
|
export type ProviderAuthResult = {
|
|
profiles: Array<{ profileId: string; credential: AuthProfileCredential }>;
|
|
configPatch?: Partial<ClawdbotConfig>;
|
|
defaultModel?: string;
|
|
notes?: string[];
|
|
};
|
|
|
|
export type ProviderAuthContext = {
|
|
config: ClawdbotConfig;
|
|
agentDir?: string;
|
|
workspaceDir?: string;
|
|
prompter: WizardPrompter;
|
|
runtime: RuntimeEnv;
|
|
isRemote: boolean;
|
|
openUrl: (url: string) => Promise<void>;
|
|
oauth: {
|
|
createVpsAwareHandlers: typeof createVpsAwareOAuthHandlers;
|
|
};
|
|
};
|
|
|
|
export type ProviderAuthMethod = {
|
|
id: string;
|
|
label: string;
|
|
hint?: string;
|
|
kind: ProviderAuthKind;
|
|
run: (ctx: ProviderAuthContext) => Promise<ProviderAuthResult>;
|
|
};
|
|
|
|
export type ProviderPlugin = {
|
|
id: string;
|
|
label: string;
|
|
docsPath?: string;
|
|
aliases?: string[];
|
|
envVars?: string[];
|
|
models?: ModelProviderConfig;
|
|
auth: ProviderAuthMethod[];
|
|
formatApiKey?: (cred: AuthProfileCredential) => string;
|
|
refreshOAuth?: (cred: OAuthCredential) => Promise<OAuthCredential>;
|
|
};
|
|
|
|
export type ClawdbotPluginGatewayMethod = {
|
|
method: string;
|
|
handler: GatewayRequestHandler;
|
|
};
|
|
|
|
export type ClawdbotPluginHttpHandler = (
|
|
req: IncomingMessage,
|
|
res: ServerResponse,
|
|
) => Promise<boolean> | boolean;
|
|
|
|
export type ClawdbotPluginCliContext = {
|
|
program: Command;
|
|
config: ClawdbotConfig;
|
|
workspaceDir?: string;
|
|
logger: PluginLogger;
|
|
};
|
|
|
|
export type ClawdbotPluginCliRegistrar = (ctx: ClawdbotPluginCliContext) => void | Promise<void>;
|
|
|
|
export type ClawdbotPluginServiceContext = {
|
|
config: ClawdbotConfig;
|
|
workspaceDir?: string;
|
|
stateDir: string;
|
|
logger: PluginLogger;
|
|
};
|
|
|
|
export type ClawdbotPluginService = {
|
|
id: string;
|
|
start: (ctx: ClawdbotPluginServiceContext) => void | Promise<void>;
|
|
stop?: (ctx: ClawdbotPluginServiceContext) => void | Promise<void>;
|
|
};
|
|
|
|
export type ClawdbotPluginChannelRegistration = {
|
|
plugin: ChannelPlugin;
|
|
dock?: ChannelDock;
|
|
};
|
|
|
|
export type ClawdbotPluginDefinition = {
|
|
id?: string;
|
|
name?: string;
|
|
description?: string;
|
|
version?: string;
|
|
kind?: PluginKind;
|
|
configSchema?: ClawdbotPluginConfigSchema;
|
|
register?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
|
activate?: (api: ClawdbotPluginApi) => void | Promise<void>;
|
|
};
|
|
|
|
export type ClawdbotPluginModule =
|
|
| ClawdbotPluginDefinition
|
|
| ((api: ClawdbotPluginApi) => void | Promise<void>);
|
|
|
|
export type ClawdbotPluginApi = {
|
|
id: string;
|
|
name: string;
|
|
version?: string;
|
|
description?: string;
|
|
source: string;
|
|
config: ClawdbotConfig;
|
|
pluginConfig?: Record<string, unknown>;
|
|
runtime: PluginRuntime;
|
|
logger: PluginLogger;
|
|
registerTool: (
|
|
tool: AnyAgentTool | ClawdbotPluginToolFactory,
|
|
opts?: ClawdbotPluginToolOptions,
|
|
) => void;
|
|
registerHook: (
|
|
events: string | string[],
|
|
handler: InternalHookHandler,
|
|
opts?: ClawdbotPluginHookOptions,
|
|
) => void;
|
|
registerHttpHandler: (handler: ClawdbotPluginHttpHandler) => void;
|
|
registerChannel: (registration: ClawdbotPluginChannelRegistration | ChannelPlugin) => void;
|
|
registerGatewayMethod: (method: string, handler: GatewayRequestHandler) => void;
|
|
registerCli: (registrar: ClawdbotPluginCliRegistrar, opts?: { commands?: string[] }) => void;
|
|
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";
|
|
|
|
export type PluginDiagnostic = {
|
|
level: "warn" | "error";
|
|
message: string;
|
|
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;
|
|
};
|