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
34 changes: 34 additions & 0 deletions .github/workflows/publish-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,37 @@ jobs:
-f "client_payload[image_tag]=${IMAGE_TAG}" \
-f "client_payload[sha]=${GITHUB_SHA}" \
-f "client_payload[ref]=${GITHUB_REF}"

# Production Cloud rolls its tenant fleet from immutable main builds the
# same way staging follows develop builds. The Cloud-side workflow
# validates the tag against the commit before touching anything.
notify-production-cloud:
name: Notify production Cloud
runs-on: ubuntu-latest
needs:
- prepare
- publish
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && vars.ROOMOTE_CLOUD_REPO != '' }}
permissions:
contents: read
steps:
- name: Send production repository dispatch
env:
GH_TOKEN: ${{ secrets.ROOMOTE_CLOUD_DISPATCH_TOKEN }}
CLOUD_REPO: ${{ vars.ROOMOTE_CLOUD_REPO }}
IMAGE_TAG: ${{ needs.prepare.outputs.version }}
run: |
set -euo pipefail
: "${GH_TOKEN:?ROOMOTE_CLOUD_DISPATCH_TOKEN is required}"
case "$CLOUD_REPO" in
RooCodeInc/Roomote-Cloud) ;;
*)
echo "Refusing unexpected ROOMOTE_CLOUD_REPO: $CLOUD_REPO" >&2
exit 1
;;
esac
gh api "repos/${CLOUD_REPO}/dispatches" \
-f event_type=roomote-main-build \
-f "client_payload[image_tag]=${IMAGE_TAG}" \
-f "client_payload[sha]=${GITHUB_SHA}" \
-f "client_payload[ref]=${GITHUB_REF}"
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

This file tracks product releases for Roomote (single monorepo version). Automated release entries are prepended by `pnpm run version`.

## 0.19.0 (2026-07-23)

This release makes pull-request review feedback clearer and safer to handle across chat, with more reliable task resumption.

### Highlights

- Resolve the feedback in a notification or have Roomote handle all future feedback on a pull request.
- Replying in a review-feedback thread retires its pending buttons so stale actions cannot conflict with the conversation.
- Resumed tasks retain their original source-control provider through older resume chains.

### Minor changes

- Make pull-request review feedback easier to handle by retiring stale offers after thread replies and providing clear actions to resolve selected or all issues.

### Patch changes

- Keep snapshot-resumed tasks connected to their original source-control provider when they resume through older task chains.

## 0.18.0 (2026-07-23)

This release makes pull-request feedback easier to act on across chat, extends Gitea automations, and improves task routing and reliability.
Expand Down
67 changes: 28 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repo into an isolated sandbox, writes the code, runs the tests, takes a
screenshot, and opens a PR. You review the diff like you would from any teammate.

No IDE plugin. No terminal session. No babysitting. It works while you do
something else.
something else, all the way.

```
"Fix the 500 on /api/billing for annual plans"
Expand All @@ -19,14 +19,17 @@ something else.
→ you review, merge, done
```

Source-available. Self-hostable. Use your ChatGPT subscription or bring your own
Source-available. Self-hostable or our Cloud. Use your ChatGPT subscription or bring your own
API keys.

![Roomote chat workflow demo](assets/roomote-hero.gif)

[![Deploy on Roomote Cloud](https://roomote.dev/images/deploy-button.png)](https://cloud.roomote.dev/sign-up)
  
[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Rj2cFo?referralCode=roomote)
  
[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/RooCodeInc/Roomote)

![Roomote chat workflow demo](assets/roomote-hero.gif)

---

Expand Down Expand Up @@ -93,52 +96,40 @@ cleans up after itself.

Pick one path. You will have a working Roomote instance at the end.

### Option A: One-click deploy (Railway)

New to Railway? [Sign up with our referral link](https://railway.com?referralCode=roomote)
to get $20 in credit.
### Roomote Cloud (fastest, ~2 min)

1. Click the button:
Don't want to run infrastructure? We'll host your deployment: free 7-day trial, no credit card. Same single-tenant product — your own isolated instance, connect your ChatGPT subscription or bring an API key. Move to self-hosting anytime. [Start free →](https://cloud.roomote.dev/sign-up)

[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/Rj2cFo?referralCode=roomote)
### One-click deploy (Railway)

2. Railway provisions Postgres, Redis, and the app. Follow the prompts to set
your admin email and password.
New to Railway? [Sign up with our referral link](https://railway.com?referralCode=roomote)
to get $20 in credit.

3. Open the setup link Railway gives you. Connect models. Two options:
- **ChatGPT subscription:** Sign in with your ChatGPT account. If you
already pay for Plus or Pro, you're done. No API key needed.
- **API key:** Paste a key from OpenRouter, Anthropic, OpenAI, or any other
supported provider.
1. [Use the template](https://railway.com/deploy/Rj2cFo?referralCode=roomote)

4. Connect source control: GitHub, GitLab, Gitea, Azure DevOps, or Bitbucket
Cloud.
2. Railway provisions Postgres, Redis, and the app.

5. Open Slack, send Roomote a message:
3. Open the setup link Railway gives you. Configure your communication, source control and inference providers.

```
What files are in the root of the repo?
```
4. Connect your sandbox provider.

**Expected output:** Roomote lists your repo's top-level files and asks what
you'd like to work on.
5. Open Slack/Telegram/Discord/Teams, send Roomote a message and you're off.

You now have a working cloud coding agent. Total time: ~3 minutes.
You now have a working cloud coding agent. Total time: ~6 minutes.

### Option B: One-click deploy (Render)
### One-click deploy (Render)

1. Click the button:
1. [Use the template](https://render.com/deploy?repo=https://github.com/RooCodeInc/Roomote)

[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/RooCodeInc/Roomote)
2. Render provisions Postgres, Redis, and the app.

2. Render creates managed Postgres, Redis, and the app. Set your admin
credentials when prompted.
3. Open the setup link Render gives you. Configure your communication, source control and inference providers.

3. Open the setup link, connect a model provider and source control.
4. Connect your sandbox provider.

4. Send Roomote a test message in Slack. Same expected output as above.
5. Open Slack/Telegram/Discord/Teams, send Roomote a message and you're off.

### Option C: Self-host on your own server
### Self-host on your own server

SSH into a fresh Ubuntu/Debian machine (4 GB RAM recommended) and run:

Expand All @@ -147,13 +138,13 @@ curl -fsSL https://get.roomote.dev | bash
```

The installer handles Docker, secrets, and the Compose stack. It prints a setup
link when it finishes. Connect your model provider and repo, then send a test
link when it finishes. Follow the on-screen instructions, then send a test
message.

For production, point DNS at your server and pass `--domain roomote.example.com`.

See also: [Coolify](deploy/coolify/README.md) and [Fly.io](deploy/fly/README.md)
deployment guides.
for more deployment guides.

---

Expand Down Expand Up @@ -211,12 +202,9 @@ Features that matter at scale:

### Enterprise

Need SSO, custom SLAs, or dedicated support? Email
Need SSO, custom SLAs, or dedicated support? Get in touch:
[help@roomote.dev](mailto:help@roomote.dev).

Want a managed deployment instead of self-hosting? We can run it for you.
[Get in touch](mailto:help@roomote.dev).

---

## FAQ
Expand Down Expand Up @@ -255,6 +243,7 @@ GitHub, GitLab, Gitea, Azure DevOps, and Bitbucket Cloud. Connect one or many.
**What does it cost?**
Self-hosting is free for up to 10 registered users. You pay your own model
provider for tokens (or use the models bundled with your ChatGPT subscription).
Cloud starts at $49/mo, depending on total user count.
For larger teams, licenses are available by emailing
[help@roomote.dev](mailto:help@roomote.dev).

Expand Down
1 change: 1 addition & 0 deletions apps/api/src/handlers/discord/__tests__/index.test.ts

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

11 changes: 11 additions & 0 deletions apps/api/src/handlers/discord/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
resumeCommunicationTaskFromSnapshot,
} from '@roomote/sdk/server/communication';
import { tryHandleDiscordRequestUserInputMessage } from './request-user-input.js';
import { retireDiscordPrReviewOffersBestEffort } from './pr-review-action.js';
import { promptDiscordAccountLink } from './account-link.js';
import { processDiscordAttachments } from './attachments.js';
import {
Expand Down Expand Up @@ -617,6 +618,11 @@ async function processDiscordGatewayEvent(event: DiscordGatewayEvent) {
activeRun.id,
messageWithOutOfBand,
);
// A typed reply supersedes any pending PR review offers here.
retireDiscordPrReviewOffersBestEffort({
channelId: metadata.communicationChannelId,
threadId: metadata.communicationThreadId ?? null,
});
// Dedupe hit: nothing new will be delivered, so put claimed OOB
// messages and undelivered thread claims back for a later real follow-up.
if (!queued) {
Expand Down Expand Up @@ -718,6 +724,11 @@ async function processDiscordGatewayEvent(event: DiscordGatewayEvent) {
channelId: channel.channelId,
messageIds: [queuedMessage.ts],
});
// A typed reply supersedes any pending PR review offers here.
retireDiscordPrReviewOffersBestEffort({
channelId: metadata.communicationChannelId,
threadId: metadata.communicationThreadId ?? null,
});
return { ok: true, resumed: true, runId: resumed.id };
} catch (error) {
await releaseDiscordContinuationClaim(continuationClaim);
Expand Down
35 changes: 32 additions & 3 deletions apps/api/src/handlers/discord/pr-review-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { DiscordInteraction } from '@roomote/communication/discord-event';
import type { DiscordCommunicationProvider } from '@roomote/communication/discord-provider';
import {
claimPendingPrReviewAction,
claimPendingPrReviewActionsForThread,
dispatchPrReviewFollowUp,
enableAutoHandlePrReviewFeedback,
findDiscordMappedUserId,
Expand Down Expand Up @@ -83,16 +84,16 @@ export async function handleDiscordPrReviewActionCallback(input: {
if (dispatched.outcome === 'unavailable') {
await reply(
input.choice === 'auto'
? "I'll take future feedback from here, but this task can no longer be resumed for the current one. Reply here to start fresh."
? "I'll resolve future feedback on this PR, but this task can no longer be resumed for the current feedback. Reply here to start fresh."
: 'This task can no longer be resumed. Reply here to start fresh.',
);
return;
}

await reply(
input.choice === 'auto'
? "I'll take it from here — future review feedback on this PR gets handled in this task. Looking at the current feedback now."
: 'On it — taking a look at the review feedback.',
? "I'll resolve these and any future feedback on this PR automatically. Starting on the current feedback now."
: 'On it — resolving the review feedback.',
);
} catch (error) {
apiLogger.error(
Expand All @@ -103,3 +104,31 @@ export async function handleDiscordPrReviewActionCallback(input: {
await reply('Failed to start the follow-up. Reply here to ask again.');
}
}

/**
* Retires any pending PR review offers bound to a Discord conversation
* because a typed reply superseded them. Claims atomically so later clicks
* report "already handled"; the buttons stay visible but dead (Discord
* message component editing is not wired up yet). Fire-and-forget.
*/
export function retireDiscordPrReviewOffersBestEffort({
channelId,
threadId,
}: {
channelId: string;
threadId: string | null;
}): void {
void (async () => {
await claimPendingPrReviewActionsForThread({
provider: 'discord',
channelId,
threadId,
});
})().catch((error: unknown) => {
apiLogger.warn(
`[discord] Failed to retire PR review offers for channel ${channelId}: ${
error instanceof Error ? error.message : String(error)
}`,
);
});
}
Loading
Loading