Skip to content
Closed
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
2 changes: 2 additions & 0 deletions apps/web/src/lib/bot/agent-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ export async function runBotAgent(params: RunBotAgentParams): Promise<BotAgentCo

If the user attached images or files (PDF, Markdown, text, CSV) to their message, those attachments are automatically forwarded to the Cloud Agent session — you do not need to describe or re-upload them. Reference them in the prompt if relevant (e.g. "implement the design shown in the attached screenshot" or "use the requirements in the attached document").

For GitHub PR or review-thread work, Cloud Agent only sees the prompt you pass to this tool. Include the PR URL or number, relevant review-thread/comment details, and whether Cloud Agent should update the current PR branch or create a separate new PR.

This tool returns an acknowledgement immediately. The final Cloud Agent result will be posted later in the same thread after the async session completes.`,
inputSchema: spawnCloudAgentInputSchema,
execute: async args => {
Expand Down
72 changes: 71 additions & 1 deletion apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ let mockGetGitLabInstanceUrlForUser: jest.MockedFunction<typeof GetGitLabInstanc
let mockBuildGitLabCloneUrl: jest.MockedFunction<typeof BuildGitLabCloneUrl>;
let mockResolveBotSessionProfile: jest.MockedFunction<typeof ResolveBotSessionProfile>;

describe('spawnCloudAgentSession attachment forwarding', () => {
describe('spawnCloudAgentSession', () => {
beforeAll(async () => {
const client = await import('@/lib/cloud-agent-next/cloud-agent-client');
const github = await import('@/lib/cloud-agent/github-integration-helpers');
Expand Down Expand Up @@ -136,4 +136,74 @@ describe('spawnCloudAgentSession attachment forwarding', () => {
expect(prepareInput).toEqual(expect.objectContaining({ attachments }));
expect(prepareInput).not.toHaveProperty('images');
});

it('adds separate PR strategy guidance when preparing a requested GitHub session', async () => {
await spawnCloudAgentSession(
{
githubRepo: 'owner/repo',
githubPullRequestStrategy: 'separate_pull_request',
prompt: 'Update REVIEW.md',
mode: 'code',
},
'model',
platformIntegration,
'auth-token',
'ticket-user',
'request-3'
);

const prepareInput = mockPrepareSession.mock.calls[0]?.[0];
expect(prepareInput).toEqual(
expect.objectContaining({
prompt: expect.stringContaining('create a fresh branch'),
})
);
expect(prepareInput).toEqual(
expect.objectContaining({
prompt: expect.stringContaining("must not push to or modify the current PR's head branch"),
})
);
});

it('adds current PR branch guidance when preparing a current-branch GitHub session', async () => {
await spawnCloudAgentSession(
{
githubRepo: 'owner/repo',
githubPullRequestStrategy: 'current_pr_branch',
prompt: 'Update REVIEW.md',
mode: 'code',
},
'model',
platformIntegration,
'auth-token',
'ticket-user',
'request-4'
);

const prepareInput = mockPrepareSession.mock.calls[0]?.[0];
expect(prepareInput).toEqual(
expect.objectContaining({
prompt: expect.stringContaining('Work on the current PR head branch'),
})
);
expect(prepareInput).toEqual(
expect.objectContaining({
prompt: expect.not.stringContaining('create a fresh branch'),
})
);
});

it('leaves the prompt unchanged when separate PR strategy is not requested', async () => {
await spawnCloudAgentSession(
{ githubRepo: 'owner/repo', prompt: 'Update REVIEW.md', mode: 'code' },
'model',
platformIntegration,
'auth-token',
'ticket-user',
'request-5'
);

const prepareInput = mockPrepareSession.mock.calls[0]?.[0];
expect(prepareInput).toEqual(expect.objectContaining({ prompt: 'Update REVIEW.md' }));
});
});
26 changes: 25 additions & 1 deletion apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ import { captureException } from '@sentry/nextjs';
import type { PlatformIntegration } from '@kilocode/db';
import z from 'zod';

const SEPARATE_PULL_REQUEST_STRATEGY_INSTRUCTION = `

---
**Required GitHub PR strategy:**
The user explicitly requested a separate new PR. You must create a fresh branch from the original PR's base branch, open a separate new GitHub PR targeting that same base branch, and must not push to or modify the current PR's head branch.`;

const CURRENT_PULL_REQUEST_BRANCH_STRATEGY_INSTRUCTION = `

---
**Required GitHub PR strategy:**
Work on the current PR head branch for this task and do not open a separate GitHub PR.`;

/**
* Derive a per-request callback token so the dedicated callback HMAC secret
* is never stored in session metadata (which is visible via getSession).
Expand Down Expand Up @@ -64,6 +76,12 @@ export const spawnCloudAgentInputSchema = z.object({
.regex(/^[-a-zA-Z0-9_.]+\/[-a-zA-Z0-9_.]+$/)
.describe('The GitHub repository in owner/repo format (e.g., "facebook/react")')
.optional(),
githubPullRequestStrategy: z
.enum(['current_pr_branch', 'separate_pull_request'])
.describe(
'For GitHub PR or review-thread work only. Select "separate_pull_request" when the user asks for a new, fresh, separate, or different PR. Select "current_pr_branch" when the user wants the current PR updated or does not ask for a separate PR.'
)
.optional(),
gitlabProject: z
.string()
.regex(/^[-a-zA-Z0-9_.]+(?:\/[-a-zA-Z0-9_.]+)+$/)
Expand All @@ -74,7 +92,7 @@ export const spawnCloudAgentInputSchema = z.object({
prompt: z
.string()
.describe(
'The task description for the Cloud Agent. Be specific about what changes or analysis you want.'
'The task description for the Cloud Agent. Be specific about what changes or analysis you want. Cloud Agent does not see bot-only GitHub PR/review-thread context unless you include it here. For GitHub PR or review-thread work, include the PR URL or number, relevant review-thread/comment details, and the chosen current-PR or separate-PR strategy.'
),
mode: z
.enum(['code', 'ask'])
Expand Down Expand Up @@ -133,6 +151,12 @@ export default async function spawnCloudAgentSession(

let prompt = args.prompt;

if (args.githubPullRequestStrategy === 'separate_pull_request' && args.githubRepo) {
prompt += SEPARATE_PULL_REQUEST_STRATEGY_INSTRUCTION;
} else if (args.githubPullRequestStrategy === 'current_pr_branch' && args.githubRepo) {
prompt += CURRENT_PULL_REQUEST_BRANCH_STRATEGY_INSTRUCTION;
}

// Append PR/MR signature to the prompt if available
if (options?.prSignature) {
prompt += options.prSignature;
Expand Down