diff --git a/plugin/index.ts b/plugin/index.ts index ecc0287..42ae7f3 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -188,56 +188,94 @@ export default { } /** - * Wake/spawn agent with task context for slot execution. - * This is the callback invoked by CalendarScheduler when a slot is ready. + * Wake agent via gateway WebSocket API. + * Uses callGateway("agent") to trigger an agent turn — the same mechanism + * used by sessions_spawn and cron internally. */ async function wakeAgent(context: AgentWakeContext): Promise { logger.info(`Waking agent for slot: ${context.taskDescription}`); + const agentId = process.env.AGENT_ID || 'unknown'; + try { - // Method 1: Use OpenClaw spawn API if available (preferred) - if (api.spawn) { - const result = await api.spawn({ - task: context.prompt, - timeoutSeconds: context.slot.estimated_duration * 60, // Convert to seconds + // Connect to gateway via WebSocket and trigger an agent turn + const gatewayUrl = 'ws://127.0.0.1:18789'; + const ws = await import('ws'); + const WebSocket = ws.default || ws.WebSocket || ws; + + const result = await new Promise<{ sessionId?: string; error?: string }>((resolve, reject) => { + const timeout = setTimeout(() => { + client.close(); + reject(new Error('Gateway connection timeout')); + }, 15000); + + const client = new WebSocket(gatewayUrl); + + client.on('error', (err: Error) => { + clearTimeout(timeout); + reject(err); }); - if (result?.sessionId) { - logger.info(`Agent spawned for calendar slot: session=${result.sessionId}`); + client.on('open', () => { + // Send hello + client.send(JSON.stringify({ + jsonrpc: '2.0', + method: 'hello', + params: { + clientName: 'harbor-forge-calendar', + mode: 'backend', + }, + id: 1, + })); + }); - // Track session completion - trackSessionCompletion(result.sessionId, context); - return true; - } - } + let helloAcked = false; - // Method 2: Send notification/alert to wake agent (fallback) - // This relies on the agent's heartbeat to check for notifications - logger.warn('OpenClaw spawn API not available, using notification fallback'); + client.on('message', (data: Buffer | string) => { + try { + const msg = JSON.parse(data.toString()); - // Send calendar wakeup notification via backend - const live = resolveConfig(); - const agentId = process.env.AGENT_ID || 'unknown'; + if (!helloAcked && msg.id === 1) { + helloAcked = true; + // Send agent turn request + client.send(JSON.stringify({ + jsonrpc: '2.0', + method: 'agent', + params: { + message: context.prompt, + agentId, + timeoutSeconds: context.slot.estimated_duration * 60, + }, + id: 2, + })); + return; + } - const notifyResponse = await fetch(`${live.backendUrl}/calendar/agent/notify`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Agent-ID': agentId, - 'X-Claw-Identifier': live.identifier || hostname(), - }, - body: JSON.stringify({ - agent_id: agentId, - message: context.prompt, - slot_id: context.slot.id || context.slot.virtual_id, - task_description: context.taskDescription, - }), + if (msg.id === 2) { + clearTimeout(timeout); + client.close(); + if (msg.error) { + resolve({ error: msg.error.message || JSON.stringify(msg.error) }); + } else { + resolve({ sessionId: msg.result?.sessionId || 'ok' }); + } + } + } catch { + // ignore parse errors + } + }); }); - return notifyResponse.ok; + if (result.error) { + logger.error(`Gateway agent call failed: ${result.error}`); + return false; + } + + logger.info(`Agent woken via gateway for slot: session=${result.sessionId}`); + return true; } catch (err) { - logger.error('Failed to wake agent:', err); + logger.error('Failed to wake agent via gateway:', err); return false; } }