Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions TEST-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions docs/PLATFORMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion src/platforms/msteams/activity.js
Original file line number Diff line number Diff line change
Expand Up @@ -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, ""));
}
3 changes: 2 additions & 1 deletion src/platforms/msteams/help.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions test/help-text.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 …',
Expand Down
6 changes: 4 additions & 2 deletions test/platform-teams-events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

Expand Down
19 changes: 19 additions & 0 deletions test/platform-teams-graph-activity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Loading