Skip to content

Promote v0.0.2 to production - #146

Closed
mrubens wants to merge 119 commits into
mainfrom
develop
Closed

Promote v0.0.2 to production#146
mrubens wants to merge 119 commits into
mainfrom
develop

Conversation

@mrubens

@mrubens mrubens commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Promote v0.0.2

develop is ahead of main and product version 0.0.2 is not tagged yet.

  • Merge this PR with a merge commit (do not squash or rebase).
  • Merging publishes a GitHub Release for v0.0.2 and triggers the existing GHCR v* image publish (latest channel).

Changelog

0.0.2 (2026-07-10)

Patch changes

  • Seed the product version lineage so the first automated release becomes 0.0.2 above the existing v0.0.1 tag.

mrubens and others added 30 commits July 8, 2026 13:37
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: daniel-lxs <ricciodaniel98@gmail.com>
Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
…fter completion (#5)

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

* [Fix] Treat a clarification question as a valid end of turn in Slack hooks

Asking the user a question was not an accepted terminal state: after a
send_chat_reply with purpose "clarification", the stop hook blocked with
current_turn_nonterminal_reply, the harness injected up to three closeout
reminders, and the model dutifully posted redundant "Paused here — I'm
waiting on your answer" echo messages right after the question.

A clarification is a visible handoff exactly like a closeout — the turn
ends waiting on the user's answer — so the anti-silence hooks now treat
it as terminal, with the same staleness rule: non-Slack work after the
question invalidates it and a fresh reply is required before stopping.

- recordChatReplySatisfaction stamps terminal state for clarification
  replies, so staleness tracking applies to questions too.
- The stop hook accepts clarification in its terminal-satisfaction
  checks (including legacy states written by pre-deploy workers); ack
  and progress still block.
- The silence hook's terminal check mirrors the same policy so
  post-question work is stamped consistently.
- OpenCode todo bookkeeping (todowrite/todoread) no longer stales a
  terminal reply: the exemption list only knew the legacy update_plan
  name, so a post-closeout todo update forced a redundant "closed this
  turn" follow-up message.
- The send_chat_reply tool description now says a clarification ends
  the turn when the next step depends on the user's answer, and not to
  follow it with a separate "waiting on your answer" message.

Verified end-to-end against the mock Slack harness with a live planner
task: the question turn now ends cleanly (terminal state stamped, no echo
for 3+ minutes where it previously arrived within one), and the answer
round-trip completes with a single closeout.

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

* Update pinned send_chat_reply descriptions in tool-descriptions test

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#8)

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

Co-authored-by: Roomote <roomote@roocode.com>
…loy prompt (#10)

Co-authored-by: Roomote <roomote@roocode.com>
…odels (#11)

Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* Improve Microsoft Teams setup flow (#550)

* Add Teams setup redirect guidance

* Fix onboarding frame scrolling

* Make Teams reconnect message more human

* Send Teams chat images as attachments

* Require Teams bot fields during setup

* ui

* setup ui

* feat(setup): offer downloadable Teams app package in the setup flow

* feat(setup): simplify Teams bot credential entry

* feat(setup): require shared Teams app credentials

* refactor(setup): split provider setup experiences

* chore(web): remove stale comms provider import

* chore(setup): keep provider setup props local

* feat(setup): clarify Microsoft Teams setup steps

* copy

* feat(setup): refine Teams app setup flow

* test(web): mock tooltip in comms settings tests

---------

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

* [Fix] Preserve Teams image attachments when clearing thread reply footers

Keep original Bot Framework attachments when a later footer-bearing Teams
reply rewrites the previous message, and remove a stray partial Tailwind
class from provider setup URL styling.

---------

Co-authored-by: Bruno Bergher <bruno@roocode.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
…ank (#17)

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

Co-authored-by: Roomote <roomote@roocode.com>
…ttles (#21)

The first callbacks a docker worker makes (cloudJobs.dequeue / resume) go
through the public URL, and a freshly (re)started ngrok/Caddy edge can briefly
answer 5xx or without a JSON content-type. The worker failed the whole job on
the first bad response, orphaning it in 'dequeued'.

Claiming a job is idempotent server-side (FOR UPDATE SKIP LOCKED), so add the
claim mutations to the retryable-path allowlist with a longer budget
(6 attempts, ~31s of backoff). Also have the dev CLI poll the API health route
through the public edge after starting services, so local stacks don't hand
jobs to workers before the edge is serving.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…egram task entry (#22)

Testing harness (packages/communication):
- MockTelegramServer: fake Bot API (sendMessage, sendPhoto, editMessageText,
  reactions, buttons, webhooks) with /mock/state + /mock/events control plane
  and signed update dispatch into the real webhook handler. Enforces real
  Telegram limits (4096 chars, unknown chats, missing reply targets) and
  auto-computes entities so bot-command/mention detection matches production.
- TELEGRAM_API_BASE_URL env override reroutes every runtime provider
  construction to the harness (mirrors SLACK_API_BASE_URL).
- CLI runner (mock:telegram), LLM-judged eval runner (eval:telegram-scenario)
  with three scenario fixtures, and the mock-telegram-testing skill with a
  user-journey scenario catalog.

Routing confirmation (apps/api telegram handler):
- When the router is below 0.95 confidence, remapped, or picks all-repos
  while environments exist, task entry posts a compact Yes/Nope card that
  auto-starts the suggestion after ~5s; Nope edits the same message into a
  workspace picker (no timer). Router fallback shows the picker directly
  instead of silently launching in all repos.
- Pending choice lives in Redis (one-shot GETDEL claim; 64-byte-safe
  callback data; requester-only clicks; Nope re-keys state so the timer can
  never fire a rejected suggestion; superseding requests invalidate stale
  cards). Suggestion-button launches skip the card.
- New TelegramCommunicationProvider.editMessageText with HTML-to-plain
  fallback, used to finalize cards in place.

Verified with 56 API handler tests + 17 harness tests and a live end-to-end
run through the mock harness against the dev stack (card, Nope-to-picker,
Nevermind, timer no-op, zero jobs created).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
* feat: provision environments declaratively from files at startup

* fix: contain declarative dir failures and defer orphaning on invalid definitions

---------

Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
Co-authored-by: Roomote <roomote@roomote.dev>
…ilently (#32)

Messages typed on the task page while a turn is in flight were only
appended to the runtime prompt queue and never delivered until the turn
ended - unlike Slack/Teams/Telegram follow-ups, which steer. With a
request_user_input question pending this deadlocked the task entirely:
the turn never ends, so the queued message never drains.

The web sendPrompt path now passes autoSteerWhenQueued: true end to end
(web command -> sandbox-server procedure -> harness manager), so running
sends use OpenCode native mid-turn injection, falling back to
queued-prompt cancel/replay when injection fails or a question is
pending. The optimistic UI places the message in the transcript rather
than the queued-messages surface to match.

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

The request_user_input panel (and the free-text answer path in the
prompt input) were gated on taskPhase === 'waiting_for_user_input'.
A queued or steered follow-up can flip the reported phase back to
running while the harness turn is still blocked inside the question
tool - in that state the question vanished from the UI and could never
be answered, deadlocking the task.

Visibility now tracks the live pendingUserInputRequests themselves,
which the runtime clears on answer, cancel, and turn teardown. The
free-text answer routing keys off the active pending request for the
same reason.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…r JSON (#34)

When an OpenCode session errored (e.g. the provider rejected the model
with a 403), the transcript showed the entire JSON.stringify of the
error - status codes, response headers, provider metadata - as an
assistant message. Extract the human-readable provider message for the
transcript and keep the full payload in the harness log for debugging.

Seen on staging when the default Grok 4.5 model 403'd via OpenRouter:
the chat rendered a wall of cf-ray headers and stripe CSP noise instead
of "The model grok-4.5 is not available in your region."

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
…lable (#36)

* [Fix] Show initials instead of full name when avatar image is unavailable

* [Fix] Update profile avatar test for initials accessibility model

* [Fix] Fall back to initials for already-broken cached avatars

---------

Co-authored-by: Roomote <roomote@roomote.dev>
…ry refetch (#37)

The live task page reconstructed history through two different code
paths: initial load used the envelope loader (loadAcpEnvelopes, the same
builder the always-correct historical view uses), but every subsequent
refetch patched the in-memory list envelope-by-envelope through the
live-event handler, skipping any envelope whose id was already present.
Once the live state drifted - missed socket events, replacements applied
at stale positions, ordering skew - the drift persisted until the
sandbox slept, matching the corruption observed on staging (transcripts
collapsing to the prompt plus latest answer, blank renders, steered
messages out of order).

_mergeAcpHistory now rebuilds the transcript from the fetched envelopes
with the same loader, then carries over only messages the fetch does not
cover yet (optimistic sends and unpersisted live events, matched by
logical event id, clientMessageId, tool call id, or originating event
timestamp). Every refetch is a convergence point to server truth, and an
identical refetch keeps the previous references so nothing re-renders.

The new steered-task regression suite includes a drift-healing case that
fails against the previous incremental merge.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
…annel (#43)

Editing one Slack auto-respond channel re-validated every configured
channel and failed the whole save if any single one could not be
resolved by name (e.g. a channel that was archived, renamed, or turned
private). Users saw errors like "Could not resolve Slack channel
#deepstrike-roo" while editing a completely different channel.

Root cause was a lossy identity round-trip: the settings form hydrates
each row's display name via conversations.info (lookup by ID, which
still works for archived/renamed/private channels), but on save the
server re-resolved that name via conversations.list (lookup by name,
which excludes archived channels and misses private-not-member ones and
can bail on rate limits). Any row that failed re-resolution set a field
error and aborted the entire save before writing anything.

Preserve the persisted channel ID through the form round-trip and use it
as the resolution input for untouched rows. Resolving by ID short-
circuits without a Slack lookup, so a stale channel elsewhere in the list
can no longer block saving an edit to another row. The ID is dropped only
when the user actually edits that row's channel, forcing a fresh
name resolution. Mirrors how manager/coach channels already keep their
persisted IDs when not being edited.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
mrubens and others added 17 commits July 10, 2026 15:47
…worker (#119)

Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Roomote <roomote@roocode.com>
…line release (#125)

Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: Roomote <roomote@roocode.com>
Co-authored-by: Roomote <roomote@roocode.com>
…ng 15) (#130)

Co-authored-by: Roomote <roomote@roocode.com>
…artifacts in the inference image (#129)

Co-authored-by: Roomote <roomote@roocode.com>
…ilds (#137)

The gate builds its own local images for the smoke test, so serializing it
ahead of the build matrix only added its ~5 minutes to every publish. Gate
the publish job instead: builds push untagged per-arch digests, and nothing
is consumable until publish assembles the tagged manifests, so a failed gate
still prevents any image from shipping. Cuts merge-to-nightly wall clock
from ~12 to ~7 minutes.

Co-authored-by: Roomote <roomote@roocode.com>
…#139)

Self-review chat messages now say "I reviewed … on GitHub/GitLab/…" so people can tell where the review happened.

Co-authored-by: Roomote <roomote@roomote.dev>
Co-authored-by: Matt Rubens <2600+mrubens@users.noreply.github.com>
Co-authored-by: Roomote <roomote@roomote.dev>
Sandbox logs do not survive the sandbox and Modal exposes none at all,
so runtime facts worth a post-mortem keep dying with the machine. This
adds a diagnostic rail on the existing task_run_events audit table:

- a `diagnostic` run event type, recorded by workers through the
  existing sdk.taskRuns.recordEvent path (no schema change),
- a worker-side recorder that is observer-only: fire-and-forget, never
  throws into the caller, caps every string, and scrubs token-shaped
  values and credential assignments before anything leaves the process,
- a `tasks.runEvents` web tRPC reader so per-task events are queryable
  without database access.

Nothing emits through the recorder yet; the OpenCode exit certificate
lands separately on top of this rail.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@roomote-roomote

roomote-roomote Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Re-reviewed the one new commit since the last review (#147 — "Address review findings on the diagnostic rail and death certificate"). The death-certificate false-positive is fixed: deliberate reconnect/restart/teardown kills route through terminateSubprocess, which now calls markExpectedSubprocessExitIfAlive before SIGTERM (apps/worker/src/run-task/reconnectable-harness.ts:455), and certifyExit skips certification for a marked, still-alive process (.../opencode-server/start.ts:280); a process that already exited on its own (a genuine crash) is left unmarked and keeps its certificate. The markExpectedSubprocessExitIfAlive guard (exitCode !== null || killed) and the WeakSet keyed on the exact ResultPromise that start.ts holds are correct, so the crash-first-disconnect ordering the earlier review cited is preserved. Supporting changes are sound: the recorder now applies kind last so caller details can't override the classification, hash-shaped values keep an 8-char prefix instead of full redaction to preserve git SHAs/digests, memorymemoryAfterExit is renamed consistently end-to-end (no stale references), persistent stdout/stderr tail readers now capture the process's last lines for its whole lifetime rather than only during the startup URL wait, and run-events fetches newest-first-under-limit then reverses so a busy task can't push diagnostics out of the 500-event window. No new issues. Death-certificate item marked resolved; the baseline-migration item is still open on this release PR. 1 issue outstanding. See task

  • Baseline migration rename can break upgrades from v0.0.1packages/db/drizzle/meta/_journal.json (idx 0) renames the baseline 0000_stiff_lorna_dane0000_dapper_smasher and bumps its when from 1783526937997 to 1783649825042. v0.0.1 (the current main) shipped only 0000_stiff_lorna_dane, so any database that already applied it has __drizzle_migrations.created_at = 1783526937997. packages/db/src/run-migrations.ts:30 runs drizzle-orm's migrate(), which re-executes any journal entry whose when exceeds the last recorded created_at. Because the new baseline's when is larger, the migrator will re-run 0000_dapper_smasher, and its first statement is a non-idempotent CREATE TABLE "auth_accounts" (packages/db/drizzle/0000_dapper_smasher.sql:1), which fails with "relation already exists" on a populated v0.0.1 database. This is safe for fresh installs and is fine if no persistent v0.0.1 database exists yet (the changeset name seed-release-workflow suggests a deliberate baseline reset); please confirm no self-hosted or production v0.0.1 database will run this upgrade path, otherwise the migration should keep the original baseline tag/when.
  • Death certificate fires on intentional reconnect/restart, not just genuine crashesapps/worker/src/sandbox-server/lib/harnesses/opencode-server/start.ts:263 only suppressed the exit certificate when cancelSignal.aborted, so deliberate SIGTERM teardown during reconnect/restart was mis-certified as an unexpected exit and paged Sentry on every routine restart. Resolved in [Fix] Address review findings on the diagnostic rail and death certificate #147: terminateSubprocess now calls markExpectedSubprocessExitIfAlive(subprocess) before subprocess.kill('SIGTERM') (apps/worker/src/run-task/reconnectable-harness.ts:455), and certifyExit skips certification when isExpectedSubprocessExit(spawnedSubprocess) is true (apps/worker/src/sandbox-server/lib/harnesses/opencode-server/start.ts:280). Both cited paths — disconnect recovery (reconnectable-harness.ts:336) and restart (reconnectable-harness.ts:439) — route through terminateSubprocess, and the exitCode !== null || killed guard ensures an already-dead crash is left unmarked so its certificate still fires (covered by expected-exit.test.ts).

daniel-lxs and others added 4 commits July 10, 2026 17:40
…pectedly (#145)

When the OpenCode subprocess dies mid-task today, the worker silently
reconnects and every fact about the death vanishes with the sandbox.
The worker now stands witness: a bounded ring of the subprocess's last
output lines is kept while it runs, and an exit while the task is not
being cancelled records a durable diagnostic run event with the exit
code, signal, uptime, output tail, and a memory snapshot taken at the
instant of death, plus a Sentry capture for alerting. Exit code 137
versus a panic stack versus a clean exit is the difference between
"killed for memory", "OpenCode bug", and "something else" — the
distinction the last two investigations could not make.

Collection cost is constant per output line, certification runs only
on unexpected exits, and the callback is observer-only: its failures
are logged and never touch the harness lifecycle. Output lines pass
through the diagnostic recorder's secret scrubbing before persisting.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…payload kind (#134)

Co-authored-by: Roomote <roomote@roocode.com>
…s, retention, and classification constraints (#136)

Co-authored-by: Roomote <roomote@roocode.com>
…icate (#147)

* [Fix] Address review findings on the diagnostic rail and death certificate

Review follow-up for #140 and #145, which merged before their bot
reviews were read:

- Deliberate harness terminations (reconnect, restart, teardown) are no
  longer certified as unexpected exits: ReconnectableHarness marks the
  subprocess before killing it, and certification skips marked exits,
  which also silences the Sentry alert that fired on every routine
  reconnect.
- The death tail no longer depends on the startup URL wait leaving its
  line listeners attached: dedicated readers feed the ring for the
  whole subprocess lifetime.
- The run-events reader returns the newest events under its limit
  (descending query, reversed to chronological), so a busy task cannot
  push the diagnostics it exists to surface out of the window.
- A caller-supplied details.kind can no longer override the recorder's
  classification.
- Hash-shaped values (git SHAs, digests) keep an 8-character prefix
  when redacted, preserving their evidentiary value without exposing
  credential-length material.
- The memory reading is named and documented as what it is: the sandbox
  observed just after the exit, by the surviving worker process — the
  exit code/signal remains the authoritative OOM signal.

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

* Mark expected exits only while the subprocess is alive

A crash surfaces as a disconnect first, and the disconnect cleanup then
kills the already-dead process; marking it there raced the exit
certification and could suppress the certificate for exactly the crash
the rail exists to record. The mark now applies only to a process that
is still alive at kill time, so deliberate teardowns stay quiet and
self-inflicted deaths stay certified.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@roomote-roomote

Copy link
Copy Markdown
Contributor

Closing: this promote PR has head develop, so it isn't frozen — #145 (merged after the 0.0.2 version bump) would ship inside v0.0.2 without appearing in its changelog. Once #150 merges, the Release workflow will cut a frozen release/v0.0.2 branch pinned at eaee303 (the #143 version commit) and open a new promote PR from it; #145 then rolls into 0.0.3 as normal. Note: if anything else merges to develop before #150, the old workflow will re-open a develop→main promote PR — just close it again or hold merges until #150 lands.

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.

4 participants