Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ce6a8b1
[Fix] Let users select a model when editing environments (#862)
roomote-roomote[bot] Jul 28, 2026
4ec4713
feat: include cloud status in instance pings (#861)
roomote-roomote[bot] Jul 28, 2026
a895194
[Fix] Slack replies quote internal idle-session context (#847)
roomote-roomote[bot] Jul 28, 2026
b10a939
[Fix] Verify Linear workspaces with Linear OAuth tokens (#863)
daniel-lxs Jul 28, 2026
441b020
[Fix] Direct Linear delegations fail to start (#864)
roomote-roomote[bot] Jul 28, 2026
850301d
fix(web): explain unavailable OAuth integrations (#866)
daniel-lxs Jul 28, 2026
16cc24e
[Feat] Guide self-hosted Linear OAuth setup (#868)
daniel-lxs Jul 28, 2026
4782a7a
Enable Linear app mentions and delegation (#872)
daniel-lxs Jul 28, 2026
5737cfb
Allow administrators to reconfigure Linear OAuth (#873)
daniel-lxs Jul 28, 2026
620ca34
[Fix] Hide admin-only Slack/Linear connect cards from members (#869)
daniel-lxs Jul 28, 2026
4f4f997
[Fix] Daily reports omit configured providers (#877)
roomote-roomote[bot] Jul 29, 2026
430142b
feat: create a new repo (or fork) plus environment from within Roomot…
mrubens Jul 29, 2026
3380e11
[Feat] Show Roomote version in user menu (#876)
roomote-roomote[bot] Jul 29, 2026
b336fcf
fix(web): refine mobile task chrome (#878)
roomote-roomote[bot] Jul 29, 2026
a27e663
Add Discord announcements for GitHub Releases (#879)
mrubens Jul 29, 2026
d4c3802
[Improve] Show web follow-up context in GitHub replies (#870)
roomote-roomote[bot] Jul 29, 2026
c4b8067
Simplify Linear OAuth app setup (#871)
daniel-lxs Jul 29, 2026
d43f132
[Feat] Add GitHub daily management skills (#875)
roomote-roomote[bot] Jul 29, 2026
a758551
fix(web): hide license settings in cloud deployments (#882)
roomote-roomote[bot] Jul 29, 2026
5129bfc
[Fix] Sandbox startup fails on transient broker upload errors (#883)
roomote-roomote[bot] Jul 29, 2026
bc0957d
[Fix] Verify Microsoft Teams bot credentials before reporting them co…
daniel-lxs Jul 29, 2026
342c808
[Improve] Complete Linear integration setup and task flow (#885)
brunobergher Jul 29, 2026
2b21bc9
Release Roomote 0.26.0 (#886)
roomote-roomote[bot] Jul 29, 2026
15fa8e5
[Fix] Prevent duplicate GitHub follow-up quotes (#880)
roomote-roomote[bot] Jul 29, 2026
39071bf
[Fix] Linear session resumes with the wrong authorized account (#889)
roomote-roomote[bot] Jul 29, 2026
85c66c0
Fix first-session Linear routing after OAuth (#890)
daniel-lxs Jul 29, 2026
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
44 changes: 44 additions & 0 deletions .github/workflows/publish-ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}

- name: Publish GitHub Release for product tag
id: publish_release
env:
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.prepare.outputs.version }}
Expand Down Expand Up @@ -394,6 +395,7 @@ jobs:

if gh release view "$tag" >/dev/null 2>&1; then
echo "GitHub Release $tag already exists; skip."
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi

Expand All @@ -410,8 +412,50 @@ jobs:
--latest

rm -f "$notes_file"
echo "created=true" >> "$GITHUB_OUTPUT"
echo "Published GitHub Release $tag (images ready)"

- name: Announce GitHub Release in Discord
if: ${{ steps.publish_release.outputs.created == 'true' }}
continue-on-error: true
env:
DISCORD_MAIN_WEBHOOK_URL: ${{ secrets.DISCORD_MAIN_WEBHOOK_URL }}
GH_TOKEN: ${{ secrets.RELEASE_BOT_TOKEN || secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.prepare.outputs.version }}
shell: bash
run: |
set -euo pipefail

if [ -z "${DISCORD_MAIN_WEBHOOK_URL:-}" ]; then
echo "::notice::DISCORD_MAIN_WEBHOOK_URL is not configured; skipping the Discord release announcement."
exit 0
fi

# Custom Discord payloads use the base webhook endpoint. Also accept
# URLs copied from the native GitHub integration setup for convenience.
webhook_url="${DISCORD_MAIN_WEBHOOK_URL%/github}"
payload_file="$(mktemp)"
trap 'rm -f "$payload_file"' EXIT

gh release view "$VERSION" \
--json name,body,url,publishedAt,tagName \
| node scripts/release/build-discord-release-payload.mjs \
> "$payload_file"

curl \
--fail-with-body \
--silent \
--show-error \
--retry 3 \
--retry-all-errors \
--connect-timeout 10 \
--max-time 30 \
--header 'Content-Type: application/json' \
--data-binary "@$payload_file" \
"$webhook_url"

echo "Announced GitHub Release $VERSION in Discord"

notify-ops:
name: Notify ops repository
runs-on: ubuntu-latest
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

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

## 0.26.0 (2026-07-29)

This release expands Linear and GitHub workflows, makes release information easier to find, and improves setup and task reliability.

### Highlights

- Set up and manage Linear, link user accounts, and start app-mention, issue-delegation, scheduled, and direct Linear tasks.
- Create or fork a GitHub repository from Roomote, then bootstrap an empty repository and configure its environment automatically.
- Manage GitHub labels, milestones, and project status values safely from Roomote tasks.
- Find the running Roomote version easily, verify Teams credentials during setup, and start sandboxes more reliably through transient broker failures.

### Minor changes

- Announce newly published Roomote releases in Discord with the release title, notes, link, timestamp, and Roomote branding when a main-channel webhook is configured.
- Set up and manage Linear from onboarding or Settings, link user accounts, and start app-mention, issue-delegation, scheduled, and direct Linear tasks with the correct workspace and account context.
- Create a new GitHub repository or fork an existing one from Roomote, then automatically detect empty repositories, add their initial commit, and configure a working environment.
- Manage GitHub labels, milestones, and project status values from Roomote tasks with scoped credentials, confirmation for destructive changes, and read-back verification.
- See the running Roomote version from the signed-in user menu, open release details, and revisit the latest What's New notice without administrator access.

### Patch changes

- Hide self-hosted license controls from cloud deployments while keeping license management available to self-hosted administrators.
- Make task conversations easier to follow by showing request-input questions as distinct quoted context, refining mobile task chrome, quoting web follow-ups in GitHub replies, and keeping internal routing context out of Slack and Discord quotes.
- Retry transient compute-broker upload failures during sandbox startup so momentary upstream errors no longer prevent task environments from starting.
- Choose the model used by the Onboarding Agent when editing an existing environment, matching the model selection already available when creating one.
- Verify Microsoft Teams bot credentials with Microsoft before saving them, so a wrong app id, client secret, or tenant id fails the save with a message naming the field instead of reporting a configured bot that cannot authenticate. Teams settings now also reports when the saved credentials stop authenticating, and explains why the Teams app package cannot be pre-filled from a malformed App (Client) ID.

## 0.25.0 (2026-07-28)

This release expands account and diagnostics configuration while making task execution and Slack automation more reliable.
Expand Down

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { db, eq, githubInstallations } from '@roomote/db/server';
import * as GitHub from '@roomote/github';

import type { WebhookResponse } from '../../types';

interface InstallationRepositoriesChangePayload {
installation?: { id?: number } | null;
}

/**
* Resync an installation's repository list when its accessible repositories
* change on GitHub: `installation_repositories.added/removed` (selected-repos
* installs) and `repository.created/deleted/renamed` (all-repos installs emit
* these instead). A full resync is used rather than a narrow upsert because
* these payloads omit fields the `repositories` row needs (default branch,
* clone URL), and `syncRepositories` already handles upsert + deactivation.
*/
export async function handleInstallationRepositoriesChange(
payload: InstallationRepositoriesChangePayload,
): Promise<WebhookResponse> {
const installationId = payload.installation?.id;

if (typeof installationId !== 'number') {
return { status: 'ok', message: 'missing_installation' };
}

const installation = await db.query.githubInstallations.findFirst({
where: eq(githubInstallations.installationId, installationId),
columns: { installedByUserId: true },
});

if (!installation) {
return { status: 'ok', message: 'unknown_installation' };
}

const result = await GitHub.syncGitHubInstallation({
userId: installation.installedByUserId,
installationId,
});

if (!result.success) {
return {
status: 'error',
message: `Failed to resync installation ${installationId}: ${result.error}`,
};
}

return {
status: 'ok',
message: `Resynced installation ${installationId}`,
metadata: { repositoryCount: result.repositories.length },
};
}
10 changes: 10 additions & 0 deletions apps/api/src/handlers/github/handlePrComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,13 +854,15 @@ async function deliverFollowUpToExistingTask({
taskId,
userId,
message,
quoteText = message,
status,
taskPhase,
commenterDisplayName,
}: {
taskId: string;
userId?: string | null;
message: string;
quoteText?: string;
status: RunStatus;
taskPhase: string | null;
commenterDisplayName?: string;
Expand Down Expand Up @@ -890,6 +892,7 @@ async function deliverFollowUpToExistingTask({
taskId,
userId: senderUserId,
message,
quoteText,
senderMode: 'github_pr_follow_up',
...(commenterDisplayName
? { workerQuoteUserName: commenterDisplayName }
Expand All @@ -899,6 +902,7 @@ async function deliverFollowUpToExistingTask({
taskId,
userId: senderUserId,
message,
quoteText,
senderMode: 'github_pr_follow_up',
...(commenterDisplayName
? { workerQuoteUserName: commenterDisplayName }
Expand Down Expand Up @@ -932,12 +936,14 @@ async function resumeExistingTaskAndDeliverFollowUp({
userId,
sourceRunId,
message,
quoteText = message,
resumePromptFallbackTask,
}: {
taskId: string;
userId?: string | null;
sourceRunId: number;
message: string;
quoteText?: string;
resumePromptFallbackTask: SnapshotResumePromptFallbackTask;
}) {
const sourceRun = await findLatestTaskRun(taskId, {
Expand Down Expand Up @@ -984,6 +990,7 @@ async function resumeExistingTaskAndDeliverFollowUp({
taskId,
userId,
message,
quoteText,
status: sourceRun.status,
taskPhase: sourceRun.taskPhase,
});
Expand Down Expand Up @@ -1028,6 +1035,7 @@ async function resumeExistingTaskAndDeliverFollowUp({
// Carry the follow-up on the resume run itself so the resumed worker can
// send it after the harness session is actually ready.
resumePrompt: message,
resumeQuoteText: quoteText,
resumePromptSource: 'github',
resumePromptFallbackTask,
} satisfies TaskPayload<typeof TaskPayloadKind.SnapshotResume>;
Expand Down Expand Up @@ -1504,12 +1512,14 @@ export async function handlePrComment(
userId: reviewer.properties.userId,
sourceRunId: activePrOwner.runId,
message: followUpMessage,
quoteText: mention.body ?? '',
resumePromptFallbackTask,
})
: await deliverFollowUpToExistingTask({
taskId: activePrOwner.taskId,
userId: reviewer.properties.userId,
message: followUpMessage,
quoteText: mention.body ?? '',
status: activePrOwner.status,
taskPhase: activePrOwner.taskPhase,
commenterDisplayName,
Expand Down
21 changes: 21 additions & 0 deletions apps/api/src/handlers/github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { handleWorkflowRunCompleted } from './handleWorkflowRunCompleted';

// Repository metadata sync:
import { handleRepositoryEdited } from './handleRepositoryEdited';
import { handleInstallationRepositoriesChange } from './handleInstallationRepositoriesChange';

// Utilities:
import { isFromKnownInstallation } from './isFromKnownInstallation';
Expand Down Expand Up @@ -462,6 +463,26 @@ github.post('/', async (c) => {
),
);

// Keep the stored repository list in sync as repos appear, disappear, or
// change access. Selected-repos installs emit `installation_repositories`;
// all-repos installs emit `repository.created/deleted` instead. Not gated
// on isRepoSkipped: the row must exist even for skipped repos.
webhooks.on(
['installation_repositories.added', 'installation_repositories.removed'],
({ id, name, payload }) =>
recordWebhook(id, `${name}.${payload.action}`, payload, () =>
handleInstallationRepositoriesChange(payload),
),
);

webhooks.on(
['repository.created', 'repository.deleted', 'repository.renamed'],
({ id, name, payload }) =>
recordWebhook(id, `${name}.${payload.action}`, payload, () =>
handleInstallationRepositoriesChange(payload),
),
);

webhooks.on('workflow_run.completed', ({ id, name, payload }) =>
recordWebhook(id, `${name}.${payload.action}`, payload, async () => {
if (isRepoSkipped(payload.repository.full_name)) {
Expand Down
Loading
Loading