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

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

50 changes: 50 additions & 0 deletions apps/worker/src/mcp/roomote-mcp-server/__tests__/upload.test.ts

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 @@ -7,6 +7,12 @@ export function resetPostedProofThreadsForTest(): void {
postedProofThreads.clear();
}

export function isVisualProofAutoPostEnabled(): boolean {
const value = process.env.ROOMOTE_SLACK_PROOF_AUTO_POST?.trim().toLowerCase();

return value === '1' || value === 'true' || value === 'yes';
}

function hasChatReplyContext(): boolean {
return Boolean(
(process.env.ROOMOTE_SLACK_CHANNEL?.trim() &&
Expand Down Expand Up @@ -63,7 +69,7 @@ export async function replyToChatWithVisualProof({
replyToChatThreadImpl?: typeof replyToChatThread;
}): Promise<boolean> {
const config = roomoteConfig;
if (!config || !hasChatReplyContext()) {
if (!config || !isVisualProofAutoPostEnabled() || !hasChatReplyContext()) {
return false;
}

Expand Down
11 changes: 9 additions & 2 deletions apps/worker/src/mcp/roomote-mcp-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {

import { handleCreatePlan } from './create-plan.js';
import { handleUpload } from './upload.js';
import { isVisualProofAutoPostEnabled } from './chat-proof-auto-post.js';
import { handleDescribeVideo } from './describe-video.js';
import { handleDownload } from './download.js';
import { handleListArtifacts } from './list-artifacts.js';
Expand Down Expand Up @@ -170,7 +171,11 @@ roomoteMcpServer.registerTool(
description:
'Create, upload, download, and list artifacts in Roomote. ' +
'Use action "create_plan" to create a markdown plan artifact (requires title and content). Returns viewUrl for sharing. ' +
'Use action "upload" to upload a workspace-relative file or an absolute file under /tmp (requires path and type). Use type "general" for ordinary files. Use type "visual-proof" for uploaded screenshots or proof artifacts that should be treated as visual proof; for Slack-started tasks, visual-proof uploads are posted back to the originating Slack thread automatically. Returns rawUrl for direct embedding (for example PR <img src>). ' +
'Use action "upload" to upload a workspace-relative file or an absolute file under /tmp (requires path and type). Use type "general" for ordinary files. ' +
(isVisualProofAutoPostEnabled()
? 'Use type "visual-proof" for uploaded screenshots or proof artifacts that should be treated as visual proof; for Slack-started tasks when visual-proof auto-post is enabled, visual-proof uploads are posted back to the originating Slack thread automatically. '
: 'Use type "visual-proof" for uploaded screenshots or proof artifacts that should be treated as visual proof. Visual-proof uploads are not auto-posted to chat for this task; when the image should appear in the originating thread, pass returned artifact IDs to `send_chat_reply` via `imageArtifactIds` (or share `viewUrl`/`rawUrl` in the reply text for non-images). ') +
'Returns rawUrl for direct embedding (for example PR <img src>). ' +
(shouldIncludeLegacySlackArtifactCompositionGuidance()
? 'After uploading image files, if `send_chat_reply` or `post_to_slack_channel` is available, pass the returned artifact IDs to those tools via `imageArtifactIds` so the user sees them directly in Slack. '
: '') +
Expand Down Expand Up @@ -198,7 +203,9 @@ roomoteMcpServer.registerTool(
type: manageArtifactsUploadTypeSchema
.optional()
.describe(
'Artifact type for upload. Required for upload; use "general" for ordinary files and "visual-proof" for visual proof that auto-posts to Slack for Slack-started tasks.',
isVisualProofAutoPostEnabled()
? 'Artifact type for upload. Required for upload; use "general" for ordinary files and "visual-proof" for visual proof that auto-posts to Slack for Slack-started tasks when visual-proof auto-post is enabled.'
: 'Artifact type for upload. Required for upload; use "general" for ordinary files and "visual-proof" for visual proof. Visual-proof uploads do not auto-post to chat for this task.',
),
artifactType: taskArtifactTypeSchema
.optional()
Expand Down
5 changes: 5 additions & 0 deletions packages/cloud-agents/src/server/cloud-agent-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ export async function generatePrompt({
const backgroundProofCaptureEnabled = await evaluateOrgFeatureFlag(
FeatureFlag.BackgroundSubagents,
);
const visualProofAutoPostEnabled = await evaluateOrgFeatureFlag(
FeatureFlag.SlackProofAutoPost,
);
const prAction = await getDeploymentPrAction().catch(() => undefined);

switch (taskSpec.type) {
Expand Down Expand Up @@ -164,6 +167,7 @@ export async function generatePrompt({
attribution: commitAuthor,
visualProofAutoScreencastEnabled,
backgroundProofCaptureEnabled,
visualProofAutoPostEnabled,
prAction,
});
}
Expand Down Expand Up @@ -308,6 +312,7 @@ export async function generatePrompt({
if (slackChannel && slackThreadTs) {
const slackInstructions = buildSlackMessageInstructions({
includeRequestUserInputGuidance: true,
visualProofAutoPostEnabled,
});
result.harnessInstructions = result.harnessInstructions
? `${slackInstructions}\n\n${result.harnessInstructions}`
Expand Down

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

13 changes: 11 additions & 2 deletions packages/cloud-agents/src/server/workflows/slackAppMention.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@ import { standardTask } from './standardTask';

export function buildSlackMessageInstructions({
includeRequestUserInputGuidance = false,
visualProofAutoPostEnabled = false,
}: {
includeRequestUserInputGuidance?: boolean;
visualProofAutoPostEnabled?: boolean;
} = {}): string {
const slackProofDeliveryInstructions = `
const slackProofDeliveryInstructions = visualProofAutoPostEnabled
? `
<rule>Built-in visual proof for the current proof milestone is already posted back to the originating Slack thread by the worker when trusted Slack context exists.</rule>
<rule>When that built-in proof auto-post happens, do not send a second Slack reply that only narrates the visible proof, counts screenshots, names localhost capture URLs, mentions internal temp or artifact file paths, repeats the capture summary, or says there was no blocker. Treat the built-in proof post as the proof-ready update unless the proof is blocked or that detail materially changes the user's next step.</rule>
<rule>Keep later Slack replies focused on the user outcome, delivery state, blocker, or next action rather than restating what is already visible in the proof attachments.</rule>`;
<rule>Keep later Slack replies focused on the user outcome, delivery state, blocker, or next action rather than restating what is already visible in the proof attachments.</rule>`
: `
<rule>Visual-proof uploads are not auto-posted to Slack for this task. When proof needs to be visible in the originating thread, share it with \`send_chat_reply\`: pass image artifact IDs via \`imageArtifactIds\`, or include artifact \`viewUrl\`/\`rawUrl\` links in the reply text for non-images.</rule>
<rule>When other task-generated images were uploaded earlier in the same run and still need to be shown in the thread, pass those artifact IDs to \`send_chat_reply\` via \`imageArtifactIds\`.</rule>`;

return `
<slack_message_instructions>
Expand Down Expand Up @@ -239,6 +245,7 @@ export async function slackAppMention({
username: _legacyUsername,
visualProofAutoScreencastEnabled,
backgroundProofCaptureEnabled,
visualProofAutoPostEnabled,
prAction,
}: {
taskSpec: SlackAppMentionTask;
Expand All @@ -249,6 +256,7 @@ export async function slackAppMention({
username?: string;
visualProofAutoScreencastEnabled?: boolean;
backgroundProofCaptureEnabled?: boolean;
visualProofAutoPostEnabled?: boolean;
prAction?: PrAction;
}): Promise<{
prompt: string;
Expand Down Expand Up @@ -324,6 +332,7 @@ export async function slackAppMention({

const slackInstructions = buildSlackMessageInstructions({
includeRequestUserInputGuidance: true,
visualProofAutoPostEnabled,
});
result.harnessInstructions = result.harnessInstructions
? `${slackInstructions}\n\n${result.harnessInstructions}`
Expand Down
Loading