diff --git a/index.ts b/index.ts index fd530d5..9ab88f0 100644 --- a/index.ts +++ b/index.ts @@ -38,10 +38,6 @@ const plugin = { description: "Plugin-level project execution: parallel (each project independent) or sequential (only one project active at a time)", default: "parallel", }, - orchestratorDm: { - type: "string", - description: "Telegram/WhatsApp chat ID for orchestrator DM notifications (heartbeat summaries)", - }, notifications: { type: "object", description: "Notification settings for worker lifecycle events", diff --git a/lib/binding-manager.ts b/lib/binding-manager.ts index 65aa0a8..5aa2179 100644 --- a/lib/binding-manager.ts +++ b/lib/binding-manager.ts @@ -7,7 +7,7 @@ import fs from "node:fs/promises"; import path from "node:path"; -export type ChannelType = "telegram" | "whatsapp"; +export type ChannelType = string; export interface BindingAnalysis { channelEnabled: boolean; diff --git a/lib/context-guard.ts b/lib/context-guard.ts index e0367c3..c1476f6 100644 --- a/lib/context-guard.ts +++ b/lib/context-guard.ts @@ -12,10 +12,10 @@ import path from "node:path"; export type InteractionContext = | { type: "via-agent"; agentId: string; agentName?: string } - | { type: "direct"; channel?: "telegram" | "whatsapp" | "cli" } + | { type: "direct"; channel?: string; chatId?: string } | { type: "group"; - channel: "telegram" | "whatsapp"; + channel: string; groupId: string; projectName?: string; }; @@ -64,7 +64,7 @@ export async function detectContext( return { type: "group", - channel: messageChannel as "telegram" | "whatsapp", + channel: messageChannel, groupId: actualGroupId, projectName, }; @@ -72,11 +72,12 @@ export async function detectContext( } // --- Direct (DM or CLI) --- + // Extract chat ID from sessionKey (e.g. "agent:devclaw:telegram:direct:657120585") + const chatId = sessionKey ? sessionKey.split(":").pop() : undefined; return { type: "direct", - channel: messageChannel - ? (messageChannel as "telegram" | "whatsapp") - : "cli", + channel: messageChannel ?? "cli", + chatId, }; } diff --git a/lib/notify.ts b/lib/notify.ts index 4f6ab05..a12aba6 100644 --- a/lib/notify.ts +++ b/lib/notify.ts @@ -121,35 +121,31 @@ function buildMessage(event: NotifyEvent): string { } /** - * Send a notification message to a Telegram/WhatsApp target. + * Send a notification message via the native OpenClaw messaging CLI. * - * Uses the OpenClaw CLI to invoke the message tool. + * Uses `openclaw message send` which handles target resolution, chunking, + * retries, and error reporting for all supported channels. * Fails silently (logs error but doesn't throw) to avoid breaking the main flow. */ async function sendMessage( target: string, message: string, - channel: "telegram" | "whatsapp", + channel: string, workspaceDir: string, ): Promise { try { - // Use openclaw agent to send via message tool - // The message tool requires action=send, target, message await execFileAsync( "openclaw", [ - "gateway", - "call", - "tools.invoke", - "--params", - JSON.stringify({ - tool: "message", - params: { - action: "send", - target, - message, - }, - }), + "message", + "send", + "--channel", + channel, + "--target", + target, + "--message", + message, + "--json", ], { timeout: 30_000 }, ); @@ -178,8 +174,8 @@ export async function notify( config?: NotificationConfig; /** Target for project-scoped notifications (groupId) */ groupId?: string; - /** Channel type for routing */ - channel?: "telegram" | "whatsapp"; + /** Channel type for routing (e.g. "telegram", "whatsapp", "discord", "slack") */ + channel?: string; /** Target for DM notifications (orchestrator) */ orchestratorDm?: string; }, diff --git a/lib/projects.ts b/lib/projects.ts index 1e509dd..dea6a62 100644 --- a/lib/projects.ts +++ b/lib/projects.ts @@ -23,6 +23,8 @@ export type Project = { baseBranch: string; deployBranch: string; autoChain: boolean; + /** Messaging channel for this project's group (e.g. "telegram", "whatsapp", "discord", "slack"). Stored at registration time. */ + channel?: string; /** Project-level role execution: parallel (DEV+QA can run simultaneously) or sequential (only one role at a time). Default: parallel */ roleExecution?: "parallel" | "sequential"; maxDevWorkers?: number; @@ -133,6 +135,9 @@ export async function readProjects(workspaceDir: string): Promise if (project.autoChain === undefined) { project.autoChain = false; } + if (!project.channel) { + project.channel = "telegram"; + } } return data; diff --git a/lib/tools/heartbeat-tick.ts b/lib/tools/heartbeat-tick.ts index ae60c88..f8090f5 100644 --- a/lib/tools/heartbeat-tick.ts +++ b/lib/tools/heartbeat-tick.ts @@ -519,10 +519,11 @@ export function createHeartbeatTickTool(api: OpenClawPluginApi) { skipped: result.skipped.length, }); - // Send heartbeat notification to orchestrator DM + // Send heartbeat notification back to whoever triggered it + // Both channel and target are derived from context (DM sessionKey) const notifyConfig = getNotificationConfig(pluginConfig); - const orchestratorDm = pluginConfig?.orchestratorDm as string | undefined; - + const orchestratorDm = context.type === "direct" ? context.chatId : undefined; + await notify( { type: "heartbeat", @@ -541,7 +542,7 @@ export function createHeartbeatTickTool(api: OpenClawPluginApi) { workspaceDir, config: notifyConfig, orchestratorDm, - channel: "telegram", + channel: "channel" in context ? context.channel : undefined, }, ); diff --git a/lib/tools/project-register.ts b/lib/tools/project-register.ts index 42d1851..a7d06c4 100644 --- a/lib/tools/project-register.ts +++ b/lib/tools/project-register.ts @@ -161,7 +161,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) { // Provide helpful note if project is already registered const contextInfo = context.projectName ? `Note: This group is already registered as "${context.projectName}". You may be re-registering it.` - : `Registering project for this ${context.channel === "whatsapp" ? "WhatsApp" : "Telegram"} group (ID: ${actualGroupId.substring(0, 20)}...).`; + : `Registering project for this ${context.channel} group (ID: ${actualGroupId.substring(0, 20)}...).`; // 1. Check project not already registered (allow re-register if incomplete) const data = await readProjects(workspaceDir); @@ -205,6 +205,7 @@ export function createProjectRegisterTool(api: OpenClawPluginApi) { baseBranch, deployBranch, autoChain: false, + channel: context.channel, roleExecution, dev: emptyWorkerState([...DEV_TIERS]), qa: emptyWorkerState([...QA_TIERS]), diff --git a/lib/tools/task-complete.ts b/lib/tools/task-complete.ts index da946bd..7efc521 100644 --- a/lib/tools/task-complete.ts +++ b/lib/tools/task-complete.ts @@ -298,8 +298,7 @@ export function createTaskCompleteTool(api: OpenClawPluginApi) { workspaceDir, config: notifyConfig, groupId, - // Channel detection: default to telegram if not available - channel: "telegram", + channel: project.channel ?? "telegram", }, );