diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fdd7bb..047c6e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ product overview. ## Unreleased +- Teams help identifies Heart eyes robot, Stop sign and Tick button by their emoji names. + Robot activation now recognizes the documented Teams `hearteyesrobot` and `smilerobot` IDs. + - Teams `/help` now includes the practical usage guide for files, settings, skills, memory, schedules and background work, with Teams-specific session controls and command examples. diff --git a/FEATURES.md b/FEATURES.md index 744051a..64317cf 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -8,6 +8,11 @@ Composio, skills, conversation memory/rules, reminders, schedules and background work. Examples use Teams controls and explain their permissions and configuration requirements. The authorized native command responds before an engine turn, in the requesting conversation. +- Reaction labels identify `:robot_face:` as Heart eyes robot, `:octagonal_sign:` as Stop sign, + and `:white_check_mark:` as Tick button. Teams reaction intake accepts Microsoft's documented + `hearteyesrobot` and `smilerobot` IDs alongside the legacy robot spellings, using the same + verified reactor identity, membership and event gates. Stop and Tick reactions do not control + Teams work; `/stop` and `/cancel` remain the supported cancellation commands. ## Claude login for nested commands diff --git a/TEST-PLAN.md b/TEST-PLAN.md index 7fea1f5..d488f30 100644 --- a/TEST-PLAN.md +++ b/TEST-PLAN.md @@ -2,6 +2,20 @@ ## Microsoft Teams help guide acceptance (2026-10-07) +- Automated reaction mapping: `test/platform-teams-events.test.js` and + `test/platform-teams-graph-activity.test.js` verify `hearteyesrobot` / `smilerobot` trigger through + native and Graph normalization as the reactor, preserving the original target and removed-event + suppression. `stopsign`, `2705_whiteheavycheckmark`, plain heart eyes and heart eyes dog do not + start a request. Help regression checks all three requested shortcode/name pairs. +- Verification of the emoji correction: 54 focused tests passed, zero failures/skips; + static checks and secret scanning passed. The expanded guide still fits one Teams message. +- [ ] LIVE (Claude and Codex): in separate approved beta Teams QA group/channel sessions with + reaction event delivery enabled, post "Reply exactly ROBOT_MAPPING_OK", then as an approved + test actor add **Heart eyes robot** from the Teams picker. Pass: one run under the reactor's + gateway identity replies ROBOT_MAPPING_OK in the source session for each engine. Remove the + reaction; delayed removed events must not run. Stop sign and Tick button must not start a run. + Mirror these cases in the private QA catalog through the requesting user's personal connection; + that connection is unavailable in this run and these live cases remain unexecuted. - [x] Automated: `node --test test/help-text.test.js test/teams-controls.test.js test/platform-controls.test.js test/platform-ingest.test.js` verifies the essential workflows, supported command list, Teams formatting/message budget, case-insensitive native dispatch, diff --git a/docs/PLATFORMS.md b/docs/PLATFORMS.md index 4e0c876..ca5c662 100644 --- a/docs/PLATFORMS.md +++ b/docs/PLATFORMS.md @@ -247,6 +247,12 @@ Task-module dialogs and broadcast mentions remain unavailable. connections, skills, memory, reminders, schedules and background work. In channels and group chats, mention the bot with the command; quote the original message or bot reply in a group chat to address that session. Help is returned by the gateway before invoking an engine. + The reaction legend names Heart eyes robot (`:robot_face:`), Stop sign (`:octagonal_sign:`), + and Tick button (`:white_check_mark:`). Robot activation accepts Teams' `hearteyesrobot` and + `smilerobot` event IDs. Microsoft calls the green tick **Checkmark button**, ID + `2705_whiteheavycheckmark`; Stop sign is `stopsign`. See the + [Teams reactions reference](https://learn.microsoft.com/en-us/microsoftteams/platform/agents-in-teams/teams-reactions-reference). + Stop/Tick reactions have no gateway action in Teams; stop work with `/stop` or `/cancel`. - `/settings` opens a six-page console in the original channel/thread or chat: General, Variables, MCPs, Skills, Automations and Resume. It retains the source conversation and session even when opened from a channel or group chat. `/secrets` opens its Variables page directly. diff --git a/src/platforms/msteams/activity.js b/src/platforms/msteams/activity.js index 6ac8544..f4ad360 100644 --- a/src/platforms/msteams/activity.js +++ b/src/platforms/msteams/activity.js @@ -166,5 +166,7 @@ async function fetchBytes(url, fetchImpl) { } export function isRobotReaction(value) { - return ["🤖", "robot", "robot_face"].includes(String(value || "").replace(/\uFE0F/g, "")); + // Teams names these Smile robot and Heart eyes robot in its reaction picker/reference. + // Keep the existing Unicode/legacy spellings for older event payloads. + return ["🤖", "robot", "robot_face", "smilerobot", "hearteyesrobot"].includes(String(value || "").replace(/\uFE0F/g, "")); } diff --git a/src/platforms/msteams/help.js b/src/platforms/msteams/help.js index a120471..0a01b2b 100644 --- a/src/platforms/msteams/help.js +++ b/src/platforms/msteams/help.js @@ -3,7 +3,8 @@ export const TEAMS_HELP_TEXT = '**How to use me**\n' + 'In a personal chat, just send your request. In a channel or group chat, select the bot mention and write `@agent your request`, including for commands such as `@agent /help`. Keep channel follow-ups in the same thread and mention me again. In group chats, quote the original message or my reply and mention me to continue or control that session; a new unquoted group message starts a new session.\n\n' + - '• **Act on a message:** where Teams reaction events are enabled and delivered to the gateway, react 🤖 to ask me to act on that message. Otherwise, mention me with your request.\n' + + '• **Act on a message:** where Teams reaction events are enabled and delivered to the gateway, react 🤖 (**Heart eyes robot**) to ask me to act on that message. Otherwise, mention me with your request.\n' + + '• **Reaction names:** 🤖 `:robot_face:` — Heart eyes robot; 🛑 `:octagonal_sign:` — Stop sign; ✅ `:white_check_mark:` — Tick button. In Teams, stopping uses `/stop` or `/cancel`; Stop sign and Tick button reactions currently have no gateway action.\n' + '• **Voice prompts:** attach a downloadable voice note and mention me in channels/groups; personal chats need no mention. Voice notes require local Whisper to be enabled and installed. Typed text stays as instructions, and raw audio is never sent to Claude or Codex. If transcription is unavailable, send text or ask an administrator to check Whisper.\n' + '• **Control a live run:** send `/stop` or `/cancel` to stop the selected session’s active and queued work. In a channel, mention me in that thread; in a group chat, quote the original message or my reply and mention me. Only the run author or an administrator may stop a request. New requests for the same session queue while I work.\n' + '• **Open files:** attach a file/image and ask me to read it. Personal-chat uploads download directly; group/channel files require the configured allowed drives and Microsoft permissions. Use `/files [folder]` to browse this conversation’s workspace privately in your personal chat, download files, edit eligible text, or upload files when you have write access. Browser actions require the gateway Public URL. Use `/sendfile ` for a personal file-consent card; accept or decline it there. Native sending supports nonempty files up to 10 MB; larger files use browser download. Open a personal chat with the bot first for private controls.\n' + diff --git a/test/help-text.test.js b/test/help-text.test.js index 7a16a31..1f8baa7 100644 --- a/test/help-text.test.js +++ b/test/help-text.test.js @@ -10,6 +10,8 @@ const { teamsAdapter } = await import('../src/platforms/msteams.js'); test("Teams /help includes practical workflows and its supported commands", () => { for (const expected of [ '**How to use me**', '`@agent /help`', 'quote the original message', 'react 🤖', + '`:robot_face:` — Heart eyes robot', '`:octagonal_sign:` — Stop sign', + '`:white_check_mark:` — Tick button', 'reactions currently have no gateway action', 'local Whisper', 'Only the run author or an administrator', 'requests for the same session queue', 'allowed drives and Microsoft permissions', '10 MB', 'Public URL', 'personal chat', 'Stored credential values are never shown', 'Composio', 'list skills', 'remember that …', diff --git a/test/platform-teams-events.test.js b/test/platform-teams-events.test.js index 851dd6a..21a3d47 100644 --- a/test/platform-teams-events.test.js +++ b/test/platform-teams-events.test.js @@ -20,12 +20,14 @@ test('edit event subtype is explicit; real mention entity is required for the me }); test('robot additions carry reactor identity and target; other reactions and removals do not run', () => { - for (const reaction of ['🤖', 'robot', 'robot_face', '🤖\uFE0F']) { + for (const reaction of ['🤖', 'robot', 'robot_face', '🤖\uFE0F', 'smilerobot', 'hearteyesrobot']) { const value = normalize({ type: 'messageReaction', replyToId: 'bot-answer', text: '', entities: [], reactionsAdded: [{ type: reaction }] }); assert.equal(value.trigger, 'reaction'); assert.equal(value.replyToId, 'bot-answer'); assert.equal(value.userId, '29:author'); assert.equal(value.mentionsBot, false); } assert.equal(normalize({ type: 'messageReaction', replyToId: 'bot-answer', reactionsRemoved: [{ type: 'robot' }] }), null); - assert.equal(normalize({ type: 'messageReaction', replyToId: 'bot-answer', reactionsAdded: [{ type: 'like' }] }), null); + for (const reaction of ['like', 'stopsign', '2705_whiteheavycheckmark', 'hearteyes', 'hearteyesdog']) { + assert.equal(normalize({ type: 'messageReaction', replyToId: 'bot-answer', reactionsAdded: [{ type: reaction }] }), null); + } assert.equal(normalize({ type: 'messageReaction', reactionsAdded: [{ type: 'robot' }] }), null, 'no target cannot run'); }); diff --git a/test/platform-teams-graph-activity.test.js b/test/platform-teams-graph-activity.test.js index cba3e4c..ee3cdae 100644 --- a/test/platform-teams-graph-activity.test.js +++ b/test/platform-teams-graph-activity.test.js @@ -12,6 +12,25 @@ test('robot reaction runs as reactor and anchors the original message', async () assert.equal(event.mentionsBot, false); assert.equal(event.threadKey, ''); assert.match(event.messageId, /^reaction:/); }); +test('documented Teams robot IDs trigger Graph reactions without granting authority to other emoji', async () => { + for (const reactionType of ['smilerobot', 'hearteyesrobot']) { + const message = fixture(); + message.reactions = [{ ...reaction, reactionType }]; + message.messageHistory[0].reaction = message.reactions[0]; + const [event] = await normalizeGraphEvents(message, row, opts); + assert.equal(event.userId, '29:reactor'); + assert.equal(event.replyToId, 'message1'); + assert.equal(event.trigger, 'reaction'); + message.reactions = []; + assert.deepEqual(await normalizeGraphEvents(message, row, opts), []); + } + for (const reactionType of ['stopsign', '2705_whiteheavycheckmark', 'hearteyes', 'hearteyesdog']) { + const message = fixture(); + message.reactions = [{ ...reaction, reactionType }]; + message.messageHistory[0].reaction = message.reactions[0]; + assert.deepEqual(await normalizeGraphEvents(message, row, opts), []); + } +}); test('history dedup key survives later snapshots but remove/readd gets a distinct key', async () => { const message = fixture(); const [first] = await normalizeGraphEvents(message, row, opts); message.lastModifiedDateTime = '2026-09-09T10:02:00Z';