/** * ActionExecutor — Triggers OpenClaw agent turn via CLI * * Uses `openclaw agent` to inject inbound messages. * Agent replies via clawlink_send_message tool call. */ import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; import { logger } from '../util/logger.js'; import type { ChannelDelta } from './types.js'; const execFileAsync = promisify(execFile); export class ActionExecutor { private accountId = 'default'; private openclawBin = 'openclaw'; setAccountId(accountId: string): void { this.accountId = accountId || 'default'; } async handleDelta(delta: ChannelDelta): Promise { const text = this._formatDeltaText(delta); logger.info(`[executor] INBOUND ch=${delta.channelId} msgs=${delta.messageCount} mention=${delta.hasMention}`); try { const args = [ 'agent', '--agent', 'main', '--channel', 'clawlink', '--message', text, '--session-id', `clawlink:${delta.channelId}`, '--json', ]; const { stdout, stderr } = await execFileAsync(this.openclawBin, args, { timeout: 120_000, env: { ...process.env }, }); if (stderr) { logger.debug(`[executor] stderr: ${stderr.substring(0, 500)}`); } if (stdout) { logger.info(`[executor] Agent turn completed for ch=${delta.channelId}`); } } catch (err) { logger.error(`[executor] Agent turn failed: ${(err as Error).message}`); } } private _formatDeltaText(delta: ChannelDelta): string { const { channelId, messages, hasMention } = delta; const mentionTag = hasMention ? ' ⚡ @mention' : ''; let messageSection: string; if (messages.length === 1) { const m = messages[0]!; messageSection = `[Channel ${channelId}${mentionTag}] ${m.nick}: ${m.text}`; } else { const summary = messages.map(m => `[${m.nick}] ${m.text}`).join('\n'); messageSection = `📬 ${messages.length} new messages in channel ${channelId}${mentionTag}:\n${summary}`; } const instruction = `[INSTRUCTION: Reply ONLY in channel ${channelId} using clawlink_send_message. After calling the tool, respond with ONLY: NO_REPLY (this prevents your reply from leaking to the chat UI). Do NOT visit other channels. Do NOT send status reports. Just reply naturally to the conversation above, or stay silent if you have nothing to add.]`; return `${messageSection}\n\n${instruction}`; } }