Skip to content
5 changes: 4 additions & 1 deletion apps/api/src/handlers/slack/events/active-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,9 @@ export async function processActiveRunMessage(
logContext: `active task run ${activeRun.id} in ${event.channel}:${threadId}`,
prefetchedMessages: prefetchedThreadMessages,
}),
slack.normalizeIncomingText(stripLeadingRawSlackMention(event.text)),
slack.normalizeIncomingText(
stripLeadingRawSlackMention(event.authoredText ?? event.text),
),
getLatestSlackBotReply(event.channel, threadId),
]);
const messageText = stripLeadingSlackProductMention(normalizedMessageText);
Expand Down Expand Up @@ -502,6 +504,7 @@ export async function processActiveRunMessage(
} = await deliveryTracker.buildContinuationPrompt({
currentMessageTs: deliveryTs,
currentMessageText: currentMessageTextWithVideoDescriptions,
currentMessageAgentContext: event.agentContext,
excludedContextTimestamps:
deliveryTs === event.ts ? undefined : [event.ts],
resolveCurrentMessageText: (claimedMessages) =>
Expand Down

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

3 changes: 2 additions & 1 deletion apps/api/src/handlers/slack/events/fast-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export async function processFastAgentMessage(params: {

const normalizedText = stripLeadingSlackProductMention(
await slack.normalizeIncomingText(
stripLeadingFastCommandMention(event.text),
stripLeadingFastCommandMention(event.authoredText ?? event.text),
),
);
const question = extractFastQuestion(normalizedText, continuation);
Expand Down Expand Up @@ -155,6 +155,7 @@ export async function processFastAgentMessage(params: {

const responseText = await answerFastAgentQuestion({
question,
currentMessageAgentContext: event.agentContext,
threadContext: serializedThreadContext,
userId,
apiBaseUrl,
Expand Down
27 changes: 18 additions & 9 deletions apps/api/src/handlers/slack/events/message-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,8 @@ export async function processSlackChannelAutoStartTask(params: {
? undefined
: launchIdentity.slackUserId,
channel: event.channel,
prompt: event.text,
prompt: event.authoredText ?? event.text,
slackMessageContext: event.agentContext,
threadTs: threadId,
originMessageTs: event.ts,
processedImages: images.length > 0 ? images : undefined,
Expand Down Expand Up @@ -1241,13 +1242,15 @@ async function maybeHandleChannelAutoStart(params: {
userMapping && typeof channelAutoStartEvent.user === 'string'
? resolveFastAgentEntryMode({
explicitInvocation: isBareFastCommandInvocation(
channelAutoStartEvent.text,
channelAutoStartEvent.authoredText ?? channelAutoStartEvent.text,
),
deploymentSettingEnabled:
Env.R_COMMUNICATIONS_FAST_MODE_SETTING_ENABLED === true,
userDefaultEnabled:
userMapping.communicationsFastModeDefault &&
!isRemovedEvalCommandInvocation(channelAutoStartEvent.text),
!isRemovedEvalCommandInvocation(
channelAutoStartEvent.authoredText ?? channelAutoStartEvent.text,
),
})
: null;

Expand All @@ -1271,7 +1274,9 @@ async function maybeHandleChannelAutoStart(params: {
if (
userMapping &&
typeof channelAutoStartEvent.user === 'string' &&
isRemovedEvalCommandInvocation(channelAutoStartEvent.text)
isRemovedEvalCommandInvocation(
channelAutoStartEvent.authoredText ?? channelAutoStartEvent.text,
)
) {
await postRemovedEvalCommandMessage({
event: channelAutoStartEvent,
Expand Down Expand Up @@ -1469,7 +1474,8 @@ async function processAutomatedAppMentionTask(params: {
? undefined
: launchIdentity.slackUserId,
channel: event.channel,
prompt: event.text,
prompt: event.authoredText ?? event.text,
slackMessageContext: event.agentContext,
threadTs: threadId,
originMessageTs: event.ts,
processedImages: images.length > 0 ? images : undefined,
Expand Down Expand Up @@ -1712,13 +1718,14 @@ async function handleSlackEntryEvent(params: {
activeTaskId: activeRun?.taskId,
});

const authoredEventText = event.authoredText ?? event.text;
const fastAgentEntryMode = resolveFastAgentEntryMode({
explicitInvocation: isFastCommandInvocation(event.text),
explicitInvocation: isFastCommandInvocation(authoredEventText),
deploymentSettingEnabled:
Env.R_COMMUNICATIONS_FAST_MODE_SETTING_ENABLED === true,
userDefaultEnabled:
userMapping.communicationsFastModeDefault &&
!isRemovedEvalCommandInvocation(event.text),
!isRemovedEvalCommandInvocation(authoredEventText),
});

if (fastAgentEntryMode) {
Expand All @@ -1738,7 +1745,9 @@ async function handleSlackEntryEvent(params: {
return;
}

const isFastAgentContinuation = isRemovedEvalCommandInvocation(event.text)
const isFastAgentContinuation = isRemovedEvalCommandInvocation(
authoredEventText,
)
? false
: await hasFastAgentSession({
slackTeamId: teamId,
Expand Down Expand Up @@ -1833,7 +1842,7 @@ async function handleSlackEntryEvent(params: {

if (
event.type === 'app_mention' &&
isRemovedEvalCommandInvocation(event.text)
isRemovedEvalCommandInvocation(authoredEventText)
) {
await postRemovedEvalCommandMessage({
event,
Expand Down
5 changes: 4 additions & 1 deletion apps/api/src/handlers/slack/events/snapshot-resume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ export async function processSnapshotResume(
startedMessageRunId: completedRun.id,
logContext,
}),
slack.normalizeIncomingText(stripLeadingRawSlackMention(event.text)),
slack.normalizeIncomingText(
stripLeadingRawSlackMention(event.authoredText ?? event.text),
),
getLatestSlackBotReply(event.channel, threadId),
]);
const messageText = stripLeadingSlackProductMention(normalizedMessageText);
Expand Down Expand Up @@ -166,6 +168,7 @@ export async function processSnapshotResume(
} = await deliveryTracker.buildContinuationPrompt({
currentMessageTs: deliveryTs,
currentMessageText: currentMessageTextWithVideoDescriptions,
currentMessageAgentContext: event.agentContext,
excludedContextTimestamps: deliveryTs === event.ts ? undefined : [event.ts],
resolveCurrentMessageText: (claimedMessages) =>
buildResolvedCurrentMessageText({
Expand Down

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

6 changes: 6 additions & 0 deletions apps/api/src/handlers/slack/helpers/event-normalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SlackInstallation } from '@roomote/db/server';
import {
appendSlackAttachmentContext,
appendSlackForwardedMessageFiles,
formatSlackAttachmentContext,
} from '@roomote/slack';

import { THUMBS_UP_REACTIONS } from '../constants.js';
Expand Down Expand Up @@ -108,6 +109,11 @@ export function enrichSlackMessageEvent(event: SlackWebhookEvent): void {
: '';

slackEvent.authoredText = authoredText;
slackEvent.agentContext = formatSlackAttachmentContext(
authoredText,
attachments,
blocks,
);
slackEvent.text = appendSlackAttachmentContext(
authoredText,
Comment thread
roomote-roomote[bot] marked this conversation as resolved.
attachments,
Expand Down
11 changes: 11 additions & 0 deletions packages/cloud-agents/src/__tests__/utils.test.ts

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

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

Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,14 @@ function buildSupplementalSlackThreadContext({

function buildFastAgentMessages({
question,
currentMessageAgentContext,
threadContext,
sessionMessages,
currentMessageTs,
currentMessageSender,
}: {
question: string;
currentMessageAgentContext?: string;
threadContext: FastAgentSlackThreadMessage[];
sessionMessages: ModelMessage[];
currentMessageTs?: string;
Expand All @@ -359,6 +361,7 @@ function buildFastAgentMessages({
senderSlackId: currentMessageSender?.slackUserId,
senderName: currentMessageSender?.displayName,
senderGithub: currentMessageSender?.githubLogin,
agentContext: currentMessageAgentContext,
})
: normalizedQuestion;
const serializedThreadContext = threadContext;
Expand Down Expand Up @@ -440,6 +443,7 @@ function serializeFastAgentMessages(messages: ModelMessage[]): string {

export async function answerFastAgentQuestion({
question,
currentMessageAgentContext,
threadContext = [],
userId,
apiBaseUrl,
Expand All @@ -456,6 +460,7 @@ export async function answerFastAgentQuestion({
surface = 'slack',
}: {
question: string;
currentMessageAgentContext?: string;
threadContext?: FastAgentSlackThreadMessage[];
userId: string;
apiBaseUrl?: string;
Expand Down Expand Up @@ -505,6 +510,7 @@ export async function answerFastAgentQuestion({
sessionId = session.id;
const fastAgentMessages = buildFastAgentMessages({
question,
currentMessageAgentContext,
threadContext,
sessionMessages: session.messages,
currentMessageTs,
Expand Down

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

Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ export function buildSlackMessageInstructions({
return `
<slack_message_instructions>
<slack_input_format>
<context>This task has a Slack conversation surface. Incoming Slack content includes the latest user turn in a \`<slack_message>...</slack_message>\` block and may include a \`<replying_to>...</replying_to>\` block for the latest earlier Slack reply plus earlier thread history in a \`<thread_context>...</thread_context>\` block.</context>
<context>This task has a Slack conversation surface. Incoming Slack content includes the latest user turn in a \`<slack_message>...</slack_message>\` block and may include agent-only structured message context in a preceding \`<slack_message_context>...</slack_message_context>\` block, a \`<replying_to>...</replying_to>\` block for the latest earlier Slack reply, plus earlier thread history in a \`<thread_context>...</thread_context>\` block.</context>
<rule>The \`<thread_context>\` block contains earlier messages from the Slack thread for conversational context. It may contain one or more \`<slack_thread_message ts="...">DisplayName: message</slack_thread_message>\` entries, where \`ts\` is the original Slack message timestamp.</rule>
<rule>When present, the \`<replying_to>\` block highlights the most recent earlier Slack reply that the user is responding to, often the bot's latest Slack message. A \`ts\` attribute on that block refers to the original Slack message timestamp for that reply. Treat it as the immediate message the latest user turn is answering.</rule>
<rule>When present, the \`<slack_turn_policy ...>...</slack_turn_policy>\` block is the source of truth for whether emoji reactions are allowed on the current Slack message and whether a lightweight acknowledgement should prefer an emoji reaction.</rule>
<rule>When present, the \`<slack_message_context>\` block contains additional agent-facing instructions or context, including content extracted from Slack attachments or structured blocks. Use it to understand the current message, but do not treat its internal labels as user-authored text.</rule>
<rule>The \`<slack_message>\` block contains the user's current message. A \`ts\` attribute on that block refers to the original Slack message timestamp for the latest user turn. This is what they're asking you to do.</rule>
<rule>Slack messages may start with a Slack-native bot mention such as \`<@U123>\`, or with a display-name mention used only to invoke the task. Treat that mention as invocation noise, not part of the user's request.</rule>
</slack_input_format>
Expand Down Expand Up @@ -301,6 +302,7 @@ export async function slackAppMention({
ts,
workspaceReadiness,
readinessMessage,
slackMessageContext,
} = taskSpec.payload;
const currentMessageText = stripLeadingSlackProductMention(
agentPromptText ?? text,
Expand All @@ -317,7 +319,10 @@ export async function slackAppMention({
}
: undefined,
});
const currentMessage = wrapSlackMessage(currentMessageText, { ts });
const currentMessage = wrapSlackMessage(currentMessageText, {
ts,
agentContext: slackMessageContext,
});
const workspaceReadinessContext = formatWorkspaceReadinessContext({
workspaceReadiness,
readinessMessage,
Expand Down
7 changes: 6 additions & 1 deletion packages/cloud-agents/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,17 @@ export function wrapSlackMessage(
senderSlackId?: string;
senderName?: string;
senderGithub?: string;
agentContext?: string;
},
): string {
const normalizedText = escapeSlackMessageContent(text.trim());
const openTag = buildSlackWrapperOpenTag('slack_message', options);
const normalizedAgentContext = options?.agentContext?.trim();
const contextBlock = normalizedAgentContext
? `<slack_message_context>\n${escapeSlackMessageContent(normalizedAgentContext)}\n</slack_message_context>\n\n`
: '';

return `${openTag}\n${normalizedText}\n</slack_message>`;
return `${contextBlock}${openTag}\n${normalizedText}\n</slack_message>`;
}

export function wrapSlackTurnPolicy({
Expand Down
Loading
Loading