Skip to content

Promote v1.0.0 to production#351

Closed
mrubens wants to merge 69 commits into
mainfrom
release/v1.0.0
Closed

Promote v1.0.0 to production#351
mrubens wants to merge 69 commits into
mainfrom
release/v1.0.0

Conversation

@mrubens

@mrubens mrubens commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Promote v1.0.0

Frozen at 48046b5dd4671c26319e480af8881d2b63cb240a — the commit where 1.0.0 was versioned. Commits merged to develop after that point ship in the next release.

  • Merge this PR with a merge commit (do not squash or rebase).
  • If multiple promote PRs are open, merge them in version order (oldest first).
  • Merging publishes a GitHub Release for v1.0.0 and triggers the existing GHCR v* image publish (latest channel).
  • The release/v1.0.0 branch can be deleted after this PR merges.

Changelog

1.0.0 (2026-07-15)

Major changes

  • Roomote 1.0 is the first public product release: the monorepo is open and ready for people to try the product, self-host, and send feedback on web tasks, cloud agents, and chat integrations.

Minor changes

  • Docker Compose and Dockerfile environment projects are first-class: run them on Modal VM sandboxes, stream project logs into the task Logs panel, start them without blocking eligible tasks, and allocate higher sandbox memory when nested Docker is required (renamed from "container projects").
  • Multi-SCM automations and PR tooling: triage, audit, announcer, and manager-stats cover GitHub, GitLab, Gitea, Azure DevOps, and Bitbucket with shared open-PR listing, merged-PR facts, conflict resolution, and digests; reports can land on Slack, Teams, or Telegram, and the automations page shows destinations plus exception-only coverage badges.
  • Environment setup is reworked into the normal task flow: setup completion is observable to the agent and platform, setup logs show in the Logs tab, first-time hosted compute provision no longer blocks creation, excluded sandbox providers stay hidden, and completion messaging no longer shoves users back to a separate /setup page.
  • Experimental settings add a deployment-level Code Mode toggle for coding-agent task behavior.
  • Local Docker can be enabled or disabled from the Local Docker settings surface without leaving that provider’s configuration page.
  • Source-control setup expands provider OAuth and connection flows, simplifies Azure DevOps to organization and PAT by default while preserving full repository identifiers, and prefills the GitHub App description in the manifest setup flow.
  • Slack agent narrative replies prefer modern markdown blocks, the Working on footer posts out of band with notifications when linked PRs close, MCP integration setup becomes a non-blocking suggestion instead of blocking task start (with Zero detection limited to product surfaces), and agents can post to Teams or Telegram channels through a surface-generic channel-post tool.
  • When only one environment exists the homepage starts there instead of Auto, subagent rows expand to show the launch prompt, the router supplies task-relevant kickoff strings, CI failure triage runs as one environment-backed fix task, the coding agent consults the advisor on hard failures and user challenges, and Microsoft Teams onboarding setup copy and flow are refreshed.
  • Daily anonymous product stats include a 7-day PR funnel so deployments can evaluate how effectively agent work turns into shipped pull requests.

Patch changes

  • Hosted Docker runtime provisioning is more reliable across E2B, Blaxel, and related setup paths, with retryable rebuilds that preserve the prior artifact.
  • Setup can back out of earlier choices without wiping later steps when a user revisits a picker, and finishing setup into an onboarding task no longer flashes the home page first.
  • Repository resolution and PR follow-up matching are host- and provider-scoped so same-named repositories on different source-control hosts no longer cross-route, and Slack markdown link conversion no longer uses a ReDoS-prone regex.
  • Local development artifact uploads from hosted workers succeed through the Caddy edge, and presigned upload responses without an S3 ETag are no longer treated as successful.

mrubens and others added 30 commits July 13, 2026 01:22
* Move environment setup into regular task

* Fix onboarding setup task handoff

* ui

* Improve onboarding environment handoff

* Fix onboarding rebase fallout

* Remove stale setup bootstrap helper

* Guide users to their first task after setup

* env creation task UX

* Fix onboarding completion footer validation
* feat: add local docker sandbox toggle

* fix: place local docker toggle in provider section

* fix: preserve sandbox exclusions

* fix: preserve provider exclusions in launch lookup

* test: cover sandbox exclusion preservation

---------

Co-authored-by: Roomote <roomote@roomote.dev>
)

* fix: preserve ADO repository identifiers in environments

* fix: reject unresolved environment repositories everywhere

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* copy tweak

* Skip sandbox setup for env configured providers

* copy/logo

* ui

* Update communication connection onboarding

* ui

* Fix Teams onboarding copy lint

* Fix onboarding lint warnings

* Remove unused onboarding wordmark

* fixes

* Add Teams advanced setup config toggle

* Fix stale PR test expectations

* Resolve compute setup rebase conflict

* Address Teams setup feedback
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Add shared communication provider factories

Hoist the ad-hoc Teams/Telegram adapter construction into shared
runtime-credential factories in @roomote/sdk/server:

- New createTelegramCommunicationProviderFromRuntimeCredentials
  (resolves the bot token from env or settings-UI values) replacing
  five duplicated resolve-check-construct blocks across the API
  handlers, bullmq jobs, and comms settings.
- createTeamsCommunicationProviderFromRuntimeCredentials now accepts
  extra provider options (graphTokenProvider, serviceUrl) so the
  Teams delegated-Graph path uses the shared factory too.

Behavior note: PR-review Telegram notifications previously read the
bot token from process env only; via the shared factory they now also
honor a token saved from the comms settings UI, matching every other
Telegram outbound path.

Groundwork for routing automations through the provider-agnostic
communication adapters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix test mocks for the shared Telegram provider factory

Two api test files not covered by the first pass mocked
@roomote/sdk/server (or its module graph) without the new factory:
slack-reaction-add now stubs it, and submitAutomationWorkItems mocks
the automation-work-items telegram module like its teams sibling so
the sdk server barrel stays out of the test module graph.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Add generic communication adapter dispatch

getCommunicationProviderAdapter(provider) in @roomote/sdk/server
resolves a ready CommunicationProviderAdapter for slack/teams/telegram
from the deployment's runtime credentials, or null when the provider
is not connected. Slack gets its missing adapter factory (active
installation -> SlackNotifier -> SlackCommunicationProvider); Teams
and Telegram reuse the shared factories.

First consumer: the PR-review notification job now posts through the
adapter dispatch instead of a hand-rolled per-provider switch over
SlackNotifier and the provider classes. Per-route input nuances
(Teams serviceUrl/markdown, Telegram plain text) stay in a small
input builder.

Groundwork for the automation channel waterfall: destination
resolution lands with the automation target model next.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix formatting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Make Slack MCP setup a non-blocking suggestion instead of an interrupt

When a Slack message links to a service whose MCP integration is not set
up (Zero, Linear, Vercel, ...), Roomote previously refused to start the
task: the mention path posted a blocking Configure / "Nah, keep going"
prompt, and the channel auto-start path returned a bare "Task was not
started because X still needs Slack MCP setup." line with no way to
proceed.

The task now always starts. Detection still runs, but its result is
surfaced as a small context-block reply in the thread ("That looks like
a Zero link... link your Zero account if you want me to be able to use
it") posted after the task launches, with the manager-channel
notification preserved. Detection or posting failures never affect the
launch.

This removes the now-unused interrupt machinery: the Redis pending
interrupt state and nonce-claim script, the mcp_setup_configure /
mcp_setup_ignore interactive handlers, the resume-after-setup flow, and
the pending-interrupt follow-up eligibility in message entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Refine MCP setup suggestion placement from review findings

Post the mention-path suggestion only after routing decides a task flow
is proceeding, so it never appears above a pure Q&A platform answer.
Skip the suggestion when a follow-up reuses an active run so repeated
links don't stack identical nudges, and drop "so I'll work without it"
from the copy since the confirmation flow can still be cancelled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Surface-scope automation tracked messages and add button parity

Two pieces of comms groundwork so automations can post to Teams and
Telegram without corrupting the Slack feedback loop:

- Automation tracked-message reads/writes now carry an explicit
  TrackedMessageSurface instead of a hardcoded 'slack': the upsert
  takes a surface, and every lookup filters on it so rows from
  different surfaces can never collide on a shared dedupe key. All
  current callers pass 'slack'; no behavior change.
- Generic message buttons now render on every adapter. Slack turns
  link buttons into a Block Kit actions block (wrapping the text body
  in a section so it stays visible); Teams degrades them to trailing
  links (markdown links or label: url lines) since there is no
  validated outbound card renderer; Telegram already rendered inline
  keyboards. Callback-only buttons are dropped on Slack/Teams because
  no generic interaction handler exists.

The thread-feedback reply harvest still reads the persisted Slack
conversation log; non-Slack threads resolve with empty feedback until
those surfaces get an equivalent reply source (noted inline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix formatting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Scope Zero MCP link detection to product surfaces

The Zero catalog entry matched any URL on zero.xyz or withzero.ai, so
casual mentions of the marketing homepage (e.g. "grab the favicon from
zero.xyz") triggered MCP setup suggestions in Slack. Following the
Vercel entry's pattern, Zero now matches only its product surfaces:
capability pages (/c/<id>), the service directory (/browse), the
wallet/profile page, and the mcp.zero.xyz connector host. withzero.ai
is dropped entirely — it is a bare landing page with no linkable
product resources.

Adds the first direct tests for extractUrlsFromSlackText and
matchSlackMcpSetupService, covering the Zero split and existing
service matching.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Keep Zero homepage links triggering the setup suggestion

The bare zero.xyz / withzero.ai homepage is a strong enough "about
Zero" signal now that detection only produces a non-blocking hint;
deep marketing pages (/faq, /security, /getlisted, ...) stay excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Revert accidentally committed local Caddyfile dev patch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Add surface-generic post_to_channel agent tool

Teams- and Telegram-launched tasks previously had no way to post a
standalone message: post_to_slack_channel is Slack-only and
send_chat_reply only replies into the originating thread.

- The channel_post MCP endpoint now dispatches by the task's
  communication provider (mirroring thread_reply's
  maybeSendCommunicationThreadReply): Teams and Telegram tasks post
  through their provider adapter, everything else keeps the existing
  Slack path byte-for-byte.
- Cross-surface posts are restricted to the conversation the task was
  launched from — Teams and Telegram have no channel-membership model
  equivalent to Slack's isAppInChannel, and that conversation is the
  only one whose Teams serviceUrl the task holds. Telegram posts
  default into the task's own topic.
- New post_to_channel worker tool, registered only for Teams/Telegram
  chat contexts (Slack tasks keep post_to_slack_channel), passing
  opaque conversation ids through without Slack channel-name
  normalization. Added to the Slack-posting basenames so subagents
  cannot bypass the parent-only posting restriction.

reply_to_thread needs no new tool: send_chat_reply already dispatches
by surface via the thread_reply endpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: raw channel ids for communication dispatch

- parseChannelPostRequestBody no longer Slack-normalizes the channel;
  normalization moved after the communication-provider dispatch so
  Teams conversation ids keep their case (Slack normalization
  lowercases, which broke own-conversation matching for realistic
  Teams ids). Dispatch tests now use an uppercase-bearing Teams id.
- post_to_slack_channel is no longer registered for Teams/Telegram
  chat contexts; post_to_channel is the single channel-post tool
  there, so opaque ids can't be fed into Slack normalization.
- Missing Teams serviceUrl now returns a distinct 503 instead of the
  misleading wrong-conversation 403.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add 7d product PR funnel to daily instance report

Ship aggregate opened/open/closed/merged counts plus median time-to-merge
for product-authored PRs in the anonymous daily stats payload, using local
task_pull_requests (and pull_request_facts for durations) only.

* fix: make instance-report PR cohort tests isolation-safe

Stop asserting absolute pullRequests7d totals against the shared test DB.
Scope the integration case to this fixture's repository so parallel suite
data cannot flake the product-opened funnel expectations.

* fix: harden product PR funnel stats against review feedback

- Derive workflow exclusions from SOURCE_CONTROL_AUTOMATION_WORKFLOWS
- Case-insensitive history/facts name matching (plus repositoryId paths)
- Keep report tests fixture-scoped so shared DB suites stay isolation-safe

---------

Co-authored-by: Roomote <roomote@roomote.dev>
…y when PRs close (#288)

* [Improve] Move Working on footer to OOB Slack posts and notify on PR close

Keep the sticky Working on footer on the latest Slack message for review
and PR status updates, and post a closed-PR status with heavy_multiplication_x.

* [Fix] Address PR #288 review: ADO closed notify, share footer helpers

Notify Azure DevOps abandoned PRs like other providers, dedupe sticky
footer helpers into packages/slack for MCP, drop always-true terminal
guards, and use reply-only sticky footers on terminal PR status posts.

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Add automation target model and capability declarations

Groundwork for automations reporting to non-Slack surfaces:

- AutomationTarget unions gain teams/teams_channel alongside the
  existing slack/telegram/sentry kinds.
- Every triggerable automation descriptor now declares which comms
  surfaces and source-control providers its shipped runner supports
  today (dependabot_triage and ci_failure_triage stay GitHub-only by
  design; suggester and sentry_triage are already SCM-agnostic).
  Settings gating and the automations page read these as the source
  of truth, expanding per-automation as runners generalize.
- upsertAutomation gains managedTargetKinds: a settings save now only
  replaces the target kinds it owns and preserves the rest, so the
  Slack-channel settings writer can no longer clobber teams/telegram
  targets configured through other paths. All settings-update sites
  declare their managed kinds.

No behavior change for existing Slack/sentry configurations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: manage target kinds in the starter-automation writer

syncManagerStarterAutomations also writes suggester/announcer targets;
it now declares managedTargetKinds so it cannot clobber non-Slack
targets either.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ult (#278)

* Simplify Azure DevOps setup to organization and PAT by default

Hide advanced ADO fields behind a setup toggle, keep Settings complete,
and fall back ADO tenant resolution to the shared Microsoft tenant.

* Add Azure DevOps authentication modes

* ui

* ui

* Align Azure DevOps setup with Teams flow

* Clarify Azure DevOps auth labels

* Improve Azure DevOps setup guidance

* ui

* Show onboarding completion in live tasks

* More tweaks

* More ui

* Fix Azure DevOps onboarding tests

* Remove unused ADO auth type export

---------

Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Bruno Bergher <bruno@roocode.com>
SlackSetupExperience's intro screen owns the step action buttons, and it
is skipped when the Slack config is already saved (savedSatisfied). But
StepAuthEnvVars's providerOwnsActions check was missing the matching
!savedSatisfied clause, so revisiting the auth step with saved Slack
credentials rendered the value form with no action button at all,
stranding the user on the page.

Align providerOwnsActions with the intro guard and add a regression
test for the saved-Slack revisit path.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…faces (#300)

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
Keeps develop's package.json and CHANGELOG aligned after the
main-only 0.4.2 Slack setup hotfix so the next release does not
reuse the v0.4.2 tag.

Co-authored-by: Roomote <roomote@users.noreply.github.com>
* Route triage and audit automations to any comms destination

Sentry triage, Dependabot triage, and the security/code-quality
auditors can now report to Teams or Telegram, not just Slack.

Core design: instead of teaching runners about adapters, the resolved
destination is stamped onto the scan task's communication payload
fields, so the surface-generic worker tools (send_chat_reply,
post_to_channel) and the own-conversation posting restriction all
apply unchanged. Slack destinations stay byte-identical (no stamping).

- New destination module: resolveAutomationRuntimeDestination extends
  the db waterfall (own target -> Slack manager channel) with a
  primary-conversation tail for Slack-less deployments (most recently
  active Teams conversation, then the Telegram primary chat); the tail
  is skipped when Slack is connected so the "configure a manager
  channel" nudge survives. listConnectedCommunicationProviders gates
  deployment eligibility. Teams destinations resolve their Bot
  Framework serviceUrl from teamsInstallations at launch.
- scheduled-triage-runner and merged-pr-audit-runner resolve
  destinations, stamp payload fields, and keep Slack-only side effects
  (debug posts, workspace timezone) guarded on an actual Slack token.
- Scan prompts are surface-correct: channel tag, posting tool name,
  and surface wording parameterized per destination.
- ci_failure_triage explicitly skips non-Slack destinations for now
  (its prompt and webhook announcements are still Slack-shaped).
- Manual "Run now" and settings validation accept a resolved
  destination on any provider the automation's descriptor supports;
  registry declarations flip to slack/teams/telegram for the four
  generalized automations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: destination-surface feedback lookups, skip reason

- Thread-feedback loaders take the destination surface (default slack)
  and the runners pass it, so Teams/Telegram feedback context flows as
  soon as those surfaces track automation threads.
- Stale 'No active Slack installation' skip reason now reads 'No
  connected communication provider'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The merged-PR announcer's enumeration already reads the
provider-neutral taskPullRequests table, which every source-control
provider's webhooks populate; only its eligibility gate forced a
GitHub installation. The gate now requires any active repository, the
manual-trigger requirement drops the github token for the neutral
repository check, and the registry declares support for all
source-control providers.

The security/code-quality auditors and conflict resolver stay
GitHub-only by declaration: the auditors read pullRequestFacts, which
only the GitHub sync writes, and conflict resolution needs a
list-open-PRs primitive no provider package has yet — both are real
per-provider workstreams, not gate changes.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Roomote <roomote@roomote.dev>
mrubens and others added 22 commits July 14, 2026 13:19
* Sync merged-PR facts from all source-control providers

The security and code-quality auditors read pullRequestFacts, which
only the GitHub sync wrote — so they silently audited GitHub alone.
A new provider-neutral sync (gitlab/gitea/ado/bitbucket) reuses the
shared facts store extracted from the GitHub writer: provider-scoped
active-repo selection with the same repo budget, least-recently-synced
ordering and cooldowns, bounded merged-PR backfill plus incremental
updated-after passes via the extended list primitive, and webhook fact
upserts on terminal merge/close events in each provider's handler. The
GitHub sync is now scoped to github repositories so the two syncs
cannot contend over budget or cursor rows.

Per-provider timestamp realities documented in code: ADO substitutes
closedDate, Bitbucket has no merge timestamp (updated_on fallback with
a warning), GitLab merge webhooks carry no author until the next pass.

The audit runner gates on any active repository, prompts drop their
GitHub assumptions, and both auditors declare all source-control
providers — the automations-page badges update accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: repository-identity scoping and cursor tie-breaker

- Webhook fact upserts scope the repository lookup by the event's host
  (derived from the PR URL), with NULL-host legacy rows still matching,
  so same-name repositories on different self-managed hosts no longer
  cross-write facts and cursors.
- The audit manifest dedupes by repository id + PR number instead of
  fullName + number, keeping same-name cross-provider PRs distinct.
- Scan pagination orders and tie-breaks on the facts row primary key;
  cursors carry both new and legacy fields so rollbacks read them, and
  old-format cursors resume timestamp-inclusive (possible one-time
  re-audit at the boundary, never a skip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: partition audit launches by source-control provider

The merged-PR manifest now launches one audit task per provider
partition, stamping sourceControlProvider on non-GitHub payloads so
the provider-bound pull-request tools accept their PRs. All-GitHub
manifests keep today's single launch with a byte-identical payload.
Partitions exactly tile the scan page, so the page cursor advances
only after every partition launches; a partition failure leaves the
cursor untouched (at most a one-time duplicate audit, never a skip).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: host-aware audit partitioning with ambiguity guard

Audit launches now partition by (provider, host). Entries whose
(provider, fullName) matches more than one active repository row are
excluded from every manifest with a logged warning — the pull-request
tools resolve by provider and name alone, so auditing them could hit
the wrong host; threading host identity through the payload and tool
resolution is marked as the follow-up. The page cursor still tiles
exactly and advances past deliberately skipped entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…rovider (#331)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Full provider coverage now renders no badge — the absence of a warning
is the signal. Limited automations keep an explicit boundary badge:
"GitHub only", "Slack only", or the supported-provider list suffixed
with "only" (e.g. conflict resolution, which has no Gitea/Bitbucket
conflict signal).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Improve source control provider setup guidance

* Add directional setup step transitions

* fix gitea base URL normalization

* ui details

* Animate pre-sign-in setup steps

* Match setup transition timing

* normalize gitea URLs when saving config

* ui

* feat(gitlab): add deployment OAuth connection

* feat(gitea): make OAuth the primary source-control connection

* test(gitlab): update OAuth API expectations

* feat(setup): show Gitea OAuth redirect instructions

* Remove GitLab PAT deployment fallback

* feat(setup): authorize Gitea after saving credentials

* docs(setup): clarify Gitea OAuth instructions

* gitlab oauth fixes

* sync fix

* Automatically sync repositories during onboarding

* invoke

* fix GitLab webhook setup after OAuth sync

* fix GitLab OAuth API authentication

* chore(web): clean up setup lint exports

* default gitea url

* fix gitea oauth webhook registration

* invoke

* fix(source-control): resolve CI test and CodeQL findings

* fix gitea comment setup guidance

* fix gitea oauth callback setup

* address source control oauth review feedback

* fix: address remaining source control review feedback

* fix: avoid polynomial URL normalization regexes

---------

Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Bruno Bergher <bruno@roocode.com>
* Compute manager stats across all source-control providers

The weekly digest was computed exclusively from GitHub's analytics
API. Non-GitHub repositories now contribute via the provider-neutral
list primitives (open + merged-in-window per repo), mapped through a
pure per-provider normalizer; the GitHub path is unchanged. The
authored-vs-reviewed classification joins task PRs with provider-
qualified keys, fixing a latent same-name cross-provider collision.

Honest degradations, marked in the digest rather than fabricated: the
LOC line is labeled "(GitHub PRs only)" when non-GitHub PRs are
counted (no bulk additions/deletions API elsewhere), closed-without-
merge PRs are not counted off-GitHub, and rows without timestamps are
dropped. Eligibility requires any active repository; the GitHub actor
is only needed for the GitHub data path.

manager_stats declares all source-control providers — with exception-
only badges, its "GitHub only" pill disappears, leaving only the
inherently-GitHub Dependabot and CI-failure triage automations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: truncation logging, merge-race dedup, provider-scoped repo counts

Provider list warnings are logged per repo instead of silently
dropped; dedup prefers the merged summary when a PR merges between the
concurrent open and merged reads; most-active-repo counts are keyed by
provider-qualified identity. Per product direction, outbound digest
copy carries no scope qualifiers: the LOC line renders only when it
covers every counted PR and is omitted otherwise, and repo labels stay
bare names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: deterministic tie-break for same-name repo counts

Equal-count same-name repos across providers now tie-break on the
provider-qualified key instead of input order. The digest deliberately
keeps bare repository names: outbound message copy carries no provider
qualifiers by product direction; the settings-page capability badges
own that story.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Roomote <roomote@roomote.dev>
…#333)

* Make environment setup completion observable to the task

Repository setup commands run in the background for environment tasks,
but nothing told the agent (or the platform) when they finished:

- Write <workspace>/.roomote/setup-status.json with live per-command
  state (pending/running/succeeded/failed) and mirror each command's
  stdout/stderr to .roomote/setup-logs/<repo>/<command>.log
- Fix the sandbox instruction that unconditionally claimed setup
  commands "were already executed"; in background mode it now points
  the agent at the status file and logs instead
- Inject an in-session notification via the harness when background
  setup settles while the task is actively running
- Track the real setup lifecycle on task_runs
  (environment_setup_state / environment_setup_completed_at), written
  when setup settles rather than when the blocking phase returns, and
  record one task-run phase event per repository command

* Update workspace test assertions for new command lifecycle callbacks

* Defer environment-setup notification until the runtime task has started

executeStartNewTask flips the phase to running before the StartNewTask
command carrying the initial prompt reaches the harness, so a
fast-settling background setup could inject its notification into that
window and replace the initial user prompt as the session's first
message. Buffer the settled outcome and deliver it only after the
runtime emits taskStarted.

* Address review feedback on setup status file

- Replace a literal NUL byte in commandKey with the \u0000 escape so
  Git stops treating setup-status.ts as a binary blob
- Fold background Docker project warnings into the setup status file so
  it cannot report a clean 'completed' when Docker setup failed

* Always create setup-status.json for environment workspaces

The sandbox instruction and the settle notification point the agent at
.roomote/setup-status.json, but Docker-only or command-less environments
never created it. Initialize the writer for every environment workspace
and finalize it even when there are no repository commands to run, so
the file's existence is an invariant the agent can rely on.

* Track duplicate command names as separate setup-status entries

The environment schema allows repeated command names within a repo, but
the (repository, name) map kept only the first entry, so later
duplicates vanished from the published plan and their callbacks
overwrote the first entry's lifecycle. Each key now maps to all matching
entries in plan order; since commands run sequentially per repository,
callbacks target the earliest entry still in the expected state, and
duplicate log files get an occurrence suffix.

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
…ogs panel (#337)

* Stream Docker project logs to a well-known file and surface them in the Logs panel

Docker projects had no standard log: Compose startup output went only to
the worker's startup logger and container logs lived inside dockerd. Now:

- The worker writes each project's Compose startup output, failure
  diagnostics, and a detached 'docker compose logs --follow' stream to
  /tmp/roomote-docker-projects/<compose-project>.log, created up front so
  the Logs panel's tail -f finds it immediately
- toComposeProjectName / getDockerProjectLogFilePath move to
  @roomote/types so the web app derives identical paths
- The Logs sidebar lists one entry per configured docker_project

* Redact project values from Docker failure output in project logs

error.message from a failed compose up carries Compose stderr, which can
echo substituted project env values or build args. Redact it (alongside
the already-redacted diagnostics) before appending to the project log
file and before rethrowing, so neither the Logs panel nor downstream
warnings expose sensitive values.

* Log redacted message instead of raw error for optional Docker failures

The caught Error's cause chain still holds the original Compose error
with unredacted stderr, and the startup logger serializes causes into
harness.log, which the Logs panel exposes. Log the redacted message on
both the user and debug channels instead.

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Resolve repositories host-aware via payload sourceControlHost

Merged-PR audits skipped every repository whose (provider, fullName) was
active on more than one host, because launched tasks resolved
repositories by (provider, fullName) alone and could hit the wrong
self-managed instance.

Task payloads can now carry an optional sourceControlHost, stamped by
the merged-PR audit runner (per provider/host partition) and the
conflict scanner's provider-neutral path. resolveRepositoryRow narrows
by that host, preferring exact matches and falling back to legacy rows
without a recorded host; without a payload host, a multi-host identity
now fails loudly instead of resolving arbitrarily.

The audit manifest skip narrows to legacy entries whose repository row
has no recorded host; those self-heal once the host backfill reaches
them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: host-scope conflict-scan dedup and PR linkage

An active resolution run recorded for a same-name PR on another host no
longer suppresses a host-pinned launch: hasActiveResolutionRun and
findActiveGitHubBranchWork scope their lookups by the repository host
(NULL-host legacy rows still match), and the provider-neutral conflict
scan stamps the host into prLinkage so the task association itself is
host-scoped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: gate legacy branch fallback on linkage host

An unstamped legacy payload no longer suppresses a host-scoped branch
lookup when the task's own pull-request linkage records a different
host: the linkage pins the task to another instance, so the NULL-host
payload tolerance applies only when no conflicting linkage host is
known.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: stamp sourceControlHost in webhook review launches

GitLab/Gitea/ADO/Bitbucket webhook handlers already resolve the
concrete repository row before enqueueing review tasks; stamping its
host into the launch payload lets those tasks resolve repositories
host-aware instead of failing the new multi-host ambiguity check.
Null-host legacy rows omit the field, matching the audit runner and
conflict scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review: host-scope webhook automation target lookup

get*AutomationTargets selected the repository row by
externalRepoId-or-fullName alone; numeric project ids and full names
collide across self-managed instances, so a webhook could select the
other host's row and pin its review launch to the wrong host.

The handlers now derive the instance host from the webhook's own URLs
(shared toHostFromUrl, moved from pull-request-fact-sync) and the
helpers pick the row host-first with the legacy NULL-host fallback,
matching resolveRepositoryRow's tiering. A webhook whose host matches
no row resolves to none rather than another instance's repository.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Fix ReDoS-prone link regex in Slack markdown converter

The markdown link pattern used a quantified alternation whose first
branch was itself unbounded ((?:[^()]+|\([^()]*\))+), a shape prone to
catastrophic backtracking on inputs like a long run of open parens
after "[label](". Matching one character per iteration ([^()]) removes
the ambiguity while still supporting link targets with one level of
balanced parentheses.

Adds a test for balanced parens in the URL and a regression guard that
converts a pathological 50k-paren input within a strict time budget.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Make the link label capture linear as well

The label part ([^\]]+) was still quadratic on malformed input such as
a long run of "[" with no closing bracket: the unanchored global match
retried at every "[" and rescanned the remaining suffix for a "]".
Excluding "[" from the label class makes failed attempts non-overlapping
with subsequent start positions, so matching stays linear.

Flagged by CodeQL (js/polynomial-redos) and PR review on the previous
commit. Adds regression guards for "[".repeat(50_000) and
"[a".repeat(50_000).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…nner verification loop (#340)

* Feed environment setup ground truth into the verification loop

The environment-setup workflow already launches a verification task and
revises the environment when verification fails, but both ends of that
loop ran blind to the setup ground truth #333 introduced:

- The verification task's prompt told it to "be patient" instead of
  reading .roomote/setup-status.json, so it could judge the environment
  before background setup settled or fail to name the broken command.
- The task summary the setup agent polls (manage_tasks get_summary) did
  not surface environmentSetupState, so setup failures only reached the
  fix loop when the verification agent happened to describe them.

Expose environmentSetupState through the summary API and render it as an
Environment Setup line in get_summary, and update the skill so the
verification prompt consults setup-status.json / setup-logs and the
monitoring steps treat a failed or completed-with-warnings line as
direct, fixable evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Verify environments with the proof runner instead of re-provisioning

The environment-setup fix loop iterated by relaunching a full
environment-backed verification task per config revision, so every retry
paid provisioning time and inherited compute failure modes unrelated to
the config under test.

Give environment-setup tasks the hidden proof-runner subagent in
brief-supplied-target mode (they discover the app URL mid-task, so no
target exists to bake at config-generation time; detection keys off the
$environment-setup prompt invocation via the new
isEnvironmentSetupTaskPrompt helper). The skill now iterates in-workspace:
after loopback validation and on every install/start-affecting revision it
delegates a one-item proof brief to the proof runner and treats the
Blocked verdict, observed port state, and visible browser errors as the
fix-loop evidence. The spawned environment-backed task becomes the single
final end-to-end confirmation, and its prompt now relays its own proof
runner's verdict and screenshot URLs instead of judging the page itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update browser containment policy test for delegated proof-runner wording

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Prefer brief-targeted proof runner for env-setup tasks with an environment

An environment-setup task launched against an existing environment carries
that environment's initialUrl, but that URL is the thing under revision —
baking it would pin the proof runner to a possibly stale surface and make
it reject the freshly validated target the setup workflow needs to verify.

Addresses the roomote review finding on run-task.ts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
…proof-runner verification loop (#340)" (#346)

This reverts commit accb678.
* [Feat] Show environment setup logs in the task Logs tab

* [Chore] Stop exporting unused SetupStatusResult type

* [Fix] Infer getSetupStatus result type for worker declarations

* [Improve] Drop repo name from setup log dropdown labels

* [Improve] Disambiguate setup log labels only on name collisions

* [Fix] Keep setup log dropdown labels unique when repos or commands collide

* [Improve] Keep setup log disambiguation intentionally light

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Release Roomote 1.0.0

Bump the product version to 1.0.0 and record the public 1.0 changelog.

* [Improve] Lead Roomote 1.0 notes with headline features

Reorder and reword the 1.0.0 changelog so Docker projects, multi-SCM
automations, setup-flow rework, and Code Mode appear first.

---------

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
@roomote-roomote

roomote-roomote Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

No new code issues found; 7 previously reported issues remain. See task

  • packages/sdk/src/server/lib/pull-requests/source-control-pull-request-reads.ts:2103 GitLab PR listing always sends PRIVATE-TOKEN, bypassing buildGitLabTokenHeader. OAuth deployment tokens require Authorization: Bearer, so list_pull_requests and merged-PR fact sync fail for OAuth-backed GitLab connections.
  • apps/api/src/handlers/gitea/handleComment.ts:211 Removing the deployment-token identity check means a configured Gitea deployment user whose login does not start with roomote can trigger its own @roomote comments, creating review tasks instead of ignoring bot-authored replies.
  • apps/controller/src/compute-providers/spawn-docker-worker.ts:433 Failed standby resumes stop only the worker container. Docker-project tasks also resume a privileged <worker>-docker daemon, which remains running instead of being removed by the normal cleanup path.
  • apps/web/src/app/api/source-control/gitlab/oauth/authorize/route.ts:31 The GitLab OAuth redirect URI uses R_APP_URL rather than the public callback host. Deployments that keep R_APP_URL on an internal or loopback URL and set R_PUBLIC_URL redirect users to an unreachable callback; the callback exchange repeats the same URI at callback/route.ts:49.
  • apps/web/src/components/settings/SourceControlConfigForm.tsx:181 Including providerStatus?.fields in this reset effect defeats the content-keyed dependency immediately above it. A query refetch creates a new fields array and resets values, discarding unsaved configuration edits.
  • packages/db/src/lib/instance-report.ts:156 The PR-funnel key excludes the source-control host. Same-named repositories on different self-managed instances of one provider with the same PR number collapse into one cohort entry, producing incorrect funnel counts and statuses.
  • packages/db/src/lib/instance-report.ts:354 Merge-duration fallback keys facts only by repository name and PR number. For associations without repositoryId, facts from another provider or host with the same identity can be selected, making reported time-to-merge incorrect and order-dependent.

Reviewed 690cb87

@roomote-roomote

roomote-roomote Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Fixed all 7 open review checklist items from the Roomote review summary on a new branch off develop (not on release/v1.0.0), as requested.

Shipped in #352 @ 2ab0e95

Covered:

  1. GitLab list MRs via buildGitLabTokenHeader (OAuth Bearer)
  2. Gitea deployment-token identity ignore
  3. Standby-resume failure cleans nested Docker daemon
  4. GitLab OAuth uses R_PUBLIC_URL ?? R_APP_URL
  5. Source-control form content-keyed reset deps
    6–7. Instance-report funnel key + merge-duration matching host/provider scoped

Review threads on this promote PR remain unresolved because the fixes land on develop rather than this frozen release branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants