fix(hooks): backport internal message hook bridge with safe delivery semantics

This commit is contained in:
Peter Steinberger
2026-02-18 00:32:51 +01:00
parent 087dca8fa9
commit f07bb8e8fc
8 changed files with 621 additions and 66 deletions

View File

@@ -8,7 +8,7 @@
import type { WorkspaceBootstrapFile } from "../agents/workspace.js";
import type { OpenClawConfig } from "../config/config.js";
export type InternalHookEventType = "command" | "session" | "agent" | "gateway";
export type InternalHookEventType = "command" | "session" | "agent" | "gateway" | "message";
export type AgentBootstrapHookContext = {
workspaceDir: string;
@@ -25,6 +25,60 @@ export type AgentBootstrapHookEvent = InternalHookEvent & {
context: AgentBootstrapHookContext;
};
// ============================================================================
// Message Hook Events
// ============================================================================
export type MessageReceivedHookContext = {
/** Sender identifier (e.g., phone number, user ID) */
from: string;
/** Message content */
content: string;
/** Unix timestamp when the message was received */
timestamp?: number;
/** Channel identifier (e.g., "telegram", "whatsapp") */
channelId: string;
/** Provider account ID for multi-account setups */
accountId?: string;
/** Conversation/chat ID */
conversationId?: string;
/** Message ID from the provider */
messageId?: string;
/** Additional provider-specific metadata */
metadata?: Record<string, unknown>;
};
export type MessageReceivedHookEvent = InternalHookEvent & {
type: "message";
action: "received";
context: MessageReceivedHookContext;
};
export type MessageSentHookContext = {
/** Recipient identifier */
to: string;
/** Message content */
content: string;
/** Whether the message was sent successfully */
success: boolean;
/** Error message if sending failed */
error?: string;
/** Channel identifier (e.g., "telegram", "whatsapp") */
channelId: string;
/** Provider account ID for multi-account setups */
accountId?: string;
/** Conversation/chat ID */
conversationId?: string;
/** Message ID returned by the provider */
messageId?: string;
};
export type MessageSentHookEvent = InternalHookEvent & {
type: "message";
action: "sent";
context: MessageSentHookContext;
};
export interface InternalHookEvent {
/** The type of event (command, session, agent, gateway, etc.) */
type: InternalHookEventType;
@@ -179,3 +233,31 @@ export function isAgentBootstrapEvent(event: InternalHookEvent): event is AgentB
}
return Array.isArray(context.bootstrapFiles);
}
export function isMessageReceivedEvent(
event: InternalHookEvent,
): event is MessageReceivedHookEvent {
if (event.type !== "message" || event.action !== "received") {
return false;
}
const context = event.context as Partial<MessageReceivedHookContext> | null;
if (!context || typeof context !== "object") {
return false;
}
return typeof context.from === "string" && typeof context.channelId === "string";
}
export function isMessageSentEvent(event: InternalHookEvent): event is MessageSentHookEvent {
if (event.type !== "message" || event.action !== "sent") {
return false;
}
const context = event.context as Partial<MessageSentHookContext> | null;
if (!context || typeof context !== "object") {
return false;
}
return (
typeof context.to === "string" &&
typeof context.channelId === "string" &&
typeof context.success === "boolean"
);
}