Skip to content
Merged
3 changes: 2 additions & 1 deletion .agents/skills/mock-discord-testing/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: mock-discord-testing
description: Run Roomote Discord integration flows through the checked-in mock Discord REST harness and synthetic Gateway envelopes instead of a real Discord bot. Use when testing Discord task entry, follow-up queueing to active jobs, slash commands (`/new`, `/link`, `/help`), button interactions, outbound Discord posts, task threads and forum posts, message chunking, `DISCORD_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
description: Run Roomote Discord integration flows through the checked-in mock Discord REST harness and synthetic Gateway envelopes instead of a real Discord bot. Use when testing Discord task entry, follow-up queueing to active jobs, slash commands (`/new`, `/goal`, `/fast`, `/link`, `/help`), button interactions, outbound Discord posts, task threads and forum posts, message chunking, `DISCORD_API_BASE_URL` routing, `/mock/state`, or `/mock/events`.
---

# Mock Discord Testing
Expand Down Expand Up @@ -166,6 +166,7 @@ curl -s http://127.0.0.1:3014/mock/state | jq '.requests | map({method, path})'
See `references/scenarios.md` for the full user-journey catalog. Common picks:

- **`dm-task-entry`** — DM task kickoff that creates a cloud job and a task reply
- **`dm-fast-answer`** — `/fast request:<...>` interaction answers directly without creating a task
- **`guild-mention-task-entry`** — root-channel @mention creates a task thread
- **`followup-in-task-thread`** — message in the task thread queues to the running job instead of launching a new task
- **`slash-new-command`** — `/new request:<...>` interaction forces a fresh task
Expand Down
8 changes: 4 additions & 4 deletions .agents/skills/mock-discord-testing/references/scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ The core product invariant under test is: **each task owns one thread (or forum

## 1. dm-fast-answer

A linked user DMs `!fast <question>`.
A linked user invokes `/fast request:<question>` in a DM.

- Inject: `message` in a DM channel (type 1), text `!fast what file handles Discord events?`.
- Expect: eyes reaction on the inbound message; one inline bot answer in the same channel; **no** cloud job created.
- Assert: state `.messages` (one bot message), `.reactions`; DB has no new task row.
- Inject: `interaction` (type 2, `data.name: "fast"`, options `[{name: "request", type: 3, value: "what file handles Discord events?"}]`) in a DM channel.
- Expect: the deferred interaction response is edited with the answer; **no** cloud job is created.
- Assert: state interaction responses; DB has no new task row.

## 2. dm-task-entry

Expand Down

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

146 changes: 146 additions & 0 deletions apps/api/src/handlers/discord/__tests__/index.test.ts

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

33 changes: 33 additions & 0 deletions apps/api/src/handlers/discord/channel-auto-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getDiscordMessageContent,
isDiscordAudioAttachment,
isDiscordBotMentioned,
stripDiscordBotMention,
type DiscordGatewayEvent,
type DiscordMessage,
} from '@roomote/communication/discord-event';
Expand All @@ -23,6 +24,7 @@ import {
} from '@roomote/types';

import { apiLogger } from '../../logging.js';
import { hasCommunicationsFastModeDefault } from '../fast-agent-entry.js';
import { checkAutoStartChannelCache } from '../shared/auto-start-cache.js';
import {
CHANNEL_AUTO_START_FAILURE_MESSAGE,
Expand All @@ -36,6 +38,7 @@ import {
releaseAccountLinkDmSlot,
} from './account-link.js';
import { processDiscordAttachments } from './attachments.js';
import { processDiscordFastAgentMessage } from './fast-agent.js';
import { rememberPendingDiscordAccountLinkTask } from './pending-account-link-task.js';
import { startNewDiscordTask } from './task-orchestration.js';
import {
Expand Down Expand Up @@ -268,6 +271,36 @@ export async function maybeHandleDiscordChannelAutoStart(input: {
};
launchOwnerUserId = mappedUserId;
queuedMessageUserId = mappedUserId;

const defaultFastQuestion = stripDiscordBotMention(
getDiscordMessageContent(message),
botUserId,
);
if (
defaultFastQuestion &&
(await hasCommunicationsFastModeDefault(mappedUserId))
) {
void processDiscordFastAgentMessage({
event,
question: defaultFastQuestion,
sender: message.author,
senderUserId: mappedUserId,
provider,
applicationId: input.applicationId,
channel,
metadata: discordMetadataForChannel({
channel,
messageId: message.id,
anchorMessageId: message.id,
}),
sessionThreadId: message.id,
}).catch((error) => {
apiLogger.error(
`[DiscordChannelAutoStart] Failed to answer in Fast mode for ${logContext}: ${error instanceof Error ? error.message : String(error)}`,
);
});
return true;
}
}

const messageAttachments = getDiscordMessageAttachments(message);
Expand Down
Loading
Loading