diff --git a/CHANGELOG.md b/CHANGELOG.md index af857c42c..80b6b06f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- [Experimental] Review agent now acknowledges receipt of the `review` command by adding a reaction to the triggering comment. The reaction is configurable via `REVIEW_AGENT_ACK_REACTION` (default: `eyes`). [#1174](https://github.com/sourcebot-dev/sourcebot/pull/1174) + ## [4.17.1] - 2026-05-04 ### Added diff --git a/docs/docs/configuration/environment-variables.mdx b/docs/docs/configuration/environment-variables.mdx index 875a32dbe..53890ed70 100644 --- a/docs/docs/configuration/environment-variables.mdx +++ b/docs/docs/configuration/environment-variables.mdx @@ -70,6 +70,7 @@ The following environment variables allow you to configure your Sourcebot deploy | `GITHUB_REVIEW_AGENT_APP_PRIVATE_KEY_PATH` | `-` |

The container relative path to the private key file for the GitHub App used by the review agent.

| | `GITHUB_REVIEW_AGENT_APP_WEBHOOK_SECRET` | `-` |

The webhook secret for the GitHub App used by the review agent.

| | `OPENAI_API_KEY` | `-` |

The OpenAI API key used by the review agent.

| +| `REVIEW_AGENT_ACK_REACTION` | `eyes` |

The reaction/emoji added to a comment when the review agent acknowledges receipt of the review command.

| | `REVIEW_AGENT_API_KEY` | `-` |

The Sourcebot API key used by the review agent.

| | `REVIEW_AGENT_AUTO_REVIEW_ENABLED` | `false` |

Enables/disables automatic code reviews by the review agent.

| | `REVIEW_AGENT_LOGGING_ENABLED` | `true` |

Enables/disables logging for the review agent. Logs are saved in `DATA_CACHE_DIR/review-agent`

| @@ -77,4 +78,4 @@ The following environment variables allow you to configure your Sourcebot deploy ### Overriding environment variables from the config -You can override environment variables from the config file by using the `environmentOverrides` property. See [this doc](/docs/configuration/config-file#overriding-environment-variables-from-the-config) for more info. \ No newline at end of file +You can override environment variables from the config file by using the `environmentOverrides` property. See [this doc](/docs/configuration/config-file#overriding-environment-variables-from-the-config) for more info. diff --git a/docs/docs/features/agents/review-agent.mdx b/docs/docs/features/agents/review-agent.mdx index b4dc9c2f6..12971c2d6 100644 --- a/docs/docs/features/agents/review-agent.mdx +++ b/docs/docs/features/agents/review-agent.mdx @@ -141,7 +141,8 @@ You can also trigger a review manually by commenting `/review` on any PR or MR. | Variable | Default | Description | |---|---|---| +| `REVIEW_AGENT_ACK_REACTION` | `eyes` | Reaction/emoji added to acknowledge receipt of the review command | | `REVIEW_AGENT_AUTO_REVIEW_ENABLED` | `false` | Automatically review new and updated PRs/MRs | -| `REVIEW_AGENT_REVIEW_COMMAND` | `review` | Comment command that triggers a manual review (without the `/`) | -| `REVIEW_AGENT_MODEL` | first configured model | `displayName` of the language model to use for reviews | | `REVIEW_AGENT_LOGGING_ENABLED` | unset | Write prompt and response logs to disk for debugging | +| `REVIEW_AGENT_MODEL` | first configured model | `displayName` of the language model to use for reviews | +| `REVIEW_AGENT_REVIEW_COMMAND` | `review` | Comment command that triggers a manual review (without the `/`) | diff --git a/packages/shared/src/env.server.ts b/packages/shared/src/env.server.ts index 460afc29b..6e205f77e 100644 --- a/packages/shared/src/env.server.ts +++ b/packages/shared/src/env.server.ts @@ -235,10 +235,11 @@ const options = { GITLAB_REVIEW_AGENT_TOKEN: z.string().optional(), GITLAB_REVIEW_AGENT_HOST: z.string().default('gitlab.com').transform(s => s.replace(/^https?:\/\//, '').replace(/\/+$/, '')).refine(s => /^[a-z0-9.-]+$/i.test(s), { message: 'invalid hostname' }), // Review agent config - REVIEW_AGENT_MODEL: z.string().optional(), + REVIEW_AGENT_ACK_REACTION: z.string().default('eyes'), REVIEW_AGENT_API_KEY: z.string().optional(), - REVIEW_AGENT_LOGGING_ENABLED: booleanSchema.default('true'), REVIEW_AGENT_AUTO_REVIEW_ENABLED: booleanSchema.default('false'), + REVIEW_AGENT_LOGGING_ENABLED: booleanSchema.default('true'), + REVIEW_AGENT_MODEL: z.string().optional(), REVIEW_AGENT_REVIEW_COMMAND: z.string().default('review'), ANTHROPIC_API_KEY: z.string().optional(), diff --git a/packages/web/src/app/api/(server)/webhook/route.ts b/packages/web/src/app/api/(server)/webhook/route.ts index e7e380b49..b006efe77 100644 --- a/packages/web/src/app/api/(server)/webhook/route.ts +++ b/packages/web/src/app/api/(server)/webhook/route.ts @@ -130,6 +130,16 @@ if (env.GITLAB_REVIEW_AGENT_TOKEN) { } } +const ACK_TIMEOUT_MS = 1500; + +const ackWithTimeout = (promise: Promise, label: string): Promise => { + const timeout = new Promise(resolve => setTimeout(resolve, ACK_TIMEOUT_MS)); + return Promise.race([promise, timeout]).then( + () => { /* success or timeout — proceed */ }, + (error) => { logger.warn(`${label}: ${error}`); }, + ); +}; + export const POST = async (request: NextRequest) => { const body = await request.json(); const headers = Object.fromEntries(Array.from(request.headers.entries(), ([key, value]) => [key.toLowerCase(), value])); @@ -185,6 +195,17 @@ export const POST = async (request: NextRequest) => { const owner = body.repository.owner.login; const octokit = await githubApp.getInstallationOctokit(body.installation.id); + + await ackWithTimeout( + octokit.rest.reactions.createForIssueComment({ + owner, + repo: repositoryName, + comment_id: body.comment.id, + content: env.REVIEW_AGENT_ACK_REACTION as "-1" | "+1" | "laugh" | "confused" | "heart" | "hooray" | "rocket" | "eyes", + }), + 'Failed to add acknowledgment reaction to GitHub comment', + ); + const { data: pullRequest } = await octokit.rest.pulls.get({ owner, repo: repositoryName, @@ -246,6 +267,16 @@ export const POST = async (request: NextRequest) => { if (noteBody === `/${env.REVIEW_AGENT_REVIEW_COMMAND}`) { logger.info('Review agent review command received on GitLab MR, processing'); + await ackWithTimeout( + gitlabClient.MergeRequestNoteAwardEmojis.award( + parsed.data.project.id, + parsed.data.merge_request.iid, + parsed.data.object_attributes.id, + env.REVIEW_AGENT_ACK_REACTION, + ), + 'Failed to add acknowledgment emoji to GitLab note', + ); + const mrPayload: GitLabMergeRequestPayload = { object_kind: "merge_request", object_attributes: { diff --git a/packages/web/src/features/agents/review-agent/types.ts b/packages/web/src/features/agents/review-agent/types.ts index 1a9aba1eb..cb4ce5771 100644 --- a/packages/web/src/features/agents/review-agent/types.ts +++ b/packages/web/src/features/agents/review-agent/types.ts @@ -87,6 +87,7 @@ export type GitLabMergeRequestPayload = z.infer