Skip to content

fix(linearbot): Prevent Linearbot from marking issues as "done" when it just put a PR up - #959

Merged
goksu merged 2 commits into
paradigmxyz:mainfrom
0xSplits:centaur/prevent-review-done-1783467238
Jul 16, 2026
Merged

fix(linearbot): Prevent Linearbot from marking issues as "done" when it just put a PR up#959
goksu merged 2 commits into
paradigmxyz:mainfrom
0xSplits:centaur/prevent-review-done-1783467238

Conversation

@0xdiid

@0xdiid 0xdiid commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

When just a PR is up, Linear will typically handle moving the PR from "in review" to "done" when the PR is reviewed and merged. The Centaur bot was marking things as done as soon as it got put in review, which means that it often got lost as a "completed" task when the PR wasn't actually merged. This should fix that by not moving past review unless the bot explicitly does so itself.

@0xdiid
0xdiid marked this pull request as ready for review July 8, 2026 02:18
@goksu
goksu merged commit 71b6e96 into paradigmxyz:main Jul 16, 2026
16 of 25 checks passed
czxtm added a commit to darkmatter/centaur that referenced this pull request Jul 24, 2026
* fix: preserve postgres proxy credentials on resume (paradigmxyz#1099)

* feat: Make the chat agent platform-aware (Slack/Discord/Linear/Github) (paradigmxyz#704)

* fix(linearbot): don't start an assignment turn when the bot assigns itself (paradigmxyz#945)

* fix(linearbot): Prevent Linearbot from marking issues as "done" when it just put a PR up (paradigmxyz#959)

Co-authored-by: Gerard <289036782+splits-gerard@users.noreply.github.com>

* fix(harness-server): downscale oversized image attachments before the model (paradigmxyz#1077)

* fix: omit Slack client secret for PKCE login (paradigmxyz#1100)

* fix: use confidential Slack OIDC login (paradigmxyz#1101)

* fix(console): separate chat discovery from access (paradigmxyz#1102)

Keep the sidebar and default chat selection scoped to chats owned by
the signed-in user.

Allow users to continue public and explicitly shared chats when the
existing access policy permits them.

* feat: add LLM message override strategy (paradigmxyz#1096)

* feat: add LLM message override strategy

* fix: log LLM message override failures

* fix: classify overrides from Slack prompt text

* Revert "fix: classify overrides from Slack prompt text"

This reverts commit 0f553e6.

* chore: bump Helm chart version

* fix: avoid logging override response bodies

* fix: map OpenAI key for Slackbot overrides

* fix: make Slackbot OpenAI key optional

* fix: align LLM sonnet model alias

* fix: support explicit sonnet 5 override

* fix: keep Slack override retries idempotent

* fix: log successful override strategy responses

* fix: reduce slack override strategy log noise (paradigmxyz#1104)

* fix: reduce slack override strategy log noise

* refactor: accept full logger for slack override strategy

* feat: render attached images in console chat (paradigmxyz#1105)

* fix(console): clean up chat detail rows (paradigmxyz#1108)

* feat: add workflow-scoped principals (paradigmxyz#1107)

* feat: add workflow-scoped principals

* feat: add workflow host sandbox helm value

* chore: bump centaur chart

* fix: default workflow host sandbox chart value off

* fix: fail closed for workflow principals

* fix: label workflow principals by kind

* fix: simplify workflow principal labels

* fix(console): speed up chat loading (paradigmxyz#1109)

* feat(slackbotv2): dispatch Block Kit actions (paradigmxyz#1110)

Closes paradigmxyz#1070

* fix: update vulnerable console sanitizers (paradigmxyz#1118)

* fix: attach Laminar traces to thread roots (paradigmxyz#1106)

* fix: attach Laminar traces to thread roots

* fix: use thread root for Codex OTLP fallback

* ci: run console gem audit on main (paradigmxyz#1119)

* feat(sandbox): install Google Cloud CLI (paradigmxyz#1124)

* fix: install workflow analytics dependencies (paradigmxyz#1125)

* fix: install workflow analytics dependencies

* fix: avoid apt packaging uninstall in workflow images

* chore: bump iron-proxy image (paradigmxyz#1126)

* fix(session-runtime): avoid redacting sk inside words (paradigmxyz#1132)

Co-authored-by: Osraka <98612432+Osraka@users.noreply.github.com>

* feat(console): add system theme option (paradigmxyz#1131)

* fix: clarify slackbot claude model selection (paradigmxyz#1133)

* fix: clarify slackbot claude model selection

* fix: support claude opus 4.7 strategy selection

* fix: increase message override timeout

---------

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>

* fix(console): show current theme in selector (paradigmxyz#1134)

* feat: add native Nanocodex harness (paradigmxyz#1130)

* feat: add native nanocodex harness

* feat(harness): expose nanocodex child agents

* test(slackbot): preserve default around nanocodex opt-in

* fix(ci): avoid no-op chart change

* fix: harden native nanocodex integration

* fix(slackbotv2): preserve pinned thread harness (paradigmxyz#1135)

* Make Nanocodex commentary and subagents explicit (paradigmxyz#1136)

* Render Nanocodex commentary as Slack progress (paradigmxyz#1137)

* fix: surface Granola MCP tool errors (paradigmxyz#1121)

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>

* feat: support gsuite full-text Drive search (paradigmxyz#1142)

* fix: use X search pagination token (paradigmxyz#1143)

* feat: expose quote and retweet lookups (paradigmxyz#1150)

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>

* feat: add per-proxy labels (paradigmxyz#1141)

* feat: add per-proxy labels

* fix: satisfy rust proxy label clippy

* chore: revert schema dump changes

* chore: keep proxy labels in schema dump

* fix: harden proxy label sync paths

* fix: preserve proxy labels across reuse

* refactor: simplify proxy config snapshots

* refactor: remove obsolete proxy config paths

* Fix Twitter API response handling (paradigmxyz#1157)

fix twitter API response handling

* fix: support proxy labels in pg dsn form (paradigmxyz#1158)

* fix: append mounted overlay system prompts (paradigmxyz#1161)

* fix: append mounted overlay system prompts

* fix: make sandbox commit hook portable

* Increase Codex project instruction limit to 128 KiB (paradigmxyz#1162)

fix: increase Codex project instruction limit

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>

* fix: implement Python workflow event waits (paradigmxyz#1163)

Bridge WorkflowContext.wait_for_event through the Python host to Absurd's durable event primitive and support paired event emission.

Closes paradigmxyz#990

* fix: label observable iron-proxy resources (paradigmxyz#1164)

Closes paradigmxyz#1046

Closes paradigmxyz#1024

Co-authored-by: Taren Marsaw <tarenitup@Tarens-iMac.local>
Co-authored-by: blok <37964165+blokboy@users.noreply.github.com>

* feat: support client credentials broker refresh (paradigmxyz#1165)

* chore: configure dependabot updates (paradigmxyz#1166)

* chore(deps): bump the ruby-dependencies group in /services/console with 6 updates (paradigmxyz#1168)

chore(deps): bump the ruby-dependencies group

Bumps the ruby-dependencies group in /services/console with 6 updates:

| Package | From | To |
| --- | --- | --- |
| [tailwindcss-rails](https://github.com/rails/tailwindcss-rails) | `4.4.0` | `4.6.0` |
| [lograge](https://github.com/roidrage/lograge) | `0.14.0` | `0.15.0` |
| [solid_queue](https://github.com/rails/solid_queue) | `1.4.0` | `1.5.0` |
| [solid_cable](https://github.com/rails/solid_cable) | `4.0.0` | `4.0.2` |
| [thruster](https://github.com/basecamp/thruster) | `0.1.21` | `0.1.23` |
| [selenium-webdriver](https://github.com/SeleniumHQ/selenium) | `4.44.0` | `4.46.0` |


Updates `tailwindcss-rails` from 4.4.0 to 4.6.0
- [Release notes](https://github.com/rails/tailwindcss-rails/releases)
- [Changelog](https://github.com/rails/tailwindcss-rails/blob/main/CHANGELOG.md)
- [Commits](rails/tailwindcss-rails@v4.4.0...v4.6.0)

Updates `lograge` from 0.14.0 to 0.15.0
- [Changelog](https://github.com/roidrage/lograge/blob/master/CHANGELOG.md)
- [Commits](roidrage/lograge@v0.14.0...v0.15.0)

Updates `solid_queue` from 1.4.0 to 1.5.0
- [Release notes](https://github.com/rails/solid_queue/releases)
- [Commits](rails/solid_queue@v1.4.0...v1.5.0)

Updates `solid_cable` from 4.0.0 to 4.0.2
- [Release notes](https://github.com/rails/solid_cable/releases)
- [Commits](rails/solid_cable@v4.0.0...v4.0.2)

Updates `thruster` from 0.1.21 to 0.1.23
- [Changelog](https://github.com/basecamp/thruster/blob/main/CHANGELOG.md)
- [Commits](basecamp/thruster@v0.1.21...v0.1.23)

Updates `selenium-webdriver` from 4.44.0 to 4.46.0
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES)
- [Commits](SeleniumHQ/selenium@selenium-4.44.0...selenium-4.46.0)

---
updated-dependencies:
- dependency-name: tailwindcss-rails
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
- dependency-name: lograge
  dependency-version: 0.15.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
- dependency-name: solid_queue
  dependency-version: 1.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
- dependency-name: solid_cable
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ruby-dependencies
- dependency-name: thruster
  dependency-version: 0.1.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: ruby-dependencies
- dependency-name: selenium-webdriver
  dependency-version: 4.46.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: ruby-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump the api-rs-dependencies group in /services/api-rs with 25 updates (paradigmxyz#1170)

* chore(deps): bump the api-rs-dependencies group

Bumps the api-rs-dependencies group in /services/api-rs with 25 updates:

| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.102` | `1.0.104` |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.89` | `0.1.91` |
| [aws-smithy-types](https://github.com/smithy-lang/smithy-rs) | `1.5.0` | `1.6.1` |
| [bytes](https://github.com/tokio-rs/bytes) | `1.11.1` | `1.12.1` |
| [clap](https://github.com/clap-rs/clap) | `4.6.1` | `4.6.4` |
| [cron](https://github.com/zslayton/cron) | `0.16.0` | `0.17.0` |
| [crossterm](https://github.com/crossterm-rs/crossterm) | `0.28.1` | `0.29.0` |
| [base64](https://github.com/marshallpierce/rust-base64) | `0.22.1` | `0.23.0` |
| [hmac](https://github.com/RustCrypto/MACs) | `0.12.1` | `0.13.0` |
| [jiff](https://github.com/BurntSushi/jiff) | `0.2.28` | `0.2.34` |
| [k8s-openapi](https://github.com/Arnavion/k8s-openapi) | `0.27.1` | `0.28.0` |
| [kube](https://github.com/kube-rs/kube) | `3.1.0` | `4.2.0` |
| [serde](https://github.com/serde-rs/serde) | `1.0.228` | `1.0.229` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.150` | `1.0.151` |
| [sha2](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.11.0` |
| [ratatui](https://github.com/ratatui/ratatui) | `0.29.0` | `0.30.2` |
| [rustls](https://github.com/rustls/rustls) | `0.23.40` | `0.23.42` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.18` | `2.0.19` |
| [time](https://github.com/time-rs/time) | `0.3.47` | `0.3.54` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.52.3` | `1.53.1` |
| [tokio-util](https://github.com/tokio-rs/tokio) | `0.7.18` | `0.7.19` |
| [toml](https://github.com/toml-rs/toml) | `0.8.23` | `1.1.3+spec-1.1.0` |
| [tower-http](https://github.com/tower-rs/tower-http) | `0.6.11` | `0.7.0` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.3` | `1.24.0` |
| [futures](https://github.com/rust-lang/futures-rs) | `0.3.32` | `0.3.33` |


Updates `anyhow` from 1.0.102 to 1.0.104
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](dtolnay/anyhow@1.0.102...1.0.104)

Updates `async-trait` from 0.1.89 to 0.1.91
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](dtolnay/async-trait@0.1.89...0.1.91)

Updates `aws-smithy-types` from 1.5.0 to 1.6.1
- [Release notes](https://github.com/smithy-lang/smithy-rs/releases)
- [Changelog](https://github.com/smithy-lang/smithy-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/smithy-lang/smithy-rs/commits)

Updates `bytes` from 1.11.1 to 1.12.1
- [Release notes](https://github.com/tokio-rs/bytes/releases)
- [Changelog](https://github.com/tokio-rs/bytes/blob/master/CHANGELOG.md)
- [Commits](tokio-rs/bytes@v1.11.1...v1.12.1)

Updates `clap` from 4.6.1 to 4.6.4
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](clap-rs/clap@clap_complete-v4.6.1...clap_complete-v4.6.4)

Updates `cron` from 0.16.0 to 0.17.0
- [Release notes](https://github.com/zslayton/cron/releases)
- [Commits](https://github.com/zslayton/cron/commits)

Updates `crossterm` from 0.28.1 to 0.29.0
- [Release notes](https://github.com/crossterm-rs/crossterm/releases)
- [Changelog](https://github.com/crossterm-rs/crossterm/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crossterm-rs/crossterm/commits/0.29)

Updates `base64` from 0.22.1 to 0.23.0
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](marshallpierce/rust-base64@v0.22.1...v0.23.0)

Updates `hmac` from 0.12.1 to 0.13.0
- [Commits](RustCrypto/MACs@hmac-v0.12.1...hmac-v0.13.0)

Updates `jiff` from 0.2.28 to 0.2.34
- [Release notes](https://github.com/BurntSushi/jiff/releases)
- [Changelog](https://github.com/BurntSushi/jiff/blob/master/CHANGELOG.md)
- [Commits](BurntSushi/jiff@jiff-static-0.2.28...jiff-static-0.2.34)

Updates `k8s-openapi` from 0.27.1 to 0.28.0
- [Release notes](https://github.com/Arnavion/k8s-openapi/releases)
- [Changelog](https://github.com/Arnavion/k8s-openapi/blob/master/CHANGELOG.md)
- [Commits](Arnavion/k8s-openapi@v0.27.1...v0.28.0)

Updates `kube` from 3.1.0 to 4.2.0
- [Release notes](https://github.com/kube-rs/kube/releases)
- [Changelog](https://github.com/kube-rs/kube/blob/main/CHANGELOG.md)
- [Commits](kube-rs/kube@3.1.0...4.2.0)

Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](serde-rs/serde@v1.0.228...v1.0.229)

Updates `serde_json` from 1.0.150 to 1.0.151
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](serde-rs/json@v1.0.150...v1.0.151)

Updates `sha2` from 0.10.9 to 0.11.0
- [Commits](RustCrypto/hashes@sha2-v0.10.9...sha2-v0.11.0)

Updates `ratatui` from 0.29.0 to 0.30.2
- [Release notes](https://github.com/ratatui/ratatui/releases)
- [Changelog](https://github.com/ratatui/ratatui/blob/main/CHANGELOG.md)
- [Commits](ratatui/ratatui@v0.29.0...ratatui-v0.30.2)

Updates `rustls` from 0.23.40 to 0.23.42
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](rustls/rustls@v/0.23.40...v/0.23.42)

Updates `thiserror` from 2.0.18 to 2.0.19
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](dtolnay/thiserror@2.0.18...2.0.19)

Updates `time` from 0.3.47 to 0.3.54
- [Release notes](https://github.com/time-rs/time/releases)
- [Changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md)
- [Commits](time-rs/time@v0.3.47...v0.3.54)

Updates `tokio` from 1.52.3 to 1.53.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-1.52.3...tokio-1.53.1)

Updates `tokio-util` from 0.7.18 to 0.7.19
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-util-0.7.18...tokio-util-0.7.19)

Updates `toml` from 0.8.23 to 1.1.3+spec-1.1.0
- [Commits](toml-rs/toml@toml-v0.8.23...toml-v1.1.3)

Updates `tower-http` from 0.6.11 to 0.7.0
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](tower-rs/tower-http@tower-http-0.6.11...tower-http-0.7.0)

Updates `uuid` from 1.23.3 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](uuid-rs/uuid@v1.23.3...v1.24.0)

Updates `futures` from 0.3.32 to 0.3.33
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md)
- [Commits](rust-lang/futures-rs@0.3.32...0.3.33)

---
updated-dependencies:
- dependency-name: anyhow
  dependency-version: 1.0.104
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: async-trait
  dependency-version: 0.1.91
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: aws-smithy-types
  dependency-version: 1.6.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: bytes
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: clap
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: cron
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: crossterm
  dependency-version: 0.29.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: base64
  dependency-version: 0.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: hmac
  dependency-version: 0.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: jiff
  dependency-version: 0.2.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: k8s-openapi
  dependency-version: 0.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: kube
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: api-rs-dependencies
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: ratatui
  dependency-version: 0.30.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: rustls
  dependency-version: 0.23.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: thiserror
  dependency-version: 2.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: time
  dependency-version: 0.3.54
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: tokio
  dependency-version: 1.53.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: tokio-util
  dependency-version: 0.7.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
- dependency-name: toml
  dependency-version: 1.1.3+spec-1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: api-rs-dependencies
- dependency-name: tower-http
  dependency-version: 0.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-rs-dependencies
- dependency-name: futures
  dependency-version: 0.3.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-rs-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: adapt api-rs dependency updates

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>

* chore(deps): bump the harness-server-dependencies group in /crates/harness-server with 9 updates (paradigmxyz#1169)

* chore(deps): bump the harness-server-dependencies group

Bumps the harness-server-dependencies group in /crates/harness-server with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [allocative](https://github.com/facebookexperimental/allocative) | `0.3.4` | `0.3.6` |
| [base64](https://github.com/marshallpierce/rust-base64) | `0.22.1` | `0.23.0` |
| [clap](https://github.com/clap-rs/clap) | `4.6.1` | `4.6.4` |
| [serde](https://github.com/serde-rs/serde) | `1.0.228` | `1.0.229` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.150` | `1.0.151` |
| [sha2](https://github.com/RustCrypto/hashes) | `0.10.9` | `0.11.0` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.18` | `2.0.19` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.52.3` | `1.53.1` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.23.2` | `1.24.0` |


Updates `allocative` from 0.3.4 to 0.3.6
- [Release notes](https://github.com/facebookexperimental/allocative/releases)
- [Commits](https://github.com/facebookexperimental/allocative/commits)

Updates `base64` from 0.22.1 to 0.23.0
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](marshallpierce/rust-base64@v0.22.1...v0.23.0)

Updates `clap` from 4.6.1 to 4.6.4
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](clap-rs/clap@clap_complete-v4.6.1...clap_complete-v4.6.4)

Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](serde-rs/serde@v1.0.228...v1.0.229)

Updates `serde_json` from 1.0.150 to 1.0.151
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](serde-rs/json@v1.0.150...v1.0.151)

Updates `sha2` from 0.10.9 to 0.11.0
- [Commits](RustCrypto/hashes@sha2-v0.10.9...sha2-v0.11.0)

Updates `thiserror` from 2.0.18 to 2.0.19
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](dtolnay/thiserror@2.0.18...2.0.19)

Updates `tokio` from 1.52.3 to 1.53.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-1.52.3...tokio-1.53.1)

Updates `uuid` from 1.23.2 to 1.24.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](uuid-rs/uuid@v1.23.2...v1.24.0)

---
updated-dependencies:
- dependency-name: allocative
  dependency-version: 0.3.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: harness-server-dependencies
- dependency-name: base64
  dependency-version: 0.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: harness-server-dependencies
- dependency-name: clap
  dependency-version: 4.6.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: harness-server-dependencies
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: harness-server-dependencies
- dependency-name: serde_json
  dependency-version: 1.0.151
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: harness-server-dependencies
- dependency-name: sha2
  dependency-version: 0.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: harness-server-dependencies
- dependency-name: thiserror
  dependency-version: 2.0.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: harness-server-dependencies
- dependency-name: tokio
  dependency-version: 1.53.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: harness-server-dependencies
- dependency-name: uuid
  dependency-version: 1.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: harness-server-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: keep harness allocative compatible

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>

* chore(deps): bump the docs-dependencies group in /docs with 5 updates (paradigmxyz#1167)

* chore(deps): bump the docs-dependencies group in /docs with 5 updates

Bumps the docs-dependencies group in /docs with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [react](https://github.com/react/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.8` |
| [react-dom](https://github.com/react/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.8` |
| [waku](https://github.com/wakujs/waku/tree/HEAD/packages/waku) | `1.0.0-alpha.6` | `1.0.0-beta.7` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.9.3` | `7.0.2` |
| [wrangler](https://github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler) | `4.102.0` | `4.114.0` |


Updates `react` from 19.2.6 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react)

Updates `react-dom` from 19.2.6 to 19.2.8
- [Release notes](https://github.com/react/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/react/react/commits/v19.2.8/packages/react-dom)

Updates `waku` from 1.0.0-alpha.6 to 1.0.0-beta.7
- [Release notes](https://github.com/wakujs/waku/releases)
- [Changelog](https://github.com/wakujs/waku/blob/main/CHANGELOG.md)
- [Commits](https://github.com/wakujs/waku/commits/v1.0.0-beta.7/packages/waku)

Updates `typescript` from 5.9.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

Updates `wrangler` from 4.102.0 to 4.114.0
- [Release notes](https://github.com/cloudflare/workers-sdk/releases)
- [Commits](https://github.com/cloudflare/workers-sdk/commits/wrangler@4.114.0/packages/wrangler)

---
updated-dependencies:
- dependency-name: react
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docs-dependencies
- dependency-name: react-dom
  dependency-version: 19.2.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docs-dependencies
- dependency-name: waku
  dependency-version: 1.0.0-beta.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: docs-dependencies
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: docs-dependencies
- dependency-name: wrangler
  dependency-version: 4.114.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: docs-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: keep docs waku version compatible

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>

* fix: preserve applied migration versions

Keep the product branch's SQLx checksums stable and append the incoming OMP and ownership schema changes after version 47.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: zhygis <5236121+Zygimantass@users.noreply.github.com>
Co-authored-by: diid <123841842+0xdiid@users.noreply.github.com>
Co-authored-by: Gerard <289036782+splits-gerard@users.noreply.github.com>
Co-authored-by: Akshaan Kakar <akshaan.crackers@gmail.com>
Co-authored-by: Goksu Toprak <19259594+goksu@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>
Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com>
Co-authored-by: Osraka <98612432+Osraka@users.noreply.github.com>
Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
Co-authored-by: Perry Dime <svc_ai@paradigm.xyz>
Co-authored-by: Taren Marsaw <tarenitup@Tarens-iMac.local>
Co-authored-by: blok <37964165+blokboy@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: cooper <cooper@darkmatter.io>
0xdiid added a commit to 0xSplits/centaur that referenced this pull request Aug 1, 2026
* docs: add vmetrics deployment prompt guidance (#839)

* feat: persist session titles (#837)

Generate a short GPT-backed title when the first user message is appended.

Store titles on sessions and expose them in session context responses.

* Remove legacy agent API leftovers (#836)

Closes paradigmxyz/centaur#640

Co-authored-by: Amp <amp@ampcode.com>

* feat: auto-reload repo-cache tools in sandboxes (#840)

* [codex] default sandbox lifecycle cleanup (#739)

* fix: default sandbox timeout policy

* fix: rely on sandbox max lifetime reaping

* docs: clarify sandbox lifecycle timers

* fix: prune stale warm sandboxes before replenishing

* fix: prune stale warm sandboxes across workloads

* fix: preserve idle pause deadlines after restart

* refactor: simplify idle cleanup candidate query

* feat: add live activity summaries

* feat: add sandbox capacity manager (#812)

* fix: stop syncing tool secrets to infra role (#845)

* fix: gate activity summary status rendering (#846)

* Guide crypto tool usage away from bad raw endpoints (#847)

Co-authored-by: Centaur AI <ai@centaur.local>

* fix: handle attachment refs in sandbox prompt

* fix: lease session stdout ownership (#851)

* feat(chart): add console.slackOauth flag for Slack console sign-in (#862)

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

* fix: restrict non-observability sandbox egress (#858)

* fix: restrict non-observability sandbox egress

* fix: whitelist observable cluster egress explicitly

* fix: allow in-cluster database egress

* fix: tighten restricted sandbox egress review gaps

* fix: use configured control plane network peer

* fix: keep restricted blocklist observability scoped

* fix: allow direct victoria observability egress

* fix: configure sandbox observability egress explicitly

* fix: derive observability egress from endpoints

* fix: avoid hardcoded observability endpoints

* fix: allow sandbox egress to api pods

* fix: label observable sandboxes for egress (#864)

* fix: improve activity summary quality (#850)

* fix: steer activity summaries to current step, not session goal (#870)

Replayed the summary pipeline over five real threads (stg + prd) and
found the deployed prompt produces one vague goal-restating status per
thread: it steers the model toward the session goal, and the specific
summaries it does write usually land at 46-52 characters, where
sanitize_summary silently discards them (19 of 35 attempts).

Two prompt changes, validated by replaying the same threads:

- Describe the current step or latest finding instead of the overall
  session goal, with concrete example phrasings.
- Target 40 characters and state the 45-character hard limit so the
  model cuts words instead of overflowing.

Published summaries went from 14/35 attempts to 27/35, too-long
discards from 19 to 3, and the output narrates actual progress
("I found forge-std is vendored, not submodule", "I'm blocked on
tempo-obs metrics 502") instead of repeating "I'm checking X".

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

* [codex] Add MCP tool host sandboxes (#841)

* feat: add MCP tool host sandboxes

* chore: bump chart version

* fix: address MCP tool-host review findings

- Always validate the requested method against the parsed client method
  list; previously tools with zero public methods (methods == [help])
  skipped validation and shipped unknown methods to the sandbox
- Keep the timed_out result when the sandbox-id lookup fails instead of
  masking the timeout with a store error
- Serialize register_mcp_tool_host_principal under the same per-principal
  lock as run_tool_host_call so concurrent registrations cannot interleave
  with session setup, and evict idle lock entries after each call
- Cache the discovered tool catalog for 10s so bursts of MCP requests do
  not redo directory scans and metadata parsing; drop the redundant
  client.py re-read in the help path
- Extract tool_host_session_metadata and reuse tool_host_thread_key
  instead of duplicating both constructions
- Derive SandboxBootMode inside ensure_session_sandbox from the thread
  key and iron-control principal instead of threading it as a parameter

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

* feat: improve MCP tool errors and method discoverability

- tools/list and method=help now expose full method signatures parsed
  from the tool's client.py (e.g. search_tweets(query, limit=10)) so
  agents pass correct keyword arguments instead of guessing
- CALL_RUNNER binds keyword arguments against the target signature
  before invoking and returns a short 'invalid arguments for
  method(signature): ...' error instead of a TypeError traceback
- Failed tool calls reduce Python tracebacks to the final exception
  message and append a hint to call method=help for usage

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

* [codex] Add console MCP OAuth JWT auth for tool hosts (#842)

* feat: add console MCP OAuth JWT auth

* fix: revoke MCP OAuth tokens for inactive users

* fix: require approval for MCP OAuth clients

* fix: harden MCP OAuth issuer and loopback checks

* fix: address MCP OAuth review findings

- Fail closed in resolve_requested_resource: reject authorize requests
  when no canonical MCP resource URL is configured instead of minting
  tokens bound to caller-supplied audiences
- Validate JWT iat is not unreasonably in the future per RFC 0004
- Add missing apiRs.mcpPublicUrl / slackbotv2.mcpPublicUrl entries to
  values.schema.json
- Share header_value between routes.rs and mcp.rs instead of duplicating
- Extract HashedTokenLookup concern for the OAuth code/refresh-token
  models' SHA-256 hash-and-lookup pattern
- Cache static env configuration (signing secret, public URLs) in
  OnceLock instead of re-reading per request

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

* chore: bump chart version

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

---------

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

---------

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

* fix: avoid api attachment downloads from sandbox (#859)

* feat: add console threads view (#843)

* feat: add console threads view

* fix: stabilize console threads ci

* fix: center console thread content

* fix: make console threads read-only

* fix: scope console thread direct-selection to the owner (#854)

* fix: harden console markdown rendering and thread-title metadata (#855)

* fix: remove passwordless ?auth= console sign-in backdoor (#857)

* fix: transcript ordering, sidebar query scope, and session DB fallback (#856)

* fix: relax console Slack thread scope to team-when-present (#861)

* feat(slackbotv2): link first Slack message to Console session (#860)

* feat: console threads split view and thinking traces (#863)

* feat: always show the model on Console session links and thread header (#865)

* style: show model before harness and uppercase model names (#867)

* feat: console chats naming, empty state, and chat-not-found 404 (#866)

* chore: bump chart version to 0.1.86

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

* feat: hand off in-flight executions on shutdown (#872)

* feat: hand off in-flight executions on shutdown

api-rs runs as PID 1 in its container and installed no SIGTERM handler,
so Kubernetes pod terminations never reached the graceful shutdown
path: every rollout burned the full termination grace period and ended
in SIGKILL, killing stdout pumps mid-turn. The orphaned executions'
stdout-owner leases then lingered until their 45s TTL expired, and
nothing before the next deploy re-scanned to adopt them.

Changes:

- Handle SIGTERM (alongside Ctrl-C) as a shutdown signal.
- On shutdown, wait up to --shutdown-execution-drain-timeout-secs
  (default 20, under the chart's 35s grace period) for this process's
  in-flight executions to finish naturally — pumps keep running while
  we wait — then release the remaining stdout-owner leases in one
  statement and record a session.stdout_owner_released event per
  execution. A peer control plane's adoption scan can then claim the
  executions immediately instead of waiting out the lease TTL; turn
  output produced after the release is recovered from the sandbox
  backend's recorded output during adoption.

Verified end to end: the built server exits cleanly on SIGTERM and
logs the handoff, where main hangs until SIGKILL.

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

* fix: address review findings on the shutdown handoff

- Fence new stdout-owner claims once the handoff begins (new
  SessionRuntimeError::ShuttingDown, mapped to 503): an execution
  accepted mid-drain would otherwise claim a lease that outlives the
  process and strand the turn until the lease TTL expires.
- Clamp the drain deadline against Instant overflow, bound the flag to
  0..=600 seconds, and put a 5s timeout on the handoff's count/release
  queries so a hung database cannot push the release past SIGKILL
  silently.
- Pin the release's owner scoping with a bystander lease in the sqlx
  test, assert the drain-wait test's completer actually terminalized
  the execution, and cover the claim fence.

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

---------

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

* fix: retry workflow host database connections (#874)

* fix: retry workflow host database connections

* fix: hardcode workflow host database retry settings

* feat: periodically re-scan for orphaned executions (#871)

* feat: periodically re-scan for orphaned executions

The orphaned-execution adoption scan ran once at process startup, and
its lease-deferral path retried only 'on next startup' — i.e. at the
next deploy. That misses the common rollout race: the new pod's startup
scan runs while the old pod is still alive and pumping (stdout-owner
lease active), then the old pod hits its termination grace period
mid-turn. The execution is orphaned seconds after the only scan that
could have rescued it, and the finished answer sits unread in the
sandbox pod logs until the next deploy. This exact sequence lost a
completed turn in stg (thread orphaned 25s after the startup scan).

Changes:

- Re-run the adoption scan on an interval (default 60s,
  --session-execution-adoption-interval-secs / 0 disables). The
  stdout-owner lease (45s TTL, renewed every 10s) already makes
  adoption safe against live owners: scans defer while the lease is
  active and claim it atomically once it expires.
- Skip queued executions younger than a 120s grace window instead of
  failing them. Executions are queued only for the instant between
  create_execution and the running transition, but a periodic scan (or
  a startup scan racing another pod's live execute_session during
  rollout overlap) could observe that window and kill a healthy row.
- Record the session.execution_adoption_deferred event and info log
  only on the first deferral per execution, so a long turn owned by a
  live peer does not accumulate one event per tick.

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

* fix: address review findings on the periodic adoption scan

- Check the stdout-owner lease before touching the session row or the
  sandbox backend: executions owned by this process are skipped
  silently (no spurious adoption-deferred events on healthy turns, no
  per-tick kube status calls), and live peers' executions defer without
  any backend probes. The conditional claim remains the sole authority
  on ownership.
- Restrict the queued-row grace window to periodic scans. A one-shot
  scan (interval 0) has no later tick to revisit skipped rows, so it
  fails queued orphans immediately as before — otherwise a young queued
  orphan could wedge its thread until the next deploy.
- Keep deferral-dedup entries across transient per-execution scan
  errors so kube/DB flaps do not re-emit the deferred event.
- Make the lease-expiry test deterministic (force expiry in SQL instead
  of racing a 200ms TTL), cover the self-owned-execution skip and the
  spawned periodic loop, and assert the backdate helper touches a row.

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

* feat: default the adoption re-scan interval to 15s

The interval bounds how long a turn handed off by a terminating pod
stays frozen before a peer adopts it: leases released at
SIGTERM+drain (~20s) wait for the next tick, so 60s meant a worst case
of ~80s of stalled thread during every rollout. Since the scan skips
lease-held executions before any session or sandbox reads, a
steady-state tick is a single SELECT over the handful of active
executions — scanning at 15s is negligible load and cuts the worst
case to ~35s. Crash recovery stays bounded by the 45s lease TTL either
way.

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

---------

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

* fix: grant console Slack thread visibility to Slack SSO identities (#875)

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

* feat: restrict console Control and Data Sync sections to admins (#876)

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

* fix: expose full Gmail message content (#877)

* fix: support path-backed Drive uploads (#878)

* feat: search OAuth Google Docs in company context (#881)

feat: search oauth google docs in company context

* feat: add company context lookup metrics (#880)

* fix: restore Slack DM context visibility (#882)

* fix(console): thread rendering — markdown tables, generated titles, thinking traces (#879)

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

* Add interview prep tool (#886)

Co-authored-by: Centaur AI <ai@centaur.local>

* Revert interview prep tool from core (#888)

Revert "Add interview prep tool (#886)"

This reverts commit 35ce829bc39c19e332d05d825c978080b8077aad.

* feat(console): remove SSO approval requirement — users land on the console after login (#885)

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

* fix(slackbotv2): rename console link to "Open chat in Console" (#889)

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

* feat: gate sandbox API access by capability (#884)

* feat: gate sandbox API access by capability

* fix: preserve sandbox API ingress during rollout

* chore: bump chart version

* fix: harden sandbox API capability rollout

* fix: default session API capability to enabled

* fix: keep session API capability nullable

* fix: narrow sandbox API rollout compatibility

* fix: omit API label when disabled

* fix: satisfy sandbox API label clippy

* fix: remove sandbox capability stamp label

* feat(console): command/tool traces in transcripts with activity-summary previews (#890)

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

* fix: capture Slack app message content and unfreeze busy-channel ETL sync (#887)

* fix: capture Slack app message content and unfreeze busy-channel ETL sync

Two coupled Slack ETL defects, both verified on a live deployment:

1) Content loss: SlackEtlClient._serialize_message allowlists keys and
   dropped 'attachments'/'blocks' — bot integrations (e.g. the GitHub
   app) post with empty top-level text and all content in legacy
   attachments, so their messages were stored content-free (915/916 rows
   in one live channel), invisible to the FTS index over text and to
   company_context_documents. Pass both keys through into raw_payload
   and synthesize text from attachment fallback (else
   pretext/title/text) only when top-level text is empty.

2) Busy-channel deadlock: conversations.history anchors at 'oldest'
   when only 'oldest' is passed (verified empirically — the page holds
   the OLDEST slice, though the code labeled it order:desc). With a
   backlog wider than one page the incremental tick re-read the same
   oldest page forever, the watermark froze at the channel's density
   fixed point (six live channels frozen for weeks), and the hourly
   continuation re-enqueue clobbered the backfill worker's cursor
   progress via enqueue_backfill_job's unconditional ON CONFLICT
   overwrite (status->pending, attempts->0, stale cursor restored).

   Fixes: (a) when the window page overflows, probe the live head with
   a default newest-first fetch and take a monotonic max for the
   checkpoint watermark (never regress); (b) refresh_pending=False for
   periodic enqueuers so pending/running jobs are never rewritten —
   continuations now genuinely drain and complete; (c) claim jobs
   orphaned in 'running' after a stale interval, since the accidental
   rescue-via-clobber is gone.

No schema changes; frozen checkpoints self-heal on the first tick after
deploy, and re-ingesting historical pages repairs raw_payload/text via
the existing upsert.

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

* review fixes: probe resilience, stable continuation key, reclaim race, serializer polish

- Head probe is best-effort and gated on SLACK_BACKFILL_ENABLED: a probe
  failure (rate limit, likeliest on exactly the busy channels that probe)
  no longer discards the fetched window page, and the watermark is never
  jumped past a backlog nothing will drain.
- The standing incremental continuation uses one stable job_key per
  channel: keying on the window's oldest (which now advances with the
  watermark) minted a new ~fully-overlapping job every tick.
- touch_backfill_job_started re-stamps last_started_at as the worker
  reaches each claimed job, so a slow-but-alive run's tail jobs aren't
  reclaimed as stale by a concurrent run.
- _sync_etl_channel_history never returns a watermark below the one it
  was given (monotonic at the source, not just in the sync handler).
- Attachment fallback text goes through _resolve_mentions like the
  primary path; blocks are persisted only for bot/empty-text messages
  (human rich_text blocks just mirror text — jsonb bloat otherwise).

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Akshaan Kakar <akshaan.crackers@gmail.com>

* fix: gate sandbox observability egress (#898)

* fix: gate sandbox observability egress

* chore: bump chart version

* fix: preserve sandbox api netpol name

* feat: add principal create and delete UI (#896)

* feat: allow deleting principals from console

* feat: add console principal creation

* chore: titlecase principal add button

* fix: handle newline after model override (#900)

* fix: complete Claude turns on assistant end_turn (#903)

fix: complete claude turns on assistant end_turn

Co-authored-by: Centaur AI <ai@centaur.local>

* Trigger image publish for harness-server changes (#904)

fix: publish images for harness server changes

Co-authored-by: Centaur AI <ai@centaur.local>

* Use nightly for Rust fmt and clippy instructions (#908)

* feat: interrupt process through slackbot stop (#911)

* fix(console): correct light table dividers (#914)

* fix: tighten slack stop command detection (#915)

* fix: include harness-server source in sandbox image cache key (#905)

Co-authored-by: Centaur AI <ai@centaur.local>

* feat(slackbotv2): gauge open session event stream connections (#916)

Amp-Thread-ID: https://ampcode.com/threads/T-019f3913-b24f-72d7-b4a1-2efb316d97d3

Co-authored-by: Amp <amp@ampcode.com>

* Revert Claude end-turn completion fallback (#918)

Revert "fix: complete claude turns on assistant end_turn"

This reverts commit 791bd244a0d6d692719970b64e9386c07f374108.

Co-authored-by: Centaur AI <ai@centaur.local>

* fix: release session event stream connections after terminal events (#920)

* fix: complete harness turns on terminal assistant stops (#921)

* fix: complete harness turns on terminal assistant stops

* fix: settle claude terminal stops and unblock turn completion

Three interacting fixes for claude/fable turns hanging as "thinking":

- Copy harness child stderr through the unlocked handle: the claude CLI
  outlives each turn, so holding the StderrLock for the copy's lifetime
  deadlocks any eprintln! at turn completion (e.g. OTLP export failures),
  leaving the execution running after the answer already streamed.
- Replace the immediate terminal-stop completion with a settle window:
  the native result still completes the turn when it arrives, the stop
  completes it when the stream goes quiet without one, and stale trailing
  output is drained before the next turn's input so a late result cannot
  instantly terminate the following turn. Amp keeps a zero window (its
  stream has no result event).
- Drop Task subagent sidechain events (parent_tool_use_id) so a
  subagent's end_turn cannot complete or pollute the parent turn.

* chore: format otel rust code (#922)

* fix(discordbot): port live activity summaries; unstick answer-only streaming (#925)

4c17d8b8 (live activity summaries) moved reasoning/'Thinking' task
synthesis out of the shared renderer and adapted slackbotv2 and
linearbot, but not discordbot. Two regressions:

- Discord's narrator built its -# reasoning blurbs from the removed
  Thinking tasks, so runs narrated nothing. Port: forward
  session.activity_summary session events (previously dropped by
  discordbot's SSE whitelist) and route renderer.status to a new
  DiscordNarrator.status(), the Discord analog of Slack's assistant
  status (dedupes consecutive repeats, drops the end-of-run clear).

- The synthetic starting item no longer primes the renderer's task
  state, and the pre-stream grace check is event-driven, so an
  answer-only turn's deltas sat buffered until the next event or stream
  end. Fix: make the grace configurable (preStreamGraceMs, default
  500ms unchanged) and pass 0 in discordbot, which streams answer text
  into its own append-only messages and has no card to wait for.

Tests: new activity-summary blurb test; stale blurb expectations
updated to the server-side model; the failing-edit test now waits for
the first post so the tail must land as an edit (previously the deltas
could coalesce into the initial post and skip the path under test);
mock gains hasStream(threadKey) because streamCount counts live
streams across all threads and a lingering stream from the previous
test could satisfy a bare count wait.

These failures were invisible on main: the discordbot CI job is
path-filtered and 4c17d8b8 landed without a PR run.

Co-authored-by: Centaur AI <ai@centaur.local>

* fix(slackbotv2): preserve paragraph breaks in Slack plain-text extraction (#924)

* fix(slackbotv2): preserve paragraph breaks in Slack plain-text extraction

The @chat-adapter/slack SlackFormatConverter built message.text by
flattening the parsed mrkdwn AST with mdast-util-to-string, which joins
sibling block nodes with no separator. A message like

  --claude --model=fable

  examine <PR link> ...

reached extractMessageOverrides as '--claude --model=fableexamine ...',
so the harness was started with the nonexistent model 'fableexamine'.
The newline-boundary regex fix from #900 was correct but ran on input
whose paragraph breaks had already been destroyed upstream — and every
Slack message with a blank line had its paragraphs glued together in
the text forwarded to the agent.

Extend the @chat-adapter/slack patch to override extractPlainText with
a block-aware conversion: paragraphs join with a blank line, list items
and blockquote lines with a newline. Add pipeline regression tests that
run the real converter output through extractMessageOverrides.

* test: exercise patched paragraph-break handling end to end

The sticky-overrides emulation test now sends --model=fable followed by
a paragraph break — the exact production shape that selected the
nonexistent model fableexamine — covering the patched extractPlainText
through the full webhook pipeline.

---------

Co-authored-by: Centaur AI <ai@centaur.local>

* fix(slackbotv2): retry retryable handoff failures in-process instead of relying on Slack redelivery (#931)

* fix(console): serve stale proxy sync snapshots while one session rebuilds

Every iron-proxy polls /api/v1/proxy/sync on a 5s cadence. When a
principal's snapshot went stale (10min TTL) or its cache version was
bumped (e.g. broker credential refresh fans out to every referencing
principal), all of that principal's proxies stampeded into
PrincipalSyncConfigSnapshot.build_for and queued on the principal row
lock, each holding a Puma thread and DB connection for the full
effective_config rebuild. With one busy Slack channel principal shared
by 30+ sandboxes, every invalidation wave saturated all console
threads, starved the /up probe, and kubelet killed all replicas at
once (prd-centaur-na outage, 2026-07-07).

fetch_for is now stale-while-revalidate: exactly one caller rebuilds
under a non-blocking FOR UPDATE SKIP LOCKED row lock; concurrent
callers are served the stale current-version snapshot, or the newest
previous-version snapshot after a cache bump. Serving stale is safe:
iron-proxy treats the config hash as an ETag and re-applies on its
next poll. Only a cold start (no snapshot at any version) still
blocks.

Also fixes a rebuild-churn bug the new tests exposed: a rebuild that
produced a byte-identical payload no-op'd on save!, so updated_at
never advanced, the snapshot stayed permanently stale, and every poll
re-ran the expensive effective_config rebuild. build_within_lock now
touches the row to restart the TTL.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cf4-8484-71a8-a459-5286174dc20b
Co-authored-by: Amp <amp@ampcode.com>

* fix(slackbotv2): retry retryable handoff failures in-process instead of relying on Slack redelivery

Slack only redelivers webhook events when the handler fails within ~3s,
so the old design (return 503, clear dedupe, wait for redelivery) silently
dropped prompts whenever a retryable session API failure surfaced after a
slow create/load — as seen during centaur-console instability.

Retryable handoff failures now schedule local retries (5s/30s/120s) while
Slack gets an immediate 200. The assistant status stays visible through
the retry window; exhaustion renders the visible error notice. If another
mention starts an execution before a retry fires, the retry conflates into
it (the message is already appended to the session), matching healthy-path
semantics for near-simultaneous mentions.

Replaces slackbotv2_webhook_retry_requests_total with
slackbotv2_handoff_retries_total{outcome}.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cd5-b870-7386-8c36-e28a23ec5808
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>

* Revert "fix(slackbotv2): retry retryable handoff failures in-process instead of relying on Slack redelivery" (#934)

Revert "fix(slackbotv2): retry retryable handoff failures in-process instead …"

This reverts commit a503b4b069d0a3aad2863fafb24f96cf022a56b8.

* fix(slackbotv2): retry retryable handoff failures in-process instead of relying on Slack redelivery (reland) (#935)

fix(slackbotv2): retry retryable handoff failures in-process instead of relying on Slack redelivery

Slack only redelivers webhook events when the handler fails within ~3s,
so the old design (return 503, clear dedupe, wait for redelivery) silently
dropped prompts whenever a retryable session API failure surfaced after a
slow create/load — as seen during centaur-console instability.

Retryable handoff failures now schedule local retries (5s/30s/120s) while
Slack gets an immediate 200. The assistant status stays visible through
the retry window; exhaustion renders the visible error notice. If another
mention starts an execution before a retry fires, the retry conflates into
it (the message is already appended to the session), matching healthy-path
semantics for near-simultaneous mentions.

Replaces slackbotv2_webhook_retry_requests_total with
slackbotv2_handoff_retries_total{outcome}.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cd5-b870-7386-8c36-e28a23ec5808

Co-authored-by: Amp <amp@ampcode.com>

* Add sandbox commit-msg hook (#909)

* Add sandbox commit message hook

* fix: enforce conventional commit messages

* feat: add console workflows dashboard (#912)

* feat(console): admin self-descope to operator permissions (#936)

feat(console): let admins temporarily descope to operator permissions

Adds a per-session "View as operator" toggle for admins:
- session[:descoped] flag with acting_admin?/descoped? helpers; require_admin
  and the default landing path now go through acting_admin?
- Console::DescopesController (POST pauses admin perms, DELETE restores);
  descope route is a singular resource under /console
- Admin-only nav (Control, Data Sync, Workflows, Users tab) hides while
  descoped; account menu gains a View as operator item
- Persistent high-contrast amber banner while descoped with a Restore admin
  button
- Light mode only: amber notice boxes (thread-DB unavailable, managed secret)
  get the same solid amber treatment via a shared .console-amber-note class

Descope is self-healing: the flag is dropped automatically if the user is no
longer an admin, so it can never outlive the privileges it pauses.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3d7c-9cab-726b-9753-92eb40d7fe61

Co-authored-by: Amp <amp@ampcode.com>

* fix(console): serve stale proxy sync snapshots while one session rebuilds (#930)

Every iron-proxy polls /api/v1/proxy/sync on a 5s cadence. When a
principal's snapshot went stale (10min TTL) or its cache version was
bumped (e.g. broker credential refresh fans out to every referencing
principal), all of that principal's proxies stampeded into
PrincipalSyncConfigSnapshot.build_for and queued on the principal row
lock, each holding a Puma thread and DB connection for the full
effective_config rebuild. With one busy Slack channel principal shared
by 30+ sandboxes, every invalidation wave saturated all console
threads, starved the /up probe, and kubelet killed all replicas at
once (prd-centaur-na outage, 2026-07-07).

fetch_for is now stale-while-revalidate: exactly one caller rebuilds
under a non-blocking FOR UPDATE SKIP LOCKED row lock; concurrent
callers are served the stale current-version snapshot, or the newest
previous-version snapshot after a cache bump. Serving stale is safe:
iron-proxy treats the config hash as an ETag and re-applies on its
next poll. Only a cold start (no snapshot at any version) still
blocks.

Also fixes a rebuild-churn bug the new tests exposed: a rebuild that
produced a byte-identical payload no-op'd on save!, so updated_at
never advanced, the snapshot stayed permanently stale, and every poll
re-ran the expensive effective_config rebuild. build_within_lock now
touches the row to restart the TTL.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cf4-8484-71a8-a459-5286174dc20b

Co-authored-by: Amp <amp@ampcode.com>

* feat(console): add user-facing Integrations page with OAuth start links (#937)

Adds a /console/integrations page listing every enabled OauthApp as a card
with the provider's brand logo (Google, Slack, GitHub; falls back to a text
badge for unknown providers) and a Connect button pointing at the public
consent start link /oauth/<slug>/start.

Unlike the rest of the console Control section this page is not admin-gated:
the whole point of the well-known consent links is that regular team members
click them, so the Integrations nav item is visible to every signed-in user.
Only non-sensitive fields are rendered (slug, provider, description).

Also seeds dev/test databases with sample google/slack/github OAuth apps so
the page has content out of the box.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3d8c-1fbe-77cb-aa0e-9e405ac50b52

Co-authored-by: Amp <amp@ampcode.com>

* feat(console): redirect OAuth callback to Integrations and show connected state (#938)

* feat(console): redirect the OAuth callback to the Integrations page on success

A successful consent now lands the user back on /console/integrations with a
flash notice ("<slug> connected as <email>.") instead of the standalone
result page, so they end up where they started with the full list of
integrations in front of them. Denied and error outcomes keep the standalone
result page with its retry link, since those can occur outside a console
session.

* feat(console): show connected state on Integrations cards

An integration whose BrokerCredential carries the signed-in user's email
(provider_email, the only link the unauthenticated consent flow records back
to a console user) now shows a Connected badge and a secondary Reconnect
button instead of Connect. Dead credentials show "Needs reconnecting" so a
broken connection is not presented as healthy.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3d8c-1fbe-77cb-aa0e-9e405ac50b52
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>

* fix(console): recolor first divide-y divider in light mode (#940)

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

* fix(console): reflect fresh connections on Integrations after the callback redirect (#941)

* fix(console): link minted credentials to the consenting console user

After the callback redirected back to Integrations, the card often stayed on
"Connect": the page matched credentials only by provider_email, so consenting
with a provider account whose email differs from the console login (e.g.
dev@iron.local logging into a personal Google account) never flipped the state.

The callback now records created_by from the browser's console session when
one exists (never overwritten, so the first linked user keeps the credential),
and the Integrations page matches created_by first with provider_email as the
fallback for consents made without a session.

* test(console): cover the Slack no-email consent end to end

Slack's OAuth v2 token response carries no email in authed_user (enrichment
fills provider_email in later), which is the actual case the created_by link
exists for: sign in, consent to Slack, land on Integrations, see Reconnect.

Amp-Thread-ID: https://ampcode.com/threads/T-019f3d8c-1fbe-77cb-aa0e-9e405ac50b52
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>

* feat: Add Meta model support (#942)

* fix: label company context lookup metrics (#946)

* feat: add public repo cache access (#943)

* fix: migrate sandbox repo-cache setting

* feat: implement public repo cache access

* feat: expose public overlay capabilities

* fix: satisfy repo cache access ci checks

* fix(iron-proxy): deny cluster upstream cidrs (#949)

* fix(iron-proxy): deny cluster upstream cidrs

* fix(iron-proxy): render managed upstream deny config

* Revert "fix(iron-proxy): render managed upstream deny config"

This reverts commit 94dfc03e8b75243b1d9af2acbaa905f73092671e.

---------

Co-authored-by: Centaur AI <ai@centaur.local>

* fix(harness): make codex attach Responses item ids for the Meta (--meta) provider (#951)

fix(harness): make codex attach Responses item ids for the Meta provider

Long --meta turns fail with `input[N] missing required field id`:
codex serializes Responses input items with their ids stripped and only
re-attaches them (attach_item_ids) for providers detected as Azure.
Meta's Responses endpoint requires ids on replayed history items, so any
turn long enough to round-trip prior reasoning/tool items 400s.

Naming the provider "azure" opts it into codex's Azure workaround
(store: true + attach_item_ids). Durable fix is codex >= 0.142.0 with
the item_ids feature.

Co-authored-by: Centaur AI <ai@centaur.local>

* ci: rebuild images for harness config changes (#952)

* fix(iron-proxy): bump base image to 0.46.0 (#954)

Co-authored-by: Centaur AI <ai@centaur.local>

* fix(tools): migrate allium to renamed MCP tools and surface tool errors (#955)

Allium renamed its MCP tool surface: explorer_run_sql/explorer_search_schemas/
explorer_fetch_schema no longer exist. The live server now exposes
run_sql_query (async, returns run_id) + get_query_run_results, and
search_schemas handles both query search and per-id schema fetch.

- run_sql: call run_sql_query, then poll get_query_run_results until the
  run completes; raise on failed runs and timeouts
- search_schemas/fetch_schema: use the new search_schemas tool (id= fetches
  a single entry with markdown content)
- _mcp_call: raise RuntimeError on tool-level errors (isError + text
  content). Previously 'Unknown tool' and 'Not authenticated' were parsed
  as empty result lists, so every failure printed 'No results' / 'N/A' and
  'allium health' reported ok:true with the error buried in details
- describe: render markdown schema content when structured columns are absent

Co-authored-by: Centaur AI <ai@centaur.local>

* feat: GitHub client (#956)

* feat(githubbot): GitHub ingress bot (conversational + review-on-request)

Add services/githubbot, a standalone bot modeled on linearbot and built on
the official @chat-adapter/github adapter, authenticating as a GitHub teammate
via PAT.

Behavior (v1):
- @-mention in a PR/issue comment or PR review comment -> answer in that thread.
  PR conversation is one session thread; each code-comment thread is its own,
  with the anchored file/line/diff hunk injected into the turn.
- Non-mention follow-ups in an active thread are appended as context.
- Requesting the bot's review (review_requested) -> a review turn on a dedicated
  isolated, per-PR thread (re-review aware); the agent posts inline comments and
  a summary via gh. Review methodology ships as a standalone default and is fully
  replaceable via GITHUBBOT_REVIEW_PROMPT[_FILE].

Wiring: pnpm workspace, gated CI job, Helm Deployment/Service + NetworkPolicy +
ingress route, and bootstrap secrets (GITHUBBOT_TOKEN kept distinct from the
sandbox tool token). Typecheck clean; 28 unit tests pass.

feat(githubbot): v2 PR self-management (fix CI, address review, merge)

Add a PR manager that drives PRs the bot owns (authored by it, or carrying the
managed label) toward merge by reacting to PR/review/CI lifecycle webhooks.

- Fix CI: fires only once *all* checks for a head SHA are settled and red, then
  runs a bounded fix turn (agent diagnoses + pushes via gh); resets on green,
  backs off human-authored commits, and escalates to a tagged human after the
  attempt cap.
- Address review: one holistic turn per submitted review (changes coherently,
  replies per thread, resolves, re-requests review).
- Merge: deterministic — when GitHub reports mergeable_state clean the bot merges
  directly (branch protection is the source of truth); dirty -> conflict turn,
  behind -> branch update. On by default for owned PRs, with a global kill-switch,
  a per-PR hold label, and draft respect.

Agentic flows run on a dedicated github-manage:{owner}/{repo}:{n} thread. Scope
is same-repo PRs on repos we control. Config plumbed through options/env and the
Helm chart. Pure decision logic (CI evaluation, ownership, merge gating) is unit
tested; typecheck clean, 41 tests pass.

feat(githubbot): route owned-PR conversation mentions to the management session

When the bot owns a PR, an @-mention in its conversation (or a review-comment
thread) now runs in that PR's management session (github-manage:…) so it answers
with the context of the CI fixes and review work the bot has been doing — while
the rendered reply still posts back to the comment thread.

- runSessionTurn gains sessionThreadKey, decoupling the session/sandbox key from
  the posting thread (and reaction/dedup target).
- Ownership is resolved with a cached octokit lookup (isPrOwned) and remembered on
  the conversation thread (managementSessionKey) so follow-ups skip the lookup and
  append into the same session.

Typecheck clean, 41 tests pass.

feat(githubbot): assignment-based ownership + issue work on assignment

Make PR ownership purely an assignment mechanism: the bot manages a PR
iff it's assigned to the bot account (dropping author==bot and the
managed label). Being assigned triggers an immediate takeover that
forces past the human-commit CI back-off.

Add issue work on assignment: assigning an issue to the bot runs an
autonomous work turn on an isolated session that reads the issue,
implements a fix, and opens a self-assigned PR. The issue-work
methodology is a bundled default fully replaceable via
GITHUBBOT_ISSUE_PROMPT / _FILE, mirroring the review prompt override.

fix(githubbot): instant reaction acks for review/issue turns + hoist the working react

The conversational 👀 only fired inside the turn, after a state write,
subscribe, and a live ownership lookup — so it lagged. Fire it up front
in the webhook handler instead, before that setup work.

Review requests and issue assignments have no triggering comment to
react to, so they got no feedback at all. Ack on the PR/issue itself
(👀 → 🚀/😕) via a shared best-effort helper.

feat(githubbot): chart support for review/issue prompt overrides via mounted files

Add githubbot.reviewPrompt / issuePrompt values: when set, the chart
writes each into a ConfigMap, mounts it as a file, and points the bot at
it via GITHUBBOT_REVIEW_PROMPT_FILE / _ISSUE_PROMPT_FILE — the file path
the server already supports. A checksum annotation rolls the pods when a
prompt changes. Unset (default) keeps the bundled defaults, with no
ConfigMap or volume rendered. Lets a deployment supply its own review or
issue-work methodology without a large inline env var.

fix: handle githubbot PR lifecycle edge cases

fix(githubbot): close concurrency, durability, authz, and parity gaps from review

Hardens the new GitHub bot against the issues a multi-agent review surfaced,
concentrated on the autonomous lifecycle surface.

- Concurrency: switch the chat handler from the deprecated onLockConflict
  "force" to concurrency "drop" so a redelivered or near-simultaneous comment
  can't slip past the read-modify-write dedup and double-reply, and serialize
  turns targeting the same session so a conversation mention and a CI-fix turn
  can't interleave git/push in one sandbox.
- Durability: drain in-flight turns on SIGTERM (bounded) so a deploy doesn't
  silently drop running work that its own claim would then suppress, and only
  hold the merge claim once the merge actually succeeds so a transient failure
  retries instead of stranding a clean PR.
- Authorization: gate the comment-mention path on author_association (safe
  default, "*" to open) so an untrusted commenter can't steer the write-capable
  agent; lifecycle paths stay gated by GitHub permissions.
- Parity: act on review requests aimed at a team the bot belongs to, and on an
  @-mention written into a freshly-opened issue/PR body.
- Perf: stop re-serializing every attachment's payload once per attachment.
- Config: deployments can prepend their own guidance to management turns.

Adds tests for the CI-fix counter/escalation, merge-claim release, the author
gate, the serialization queue, team requests, and body mentions, plus chart and
README wiring for the new knobs.

* chore: bump chart

---------

Co-authored-by: Will Drach <drach@splits.org>

* fix(allium): update Hyperliquid queries to current table schemas (#957)

fix(allium): update to renamed MCP tools and current Hyperliquid schemas

The Allium MCP server renamed its tools, breaking the allium CLI:
- explorer_run_sql -> run_sql_query (now async: submit returns a run_id
  that must be polled via get_query_run_results)
- explorer_search_schemas -> search_schemas
- explorer_fetch_schema -> search_schemas with id argument

Also surface MCP tool errors (isError) instead of silently returning
'No results', and fix stale Hyperliquid column references:
- dex.trades: size -> amount, buyer/seller -> buyer_address/seller_address,
  usd_amount for notional, no side column (use buyer_crossed/seller_crossed)
- metrics.overview: day -> activity_date
- raw.orders: price -> limit_price, "user" -> "USER"
- raw.builder_fills join: builder_labels.address -> builder_code, quoted
  "USER", cast builder_fee to float

Co-authored-by: Centaur AI <ai@centaur.local>

* fix(console): hide admin redirect alert (#939)

* fix: limit default infra secrets to model providers (#960)

* fix: limit default infra secrets to providers

* fix: keep static infra fragment credential-free

* feat(console): grant oauth credentials and user-mcp role to console-user MCP principals (#932)

* feat(console): grant oauth credentials and user-mcp role to console-user MCP principals

Console-user principals minted by the MCP OAuth flow previously received
no roles or credentials, making per-user MCP effectively useless.

- Generalize PrincipalCredentialReconciliation to all OAuth providers and
  match console-user principals to their verified user emails, so a user's
  own OAuth credentials are auto-granted to their MCP principal.
- Seed a shared user-mcp role onto newly minted console-user principals.
  Admins attach tool secrets to this role to define the default toolset
  every MCP user gets. Seeded on create only, so removals stick (mirrors
  SessionRegistrar's infra-role semantics for session principals).

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cb0-b3e7-772e-b463-6f2882f4d362
Co-authored-by: Amp <amp@ampcode.com>

* fix(console): tighten console-user matching and make role seeding atomic

Review follow-ups:

- Console-user principals now match credentials only through their User
  record (primary email + verified identity emails). Mutable principal
  labels (email, google_email, slack_email) and provider-subject labels
  no longer participate, and provider identity labels are never synced
  onto console-user principals. Closes the stale/tampered-label path and
  the recycled-email edge.
- Principal creation and user-mcp role seeding now share a transaction,
  so a transient seeding failure can no longer commit a principal that
  create-only semantics would leave unseeded forever. The RecordNotUnique
  retry moved outside the transaction (a unique violation aborts the
  enclosing Postgres transaction).

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cb0-b3e7-772e-b463-6f2882f4d362
Co-authored-by: Amp <amp@ampcode.com>

* docs(console): note when to optimize per-principal credential reconciliation

Amp-Thread-ID: https://ampcode.com/threads/T-019f3cb0-b3e7-772e-b463-6f2882f4d362
Co-authored-by: Amp <amp@ampcode.com>

---------

Co-authored-by: Amp <amp@ampcode.com>

* fix(iron-proxy): restore GITHUB_TOKEN and SLACK_BOT_TOKEN infra secrets (#967)

#960 removed the non-model secret transforms from the static infra
fragment, but the sandbox placeholder env is derived from that same
fragment, so new sandboxes stopped receiving GITHUB_TOKEN=GITHUB_TOKEN
and SLACK_BOT_TOKEN placeholders. gh/git decide they are unauthenticated
locally and never send the Authorization header iron-proxy would
rewrite, breaking gh, git-over-HTTPS, and the slack tool in every
sandbox created after the rollout.

Restore the replace-mode GITHUB_TOKEN (github.com, api.github.com) and
SLACK_BOT_TOKEN (*.slack.com) entries. The other #960 removals
(XAI_API_KEY, GEMINI_API_KEY, AMP_API_KEY) stay removed.

* fix: inject sandbox token placeholders outside fragments (#968)

* feat(console): add Granola OAuth provider (#964)

* feat(console): add Granola OAuth provider

Targets Granola's MCP OAuth server for the console consent flow, with identity extracted from the OIDC id_token and refresh-token support requested through offline_access.

Granola does not expose a classic static third-party app dashboard; operators mint the OAuth client once via dynamic client registration at https://mcp-auth.granola.ai/oauth2/register and store that client_id/client_secret on the OauthApp.

* feat(console): add Granola provider logo to Integrations page

Embeds the Granola mark (single currentColor path, 32x32 viewBox)
in oauth_provider_logo and makes the SVG viewBox per-provider so
non-24x24 marks render correctly.

* feat(console): add Attio OAuth provider (#966)

* feat(console): add Attio OAuth provider

Add an Attio provider strategy for the console's generic OAuth consent flow. Attio scopes are configured in the Attio developer dashboard, so the provider has no identity scopes and treats token response scopes as optional.

Attio returns long-lived workspace-scoped access tokens without refresh_token or expires_in, so the provider is non-refreshable and uses the existing default expiry handling for callback persistence. Workspace identity is enriched asynchronously from /v2/self and may collide intentionally by (app, workspace_id).

* feat(console): default Attio seed scopes to user_management read + read-write for core resources

* feat(console): add Attio provider logo to Integrations page

Embeds the official Attio mark (two currentColor paths, 31x26 viewBox)
in oauth_provider_logo and makes the SVG viewBox per-provider so
non-24x24 marks render correctly.

* feat(console): add Linear OAuth provider (#965)

Linear token responses do not include account identity, so the callback mints a deterministic pending identity and a follow-up enrichment job queries the Linear GraphQL viewer to replace it.

Linear refresh tokens rotate on use; the provider is marked refreshable and refresh requests preserve the granted scopes.

* feat(console): workflows dashboard cleanup — grouping, filters, pagination, force-start, debugging (#958)

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

* feat: add Slack file proxy API (#961)

* feat: add Slack file proxy API

* feat: support Slack upload metadata

* fix: harden Slack file proxy responses

* feat: add scoped Slack search proxy

* refactor: rename Slack proxy module

* fix: drop Slack search and harden file proxy

Remove the Slack search endpoints entirely while keeping the Slack file upload and download proxy.

Harden the remaining file proxy path with stricter download validation, shared timeout-aware Slack client setup, content type validation, case-insensitive bearer auth, safer response headers, and shared JWT/env configuration.

* docs: add GitHub, Granola, Linear, and Attio to the OAuth Apps page (#969)

* docs: document console OAuth integrations

* docs: replace end-to-end flow and provider tables with short Console setup how-to

* docs: fold new OAuth providers into existing OAuth Apps page, drop duplicate integrations page

* docs: simplify OAuth Apps page to a short UI-only guide

* docs: add Granola dynamic client registration to provider-specific setup

* fix: read workflow output watermark (#972)

* feat: generate API server JWTs for sandbox proxy sync (#971)

* feat: generate API server JWTs for sandbox proxy sync

* fix: reject whitespace JWT signing secrets and jitter token rotation windows

Restore the blank? guard the HS256 extraction narrowed to empty?, so a whitespace-only CENTAUR_JWT_SIGNING_SECRET fails closed again. Offset each principal's 15-minute rotation window by a deterministic per-oid jitter so snapshot rebuilds and config_hash flips spread across the window instead of stampeding at every global boundary.

* fix: gate API JWT credentials on sandbox capability

* fix(slackbotv2): detect stop commands in Chat-SDK-normalized mention text (#970)

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

* feat: expose slack client jwt on healthz (#973)

* fix: wire centaur api url into console chart (#974)

* chore: update iron-proxy to 0.47.0 (#977)

* fix: revert iron-proxy to 0.46.0 (#978)

* fix: batch company context projection windows

* fix: batch company context projection windows

* fix: bump centaur chart version

* chore: update iron-proxy to v0.48.0 (#979)

* fix(slack): resolve user IDs and @usernames to DM channels in read paths (#981)

_resolve_channel only matched channel IDs and bot channel names, so
get_channel_history, get_thread_replies, and other read methods could
not read the bot's DMs even though send_message/send_dm could open
them. Resolve U.../<@U...> and @username references to the one-on-one
DM conversation via conversations.open, reusing _open_dm_channel.

* fix: wire slack bot token into api-rs chart (#982)

* feat: add scoped slack search proxy (#983)

* feat: add scoped slack search proxy

* fix: keep slack search claims channel scoped

* fix: strip all slack search channel filters

* fix: resolve slack search scopes

* fix: update websocket driver advisory

* fix: route slack proxy searches to requested channels

* fix: simplify slack proxy fallback flow

* feat: add slack proxy search command

* fix: scope slack proxy search to current channel

* Revert "feat: add scoped slack search proxy (#983)" (#985)

* Revert "feat: add scoped slack search proxy (#983)"

This reverts commit e0af368859343016f002a16e53191352a6f11a11.

* restore websocket-driver fix

* feat: index private Slack channels behind flag (#986)

* feat: index private Slack channels behind flag

* fix: keep Slack user directory readonly visibility

* test: derive private Slack RLS fixture

* chore: bump chart version

* granola: add MCP backend with REST fallback (#994)

* fix(slack): resolve user IDs and @usernames to DM channels in read paths

_resolve_channel only matched channel IDs and bot channel names, so
get_channel_history, get_thread_replies, and other read methods could
not read the bot's DMs even though send_message/send_dm could open
them. Resolve U.../<@U...> and @username references to the one-on-one
DM conversation via conversations.open, reusing _open_dm_channel.

* granola: add MCP backend with REST fallback

The Centaur console OAuth flow mints user-scoped Granola MCP tokens
(mcp.granola.ai), but the tool only spoke the Enterprise REST API
(public-api.granola.ai, workspace API key). Add a GranolaMcpClient
speaking Streamable HTTP JSON-RPC with the same method surface,
normalized to REST note shapes.

_client() tries MCP first (cheap get_account_info probe) and falls back
to REST; GRANOLA_BACKEND=mcp|rest overrides. The proxy injects the
OAuth Bearer for mcp.granola.ai from the console grant, so the tool
never handles the token; GRANOLA_MCP_TOKEN supports local dev.

New CLI commands: whoami, query (natural-language Q&A with citations),
folders.

* feat: proxy Slack history and files (#1001)

* fix: proxy sandbox api traffic (#1002)

* fix: proxy sandbox api traffic

* test: cover iron-proxy api capability labels

* chore: change codex default model to gpt-5.6-sol (#1005)

* Add Granola sync workflow (#987)

feat: add granola sync workflow

* feat: add Attio sync workflow

Adds scheduled Attio meeting/transcript ingest and projects synced meetings into company context documents.

* fix: sync slack labels for mcp principals

* feat: add MPP fallback discovery (#1007)

* feat: add MPP service discovery (#1006)

* fix: label MCP principals from Slack SSO (#1010)

* fix: sync slack labels for mcp principals

* fix: label MCP principals from Slack SSO

* fix: default slack file and thread tools to api routes (#1009)

* fix: default slack file and thread tools to api routes

* test: assert slack etl uses direct slack routes

* fix: avoid stale api-rs build artifacts (#1012)

Co-authored-by: Centaur AI <ai@centaur.local>

* fix: gate readonly DM access by Slack identity (#1011)

* feat: make the console an installable PWA (#980)

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

* fix(proxy): allowlist Datadog auth headers for the agent Datadog tool (#944)

* fix: wire console slack bot token (#1013)

* feat: add Slack channel claims to principals

* refactor: rename Slack channel claims

* fix: wire console slack bot token

* fix: backfill slack channel permissions

* chore: fix slack permission lint

* fix: address slack permission review

* refactor: simplify slack permission replacement

* refactor: simplify slack permission cache updates

* fix: harden slack permission inputs

* refactor: use form params for slack permissions

* refactor: simplify slack permission inputs

* refactor: move slack channel option formatting to helper

* fix: seed slack permissions during session registration (#1017)

* fix: grant slack dm permissions for user principals

* fix: restrict slack dm permissions to user principals

* fix: seed slack permissions from session registration

* test: trim slack permission negative coverage

* fix: harden slack permission upserts

* fix: match slack dm ids case-insensitively

* chore: agents.md cleanup (#1021)

* fix: grace executions awaiting sandbox assignment (#1020)

* fix: upsert slack dm permissions for existing principals (#1022)

* feat: proxy slack channel listing (#1023)

* feat: proxy slack channel listing

* fix: harden slack channel enrichment

* fix(sandbox): bump Codex for gpt-5.6-sol (#1016)

fix(sandbox): support gpt-5.6-sol

Co-authored-by: ecejas <15933097+ecejas@users.noreply.github.com>

* chore: move the PWA install CTA into the account menu (#1026)

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

* fix(sandbox): avoid Centaur AI co-author attribution as it is non-existent account (#1015)

* feat: support 5.6 max Codex reasoning effort (#1027)

* fix: proxy Slack file listing (#1025)

* fix: proxy Slack file listing

* feat: add direct Slack file search command

* fix: correct Slack files list pagination

* fix: require channel for Slack file proxy

* fix: require channel for Slack file search

* fix: update Slack file search callers

* docs: clarify model and harness switching (#1028)

* feat: expose Slack file and member proxies (#1029)

* feat: expose Slack file info proxy

* refactor: share Slack file info helpers

* fix: authorize Slack file info before config

* feat: proxy Slack channel members

* fix: make Attio sync resilient to detail failures (#1031)

* fix: use host Slack team for principals (#1032)

* fix: use host Slack team for principals

* fix: stop scoping Slack channel principals by requester team

The prior approach threaded the host Slack team through a request
context so channel principals would use it. That is unnecessary: a
Slack channel id is globally unique, so channel principals never need
a team in scope. Scoping them by the message-derived team (introduced
in #882) let a Slack Connect requester's external workspace fork a
separate principal from the host channel's.

Derive channel principals without any team scope, keeping the DM
metadata-team folding from #882 intact, and revert the request-context
plumbing.

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

---------

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>
Co-authored-by: Matthew Slipper <me@matthewslipper.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat: manage sandbox defaults in console (#1036)

* feat: configure default sandbox capabilities

* feat: manage sandbox defaults in console

* feat: manage sandbox defaults in console

* refactor: simplify sandbox default application

* refactor: rely on sandbox setting validation

* refactor: remove redundant sandbox default constants

* fix: preserve explicit sandbox repo-cache labels

* refactor: store sandbox repo cache as enum

* refactor: require repo cache label for principals

* fix: prefer principal repo cache setting over label

* fix: reject sandbox repo cache label input

* fix: validate sandbox repo cache label consistency

* refactor: always project sandbox repo cache label

* fix: gate otlp egress on observability (#1037)

* fix: gate otlp egress on observability

* chore: bump helm chart version

* fix: always label api-rs for observability

* fix(slackbotv2): resolve trigger bot identities (#1043)

* fix(slackbotv2): resolve trigger bot identities

* fix(slackbotv2): match webhook bots by app identity

* refactor(slackbotv2): allowlist bot members only

* feat(console): chat composer: start and continue chats from the console (#984)

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

* fix(console): preserve optimistic chat messages (#1047)

* docs: fix company context latest-date guidance (#1049)

* feat: batch company context projections (#1055)

* fix(slackbotv2): support scoped trigger bot IDs (#1051)

fix(slackbotv2): support scoped trigger bot ids

Co-authored-by: Perry Dime <260989497+svc-paradigm@users.noreply.github.com>

* fix: include API controllers in console request logs (#1056)

* fix: remove company context size metric (#1057)

* fix(console): keep composer model picker menu inside the viewport (#1053)

* fix(console): preserve requester attribution for PRs (#1054)

* feat: add Airtable record write operations (#1058)

* fix: allow hosted MCP OAuth redirects (#1059)

* fix: allow hosted MCP OAuth redirects

* test: update MCP redirect rejection case

* fix: fall back to direct Slack threads (#1061)

* feat: sync Granola via user OAuth (#1060)

* feat: sync Granola via user OAuth

* fix: satisfy Granola sync CI checks

* feat: add Airtable identity health check (#1062)

* feat: expose sandbox permission query (#1039)

* feat: expose sandbox permission query

* fix: expose console URL to sandboxes

* refactor: clarify sandbox console URL wiring

* fix: require explicit API server proxy hosts

* chore: bump chart version to 0.1.99

* fix: require configured sandbox entitlement host

* feat: add centaur console permissions tool (#1065)

* feat: project Granola notes into context (#1063)

* feat: project Granola notes into context

* test: provision Granola RLS source table

* test: provision Granola RLS identity helpers

* fix(slackbot): trigger on rich message mentions (#1048)

* feat: harden console public login (#1072)

* feat: harden console public login

* refactor: simplify console SSO domain parsing

* chore: bump chart version

* fix: require console login for oauth credential minting (#1074)

* fix: harden console public access controls (#1076)

* feat: ingest private Slack channels from OAuth (#1079)

* feat: ingest private Slack channels from OAuth

* fix: remove private channel membership expiry

* refactor: rename private Slack tables

* feat: add console chat privacy controls (#1078)

* feat(console): make the operator console responsive on mobile (#1084)

* fix(console): scope while-running transcript refresh to the active panel (#1083)

* feat: expose sandbox oauth connection discovery (#1086)

* feat(slackbotv2): show Codex effort and speed (#1085)

Closes paradigmxyz/centaur#1068

Co-authored-by: Derek Cofausper <256792747+decofe@users.noreply.github.com>

* feat(slackbotv2): default model and reasoning effort per Slack channel (#1075)

* feat(workflows): plumb model and reasoning effort through ctx.agent_turn (#1073)

* docs: clarify cron weekday numbering (#1089)

Closes paradigmxyz/centaur#1040

Co-authored-by: Anđelo Kandić <andelo.kandic@gmail.com>

* fix: support non-rotating Slack OAuth tokens (#1088)

* fix: support non-rotating Slack OAuth tokens

* fix: preserve Slack OAuth token expiry semantics

* fix: preserve Granola projection document IDs (#1091)

fix: preserve granola projection document ids

* fix: label Slack DM principals with email (#1092)

* fix: label Slack principal kinds (#1093)

* fix: log console JWT rejection reasons (#1090)

* fix: preserve console return path on login (#1094)

* fix: preserve console return path on login

* fix: avoid verb confusion in login return path

* fix(slackbotv2): hide Codex effort and speed (#1095)

* fix(console): use GitHub login for PR attribution (#1087)

* fix: improve console dropdown labels (#1097)

* fix: preserve postgres proxy credentials on resume (#1099)

* feat: Make the chat agent platform-aware (Slack/Discord/Linear/Github) (#704)

* fix(linearbot): don't start an assignment turn when the bot assigns itself (#945)

* fix(linearbot): Prevent Linearbot from marking issues as "done" when it just put a PR up (#959)

Co-authored-by: Gerard <289036782+splits-gerard@users.noreply.github.com>

* fix(harness-server): downscale oversized image attachments before the model (#1077)

* fix: omit Slack client secret for PKCE login (#1100)

* fix: use confidential Slack OIDC login (#1101)

* fix(console): separate chat discovery from access (#1102)

Keep the sidebar and default chat selection scoped to chats owned by
the signed-in user.

Allow users to continue public and explicitly shared chats when the
existing access policy permits them.

* feat: add LLM message override strategy (#1096)

* feat: add LLM message override strategy

* fix: log LLM message override failures

* fix: classify overrides from Slack prompt text

* Revert "fix: classify overrides from Slack prompt text"

This reverts commit 0f553e6b2a500978732056077539803f6836ca72.

* chore: bump Helm chart version

* fix: avoid logging override response bodies

* fix: map OpenAI key for Slackbot overrides

* fix: make Slackbot OpenAI key optional

* fix: align LLM sonnet model alias

* fix: support explicit sonnet 5 override

* fix: keep Slack override retries idempotent

* fix: log successful override strategy responses

* fix: reduce slack override strategy log noise (#1104)

* fix: reduce slack override strategy log noise

* refactor: accept full logger for slack override strategy

* feat: render attached images in console chat (#1105)

* fix(console): clean up chat detail rows (#1108)

* feat: add workflow-scoped principals (#1107)

* feat: add workflow-scoped principals

* feat: add workflow host sandbox helm value

* chore: bump centaur chart

* fix: default workflow host sandbox chart value off

* f…
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