From e68819cf0d3162a797d67c10c7e568b59d6c1d7b Mon Sep 17 00:00:00 2001 From: "@daniel-lxs" <57051444+daniel-lxs@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:41:10 +0000 Subject: [PATCH 1/3] feat: pass Slack Fast images to coding tasks --- .../src/run-task/__tests__/run-task.test.ts | 12 ++++++-- .../__tests__/fast-agent-service.test.ts | 8 ++++++ .../fast-agent-task-launcher.test.ts | 28 +++++++++++++++++++ .../fast-agent/fast-agent-conversation.ts | 1 + .../server/fast-agent/fast-agent-service.ts | 1 + .../fast-agent/fast-agent-task-launcher.ts | 12 +++++++- .../src/__tests__/slack-notifier.test.ts | 9 ++++++ packages/slack/src/thread-image-utils.ts | 1 - 8 files changed, 68 insertions(+), 4 deletions(-) diff --git a/apps/worker/src/run-task/__tests__/run-task.test.ts b/apps/worker/src/run-task/__tests__/run-task.test.ts index 76e42d9bf..fc0d5aacc 100644 --- a/apps/worker/src/run-task/__tests__/run-task.test.ts +++ b/apps/worker/src/run-task/__tests__/run-task.test.ts @@ -3233,7 +3233,12 @@ describe('runTask', () => { taskId: 'task-151', payloadKind: TaskPayloadKind.StandardTask, harness: 'opencode-server', - payload: {}, + payload: { + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], + }, result: null, } as never, envVars: {}, @@ -3282,7 +3287,10 @@ describe('runTask', () => { const harnessManager = harnessManagerInstances.at(0); expect(harnessManager?.startNewTask).toHaveBeenCalledWith({ prompt: 'Fix the failing test', - images: undefined, + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], visibleInTranscript: false, }); expect(harnessManager?.initializeWithoutPrompt).not.toHaveBeenCalled(); diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts index ce25ce75f..effc03197 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts @@ -1848,6 +1848,10 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { const result = await answerFastAgentQuestion({ ...baseParams, question: 'Fix checkout.', + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/gif;base64,c2NyZWVuc2hvdC0y', + ], adapter, }); @@ -1860,6 +1864,10 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { ); expect(launchTask).toHaveBeenCalledWith( expect.objectContaining({ + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/gif;base64,c2NyZWVuc2hvdC0y', + ], model: 'anthropic/claude-sonnet-5', prompt: 'Fix checkout.', }), diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-task-launcher.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-task-launcher.test.ts index ee400820e..54c629590 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-task-launcher.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-task-launcher.test.ts @@ -116,6 +116,9 @@ describe('createFastAgentSlackTaskLauncher', () => { taskId: 'task-1', taskUrl: 'https://roomote.example/task/task-1', }); + expect( + mocks.enqueueTask.mock.calls[0]?.[0]?.task.payload, + ).not.toHaveProperty('images'); expect(order).toEqual(['kickoff', 'queued']); }); @@ -163,6 +166,31 @@ describe('createFastAgentSlackTaskLauncher', () => { expect(task.payload).not.toHaveProperty('environmentId'); }); + it('retains multiple Fast turn images in the child task payload', async () => { + const images = [ + 'data:image/png;base64,cG5nLWJ5dGVz', + 'data:image/webp;base64,d2VicC1ieXRlcw==', + ]; + const launchTask = createFastAgentSlackTaskLauncher({ + userId: 'user-1', + teamId: 'T123', + channelId: 'C123', + threadTs: '100.001', + }); + + await launchTask({ + prompt: 'Implement the UI shown in these screenshots', + images, + environmentId: null, + parentSessionId: '11111111-1111-4111-8111-111111111111', + postKickoff: vi.fn(), + }); + + expect(mocks.enqueueTask.mock.calls[0]?.[0]?.task.payload.images).toEqual( + images, + ); + }); + it('runs afterKickoff inside the launch gate', async () => { const order: string[] = []; const afterKickoff = vi.fn(async () => { diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-conversation.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-conversation.ts index 0c6c4eafa..3bf9bb36d 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-conversation.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-conversation.ts @@ -53,6 +53,7 @@ export type FastAgentReaction = { export type LaunchFastAgentTask = (params: { prompt: string; + images?: string[]; environmentId: string | null; model?: string | null; parentSessionId: string; diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts index 33118bd5f..7b8310cfb 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts @@ -1635,6 +1635,7 @@ export async function answerFastAgentQuestion({ throwIfTurnCancelled(); const result = await adapter.launchTask({ prompt: args.prompt, + ...(images.length > 0 ? { images } : {}), environmentId: args.environmentId ?? null, model: args.model ?? null, parentSessionId: session.id, diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-task-launcher.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-task-launcher.ts index ccbf50880..ca8b5bba1 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-task-launcher.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-task-launcher.ts @@ -46,17 +46,27 @@ export function createFastAgentTaskLauncher( ): LaunchFastAgentTask { return async ({ prompt, + images, environmentId, model, parentSessionId, postKickoff, }) => { - const task = await params.buildTask({ + const builtTask = await params.buildTask({ prompt, environmentId, model, parentSessionId, }); + const task = images?.length + ? { + ...builtTask, + payload: { + ...builtTask.payload, + images, + }, + } + : builtTask; let taskUrl: string | undefined; let preparedTaskRun: { id: number; taskId: string } | undefined; diff --git a/packages/slack/src/__tests__/slack-notifier.test.ts b/packages/slack/src/__tests__/slack-notifier.test.ts index f3f57152a..a000f642a 100644 --- a/packages/slack/src/__tests__/slack-notifier.test.ts +++ b/packages/slack/src/__tests__/slack-notifier.test.ts @@ -1168,6 +1168,14 @@ describe('SlackNotifier', () => { filetype: 'svg', }; + const misleadingFilename: SlackFile = { + ...smallImage, + id: 'F5', + name: 'document.png', + mimetype: 'application/pdf', + filetype: 'pdf', + }; + getGlobalWithFetch().fetch = vi.fn().mockResolvedValue({ ok: true, arrayBuffer: async () => new TextEncoder().encode('fake-image').buffer, @@ -1178,6 +1186,7 @@ describe('SlackNotifier', () => { largeImage, textFile, svgFile, + misleadingFilename, ]); expect(getGlobalWithFetch().fetch).toHaveBeenCalledTimes(1); diff --git a/packages/slack/src/thread-image-utils.ts b/packages/slack/src/thread-image-utils.ts index f9d3bd404..0f23f2179 100644 --- a/packages/slack/src/thread-image-utils.ts +++ b/packages/slack/src/thread-image-utils.ts @@ -14,7 +14,6 @@ export const MAX_THREAD_ATTACHMENT_FILES = 20; export function isSlackImageFile(file: SlackFile): boolean { return ( isRoomoteImageAttachment({ - filename: file.name, mimeType: file.mimetype, }) && file.size < MAX_SLACK_IMAGE_FILE_SIZE_BYTES ); From cb5493b5834a82ac02c23c0f7bb8cf02fc1b6af9 Mon Sep 17 00:00:00 2001 From: "@daniel-lxs" <57051444+daniel-lxs@users.noreply.github.com> Date: Fri, 28 Aug 2026 04:13:02 +0000 Subject: [PATCH 2/3] fix: require opt-in for Fast task images --- .../__tests__/fast-agent-native-tool-bridge.test.ts | 5 +++++ .../server/fast-agent/__tests__/fast-agent-prompt.test.ts | 2 ++ .../fast-agent/__tests__/fast-agent-service.test.ts | 8 +++++++- .../server/fast-agent/fast-agent-native-tool-bridge.ts | 3 ++- .../src/server/fast-agent/fast-agent-prompt.ts | 1 + .../src/server/fast-agent/fast-agent-service.ts | 4 +++- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts index a9abb10b8..6411a0bcc 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts @@ -112,6 +112,11 @@ describe('Fast native OpenCode tool bridge', () => { expect(replySource).toContain('Launchable follow-ups'); expect(launchTaskSource).toContain('model: z.string().min(1)'); expect(launchTaskSource).toContain('deployment-enabled model ID'); + expect(launchTaskSource).toContain('includeImages: z.boolean().optional()'); + expect(launchTaskSource).toContain( + 'Current-turn images are attached only when includeImages is true', + ); + expect(launchTaskSource).toContain('defaults to false'); expect(launchTaskSource).toContain( 'Brief user-facing description of the work now underway', ); diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts index e9f074fef..76c3e1bed 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts @@ -127,6 +127,8 @@ describe('buildFastAgentSystemPrompt', () => { 'Call it immediately, before an acknowledgement or other user-visible response', ); expect(prompt).toContain('kickoffMessage'); + expect(prompt).toContain('"includeImages"'); + expect(prompt).toContain('images are not attached by default'); expect(prompt).toContain("describing the user's work now underway"); expect(prompt).toContain( 'The kickoff acknowledges the request, but it is not the only communication expected while longer work continues', diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts index effc03197..fc6b6f357 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts @@ -1836,6 +1836,7 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { prompt: 'Fix checkout.', environmentId: 'env-1', model: 'anthropic/claude-sonnet-5', + includeImages: true, kickoffMessage: 'I’m delegating the checkout fix.', }); expect(result).toEqual( @@ -1920,11 +1921,16 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { }, ); - await answerFastAgentQuestion({ ...baseParams, adapter }); + await answerFastAgentQuestion({ + ...baseParams, + images: ['data:image/png;base64,bm90LWZvcndhcmRlZA=='], + adapter, + }); expect(launchTask).toHaveBeenCalledWith( expect.objectContaining({ environmentId: ALL_REPOSITORIES }), ); + expect(launchTask.mock.calls[0]?.[0]).not.toHaveProperty('images'); }); it.each(['slack', 'discord', 'teams', 'telegram'] as const)( diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts index 4e87c67ec..4bcbc81f3 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts @@ -281,11 +281,12 @@ import { z } from "zod" import { invoke } from "../roomote-fast-tool-bridge.js" export default { - description: "Delegate new repository or workspace execution work to a Roomote task, optionally using an exact deployment-enabled model ID from the system prompt.", + description: "Delegate new repository or workspace execution work to a Roomote task, optionally using an exact deployment-enabled model ID. Current-turn images are attached only when includeImages is true.", args: { prompt: z.string().min(1).describe("Complete task instruction"), environmentId: z.string().nullable().optional().describe(${JSON.stringify(`Exact environment ID from the system prompt; omit, pass null, or pass "${ALL_REPOSITORIES}" to run against all active repositories`)}), model: z.string().min(1).nullable().optional().describe("Exact deployment-enabled model ID; omit or pass null to use the deployment default"), + includeImages: z.boolean().optional().describe("Set true to attach supported images from the active conversation turn; defaults to false"), kickoffMessage: z.string().min(1).describe("Brief user-facing description of the work now underway; do not mention delegation, launching, or queue state"), }, execute: (args, context) => invoke("launch_task", args, context), diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts index 72a0e9467..cfa12456c 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts @@ -175,6 +175,7 @@ ${formatIntegrationsForPrompt(availableIntegrations)} - An acknowledgement or progress update does not end the turn. Continue using native tools, then post a closeout or clarification. - Before calling a deployment MCP tool other than Roomote custom automation management, or canceling a task on a human-authored turn, first post a brief acknowledgement. The runtime rejects those calls until an acknowledgement or progress update has been delivered. Platform events are exempt. Sending a task message is also exempt so steering is not delayed behind a user-visible reply. - "launch_task" behaves like a normal tool. Do not send a separate acknowledgement before it. Include a brief "kickoffMessage" describing the user's work now underway; the runtime automatically posts that kickoff and task link as a progress artifact for each launch. The kickoff acknowledges the request, but it is not the only communication expected while longer work continues. +- Set "includeImages" on "launch_task" to true only when supported images from the active conversation turn are relevant to the coding task. Omit it otherwise; images are not attached by default. - If the answer is immediate, call the closeout tool directly. ${reactionGuidance} - Prefer one direct closeout over an acknowledgement followed immediately by the same answer. diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts index 7b8310cfb..9be1ab305 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts @@ -215,6 +215,7 @@ const launchTaskArgsSchema = z.object({ prompt: z.string().trim().min(1), environmentId: z.string().trim().min(1).nullable().optional(), model: z.string().trim().min(1).nullable().optional(), + includeImages: z.boolean().optional().default(false), kickoffMessage: z.string().trim().min(1), }); const taskMessageArgsSchema = z.object({ @@ -1601,6 +1602,7 @@ export async function answerFastAgentQuestion({ args.prompt, args.environmentId ?? null, args.model ?? null, + args.includeImages, ])}`; if (completedTaskActions.has(signature)) { return { @@ -1635,7 +1637,7 @@ export async function answerFastAgentQuestion({ throwIfTurnCancelled(); const result = await adapter.launchTask({ prompt: args.prompt, - ...(images.length > 0 ? { images } : {}), + ...(args.includeImages && images.length > 0 ? { images } : {}), environmentId: args.environmentId ?? null, model: args.model ?? null, parentSessionId: session.id, From 3d9716537230ada0458a974a41c2fca62e1d57d5 Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Fri, 28 Aug 2026 01:47:23 -0500 Subject: [PATCH 3/3] fix: forward Fast follow-up images to tasks --- .../fast-agent-native-tool-bridge.test.ts | 11 +++++ .../__tests__/fast-agent-prompt.test.ts | 3 ++ .../__tests__/fast-agent-service.test.ts | 48 ++++++++++++++++++- .../__tests__/fast-agent-tasks.test.ts | 10 +++- .../fast-agent-native-tool-bridge.ts | 3 +- .../server/fast-agent/fast-agent-prompt.ts | 2 +- .../server/fast-agent/fast-agent-service.ts | 7 ++- .../src/server/fast-agent/fast-agent-tasks.ts | 8 +++- 8 files changed, 85 insertions(+), 7 deletions(-) diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts index 6411a0bcc..40b466ca4 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-native-tool-bridge.test.ts @@ -80,6 +80,10 @@ describe('Fast native OpenCode tool bridge', () => { join(toolsDirectory, 'launch_task.js'), 'utf8', ); + const sendTaskMessageSource = await readFile( + join(toolsDirectory, 'send_task_message.js'), + 'utf8', + ); const showWidgetSource = await readFile( join(toolsDirectory, 'show_widget.js'), 'utf8', @@ -130,6 +134,13 @@ describe('Fast native OpenCode tool bridge', () => { expect(launchTaskSource).toContain( 'to run against all active repositories', ); + expect(sendTaskMessageSource).toContain( + 'includeImages: z.boolean().optional()', + ); + expect(sendTaskMessageSource).toContain( + 'Current-turn images are attached only when includeImages is true', + ); + expect(sendTaskMessageSource).toContain('defaults to false'); expect(showWidgetSource).toContain('invoke("show_widget"'); expect(showWidgetSource).toContain('textFallback: z.string().max(4000)'); expect(showWidgetSource).toContain( diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts index 76c3e1bed..eecc9bd85 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-prompt.test.ts @@ -129,6 +129,9 @@ describe('buildFastAgentSystemPrompt', () => { expect(prompt).toContain('kickoffMessage'); expect(prompt).toContain('"includeImages"'); expect(prompt).toContain('images are not attached by default'); + expect(prompt).toContain( + 'supported images from the active conversation turn are relevant to that instruction', + ); expect(prompt).toContain("describing the user's work now underway"); expect(prompt).toContain( 'The kickoff acknowledges the request, but it is not the only communication expected while longer work continues', diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts index fc6b6f357..f2306719a 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-service.test.ts @@ -2228,6 +2228,7 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { invokeTool(nativeToolNames.sendTaskMessage, { taskId: 'task-1', message: 'Include the failing test.', + includeImages: true, }), ).resolves.toEqual({ success: true }); await invokeTool(nativeToolNames.sendChatReply, { @@ -2243,18 +2244,63 @@ describe('answerFastAgentQuestion native OpenCode tools', () => { }), }); - await answerFastAgentQuestion({ ...baseParams, adapter }); + await answerFastAgentQuestion({ + ...baseParams, + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], + adapter, + }); expect(mocks.sendTaskMessage).toHaveBeenCalledWith( expect.objectContaining({ userId: 'user-1' }), { taskId: 'task-1', message: 'Include the failing test.', + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], }, ); expect(order).toEqual(['steer', 'reply']); }); + it('does not attach current-turn images to a task message without opt-in', async () => { + mocks.getActiveTasks.mockResolvedValue([ + { taskId: 'task-1', title: 'Checkout', status: 'running' }, + ]); + mocks.generateText.mockImplementation( + async (_params, _session, options) => { + await options.onSessionReady('opencode-session-1'); + await invokeTool(nativeToolNames.sendTaskMessage, { + taskId: 'task-1', + message: 'Include the failing test.', + }); + await invokeTool(nativeToolNames.sendChatReply, { + purpose: 'closeout', + message: 'The task was updated.', + }); + return ''; + }, + ); + + await answerFastAgentQuestion({ + ...baseParams, + images: ['data:image/png;base64,bm90LWZvcndhcmRlZA=='], + adapter: callbacks(), + }); + + expect(mocks.sendTaskMessage).toHaveBeenCalledWith( + expect.objectContaining({ userId: 'user-1' }), + { + taskId: 'task-1', + message: 'Include the failing test.', + }, + ); + }); + it('still requires an acknowledgement before canceling a task', async () => { mocks.getActiveTasks.mockResolvedValue([ { taskId: 'task-1', title: 'Checkout', status: 'running' }, diff --git a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-tasks.test.ts b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-tasks.test.ts index d0f8974b1..2b09adb3d 100644 --- a/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-tasks.test.ts +++ b/packages/cloud-agents/src/server/fast-agent/__tests__/fast-agent-tasks.test.ts @@ -5,7 +5,7 @@ describe('fast-agent task operations', () => { vi.unstubAllGlobals(); }); - it('steers messages to active tasks through a reverse-proxy pathname', async () => { + it('steers messages with images through a reverse-proxy pathname', async () => { const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ success: true }), { status: 200, @@ -23,6 +23,10 @@ describe('fast-agent task operations', () => { { taskId: 'task-42', message: 'Also add a test.', + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], }, ); @@ -36,6 +40,10 @@ describe('fast-agent task operations', () => { }), body: JSON.stringify({ message: 'Also add a test.', + images: [ + 'data:image/png;base64,c2NyZWVuc2hvdC0x', + 'data:image/webp;base64,c2NyZWVuc2hvdC0y', + ], senderMode: 'fast_agent', }), }), diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts index 4bcbc81f3..d0f85b63e 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-native-tool-bridge.ts @@ -298,10 +298,11 @@ import { z } from "zod" import { invoke } from "../roomote-fast-tool-bridge.js" export default { - description: "Send a new instruction to an active or resumable task delegated by this Fast conversation.", + description: "Send a new instruction to an active or resumable task delegated by this Fast conversation. Current-turn images are attached only when includeImages is true.", args: { taskId: z.string().nullable().optional(), message: z.string().min(1), + includeImages: z.boolean().optional().describe("Set true to attach supported images from the active conversation turn; defaults to false"), }, execute: (args, context) => invoke("send_task_message", args, context), } diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts index cfa12456c..8dea4e604 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-prompt.ts @@ -220,7 +220,7 @@ ${reactionGuidance} - Use "launch_task" for new independent repository or workspace work when external inspection, editing, execution, or validation is required, regardless of whether the message is phrased as a question, request, or declarative feedback. Existing active tasks do not block a new independent task. - You may launch multiple independent tasks in one turn. Each successful launch posts its own kickoff automatically, and the turn remains open for more tools. - Set "model" on "launch_task" only to an exact ID from Available Delegated Task Models when a specific model is useful or requested. Omit it to use the deployment default. Never invent or abbreviate model IDs. -- Use "send_task_message" when an active or resumable task is listed above and the user clearly gives that task a new instruction. Call it immediately, before an acknowledgement or other user-visible response, so the instruction reaches the task without an extra inference round. A resumable settled task continues under the same task identity. Set "taskId" when needed; with exactly one listed task, omit it or use null. Afterward, post a concise closeout confirming the outcome when useful. +- Use "send_task_message" when an active or resumable task is listed above and the user clearly gives that task a new instruction. Call it immediately, before an acknowledgement or other user-visible response, so the instruction reaches the task without an extra inference round. Set "includeImages" to true only when supported images from the active conversation turn are relevant to that instruction; omit it otherwise. A resumable settled task continues under the same task identity. Set "taskId" when needed; with exactly one listed task, omit it or use null. Afterward, post a concise closeout confirming the outcome when useful. - Use \`roomote_manage_tasks\` to inspect tasks in this deployment. Use "get_summary" for current status and failures, "get_messages" for transcript details, and "get_compute_logs" for runtime output when supported. Keep using "launch_task", "send_task_message", or "cancel_task" for task changes so Fast conversation kickoff and follow-up behavior is preserved. - Use \`roomote_get_chat_message_context\` or \`roomote_get_chat_channel_messages\` for additional chat context. Pass the target channel or message reference required by the native tool schema. Slack channel history defaults to the previous 24 hours when \`oldest\` is omitted. - Never send conversational acknowledgements to a task. "Okay", "cool", "thanks", status questions, and similar conversation are addressed to you. Use a user-visible chat tool. diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts index 9be1ab305..916ba3216 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-service.ts @@ -221,6 +221,7 @@ const launchTaskArgsSchema = z.object({ const taskMessageArgsSchema = z.object({ taskId: z.string().trim().min(1).nullable().optional(), message: z.string().trim().min(1), + includeImages: z.boolean().optional().default(false), }); const taskIdArgsSchema = z.object({ taskId: z.string().trim().min(1).nullable().optional(), @@ -1670,7 +1671,11 @@ export async function answerFastAgentQuestion({ throwIfTurnCancelled(); const result = await sendFastAgentTaskMessage( { userId, apiBaseUrl }, - { taskId: target.taskId, message: args.message }, + { + taskId: target.taskId, + message: args.message, + ...(args.includeImages && images.length > 0 ? { images } : {}), + }, ); return result; } diff --git a/packages/cloud-agents/src/server/fast-agent/fast-agent-tasks.ts b/packages/cloud-agents/src/server/fast-agent/fast-agent-tasks.ts index 99b7dd30f..e2151d8ba 100644 --- a/packages/cloud-agents/src/server/fast-agent/fast-agent-tasks.ts +++ b/packages/cloud-agents/src/server/fast-agent/fast-agent-tasks.ts @@ -188,13 +188,17 @@ async function callFastAgentTaskApi({ export async function sendFastAgentTaskMessage( context: FastAgentTaskApiContext, - params: { taskId: string; message: string }, + params: { taskId: string; message: string; images?: string[] }, ): Promise { return callFastAgentTaskApi({ ...context, method: 'POST', path: `${FAST_AGENT_TASKS_API_PATH}/${params.taskId}/steer_message`, - body: { message: params.message, senderMode: 'fast_agent' }, + body: { + message: params.message, + ...(params.images?.length ? { images: params.images } : {}), + senderMode: 'fast_agent', + }, }); }