@jcheesepkg/nanobot 0.8.96 → 0.8.98
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/tools/flex.d.mts +7 -0
- package/dist/agent/tools/flex.d.mts.map +1 -1
- package/dist/agent/tools/flex.mjs +16 -1
- package/dist/agent/tools/flex.mjs.map +1 -1
- package/dist/agent/tools/flex.test.mjs +86 -0
- package/dist/agent/tools/flex.test.mjs.map +1 -1
- package/dist/config/schema.d.mts +18 -18
- package/package.json +1 -1
- package/skills/skill-creator/SKILL.md +23 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flex.d.mts","names":[],"sources":["../../../src/agent/tools/flex.ts"],"mappings":";;;;;AASA;;;;;cAAa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA
|
|
1
|
+
{"version":3,"file":"flex.d.mts","names":[],"sources":["../../../src/agent/tools/flex.ts"],"mappings":";;;;;AASA;;;;;cAAa,QAAA,SAAiB,IAAA;EAAA,SACnB,IAAA;EAAA,SACA,WAAA;EAAA,SAEA,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiFH,OAAA,CAAQ,IAAA,EAAM,MAAA,oBAA0B,OAAA;EAAA,QAwCtC,YAAA;EAAA,QAgEA,aAAA;EAAA,QA4BA,kBAAA;EAAA,QAwCA,YAAA;EAAA,QA+CA,mBAAA;EAAA,QAiDA,cAAA;EAAA,QAkCA,WAAA;AAAA"}
|
|
@@ -136,6 +136,11 @@ var FlexTool = class extends Tool {
|
|
|
136
136
|
contents: {
|
|
137
137
|
type: "array",
|
|
138
138
|
description: "Raw Flex body contents array for custom template"
|
|
139
|
+
},
|
|
140
|
+
bubbles: {
|
|
141
|
+
type: "array",
|
|
142
|
+
items: { type: "object" },
|
|
143
|
+
description: "Array of bubble objects for a carousel (horizontal swipeable cards). Each item should be a complete bubble with type, body, etc."
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
}
|
|
@@ -483,6 +488,11 @@ var FlexTool = class extends Tool {
|
|
|
483
488
|
};
|
|
484
489
|
}
|
|
485
490
|
buildCustom(data) {
|
|
491
|
+
const bubbles = data.bubbles;
|
|
492
|
+
if (Array.isArray(bubbles) && bubbles.length > 0) return {
|
|
493
|
+
type: "carousel",
|
|
494
|
+
contents: bubbles
|
|
495
|
+
};
|
|
486
496
|
const rawContents = data.contents;
|
|
487
497
|
if (Array.isArray(rawContents) && rawContents.length > 0) return {
|
|
488
498
|
type: "bubble",
|
|
@@ -494,8 +504,13 @@ var FlexTool = class extends Tool {
|
|
|
494
504
|
};
|
|
495
505
|
const title = data.title ? String(data.title) : null;
|
|
496
506
|
const body = data.body ? String(data.body) : null;
|
|
497
|
-
|
|
507
|
+
let buttons = Array.isArray(data.buttons) ? data.buttons : [];
|
|
498
508
|
const headerColor = data.header_color ? String(data.header_color) : null;
|
|
509
|
+
if (buttons.length === 0 && data.button_label) buttons = [{
|
|
510
|
+
label: String(data.button_label),
|
|
511
|
+
text: String(data.button_text ?? data.button_label),
|
|
512
|
+
style: data.button_style ?? "primary"
|
|
513
|
+
}];
|
|
499
514
|
if (!title && !body && buttons.length === 0) throw new Error("custom template requires 'contents' array, or at least one of: title, body, buttons");
|
|
500
515
|
const bodyContents = [];
|
|
501
516
|
if (title) bodyContents.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flex.mjs","names":[],"sources":["../../../src/agent/tools/flex.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/**\n * Tool for building LINE Flex Messages from structured data.\n *\n * The LLM calls this tool with a template name and data object,\n * and the tool returns valid Flex JSON that line.ts parseMessage()\n * will detect and send as a Flex Message.\n */\nexport class FlexTool extends Tool {\n readonly name = \"flex_message\";\n readonly description =\n \"Build a LINE Flex Message from a template. Returns JSON that will be rendered as a rich card in LINE. Use this instead of outputting raw JSON.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n template: {\n type: \"string\",\n description:\n \"Template name. Available: fortune, info_card, action_buttons, receipt, morning_summary, hydration, custom.\",\n },\n data: {\n type: \"object\",\n description: \"Template-specific data. See each template for fields.\",\n properties: {\n // fortune\n sign: { type: \"string\", description: \"Zodiac sign with emoji (e.g. '♍ 乙女座')\" },\n stars: { type: \"integer\", description: \"1-5 star rating\" },\n message: { type: \"string\", description: \"Fortune message\" },\n lucky_color: { type: \"string\", description: \"Lucky color name\" },\n lucky_item: { type: \"string\", description: \"Lucky item name\" },\n // info_card\n title: { type: \"string\", description: \"Card title\" },\n body: { type: \"string\", description: \"Card body text\" },\n // action_buttons\n prompt: { type: \"string\", description: \"Question or prompt text\" },\n buttons: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n label: { type: \"string\" },\n text: { type: \"string\" },\n style: { type: \"string\", enum: [\"primary\", \"secondary\", \"link\"] },\n },\n },\n description: \"Button definitions\",\n },\n // receipt\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n value: { type: \"string\" },\n },\n },\n description: \"Line items\",\n },\n total: { type: \"string\", description: \"Total amount\" },\n // morning_summary\n greeting: { type: \"string\", description: \"Greeting text (e.g. 'おはよう!')\" },\n date: { type: \"string\", description: \"Date string (e.g. '2月13日 木曜日')\" },\n weather: { type: \"string\", description: \"Weather info (e.g. '東京 12°C 曇り')\" },\n advice: { type: \"string\", description: \"Short advice (e.g. 'コートでOK')\" },\n schedule: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Schedule/reminder lines\",\n },\n header_color: { type: \"string\", description: \"Header background color hex (default: #1DB446)\" },\n // hydration\n current: { type: \"integer\", description: \"Current count (e.g. glasses drunk)\" },\n goal: { type: \"integer\", description: \"Goal count\" },\n unit: { type: \"string\", description: \"Unit label (default: 杯)\" },\n button_label: { type: \"string\", description: \"Button label (default: '飲んだ!')\" },\n button_text: { type: \"string\", description: \"Button message text (default: '水飲んだ')\" },\n // custom\n contents: {\n type: \"array\",\n description: \"Raw Flex body contents array for custom template\",\n },\n },\n },\n },\n required: [\"template\", \"data\"],\n };\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const template = String(args.template);\n const data = (args.data ?? {}) as Record<string, unknown>;\n\n try {\n let flex: Record<string, unknown>;\n\n switch (template) {\n case \"fortune\":\n flex = this.buildFortune(data);\n break;\n case \"info_card\":\n flex = this.buildInfoCard(data);\n break;\n case \"action_buttons\":\n flex = this.buildActionButtons(data);\n break;\n case \"receipt\":\n flex = this.buildReceipt(data);\n break;\n case \"morning_summary\":\n flex = this.buildMorningSummary(data);\n break;\n case \"hydration\":\n flex = this.buildHydration(data);\n break;\n case \"custom\":\n flex = this.buildCustom(data);\n break;\n default:\n return `Error: unknown template '${template}'. Use: fortune, info_card, action_buttons, receipt, morning_summary, hydration, custom.`;\n }\n\n const json = JSON.stringify(flex);\n return `${json}\\n\\n(Card sent automatically. Do NOT repeat the JSON. Just respond naturally.)`;\n } catch (err) {\n return `Error building flex message: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private buildFortune(data: Record<string, unknown>): Record<string, unknown> {\n const sign = String(data.sign ?? \"♈ 牡羊座\");\n const stars = Math.max(1, Math.min(5, Number(data.stars ?? 3)));\n const message = String(data.message ?? \"\");\n const luckyColor = data.lucky_color ? String(data.lucky_color) : null;\n const luckyItem = data.lucky_item ? String(data.lucky_item) : null;\n\n const starText = \"★\".repeat(stars) + \"☆\".repeat(5 - stars);\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: sign, weight: \"bold\", size: \"xl\" },\n { type: \"text\", text: \"今日の運勢\", size: \"sm\", color: \"#666666\" },\n { type: \"text\", text: starText, size: \"xxl\", margin: \"md\" },\n ];\n\n if (message) {\n contents.push({\n type: \"text\",\n text: message,\n wrap: true,\n margin: \"sm\",\n });\n }\n\n if (luckyColor || luckyItem) {\n contents.push({ type: \"separator\", margin: \"md\" });\n }\n\n if (luckyColor) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: \"ラッキーカラー\", size: \"sm\", color: \"#228B22\", flex: 0 },\n { type: \"text\", text: luckyColor, size: \"sm\", align: \"end\" },\n ],\n });\n }\n\n if (luckyItem) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: luckyColor ? \"sm\" : \"md\",\n contents: [\n { type: \"text\", text: \"ラッキーアイテム\", size: \"sm\", color: \"#228B22\", flex: 0 },\n { type: \"text\", text: luckyItem, size: \"sm\", align: \"end\" },\n ],\n });\n }\n\n return {\n type: \"bubble\",\n size: \"kilo\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n paddingAll: \"lg\",\n contents,\n },\n };\n }\n\n private buildInfoCard(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"お知らせ\");\n const body = String(data.body ?? \"\");\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: title, weight: \"bold\", size: \"lg\" },\n { type: \"separator\", margin: \"md\" },\n ];\n\n if (body) {\n contents.push({\n type: \"text\",\n text: body,\n wrap: true,\n margin: \"md\",\n });\n }\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents,\n },\n };\n }\n\n private buildActionButtons(data: Record<string, unknown>): Record<string, unknown> {\n const prompt = String(data.prompt ?? \"選択してください\");\n const buttons = (data.buttons ?? []) as Array<Record<string, unknown>>;\n\n if (buttons.length === 0) {\n // Default yes/no\n buttons.push(\n { label: \"はい\", text: \"はい\", style: \"primary\" },\n { label: \"いいえ\", text: \"いいえ\", style: \"secondary\" },\n );\n }\n\n const buttonContents = buttons.map((btn) => ({\n type: \"button\",\n style: String(btn.style ?? \"primary\"),\n action: {\n type: \"message\",\n label: String(btn.label ?? \"\"),\n text: String(btn.text ?? btn.label ?? \"\"),\n },\n }));\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [\n { type: \"text\", text: prompt, weight: \"bold\", wrap: true },\n ],\n },\n footer: {\n type: \"box\",\n layout: \"vertical\",\n spacing: \"sm\",\n contents: buttonContents,\n },\n };\n }\n\n private buildReceipt(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"明細\");\n const items = (data.items ?? []) as Array<Record<string, unknown>>;\n const total = data.total ? String(data.total) : null;\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: `💰 ${title}`, weight: \"bold\" },\n { type: \"separator\", margin: \"lg\" },\n ];\n\n for (const item of items) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: String(item.name ?? \"\"), flex: 0 },\n { type: \"text\", text: String(item.value ?? \"\"), align: \"end\" },\n ],\n });\n }\n\n if (total) {\n contents.push(\n { type: \"separator\", margin: \"lg\" },\n {\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: \"合計\", weight: \"bold\" },\n { type: \"text\", text: total, weight: \"bold\", align: \"end\" },\n ],\n },\n );\n }\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents,\n },\n };\n }\n\n private buildMorningSummary(data: Record<string, unknown>): Record<string, unknown> {\n const greeting = String(data.greeting ?? \"おはよう!\");\n const date = String(data.date ?? \"\");\n const weather = data.weather ? String(data.weather) : null;\n const advice = data.advice ? String(data.advice) : null;\n const schedule = (data.schedule ?? []) as string[];\n const headerColor = String(data.header_color ?? \"#1DB446\");\n\n const headerContents: Record<string, unknown>[] = [\n { type: \"text\", text: greeting, color: \"#ffffff\", weight: \"bold\", size: \"lg\" },\n ];\n if (date) {\n headerContents.push({ type: \"text\", text: date, color: \"#ffffff\", size: \"sm\" });\n }\n\n const bodyContents: Record<string, unknown>[] = [];\n\n if (weather) {\n bodyContents.push({ type: \"text\", text: weather, weight: \"bold\" });\n }\n if (advice) {\n bodyContents.push({ type: \"text\", text: advice, size: \"sm\", color: \"#666666\" });\n }\n\n if (schedule.length > 0) {\n bodyContents.push({ type: \"separator\", margin: \"md\" });\n for (const line of schedule) {\n bodyContents.push({ type: \"text\", text: line, margin: \"md\", wrap: true });\n }\n }\n\n return {\n type: \"bubble\",\n header: {\n type: \"box\",\n layout: \"vertical\",\n backgroundColor: headerColor,\n paddingAll: \"lg\",\n contents: headerContents,\n },\n body: {\n type: \"box\",\n layout: \"vertical\",\n paddingAll: \"lg\",\n contents: bodyContents.length > 0 ? bodyContents : [{ type: \"text\", text: \"良い一日を!\" }],\n },\n };\n }\n\n private buildHydration(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"水飲んだ?\");\n const current = Number(data.current ?? 0);\n const goal = Number(data.goal ?? 8);\n const unit = String(data.unit ?? \"杯\");\n const buttonLabel = String(data.button_label ?? \"飲んだ!\");\n const buttonText = String(data.button_text ?? \"水飲んだ\");\n\n return {\n type: \"bubble\",\n size: \"kilo\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [\n { type: \"text\", text: title, size: \"xl\" },\n { type: \"text\", text: `今日: ${current}${unit} / ${goal}${unit}`, color: \"#666666\" },\n ],\n },\n footer: {\n type: \"box\",\n layout: \"horizontal\",\n contents: [\n {\n type: \"button\",\n style: \"primary\",\n color: \"#00B9ED\",\n action: { type: \"message\", label: buttonLabel, text: buttonText },\n },\n ],\n },\n };\n }\n\n private buildCustom(data: Record<string, unknown>): Record<string, unknown> {\n // If raw contents array is provided, use it directly\n const rawContents = data.contents as unknown[];\n if (Array.isArray(rawContents) && rawContents.length > 0) {\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: rawContents,\n },\n };\n }\n\n // Fallback: auto-build a card from common fields (title, body, buttons, header_color)\n const title = data.title ? String(data.title) : null;\n const body = data.body ? String(data.body) : null;\n const buttons = Array.isArray(data.buttons) ? data.buttons as Array<Record<string, unknown>> : [];\n const headerColor = data.header_color ? String(data.header_color) : null;\n\n if (!title && !body && buttons.length === 0) {\n throw new Error(\"custom template requires 'contents' array, or at least one of: title, body, buttons\");\n }\n\n const bodyContents: Record<string, unknown>[] = [];\n\n if (title) {\n bodyContents.push({ type: \"text\", text: title, weight: \"bold\", size: \"lg\" });\n }\n if (body) {\n if (title) bodyContents.push({ type: \"separator\", margin: \"md\" });\n bodyContents.push({ type: \"text\", text: body, wrap: true, margin: title ? \"md\" : undefined });\n }\n\n const bubble: Record<string, unknown> = {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: bodyContents.length > 0 ? bodyContents : [{ type: \"text\", text: \" \" }],\n },\n };\n\n // Add colored header if specified\n if (headerColor && title) {\n bubble.header = {\n type: \"box\",\n layout: \"vertical\",\n backgroundColor: headerColor,\n paddingAll: \"lg\",\n contents: [\n { type: \"text\", text: title, color: \"#ffffff\", weight: \"bold\", size: \"lg\" },\n ],\n };\n // Remove title from body since it's in the header now\n bodyContents.shift();\n }\n\n // Add buttons as footer\n if (buttons.length > 0) {\n bubble.footer = {\n type: \"box\",\n layout: \"vertical\",\n spacing: \"sm\",\n contents: buttons.map((btn) => ({\n type: \"button\",\n style: String(btn.style ?? \"primary\"),\n action: {\n type: \"message\",\n label: String(btn.label ?? \"\"),\n text: String(btn.text ?? btn.label ?? \"\"),\n },\n })),\n };\n }\n\n return bubble;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flex.mjs","names":[],"sources":["../../../src/agent/tools/flex.ts"],"sourcesContent":["import { Tool } from \"./base.js\";\n\n/**\n * Tool for building LINE Flex Messages from structured data.\n *\n * The LLM calls this tool with a template name and data object,\n * and the tool returns valid Flex JSON that line.ts parseMessage()\n * will detect and send as a Flex Message.\n */\nexport class FlexTool extends Tool {\n readonly name = \"flex_message\";\n readonly description =\n \"Build a LINE Flex Message from a template. Returns JSON that will be rendered as a rich card in LINE. Use this instead of outputting raw JSON.\";\n readonly parameters = {\n type: \"object\",\n properties: {\n template: {\n type: \"string\",\n description:\n \"Template name. Available: fortune, info_card, action_buttons, receipt, morning_summary, hydration, custom.\",\n },\n data: {\n type: \"object\",\n description: \"Template-specific data. See each template for fields.\",\n properties: {\n // fortune\n sign: { type: \"string\", description: \"Zodiac sign with emoji (e.g. '♍ 乙女座')\" },\n stars: { type: \"integer\", description: \"1-5 star rating\" },\n message: { type: \"string\", description: \"Fortune message\" },\n lucky_color: { type: \"string\", description: \"Lucky color name\" },\n lucky_item: { type: \"string\", description: \"Lucky item name\" },\n // info_card\n title: { type: \"string\", description: \"Card title\" },\n body: { type: \"string\", description: \"Card body text\" },\n // action_buttons\n prompt: { type: \"string\", description: \"Question or prompt text\" },\n buttons: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n label: { type: \"string\" },\n text: { type: \"string\" },\n style: { type: \"string\", enum: [\"primary\", \"secondary\", \"link\"] },\n },\n },\n description: \"Button definitions\",\n },\n // receipt\n items: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n value: { type: \"string\" },\n },\n },\n description: \"Line items\",\n },\n total: { type: \"string\", description: \"Total amount\" },\n // morning_summary\n greeting: { type: \"string\", description: \"Greeting text (e.g. 'おはよう!')\" },\n date: { type: \"string\", description: \"Date string (e.g. '2月13日 木曜日')\" },\n weather: { type: \"string\", description: \"Weather info (e.g. '東京 12°C 曇り')\" },\n advice: { type: \"string\", description: \"Short advice (e.g. 'コートでOK')\" },\n schedule: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Schedule/reminder lines\",\n },\n header_color: { type: \"string\", description: \"Header background color hex (default: #1DB446)\" },\n // hydration\n current: { type: \"integer\", description: \"Current count (e.g. glasses drunk)\" },\n goal: { type: \"integer\", description: \"Goal count\" },\n unit: { type: \"string\", description: \"Unit label (default: 杯)\" },\n button_label: { type: \"string\", description: \"Button label (default: '飲んだ!')\" },\n button_text: { type: \"string\", description: \"Button message text (default: '水飲んだ')\" },\n // custom\n contents: {\n type: \"array\",\n description: \"Raw Flex body contents array for custom template\",\n },\n bubbles: {\n type: \"array\",\n items: { type: \"object\" },\n description: \"Array of bubble objects for a carousel (horizontal swipeable cards). Each item should be a complete bubble with type, body, etc.\",\n },\n },\n },\n },\n required: [\"template\", \"data\"],\n };\n\n async execute(args: Record<string, unknown>): Promise<string> {\n const template = String(args.template);\n const data = (args.data ?? {}) as Record<string, unknown>;\n\n try {\n let flex: Record<string, unknown>;\n\n switch (template) {\n case \"fortune\":\n flex = this.buildFortune(data);\n break;\n case \"info_card\":\n flex = this.buildInfoCard(data);\n break;\n case \"action_buttons\":\n flex = this.buildActionButtons(data);\n break;\n case \"receipt\":\n flex = this.buildReceipt(data);\n break;\n case \"morning_summary\":\n flex = this.buildMorningSummary(data);\n break;\n case \"hydration\":\n flex = this.buildHydration(data);\n break;\n case \"custom\":\n flex = this.buildCustom(data);\n break;\n default:\n return `Error: unknown template '${template}'. Use: fortune, info_card, action_buttons, receipt, morning_summary, hydration, custom.`;\n }\n\n const json = JSON.stringify(flex);\n return `${json}\\n\\n(Card sent automatically. Do NOT repeat the JSON. Just respond naturally.)`;\n } catch (err) {\n return `Error building flex message: ${err instanceof Error ? err.message : err}`;\n }\n }\n\n private buildFortune(data: Record<string, unknown>): Record<string, unknown> {\n const sign = String(data.sign ?? \"♈ 牡羊座\");\n const stars = Math.max(1, Math.min(5, Number(data.stars ?? 3)));\n const message = String(data.message ?? \"\");\n const luckyColor = data.lucky_color ? String(data.lucky_color) : null;\n const luckyItem = data.lucky_item ? String(data.lucky_item) : null;\n\n const starText = \"★\".repeat(stars) + \"☆\".repeat(5 - stars);\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: sign, weight: \"bold\", size: \"xl\" },\n { type: \"text\", text: \"今日の運勢\", size: \"sm\", color: \"#666666\" },\n { type: \"text\", text: starText, size: \"xxl\", margin: \"md\" },\n ];\n\n if (message) {\n contents.push({\n type: \"text\",\n text: message,\n wrap: true,\n margin: \"sm\",\n });\n }\n\n if (luckyColor || luckyItem) {\n contents.push({ type: \"separator\", margin: \"md\" });\n }\n\n if (luckyColor) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: \"ラッキーカラー\", size: \"sm\", color: \"#228B22\", flex: 0 },\n { type: \"text\", text: luckyColor, size: \"sm\", align: \"end\" },\n ],\n });\n }\n\n if (luckyItem) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: luckyColor ? \"sm\" : \"md\",\n contents: [\n { type: \"text\", text: \"ラッキーアイテム\", size: \"sm\", color: \"#228B22\", flex: 0 },\n { type: \"text\", text: luckyItem, size: \"sm\", align: \"end\" },\n ],\n });\n }\n\n return {\n type: \"bubble\",\n size: \"kilo\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n paddingAll: \"lg\",\n contents,\n },\n };\n }\n\n private buildInfoCard(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"お知らせ\");\n const body = String(data.body ?? \"\");\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: title, weight: \"bold\", size: \"lg\" },\n { type: \"separator\", margin: \"md\" },\n ];\n\n if (body) {\n contents.push({\n type: \"text\",\n text: body,\n wrap: true,\n margin: \"md\",\n });\n }\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents,\n },\n };\n }\n\n private buildActionButtons(data: Record<string, unknown>): Record<string, unknown> {\n const prompt = String(data.prompt ?? \"選択してください\");\n const buttons = (data.buttons ?? []) as Array<Record<string, unknown>>;\n\n if (buttons.length === 0) {\n // Default yes/no\n buttons.push(\n { label: \"はい\", text: \"はい\", style: \"primary\" },\n { label: \"いいえ\", text: \"いいえ\", style: \"secondary\" },\n );\n }\n\n const buttonContents = buttons.map((btn) => ({\n type: \"button\",\n style: String(btn.style ?? \"primary\"),\n action: {\n type: \"message\",\n label: String(btn.label ?? \"\"),\n text: String(btn.text ?? btn.label ?? \"\"),\n },\n }));\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [\n { type: \"text\", text: prompt, weight: \"bold\", wrap: true },\n ],\n },\n footer: {\n type: \"box\",\n layout: \"vertical\",\n spacing: \"sm\",\n contents: buttonContents,\n },\n };\n }\n\n private buildReceipt(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"明細\");\n const items = (data.items ?? []) as Array<Record<string, unknown>>;\n const total = data.total ? String(data.total) : null;\n\n const contents: Record<string, unknown>[] = [\n { type: \"text\", text: `💰 ${title}`, weight: \"bold\" },\n { type: \"separator\", margin: \"lg\" },\n ];\n\n for (const item of items) {\n contents.push({\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: String(item.name ?? \"\"), flex: 0 },\n { type: \"text\", text: String(item.value ?? \"\"), align: \"end\" },\n ],\n });\n }\n\n if (total) {\n contents.push(\n { type: \"separator\", margin: \"lg\" },\n {\n type: \"box\",\n layout: \"horizontal\",\n margin: \"md\",\n contents: [\n { type: \"text\", text: \"合計\", weight: \"bold\" },\n { type: \"text\", text: total, weight: \"bold\", align: \"end\" },\n ],\n },\n );\n }\n\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents,\n },\n };\n }\n\n private buildMorningSummary(data: Record<string, unknown>): Record<string, unknown> {\n const greeting = String(data.greeting ?? \"おはよう!\");\n const date = String(data.date ?? \"\");\n const weather = data.weather ? String(data.weather) : null;\n const advice = data.advice ? String(data.advice) : null;\n const schedule = (data.schedule ?? []) as string[];\n const headerColor = String(data.header_color ?? \"#1DB446\");\n\n const headerContents: Record<string, unknown>[] = [\n { type: \"text\", text: greeting, color: \"#ffffff\", weight: \"bold\", size: \"lg\" },\n ];\n if (date) {\n headerContents.push({ type: \"text\", text: date, color: \"#ffffff\", size: \"sm\" });\n }\n\n const bodyContents: Record<string, unknown>[] = [];\n\n if (weather) {\n bodyContents.push({ type: \"text\", text: weather, weight: \"bold\" });\n }\n if (advice) {\n bodyContents.push({ type: \"text\", text: advice, size: \"sm\", color: \"#666666\" });\n }\n\n if (schedule.length > 0) {\n bodyContents.push({ type: \"separator\", margin: \"md\" });\n for (const line of schedule) {\n bodyContents.push({ type: \"text\", text: line, margin: \"md\", wrap: true });\n }\n }\n\n return {\n type: \"bubble\",\n header: {\n type: \"box\",\n layout: \"vertical\",\n backgroundColor: headerColor,\n paddingAll: \"lg\",\n contents: headerContents,\n },\n body: {\n type: \"box\",\n layout: \"vertical\",\n paddingAll: \"lg\",\n contents: bodyContents.length > 0 ? bodyContents : [{ type: \"text\", text: \"良い一日を!\" }],\n },\n };\n }\n\n private buildHydration(data: Record<string, unknown>): Record<string, unknown> {\n const title = String(data.title ?? \"水飲んだ?\");\n const current = Number(data.current ?? 0);\n const goal = Number(data.goal ?? 8);\n const unit = String(data.unit ?? \"杯\");\n const buttonLabel = String(data.button_label ?? \"飲んだ!\");\n const buttonText = String(data.button_text ?? \"水飲んだ\");\n\n return {\n type: \"bubble\",\n size: \"kilo\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: [\n { type: \"text\", text: title, size: \"xl\" },\n { type: \"text\", text: `今日: ${current}${unit} / ${goal}${unit}`, color: \"#666666\" },\n ],\n },\n footer: {\n type: \"box\",\n layout: \"horizontal\",\n contents: [\n {\n type: \"button\",\n style: \"primary\",\n color: \"#00B9ED\",\n action: { type: \"message\", label: buttonLabel, text: buttonText },\n },\n ],\n },\n };\n }\n\n private buildCustom(data: Record<string, unknown>): Record<string, unknown> {\n // Carousel: array of bubble objects → horizontal swipeable cards\n const bubbles = data.bubbles as unknown[];\n if (Array.isArray(bubbles) && bubbles.length > 0) {\n return {\n type: \"carousel\",\n contents: bubbles,\n };\n }\n\n // If raw contents array is provided, use it directly as body contents\n const rawContents = data.contents as unknown[];\n if (Array.isArray(rawContents) && rawContents.length > 0) {\n return {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: rawContents,\n },\n };\n }\n\n // Fallback: auto-build a card from common fields (title, body, buttons, header_color)\n const title = data.title ? String(data.title) : null;\n const body = data.body ? String(data.body) : null;\n let buttons = Array.isArray(data.buttons) ? data.buttons as Array<Record<string, unknown>> : [];\n const headerColor = data.header_color ? String(data.header_color) : null;\n\n // Support flat button_label/button_text shorthand (like hydration template)\n if (buttons.length === 0 && data.button_label) {\n buttons = [{\n label: String(data.button_label),\n text: String(data.button_text ?? data.button_label),\n style: data.button_style ?? \"primary\",\n }];\n }\n\n if (!title && !body && buttons.length === 0) {\n throw new Error(\"custom template requires 'contents' array, or at least one of: title, body, buttons\");\n }\n\n const bodyContents: Record<string, unknown>[] = [];\n\n if (title) {\n bodyContents.push({ type: \"text\", text: title, weight: \"bold\", size: \"lg\" });\n }\n if (body) {\n if (title) bodyContents.push({ type: \"separator\", margin: \"md\" });\n bodyContents.push({ type: \"text\", text: body, wrap: true, margin: title ? \"md\" : undefined });\n }\n\n const bubble: Record<string, unknown> = {\n type: \"bubble\",\n body: {\n type: \"box\",\n layout: \"vertical\",\n contents: bodyContents.length > 0 ? bodyContents : [{ type: \"text\", text: \" \" }],\n },\n };\n\n // Add colored header if specified\n if (headerColor && title) {\n bubble.header = {\n type: \"box\",\n layout: \"vertical\",\n backgroundColor: headerColor,\n paddingAll: \"lg\",\n contents: [\n { type: \"text\", text: title, color: \"#ffffff\", weight: \"bold\", size: \"lg\" },\n ],\n };\n // Remove title from body since it's in the header now\n bodyContents.shift();\n }\n\n // Add buttons as footer\n if (buttons.length > 0) {\n bubble.footer = {\n type: \"box\",\n layout: \"vertical\",\n spacing: \"sm\",\n contents: buttons.map((btn) => ({\n type: \"button\",\n style: String(btn.style ?? \"primary\"),\n action: {\n type: \"message\",\n label: String(btn.label ?? \"\"),\n text: String(btn.text ?? btn.label ?? \"\"),\n },\n })),\n };\n }\n\n return bubble;\n }\n}\n"],"mappings":"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"}
|
|
@@ -175,6 +175,65 @@ describe("hydration", () => {
|
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
177
|
describe("custom", () => {
|
|
178
|
+
it("builds carousel from bubbles array", async () => {
|
|
179
|
+
const flex = await exec("custom", { bubbles: [
|
|
180
|
+
{
|
|
181
|
+
type: "bubble",
|
|
182
|
+
body: {
|
|
183
|
+
type: "box",
|
|
184
|
+
layout: "vertical",
|
|
185
|
+
contents: [{
|
|
186
|
+
type: "text",
|
|
187
|
+
text: "Card 1"
|
|
188
|
+
}]
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
type: "bubble",
|
|
193
|
+
body: {
|
|
194
|
+
type: "box",
|
|
195
|
+
layout: "vertical",
|
|
196
|
+
contents: [{
|
|
197
|
+
type: "text",
|
|
198
|
+
text: "Card 2"
|
|
199
|
+
}]
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
type: "bubble",
|
|
204
|
+
body: {
|
|
205
|
+
type: "box",
|
|
206
|
+
layout: "vertical",
|
|
207
|
+
contents: [{
|
|
208
|
+
type: "text",
|
|
209
|
+
text: "Card 3"
|
|
210
|
+
}]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
] });
|
|
214
|
+
globalExpect(flex.type).toBe("carousel");
|
|
215
|
+
globalExpect(flex.contents).toHaveLength(3);
|
|
216
|
+
globalExpect(flex.contents[0].type).toBe("bubble");
|
|
217
|
+
globalExpect(flex.contents[2].body.contents[0].text).toBe("Card 3");
|
|
218
|
+
});
|
|
219
|
+
it("prefers bubbles over other custom fields", async () => {
|
|
220
|
+
const flex = await exec("custom", {
|
|
221
|
+
title: "Ignored",
|
|
222
|
+
bubbles: [{
|
|
223
|
+
type: "bubble",
|
|
224
|
+
body: {
|
|
225
|
+
type: "box",
|
|
226
|
+
layout: "vertical",
|
|
227
|
+
contents: [{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: "Used"
|
|
230
|
+
}]
|
|
231
|
+
}
|
|
232
|
+
}]
|
|
233
|
+
});
|
|
234
|
+
globalExpect(flex.type).toBe("carousel");
|
|
235
|
+
globalExpect(flex.contents).toHaveLength(1);
|
|
236
|
+
});
|
|
178
237
|
it("uses raw contents array when provided", async () => {
|
|
179
238
|
const contents = [{
|
|
180
239
|
type: "text",
|
|
@@ -226,6 +285,33 @@ describe("custom", () => {
|
|
|
226
285
|
globalExpect(flex.footer.contents).toHaveLength(2);
|
|
227
286
|
globalExpect(flex.footer.contents[0].action.label).toBe("Option A");
|
|
228
287
|
});
|
|
288
|
+
it("auto-builds button from button_label/button_text shorthand", async () => {
|
|
289
|
+
const flex = await exec("custom", {
|
|
290
|
+
title: "🪴 モンステラ",
|
|
291
|
+
body: "水やり記録なし\n今日は水やりした?",
|
|
292
|
+
header_color: "#4CAF50",
|
|
293
|
+
button_label: "水やりした!",
|
|
294
|
+
button_text: "水やり モンステラ した"
|
|
295
|
+
});
|
|
296
|
+
globalExpect(flex.footer).toBeTruthy();
|
|
297
|
+
globalExpect(flex.footer.contents).toHaveLength(1);
|
|
298
|
+
globalExpect(flex.footer.contents[0].action.label).toBe("水やりした!");
|
|
299
|
+
globalExpect(flex.footer.contents[0].action.text).toBe("水やり モンステラ した");
|
|
300
|
+
globalExpect(flex.footer.contents[0].style).toBe("primary");
|
|
301
|
+
});
|
|
302
|
+
it("prefers buttons array over button_label shorthand", async () => {
|
|
303
|
+
const flex = await exec("custom", {
|
|
304
|
+
title: "Test",
|
|
305
|
+
button_label: "Ignored",
|
|
306
|
+
buttons: [{
|
|
307
|
+
label: "Used",
|
|
308
|
+
text: "used",
|
|
309
|
+
style: "secondary"
|
|
310
|
+
}]
|
|
311
|
+
});
|
|
312
|
+
globalExpect(flex.footer.contents).toHaveLength(1);
|
|
313
|
+
globalExpect(flex.footer.contents[0].action.label).toBe("Used");
|
|
314
|
+
});
|
|
229
315
|
it("errors when no contents, title, body, or buttons", async () => {
|
|
230
316
|
const result = await execRaw("custom", {});
|
|
231
317
|
globalExpect(result).toContain("Error");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flex.test.mjs","names":[],"sources":["../../../src/agent/tools/flex.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { FlexTool } from \"./flex.js\";\n\nconst tool = new FlexTool();\n\n// Helper: execute and parse result as JSON (strip instruction suffix)\nasync function exec(template: string, data: Record<string, unknown> = {}) {\n const result = await tool.execute({ template, data });\n const jsonPart = result.split(\"\\n\\n(\")[0];\n return JSON.parse(jsonPart);\n}\n\n// Helper: execute and return raw string (for error cases)\nasync function execRaw(template: string, data: Record<string, unknown> = {}) {\n return tool.execute({ template, data });\n}\n\n// ---------------------------------------------------------------------------\n// fortune\n// ---------------------------------------------------------------------------\ndescribe(\"fortune\", () => {\n it(\"builds with defaults\", async () => {\n const flex = await exec(\"fortune\", { sign: \"♍ 乙女座\", stars: 4, message: \"良い日\" });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.size).toBe(\"kilo\");\n\n const body = flex.body;\n expect(body.type).toBe(\"box\");\n // sign, 今日の運勢, stars, message\n expect(body.contents.length).toBeGreaterThanOrEqual(4);\n expect(body.contents[0].text).toBe(\"♍ 乙女座\");\n expect(body.contents[2].text).toBe(\"★★★★☆\");\n expect(body.contents[3].text).toBe(\"良い日\");\n });\n\n it(\"clamps stars to 1-5\", async () => {\n const low = await exec(\"fortune\", { stars: -10 });\n expect(low.body.contents[2].text).toBe(\"★☆☆☆☆\");\n\n const high = await exec(\"fortune\", { stars: 99 });\n expect(high.body.contents[2].text).toBe(\"★★★★★\");\n });\n\n it(\"includes lucky color and item\", async () => {\n const flex = await exec(\"fortune\", {\n sign: \"♈ 牡羊座\",\n stars: 3,\n lucky_color: \"赤\",\n lucky_item: \"傘\",\n });\n const contents = flex.body.contents;\n // Should have separator + color row + item row\n const colorRow = contents.find(\n (c: Record<string, unknown>) =>\n c.type === \"box\" &&\n Array.isArray(c.contents) &&\n (c.contents as Record<string, unknown>[]).some((t) => t.text === \"ラッキーカラー\"),\n );\n expect(colorRow).toBeTruthy();\n\n const itemRow = contents.find(\n (c: Record<string, unknown>) =>\n c.type === \"box\" &&\n Array.isArray(c.contents) &&\n (c.contents as Record<string, unknown>[]).some((t) => t.text === \"ラッキーアイテム\"),\n );\n expect(itemRow).toBeTruthy();\n });\n\n it(\"omits lucky fields when not provided\", async () => {\n const flex = await exec(\"fortune\", { stars: 5 });\n const contents = flex.body.contents;\n const hasSeparator = contents.some((c: Record<string, unknown>) => c.type === \"separator\");\n expect(hasSeparator).toBe(false);\n });\n});\n\n// ---------------------------------------------------------------------------\n// info_card\n// ---------------------------------------------------------------------------\ndescribe(\"info_card\", () => {\n it(\"builds with title and body\", async () => {\n const flex = await exec(\"info_card\", { title: \"テスト\", body: \"本文テキスト\" });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n expect(contents[0].text).toBe(\"テスト\");\n expect(contents[1].type).toBe(\"separator\");\n expect(contents[2].text).toBe(\"本文テキスト\");\n });\n\n it(\"uses defaults when no data\", async () => {\n const flex = await exec(\"info_card\", {});\n expect(flex.body.contents[0].text).toBe(\"お知らせ\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// action_buttons\n// ---------------------------------------------------------------------------\ndescribe(\"action_buttons\", () => {\n it(\"builds with custom buttons\", async () => {\n const flex = await exec(\"action_buttons\", {\n prompt: \"好きな色は?\",\n buttons: [\n { label: \"赤\", text: \"赤が好き\", style: \"primary\" },\n { label: \"青\", text: \"青が好き\", style: \"secondary\" },\n ],\n });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.body.contents[0].text).toBe(\"好きな色は?\");\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"赤\");\n expect(flex.footer.contents[1].action.text).toBe(\"青が好き\");\n });\n\n it(\"adds default yes/no buttons when empty\", async () => {\n const flex = await exec(\"action_buttons\", { prompt: \"OK?\" });\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"はい\");\n expect(flex.footer.contents[1].action.label).toBe(\"いいえ\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// receipt\n// ---------------------------------------------------------------------------\ndescribe(\"receipt\", () => {\n it(\"builds with items and total\", async () => {\n const flex = await exec(\"receipt\", {\n title: \"ランチ\",\n items: [\n { name: \"ラーメン\", value: \"¥900\" },\n { name: \"餃子\", value: \"¥400\" },\n ],\n total: \"¥1,300\",\n });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n // title, separator, 2 items, separator, total\n expect(contents.length).toBe(6);\n expect(contents[0].text).toContain(\"ランチ\");\n // total row\n const totalRow = contents[5] as Record<string, unknown>;\n expect((totalRow.contents as Record<string, unknown>[])[1].text).toBe(\"¥1,300\");\n });\n\n it(\"omits total when not provided\", async () => {\n const flex = await exec(\"receipt\", {\n items: [{ name: \"Item\", value: \"100\" }],\n });\n // title, separator, 1 item = 3 contents (no total separator or total row)\n expect(flex.body.contents.length).toBe(3);\n });\n});\n\n// ---------------------------------------------------------------------------\n// morning_summary\n// ---------------------------------------------------------------------------\ndescribe(\"morning_summary\", () => {\n it(\"builds with all fields\", async () => {\n const flex = await exec(\"morning_summary\", {\n greeting: \"おはよう!\",\n date: \"2月13日 木曜日\",\n weather: \"東京 12°C 曇り\",\n advice: \"コートでOK\",\n schedule: [\"10:00 ミーティング\", \"14:00 歯医者\"],\n header_color: \"#FF6B6B\",\n });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.header.backgroundColor).toBe(\"#FF6B6B\");\n expect(flex.header.contents[0].text).toBe(\"おはよう!\");\n expect(flex.header.contents[1].text).toBe(\"2月13日 木曜日\");\n\n const body = flex.body.contents;\n expect(body[0].text).toBe(\"東京 12°C 曇り\");\n expect(body[1].text).toBe(\"コートでOK\");\n // separator + 2 schedule items\n expect(body.length).toBe(5);\n });\n\n it(\"uses default header color\", async () => {\n const flex = await exec(\"morning_summary\", {});\n expect(flex.header.backgroundColor).toBe(\"#1DB446\");\n });\n\n it(\"shows fallback body when no weather/schedule\", async () => {\n const flex = await exec(\"morning_summary\", { greeting: \"やぁ\" });\n expect(flex.body.contents[0].text).toBe(\"良い一日を!\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// hydration\n// ---------------------------------------------------------------------------\ndescribe(\"hydration\", () => {\n it(\"builds with current/goal\", async () => {\n const flex = await exec(\"hydration\", { current: 3, goal: 8 });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.size).toBe(\"kilo\");\n expect(flex.body.contents[0].text).toBe(\"水飲んだ?\");\n expect(flex.body.contents[1].text).toBe(\"今日: 3杯 / 8杯\");\n expect(flex.footer.contents[0].action.label).toBe(\"飲んだ!\");\n });\n\n it(\"uses custom unit and button text\", async () => {\n const flex = await exec(\"hydration\", {\n title: \"Coffee\",\n current: 2,\n goal: 4,\n unit: \"cups\",\n button_label: \"Had one!\",\n button_text: \"drank coffee\",\n });\n expect(flex.body.contents[0].text).toBe(\"Coffee\");\n expect(flex.body.contents[1].text).toBe(\"今日: 2cups / 4cups\");\n expect(flex.footer.contents[0].action.label).toBe(\"Had one!\");\n expect(flex.footer.contents[0].action.text).toBe(\"drank coffee\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// custom\n// ---------------------------------------------------------------------------\ndescribe(\"custom\", () => {\n it(\"uses raw contents array when provided\", async () => {\n const contents = [\n { type: \"text\", text: \"Hello\" },\n { type: \"text\", text: \"World\" },\n ];\n const flex = await exec(\"custom\", { contents });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.body.contents).toEqual(contents);\n });\n\n it(\"auto-builds card from title + body\", async () => {\n const flex = await exec(\"custom\", { title: \"Title\", body: \"Body text\" });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n expect(contents[0].text).toBe(\"Title\");\n expect(contents[1].type).toBe(\"separator\");\n expect(contents[2].text).toBe(\"Body text\");\n });\n\n it(\"auto-builds card with colored header\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Workout\",\n body: \"Great session!\",\n header_color: \"#FF0000\",\n });\n expect(flex.header).toBeTruthy();\n expect(flex.header.backgroundColor).toBe(\"#FF0000\");\n expect(flex.header.contents[0].text).toBe(\"Workout\");\n // Title should be removed from body since it's in the header\n const bodyTexts = flex.body.contents.filter(\n (c: Record<string, unknown>) => c.type === \"text\",\n );\n expect(bodyTexts.every((t: Record<string, unknown>) => t.text !== \"Workout\")).toBe(true);\n });\n\n it(\"auto-builds card with buttons\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Choose\",\n buttons: [\n { label: \"Option A\", text: \"A\", style: \"primary\" },\n { label: \"Option B\", text: \"B\", style: \"secondary\" },\n ],\n });\n expect(flex.footer).toBeTruthy();\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"Option A\");\n });\n\n it(\"errors when no contents, title, body, or buttons\", async () => {\n const result = await execRaw(\"custom\", {});\n expect(result).toContain(\"Error\");\n expect(result).toContain(\"custom template requires\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// unknown template\n// ---------------------------------------------------------------------------\ndescribe(\"unknown template\", () => {\n it(\"returns error for unknown template\", async () => {\n const result = await execRaw(\"nonexistent\", {});\n expect(result).toContain(\"Error: unknown template\");\n expect(result).toContain(\"nonexistent\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// tool metadata\n// ---------------------------------------------------------------------------\ndescribe(\"tool metadata\", () => {\n it(\"has correct name and description\", () => {\n expect(tool.name).toBe(\"flex_message\");\n expect(tool.description).toContain(\"Flex Message\");\n });\n\n it(\"generates valid tool definition\", () => {\n const def = tool.getDefinition();\n expect(def.type).toBe(\"function\");\n expect(def.function.name).toBe(\"flex_message\");\n expect(def.function.parameters.required).toContain(\"template\");\n expect(def.function.parameters.required).toContain(\"data\");\n });\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"flex.test.mjs","names":[],"sources":["../../../src/agent/tools/flex.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { FlexTool } from \"./flex.js\";\n\nconst tool = new FlexTool();\n\n// Helper: execute and parse result as JSON (strip instruction suffix)\nasync function exec(template: string, data: Record<string, unknown> = {}) {\n const result = await tool.execute({ template, data });\n const jsonPart = result.split(\"\\n\\n(\")[0];\n return JSON.parse(jsonPart);\n}\n\n// Helper: execute and return raw string (for error cases)\nasync function execRaw(template: string, data: Record<string, unknown> = {}) {\n return tool.execute({ template, data });\n}\n\n// ---------------------------------------------------------------------------\n// fortune\n// ---------------------------------------------------------------------------\ndescribe(\"fortune\", () => {\n it(\"builds with defaults\", async () => {\n const flex = await exec(\"fortune\", { sign: \"♍ 乙女座\", stars: 4, message: \"良い日\" });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.size).toBe(\"kilo\");\n\n const body = flex.body;\n expect(body.type).toBe(\"box\");\n // sign, 今日の運勢, stars, message\n expect(body.contents.length).toBeGreaterThanOrEqual(4);\n expect(body.contents[0].text).toBe(\"♍ 乙女座\");\n expect(body.contents[2].text).toBe(\"★★★★☆\");\n expect(body.contents[3].text).toBe(\"良い日\");\n });\n\n it(\"clamps stars to 1-5\", async () => {\n const low = await exec(\"fortune\", { stars: -10 });\n expect(low.body.contents[2].text).toBe(\"★☆☆☆☆\");\n\n const high = await exec(\"fortune\", { stars: 99 });\n expect(high.body.contents[2].text).toBe(\"★★★★★\");\n });\n\n it(\"includes lucky color and item\", async () => {\n const flex = await exec(\"fortune\", {\n sign: \"♈ 牡羊座\",\n stars: 3,\n lucky_color: \"赤\",\n lucky_item: \"傘\",\n });\n const contents = flex.body.contents;\n // Should have separator + color row + item row\n const colorRow = contents.find(\n (c: Record<string, unknown>) =>\n c.type === \"box\" &&\n Array.isArray(c.contents) &&\n (c.contents as Record<string, unknown>[]).some((t) => t.text === \"ラッキーカラー\"),\n );\n expect(colorRow).toBeTruthy();\n\n const itemRow = contents.find(\n (c: Record<string, unknown>) =>\n c.type === \"box\" &&\n Array.isArray(c.contents) &&\n (c.contents as Record<string, unknown>[]).some((t) => t.text === \"ラッキーアイテム\"),\n );\n expect(itemRow).toBeTruthy();\n });\n\n it(\"omits lucky fields when not provided\", async () => {\n const flex = await exec(\"fortune\", { stars: 5 });\n const contents = flex.body.contents;\n const hasSeparator = contents.some((c: Record<string, unknown>) => c.type === \"separator\");\n expect(hasSeparator).toBe(false);\n });\n});\n\n// ---------------------------------------------------------------------------\n// info_card\n// ---------------------------------------------------------------------------\ndescribe(\"info_card\", () => {\n it(\"builds with title and body\", async () => {\n const flex = await exec(\"info_card\", { title: \"テスト\", body: \"本文テキスト\" });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n expect(contents[0].text).toBe(\"テスト\");\n expect(contents[1].type).toBe(\"separator\");\n expect(contents[2].text).toBe(\"本文テキスト\");\n });\n\n it(\"uses defaults when no data\", async () => {\n const flex = await exec(\"info_card\", {});\n expect(flex.body.contents[0].text).toBe(\"お知らせ\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// action_buttons\n// ---------------------------------------------------------------------------\ndescribe(\"action_buttons\", () => {\n it(\"builds with custom buttons\", async () => {\n const flex = await exec(\"action_buttons\", {\n prompt: \"好きな色は?\",\n buttons: [\n { label: \"赤\", text: \"赤が好き\", style: \"primary\" },\n { label: \"青\", text: \"青が好き\", style: \"secondary\" },\n ],\n });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.body.contents[0].text).toBe(\"好きな色は?\");\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"赤\");\n expect(flex.footer.contents[1].action.text).toBe(\"青が好き\");\n });\n\n it(\"adds default yes/no buttons when empty\", async () => {\n const flex = await exec(\"action_buttons\", { prompt: \"OK?\" });\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"はい\");\n expect(flex.footer.contents[1].action.label).toBe(\"いいえ\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// receipt\n// ---------------------------------------------------------------------------\ndescribe(\"receipt\", () => {\n it(\"builds with items and total\", async () => {\n const flex = await exec(\"receipt\", {\n title: \"ランチ\",\n items: [\n { name: \"ラーメン\", value: \"¥900\" },\n { name: \"餃子\", value: \"¥400\" },\n ],\n total: \"¥1,300\",\n });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n // title, separator, 2 items, separator, total\n expect(contents.length).toBe(6);\n expect(contents[0].text).toContain(\"ランチ\");\n // total row\n const totalRow = contents[5] as Record<string, unknown>;\n expect((totalRow.contents as Record<string, unknown>[])[1].text).toBe(\"¥1,300\");\n });\n\n it(\"omits total when not provided\", async () => {\n const flex = await exec(\"receipt\", {\n items: [{ name: \"Item\", value: \"100\" }],\n });\n // title, separator, 1 item = 3 contents (no total separator or total row)\n expect(flex.body.contents.length).toBe(3);\n });\n});\n\n// ---------------------------------------------------------------------------\n// morning_summary\n// ---------------------------------------------------------------------------\ndescribe(\"morning_summary\", () => {\n it(\"builds with all fields\", async () => {\n const flex = await exec(\"morning_summary\", {\n greeting: \"おはよう!\",\n date: \"2月13日 木曜日\",\n weather: \"東京 12°C 曇り\",\n advice: \"コートでOK\",\n schedule: [\"10:00 ミーティング\", \"14:00 歯医者\"],\n header_color: \"#FF6B6B\",\n });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.header.backgroundColor).toBe(\"#FF6B6B\");\n expect(flex.header.contents[0].text).toBe(\"おはよう!\");\n expect(flex.header.contents[1].text).toBe(\"2月13日 木曜日\");\n\n const body = flex.body.contents;\n expect(body[0].text).toBe(\"東京 12°C 曇り\");\n expect(body[1].text).toBe(\"コートでOK\");\n // separator + 2 schedule items\n expect(body.length).toBe(5);\n });\n\n it(\"uses default header color\", async () => {\n const flex = await exec(\"morning_summary\", {});\n expect(flex.header.backgroundColor).toBe(\"#1DB446\");\n });\n\n it(\"shows fallback body when no weather/schedule\", async () => {\n const flex = await exec(\"morning_summary\", { greeting: \"やぁ\" });\n expect(flex.body.contents[0].text).toBe(\"良い一日を!\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// hydration\n// ---------------------------------------------------------------------------\ndescribe(\"hydration\", () => {\n it(\"builds with current/goal\", async () => {\n const flex = await exec(\"hydration\", { current: 3, goal: 8 });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.size).toBe(\"kilo\");\n expect(flex.body.contents[0].text).toBe(\"水飲んだ?\");\n expect(flex.body.contents[1].text).toBe(\"今日: 3杯 / 8杯\");\n expect(flex.footer.contents[0].action.label).toBe(\"飲んだ!\");\n });\n\n it(\"uses custom unit and button text\", async () => {\n const flex = await exec(\"hydration\", {\n title: \"Coffee\",\n current: 2,\n goal: 4,\n unit: \"cups\",\n button_label: \"Had one!\",\n button_text: \"drank coffee\",\n });\n expect(flex.body.contents[0].text).toBe(\"Coffee\");\n expect(flex.body.contents[1].text).toBe(\"今日: 2cups / 4cups\");\n expect(flex.footer.contents[0].action.label).toBe(\"Had one!\");\n expect(flex.footer.contents[0].action.text).toBe(\"drank coffee\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// custom\n// ---------------------------------------------------------------------------\ndescribe(\"custom\", () => {\n it(\"builds carousel from bubbles array\", async () => {\n const flex = await exec(\"custom\", {\n bubbles: [\n { type: \"bubble\", body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Card 1\" }] } },\n { type: \"bubble\", body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Card 2\" }] } },\n { type: \"bubble\", body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Card 3\" }] } },\n ],\n });\n expect(flex.type).toBe(\"carousel\");\n expect(flex.contents).toHaveLength(3);\n expect(flex.contents[0].type).toBe(\"bubble\");\n expect(flex.contents[2].body.contents[0].text).toBe(\"Card 3\");\n });\n\n it(\"prefers bubbles over other custom fields\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Ignored\",\n bubbles: [\n { type: \"bubble\", body: { type: \"box\", layout: \"vertical\", contents: [{ type: \"text\", text: \"Used\" }] } },\n ],\n });\n expect(flex.type).toBe(\"carousel\");\n expect(flex.contents).toHaveLength(1);\n });\n\n it(\"uses raw contents array when provided\", async () => {\n const contents = [\n { type: \"text\", text: \"Hello\" },\n { type: \"text\", text: \"World\" },\n ];\n const flex = await exec(\"custom\", { contents });\n expect(flex.type).toBe(\"bubble\");\n expect(flex.body.contents).toEqual(contents);\n });\n\n it(\"auto-builds card from title + body\", async () => {\n const flex = await exec(\"custom\", { title: \"Title\", body: \"Body text\" });\n expect(flex.type).toBe(\"bubble\");\n const contents = flex.body.contents;\n expect(contents[0].text).toBe(\"Title\");\n expect(contents[1].type).toBe(\"separator\");\n expect(contents[2].text).toBe(\"Body text\");\n });\n\n it(\"auto-builds card with colored header\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Workout\",\n body: \"Great session!\",\n header_color: \"#FF0000\",\n });\n expect(flex.header).toBeTruthy();\n expect(flex.header.backgroundColor).toBe(\"#FF0000\");\n expect(flex.header.contents[0].text).toBe(\"Workout\");\n // Title should be removed from body since it's in the header\n const bodyTexts = flex.body.contents.filter(\n (c: Record<string, unknown>) => c.type === \"text\",\n );\n expect(bodyTexts.every((t: Record<string, unknown>) => t.text !== \"Workout\")).toBe(true);\n });\n\n it(\"auto-builds card with buttons\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Choose\",\n buttons: [\n { label: \"Option A\", text: \"A\", style: \"primary\" },\n { label: \"Option B\", text: \"B\", style: \"secondary\" },\n ],\n });\n expect(flex.footer).toBeTruthy();\n expect(flex.footer.contents).toHaveLength(2);\n expect(flex.footer.contents[0].action.label).toBe(\"Option A\");\n });\n\n it(\"auto-builds button from button_label/button_text shorthand\", async () => {\n const flex = await exec(\"custom\", {\n title: \"🪴 モンステラ\",\n body: \"水やり記録なし\\n今日は水やりした?\",\n header_color: \"#4CAF50\",\n button_label: \"水やりした!\",\n button_text: \"水やり モンステラ した\",\n });\n expect(flex.footer).toBeTruthy();\n expect(flex.footer.contents).toHaveLength(1);\n expect(flex.footer.contents[0].action.label).toBe(\"水やりした!\");\n expect(flex.footer.contents[0].action.text).toBe(\"水やり モンステラ した\");\n expect(flex.footer.contents[0].style).toBe(\"primary\");\n });\n\n it(\"prefers buttons array over button_label shorthand\", async () => {\n const flex = await exec(\"custom\", {\n title: \"Test\",\n button_label: \"Ignored\",\n buttons: [{ label: \"Used\", text: \"used\", style: \"secondary\" }],\n });\n expect(flex.footer.contents).toHaveLength(1);\n expect(flex.footer.contents[0].action.label).toBe(\"Used\");\n });\n\n it(\"errors when no contents, title, body, or buttons\", async () => {\n const result = await execRaw(\"custom\", {});\n expect(result).toContain(\"Error\");\n expect(result).toContain(\"custom template requires\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// unknown template\n// ---------------------------------------------------------------------------\ndescribe(\"unknown template\", () => {\n it(\"returns error for unknown template\", async () => {\n const result = await execRaw(\"nonexistent\", {});\n expect(result).toContain(\"Error: unknown template\");\n expect(result).toContain(\"nonexistent\");\n });\n});\n\n// ---------------------------------------------------------------------------\n// tool metadata\n// ---------------------------------------------------------------------------\ndescribe(\"tool metadata\", () => {\n it(\"has correct name and description\", () => {\n expect(tool.name).toBe(\"flex_message\");\n expect(tool.description).toContain(\"Flex Message\");\n });\n\n it(\"generates valid tool definition\", () => {\n const def = tool.getDefinition();\n expect(def.type).toBe(\"function\");\n expect(def.function.name).toBe(\"flex_message\");\n expect(def.function.parameters.required).toContain(\"template\");\n expect(def.function.parameters.required).toContain(\"data\");\n });\n});\n"],"mappings":"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"}
|
package/dist/config/schema.d.mts
CHANGED
|
@@ -556,15 +556,15 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
556
556
|
export?: string | undefined;
|
|
557
557
|
}>, "many">>;
|
|
558
558
|
}, "strip", z.ZodTypeAny, {
|
|
559
|
-
exec: {
|
|
560
|
-
timeout: number;
|
|
561
|
-
};
|
|
562
559
|
web: {
|
|
563
560
|
search: {
|
|
564
561
|
apiKey: string;
|
|
565
562
|
maxResults: number;
|
|
566
563
|
};
|
|
567
564
|
};
|
|
565
|
+
exec: {
|
|
566
|
+
timeout: number;
|
|
567
|
+
};
|
|
568
568
|
restrictToWorkspace: boolean;
|
|
569
569
|
enabled?: string[] | undefined;
|
|
570
570
|
disabled?: string[] | undefined;
|
|
@@ -574,9 +574,6 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
574
574
|
export?: string | undefined;
|
|
575
575
|
}[] | undefined;
|
|
576
576
|
}, {
|
|
577
|
-
exec?: {
|
|
578
|
-
timeout?: number | undefined;
|
|
579
|
-
} | undefined;
|
|
580
577
|
enabled?: string[] | undefined;
|
|
581
578
|
web?: {
|
|
582
579
|
search?: {
|
|
@@ -584,6 +581,9 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
584
581
|
maxResults?: number | undefined;
|
|
585
582
|
} | undefined;
|
|
586
583
|
} | undefined;
|
|
584
|
+
exec?: {
|
|
585
|
+
timeout?: number | undefined;
|
|
586
|
+
} | undefined;
|
|
587
587
|
restrictToWorkspace?: boolean | undefined;
|
|
588
588
|
disabled?: string[] | undefined;
|
|
589
589
|
custom?: {
|
|
@@ -1015,15 +1015,15 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1015
1015
|
export?: string | undefined;
|
|
1016
1016
|
}>, "many">>;
|
|
1017
1017
|
}, "strip", z.ZodTypeAny, {
|
|
1018
|
-
exec: {
|
|
1019
|
-
timeout: number;
|
|
1020
|
-
};
|
|
1021
1018
|
web: {
|
|
1022
1019
|
search: {
|
|
1023
1020
|
apiKey: string;
|
|
1024
1021
|
maxResults: number;
|
|
1025
1022
|
};
|
|
1026
1023
|
};
|
|
1024
|
+
exec: {
|
|
1025
|
+
timeout: number;
|
|
1026
|
+
};
|
|
1027
1027
|
restrictToWorkspace: boolean;
|
|
1028
1028
|
enabled?: string[] | undefined;
|
|
1029
1029
|
disabled?: string[] | undefined;
|
|
@@ -1033,9 +1033,6 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1033
1033
|
export?: string | undefined;
|
|
1034
1034
|
}[] | undefined;
|
|
1035
1035
|
}, {
|
|
1036
|
-
exec?: {
|
|
1037
|
-
timeout?: number | undefined;
|
|
1038
|
-
} | undefined;
|
|
1039
1036
|
enabled?: string[] | undefined;
|
|
1040
1037
|
web?: {
|
|
1041
1038
|
search?: {
|
|
@@ -1043,6 +1040,9 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1043
1040
|
maxResults?: number | undefined;
|
|
1044
1041
|
} | undefined;
|
|
1045
1042
|
} | undefined;
|
|
1043
|
+
exec?: {
|
|
1044
|
+
timeout?: number | undefined;
|
|
1045
|
+
} | undefined;
|
|
1046
1046
|
restrictToWorkspace?: boolean | undefined;
|
|
1047
1047
|
disabled?: string[] | undefined;
|
|
1048
1048
|
custom?: {
|
|
@@ -1139,15 +1139,15 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1139
1139
|
port: number;
|
|
1140
1140
|
};
|
|
1141
1141
|
tools: {
|
|
1142
|
-
exec: {
|
|
1143
|
-
timeout: number;
|
|
1144
|
-
};
|
|
1145
1142
|
web: {
|
|
1146
1143
|
search: {
|
|
1147
1144
|
apiKey: string;
|
|
1148
1145
|
maxResults: number;
|
|
1149
1146
|
};
|
|
1150
1147
|
};
|
|
1148
|
+
exec: {
|
|
1149
|
+
timeout: number;
|
|
1150
|
+
};
|
|
1151
1151
|
restrictToWorkspace: boolean;
|
|
1152
1152
|
enabled?: string[] | undefined;
|
|
1153
1153
|
disabled?: string[] | undefined;
|
|
@@ -1245,9 +1245,6 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1245
1245
|
port?: number | undefined;
|
|
1246
1246
|
} | undefined;
|
|
1247
1247
|
tools?: {
|
|
1248
|
-
exec?: {
|
|
1249
|
-
timeout?: number | undefined;
|
|
1250
|
-
} | undefined;
|
|
1251
1248
|
enabled?: string[] | undefined;
|
|
1252
1249
|
web?: {
|
|
1253
1250
|
search?: {
|
|
@@ -1255,6 +1252,9 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1255
1252
|
maxResults?: number | undefined;
|
|
1256
1253
|
} | undefined;
|
|
1257
1254
|
} | undefined;
|
|
1255
|
+
exec?: {
|
|
1256
|
+
timeout?: number | undefined;
|
|
1257
|
+
} | undefined;
|
|
1258
1258
|
restrictToWorkspace?: boolean | undefined;
|
|
1259
1259
|
disabled?: string[] | undefined;
|
|
1260
1260
|
custom?: {
|
package/package.json
CHANGED
|
@@ -76,27 +76,44 @@ Skills use three-level loading:
|
|
|
76
76
|
|
|
77
77
|
If the skill produces output that would benefit from rich visual display (cards, progress trackers, summaries, confirmations), include `flex_message` tool usage in the SKILL.md.
|
|
78
78
|
|
|
79
|
-
Available templates: `fortune`, `info_card`, `action_buttons`, `receipt`, `morning_summary`, `hydration`.
|
|
79
|
+
Available templates: `fortune`, `info_card`, `action_buttons`, `receipt`, `morning_summary`, `hydration`, `custom`.
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
#### Custom template
|
|
82
|
+
|
|
83
|
+
Single card with auto-built layout:
|
|
82
84
|
```
|
|
83
85
|
flex_message(template="custom", data={
|
|
84
86
|
title: "カードタイトル",
|
|
85
87
|
body: "本文テキスト",
|
|
86
|
-
header_color: "#FF6B6B",
|
|
88
|
+
header_color: "#FF6B6B", // optional: colored header
|
|
89
|
+
button_label: "OK", // optional: single button shorthand
|
|
90
|
+
button_text: "送信テキスト",
|
|
91
|
+
// or use buttons array for multiple:
|
|
87
92
|
buttons: [
|
|
88
93
|
{ label: "ボタン", text: "送信テキスト", style: "primary" }
|
|
89
94
|
]
|
|
90
95
|
})
|
|
91
96
|
```
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
Carousel (horizontally swipeable cards):
|
|
99
|
+
```
|
|
100
|
+
flex_message(template="custom", data={
|
|
101
|
+
bubbles: [
|
|
102
|
+
{ type: "bubble", body: { type: "box", layout: "vertical", contents: [...] } },
|
|
103
|
+
{ type: "bubble", body: { type: "box", layout: "vertical", contents: [...] } }
|
|
104
|
+
]
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Output section for skills using flex
|
|
109
|
+
|
|
110
|
+
The card is **auto-sent** when the tool returns. Include this in SKILL.md:
|
|
94
111
|
```markdown
|
|
95
112
|
## Output
|
|
96
113
|
|
|
97
114
|
**ALWAYS use the `flex_message` tool** to display results as a rich card.
|
|
98
|
-
|
|
115
|
+
The card is sent automatically — do NOT repeat the JSON. Just respond naturally after calling the tool.
|
|
99
116
|
```
|
|
100
117
|
|
|
101
|
-
Good candidates for flex: trackers, reminders, summaries, confirmations, selections.
|
|
118
|
+
Good candidates for flex: trackers, reminders, summaries, confirmations, selections, lists.
|
|
102
119
|
Not needed for: conversational replies, simple text answers, file operations.
|