Use indexed lookups for task lifecycle events - #3212
Merged
Conversation
## Human comments ## What was wrong When a user opened a child folder after scrolling deeply through a large directory, the virtualized folder browser retained the prior directory's scroll offset. A similarly large child therefore opened partway down rather than at its first entry. ## What changed The shared remote path browser now resets its virtualizer offset whenever it navigates to a directory. Its virtualizer option callbacks are also stable between unrelated renders, avoiding unnecessary measurement churn. Added a regression for browsing between two large directories. No wire, CLI, guide, or protocol changes. ## How you verified - `pnpm exec turbo run test --filter=@bb/app --force -- --run src/components/dialogs/RemotePathBrowser.createFolder.test.tsx` - `pnpm exec turbo run typecheck --filter=@bb/app` - `pnpm exec oxfmt --check apps/app/src/components/dialogs/RemotePathBrowser.tsx apps/app/src/components/dialogs/RemotePathBrowser.createFolder.test.tsx` - `git diff --check` Fixes # > AGENT GENERATED
#3200) ## Human comments ## What was wrong Markdown image URLs were only routed to host files when a caller supplied `linkRouting.localImage`. Assistant messages supplied it, but file previews and several other Markdown surfaces did not. Consequently, absolute host paths were requested from the app origin and relative paths were resolved against the SPA URL instead of the Markdown file's directory. For example, `docs/report.md` containing `` should load `docs/images/chart.png`. ## What changed - Share file-image routing across workspace, project, thread-host, host-scoped, and thread-storage previews. Resolve relative images from the Markdown directory using the existing local-path resolver, and use existing content endpoints for each source. - Share lease routing with local skill previews and message routing across assistant, user, generated/system, and thread-context Plugin SDK Markdown. Preserve intentionally suppressed images and explicit caller routing overrides. - Preserve existing root restrictions for scoped relative paths and existing trusted absolute-host access. These restrictions are endpoint constraints, not a sandbox for Markdown that can also reference absolute host paths. - Consolidate seven backend file-serving handlers through one host-read/error-handling function, retaining their existing target selection, caching, HTML sandbox headers, and size limits. No public plugin API, CLI, endpoint contract, or server/daemon wire behavior changes; no daemon protocol bump is needed. ## How you verified - Added rendered-preview regression coverage for absolute and file-relative images, all five preview adapters, root escapes, explicit routing overrides, nested lease paths, user/system messages, Plugin SDK Markdown, and local skill files. - `pnpm exec turbo run typecheck lint test --filter=@bb/app`: 481 test files passed; 3,890 tests passed and 3 skipped. App lint completed with 182 warnings and 0 errors. - `pnpm exec turbo run typecheck lint --filter=@bb/server`: typecheck passed (the server package has no lint task). - `pnpm exec turbo run test --filter=@bb/server -- test/hosts/daemon-file-response.test.ts test/files/host-file-routes.test.ts test/public/public-projects-local-host.test.ts test/public/public-project-workspace-routing.test.ts test/public/public-thread-data.test.ts`: 5 files / 100 tests passed, including byte responses, root propagation, host selection, validators, HTML policies, and error mapping. - Rebuilt and started the final code with `pnpm start:worktree`; confirmed HTTP 200 and daemon connectivity. The dev QA thread has workspace and thread-storage fixtures; the other preview types are covered by automated tests, not completed manual demos. No linked GitHub issue. > AGENT GENERATED
## Human comments ## What was wrong The loaded-empty branch picker followed a terminal fallback that omitted the Branches section and used double vertical padding (`py-6`), while the loading state rendered within the section with the standard compact row spacing. ## What changed Loaded-empty results without a current selection now remain in the options section, preserving the Branches heading and the same spacing as loading. The terminal fallback also uses the compact row padding. Added an Empty states Ladle story and a regression test. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- --run src/components/pickers/BranchPicker.scroll.test.tsx` - `pnpm exec turbo run typecheck --filter=@bb/app` - `pnpm exec turbo run lint --filter=@bb/app` (passes with existing warnings) - Captured before/after Ladle screenshots using SawyerHood/doobie. Fixes # > AGENT GENERATED
…3147) ## Human comments ## What was wrong Built-in questions and the ask-user-question plugin duplicated their form UI, state, and shortcut handling. Their containers also diverged: built-in prompts had an orange border and collapse controls while plugin forms used a neutral border without collapse controls. Compact permission prompts wrapped across multiple lines on mobile. ## What changed - Use one `PendingInteractionShell` for built-in questions, approvals, plan reviews, and plugin forms: neutral border, attention dot, title/caret disclosure controls, and a single-line compact mobile layout. - Use one `QuestionForm` and answer-state implementation for built-in and plugin questions, including tabs, single/multiple selection, Other/free text, validation, navigation, and option previews. Delete the duplicate implementations and consolidate form tests. - Keep small transport adapters: built-in questions submit a `user_answer` resolution and cancellation stops the thread; the plugin submits `{answers}` and retains its interaction cancellation behavior. - Route both forms through the app's configured question shortcuts, limited to the focused pane. Collapsing pauses shortcut effects while preserving draft text, selections, and the active question. Typing in an input does not select answers. - Share the internal question-host React context across plugin bundles through a runtime shim. No public Plugin SDK, CLI, or server/daemon wire changes. - Title taps toggle the form; approval actions remain separate controls in the expanded form. Escape collapses and restores focus. Attention-dot policy is unchanged by the form refactor. ## How you verified - App: 50 targeted tests passed across shared form/state, native adapter/shell, plugin composer, shortcuts, and runtime installation. The native adapter regression also checks the submitted resolution and thread cancellation. - Ask-user-question plugin: 30 tests passed, including submission, cancellation, invalid payloads, server behavior, and translation. - Plugin build: 11 tests passed, including a bundled-module check that the question-host hook uses the host runtime instance. - `pnpm exec turbo run typecheck lint --filter=@bb/app --filter=@bb/shared-ui --filter=bb-plugin-ask-user-question --filter=@bb/plugin-build` passed. Formatting and diff checks passed. - Dev-browser against native and actual plugin-source Ladle fixtures: desktop selections, multiselect/free text, mobile single-line collapse, title-tap expansion, and retained draft/current question. Earlier permission checks cover compact decisions and mobile expansion. - Rebuilt and restarted using `pnpm start:worktree`; app responds HTTP 200 and the host daemon connects. - Verification inventory has an unrelated existing failure: unmapped `browser` CLI family. ## Screenshots [Before / After screenshot gallery](https://get-bb.github.io/reports/prs/3147.html) — 19 surfaces/states × desktop/mobile × compact/expanded, compared with PR base `6cdb4ba612`. Both columns use the same fixture data and viewport sizes; select a surface to compare it directly. Plugin forms without an earlier compact mode show their actual expanded Before view with an explicit label. Each image opens at full size. All 38 compact captures measure 38px high with no horizontal overflow. This includes built-in and plugin questions, command/file/tool permissions, child-thread approvals, plan review, resolving requests, secrets, unavailable plugins, errors, Other answers, and free-text drafts. Compact errors use a red dot without competing with the title; expanded errors show the full message. Compact mode now shows the short label (for example, **Approval needed**), dot, and caret; details, source links, and decisions appear after expansion. Expanded headings remain readable, with child-thread links truncating first. Attention dots align with the first line when headings wrap. All 3,935 app tests passed (3 skipped), along with typecheck and lint. | Desktop compact | Mobile compact | | --- | --- | |  |  | | Desktop expanded | Mobile expanded | | --- | --- | |  |  | Browser captures use synthetic Ladle previews at 1280px and 390px viewport widths; mobile is Chromium emulation. No credentials entered. Following the compact-error change, 20 shell/controller tests, app typecheck, and lint passed. > AGENT GENERATED
## Summary Root cause: the Files settings navigation item used the folder icon instead of the existing file icon used by file @-mention rows. Change: use the shared `File` icon and assert that navigation metadata in the settings test. ## Verification - `pnpm exec turbo run test --filter=@bb/app -- --run src/components/settings/settings-nav.test.tsx` - `pnpm exec turbo run typecheck --filter=@bb/app --filter=@bb/shared-ui` > AGENT GENERATED
## Human comments ## What was wrong Completed reasoning has two valid persisted channels, summary and body, whose deltas can arrive interleaved. The final parser originally selected only one channel; the first version of this fix retained both but rebuilt them as all summary followed by all body, so a live `S1 → B1 → S2` stream reopened as `S1 → S2 → B1`. This became user-visible after [the completed-thinking history change](6e83eb2). ## What changed The shared reasoning projection now keeps the arrival-order buffer and separately totals each channel. When both totals exactly match the completed item, completion preserves the live buffer; when deltas are missing or the provider corrects the final payload, the combined completed summary and body remain the fallback replacement. The same decision feeds normal and late post-interruption completion, preserving the generic Thought row, timing, status, single-channel behavior, and deterministic live/restored parity. There are no wire, database, CLI, Plugin SDK, configuration, documentation, or mobile changes. ## How you verified - Added an assembler-valid `summary S1 → body B1 → summary S2` regression that failed before the projection change and passed after it across active, just-completed, restored, and replayed projections. - `pnpm exec turbo run test --filter=@bb/thread-view --force`: 23 files and 379 tests passed. - `pnpm exec turbo run typecheck --filter=@bb/thread-view --force` passed. - Repeated both Turbo checks after rebasing onto current local `origin/main`. - DevBrowser at the same real route and 1440×1000 viewport showed the completed row change from `S1 → S2 → B1` to the live `S1 → B1 → S2` order. Fixes #1248 > AGENT GENERATED
## Human comments ## What was wrong The shared timeline window closure treated every scoped item that crossed a page boundary as belonging to the newer sequence window. For a completed thinking item whose start was in an older user turn but whose completion arrived after the next prompt began, standard turn pagination therefore separated the lifecycle: the newest page rendered the completion beneath the wrong prompt as “Thought for 0ms,” and loading the older page left the originating turn without its thought. This followed [6e83eb2](6e83eb2) from #3066. A thought that never receives `item/completed` could also disappear from both pages when its original turn moved into older history. ## What changed - Expose the completion sequence in the existing item-span query. - Give the shared whole-item closure explicit origin-versus-later page ownership. - Keep an unfinished thought on its origin page until it receives a newer-page event; then keep the active lifecycle on the newer page without duplication. - Assign completed items to their originating user segment while preserving existing later-page ownership for byte and details windows. - Constrain span lookups to item events and distribute their thread/type constraints into each scoped lookup, allowing SQLite to use the existing `(thread_id, turn_id, type, item_id, sequence)` index. - Guard that multi-turn query plan and the unfinished and late-completed latest/older/details behavior with focused regressions. There are no database schema or migration, host-daemon wire, CLI, documentation, UI-component, pagination-limit, or mobile changes. ## How you verified - The focused regression fails before the fixes: late completion appears on the newest page, while a never-completed thought disappears from both pages. - `pnpm exec turbo run test --filter=@bb/server -- test/services/threads/timeline-in-turn-window.test.ts` — 28/28 passed after rebasing onto current local `origin/main`. - `pnpm exec turbo run test --filter=@bb/db` — 448/448 passed. - `pnpm exec turbo run typecheck --filter=@bb/server` and `pnpm exec turbo run typecheck --filter=@bb/db` — passed. - The committed multi-turn `EXPLAIN QUERY PLAN` regression proves the span lookup uses `events_thread_turn_type_item_sequence_idx`; no new index is created. - DevBrowser verified the real app history boundary: after loading the older page, the original turn expands through “Worked for 5s” to the interrupted “Thought for 2s,” its text, and “Stopped manually,” with no thought under the next prompt. > AGENT GENERATED
## Human comments ## What was wrong The tooltip test compared a 250 ms real-time sleep with the production 700 ms dismissal timer. If the test worker was suspended past 700 ms, React's async `act` could process the already-due dismissal before returning from the shorter sleep, so the intermediate assertion observed null even though production retained the correct 700 ms delay. A bounded 750 ms event-loop suspension reproduced the exact current-main assertion failure. ## What changed Theme Preview's test now switches to fake timers only after focus has opened the tooltip, then checks the exact dismissal boundary: content remains at 699 ms and disappears after the final 1 ms. Production code and its 700 ms delay are unchanged. ## How you verified - Before: focused current-main test passed unloaded and in 6 contended runs; a bounded 750 ms event-loop suspension reproduced `expected null not to be null` at the intermediate assertion. - After: focused regression test passed. - After: 10/10 focused runs passed with four bounded CPU-load workers and two concurrent single-worker Vitest lanes; cleanup left zero load or Vitest processes. - `pnpm exec turbo run test --filter=bb-plugin-theme-preview --concurrency=2 --output-logs=full --summarize=false` (74 tests passed) - `pnpm exec turbo run typecheck --filter=bb-plugin-theme-preview --concurrency=2 --output-logs=full --summarize=false` - `pnpm exec turbo run build --filter=bb-app --concurrency=2 --output-logs=full --summarize=false` (12 tasks passed) - `git diff --check` > AGENT GENERATED: by GPT-5.6-Sol
## Human comments ## What was wrong The Docs jsdom suite provided `matchMedia` but no Range geometry boundary. jsdom 29 exposes Element geometry while omitting `Range.getClientRects()` and `Range.getBoundingClientRect()`. ProseMirror 1.41.8 calls those methods while reconciling an edited table and scrolling its selection, so the uncaught TypeError aborted the editor update before the existing 700 ms autosave could call `saveNote`. The 2-second assertion in [the failing current-main run](https://github.com/get-bb/bb/actions/runs/33923531372) reported that upstream exception rather than a slow save. ## What changed The Docs app test setup now provides zero-sized, browser-shaped Range geometry for jsdom and restores the original property descriptors after every test. No app behavior, timer, assertion, wire contract, CLI, or documentation changed. ## How you verified - Confirmed the cited push run failed at `c49ba3f546d1049f0d0f4e51850908028a318d52` with `TypeError: target.getClientRects is not a function` from ProseMirror `singleRect` / `coordsAtPos`. - Confirmed directly against the installed jsdom 29 environment that both Range geometry methods are undefined while Element geometry exists. - `pnpm exec vitest run --config plugins/docs/vitest.config.ts plugins/docs/app.test.tsx -t "renders and autosaves editable Markdown tables" --reporter verbose --maxWorkers=2` (1 passed; unchanged 2-second wait). - `pnpm exec turbo run test --filter=bb-plugin-simple-notes --concurrency=2 -- --maxWorkers=2` (66 passed). - `pnpm exec turbo run typecheck --filter=bb-plugin-simple-notes --concurrency=2`. - `pnpm exec turbo run build --filter=bb-plugin-simple-notes --concurrency=2 --force`. - `pnpm exec prettier --check plugins/docs/app.test.tsx`. > AGENT GENERATED: by GPT-5.6-Sol
…oved (#3099) ## Human comments Similar to the other PR I just opened, this almost drove me nuts :sweat_smile: I'm now rebuilding my container with both patches and will report back if anything goes wrong. ## What was wrong When `update_environment_directory` moved a thread to a new environment (e.g. the agent deleted the old worktree first), the next turn died at the bridge: `thread/resume` rejected with `Cannot resume: the pi session's working directory "...no longer exists."` because the pi session file still recorded the old cwd — even though the runtime supplied a valid, existing requested cwd. In the observed case the thread was effectively bricked until an external stop/recovery. Root cause: `resumedSessionMissingCwd` (`plugins/provider-pi/src/bridge/bridge.ts:532`) rejects the whole resume whenever the persisted cwd is gone, without considering the requested cwd. ## What changed - `thread/resume` now only rejects when the **requested** cwd is also missing — i.e. there is genuinely nowhere to run. With the persisted cwd gone but a valid requested cwd present, resume proceeds. - `constructPiThreadSession` prefers the persisted session cwd only when the directory still exists (new `usablePersistedSessionCwd()`), falling back to the requested cwd for the child spawn and delta-translation `sessionCwd`. When the persisted cwd still exists, behavior is unchanged (session reports the header cwd). - No wire/protocol changes; provider-bridge-only. No CLI/guide/doc updates needed. ## How you verified - New regression test `resumes at bb's requested cwd when the session header's cwd was removed` (`bridge.round2.test.ts`): **red** before the fix (reproduced the exact user error text), **green** after. - Existing `a resumed thread reports the session header's cwd, not the cwd bb asked for` still passes — persisted-cwd precedence preserved when the dir exists. - `bridge.round2` + `bridge.settings` + `bridge.checkpoint-fork`: 15/15 pass. `turbo run typecheck --filter=bb-plugin-provider-pi`: clean. > AGENT GENERATED > <sub><em>Generated with "Omen Alpha" (Opencode Go) via BB / Pi</em></sub>
## Human comments ## What was wrong Realtime workspace-change invalidation deleted every cached per-file patch before the refreshed diff table of contents could establish whether its contents had changed. Visible cards therefore regressed from rendered content to loading skeletons, including for filesystem changes that produced the same Git diff. ## What changed Retain per-file patch cache across realtime workspace invalidations. When the diff table of contents refreshes, refresh visible cached patches in place while retaining their rendered content; replacement patch content is rendered only after it arrives. Initial patch seeding only notifies React when its patch payload actually changed. No wire or CLI changes. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- --run src/hooks/realtime-cache-effects.test.ts src/hooks/queries/use-environment-diff-patches.test.tsx` - `pnpm exec turbo run typecheck --filter=@bb/app` - `pnpm exec turbo run lint --filter=@bb/app` - `git diff --check` Fixes # > AGENT GENERATED
## Human comments
Choosing a color palette in different editors (take Zed) allows you to
preview when scrolling through them on Command Palette - wanted to bring
similar functionality here.
If I had to choose, I would also do a transition-colors but deemed that
unnecessary.
## What was wrong
Choosing a palette in Settings → Appearance required users to choose the
selection in order to preview it. Takes a lot of time to review themes
and a bit harder to choose from there.
## What changed
- Hovering or arrow-keying over a palette item now applies that theme to
the current window as an ephemeral preview. Leaving the item, closing
the menu, or leaving Settings restores the committed theme. Selecting an
item keeps the preview in place until the saved appearance lands, so
there is no flash back to the old palette.
- `apps/app/src/lib/themes/index.ts` and `lib/code-theme.ts` gain a
preview layer over the committed palette CSS and code theme. Previews
never touch the localStorage pre-paint cache, and a committed apply
always supersedes a preview.
- New `useAppThemePreview` hook resolves themes through react-query
(`systemTheme` query key, 60s stale time) and prefetches every listed
palette when the menu opens so the first hover applies from cache.
Resolved themes are invalidated with system config, including realtime
`config-changed`.
- New route `GET /api/v1/settings/themes/:id` resolves a built-in,
custom, or plugin theme without activating it (400 for a malformed id,
404 for a missing custom theme). The server shares the validation with
`PUT /settings/appearance`. No host-daemon wire change, so
`HOST_DAEMON_PROTOCOL_VERSION` is unchanged.
- SDK: `sdk.theme.resolve({ themeId })`. CLI: `bb theme show [id]
[--css]` resolves a theme by id without activating it. Updated the CLI
guide (`bb-guide-customization.md`) and the bb-cli skill reference
(`theme-commands.md`).
- Bumped `@get-bb/plugin-sdk` to 0.4.48 with
`scripts/bump-plugin-sdk.mjs` because the bundled `ThemeArea` type
changed and 0.4.47 is already on npm.
## How you verified
Hand-tested.
https://github.com/user-attachments/assets/441a8fd8-e235-482c-958c-60a81044490d
Additionally:
- New tests: `apps/app/src/lib/themes/preview.test.ts` (preview does not
persist, commit supersedes preview),
`apps/app/src/hooks/useAppThemePreview.test.tsx` (stale resolutions
ignored, cache reuse, prefetch, unmount clears),
`apps/app/src/views/SettingsView.palette-preview.test.tsx`
(focus/blur/Escape/select flows, prefetch on open),
`packages/sdk/test/sdk.test.ts` (`theme.resolve` transport), and
`tests/integration/fake/smoke/theme-resolve.test.ts` (built-in, custom,
404, 400, active theme untouched).
- `pnpm exec turbo run typecheck lint` across the repo passed.
- `pnpm exec turbo run test --filter=@bb/app --filter=@bb/cli
--filter=@bb/sdk --filter=@bb/server-contract` passed (483 app files, 54
CLI files, 6 SDK files, 7 contract files).
- `node packages/plugin-sdk/scripts/check-npm-version-guard.mjs` passed
for 0.4.48.
- `oxfmt --check` on every changed file and `git diff --check` passed.
Fixes: no linked issue.
> AGENT GENERATED
---------
Co-authored-by: vznh <nailguninthebackpocket@gmail.com>
Co-authored-by: Sawyer Hood <sawyerjhood@gmail.com>
Co-authored-by: Michael Yong <wrong92@gmail.com>
…ssions (#3203) ## Human comments ## What was wrong Account Pooler used the process-wide fetch dispatcher for upstream requests. In #3201, a cached upstream HTTP/2 session was destroyed but continued failing requests immediately with `TypeError: fetch failed`, caused by `The session has been destroyed` (`ERR_HTTP2_INVALID_SESSION`). This was observed for both the Anthropic (`api.anthropic.com`) and ChatGPT/Codex (`chatgpt.com`) origins on the same host: whichever origin's cached session was destroyed failed every request to it, while the other origin could remain healthy. Clearing the affected origin's cached pool restored a real pooled request. The hub discarded the fetch cause, leaving only a generic 502 and obscuring the transport failure. The event that originally destroyed the session remains unknown. ## What changed Give Account Pooler an owned `EnvHttpProxyAgent` with HTTP/2 disabled, covering proxied inference, token refresh, usage requests, and OAuth login. Because this transport is used for every upstream, the fix applies uniformly to all providers (Claude and ChatGPT/Codex), for both the plain HTTP path and the Codex realtime path, which forwards its upstream leg through the same hub transport. It honors proxy environment variables and avoids reuse of the process-wide HTTP/2 pool. Disposal waits for the hub's existing request-drain window before destroying connections. No request retries are added. Retain the original fetch cause internally and log only a recognized transport error code, without raw error messages or credentials. Document the transport behavior in `PLUGIN_OVERVIEW.md`. There are no CLI or server/daemon wire changes. ## Trade-offs - Upstream traffic now uses HTTP/1.1 instead of HTTP/2. This is the mechanism that avoids reusing a poisoned shared H2 session, and undici's HTTP/1.1 pool discards dead sockets rather than caching a destroyed session. The cost is losing H2 multiplexing: under high concurrency the pooler opens more connections. Both providers support HTTP/1.1 including streaming, so this is an efficiency trade, not a correctness one. - This isolates the pooler from the shared dispatcher and avoids the observed failure mode. It does not identify or fix the underlying trigger that destroys a session, and it adds no retry, so a connection that dies mid-request still fails that request. ## How you verified - `pnpm exec turbo run test typecheck --filter=bb-plugin-account-pool`: 261 tests passed across 10 files; typecheck passed. - `pnpm exec turbo run build --filter=@bb/server`: passed. Scoped Oxlint and `git diff --check` passed. - Regression coverage injects the observed destroyed-session error at the default-fetch boundary and verifies that the owned transport completes actual local requests. A committed TLS/ALPN test also verifies HTTP/1.1 against a server offering both protocols; temporarily enabling HTTP/2 makes it fail with `h2` / `2.0`, as expected. The natural session-destruction trigger remains unknown. - A streamed-response disposal regression failed before adding the drain wait and passes after it. Additional tests cover cancellation, no replay after an accepted POST loses its connection, and sanitized logging. - All six transport tests, including TLS/ALPN negotiation, also passed directly under Node 22.23.2 as a compatibility investigation. The full plugin suite ran on Node 26.7.0. Local transport tests bypass ambient proxies. - Cross-provider transport check: unauthenticated requests through the new transport reached both `https://api.anthropic.com` and `https://chatgpt.com`, each returning the expected HTTP 401 rather than the destroyed-session error, confirming the fix covers both providers. The installed BB runtime was not replaced with this build. Fixes #3201 > AGENT GENERATED
## Human comments ## What was wrong Assistant messages passed incomplete Markdown straight to the renderer. During streaming, unfinished bold and inline code showed their delimiters, while partial links and images exposed unfinished syntax. ## What changed Use pinned `remend@1.3.1` on the displayed live tail in `AssistantConversationMessage`. This also covers the first paragraph, before a settled/tail split exists. Incomplete links render as plain text and incomplete images stay hidden. Settled content, completed messages, and the original text used by message actions remain unchanged. Tails containing directives or triple code fences bypass repair. Probes found that remend can alter partial directive syntax and tilde-fenced code. Repair resumes after those move into the settled prefix. Unrelated HTML, math, setext, comparison, and single-tilde transformations are disabled. https://github.com/user-attachments/assets/3b7de693-7fa2-4f6c-adba-c56db0200503 ## How you verified - Captured six failing repair regressions before the implementation. - Passed 72 tests across the conversation, streaming split, directive, and local-link suites. These include real-renderer checks for formatting, link/image completion, directive attributes, thread mentions, file navigation, and copying original source. - Passed `pnpm exec turbo run typecheck lint --filter=@bb/app`. Lint reports 184 warnings and no errors; none names the changed conversation component. - Passed `pnpm exec turbo run build --filter=@bb/app` and formatting checks on the changed source files. - Passed eight browser replay assertions using the actual conversation component from the source-built dev app. Checked partial bold/code, split tails, incomplete links/images, literal directives, tilde fences, and stopped generation. Captured a short streaming comparison. This is a component replay, not an end-to-end provider turn. Focused test command: ```sh pnpm exec turbo run test --filter=@bb/app -- \ src/components/thread/timeline/ConversationMessageContent.streaming.test.tsx \ src/components/thread/timeline/ConversationMessageContent.streaming-render.test.tsx \ src/components/thread/timeline/ConversationMessageContent.test.tsx \ src/components/thread/timeline/streaming-markdown-split.test.ts \ src/components/ui/markdown-message-directives.test.tsx \ src/components/ui/markdown-local-file-link-normalize.test.ts ``` > AGENT GENERATED --------- Co-authored-by: Sawyer Hood <sawyerjhood@gmail.com>
## Human comments ## What was wrong Live thinking used a separate indicator with italic details, while completed thoughts became generic system rows with monospace details and lost their expansion state. Persisted thoughts also broke activity groups when interleaved with tool work. Routine provider environment resolution also appeared in the conversation unconditionally, and development builds ignored the existing unhandled-event visibility setting. ## What changed - Give reasoning its own timeline operation kind and a stable identity across completion. Active and completed thoughts share disclosure styling and prose details, and preserve manual expansion within the mounted thread. Completed thought headers use the same dimming as completed tool rows. Only completed thoughts display the AiBrain01 icon; live Thinking has no leading icon. Completed durations use the existing timeline treatment, which hides durations of one second or less. - Replace `showUnhandledProviderEvents` with `showDiagnosticEvents`, covering environment resolution and unhandled provider events. It defaults to false in all builds; existing saved preferences carry over. Preserve the old setting name as a deprecated API alias for existing SDK clients, normalizing both names at the route boundary. Warnings and model fallback remain visible, and completed warnings/deprecations no longer receive completed-row dimming. - Update timeline cache options, API/SDK settings, CLI coverage, configuration docs, guide templates, and the CLI skill. The CLI setting is `bb settings general showDiagnosticEvents <true|false>`. - Bump the plugin SDK to 0.4.49 because the settings and timeline type changes alter its bundled declarations. - Carry a canonical reasoning disclosure ID separately from the structural row ID so delegation nesting preserves expansion. Add a reasoning lifecycle story and regression coverage. No server/host-daemon wire payload changed, so no daemon protocol bump is needed. - Register AiBrain01 across shared and mobile icon maps and regenerate the plugin icon registry. Use the existing Bot icon for Tasks agent presets in the sidebar and management panel. - Allow 60 seconds for the existing Account Pool 4,096-session eviction test after repeated CI timeouts at 20 seconds; retain the workload and assertions. - Include completed thoughts in activity groups, retaining chronological details while deriving labels only from work. Thoughts-only sequences stay visible, and messages, warnings, and approval boundaries still split groups. Update shared expansion handling and CLI rendering for mixed children. - Exclude disabled diagnostics in database selection, event/byte budgets, nested-child hydration, and effective timeline cache sequences. Retain legacy Claude model-fallback events. - Normalize canonical reasoning IDs in provider replay comparisons. Recognize legacy fallback records by their validated event shape in both SQL selection and projection, removing provider-specific branching from this path. ## How you verified - CI follow-up: all 56 provider parity tests and 238 bridge-protocol tests pass, as do fallback extraction/visibility regressions and the provider-literal architecture guard. - Review regressions cover old SDK payloads and read-modify-write updates through either setting name, canonical nested reasoning identity and expansion, a hidden 5 MiB environment event, unchanged visible cache sequence, and legacy model fallback. Database suite (449 tests), nested projection suite (74), reasoning UI suite (4), and affected typechecks/lint pass. - Dimming and reasoning UI tests pass (14 tests); browser confirms completed thought headers use the same 0.4 opacity as completed work. - Grouping verification: 382 thread-view tests and 254 client-core tests pass; all three reasoning UI tests pass. App, mobile, CLI, thread-view and client-core typechecks and lint pass. Browser fixture confirms grouped thoughts expand and retain expansion when a running exploration bundle becomes a closed exploration/edit step. - Updated reasoning lifecycle tests pass: live thinking omits the icon, completion adds it, and expansion persists. - Tasks plugin typecheck passes after switching preset icons to Bot. - Plugin registry typecheck/tests and the Account Pool eviction test pass locally. - App lint and the plugin SDK npm version guard pass after fixing the expansion callback dependency and bumping the SDK. - Turbo typechecks passed for app, server, CLI, database, domain, thread-view, and server-contract. - Full database and thread-view suites passed; subsequent targeted projection/title tests covered the added stable-identity regression. - Focused app tests cover expansion through completion, independent subsequent thoughts, icon-free live thinking and icons on completed thoughts, warning readability, and settings cache invalidation. CLI settings tests passed. - Server timeline-window/cache tests and production/development diagnostic visibility tests passed, including toggling cached results off, on, and off while retaining warnings. - In an isolated source app, exercised the Settings toggle, verified persistence after reload and through the source CLI, and checked diagnostic visibility in a synthetic thread and expanded turn details. Browser-driven production component checks confirmed expanded Thinking becomes expanded Thought with preserved prose styling. - `pnpm start:worktree` built and served successfully; server and host-daemon health checks passed. Browser checks used Chromium on macOS, not iOS or a real provider turn. - `git diff --check` passed and the branch merges cleanly with current main. > AGENT GENERATED
## Human comments ## What was wrong A pinned request that received a short per-minute 429 waited independently from another request for the same session. The second request could finish its remaining hold timer while the wall clock still reported the account as held; because routing tracked only that it had waited once, it returned 429 without making its permitted retry. This produced the four-attempt sequence seen in [current-main CI](https://github.com/get-bb/bb/actions/runs/33977434733) and again in [an unrelated PR run](https://github.com/get-bb/bb/actions/runs/34155037225). ## What changed Account Pool now records the active per-account pacing flight with its exact hold deadline. Requests that arrive during that hold join the same completion promise and then make their one permitted retry. A completed successful flight remains observable until the wall deadline expires, while a renewed or failed attempt releases it so repeated hold extensions stay bounded. Hub restart also clears the in-memory flights. The regression keeps the original 250 ms hold and pins the clock one millisecond before the deadline, proving the concurrent request follows the pacing completion event instead of returning a clock-bound 429. No hold duration, retry budget, timeout, wire contract, CLI, or public API changed. The separate 4096-session LRU timeout is not part of this change. ## How you verified - Red: the controlled-clock regression failed before the implementation with `expected 429 to be 200`. - Green focused: the short-hold regression and repeated-hold bound both passed. - `pnpm exec turbo run test --filter=bb-plugin-account-pool --concurrency=1 -- --maxWorkers=1 --no-file-parallelism` — 9 files and 253 tests passed. - `pnpm exec turbo run typecheck --filter=bb-plugin-account-pool --concurrency=1` — 4 Turbo tasks passed. - `pnpm exec turbo run build --filter=bb-plugin-account-pool --concurrency=1` — the two generated prerequisites passed; this source plugin has no package build script. - `pnpm exec oxfmt --check plugins/account-pool/src/hub.ts plugins/account-pool/src/server.test.ts` and `git diff --check` passed. > AGENT GENERATED: by GPT-5.6-Sol
## Human comments ## What was wrong The affinity-eviction integration test filled the production capacity by sending 4,095 sequential requests through the complete fake plugin host. Each request repeated parsing, account/quota selection, persistence, routing, and response consumption even though the eviction itself is an O(1) `Map` operation. On the untouched Intel baseline the single test took 10.7–12.2 seconds without real network I/O, leaving little headroom under its original 20-second timeout. The [current-main failure](https://github.com/get-bb/bb/actions/runs/34256473278/job/102163451056) ran the packages shard with four concurrent Turbo package tasks on four vCPUs while Account Pool Vitest also parallelized its ten files; that systemic nested CPU oversubscription made the serial test intermittently exceed 20 seconds. The merged 60-second timeout only masked this structural cost. ## What changed Thread an internal `maxAffinityBindings` construction option into the hub and apply the existing 4,096 production default once at that boundary. The end-to-end LRU test injects a capacity of four and keeps the same recency refresh, retention, next-oldest, and eviction assertions while removing 4,092 redundant HTTP requests. The custom 60-second timeout is removed, restoring the normal test timeout as the hang detector. There are no CLI, user configuration, SDK, server/daemon wire, migration, or protocol-version changes. ## How you verified - Before the change, the focused untouched-main case took 12,163ms unloaded and 10,700–10,820ms in two bounded contention samples; CI independently recorded the exact 20-second timeout under the packages shard. - After the change, the focused case passed in 125–131ms through Turbo on the rebased commit. - `pnpm exec turbo run test typecheck --filter=bb-plugin-account-pool --concurrency=2 --force`: 10 files and 261 tests passed; typecheck passed. - `pnpm exec turbo run typecheck --filter=bb-plugin-account-pool --concurrency=2 --force`: passed after rebasing onto current main. - `pnpm exec turbo run build --filter=@bb/server --concurrency=2 --force`: passed before and after the rebase. - Scoped Oxlint, Oxfmt check, and `git diff --check`: passed. - Final merge-base: `d9f2ac5a4390b8b8507da3cc32a3bae01fc3f868`. > AGENT GENERATED: by GPT-5.6-Sol
## Human comments ## What was wrong Main implements three workspace kinds directly in core server and host-daemon provisioning code: the project's checkout, a managed Git worktree, and a personal directory. Adding another environment requires extending those switches and their cleanup paths. Plugins cannot supply workspace creation/removal, validate inputs, report availability, or render the associated composer controls through a common environment contract. ## What changed [Reviewer report — overview, API, review fixes, and before/after screenshots](https://get-bb.github.io/reports/prs/3227.html) Environment plugins own resource operations; core owns placement, durable launches, cancellation, recovery, retries, retirement, and teardown. The three existing behaviors are bundled, auto-installed plugins. Main's fixed defaults remain: a Git project defaults to a fresh worktree, a non-Git project uses its checkout, and a projectless root gets a personal workspace. A project child gets a fresh worktree; a projectless child shares its parent's environment. The request shape already names an enrolled machine inside the provider selection: ```ts { type: "provider", environmentProviderId: "git-worktree", machine: { type: "existing", hostId }, inputs: { branch: { kind: "default" } } } ``` `bb.experimental_environments.register(...)` accepts `create` and `remove` operations with `AbortSignal`, plus availability, validation, input schemas, and lifecycle policy. The type-only `@get-bb/plugin-sdk/environment-provider` entry exports unprefixed types. New runtime values remain experimental. Creation returns a path and ownership flag; removal receives the persisted provider resource and stable path key. Core persists launch progress and settles interrupted attempts in `apps/server/src/services/environments/provider-orchestration.ts`. | Bundled plugin | Responsibility | Path ownership | | --- | --- | --- | | `environment-project-checkout` | Reuse the project checkout; own the Current/New branch/Checkout control and validate branch mutations | Keeps the checkout | | `environment-git-worktree` | Create an isolated worktree and recover interrupted creation; core runs setup/teardown | Removes the worktree | | `environment-personal-workspace` | Create a separate directory for a projectless root thread | Removes the directory | `packages/environment-provider-host` supplies shared host-side primitives. The daemon now attaches resolved paths with `environment.attach`/`environment.attach.cancel`; it no longer provisions or destroys environments. Git inspection reports `isWorktree`. `HOST_DAEMON_PROTOCOL_VERSION` increases from **183 to 193**, and the SDK patch-bump script advances **0.4.49 to 0.4.50**. Migration **0113** is the single migration from main: it converts legacy environment kinds and pending intents to provider records, records owning plugin IDs, indexes active path claims, remaps lifecycle statuses, and drops the old managed/provision-type/cleanup columns. It creates neither canonical-path storage nor a hook-operation table. The implementation uses stored paths without host-wide canonicalization or backfills. Published environments expose provider fields and lifecycle; deprecated `managed`, `workspaceProvisionType`, and display kind are derived compatibility fields. Provider policy exposes only retirement grace and path-key strategy. Core uses fixed retry defaults (60 seconds for removal; three transient creation retries 30 seconds apart) with no overall provider-create timeout. Setup/teardown scripts retain their separate 15-minute timeout. Hooks use IDs derived from existing launch/environment identity and are tracked only in daemon memory. A server restart can join a known daemon operation. If a daemon restart loses hook state, core reports an unknown outcome and blocks automatic cleanup rather than rerunning the script or deleting its workspace. Recovery then requires manual inspection; there are no persisted hook files, PID reconciliation, or separate hook ledger. API and CLI additions include environment provider discovery, environment list/show/delete, `bb thread spawn --environment-provider/--environment-inputs`, and `bb thread list --environment`. Existing path shorthand and legacy request forms normalize at the server boundary. Hooks expose only `environment` or `provider` environment intents, and add `thread.unarchived` and `message.cancelled`. SDK areas, host helpers, Plugin Guide/API map, audit documentation, built-in authoring and CLI skills, guide chapters, and READMEs are updated together. User-facing changes: - Provider rows appear in fixed order: Project checkout, Worktree, then additional environment providers. Personal workspace is projectless-only. Availability and plugin input blockers explain unusable choices. - Plugins can supply environment inputs and use `experimental_useBranches`, `experimental_useCheckoutState`, and the seven-prop `experimental_BranchPicker`. The checkout plugin preserves main's chip interactions. - Reuse rows show environment identity and threads without provider text. Follow-up and Info panels distinguish available, archived, failed, and destroyed environments; failed creation reads **Not created · provisioning failed**. - Sidebar grouping uses `isWorktree`; the plugin catalog gains **Environments**; General uses **New branch prefix**. This change includes no machine-provider API, machine launch table, host lifecycle columns, installer behavior changes, machine controls, new-machine selection, Modal plugin, or SSH plugin. Two existing test fixtures were made portable: README-directory copy errors accept macOS `ENOTSUP`, and the nested-ignore watcher test accounts for native event coalescing and checks nested paths specifically. Production installer and watcher behavior are unchanged. ## How you verified - Migration squash: all **448 database tests** and **248 SDK tests** pass, including legacy provider/owner and pending-intent conversion. - Latest simplification: server orchestration **40 tests**, real hook interruption integration **3 tests**, daemon script/dispatch **12 tests**, database **448 tests**, daemon contract **54 tests**, and Plugin Guide **74 tests** pass. - Typecheck/lint passes for server, daemon, database, daemon contract, and integration packages. - Regression coverage includes joining a still-running hook after server restart, ordinary cancellation before cleanup, and blocking cleanup for unknown daemon hook state. - Earlier targeted checks cover offline stored-path filtering and rejection of removed provider policy settings; the SDK and bundled environment provider typechecks pass. - `git diff --check` passes. The local worktree app is rebuilt and restarted with the latest migrations. > AGENT GENERATED
…ns (#3268) ## Human comments ## What was wrong The machine authentication proxy piped upgraded WebSocket sockets without handling their errors or closing the peer. An upstream reset could therefore raise an uncaught `ECONNRESET` in the host daemon. Separately, reconnect reconciliation labeled a missing active turn as a daemon restart even when the reconnecting process had the same instance ID. ## What changed Handle errors and close both sides of upgraded proxy connections, including clients that disconnect during the upgrade. When the same daemon reconnects without a previously active turn, emit the existing `host-connection-lost` cause so the timeline reports a lost connection; preserve the restart message for a different daemon instance. No server/daemon wire fields or public API changed, so no protocol bump is needed. These fixes do not address the underlying Connect tunnel instability. ## How you verified - Reproduced an uncaught `read ECONNRESET` with a real upstream TCP reset before the proxy fix; the regression passes afterward. Coverage also exercises client-side resets and peer cleanup. - `pnpm exec turbo run test --filter=@bb/host-daemon -- --run src/machine-auth-proxy.test.ts` — 10 passed. - `pnpm exec turbo run test --filter=@bb/server -- --run test/internal/background-task-reconciliation.test.ts` — 12 passed, covering missing/still-active turns on same-instance reconnect, actual daemon replacement, and disconnect grace behavior. - `pnpm exec turbo run typecheck --filter=@bb/host-daemon --filter=@bb/server` — passed. - `git diff --check` — passed. EAP codename scan: clean working tree, HEAD, added lines, and commit messages in the push range. > AGENT GENERATED
## Human comments This is something that almost made me give up on bb but my clanker got through and was able to diagnose the problem. TBH, not sure how this hasn't been reported before, I got it to do some searching on the repo issues / PRs / discussions but it did not find anything related. Anyways, I hope this is the right way to fix the problem but if not, happy to provide more info about my environment as a separate ticket for discussion and investigation. I'm about to rebuild my container with this patch + another one I got in the works and will report back if it goes sideways before marking the PR as ready for review. ## What was wrong The pi provider's dynamic tools (`AskUserQuestion`, `update_environment_directory`, plugin agent tools) hang forever under the shipped pi. pi has been distributed as a Bun-compiled binary since v0.55.0 ([release assets](https://github.com/earendil-works/pi/releases/tag/v0.55.0), built via `bun build --compile` in [earendil-works/pi scripts/build-binaries.sh#L205](https://github.com/earendil-works/pi/blob/main/scripts/build-binaries.sh#L205)), and pi 0.85.0's binary embeds Bun 1.3.14. Bun's `net.Socket({ fd })` validates the fd and attaches no handle (`_handle === null`, no poll registered — see [bun-v1.3.14 net.ts#L684](https://github.com/oven-sh/bun/blob/bun-v1.3.14/src/js/node/net.ts#L684) and the open upstream fix PRs [oven-sh/bun#35341](oven-sh/bun#35341), [#29141](oven-sh/bun#29141)). The bridge→extension channel read ([bb-pi-extension.ts#L177](https://github.com/get-bb/bb/blob/c49ba3f54/plugins/provider-pi/src/bridge/bb-pi-extension.ts#L177)) relied on a Node-only behavior, so under the compiled pi every dynamic tool result was written but never read: the extension's tool promise never settled, pi never emitted `tool_execution_end`, and turns hung until manually stopped. This is not a Bun regression (read adoption has never shipped) and cannot be worked around by pinning Bun because pi embeds its runtime. The existing suite missed it because the fake pi loads extensions under Node, where the fd semantics work. ## What changed - `plugins/provider-pi/src/bridge/bb-pi-extension.ts` — the bridge→extension channel reader branches on the runtime: under Bun it reads fd 4 through `Bun.file(fd).stream()` (same newline framing and `StringDecoder` handling); under Node the existing `net.Socket` path is unchanged, with its non-blocking comment moved onto that branch. No message formats, wire content, or fd assignments changed — server↔daemon payloads are untouched, so `HOST_DAEMON_PROTOCOL_VERSION` is not bumped. - `plugins/provider-pi/src/bridge/fake-pi-rpc.mjs` — test fake: under runtimes without `module.registerHooks` (Bun) it stages the extension copy beside the package's gitignored `node_modules` so the extension's bare imports resolve natively; Node keeps the resolve-hook path. Header docs updated. - `plugins/provider-pi/src/bridge/bridge.bun-runtime.test.ts` (new) — regression test running the fake pi under Bun and driving a dynamic tool round trip end to end. Known limitation, called out: it skips when Bun is absent from `PATH` (`it.skipIf(bunBinary() === null)`), and no CI leg installs Bun today, so this guard does not run in CI yet (follow-up issue; without it, CI cannot detect a revert of the Bun branch). - An adversarial review pass shaped the change: staging lives in gitignored `node_modules` (not the source tree), the Bun branch is gated on the runtime rather than on `registerHooks` availability, and the single-message fd-4 coverage plus a bridge→extension liveness handshake are tracked as follow-ups rather than shipped here. - Verified against Bun 1.4.0 in the test and against Bun 1.3.14 (pi's embedded runtime) via a direct fd probe. ## How you verified - `plugins/provider-pi/src/bridge/bridge.bun-runtime.test.ts`: red on `main` at `timed out waiting for the tool result to reach pi under Bun` (the exact production symptom), green with this change. - `pnpm exec vitest run --config vitest.config.ts src/bridge/bridge.bun-runtime.test.ts src/bridge/bridge.round2.test.ts src/bridge/bridge.framing.test.ts` — 13/13 pass (Bun on PATH). - `pnpm exec turbo run typecheck --filter=bb-plugin-provider-pi` — clean. - Live-incident capture (`BB_PROVIDER_BRIDGE_RECORD_DIR`): recorded lanes show the full request→response chain through the bridge's fd-4 write with no error anywhere, matching the failure mode; pi's own session file shows it only ever received the abort-path tool result. > AGENT GENERATED --- <sub><em>Generated with "Omen Alpha" (Opencode Go) via BB / Pi</em></sub>
## Human comments ## What was wrong Reasoning choices used full-width menu rows, making the model picker unnecessarily tall when a provider offered several effort levels. ## What changed Display reasoning choices as clickable pills arranged horizontally with wrapping. Highlight the selected level and expose its state with `aria-pressed`, retain disabled and selection behavior, and use larger touch targets in compact layouts. ## How you verified - `pnpm exec turbo run test --filter=@bb/app -- ModelReasoningPicker.test.tsx`: all 29 tests pass on Node 22.23.2. - Browser checks in the interactive component preview confirmed selection updates, selected styling, and wrapping at a 320px viewport. - Started the source dev app for user testing; app and server/daemon health checks pass. - `git diff --check` passes. - Secret-model scan: zero hits in the working tree, tracked HEAD, added lines, and commit messages for `origin/main..HEAD`. > AGENT GENERATED
…actions (#3228) ## Human comments yet another PR for making the pi provider more polished here. I’ve been running this and my other PRs on my local install of bb and been working fine <img width="1170" height="2205" alt="image" src="https://github.com/user-attachments/assets/d300ebf0-23ef-4cb0-9e0d-83832f61537a" /> ## What was wrong Pi extensions prompt the user through `ctx.ui.select`, `ctx.ui.confirm`, `ctx.ui.input`, and `ctx.ui.editor`. In RPC mode these emit an `extension_ui_request` line on stdout and block until an `extension_ui_response` arrives on stdin (see `docs/rpc.md`, "Extension UI Protocol"). bb's provider-pi bridge answered every such request with `{cancelled: true}`, so any extension that raises a permission prompt had it silently dismissed — [pi-landstrip](https://github.com/landstrip/landstrip) sandbox-approval prompts were unusable over bb. Investigation and reproduction trail: #1668. ## What changed - `plugins/provider-pi/src/bridge/rpc-child.ts` forwards `extension_ui_request` dialogs to an optional handler; with no handler the old auto-cancel remains, so behavior is unchanged unless the bridge opts in. - `plugins/provider-pi/src/extension-ui-contract.ts` validates dialog requests at the boundary: method enum, bounded title/options/message/placeholder/prefill, `select` requires a non-empty options list. - `plugins/provider-pi/src/bridge/extension-ui.ts` forwards each dialog as a `provider-pi/extension-ui` pending interaction and maps resolutions back (`value` for select/input/editor, `confirmed` for confirm, `cancelled` on error, unknown value, or session close). Request ids use a `pi-ui-N` string space so they cannot collide with numeric item/tool/call ids. Pending dialogs are cancelled on thread stop, session replace, and discard. Fire-and-forget methods (`notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`) are dropped. - Helper sessions such as fork helpers retain automatic dialog cancellation when no UI handler is installed. Select responses are checked against the originally offered options before reaching Pi. - `plugins/provider-pi/app.tsx` renders the four dialog shapes in the plugin pending-interaction slot. The host passes plugin components the interaction's data object rather than the stored `{kind, title, data}` wrapper, so the renderer parses the unwrapped payload directly. No protocol or wire changes: no `HOST_DAEMON_PROTOCOL_VERSION` bump, no new grammar, no new plugin API surface — this reuses the existing `interaction/request` plugin-extension payload kind and pending-interaction slot. This is deliberately the minimal bridge-only subset of #2446/#2447 (both still open): thread-scoped requests outside an active turn, withdrawal notifications, and queued-before-announcement dialogs are left as follow-ups — and the behavior is strictly better than the current always-cancelled path. ## How you verified - `pnpm exec turbo run test --filter=bb-plugin-provider-pi` (26 files, 143 tests) and `turbo run typecheck --filter=bb-plugin-provider-pi` — clean. - Both review regressions were reproduced before the fixes: helper startup timed out waiting for a dialog response, and an unoffered select string reached Pi. Both now pass; `pnpm exec turbo run test typecheck --filter=bb-plugin-provider-pi` passed with 26 test files and 143 tests. The working-tree and commit-range secret scan was clean. - New bridge round-trip tests over the fake Pi harness (`/ui` directive in `fake-pi-rpc.mjs`), rpc-child handler unit tests, and jsdom renderer tests covering select/confirm/input/editor and the host-unwrapped payload shape. - Live: pi-landstrip read/write permission prompts render the full option list over bb, and user answers propagate back and drive the sandbox decision (e.g. "Keep blocked" denies the underlying write). Fixes: partial groundwork for #1668 --- <sub><em>Generated with "Omen Alpha" (Opencode Go) via BB / Pi</em></sub> > AGENT GENERATED --------- Co-authored-by: Sawyer Hood <sawyerjhood@gmail.com>
## Human comments ## What was wrong Archiving a thread and its children collected only immediate children and hidden source forks. Archiving each child then released its unarchived children, leaving grandchildren active and detached from their parents. ## What changed Collect the descendant tree before archiving, then archive children before their parents. Traverse archived hierarchy intermediaries, include hidden source forks at each level, and deduplicate threads reached through both relationships. Preserve parent relationships and leave unrelated threads and visible source forks untouched. ## How you verified Added public API regression coverage for four hierarchy levels, hidden forks with children, duplicate relationships, and an already-archived intermediary. The nested regression failed before the fix. All 17 tests in the selected suites pass afterward. - `pnpm exec turbo run test --filter=@bb/server -- test/public/public-thread-parenting.test.ts test/threads/archive-pruned-environment.test.ts` - `pnpm exec turbo run typecheck --filter=@bb/server` - `git diff --check` > AGENT GENERATED
## Human comments ## What was wrong The Plugin marketplaces settings section used the default bordered `SettingsSection` body while each marketplace already rendered as its own bordered row. That produced a visually redundant card-inside-a-card layout and unnecessarily indented the add form. ## What changed Remove the outer body border, background, and padding for this section while retaining every individual marketplace row card. Increase the gap above the marketplace list so the unwrapped form and rows keep clear visual separation. Adding, refreshing, and removing marketplaces are unchanged. ## How you verified - `pnpm exec turbo run test --filter=@bb/app --force -- MarketplacesSettingsSection` — 4/4 passed. - `pnpm exec turbo run typecheck build lint --filter=@bb/app --force` — passed; lint reported no errors. - `pnpm exec oxfmt --check apps/app/src/components/settings/MarketplacesSettingsSection.tsx` - `git diff --check origin/main...HEAD` - Reproduced the nested-card layout before the change and verified the updated layout and existing Add/Refresh interactions in the worktree app. > AGENT GENERATED Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong On a fresh iOS simulator, Maestro reports `openLink: "bb://threads"` complete before the operating system's first custom-scheme confirmation has been accepted. The app remains behind the native `Open in “bb”?` dialog while `shell-deep-link` waits for `shell-webview`; that simulator-owned dialog survives the next flow's app stop and launch, so later flows cannot see their native screens. The original and prior scheduled evidence show that exact sequence: [run 33637452626](https://github.com/get-bb/bb/actions/runs/33637452626) and [run 33517924818](https://github.com/get-bb/bb/actions/runs/33517924818). After this PR was labeled, [run 34273156200](https://github.com/get-bb/bb/actions/runs/34273156200) checked out the PR merged with newer main and exposed a second deterministic blocker introduced by #2881: the first-run `Get notified when a thread needs you?` action sheet covered an otherwise rendered shell after every direct-server pairing. That explains why `shell-launch` now failed before any deep link. Its artifact shows the sheet in all three failed-flow screenshots; `clear-open-confirmation` passed after each failure and `shell-unreachable-server` passed, proving the PR's fail-closed cleanup prevented native state from leaking. Every scheduled main run since #2881 merged shows the same new `shell-launch` failure, so this recurrence is current-main flow orchestration, not infrastructure. ## What changed - Route every Mobile E2E `bb://` action through one parameterized Maestro subflow. It preserves the real custom-scheme invocation, conditionally accepts the native confirmation only when visible on iOS, and proves the dialog is absent before route assertions continue. - Make the existing direct-server pairing subflow own the post-connect UI boundary. It uses the unchanged 30-second readiness budget to wait for either the notification opt-out or rendered page content, conditionally chooses `Not now`, proves the sheet is absent, and keeps the `shell-webview` assertion. - Have `shell-launch`, `shell-deep-link`, and `shell-send` use that one pairing boundary instead of duplicating pairing. The notifications deep-link added by #2881 also uses the custom-scheme helper. - After a failed CI flow, preserve the original final screenshot, cancel and verify removal of the native confirmation, and stop the app before continuing. If cleanup cannot establish isolation, the runner stops before the next flow. - Add regression coverage for cleanup-before-continuation, fail-closed cleanup failure, shared pairing, and the absence of direct `bb://` Maestro commands. - No assertion, timeout, polling budget, or retry budget increased. No server/host-daemon wire, CLI, SDK, or user-facing configuration changed, so `HOST_DAEMON_PROTOCOL_VERSION` is unchanged. ## How you verified - Mandatory initial fresh-main gate: clean worktree; `HEAD`, `origin/main`, and merge-base all independently verified as `ab8e0a202c70525c3c935733300198dac617e3de` before task inspection. - Before the follow-up edit, fetched and recorded live `origin/main` as `06047280e04e6ce57554052ec788a1c5defe7d1e`, with PR head `5f2f960c2c289518b8493dece775f4f22103f722` and retained merge-base `ab8e0a202c70525c3c935733300198dac617e3de`; then rebased cleanly. Current PR merge-base is `06047280e04e6ce57554052ec788a1c5defe7d1e`. - Native red: run 34273156200 failed `shell-launch`, `shell-deep-link`, and `shell-send` at `shell-webview`; its screenshots show the notification action sheet over the rendered shell. The new cleanup passed after every failure, and `shell-unreachable-server` passed. - Original focused runner red on pre-fix main: 2/2 tests failed because cleanup was never invoked and the next flow always ran. - Focused green: `pnpm exec turbo run test --filter=@bb/mobile --force --concurrency=2 -- --run src/lib/e2e/mobile-e2e-flows.test.ts src/lib/e2e/ci-run-flows.test.ts` — 2 files, 4 tests passed. - `pnpm exec turbo run test --filter=@bb/mobile --force --concurrency=2` — 46 files, 331 tests passed. - `pnpm exec turbo run typecheck --filter=@bb/mobile --force --concurrency=2` — passed. - `pnpm exec turbo run lint --filter=@bb/mobile --force --concurrency=2` — passed with two pre-existing React warnings and zero errors. - `pnpm exec turbo run build --filter=@bb/app --force --concurrency=2` — passed after a frozen-lockfile install refreshed dependencies for current main; 439 files precompressed. - `bash -n`, Ruby YAML parsing for every changed Maestro file, targeted `oxfmt --check`, `git diff --check`, and the no-direct-`bb://` scan passed. ShellCheck is unavailable on this host. - [Ordinary CI run 34277221769](https://github.com/get-bb/bb/actions/runs/34277221769) passed on commit `1594a59a2b2ccf1c17645200f2fb0db522e8861c`, including build/typecheck/lint, package tests, all app test shards, server, integration, and Linux/macOS package smoke. - All local build, test, typecheck, lint, and install commands ran with explicit process-group deadlines on Intel host `host_nwqfteeqz4` (`x86_64`, Intel Core i5-1038NG7). A final cwd audit found one stale earlier Xcode-discovery process group, terminated it, and then proved no task build, test, backend, fixture, Maestro, or simulator process remained. Temporary artifacts and logs were moved recoverably to Trash. - This host has Command Line Tools only, with no Xcode app, iOS runtime, or Maestro, so no after-change simulator result is claimed. The `mobile-e2e` label was removed before the updated push; [run 34277221938](https://github.com/get-bb/bb/actions/runs/34277221938) therefore skipped without starting a simulator. > AGENT GENERATED: by GPT-5.6-Sol.
…cy rows (#2920) ## Human comments ## What was wrong Large completed outputs were stored directly in `events.data`. Ordinary timeline reads therefore loaded and parsed megabytes that the UI did not need. Image-generation completions were also left in large provider envelopes, so an otherwise useful generated-image event could exceed the timeline response limit and appear as an unexplained placeholder. Existing databases had a second problem: old inline outputs were truncated in a bulk synchronous sweep. Large backlogs could block the Node event loop long enough to make the server visibly unresponsive. ## What changed ### In simple terms - New large command, tool, fetch, search, and image-generation outputs keep a small payload preview in the event row and the exact full value in a retained-output sidecar. - Normal timelines read only the compact event. Explicit raw/detail reads restore the exact full value while it is retained. - Existing inline rows migrate incrementally. Each synchronous advance has a deliberate bound, persists its cursor, and yields before more work. - Generated images render as **Generating image** / **Generated image** rows from prompt/path metadata. The visible image preview still loads from the saved image path; the large provider `result` payload is not needed to render it. - Migration and expiry invalidate affected timeline/projection caches even though event sequence numbers do not change. - All migration work stays on the Node main event loop. This PR adds no worker. ### Output lifecycle and contract The authoritative retained-output pairs are: - `commandExecution.aggregatedOutput` - `imageGeneration.result` - `toolCall.result` - `webFetch.resultText` - `webSearch.resultText` For a supported completed output above 32,768 JavaScript characters: 1. `events.data` receives a Unicode-safe 2 KiB head + marker + 2 KiB tail payload preview. 2. `retained_event_outputs` stores the exact JSON-encoded string with `event_id`, `output_path`, and `expires_at`. 3. Bounded timeline/event reads use the compact event. Raw/detail reads hydrate from the sidecar only when the response budget allows it. 4. The retained value expires at `event.createdAt + 7 days`; the payload preview then becomes authoritative. 5. Fork/copy preserves an unexpired sidecar value and its original expiry without materializing the value in Node memory. The actual generated-image preview is not truncated by this policy. The UI loads the saved image file from `savedPath`; `imageGeneration.result` is the large encoded provider payload. Existing command/tool/fetch/search retention behavior is unchanged apart from moving the exact value into the sidecar during the retention window. Image generation is the new fifth target. Public event pages accept at most 100 rows. A hydrated raw response above 8 MiB returns HTTP 413 so callers can reduce their page size. The CLI does this automatically and grows its page size again after a dense output region. ### Existing databases - Standard legacy completed rows use the existing `events_completed_item_truncation_idx` and scan 25 indexed rows per window. - Legacy provider-envelope image events walk the existing `events.id` primary-key index in 250-row windows. This bounds total rows examined, not merely matching rows, and needs no new index over the existing events table. - At most one eligible output is rewritten per advance. Cursor/window state and the rewrite happen transactionally, so restart resumes safely. - Malformed, mismatched, already-previewed, expired, or over-8-MiB events advance the cursor without blocking later rows. Expired values become previews without creating already-expired sidecars. - Cursor completion is persisted, so the historical database is not rescanned on every sweep. Drizzle migration `0113_solid_night_thrasher.sql` creates only the sidecar table and its empty expiry index. It does not scan or rewrite `events` during startup. ### Generated-image boundary The Codex provider plugin translates new image-generation items into the provider-neutral bridge delta union. The assembler persists canonical `imageGeneration` items. Old `provider/unhandled` image completions are recognized through one shared strict parser; migration preserves their original envelope and sidecarizes only the nested `result`. The provider-bridge grammar remains v2 because image generation is additive vocabulary. The host-daemon protocol is bumped from current-main 180 to 181 because that new delta shape can cross the server/daemon wire; this forces already-enrolled daemons to self-update. ## Measurements ### Normal reads and new writes BEFORE is inline baseline `eeaaa3e8db7b3aeb3c4ab46873816c84cb6ea513`; AFTER is sidecar runtime `b78bf6038f15c081c8274f3e8141ccc6afa8f5b2`. Same harness, Apple M4 Max, Node v22.23.1, 12 read warmups + 60 iterations and 8 write warmups + 40 iterations. | Main-loop operation, p95 | Inline | Sidecar | Result | | --- | ---: | ---: | ---: | | Normal bounded event read | 42.382 ms | 0.902 ms | 47× faster | | Normal bounded timeline read | 42.752 ms | 5.857 ms | 7.3× faster | | Raw full-output read | 7.127 ms | 26.562 ms | explicit hydration costs more | | Four-row write transaction | 10.157 ms | 5.929 ms | 1.7× faster | | Event data materialized by bounded reads | 2,327,734 B | 331,678 B | 85.8% less | Foundation artifacts: [comparison](https://ymichael.getbb.app/api/v1/threads/thr_pkwpa3qag4/thread-storage/files/benchmarks/comparison.md), [inline JSON](https://ymichael.getbb.app/api/v1/threads/thr_pkwpa3qag4/thread-storage/files/benchmarks/origin-main.json), [sidecar JSON](https://ymichael.getbb.app/api/v1/threads/thr_pkwpa3qag4/thread-storage/files/benchmarks/branch.json), [harness](https://ymichael.getbb.app/api/v1/threads/thr_pkwpa3qag4/thread-storage/files/benchmarks/retained-event-output-benchmark.mjs), [checksums](https://ymichael.getbb.app/api/v1/threads/thr_pkwpa3qag4/thread-storage/files/benchmarks/SHA256SUMS). ### Legacy completed-output responsiveness BEFORE is foundation `259f937f7b327ea73f56b69ace6ed35645c8045e`; AFTER is bounded migration runtime `f89baf1d0384d3f59cba5e655a1a65d083160a51`. The later fixes do not change these four standard target advances. Same harness, 2 warmups + 10 iterations, with one busy child in CPU runs. | Fixture/load | Bulk synchronous sweep p95 | Bounded migration p95 | | --- | ---: | ---: | | 36 KiB rows, idle | 110.226 ms | 1.587 ms | | 36 KiB rows, CPU load | 247.957 ms | 2.468 ms | | 4 MiB rows, idle | 94.668 ms | 14.589 ms | | 4 MiB rows, CPU load | 165.865 ms | 13.308 ms | Every 4,000-row drain migrated 147,456,000 bytes. Persisted windows reduced fetched scan rows from 875,500 to 4,000. Expected, hydrated, and raw-event hashes match. A 4,000-row backlog projects to about 10.5 minutes at the production cadence, trading total drain time for responsiveness. Migration artifacts: [comparison](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-comparison.md), [BEFORE idle](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-before-idle.json), [AFTER idle](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-after-idle.json), [BEFORE CPU](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-before-cpu.json), [AFTER CPU](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-after-cpu.json), [checksums](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/benchmarks/review-fixes-SHA256SUMS). ### Production-shaped existing-database upgrade Exact HEAD `e1d7c0f054012b8b4126134d58bbadddad4bc91d`, sanitized database with all 1,490 threads and all 1,659,038 events (3.68 GiB), 2 warmups + 10 measured fresh-clone upgrades per load: | Load | Pending-upgrade startup p50 / p95 | Already-current startup p50 / p95 | Paired upgrade overhead p50 / max | | --- | ---: | ---: | ---: | | Idle | 15.83 / 29.14 ms | 17.80 / 27.36 ms | 0.53 / 7.18 ms | | Controlled CPU | 15.98 / 23.42 ms | 13.91 / 20.65 ms | 1.43 / 5.83 ms | The removed index-building migration measured 8,164.99 ms p50 and 8,886.13 ms p95/max idle. The new pending-upgrade p50 is about 516× lower. The new schema adds 12,288 bytes instead of 425,984 bytes. The exact-HEAD primary-key image scan covered all 1,659,038 events in 6,678 yielded advances: | Load | Sync p50 / p95 / max | Timer delay p50 / p95 / max | Direct wall time | | --- | ---: | ---: | ---: | | Idle | 0.49 / 1.96 / 78.11 ms | 1.37 / 3.65 / 93.21 ms | 11.62 s | | Controlled CPU | 0.54 / 2.50 / 225.15 ms | 1.40 / 5.20 / 226.03 ms | 15.53 s | Both passes migrated all 40 eligible legacy image rows and 99,775,504 output bytes, scanned exactly 1,659,038 rows, and executed about 33,425 measured statements. Every retained raw event hydrated to its byte-identical pre-migration SHA-256. A legacy-only production-scheduled pass projects to about 17.4 minutes after other targets are exhausted. Exact-HEAD follow-up artifacts: [report](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/no-index-followup-report.md), [upgrade JSON](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/0113-no-index-upgrade-production-shaped-final.json), [scan JSON](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/legacy-image-primary-key-scan-production-shaped.json), [upgrade harness](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/measure-0113-no-index-upgrade.mjs), [scan harness](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/benchmark-legacy-image-primary-key-scan.mjs), [checksums](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/no-index-followup-SHA256SUMS). <details> <summary><strong>Earlier full production-copy stress run</strong></summary> The earlier all-target stress pass used the same complete sanitized 1.66-million-event population. It migrated 2,989 rows and 457,525,580 source bytes with four forced reopens and identical idle/CPU totals and hashes. The standard target behavior remains unchanged; the exact-HEAD follow-up above replaces its legacy-image index scan and separately measures that new pass. | Load | Sync p50 / p95 / max | Timer delay p50 / p95 / max | Direct drain time | | --- | ---: | ---: | ---: | | Idle | 4.970 / 12.681 / 388.411 ms | 4.976 / 12.691 / 388.635 ms | 82.686 s | | Controlled CPU | 4.983 / 12.730 / 379.899 ms | 4.992 / 12.744 / 379.996 ms | 82.270 s | Artifacts: [QA report](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/qa-report.md), [idle JSON](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/prod-copy-migration-idle-fixed.json), [CPU JSON](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/prod-copy-migration-cpu-fixed.json), [snapshot validation](https://ymichael.getbb.app/api/v1/threads/thr_9mfhzth64b/thread-storage/files/stress-tests/prod-thread-events-validation.json). </details> <details> <summary><strong>Exact combined file list (73 files)</strong></summary> ```text apps/app/src/components/thread/timeline/TimelineRowDetails.output-preview.test.tsx apps/app/src/components/thread/timeline/TimelineRowDetails.tsx apps/app/src/components/thread/timeline/useTimelineWorkRowFullOutput.ts apps/cli/src/__tests__/command-output/thread-log.test.ts apps/cli/src/commands/thread/show.ts apps/server/scripts/benchmark-completed-event-output-migration.mjs apps/server/src/routes/threads/data.ts apps/server/src/services/system/event-loop-work.ts apps/server/src/services/system/periodic-sweeps.ts apps/server/src/services/threads/thread-data.ts apps/server/src/services/threads/timeline-cache.ts apps/server/src/services/threads/timeline-latest-rows-cache.ts apps/server/src/services/threads/timeline-output-preview.ts apps/server/src/services/threads/timeline.ts apps/server/test/public/public-thread-data.test.ts apps/server/test/public/public-thread-timeline-output-preview.test.ts apps/server/test/services/periodic-sweeps.test.ts apps/server/test/services/threads/timeline-cache.test.ts apps/server/test/services/threads/timeline-in-turn-window.test.ts apps/server/test/services/threads/timeline-latest-rows-cache.test.ts docs/api_to_audit.md docs/provider-bridge-protocol.md packages/client-core/src/timeline/timeline-auto-expand.ts packages/client-core/src/timeline/timelineRowSignatures.ts packages/db/drizzle/0113_solid_night_thrasher.sql packages/db/drizzle/meta/0113_snapshot.json packages/db/drizzle/meta/_journal.json packages/db/src/data/event-output-truncation.ts packages/db/src/data/events.ts packages/db/src/data/index.ts packages/db/src/data/retained-event-outputs.ts packages/db/src/data/sweeps.ts packages/db/src/retained-event-output.ts packages/db/src/schema.ts packages/db/test/data/completed-event-output-migration.test.ts packages/db/test/data/retained-event-outputs.test.ts packages/db/test/data/sweeps.test.ts packages/db/test/helpers/migrated-connection.ts packages/db/test/migrate.test.ts packages/db/test/query-plans.test.ts packages/domain/package.json packages/domain/src/index.ts packages/domain/src/legacy-image-generation.ts packages/domain/src/provider-event.ts packages/domain/src/utf16.ts packages/domain/test/legacy-image-generation.test.ts packages/host-daemon-contract/src/protocol.ts packages/host-daemon-contract/test/contract.test.ts packages/provider-bridge-protocol/src/assembler/delta-assembler.ts packages/provider-bridge-protocol/src/contract-tests/provider-bridge-grammar.v2.snapshot.json packages/provider-bridge-protocol/src/thread-delta.ts packages/server-contract/src/api/threads.ts packages/server-contract/src/common.ts packages/server-contract/src/thread-timeline.ts packages/thread-view/src/build-event-projection.ts packages/thread-view/src/build-thread-timeline.ts packages/thread-view/src/event-projection-message.ts packages/thread-view/src/event-projection-types.ts packages/thread-view/src/format-timeline-text.ts packages/thread-view/src/timeline-row-title.ts packages/thread-view/src/timeline-view.ts packages/thread-view/src/timeline-work-row-glyph.ts packages/thread-view/src/tool-activity-cells.ts packages/thread-view/src/tool-activity-projection.ts packages/thread-view/src/tool-activity-web-projection.ts packages/thread-view/src/web-activity-lifecycle.ts packages/thread-view/test/build-thread-timeline.test.ts plugins/provider-codex/src/delta-translation.test.ts plugins/provider-codex/src/delta-translation.ts plugins/provider-codex/src/presentation.ts plugins/provider-codex/src/schemas.ts tests/integration/helpers/api.ts tests/integration/helpers/assertions.ts ``` </details> ### Residual tradeoffs - Explicit raw/detail reads are slower because they load full sidecar values; normal timeline reads avoid that work. - Full values intentionally disappear after seven days. The compact payload preview and generated image file/path remain. - A raw event response above 8 MiB returns 413. An individual legacy event above 8 MiB is preserved inline rather than migrated and can remain too large for that endpoint. The complete sanitized production snapshot contains no such event. - SQLite may retain freed pages for reuse instead of immediately shrinking the physical file. This is reusable database space, not live duplicate data. - Historical migration takes longer overall and uses more small statements in exchange for bounded main-loop occupancy and a yield after every advance. - Rare cold/large-row advances remain visible: the exact-HEAD legacy-image maximum was 78 ms idle and 225 ms under controlled CPU load, while steady-state p95 stayed at or below 2.50 ms. - An out-of-band historical row inserted behind a permanently completed cursor would require a future cursor-version bump or explicit maintenance action. ## How you verified All database regressions use real in-memory SQLite without mocks. Coverage includes byte-identical raw/detail hydration, copy and expiry, malformed and extension events, UTF-16 boundaries, 8 MiB guards, query plans, cursor windows, restart/completion, cache invalidation, CLI 413 pagination recovery, canonical and legacy image envelopes, and yielded scheduler behavior. Latest focused validation: - `@bb/db` Turbo typecheck/build/test: 474/474 tests passed. - `@bb/server` Turbo typecheck/build/test: 2,239/2,239 tests passed across 230 passing files; 1 file skipped. - `@bb/host-daemon-contract` and `@bb/host-daemon` Turbo typecheck/build/test: 51/51 contract tests and 572/572 daemon tests passed. - The earlier complete combined branch validation remains green: provider bridge 238/238, Codex plugin 260/260, thread view 370/370, client core 253/253, domain 195/195, server contract 64/64, full app 3,867 passed with 3 skipped, and integration 77/77. - Drizzle migration and snapshot were regenerated mechanically from the schema; no snapshot was hand-edited. - `origin/main...HEAD` contains the complete sidecar, image rendering, incremental migration, and protocol-version changes with no worker or unrelated feature work. Fixes: no linked issue. > AGENT GENERATED
## Human comments ## What was wrong The timeline discarded parent links while projecting compaction lifecycle events, so subagent compactions appeared in the main feed. The Codex adapter also rejected `subAgentActivity` items with `kind: "completed"`, producing duplicate “Unhandled Codex event” diagnostics alongside the existing agent completion summary. ## What changed Preserve explicit and inherited parent links throughout compaction projection, including completion-only events. Recognize Codex's completion activity notifications and consume them without duplicating the delegation lifecycle already settled by the child turn. Root-agent compactions remain visible in the main feed. Previously persisted diagnostic rows are unchanged. ## How you verified - `pnpm exec turbo run test typecheck --filter=@bb/thread-view --filter=bb-plugin-provider-codex`: 651 tests and both typechecks passed. - Added regression coverage for explicit/inherited nesting, completion-only compactions, concurrent root compaction, and duplicate Codex completion notifications. - Replayed the reported thread's saved events locally: all three compactions remain nested, zero compactions appear at the root, and all 18 completion activity notifications produce zero unhandled deltas. Raw thread data was not committed. - Formatting and `git diff --check` passed. - Secret-model scan: clean working tree, HEAD, added lines, and commit messages for `origin/main..HEAD`. > AGENT GENERATED
## Human comments ## What was wrong The `needs-configuration` plugin banner knew whether a plugin exposed in-app settings, but its actions did not provide a route to those settings. Plugins configured through a frontend-contributed settings section also still need an explicit reload after configuration, and the banner's single-row layout left too little width for its recovery copy once both actions were present on compact screens. ## What changed - Adds an **Open settings** action for `needs-configuration` plugins with either declarative settings or a frontend-contributed settings section. - Keeps **Reload** for plugins whose configuration is not automatically applied, including plugins with frontend-contributed settings. - Lets banner content wrap so the actions move below the message when horizontal space is limited. - Adds focused coverage for declarative settings, frontend-contributed settings, config-less plugins, and unrelated runtime failures. - No wire, CLI, guide, or public plugin API changes. ## How you verified - `pnpm exec turbo run test --filter=@bb/app --force -- ToolsView.plugin-detail` (36 tests passed) - `pnpm exec turbo run typecheck lint --filter=@bb/app --force` (passed; lint reported 0 errors) - `pnpm exec oxfmt --check apps/app/src/components/tools/PluginCapabilities.tsx apps/app/src/components/tools/plugin-detail-banner.tsx apps/app/src/views/ToolsView.plugin-detail.test.tsx` - `git diff --check origin/main...HEAD` - Manually verified the link destination, retained reload action, and compact 390px wrapping in the isolated review app. > AGENT GENERATED --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
🚨 **SLOP COP** 🚨 · `new-issue-autopilot` ## Human comments ## What was wrong Project header controls inherited hover-only visibility without the marker that exposes actions on compact touch screens. This made their wrapper transparent and excluded it from pointer input. [Reproduction and root-cause report](https://get-bb.github.io/reports/issues/3330.html). ## What changed Apply the existing mobile visibility marker to the header-actions wrapper and cover closed and open states with a regression test. The change is 34 added text lines across two sidebar files, with no dependencies or public contract changes. ## How you verified The new assertions failed on unchanged production code with `expected null to be 'always'`. A browser fixture extracted from the production JSX and CSS reproduced invisible, untappable controls in two clean checkouts. After the fix, the same fixture is visible and receives a real touch tap. `pnpm exec turbo run test --filter=@bb/app -- ProjectRow sidebarRowClasses theme.test SidebarSectionRow ProjectList.modes --maxWorkers=1 --testTimeout=60000`: 62 tests passed across 8 files. The initial 15-second run had three existing timeouts; all passed on the final run. App lint passed with 0 errors (187 existing warnings). `git diff --check` and the publication-name scan passed. Broad build and full-app startup were stopped under host load; full-app and Safari verification are not claimed. Fixes #3330 > AGENT GENERATED <!-- slopcop:rule=new-issue-autopilot run=run_69t7lbzqmtuda5d9 sha=issue-3330 kind=summary --> Co-authored-by: Sawyer Hood <sawyerjhood@gmail.com>
## Human comments ## What was wrong Splitting Plugins and Skills into separate workspaces removed installed-plugin management from Settings and redirected its existing routes into Plugins. ## What changed Restore Settings → Installed plugins while retaining Plugins → Installed plugins. Both locations manage the same installed plugins, and opening a list row keeps its detail page in the originating section. Restore Settings navigation memory and route the command palette and general installed-plugin links to Settings. Keep individual plugin configuration pages and legacy workspace routes working. Update the agent guide and navigation documentation. ## How you verified - Turbo app typecheck and lint passed (lint has existing warnings, no errors). - Focused Turbo app tests passed: 86 tests covering both installed-plugin collections/details, Settings and Plugins sidebars, canonical routes, and compatibility navigation. Earlier focused checks also covered command palette, Settings route memory, notification links, and update/configuration links. - Browser verification in an isolated source dev app: clicked Installed plugins and BB guide from both Settings and Plugins, checked destination URLs and a single detail heading, and confirmed Settings stays selected for its detail page. - EAP codename scanner passed for the working tree, tracked HEAD, added lines, and commit messages in the push range. - SlopCop skipped at the user's request. > AGENT GENERATED
## Human comments ## What was wrong The compact model picker let its model list grow without a height allocation while the entire drawer body handled scrolling. With long catalogs such as Pi’s 437 models, the list became roughly 15,800px tall and pushed Reasoning, fast-mode, and footer controls far below the visible drawer. The persistent drawer also did not account for software-keyboard overlap. ## What changed Make the compact picker a shrinkable flex column with the model list as its sole independently scrollable region. Provider controls and the reasoning/footer controls remain outside that scroller, while desktop retains its existing 256px list cap. Teach the shared persistent drawer to measure Visual Viewport keyboard overlap, lift above it, and reduce its maximum height by the same inset. The existing close/reset path clears the inline keyboard styles, and the app-root/deferred-realization behavior is unchanged. ## How you verified - Reproduced with the real Pi catalog at 390×844: 437 models produced a 15,806px list and placed Reasoning around y=16,036 - After the fix, the list is capped at 567px on 390×844 and 314px on 320×568, with reasoning controls visible - Verified mobile touch scrolling, reasoning selection, search, keyboard selection, selected-item visibility, and drawer drag-to-close - Verified desktop long/short catalogs retain the existing 397px popover and 256px model-list cap - Verified the layout in WebKit with iPhone 15 emulation; iOS Simulator rendering was checked, but interactive taps were unavailable due local accessibility permissions - Full `@bb/app` suite passed: 502 files and 4,204 tests - Focused picker and responsive-overlay suites passed after formatting: 61/61 - Turbo typecheck and lint passed for `@bb/app` and `@bb/shared-ui` - Targeted formatting and `git diff --check` passed No linked issue. > AGENT GENERATED Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong Radix opens a tooltip when its trigger receives focus after its pointer-down guard has cleared. On touch, focus restoration can arrive after pointer-up; the footer-specific suppression was then cleared when the More drawer moved focus, so dismissing that drawer restored focus to the footer action and left its tooltip open without hover. ## What changed The shared tooltip trigger now composes its focus handler and allows focus-opened tooltips only when the existing input-modality tracker reports keyboard input. Mouse hover and keyboard focus still open tooltips, while touch/pointer focus restoration does not. The now-redundant footer suppression state was removed while preserving focus restoration, and the plugin registry artifact was regenerated for the shared dependency. Existing tooltip tests now use an explicit keyboard-focus helper where that is the intended interaction. ## How you verified - Focused Turbo app tests: 12/12 passed across the tooltip modality and plugin footer suites. - Turbo plugin-registry test and typecheck passed; the registry is fresh and its vendor test passes. - Turbo app and shared-UI typechecks passed. - Formatting checks for every changed TypeScript file and git diff checks passed. - Browser QA reproduced the exact touch sequence before the fix and repeated it after the fix, alongside mouse-hover and keyboard-focus checks. > AGENT GENERATED --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong On coarse-pointer devices, follow-up submission happens on pointer-up. Sending clears the composer immediately, replacing the Submit button with the microphone button before the browser dispatches the tap compatibility click. That trailing click is hit-tested against the replacement microphone control and starts recording even though the gesture began on Submit. ## What changed Voice activation now records the exact microphone button that received pointer-down and ignores pointer-driven clicks retargeted to a different replacement button. Deliberate microphone taps, mouse clicks, and keyboard or assistive activation remain supported, and touch submission still keeps its pointer-up latency improvement. ## How you verified - Focused Turbo PromptBoxInternal suite: 121/121 tests passed. - Added regressions for a retargeted touch-submit click, a deliberate microphone tap, and keyboard activation. - Turbo app typecheck passed. - Formatting and git diff checks passed. - Browser QA reproduced the original compatibility-click target and confirmed one-tap submit sends without requesting the microphone, while a deliberate microphone tap still starts recording. > AGENT GENERATED Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong The new-thread composer rendered different environment-slot component types for project and projectless scope, and the dropdown owned its open state internally. Transient scope replay, provider loading, or provider-count changes could therefore unmount the open picker and recreate it closed. Loading reusable environments also temporarily collapsed a specific reuse selection to the generic reuse value. ## What changed Project and projectless composers now share one stable EnvironmentSlot. It controls the dropdown open state and keeps the picker mounted while open even if projectless options settle to the machine-only layout; the compact machine control returns after normal dismissal. Specific reuse selections are retained while their options load. ## How you verified - Focused Turbo app tests: 95/95 passed across NewThreadPromptBox, RootComposeView, and EnvironmentPicker. - Regression tests assert the same trigger DOM node and open menu survive scope replay and projectless option settling. - Turbo app typecheck passed. - Formatting and git diff checks passed. > AGENT GENERATED
## Human comments ## What was wrong The launcher forwarded service stdout from a data callback into `process.stdout.write()` and inherited terminal stderr. On macOS, a stalled terminal can block that synchronous write and stop the launcher from draining child output. A log burst can consequently freeze the service. Replacing the callback with `pipe()` still blocks in the terminal write; an isolated pseudo-terminal reproduction confirmed this. ## What changed Send both service stdout and stderr directly to private append-only `server-stdio.log` and `host-daemon-stdio.log` files in the selected logs directory. Apply this to full-stack launches and the standalone server and host-daemon commands, and remove the launcher output buffer and forwarding callbacks. Close the parent's log descriptor after spawning each child. Update launcher help, the package README, debugging docs, CLI guide, and skill with the capture locations and `tail -F` usage. Existing application logs retain their rotation and remain the desktop log viewer's source. The new capture files append across restarts without automatic rotation. There are no server/daemon wire changes. ## How you verified - `pnpm exec turbo run smoke:tarball --filter=bb-app` — passed locally, including full-stack startup and separate daemon enrollment. The smoke harness now checks each daemon capture file for its plugin-ready message and includes that output on failure. - `pnpm exec turbo run test typecheck --filter=bb-app` — passed; 79 tests across four files. - New real-child tests cover multi-megabyte stdout/stderr bursts without parent stream readers, file permissions, retained output across restarts and early failures, inaccessible log paths, and executable spawn failures. - Isolated macOS pseudo-terminal check: leave terminal output undrained, write 8 MiB across child stdout/stderr, and verify the child health endpoint responds and the launcher heartbeat advances. All 8,388,610 output bytes were captured. - `git diff --check` — passed. No production processes were restarted; desktop UI was not exercised. > AGENT GENERATED
## Human comments ## What was wrong After selecting High reasoning and sending a follow-up in a thread with a saved Max override, the turn used High but reload could restore Max. Explicit sends already repaired stale model overrides; reasoning overrides were left unchanged. See #3257. ## What changed Extend the existing model override recovery path to reasoning in direct sends and queued dispatch. Picker changes remain local until submission; queue timing and retry behavior are unchanged. This replaces the previous save-on-selection approach. No client, wire, protocol, schema, or CLI changes are needed. The final diff is 6 files, 48 additions and 11 deletions. Tests extend existing fixtures without renaming files. ## How you verified - Fail-before regression demonstrated stale Max after explicitly choosing High; existing regression cases now cover changed and unchanged models. - Focused/adjacent server tests: 128 passed across override recovery/policy, send/queue dispatch, retries, and public thread data. Existing direct/queued tests assert the recorded model and reasoning. - Adjacent app tests: 97 passed in the implementation worktree. - Turbo build/typecheck/lint for `@bb/app` and `@bb/server`, formatting, and diff checks passed. App lint has existing warnings; server has no lint task. - Live isolated browser QA: an unsent selection stayed local; sent High persisted in server defaults and after reload. The issue's exact immediate post-send reset was not reproduced in the Codex fixture. Fixes #3257 > AGENT GENERATED Co-authored-by: Sawyer Hood <sawyerjhood@gmail.com> Co-authored-by: Michael Yong <wrong92@gmail.com>
## Human comments ## What was wrong The Account Pooler sign-in flow rendered the provider sign-in label twice: once as the accessible dialog title and again as a visual step heading. The footer Cancel button duplicated the dialog close action, while the compact drawer did not expose the corresponding header close control. ## What changed - Keep one accessible `Sign in to …` title and retain the provider-specific instruction copy. - Remove Cancel from the active sign-in footer; Codex now omits the empty footer while Claude retains Complete and the connected step retains Add another/Done. - Put the shared responsive `DialogClose` in the Account Pooler dialog header so both desktop dialogs and compact drawers retain an explicit close action. - Add regressions for the single heading, absent Cancel action, and pending-session cancellation/poll teardown across wide and compact layouts. No host-daemon wire, CLI, guide, or public plugin API contract changes are involved. ## How you verified - `pnpm exec turbo run test typecheck --filter=bb-plugin-account-pool --force` — 269 tests passed; typecheck passed. - `pnpm exec oxfmt --check plugins/account-pool/app.tsx plugins/account-pool/app.test.tsx` - `git diff --check` - Browser QA at 1280×900 and 390×844 with a local fake device-auth fixture confirmed the single title, absent Cancel button, responsive header close, preserved device code/copy/open/countdown behavior, and preserved Claude/final-step actions. > AGENT GENERATED --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
## Human comments ## What was wrong Message editing was already enabled by default, but the app and server still gated it behind the `editMessages` experiment. Users who previously disabled the experiment could not edit otherwise eligible messages. ## What changed Promote message editing to a standard feature by removing the experiment key, Settings toggle, and app/server gates. Preserve provider rewind support and existing thread/message eligibility checks. Update fixtures, CLI guide templates, settings documentation, and the affected verification recipes. Existing CLI and SDK editing surfaces now work without the experiment. Bump the plugin SDK to `0.4.54` because removing the experiment changes its bundled declarations. No host-daemon wire changes. ## How you verified - `node packages/plugin-sdk/scripts/check-npm-version-guard.mjs` — passed after the SDK version bump. - `pnpm exec turbo run test --filter=@bb/server --filter=@bb/db --filter=@bb/app -- experiments thread-edit-message` — 51 tests passed. Message-edit service tests now seed a legacy disabled experiment value to verify it no longer blocks editing. - `pnpm exec turbo run typecheck --filter=@bb/app --filter=@bb/server --filter=@bb/desktop --filter=@bb/cli` — passed. - Isolated source app: confirmed Settings → Experiments shows only the four remaining toggles, and `/api/v1/system/config` excludes `editMessages`. Stopped the test app and removed its marked test data afterward. - `git diff --check` — passed. Live provider rewind was not rerun; service tests cover the editing flow. The broader verification inventory remains blocked by pre-existing drift for the unmapped `browser` CLI family. > AGENT GENERATED
## Human comments ## What was wrong The unshipped idle Claude process release setting exposed a dormant provider behavior and its associated bridge lifecycle path despite having no supported use. ## What changed Removed the provider setting, option derivation, bridge protocol fields, idle-release implementation, obsolete coverage, and related CLI guidance and documentation. ## How you verified `pnpm exec turbo run typecheck test --filter=bb-plugin-provider-claude-code` 348 tests passed. Fixes # > AGENT GENERATED
## Human comments ## What was wrong Timeline pages grouped independent event/byte windows and assigned fragment IDs. The client merged by row ID, so boundaries could change accepted messages to pending, fragment turns, and lose or duplicate content. Reproduced on main before implementation: three of four large copied-corpus walks disagreed with canonical history. ## What changed Select conversation groups using existing anchors, load their full turn/delegation context, and filter context-only groups out of page ownership. Oversized groups paginate canonical children under stable summary IDs; the client and CLI recursively merge those children. The existing detail endpoint and SDK now support continuation cursors, and the app walks them on expansion. Opaque cursors bind the history tip, grouping version, status, and display surface. Appends stay outside an existing walk. Pagination continues on a best-effort basis across history edits and suffix replacement: pages loaded before and after an edit may disagree until history is reloaded. A new latest snapshot replaces loaded history because late events can change earlier grouping. Cursors whose anchor event was deleted return HTTP 400, preserving main's behavior. Existing message-edit notifications invalidate request and grouping-context caches. There is no new database table, migration, event trigger or daemon wire change. Main's large-output sidecars remain intact; no rejected streaming-projection code was used. ## How you verified - Actual Hono endpoint plus actual client merge matched complete canonical content/order on four copied threads: 63,853, 42,033, 32,384, and 22,940 events. The oracle removes one exactly duplicated canonical delegation row in the fourth thread; it does not discard differing content with the same ID. - Tiny event/512-byte targets, huge turns/details, nested/late events, straddling items, appends between requests, continuation after in-place edits, same-sequence suffix replacement, rejection of deleted anchors, and a stale in-flight app page are covered. - Turbo: 158 server timeline/public-route tests, 19 client merge tests, 142 database event/migration tests, 30 app query/controller tests, 18 CLI log tests, 104 SDK tests, and 67 server-contract tests. Affected server/client/CLI/app/database/SDK/contract typechecks passed. Server tests and typechecks passed again after rebasing onto `682f0d2c5d`. - Isolated source browser: paged to all 45 synthetic messages exactly once, expanded the oldest summary into 20 commands, and reloaded. Source CLI `thread log --all --format verbose` returned all 45 messages exactly once. Owned dev processes/store were cleaned up. No provider execution or mobile Safari verification is claimed. The verification inventory has an existing unrelated unmapped `browser` CLI family. The initial 1.7–5.0 s walks were rejected as too slow. The optimized paired run restores main's original timeline builder, event queries and client merge from `682f0d2c5d` and runs main/fixed/fixed/main per copied thread, with fresh databases and route caches. Correct walks now take **0.72–1.76 s**, versus **0.68–2.15 s** for main: the two largest walks are faster, and the other two are within 6%. All eight fixed walks match canonical content/order. Cold latest requests retain roughly 3 ms and 13 ms overhead on two threads; exact numbers are in the report. The optimization scopes payload context, selects turn IDs before reading full event bodies, skips already-loaded request lookups, and uses existing selective indexes for turn, child and interruption queries. Only compact request-to-turn IDs and an ordering boundary are cached per exact snapshot; no projected rows or payloads are cached. The profile clears that cache to include its cold construction cost. New tests guard against decoding unrelated history and acceptance after a later conversation boundary. CI's missed optional-contract and old sequence-fragment expectations are now covered and pass locally. Ordering overlap detection uses a linear scan, with six cases for accepted turns, overlapping alternatives and expired spans. A paired comparison against `51e0dc78d2` preserves canonical results but does not establish another general latency improvement. History-edit invalidation was removed in favor of the best-effort contract above; regression tests require these cursors to continue. [Full benchmark report](https://github.com/get-bb/bb/blob/bb/timeline-pagination-correctness/docs/timeline-pagination-verification.md). [Pagination lifecycle and SDK contract](https://github.com/get-bb/bb/blob/bb/timeline-pagination-correctness/docs/timeline-pagination.md). > AGENT GENERATED
## Human comments ## What was wrong Several synchronous database paths read more event history or payload data than their results required: filtered high-water marks aggregated complete histories, open-turn recovery did not seek directly past interruption boundaries, request recovery repeated scans for requests from the same thread, and search loaded text before limiting matches. Destroyed-environment cleanup bounded rows but could rewrite megabytes in one batch. The timeline byte-budget helper also aggregated up to 2,001 payloads before potentially scanning them again. ## What changed Six separate fixes: | Issue | Fix | Before → after, ms | |---|---|---:| | Latest sequence for seven active threads | Seek the final indexed event per requested thread | 5.795 → 0.007 | | Open accepted turns | Seek past each requested thread’s latest interruption | 1.467 → 0.216 | | Search for `the` | Rank and limit metadata before loading result text | 87.482 → 80.570 | | Eight request IDs in one thread | Group request IDs so each thread history is scanned once per batch | 21.932 → 2.926 | | Large-event environment detachment | Cap each batch at 256 KiB, preserving progress for oversized events and existing yields between batches | 4.038 → 0.619 longest batch | | Timeline byte-budget helper | Stop at the byte budget without a preceding aggregate scan | 5.075 → 0.819 in the historical 512 KiB case | These are controlled warm benchmark medians from a private database snapshot or isolated fixtures, not deployed latency measurements. The original measurements preceded the removal of an experimental delta index; that index remains present in the benchmark snapshot. Detachment total work was 4.038 → 3.616 ms across 1 → 17 batches. Rebased onto the snapshot-bound conversation-group pagination change in #3325. The byte-budget helper now serves only turn-summary detail expansion; main timeline pages no longer call it. A fresh comparison against main at the caller’s 4 MiB budget returned identical results in all six fixtures. An oversized 5,000-event window improved 2.667 → 1.717 ms, while a fitting 2,000-event window regressed 0.454 → 1.114 ms due to JavaScript iteration. Retained the early-stop behavior with this explicit tradeoff. The net change has no checkpoint worker, schema/migration, server/daemon wire, CLI, or configuration changes. ## How you verified - Database regression coverage exercises lookup batching and query plans, byte-budget early exit, and byte-bounded environment cleanup, including an oversized first event. - `pnpm exec turbo run test typecheck --filter=@bb/db`: 34 files, 481 tests passed; typecheck passed. - `pnpm exec turbo run test --filter=@bb/server -- test/services/threads/timeline test/services/threads/thread-runtime-display.test.ts test/services/database-maintenance-sweep.test.ts test/system/event-pruning.test.ts test/public/public-thread-data.test.ts test/public/public-thread-diagnostic-events.test.ts test/public/public-thread-timeline test/public/public-thread-search.test.ts`: 20 files, 205 tests passed, including the new pagination and detail-expansion cases. - `pnpm exec turbo run build typecheck --filter=@bb/server`: passed. - `git diff --check`: passed. Rebase range comparison confirmed unchanged patches. - Fresh timeline helper benchmark: migrated in-memory database, five warmups, 31 alternating measured iterations per implementation, exact result equality. Production was not modified or restarted; no deployed latency or full historical corpus replay is claimed. > AGENT GENERATED
## Human comments ## What was wrong The `desktop-latest` release has published a Linux x64 AppImage and a `desktop-version-linux.json` feed since desktop 0.42, but getbb.app only advertised "Download for macOS" everywhere and told Linux visitors to use `npx`. There was no Linux download redirect on the site at all. ## What changed - `apps/web/src/landing/site.ts`: a `DesktopPlatform` type and a `DESKTOP_DOWNLOADS` table (labels, installer extension, version feed URL, redirect path) replace the macOS-only constants. `downloadHref(platform, placement)` replaces `downloadMacosHref`. - `apps/web/src/landing/endpoints.ts`: `handleDownload(platform, ...)` generalizes the macOS redirect. It fetches the platform's feed, picks the first asset with that platform's installer extension, and falls back to the release page. The PostHog event keeps its per-platform name (`landing_download_<platform>_clicked`) and `download_target`, so the existing macOS dashboards keep working. - `apps/web/src/routes/download.linux.tsx`: new `/download/linux` route. `/download/macos` now calls the shared handler. - `apps/web/src/landing/desktop-platform.ts`: `detectDesktopPlatform` reads client hints, `navigator.platform`, and the UA string. It returns `linux` for X11 Linux (not Android or ChromeOS), `macos` for Macs (not iPads reporting `MacIntel`), and `null` otherwise. `useDesktopPlatform` applies it after hydration with macOS as the SSR default. - Hero, nav, and footer download links follow the detected platform. The hero shows an "Also for macOS" / "Also for Linux" link under the primary button, and the `npx` note now reads "Windows (via WSL), Intel Macs & remote machines". The Linux button uses a new inline `LinuxIcon` carrying the CC0-licensed Tux glyph from Simple Icons, since hugeicons free has no Linux icon. - Windows and mobile visitors see the same page as before (macOS button plus `npx`). Design note: this is the "detect the visitor's OS, primary button follows it, other platforms as a secondary link" pattern that VS Code, Cursor, Zed, and Warp use. Two alternatives were mocked up and rejected: side-by-side macOS and Linux buttons (three CTAs crowd the hero), and a static macOS button with an "Also available: Linux AppImage" line beneath (buries the Linux path for Linux visitors). ## How you verified - New tests: `desktop-platform.test.ts` covers client hints, X11 Linux, Android, ChromeOS, macOS, iPad-as-MacIntel, and Windows. `endpoints.test.ts` adds Linux feed resolution, a guard that a Linux request never returns a `.dmg`, and asserts the tracked event name, `download_target`, and placement. `site-chrome.test.tsx` asserts the SSR nav href stays `/download/macos?placement=nav`. - `pnpm exec turbo run typecheck test lint --filter=@bb/web` passes (23 files, 124 tests). - Manual, against `vite dev`: `curl -I /download/linux` returns a 302 to `bb-0.42.1-x86_64.AppImage`; `/download/macos` still returns the 0.42.1 dmg. In headless Chrome on Linux the nav and hero show "Download for Linux" with hrefs to `/download/linux`; with a macOS UA and `MacIntel` platform emulated they show "Download for macOS". Checked the 390px dark-mode layout as well. 🤖 Generated with [Claude Code](https://claude.com/claude-code) > AGENT GENERATED --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## Human comments ## What was wrong Docs could not load any vault when the host list was nonempty: `listNotes` returned `rpc output validation failed`. PR #3227 removed `type` from Docs' strict host schema, while the SDK continued returning that field. Existing Docs fixtures returned empty host lists, hiding the regression. ## What changed Strip additional host properties from the Docs RPC response while continuing to validate its declared fields. Add regression coverage using the complete SDK host fixture for both successful file listing and the unavailable-vault fallback. ## How you verified - Both new regression cases failed before the fix with `Unrecognized key: "type"`; all 68 Docs tests pass afterward. - `pnpm exec turbo run test typecheck build --filter=bb-plugin-simple-notes` passed. - Verified in an isolated source dev app with a connected host and synthetic vault. Temporarily restoring the original validator reproduced the exact error in both the browser and real RPC. Rebuilding/reloading the fixed plugin loaded the vault and opened its Markdown note through browser input. Captured before/after screenshots and RPC responses. - Secret-model scan clean for the working tree, HEAD, added lines, and commit messages in the push range. - SlopCop skipped as requested. > AGENT GENERATED
## Human comments ## What was wrong - Thread-list controls changed placement and menu structure between Pinned, projects, sections, machines, and loose threads. Worktree creation was hidden in its menu, and independently styled buttons made the sidebar harder to scan and maintain. - Pinned could remain stuck above unrelated groups after its threads scrolled away. Compact worktree actions were inaccessible to touch, and reversing Updated at also reversed the existing active-first priority. ## What changed - All organizing headers share New thread followed by **…** on hover/focus, with visible controls on touch. Every header menu offers New project, New section, and compact nested Organize and Sort by menus, followed by existing context-specific actions. - Organize is an exclusive choice and closes after selection. Sort remains open while tuning; selecting the current field reverses its arrow and direction while Updated at keeps active threads first. The additive `sidebar.sortDirection` preference uses the existing persistence, SDK, and CLI paths; `default` preserves prior ordering. - Worktrees expose New thread inline, leaving Rename and Archive in their menu. Project editing actions are grouped together and separated from the existing Remove action. No creation destinations or destructive consequences change. - Shared row-control components and semantic color/state tokens keep titles primary, grouping labels quieter, and controls secondary—including on hovered or selected rows. Navigation **More** uses a quieter resting color. Pinned now scrolls with its contents. - Production Sidebar Overview stories replace the discarded prototype. The implementation removes superseded controls and delegates menu open/close state to the shared dropdown; the slimming pass removes 96 net lines (78 production, 18 tests) without changing approved behavior. Thread nesting, archive placement, plugin actions, and command-palette behavior are outside this change. ## How you verified - [Remote CI passed](https://github.com/get-bb/bb/actions/runs/34518997666) at exact head `cde7aeceeeb00958b82badfecd03437b04059f2d`: build/typecheck/lint, all app/server/package/integration tests, and Linux/macOS package smoke. All 14 reported checks passed; native iOS and Node compatibility were workflow-skipped. No local CI-equivalent checks were run. - Rebased onto `main@e02f6e6`, retaining the prior merge resolutions. Tree comparison confirms the result is exactly the previous implementation plus upstream changes; the PR remains 27 files. Main's touch-visibility regression exercises the actual shared project controls through closed, open, and reclosed states. - Exact-head Chrome for Testing **153.0.8010.36** verification passed at **1440×900** and **390×844**: project menu ordering, Organize closing after selection, Sort remaining open during direction changes, and compact Back/close/reopen reset. Project controls remain visible and interactive with 36×36 touch targets before/during/after opening. Restored fixture preferences persist after reload. - Earlier bounded QA covered project/machine/custom views, active-first sorting, light/dark control states, plugin styling, Pinned scrolling, worktree creation/rename, and collapsed touch controls. Retained regression tests cover action ordering, callbacks, focus behavior, menu lifecycle, preference validation, and active-first date sorting. - **Remaining verification limits:** phone coverage is Chrome touch emulation, not native iOS/Safari. The existing compact retained-focus `aria-hidden` warning also occurred during close/reopen; the app root remained non-inert and exposed. The warning is not claimed fixed by this rebase. - One cumulative review was completed previously; no second review was run. `git diff --check` passed. - Screenshots use the same synthetic data, route `/`, project view, Updated at sorting, light theme, and open Atlas project menu. Before: exact merge base `e02f6e64cc7afdf490e70a662c6445b5b8dee407`. After: exact head `cde7aeceeeb00958b82badfecd03437b04059f2d`. Desktop images are matching 460×844 crops of a 1440×900 viewport; phone images are full 390×844 captures. | Surface | Before | After | | --- | --- | --- | | Desktop project menu |  |  | | Phone project menu |  |  | BB-Thread-ID: thr_ccffp4w2p2 > AGENT GENERATED
## Human comments ## What was wrong - Custom-organized sidebar threads could be moved by drag and drop, but their row actions offered no destination picker. ## What changed - Adds **Move to section** with `MoveTo` to active root-thread actions in **Custom** view: desktop dropdown/right-click submenus and a compact long-press step with **Back**. - Follows saved sidebar order, including **Threads**, and omits **Pinned**. The current destination is disabled for unpinned threads. Existing mutations preserve move persistence; moving a pinned thread unpins it, and choosing its stored section only unpins it. - Integrates with the shared sidebar header controls from #3402. The focused diff remains 12 files; main's bundle limits are unchanged. ## How you verified - Remote [CI](https://github.com/get-bb/bb/actions/runs/34524003966) for `d334e6953963fd5891a1fdb7eb75f020cd604d0f`: **14 passed, 2 skipped**, including checks, app/server/package/integration tests, and macOS/Ubuntu package smoke. Optional Node compatibility and native iOS jobs were skipped. The app fits unchanged bundle limits: **1681.7 KB raw / 410.9 KB brotli** against **1683.2 KB / 419.0 KB**. - Menu and mutation tests cover desktop/context parity, eligibility, compact Back/reset, destination no-op, section moves, and both pinned transitions. Existing optimistic-update and serialized-unpin coverage remains. - Final-head Chrome for Testing **153.0.8010.36**: shared header **By project → Custom** eligibility; desktop dropdown and right-click moves to a section and **Threads**; hard-reload persistence and disabled current destination; compact touch long-press, **Back**, close/reopen reset, and saved moves. No captured runtime errors or compact app-root `inert`/`aria-hidden` changes. All six synthetic threads and sidebar settings were restored. The unchanged pinned transitions were exercised before this rebase and retain passing remote mutation coverage. - Native iOS is unverified: the local Xcode/CoreSimulator setup is incompatible and cannot resolve `simctl`; remote iOS coverage was skipped. Phone-width Chrome does not establish native iOS or Safari behavior. | Surface | Before — merge base `de9361268088` | After — head `d334e6953963` | | --- | --- | --- | | Desktop web · 1440 × 900 |  |  | | Phone web · 390 × 844 |  |  | - Both revisions use `scripts/bb-dev-app current` under **Node 22.19.0**, sequentially in the same managed worktree. Captures match the synthetic **Release notes** thread in **Follow-ups**, `/threads/thr_move_release`, **Custom** organization, light theme, scale 1, and viewport per row. Images were inspected directly. BB-Thread-ID: thr_ccffp4w2p2 > AGENT GENERATED
## Human comments ## What was wrong After #3390 enabled hidden entries, every file-mention query recursively scanned ignored build output, dependency stores, and nested worktrees. Incremental typing started overlapping scans. A large checkout took 5.84 seconds to search roughly 304,000 entries, leaving the new-thread composer on “Searching mentions…”. ## What changed - Project and environment searches ask Git for ignored paths and prune those subtrees before traversal. Tracked files and non-ignored untracked files remain searchable, including hidden files. Empty directories, linked worktrees, and non-Git workspaces remain supported. - Concurrent queries share pending discovery but rank their results independently. Completed and failed listings are discarded, so subsequent queries see file and ignore-rule changes. Each fresh Git-backed discovery uses two Git subprocesses; there are no per-file subprocesses. - General filesystem APIs and skill/thread-storage listing retain their existing ignore behavior. The server explicitly supplies the new `respectGitIgnore` daemon field; protocol version increases from 198 to 199. - Keep `.pnpm-store` and root-relative `.claude/worktrees` in the default exclusions. Exclusions now support exact root-relative paths as well as basenames. Update CLI help and guide/SDK documentation. ## How you verified 226 relevant tests passed, including tracked files matched by ignore patterns, new untracked files, nested ignore rules and negation, linked worktrees, hidden files, shared discovery, failure recovery, and refresh after edits: ```sh pnpm exec turbo run test --filter=@bb/host-daemon -- file-list.test.ts workspace-path-list.test.ts pnpm exec turbo run test --filter=@bb/host-daemon -- workspace-dispatch.test.ts pnpm exec turbo run test --filter=@bb/server -- host-file-routes.test.ts public-environments.test.ts internal-environment-change.test.ts public-thread-data.test.ts pnpm exec turbo run test --filter=@bb/host-daemon-contract pnpm exec turbo run typecheck --filter=@bb/host-daemon --filter=@bb/server --filter=@bb/cli ``` With both new directory exclusions disabled, the same checkout improved from **5.84 s / 304,068 entries to 119 ms / 6,681 entries**, retaining README and hidden workflows. Verified in an isolated source app with Worktree selected: incremental `@README` search returned tracked and untracked files, selecting the untracked file inserted its mention, and an arbitrary Git-ignored build directory was omitted. The source CLI confirmed general filesystem listing can still inspect ignored files. No deployed instance was changed. > AGENT GENERATED
## Summary `.bb-env-setup.sh` runs `pnpm install` on every new environment. For a fresh git worktree that is necessary, but copy-on-write environments (reflink copy or Btrfs snapshot of the checkout, via the `btrfs-cow` provider plugin) arrive with `node_modules` already installed, and the install is a 5 to 15 s no-op. This change hashes the install inputs (`pnpm-lock.yaml`, `pnpm-workspace.yaml`, `package.json`, `.npmrc`) and stores the hash at `node_modules/.bb-env-setup-install-hash` after a successful install. On the next run, if the stamp matches and `node_modules/.pnpm` exists, the install is skipped. Because the stamp lives inside `node_modules`, it travels with a copied tree and disappears with a fresh worktree, so worktree provisioning is unchanged. ## Behavior Measured on a reflink copy of this repo (Linux, Btrfs): | Scenario | Result | |---|---| | Copied tree, no stamp yet | installs, 5.8 s, writes stamp | | Copied tree, stamp matches | skipped, 0.01 s | | Lockfile changed | installs, 6.3 s, rewrites stamp | | No `node_modules` (fresh worktree) | installs, 13.7 s, writes stamp | Other notes: - A failed `pnpm install` no longer writes a stamp, so the next environment retries. The hook still exits 0 in every case, as before. - Falls back to always installing when neither `sha256sum` nor `shasum` is available. - Existing checkouts get the stamp the first time the hook runs after this lands; running `sh .bb-env-setup.sh` in the project checkout once seeds it for copies made from it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
#3437) ## Human comments ## What was wrong The follow-up prompt box only recognised Meta+Enter as the modifier submit (`PromptBoxInternal.tsx` explicitly required `metaKey` and rejected `ctrlKey`). On Windows and Linux there was no working key for the secondary action: in Queue mode nothing steered, in Steer mode nothing queued. Worse, Ctrl+Enter fell through to the plain-submit branch, which only checks Shift, so it silently performed the primary action instead. The `aria-keyshortcuts` attribute, the Settings picker descriptions, and `docs/configuration.md` all described the shortcut as Command+Enter only. The tasks plugin editor already accepted either Meta or Ctrl plus Enter; the composer was the odd one out. ## What changed - `apps/app/src/components/promptbox/modifier-submit-shortcut.ts` (new): `isModifierSubmitKeyEvent` accepts Enter with Meta or Ctrl and no Alt/Shift, plus platform-aware label and aria helpers built on the existing `formatAppShortcut`/`formatAppShortcutAria` mapping ("⌘ Enter" / "Meta+Enter" on Mac, "Ctrl + Enter" / "Control+Enter" elsewhere). - `PromptBoxInternal.tsx`: uses the shared key check and sets `aria-keyshortcuts` from the platform helper instead of a hard-coded "Meta+Enter". - `FollowUpPromptBox.tsx`: the queue/steer submit tooltips now include the modifier shortcut and what it does, e.g. "Queue follow-up (Enter), Ctrl + Enter to steer", shown only when a modifier submit is available. - `SettingsView.tsx` and `docs/configuration.md`: mention Ctrl+Enter on Windows and Linux alongside Command+Enter. No wire or CLI changes. ## How you verified - New `modifier-submit-shortcut.test.ts` covers key matching (Ctrl+Shift+Enter and Meta+Alt+Enter rejected) and the platform labels. - New `PromptBoxInternal.test.tsx` cases: Ctrl+Enter on a Linux platform calls `onModifierSubmit` and not `onSubmit`, and `aria-keyshortcuts` reads "Control+Enter" on Linux and "Meta+Enter" on Mac. The Ctrl+Enter case fails before this change (it called `onSubmit`). - New `FollowUpPromptBox.test.tsx` cases assert the tooltip text for both steer-on-Enter settings on Win32. - `pnpm exec vitest run` on the three promptbox test files: 163 passed. - `pnpm exec turbo run typecheck lint --filter=@bb/app`: passes. > AGENT GENERATED Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## Human comments ## What was wrong Opening Account Pooler settings sat on "Loading…" for a long time. The `status.get` RPC assembled `routedThreadsWithoutLocalLogin`, which calls `system.providerStates` over host RPC for every machine that routed a thread in the last window. A sleeping or offline machine stalls that call up to the host RPC deadline, and the UI rendered nothing until the whole status returned. Every action afterward (toggle, refresh usage, reorder) re-ran the same slow status call. ## What changed - `plugins/account-pool/src/operations.ts`, `rpc.ts`, `contracts.ts`: `status.get` no longer waits on host RPCs. The routed-thread inspection is its own `status.routedThreads` RPC. `statusSchema` drops `routedThreadsWithoutLocalLogin`; a new `statusReportSchema` / `PoolStatusReport` keeps the combined shape. - `plugins/account-pool/src/cli.ts`: `bb pool status` calls both operations and prints the same text and `--json` output as before. - `plugins/account-pool/app.tsx`: the settings view caches the last `PoolStatus` in `localStorage` (validated with `statusSchema` on read) and renders it immediately. While the live status is in flight the hub line shows "refreshing…", rows show "refreshing usage…", and quota values are dimmed. A row also shows the refreshing state while its manual "Refresh usage" RPC is pending. The empty-state card only renders from live status so a stale cache never flashes "No accounts in the pool". No wire changes to the host daemon protocol; this is plugin-internal RPC. ## How you verified - New tests in `plugins/account-pool/app.test.tsx`: cached accounts render with the refreshing indicator before `status.get` resolves and the cache is rewritten afterward; a malformed cache entry falls back to the loading state; a row shows "refreshing usage…" while `account.refreshUsage` is pending. The first and third fail on main (no cache, no indicator). - `plugins/account-pool/src/server.test.ts` updated to read routed threads from `status.routedThreads` and parse CLI JSON with `statusReportSchema`. - `pnpm exec turbo run test typecheck --filter=bb-plugin-account-pool` passes (10 files, 274 tests). - Bundled the plugin app once through `buildPluginApp` to confirm the runtime `statusSchema` import bundles cleanly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) > AGENT GENERATED Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…#3436) ## Human comments ## What was wrong The prompt-box environment picker offered every environment provider on every machine, so CoW copy showed up under Macs where its reflink probe can never succeed. Per-machine plugin availability existed briefly (#3227), but #3393 removed it from discovery because the app's per-machine fan-out blocked the picker on the slowest machine's Git inspection and plugin probe. That fix removed the information rather than making it non-blocking: discovery became structural only, availability is always null, and an unsupported choice now fails only at thread creation. ## What changed Server (`apps/server/src/services/environments/provider-machine-availability.ts`, new): a background per-machine availability probe. When a project's providers are listed, each structurally eligible provider and connected persistent machine is probed in the background (Git inspection when `requires.gitCheckout`, then the plugin's `availability` hook), keyed by provider, project, and machine. Listing never awaits a probe: it returns the cached answer or null. Results are cached for ten minutes, cleared when a plugin rechecks, and disconnected machines are never probed. When an answer changes, the server emits a new `environment-availability-changed` system change so clients refetch. Contract (`packages/server-contract/src/api/system.ts`): each provider now carries `machineAvailability`, a map from host id to `available`, `setup-required`, `unavailable`, or null while unknown. `availability` is filled from that map when `hostId` is given. Thread creation still checks the selected provider and machine afresh, exactly as #3393 left it. App: `NewThreadComposer` lists providers for the current project and derives each machine's rows from `machineAvailability`. The picker hides providers a machine reports as unavailable, except the currently selected one, which stays visible and disabled with its reason so the selection never disappears; setup-required rows stay selectable with their message; unknown rows show as before. The provider list is remembered per project in localStorage through the existing last-known cache and served as placeholder data, so the picker is already trimmed on the first open after a reload. The realtime registry maps the new system change to the provider query. CLI: `bb environment providers --project <id> --machine <id>` prints each provider's availability on that machine, and `--json` includes `machineAvailability`. Guide template, bb-cli skill, `docs/api_to_audit.md`, the Plugin Guide surface text, and the SDK doc comment were updated to describe background probing. No server/daemon wire fields changed, so `HOST_DAEMON_PROTOCOL_VERSION` is unchanged. ## How you verified - Server tests in `apps/server/test/threads/environment-providers.test.ts` rewritten for the new contract: listing answers null first, the background answer is served on the next listing and `environment-availability-changed` fires once; Git inspection runs in the background and a missing branch is reported as unavailable; disconnected machines are not probed; unmet structural requirements never probe; a plugin recheck invalidates the cache and probes again; a throwing hook is reported as unavailable without failing the listing; creation still checks afresh. 43 passed. - App tests: picker hides unavailable rows, keeps the selected unavailable row disabled with its reason, keeps setup-required selectable with its message, hides per machine in the grouped menu; provider query writes and serves the last-known cache and rejects a corrupt entry; realtime registry covers the new system change. - CLI test for the availability column with available, unavailable, and unknown rows. - `pnpm exec turbo run typecheck` for server, app, cli, domain, server-contract, sdk, plugin-sdk, plugin-api-map, and templates passes. - `pnpm exec turbo run test` for cli, server-contract, sdk, domain, templates, plugin-api-map, and plugin-sdk passes. Full server and app Vitest suites pass (the app's turbo test task fails before tests on this machine because its PWA icon check needs a libvips module that isn't installed, so the app suite was run with vitest directly). - Manual, on the earlier client-only version of this change: a dev server with the btrfs-cow plugin and two projects on one machine, one on btrfs and one on tmpfs; the tmpfs picker omitted CoW copy and was already trimmed on first open after a reload. > AGENT GENERATED Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
## Human comments ## What was wrong The Cmd-K palette consumed Enter and navigation keys during IME composition. Confirming a candidate could activate the highlighted result and close the palette; navigation keys could move the palette selection. ## What changed Return before palette keyboard handling when the native event is composing. Cover Enter, ArrowDown, ArrowUp, Home, and End, preserving selection and leaving composing events unconsumed. Also verify ordinary navigation and exactly-once command activation after composition ends. Combines the useful regression assertions from #3414 without duplicating its identical production fix. No wire, SDK, CLI, configuration, or host-daemon protocol changes. ## How you verified - Independently reproduced on pristine main `7ba69eb050c4a800db9255c8fbaaa23ca4bab32d` in the running app using Chromium DOM CompositionEvent/KeyboardEvent dispatches. - Regression-only run before the guard: 6 failed, 23 passed. - `pnpm exec turbo run test --filter=@bb/app -- src/components/commands src/lib/command-palette`: 76 passed across 7 files. - `pnpm exec turbo run typecheck build lint --filter=@bb/app`: passed; lint reported 190 warnings and zero errors. - Changed-file formatting and `git diff --check`: passed. - Real Chromium browser QA: 10 scenarios passed, including unchanged composing selection, default-prevention semantics, resumed navigation and one sidebar toggle after normal Enter. - Composition events were synthetic (`isTrusted: false`); ordinary keys used trusted browser keyboard input. Native OS Pinyin candidate UI, Safari event ordering, and iOS were not verified. Fixes #3413 > AGENT GENERATED
## Human comments ## What was wrong Opening a thread with a plugin palette such as Monokai could leave diff bodies blank. The diff worker pool resolved its initial theme before the theme-sync component registered that palette's loader, producing `No valid theme loader registered`. ## What changed Register resolved custom theme files at the worker-pool acquisition boundary before creating the singleton. Existing theme synchronization still handles later palette changes. ## How you verified - Added a regression test using Pierre's real theme resolver. It fails with the original missing-loader error before the fix and passes afterward. - Eight focused worker-pool and theme synchronization tests pass. - All CI test packages pass in a clean checkout with a minimal environment. The integration suite passes all 77 tests across 26 files with `--maxWorkers=4`. - Clean-branch CI build, typecheck, lint, bundle budgets, version guards, provider-literal ratchet, SDK npm guard, and macOS tarball smoke pass. The SDK guard used npm 10 for its supported JSON output. - Linux AppImage checks require a Linux/FUSE runner. Actions cache usage requires the GitHub Actions token and was skipped locally. - In the source-built dev app, cold-loaded a recovered Smart Diff using the Monokai palette. The diff body rendered with syntax highlighting and no browser errors. > AGENT GENERATED
…ew-and-finish-pr-3212-thr_qaatfjmeix
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.
Human comments
What was wrong
Every global thread lifecycle event made Tasks enumerate all tasks and query each task's worker mappings before filtering by the event's thread ID. On current main
f41d564163180f87304986b61706db6d69314759, an unrelated event executes 202 SQL statements with 200 tasks and 505 with 501 tasks. The task-list method itself performs two statements per page, so method-call counts understated the database work. Original investigation: https://get-bb.github.io/reports/issues/3210.html.What changed
Use an internal lookup returning every mapping for the event's thread ID through the existing
idx_task_threads_threadindex. Unrelated events now execute one lookup; legitimate multi-task fan-out, comments, notifications, recovery, and terminal protection remain intact. Startup and periodic reconciliation are unchanged; #3213 addresses the separate duplicate-startup issue.Added SQL-level coverage using real in-memory SQLite and the Tasks schema migrations, alongside the original no-scan regression. Removed two existing non-semantic comments to satisfy repository lint. No schema/index, public SDK/CLI, configuration, or server/daemon wire changes; no host-daemon protocol bump is needed.
How you verified
pnpm exec turbo run test typecheck build lint --filter=bb-plugin-tasks: 388 tests passed in 36 files, plus typecheck/build. Tasks has no lint script, so explicit changed-file oxlint was also run.git diff --checkpassed.pnpm start:worktree; server and daemon health checks passed. Installed/enabled the bundled Tasks plugin and confirmed 12 sample tasks across 3 projects through the running API/CLI. No browser-driven UI verification is claimed.Fixes #3210