Repository navigation
Conversation
This was referenced Jun 22, 2026
ymichael
force-pushed
the
bb/compare-similar-pr-issues-thr_ayvcdnzf5w
branch
from
June 22, 2026 21:40
ee4b48a to
f211a27
Compare
Samuka007
added a commit
to Samuka007/bb
that referenced
this pull request
Oct 8, 2026
…earXNG 凭据行堆叠布局 (#17) * fix(app): give pending-interaction cards a visible Stop run entry (get-bb#251) While an approval or user question blocks a thread, the card swaps out the composer, so the composer's own stop affordance is not on screen — a stuck thread could only be cancelled by hitting the stop route by hand (get-bb#226 residual affordance gap). - StopRunButton: shared ghost entry (Square icon, Stop run / Stopping run... with spinner) used by every pending card - approval card: BannerShell footer mirrors onStop at the left of the decision row (mr-auto keeps the decision hierarchy right-aligned); disabled while a resolution is in flight or a stop was requested - question card: the form's bottom-left cancel control is the same stop action (it already mutated stopThread) — relabelled Cancel -> Stop run so the affordance is legible as stop-the-run, with the same in-flight progress state; exactly one stop control per card - ThreadDetailPromptArea and EmbeddedThreadChat pass their existing handleStopThread/isStopRequested into the active thread's card (plugin interactions keep their own composer and are untouched) Acceptance anchor: pending visible entry -> click -> turn terminal -> card dismissed -> composer resets (L2 in thread suites; staging browser pass owed at close-out per get-bb#239 gate) * feat(app): Settings→Providers→Server read-only provider projection section (get-bb#266) Port-only settings section for cloudflare-agent-project get-bb#266 (get-bb#255 solution C): the server-side provider configuration's source of truth stays the deployment env, so the SPA renders a read-only projection of GET /system/provider-projections plus an 'edit via deployment env' pointer — no PUT, no controls. - settings-nav: 'server' provider entry (port-only; codex/claude-code keep their external-CLI pages, E8 semantics untouched) - provider-projection-queries: typed query over the port-only endpoint (schema local to the fork; @bb/server-contract stays upstream-verbatim) - ServerProviderSettingsSection: relay mode/endpoint host/model/key gate/permission/machine + web_search chain order, per-engine credential gates, browser-backed exclusion note, decodeError path - SettingsView routes /settings/providers/server to the new section Tests: ServerProviderSettingsSection.test.tsx (projection rendering, credential-gate badges, decode-broken env, mock-mode hint, no switch on the face) + settings-nav resolution; @bb/app suite 350 files / 2780 tests green; build green. * feat(app): get-bb#309 context-window indicator embeds Compact action (hermes-style) The context-window indicator popover gains an optional Compact row, turning the ring + hover panel into a single hermes-style control: inspect usage and compact from the same affordance instead of a separate menu entry. ThreadContextWindowIndicator takes an optional compactAction ({onCompact, pending}); when absent it renders exactly as before, so secondary composers and embedded usages are unchanged. FollowUpPromptBox forwards it next to contextWindowUsage via a conditional spread, and ThreadDetailPromptArea wires it to the new useCompactThread() mutation, which calls the server worker's POST /threads/:id/compact through the already-existing bb wire contract (packages/server-contract public-api + sdk.threads.compact returning {ok: true}) and invalidates the thread detail/timeline/list queries via the established invalidateThreadBannerQueries helper so the indicator refetches immediately. While pending, the quiet full-width ghost Button is disabled and reads Compacting...; a plain onClick keeps the hover popover open, no close/reopen churn. * feat(app): get-bb#303 port upstream get-bb#3250 thinking rows onto pin ba42654+3 — reasoning operation rows with canonical reasoningId expansion Semantic port of get-bb/bb e038786 (Unify thinking rows) adapted to this fork's pin generation; a literal cherry-pick is impossible (748 commits of upstream drift touch the same files, and mobile/plugin-registry/icon-registry do not exist at this base). Ported (SPA-visible surface): - server-contract: reasoning operation kind + optional reasoningId on the generic operation system row. - thread-view: completed thoughts join activity groups (TimelineWorkSummaryChild), Thinking…/Thought titles with duration decoration, prose child rendering. - shared-ui: AiBrain01 registration (same @hugeicons/core-free-icons 4.1.3). - app: TimelineReasoningExpansion provider (per-canonical-id manual overrides keyed off the structural row id), reasoning prose body, dim rules (warnings/ deprecations stay readable, reasoning recedes like completed work), working indicator rendered as an expandable reasoning row driven by activeThinking. Deliberately NOT ported (recorded deviation): the showUnhandledProviderEvents → showDiagnosticEvents rename bundled into upstream get-bb#3250 — this fork only builds apps/app and the setting's server/db/cli surface is not deployed from here; our server keeps showUnhandledProviderEvents. Verified: apps/app tsc clean; thread-view/server-contract/shared-ui tsc clean; SPA vitest 25 files/207 tests green (incl. new TimelineReasoning suite); thread-view vitest 21 files/368 tests green; vite production build ok. > AGENT GENERATED * feat(plugin): cap-provider-config 插件交付——settingsSection 槽×2,core diff 清零 (get-bb#362/get-bb#382) 零 core 触碰的插件化交付(cloudflare-agent-project get-bb#382 验收): - plugins/cap-provider-config:app.slots.settingsSection ×2——Configured 写面(CRUD onto /api/v1/system/providers,write-only key null 协议/ 全字段模型行编辑含 effort 阶梯与 thinkingBudgetTokens/发现合并 skip-with-warning/test-connection 判词)+get-bb#266 Server 只读投影迁移; 查询层入插件(本地 query keys,app query-keys pristine);插件自带 QueryClientProvider(SDK 只 shim react,不 shim react-query);宿主面 刷新走 server 的 system config-changed 广播,不触宿主缓存。 - 回退 get-bb#266 core 接线:settings-nav/SettingsView/query-keys 恢复上游 pristine(对上游 diff=0 行);ServerProviderSettingsSection 与 projection 查询迁入插件。 - scripts/build-cap-provider-config.mjs:独立构建(不走 BUILTIN_PLUGINS registry);pnpm-lock 增补 workspace 成员。 Tests: 插件包 12 绿(app.test+server-section)+tsc;@bb/app tsc 绿。 * feat(plugin): cap-provider-config models.yml 粘贴导入——面板一键上云 omp 本地配置 (get-bb#364) Import models.yml 入口(configured 节): - Dialog 粘贴 omp ~/.omp/agent/models.yml 片段(全 providers 表或裸片段), POST /api/v1/system/providers/import-models-yml 单请求上云;apiKey 明文 只经此一次请求体,服务端走 get-bb#362 AES-GCM 链落库。 - 判词面逐 provider 渲染:created (N models)/skipped <status> 徽章+判词 文案+迁移警告 transcript(越界 api=422 unsupported_api 诚实判词、 $$CREDENTIAL$$ 占位符无 key 导入、omp 专属键丢弃清单)——零静默丢。 - 成功即 invalidate 本地 query 缓存;host 面走服务端 config-changed 广播。 - queries 层 importModelsYml + zod 判词 schema;sonner toast 断言走 mock。 * fix(cap-provider-config): get-bb#387 Server section supplies the plugin QueryClient — staging crash The staging SPA mounted the Server settingsSection with no QueryClientProvider above it: the plugin bundles its own react-query copy (the SDK runtime-shims react but NOT react-query), so the host app's provider is on a different context object and cannot be inherited. The bare useQuery inside useProviderProjections threw 'No QueryClient set', the per-slot error boundary disabled the section for the session, and the Providers panel crashed on the user's machine (cloudflare-agent-project get-bb#387). - src/plugin-query-client.tsx: shared module-singleton QueryClient + PluginQueryProvider + reset seam — one client per page load, shared by both sections so a Configured write invalidates the Server projection by key. - ConfiguredProviderSettingsSection: same provider, now sourced from the shared module (behavior unchanged). - ServerProviderSettingsSection: the crash site — now self-supplies the provider like the Configured section always did. - server-section.test.tsx: mount the REGISTERED slot (renderSlot) and a bare export mount with NO test-side provider — the wrapper that masked the crash is gone, so this suite now fails exactly the way staging did. - app.test.tsx: reset seam import follows the shared module. Verified: plugin vitest 15/15, tsc clean; real staging SPA (SERVER_VERSION 41a60f1) driven in Chromium with the rebuilt bundle — both sections render, the Server projection shows live relay/web_search facts, CRUD create → edit → remove walks through against the real D1 with zero console crashes. * feat(plugin): cap-provider-config 面板=合并目录真身——deployment-seed 行只读标注 (get-bb#388) - Configured 列表面改为合并目录真值:env MODEL_RELAY_CATALOG 种子行 (未被 D1 行覆盖者)以 source=deployment-seed 徽章只读渲染——不再 Test/Edit/Remove;面板所见 = execution-options 执行可用集合。 - 行 zod schema +source(default "user":旧 server 响应容错为可编辑)。 - key 加 source 前缀(seed⊕broken-D1 同 id 并列时不撞 React key); 空态文案改为「deployment 无种子且未配置」。 - 顺带 prettier 归一(bb 默认 printWidth;纯格式无语义)。 cloudflare-agent-project get-bb#388 * fix(cap-provider-config): get-bb#434 Configured 面只列用户行——空态指针 Server 节 服务端(cloudflare-agent-project 同票)把 CRUD display face 收敛到 D1 provider_configs 用户行正本:env 部署种子(deployment-seed 行)不再骑 写面,其只读真身在 Server section(provider-projections)。本仓只带 面板半:空态文案从「env seed 仍在服务」改为「只列本面板存储的用户行, 部署声明目录只读于 Server 节」,并以组件测试钉住指针文案。 Ref Samuka007/cloudflare-agent-project#434 * fix(cap-provider-config): get-bb#452 API family 控件=服务端契约枚举单选下拉 自由文本 Input+datalist 的建议值与服务端枚举脱节:面板建议 "anthropic" (relayApiValues 只认 anthropic-messages),模型行提交即 422 validation_failed;provider 行则落成 skip-with-warning 僵行。 - provider-config-queries: datalist 建议数组 → 两个 zod 枚举(各 seat 一源): modelApiFamilySchema = relayApiValues 三面(relayCatalogModelSchema.api); providerApiFamilySchema = 模型面 + openai-images(relayCatalogProviderSchema 的 union,get-bb#362 图像源族) - 控件:两处 family Input+datalist → 共享 ApiFamilySelect 单选下拉,选项即 schema.options;未选 = 无 api seat(服务端回退链管)。遗留 off-contract 值 显式呈为 "Out of contract · X" 项——不藏不吞,选中即替换 - 测试:Radix Select 在 jsdom 需 no-op hasPointerCapture/releasePointerCapture/ scrollIntoView 垫片(触发器 pointerdown 探测 + 内容挂载聚焦);新增四族下拉 断言、模型族三面断言(openai-images 会上 422,永不提供)、遗留值可见可换且 原样保存不静默改写 * feat(cap-provider-config): get-bb#448 Image Source 设置节——产图源显式单选写 D1 - 新 ImageSourceSettingsSection:当前生效源徽章 + 候选行 radio(含 None=generate_image 不可用),空态指向 Configured 节(零 openai-images 行时);PUT /api/v1/system/image-source 写面,toast 热生效说明 - queries/image-source-queries:GET/PUT + seat/projections 双失效 - app.tsx 注册 imageSource slot(configured 与 server 之间) - 测试:slot 注册/空态/切换写面/None 清座/422 判词不移动选择(23 tests) * feat(cap-provider-config): get-bb#447 discover 导入喂满目录元数据——行级 contextWindow/maxTokens/reasoning/thinking/cost 显式 unknown(lane/447) - queries: discover 响应 schema 对齐服务端 discoveredModelEntrySchema 正本 (id/name/api/reasoning/input/contextWindow/maxTokens/cost/thinking/ metadataSource)——旧 schema zod strip 掉全部元信息,是本 bug 面板侧根因 - discoveredModelToDraft: 已知目录 seat 直填编辑器字段(含 panel 阶梯成员 过滤),未知 seat 留空由 discoveredMeta 驱动显式 unknown 显示行; metadataSource 全量保留(models_dev/bundled/none/unavailable) - ConfiguredProviderSettingsSection: discover 合并走富化映射;行头渲染 Metadata 行(已知值 verbatim、未中选 seat 显式 unknown,绝不留空单元格); 判词新增 Enrichment 来源计数;说明文案同步 - 写面纯净: modelDraftToWire 丢弃 display seat(存储 catalog schema 拒收 thinking/metadataSource),存储行不受影响 - 测试: 新增 provider-config-queries.test.ts(映射/unknown 显示/写面纯净 /wire 解析),discover 合并测试升级为富化 wire 断言(36 tests) * feat(provider-config): get-bb#449 Web Search 引擎链可编辑——D1 正本 GET/PUT /system/web-search,零 env 回落(lane/449) * fix(provider-config): get-bb#484 harness 遗留部署通道投影收编——零 channel env 整块不渲染(mock 默认值不再误读为用户 provider),env 配置时重标注 legacy deployment relay channel 并写明各块正本归属 * feat(app): get-bb#486 selected-only 池行标注 Model unavailable——存档模型不再静默漂移(lane/486) picker 的 selected-only 池行(目录已不含、派发必然 422 fail-closed 的存档 选择)此前按普通可运行项渲染:触发钮只显示模型名,行无任何标记,用户无从 分辨存档模型已死。给 ModelPickerOption 加 unavailable 座(池行=active 目录 外),行 qualifier 与触发钮 tag 统一显示 Model unavailable,标题属性同步。 配对服务端半(cap 仓 get-bb#486):threads/:id/default-execution-options 读面 + selectedOnlyModels 存档池投影。 * fix(app): get-bb#479 projectless URL 打开项目线程整页 Not found——路由层按线程真项目规范化为项目形 项目所属线程经 /threads/:tid(projectless 形)打开时 useRouteState 把 route projectId 映射为 proj_personal,而 ThreadDetailView 以 `thread.projectId !== projectId` 守卫(views/thread-detail/ThreadDetailView.tsx:2420)渲染整页 "Not found"——线程真实存在,裸 404 是伪影(cap 仓 get-bb#476 走查伪影源;上游 bb 同码未修)。 SplitWorkspaceRoute 在路由层规范化:projectless 形 URL + 线程缓存行真项目非 personal → <Navigate replace> 至 /projects/:pid/threads/:tid,保留 ?message=/#event- 深链;缓存行只读(enabled:false),页面自身 bootstrap 负责 取数,零额外 GET;personal 线程与项目形 URL 不动(防环)。 测试:SplitWorkspaceRoute.projectless-thread.test.tsx 4 例(标准项目线程重定向 保 query/hash、行晚到后重定向、personal 线程保持、项目形 URL 不转);既有 SplitWorkspaceRoute.test.tsx 补 QueryClientProvider。@bb/app 全量 352 文件 / 2791 例绿,typecheck / lint / vite build 绿。 * feat(provider-config): get-bb#485 模型行族分离——产图行渲染产图座位(id/name/sizes/outputFormat/单价),零 chat 座位;族切换转换草稿+警示;discover 合并按 family 席位分派(image 行落产图草稿+座位推荐,chat 行拒收产图 id+Image Source 指针);Image Source 每候选行展示首个模型产图元信息 * feat(app): get-bb#499 模型不可用统一 UX——发送门前置拦发+灰显 last-use+一键回退卡 线程真相=last-use model id:目录漂移不改写、不恢复默认(零静默漂移,get-bb#486 同根)。 后端到 SPA 的一条规则,no special case: - composer 主动发送门:所选模型已不在目录(get-bb#486 池行)或存档脸为空(get-bb#496 删缝后 第零格)→ submit blocked:model-unavailable(title「Model unavailable — select a model」),替代「发送后 422」;活动 turn 仍保留 stop 闸。 - picker 灰显:不可用行名 trigger+菜单行改灰(沿用 Model unavailable 标注)。 - 一键回退卡:ThreadModelFallbackCard 扩 unavailable 形(同 bb.thread.model-fallback-dismissed 机制,occurrence=last-use model),建议最近可用行 (同 provider 其余模型→目录首行);点击才显式改写(PATCH /threads/:id model), 永不自动;空选择=同族第零格同一卡片。 - useThreadCreationOptions:preserveMissingModelSelection(component-local)—— 无存档选择不再恢复目录默认(空模型+拦发+必须选);selectedModelUnavailable 供门读取。 测试:app 套 2802 tests 全绿(新增 get-bb#499:卡片 3 例、submit 门 1 例、PromptArea 2 例、 hook 3 例);tsc --noEmit 通过;eslint 0 error。 * fix(app): get-bb#499 卡片点击后刷新存档面——PATCH 成功后失效 default-execution-options 查询(+test) * feat(cap-provider-config): get-bb#502 实验工具闸 D1 正本——Tool Capabilities 设置节(think/context_notes/checkpoint 三开关写 /system/tool-capabilities) 三闸源=D1 tool_capabilities 单行 seat(GET/PUT /api/v1/system/tool-capabilities, 零 env 回落——AGENT_DO_EXTERNAL_THINKING/_CONTEXT_NOTES/_CHECKPOINT 对已删除); absent row=omp 姿态全 off 空态徽章,首次翻转落行;整行三布尔替换写 (非 tri-state),成功 toast 标注 hot-applied on the next turn——无重部署; 写失败报服务器判词并重读正本(image-source 先例)。generate_image 不属本 seat (Image Source 节语义不动)。 测试:tool-capabilities-section.test.tsx 5 例(注册/空态徽章/整行替换写/ 单闸关闭保其余/失败判词不动开关)+ app.test 顺序断言更新;插件 59✓。 * refactor(cap-provider-config): get-bb#500 legacy relay harness block retired — Server section drops the deployment-channel projection (schema aligned with the server response losing 'harness') * refactor(cap-provider-config): get-bb#534 thinkingBudgetTokens 门禁退役——pi thinking 编辑器(transport+efforts 阶梯+default+requiresEffort),effortMap/Routing/Budgets 原样透传,legacy reasoningLevels 行显示兼容,'blank = deployment default' 文案删除 * fix(cap-provider-config): get-bb#538 web_search 面板链序按 position 渲染 + SearXNG 凭据行堆叠布局 链行此前按 availableEngines 字母序渲染,位置标签各说各话(position 3/1/2)——改为在链行按 chain position 升序、不在链行按词汇表固定次序排尾部,标签与渲染序一致(+排序回归测试)。 SearXNG 凭据行把 badge+三输入+两按钮塞进 SettingsWithControl 的 shrink-0 控件列,把 min-w-0 标签列挤到 ~30px 一行一词——新增 stacked 布局变体(label 在上、控件全宽在下),凭据行改 flex-wrap 控件行;Brave 单字段行保持原 side-by-side(+结构断言)。
ymichael
added a commit
that referenced
this pull request
Oct 22, 2026
## Human comments
## What was wrong
GitHub pull request status was core. The daemon ran `gh`, the server
assembled and watched the status, and the app, SDK, CLI and Tasks and
GitHub plugins each reached it through core routes and commands. Core's
PR UI couldn't leave core because there was nothing outside core to read
from.
## What changed
**New bundled, default-enabled plugin: `plugins/github-pr-status`
(`bb--github-pr-status`)**
- `host.ts` / `host/git-host.ts`: the in-worktree lookup moved from
`packages/host-workspace/src/git-host.ts`. It covers the current branch,
upstream and fork remotes (`getPullRequestTarget`), the merge-queue
GraphQL query, and `gh pr ready|ready --undo|merge`. It runs on the
environment's own machine with the daemon's shell `PATH`.
- `server.ts`:
- resolves the environment;
- serves a keyed `pullRequests` read, one row per environment id (up to
200), so a list of rows loads in one request;
- serves `markReady`, `markDraft` and `merge`, each of which re-checks
state before acting;
- registers `bb pull-request show|ready|draft|merge <environment-id>`.
- `store.ts` is the single place reads are cached (10 s), re-checked
while watched, and published when they change.
- PR snapshots are kept by PR URL, so environments sharing a PR share
one snapshot. Persistence, cross-row reuse and webhooks can feed this
one place later.
- While a row is watched (`bb.rpc.experimental_whileWatched`), it
re-checks every 30 s when checks are pending, auto-merge is on, the PR
is queued or mergeability is unknown. That is the same rule as core's
`pull-request-watch.ts`.
- Other triggers match main's:
- `status-changed` and environment create/delete fetch fresh;
- a completed turn re-checks through the 10-second cache;
- `git-refs-changed` only marks the cached row stale;
- `metadata-changed` re-checks only when the environment's branch
actually changed (one environment read, no `gh`);
- in the app, a cached row is re-fetched on mount, and again when the
page is shown if it's older than 30 s (1 h once merged or closed). Main
got this from TanStack's `refetchOnMount: "always"` and focus refetch.
Here it uses only the public read hook's `refetch`, in
`pull-request-queries.ts`.
- It invalidates the row's tag only when the row changed, and never for
a result a viewer is already receiving.
- `contract.ts` is the public contract. The read returns the fully
assembled shape the UI renders: state, attention, checks, review,
mergeability, auto-merge and merge queue.
**Watched reads**
- `bb.rpc.experimental_whileWatched` comes from `bb/5939-recut`,
mechanism only: the `plugin-read` subscription target, the server
registry and producer lifecycle, app query hooks holding the
subscription, per-target subscribe validation, and fake-host support.
- Left out: the SDK `experimental_watchRpc` /
`experimental_watchDiscovery`, the CLI `--watch` flags, and
plugin-signal scoping.
**Core reads and acts only through the plugin's public contract**
- The composer banner segment, Info row and thread view use
`experimental_useRpcKeyedQuery` / `experimental_useRpcMutation` with
`pluginId: "bb--github-pr-status"`
(`apps/app/src/hooks/queries/pull-request-queries.ts`). These are the
same calls another plugin makes, so moving the UI later is a cut and
paste.
- The app's RPC hook host now allows those hooks outside a plugin slot
when `pluginId` is explicit.
- `bb thread show`, Tasks (`listTaskPullRequests`, now one batched read)
and the GitHub plugin's `pullForThread` call the same RPC.
**Deleted from core**
- The server PR watcher, route, cache, `environment-pull-request` tag
and subscription target, and the PR action branches.
- The SDK `environments.pullRequest` / `markPullRequestReady` /
`markPullRequestDraft` / `mergePullRequest`, and `bb environment
pull-request …`.
- The `ThreadPullRequest` / `GitHostPullRequest` domain schemas.
- The daemon's `workspace.pull_request` and
`workspace.pull_request_action`. **`HOST_DAEMON_PROTOCOL_VERSION` 250 →
251**, compatible `[250, 251]`. 250 stays compatible because the server
no longer sends either command. 246–249 stay removed, as #6018 intended.
**`experimental_useSidebarThreadPullRequest`**
- Removed from `@get-bb/plugin-sdk/app`, the Plugin Guide and the docs.
- Published bundles keep working: the app installs
`retiredPluginSdkAppMembers` on the plugin runtime, backed by the
plugin's read. It returns `{ isLoading: false, pullRequest: null }` when
the plugin is disabled.
- **Builds never fail on it.** bb builds git-sourced marketplace plugins
at install and update, and rebuilds path and builtin plugins when the
SDK changes. So the `bb-plugin-build` runtime shim keeps the name in its
`@get-bb/plugin-sdk/app` export list. Importing it is a build warning
with the replacement call, which `bb plugin build`/`dev` print and the
server writes to the plugin's log.
- Migration guide:
`bb-plugin-authoring/references/frontend-pull-requests.md`. The runtime
shim is recorded in `docs/api_to_audit.md`.
- Plugin SDK 0.6.72.
**No wake.** Plugin host calls never wake a suspended host. A read on an
asleep or offline machine returns `unavailable`, which the UI renders as
no PR. Ready, Draft and Merge fail with "<machine> isn't available …".
**Behaviour change:** reads no longer wake a suspended host. On main,
`workspace.pull_request` used the `work` wake policy, so opening a
thread whose machine was suspended woke it.
## How you verified
**Request measurement in the real app.** Main and this branch ran as two
`pnpm start:worktree` instances side by side. Each had a thread on a
project checkout of PR #6117's branch while its checks were pending
(`checks_pending`, 0 failed). In each, a headless page opened the thread
and every `/api` request was recorded for 3 minutes, both runs at the
same time:
| | PR requests | when (s) | all `/api` requests |
|---|---|---|---|
| main | 6 × `GET /environments/:id/pull-request` | 1, 32, 64, 95, 127,
158 | 84 |
| branch | 6 × `POST /plugins/bb--github-pr-status/rpc/pullRequests` |
1, 33, 64, 96, 127, 159 | 88 |
The other `/api` differences changed from run to run (hosts, server-move
and execution-options in this run; system config, providers and timeline
in another). They come from background activity in the freshly restarted
branch instance and its extra sidebar threads, not PR traffic. An
earlier run found two bugs that are fixed here with tests:
- a re-check right after open published a result the opening read was
already fetching, costing one extra request;
- a watch that went idle when checks finished never resumed when a new
CI run started.
**Busy thread.** Main and this branch ran side by side again, each with
a thread on its own scratch PR in a private test repo
(`ymichael/test-project` #303 and #304, both closed and their branches
deleted afterwards). Each thread was open in a page. Both received the
same agent turn at the same moment: three commits, a `git fetch`, a
third commit between them, and a `git push`, which also started the
repo's CI. Over 5 minutes, a process monitor counted every `gh` started
by each instance, attributed by process ancestry and confirmed by
working directory. The page counted PR read requests. Two runs gave
identical counts:
| | `gh` calls | PR read requests |
|---|---|---|
| main | 7 (4 `pr view`, 3 merge-queue `api graphql`) | 4 |
| branch | 5 (3 `pr view`, 2 merge-queue `api graphql`) | 2 |
Before this fix the branch also re-checked with `gh` on every
`git-refs-changed` and `metadata-changed`, so a thread that commits and
pushes would have exceeded main.
**Focus refresh.** Main (`f909788625`) and this branch ran side by side
with the same open PR. A page on each hid and showed itself 5 times over
4 minutes, with gaps from 20 s to 60 s, so some shows were under the 30
s stale time and some over. A process monitor counted `gh` per instance:
| | `gh` calls | PR read requests |
|---|---|---|
| main | 5 | 5 |
| branch | 5 | 6 |
The extra read is the realtime connection resuming at the same moment
the page showed. Both reads hit the plugin's 10 s cache, so there's no
extra `gh`.
**Published plugins that use the retired hook.** On the review app, I
installed four git-sourced Community marketplace plugins that call
`experimental_useSidebarThreadPullRequest`. Each went through `bb plugin
install <plugin>@bb-community`, so bb cloned and built it at its
published commit. Each was then used as the sidebar thread list:
| Plugin | Commit | Builds | Warning in `plugin.log` | Renders PR badges
|
|---|---|---|---|---|
| t3sidebar | `8b535729dd` | ✅ | ✅ `src/ThreadCard.tsx:6` | ✅ |
| dockside | `e8736f3858` | ✅ | ✅ `components/inbox/thread-card.tsx:7` |
✅ |
| radar-sidebar | `9a20bfab7e` | ✅ | ✅
`components/radar/RadarThreadRow.tsx:23` | ✅ |
| thread-inbox | `c0981fb6fe` | ✅ | ✅ `src/ThreadCard.tsx:18` | ✅ |
gtd-sidebar fails to install on main too ("Could not resolve
@get-bb/plugin-sdk"), so it's unrelated. This test caught a real bug: T3
Sidebar crashed with a React hook-order error. React Compiler only
treats `use…` names as hooks, so it cached the
`experimental_useRpcKeyedQuery` call inside `useEnvironmentPullRequest`
and skipped it on re-renders with unchanged arguments. Core's own
callers pass a fresh options object on every render, which hid it. The
hooks are now imported under `use`-prefixed local names, and all four
plugins render.
**UI parity.** Screenshots of the same thread on main and on the branch
show the same composer banner segment (PR #6117 with the Merge split
button) and the same Info row (#6117, Open, Checks running). `bb thread
show` printed the PR through the plugin. On this PR, `bb pull-request
draft <env>` converted it to draft. The banner then showed "PR #6125 ·
Draft" with Mark ready, and clicking Mark ready showed the loading
toast, made the PR ready on GitHub, and returned the banner to the open
state with Merge.
**Tests**
- New plugin tests (75):
- host-entry fork, upstream and detached cases using a fake `gh`, moved
from host-workspace;
- the parse, action-argument and merge-queue table;
- the assembly table, moved from the server;
- store polling, publish-on-change and the viewer race;
- server reads, refusals, the no-wake refusal and the CLI.
- Also added: the `bb-plugin-build` retired-import warning test, the
focus-refresh staleness table and refetch tests, Tasks batched reads and
the disabled-plugin case, CLI `thread show` through RPC, and a hub test
for the `plugin-read` target.
- After merging #6135: `pnpm exec turbo run typecheck lint
--filter='...[origin/main]'` passes 162/162, and `turbo run test
--filter='...[origin/main]'` passes 115/115. That includes #6135's
host-workspace default-branch tests and the host-daemon
`watch-manager-startup` setup-window test. The host-workspace merge kept
#6135's code. This branch only removes the PR lookup from it.
**Not covered**
- `gh` ran only on a local host. Remote hosts use the same plugin
host-artifact path as the other host plugins but weren't exercised.
- Merge wasn't run against a real PR. It uses the same `gh pr merge
--<method>` arguments, covered by the host tests.
> AGENT GENERATED
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This folds the surviving intent from #245, #259, and #300 into one branch while keeping the model event-sourced instead of adding a separate table.
activeBackgroundSubagentCountand the app-side non-workflow background busy paths; foreground subagents stay on the delegation rendering path.spawnAgentparent linkage by stampingparentToolCallIdonto delegated child turns/events and projecting those child rows under the delegation.EventProjectionTurn; summary split boundaries now live as projection/row-building metadata.thread/resumeguard and background-only timeline working indicators.Relationship to open PRs
Validation
pnpm exec turbo run typecheck --filter=@bb/domain --filter=@bb/server-contract --filter=@bb/db --filter=@bb/thread-view --filter=@bb/agent-runtime --filter=@bb/server --filter=@bb/apppnpm exec turbo run test --filter=@bb/thread-view -- --run test/completed-turn-summary-rendering.test.ts test/timeline-cli-rendering.snapshots.test.ts test/completed-turn-grouping.test.tspnpm exec turbo run test --filter=@bb/agent-runtime -- --run src/claude-code/bridge/__tests__/bridge.test.ts src/codex/adapter.test.ts src/claude-code/task-translation.test.tspnpm exec turbo run test --filter=@bb/agent-runtime -- --run src/runtime-turn-state.test.tspnpm exec turbo run test --filter=@bb/app -- --run src/components/thread/timeline/ThreadTimelinePanelContent.test.tsx src/lib/thread-activity.test.tspnpm exec turbo run test --filter=@bb/app -- --run src/components/sidebar/ThreadRow.test.tsx src/components/sidebar/SidebarThreadSearchPanel.test.tsx src/views/RootComposeMobileRecents.test.tsx src/views/RootComposeView.test.tspnpm exec turbo run test --filter=@bb/db -- --run test/data/events.test.tspnpm exec turbo run test --filter=@bb/server -- --run test/public/public-thread-data.test.tspnpm exec turbo run test --filter=@bb/server-contract -- --run test/contract.test.tsgit diff --check