/** * 発信・発注・発令 Agent * * accessiblePools: [media-pool] * 利用可能ツール: read_site_stats (media-pool, read), * send_email (media-pool, action, passkey-workflow), * post_sns (media-pool, action, passkey-workflow) * * タスク: 企画・Plan Agent が作成した施策を実行する。 * send_email は action ツール (enforcement: workflow)。 * 最初の send_email 呼び出し時に一度だけ Passkey 認証を行い、 * 同じ contextId 内の後続呼び出しはキャッシュされた assertion を再利用する。 */ import type { A2ATaskRequest, A2ATaskResponse } from '../platform-types.js'; import { AgentBase } from '../agent-base.js'; interface Plan { title: string; targetDate: string; campaign: { dessertItem: string }; targetCustomers: Array<{ customerId: string; name: string }>; emailTemplate: string; } export class DispatchAgent extends AgentBase { readonly agentId = 'dispatch-agent'; readonly accessiblePools = ['media-pool']; async handle(task: A2ATaskRequest): Promise { const { plan } = task.payload.parameters as { plan?: Plan }; if (!plan) { return this.error(task, 'No plan provided in task parameters'); } // Set contextId so buildPlatformCallTool can scope the workflow assertion cache this._currentContextId = task.contextId; const results: Array<{ customerId: string; name: string; status: string; error?: string }> = []; for (const customer of plan.targetCustomers) { try { // send_email enforcement: 'workflow'. First call in this contextId triggers // one Passkey ceremony; subsequent calls reuse the cached assertion. await this.callTool('send_email', { to: `${customer.customerId}@example.com`, subject: `【特別ご招待】明日のご来店に無料${plan.campaign.dessertItem}をプレゼント`, body: plan.emailTemplate, customerId: customer.customerId, }); results.push({ customerId: customer.customerId, name: customer.name, status: 'sent' }); console.log(`[dispatch] Email sent to ${customer.name} (${customer.customerId})`); } catch (err) { results.push({ customerId: customer.customerId, name: customer.name, status: 'failed', error: (err as Error).message, }); console.error(`[dispatch] Failed to send to ${customer.name}: ${(err as Error).message}`); } } const sent = results.filter((r) => r.status === 'sent').length; const failed = results.filter((r) => r.status === 'failed').length; console.log(`[dispatch] Campaign dispatch complete: ${sent} sent, ${failed} failed`); return this.success(task, { campaign: plan.title, targetDate: plan.targetDate, dispatch: { sent, failed, total: plan.targetCustomers.length }, results, }); } }