From abc5c1f1978fb5eeac047de7a08e47d7f91e6f96 Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 28 May 2026 15:27:53 +0300 Subject: [PATCH 1/2] fix(bot): open separate prs from pr comments --- apps/web/src/lib/bot/agent-runner.ts | 2 + .../src/lib/bot/pr-creation-strategy.test.ts | 85 +++++++++++++++++++ apps/web/src/lib/bot/pr-creation-strategy.ts | 62 ++++++++++++++ apps/web/src/lib/bot/run.ts | 2 + .../tools/spawn-cloud-agent-session.test.ts | 41 ++++++++- .../bot/tools/spawn-cloud-agent-session.ts | 11 +++ 6 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/lib/bot/pr-creation-strategy.test.ts create mode 100644 apps/web/src/lib/bot/pr-creation-strategy.ts diff --git a/apps/web/src/lib/bot/agent-runner.ts b/apps/web/src/lib/bot/agent-runner.ts index 1562b2e51b..4b9a2f0a1c 100644 --- a/apps/web/src/lib/bot/agent-runner.ts +++ b/apps/web/src/lib/bot/agent-runner.ts @@ -59,6 +59,7 @@ type RunBotAgentParams = { prompt: string; /** Pre-uploaded supported attachments from the user's message (already in R2). */ attachments?: CloudAgentAttachments; + useSeparatePullRequestStrategy?: boolean; completedStepCount?: number; initialSteps?: BotRequestStep[]; onSessionReady?: (params: { @@ -308,6 +309,7 @@ This tool returns an acknowledgement immediately. The final Cloud Agent result w chatPlatform, currentStep, attachments: params.attachments, + useSeparatePullRequestStrategy: params.useSeparatePullRequestStrategy, } ); diff --git a/apps/web/src/lib/bot/pr-creation-strategy.test.ts b/apps/web/src/lib/bot/pr-creation-strategy.test.ts new file mode 100644 index 0000000000..a07f61712d --- /dev/null +++ b/apps/web/src/lib/bot/pr-creation-strategy.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from '@jest/globals'; +import type { Message, Thread } from 'chat'; +import { shouldUseSeparatePullRequestStrategy } from './pr-creation-strategy'; + +function createThread(id: string, adapterName = 'github'): Thread { + return { id, adapter: { name: adapterName } } as Thread; +} + +function createMessage(text: string, raw: unknown = {}): Message { + return { id: 'comment-1', text, raw } as Message; +} + +describe('shouldUseSeparatePullRequestStrategy', () => { + it('detects an explicit new PR request in a GitHub PR review thread', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), + message: createMessage('@kilocode open a new PR with an updated REVIEW.md per this thread'), + }) + ).toBe(true); + }); + + it('detects an explicit new pull request request in a GitHub PR issue comment', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:issue:3427'), + message: createMessage('@kilocode create a new pull request for this', { + issue: { pull_request: {} }, + }), + }) + ).toBe(true); + }); + + it('detects an explicit separate PR request in a GitHub PR thread', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:3427'), + message: createMessage('@kilocode use a separate PR for this change'), + }) + ).toBe(true); + }); + + it('does not trigger for a normal Kilo mention in a GitHub PR thread', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), + message: createMessage('@kilocode update REVIEW.md per this thread'), + }) + ).toBe(false); + }); + + it('respects negated new PR requests', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), + message: createMessage('@kilocode don\u2019t open a new PR; update this PR'), + }) + ).toBe(false); + + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), + message: createMessage('@kilocode no new PR, just summarize this thread'), + }) + ).toBe(false); + }); + + it('does not trigger for GitHub issue-only context', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('github:Kilo-Org/cloud:issue:37'), + message: createMessage('@kilocode open a new PR for this issue'), + }) + ).toBe(false); + }); + + it('does not trigger outside GitHub', () => { + expect( + shouldUseSeparatePullRequestStrategy({ + thread: createThread('slack:C123:1700000000.000000', 'slack'), + message: createMessage('@kilocode open a new PR with this change'), + }) + ).toBe(false); + }); +}); diff --git a/apps/web/src/lib/bot/pr-creation-strategy.ts b/apps/web/src/lib/bot/pr-creation-strategy.ts new file mode 100644 index 0000000000..296fbfa8de --- /dev/null +++ b/apps/web/src/lib/bot/pr-creation-strategy.ts @@ -0,0 +1,62 @@ +import type { Message, Thread } from 'chat'; + +const EXPLICIT_NEW_PULL_REQUEST_PATTERNS = [ + /\b(?:open|create|start|make)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, + /\b(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, +]; + +const NEGATED_NEW_PULL_REQUEST_PATTERNS = [ + /\b(?:no|without)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, + /\b(?:do not|don't|dont|never)\s+(?:open|create|start|make)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, +]; + +function normalizeRequestText(text: string): string { + return text + .toLowerCase() + .replace(/\u2019/g, "'") + .replace(/\s+/g, ' ') + .trim(); +} + +function hasObjectProperty(value: unknown, key: string): value is object { + return typeof value === 'object' && value !== null && key in value; +} + +function getObjectProperty(value: unknown, key: string): unknown { + if (!hasObjectProperty(value, key)) return undefined; + return Reflect.get(value, key); +} + +function isGitHubPullRequestThread(thread: Thread): boolean { + return /^github:[^/]+\/[^:]+:\d+(?::rc:\d+)?$/.test(thread.id); +} + +function isGitHubPullRequestMessage(message: Message): boolean { + const raw = message.raw; + if (hasObjectProperty(raw, 'pull_request')) return true; + + const issue = getObjectProperty(raw, 'issue'); + return hasObjectProperty(issue, 'pull_request'); +} + +function requestsSeparatePullRequest(text: string): boolean { + const normalizedText = normalizeRequestText(text); + if (NEGATED_NEW_PULL_REQUEST_PATTERNS.some(pattern => pattern.test(normalizedText))) { + return false; + } + + return EXPLICIT_NEW_PULL_REQUEST_PATTERNS.some(pattern => pattern.test(normalizedText)); +} + +export function shouldUseSeparatePullRequestStrategy({ + thread, + message, +}: { + thread: Thread; + message: Message; +}): boolean { + if (thread.adapter.name !== 'github') return false; + if (!requestsSeparatePullRequest(message.text)) return false; + + return isGitHubPullRequestThread(thread) || isGitHubPullRequestMessage(message); +} diff --git a/apps/web/src/lib/bot/run.ts b/apps/web/src/lib/bot/run.ts index 50c59440e7..3f401c93c8 100644 --- a/apps/web/src/lib/bot/run.ts +++ b/apps/web/src/lib/bot/run.ts @@ -1,6 +1,7 @@ import { createBotRequest, updateBotRequest } from '@/lib/bot/request-logging'; import { runBotAgent } from '@/lib/bot/agent-runner'; import { extractAndUploadAttachments } from '@/lib/bot/attachments'; +import { shouldUseSeparatePullRequestStrategy } from '@/lib/bot/pr-creation-strategy'; import type { PlatformIntegration, User } from '@kilocode/db'; import type { Message, Thread } from 'chat'; import { captureException } from '@sentry/nextjs'; @@ -91,6 +92,7 @@ async function processMessage({ botRequestId, prompt: message.text, attachments, + useSeparatePullRequestStrategy: shouldUseSeparatePullRequestStrategy({ thread, message }), }); updateBotRequest(botRequestId, { diff --git a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts index 8d20abb374..3897981f0b 100644 --- a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts +++ b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts @@ -68,7 +68,7 @@ let mockGetGitLabInstanceUrlForUser: jest.MockedFunction; let mockResolveBotSessionProfile: jest.MockedFunction; -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'); @@ -136,4 +136,43 @@ 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', prompt: 'Update REVIEW.md', mode: 'code' }, + 'model', + platformIntegration, + 'auth-token', + 'ticket-user', + 'request-3', + undefined, + { useSeparatePullRequestStrategy: true } + ); + + 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('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-4' + ); + + const prepareInput = mockPrepareSession.mock.calls[0]?.[0]; + expect(prepareInput).toEqual(expect.objectContaining({ prompt: 'Update REVIEW.md' })); + }); }); diff --git a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts index d178c92011..5d9fbe3852 100644 --- a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts +++ b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts @@ -31,6 +31,12 @@ 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.`; + /** * Derive a per-request callback token so the dedicated callback HMAC secret * is never stored in session metadata (which is visible via getSession). @@ -103,6 +109,7 @@ export default async function spawnCloudAgentSession( chatPlatform?: string; currentStep?: number; attachments?: CloudAgentAttachments; + useSeparatePullRequestStrategy?: boolean; } ): Promise { console.log('[KiloBot] spawnCloudAgentSession called with args:', JSON.stringify(args, null, 2)); @@ -133,6 +140,10 @@ export default async function spawnCloudAgentSession( let prompt = args.prompt; + if (options?.useSeparatePullRequestStrategy && args.githubRepo) { + prompt += SEPARATE_PULL_REQUEST_STRATEGY_INSTRUCTION; + } + // Append PR/MR signature to the prompt if available if (options?.prSignature) { prompt += options.prSignature; From bb630880a95c9d67ad43f90ab43f11bbd65ee49c Mon Sep 17 00:00:00 2001 From: Alex Alecu Date: Thu, 28 May 2026 17:26:11 +0300 Subject: [PATCH 2/2] fix(bot): let LLM choose PR strategy --- apps/web/src/lib/bot/agent-runner.ts | 4 +- .../src/lib/bot/pr-creation-strategy.test.ts | 85 ------------------- apps/web/src/lib/bot/pr-creation-strategy.ts | 62 -------------- apps/web/src/lib/bot/run.ts | 2 - .../tools/spawn-cloud-agent-session.test.ts | 41 +++++++-- .../bot/tools/spawn-cloud-agent-session.ts | 19 ++++- 6 files changed, 54 insertions(+), 159 deletions(-) delete mode 100644 apps/web/src/lib/bot/pr-creation-strategy.test.ts delete mode 100644 apps/web/src/lib/bot/pr-creation-strategy.ts diff --git a/apps/web/src/lib/bot/agent-runner.ts b/apps/web/src/lib/bot/agent-runner.ts index 4b9a2f0a1c..b4727ce42d 100644 --- a/apps/web/src/lib/bot/agent-runner.ts +++ b/apps/web/src/lib/bot/agent-runner.ts @@ -59,7 +59,6 @@ type RunBotAgentParams = { prompt: string; /** Pre-uploaded supported attachments from the user's message (already in R2). */ attachments?: CloudAgentAttachments; - useSeparatePullRequestStrategy?: boolean; completedStepCount?: number; initialSteps?: BotRequestStep[]; onSessionReady?: (params: { @@ -269,6 +268,8 @@ export async function runBotAgent(params: RunBotAgentParams): Promise { @@ -309,7 +310,6 @@ This tool returns an acknowledgement immediately. The final Cloud Agent result w chatPlatform, currentStep, attachments: params.attachments, - useSeparatePullRequestStrategy: params.useSeparatePullRequestStrategy, } ); diff --git a/apps/web/src/lib/bot/pr-creation-strategy.test.ts b/apps/web/src/lib/bot/pr-creation-strategy.test.ts deleted file mode 100644 index a07f61712d..0000000000 --- a/apps/web/src/lib/bot/pr-creation-strategy.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import type { Message, Thread } from 'chat'; -import { shouldUseSeparatePullRequestStrategy } from './pr-creation-strategy'; - -function createThread(id: string, adapterName = 'github'): Thread { - return { id, adapter: { name: adapterName } } as Thread; -} - -function createMessage(text: string, raw: unknown = {}): Message { - return { id: 'comment-1', text, raw } as Message; -} - -describe('shouldUseSeparatePullRequestStrategy', () => { - it('detects an explicit new PR request in a GitHub PR review thread', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), - message: createMessage('@kilocode open a new PR with an updated REVIEW.md per this thread'), - }) - ).toBe(true); - }); - - it('detects an explicit new pull request request in a GitHub PR issue comment', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:issue:3427'), - message: createMessage('@kilocode create a new pull request for this', { - issue: { pull_request: {} }, - }), - }) - ).toBe(true); - }); - - it('detects an explicit separate PR request in a GitHub PR thread', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:3427'), - message: createMessage('@kilocode use a separate PR for this change'), - }) - ).toBe(true); - }); - - it('does not trigger for a normal Kilo mention in a GitHub PR thread', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), - message: createMessage('@kilocode update REVIEW.md per this thread'), - }) - ).toBe(false); - }); - - it('respects negated new PR requests', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), - message: createMessage('@kilocode don\u2019t open a new PR; update this PR'), - }) - ).toBe(false); - - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:3427:rc:123'), - message: createMessage('@kilocode no new PR, just summarize this thread'), - }) - ).toBe(false); - }); - - it('does not trigger for GitHub issue-only context', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('github:Kilo-Org/cloud:issue:37'), - message: createMessage('@kilocode open a new PR for this issue'), - }) - ).toBe(false); - }); - - it('does not trigger outside GitHub', () => { - expect( - shouldUseSeparatePullRequestStrategy({ - thread: createThread('slack:C123:1700000000.000000', 'slack'), - message: createMessage('@kilocode open a new PR with this change'), - }) - ).toBe(false); - }); -}); diff --git a/apps/web/src/lib/bot/pr-creation-strategy.ts b/apps/web/src/lib/bot/pr-creation-strategy.ts deleted file mode 100644 index 296fbfa8de..0000000000 --- a/apps/web/src/lib/bot/pr-creation-strategy.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Message, Thread } from 'chat'; - -const EXPLICIT_NEW_PULL_REQUEST_PATTERNS = [ - /\b(?:open|create|start|make)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, - /\b(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, -]; - -const NEGATED_NEW_PULL_REQUEST_PATTERNS = [ - /\b(?:no|without)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, - /\b(?:do not|don't|dont|never)\s+(?:open|create|start|make)\s+(?:a\s+)?(?:new|fresh|separate)\s+(?:github\s+)?(?:pr|pull request)\b/, -]; - -function normalizeRequestText(text: string): string { - return text - .toLowerCase() - .replace(/\u2019/g, "'") - .replace(/\s+/g, ' ') - .trim(); -} - -function hasObjectProperty(value: unknown, key: string): value is object { - return typeof value === 'object' && value !== null && key in value; -} - -function getObjectProperty(value: unknown, key: string): unknown { - if (!hasObjectProperty(value, key)) return undefined; - return Reflect.get(value, key); -} - -function isGitHubPullRequestThread(thread: Thread): boolean { - return /^github:[^/]+\/[^:]+:\d+(?::rc:\d+)?$/.test(thread.id); -} - -function isGitHubPullRequestMessage(message: Message): boolean { - const raw = message.raw; - if (hasObjectProperty(raw, 'pull_request')) return true; - - const issue = getObjectProperty(raw, 'issue'); - return hasObjectProperty(issue, 'pull_request'); -} - -function requestsSeparatePullRequest(text: string): boolean { - const normalizedText = normalizeRequestText(text); - if (NEGATED_NEW_PULL_REQUEST_PATTERNS.some(pattern => pattern.test(normalizedText))) { - return false; - } - - return EXPLICIT_NEW_PULL_REQUEST_PATTERNS.some(pattern => pattern.test(normalizedText)); -} - -export function shouldUseSeparatePullRequestStrategy({ - thread, - message, -}: { - thread: Thread; - message: Message; -}): boolean { - if (thread.adapter.name !== 'github') return false; - if (!requestsSeparatePullRequest(message.text)) return false; - - return isGitHubPullRequestThread(thread) || isGitHubPullRequestMessage(message); -} diff --git a/apps/web/src/lib/bot/run.ts b/apps/web/src/lib/bot/run.ts index 3f401c93c8..50c59440e7 100644 --- a/apps/web/src/lib/bot/run.ts +++ b/apps/web/src/lib/bot/run.ts @@ -1,7 +1,6 @@ import { createBotRequest, updateBotRequest } from '@/lib/bot/request-logging'; import { runBotAgent } from '@/lib/bot/agent-runner'; import { extractAndUploadAttachments } from '@/lib/bot/attachments'; -import { shouldUseSeparatePullRequestStrategy } from '@/lib/bot/pr-creation-strategy'; import type { PlatformIntegration, User } from '@kilocode/db'; import type { Message, Thread } from 'chat'; import { captureException } from '@sentry/nextjs'; @@ -92,7 +91,6 @@ async function processMessage({ botRequestId, prompt: message.text, attachments, - useSeparatePullRequestStrategy: shouldUseSeparatePullRequestStrategy({ thread, message }), }); updateBotRequest(botRequestId, { diff --git a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts index 3897981f0b..5fa20fa55d 100644 --- a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts +++ b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.test.ts @@ -139,14 +139,17 @@ describe('spawnCloudAgentSession', () => { it('adds separate PR strategy guidance when preparing a requested GitHub session', async () => { await spawnCloudAgentSession( - { githubRepo: 'owner/repo', prompt: 'Update REVIEW.md', mode: 'code' }, + { + githubRepo: 'owner/repo', + githubPullRequestStrategy: 'separate_pull_request', + prompt: 'Update REVIEW.md', + mode: 'code', + }, 'model', platformIntegration, 'auth-token', 'ticket-user', - 'request-3', - undefined, - { useSeparatePullRequestStrategy: true } + 'request-3' ); const prepareInput = mockPrepareSession.mock.calls[0]?.[0]; @@ -162,6 +165,34 @@ describe('spawnCloudAgentSession', () => { ); }); + 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' }, @@ -169,7 +200,7 @@ describe('spawnCloudAgentSession', () => { platformIntegration, 'auth-token', 'ticket-user', - 'request-4' + 'request-5' ); const prepareInput = mockPrepareSession.mock.calls[0]?.[0]; diff --git a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts index 5d9fbe3852..092ed26883 100644 --- a/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts +++ b/apps/web/src/lib/bot/tools/spawn-cloud-agent-session.ts @@ -37,6 +37,12 @@ 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). @@ -70,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_.]+)+$/) @@ -80,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']) @@ -109,7 +121,6 @@ export default async function spawnCloudAgentSession( chatPlatform?: string; currentStep?: number; attachments?: CloudAgentAttachments; - useSeparatePullRequestStrategy?: boolean; } ): Promise { console.log('[KiloBot] spawnCloudAgentSession called with args:', JSON.stringify(args, null, 2)); @@ -140,8 +151,10 @@ export default async function spawnCloudAgentSession( let prompt = args.prompt; - if (options?.useSeparatePullRequestStrategy && args.githubRepo) { + 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