First running version
This commit is contained in:
108
reference_impl/agents/planning/index.ts
Normal file
108
reference_impl/agents/planning/index.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 企画・Plan Agent
|
||||
*
|
||||
* accessiblePools: [] (pool-less のみ)
|
||||
* 利用可能ツール: query_crm, query_erp, query_hr (全て pool-less)
|
||||
*
|
||||
* タスク: 情報収集結果を受け取り、具体的な施策を立案する。
|
||||
* Passkey (host-only) で店長に承認を求める。
|
||||
*/
|
||||
import type { A2ATaskRequest, A2ATaskResponse } from '../platform-types.js';
|
||||
import { AgentBase } from '../agent-base.js';
|
||||
|
||||
interface AnalysisResult {
|
||||
targetDate: string;
|
||||
weather: { isRainy: boolean; precipitationProbability: number; summary: string };
|
||||
inventory: { hasDessertStock: boolean; dessertsAvailable: Array<{ name: string; stock: number }> };
|
||||
customers: { highCancelTendencyCount: number };
|
||||
reservations: { tomorrowCount: number };
|
||||
actionRecommended: boolean;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export class PlanningAgent extends AgentBase {
|
||||
readonly agentId = 'planning-agent';
|
||||
readonly accessiblePools: string[] = [];
|
||||
|
||||
async handle(task: A2ATaskRequest): Promise<A2ATaskResponse> {
|
||||
const { analysis, rawData } = task.payload.parameters as {
|
||||
analysis?: AnalysisResult;
|
||||
rawData?: unknown;
|
||||
};
|
||||
|
||||
if (!analysis) {
|
||||
return this.error(task, 'No analysis data provided in task parameters');
|
||||
}
|
||||
|
||||
if (!analysis.actionRecommended) {
|
||||
return this.cancelled(task, `施策実施条件未達: ${analysis.summary}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Get today's reservations from CRM for targeting
|
||||
const reservationData = await this.callTool('query_crm', {
|
||||
entity: 'reservations',
|
||||
filter: `date=${analysis.targetDate}`,
|
||||
}) as { reservations?: Array<{ customerName: string; customerId: string }> };
|
||||
|
||||
// 2. Get dessert inventory details from ERP
|
||||
const inventoryData = await this.callTool('query_erp', {
|
||||
entity: 'inventory',
|
||||
item: 'dessert',
|
||||
}) as { items?: Array<{ name: string; stock: number; costPerUnit: number }> };
|
||||
|
||||
// 3. Build the plan
|
||||
const desserts = (inventoryData?.items ?? []).filter((i) => (i.stock ?? 0) > 0);
|
||||
const targetDessert = desserts[0];
|
||||
const targetCustomers = (reservationData?.reservations ?? []).slice(0, analysis.customers.highCancelTendencyCount);
|
||||
|
||||
const plan = {
|
||||
title: '無料デザートキャンペーン',
|
||||
targetDate: analysis.targetDate,
|
||||
rationale: analysis.summary,
|
||||
campaign: {
|
||||
dessertItem: targetDessert?.name ?? 'デザート',
|
||||
freeItemPerCustomer: 1,
|
||||
totalCost: (targetDessert?.costPerUnit ?? 0) * targetCustomers.length,
|
||||
},
|
||||
targetCustomers: targetCustomers.map((c) => ({
|
||||
customerId: c.customerId,
|
||||
name: c.customerName,
|
||||
})),
|
||||
emailTemplate: buildEmailTemplate(analysis.targetDate, targetDessert?.name ?? 'デザート'),
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'pending_approval',
|
||||
};
|
||||
|
||||
console.log(
|
||||
`[planning] Plan created: ${plan.title} for ${targetCustomers.length} customers`,
|
||||
);
|
||||
|
||||
// 4. Approval is handled by the Host (Passkey ceremony triggered by DispatchAgent)
|
||||
// PlanningAgent returns the plan with status: pending_approval.
|
||||
// The orchestration layer (Director → Dispatch) is responsible for auth flow.
|
||||
return this.success(task, {
|
||||
plan,
|
||||
rawData,
|
||||
nextAgent: 'dispatch-agent',
|
||||
});
|
||||
} catch (err) {
|
||||
return this.error(task, (err as Error).message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildEmailTemplate(date: string, dessertName: string): string {
|
||||
return `件名: 【特別ご招待】明日のご来店に無料${dessertName}をプレゼント
|
||||
|
||||
お客様へ
|
||||
|
||||
明日 ${date} のご予約、誠にありがとうございます。
|
||||
|
||||
明日は雨模様のお天気が予想されますが、
|
||||
特別に無料の${dessertName}をご用意しております。
|
||||
|
||||
ぜひお越しください。スタッフ一同、お待ちしております。
|
||||
|
||||
※ 本メールは予約システムより自動送信されています。`;
|
||||
}
|
||||
Reference in New Issue
Block a user