Add bb thread import for existing ACP sessions - #1047
Open
mgpai22 wants to merge 14 commits into
Open
Conversation
Adds supportsSessionImport to provider capabilities (true for ACP providers, whose session/load maps to import; verified live at session open), the /threads/import request contract, the thread.start sessionImport wire descriptor, and the historical marker on turn framing events so replayed history persists without lifecycle side effects. Bumps HOST_DAEMON_PROTOCOL_VERSION to 72 for the thread.start payload change.
POST /threads/import validates the imported session's cwd against the project source (or an existing project workspace) and creates a thread carrying a sessionImport descriptor through provisioning to thread.start. The runtime maps it to a new thread/import adapter command; the ACP bridge opens the session via session/load (no fresh-session fallback, clear errors when the agent lacks loadSession or the load fails) and forwards the replayed history as historical updates instead of dropping them behind the loading gate. Historical turn framing settles the thread idle with no live turn state or server lifecycle effects, and replayed user_message_chunk updates become userMessage items.
bb thread import --project --provider --provider-session creates a thread bound to an existing external ACP session, with optional --host, --cwd, --title, --permission-mode, and --visibility. The SDK exposes threads.import and the bb-cli skill documents the workflow.
The fake ACP agent can now fail session/load and replay a scripted history during it. Bridge tests prove the replay is forwarded as historical updates, that imports refuse agents without session/load, and that load failures surface a clear error instead of a fresh session. Adapter tests cover the historical replay translation; route tests cover the sessionImport thread.start dispatch, cwd mismatch refusal, and the capability gate.
The skill-tree route test hardcoded mode 0o644 for a file written with default permissions, which only holds under a 0022 umask; write it explicitly instead. The install-machine-script fallback test assumed no bb-app binary is reachable on PATH, which a machine with a real global bb-app install breaks; give the script a hermetic PATH that strips any entry resolving bb-app while still exposing node.
…agents Refuse importing a provider session another live thread already binds (server-side 409 via a new findLiveThreadIdByProviderThreadId reverse lookup, plus a bridge-side guard so the process-local routing map is never silently overwritten). Derive supportsSessionImport from the agent's live `initialize` handshake instead of trusting the static ACP-family constant, so an agent without session/load is refused before an environment is provisioned and a doomed thread.start is dispatched; this needed a new provider.list_models result field, bumping HOST_DAEMON_PROTOCOL_VERSION. Also close two historical-replay gaps: exclude historical turn/completed rows from start-activation staleness so a replayed frame can never strand a thread in "starting", and close the synthetic historical turn with a cancelled turn/completed when session/load fails after a partial replay so it doesn't stay open forever.
The in-CLI guide (bb-guide-threads.md) documented Forking but never gained an Importing section when the import verb shipped, so agents reading it in-app never learn the command exists. Add it alongside the other flags in the same style, and regenerate the derived template and plugin-sdk bundle outputs.
The suite mixes fast unit tests with fixtures that shell out for real git clone / npm install / esbuild work (plugin install, bb-app artifact builds). Under full-parallel turbo runs, those fixtures can exceed vitest's default 5s budget even though nothing is hung, which was tripping a rotating, unrelated subset of tests on every full gate run. Sibling packages already raise this for the same reason (plugin-registry, host-daemon, app); do the same here.
…nd lossy replay Move the duplicate-binding check before session/load so a rejected import never forwards history for a provider session it won't end up owning, and close any historical turn that still slips through a later race instead of leaving it open forever. Probe supportsSessionImport for ACP agents whose model list comes from a CLI command too, since they previously never reached the session-discovery path that populates the capability cache. Replace dropped non-text replayed user message chunks with a placeholder instead of losing them, since a later resume drops replay entirely. Require an explicit --cwd for `bb thread import`: bb has no way to read the external session's real working directory back from it, so a silently-defaulted cwd made the mismatch refusal unreachable for the common case. Add a runtime-level test covering the historical-replay bypass that skips turn/background/idle/goal state machines for imported history.
findLiveThreadIdByProviderThreadId ordered by events.sequence, a per-thread counter, so across multiple matching threads it picked whichever had logged the most events rather than the one most recently touched. Order by createdAt instead. The lookup also had no supporting index and scanned the full events table on every thread import; add a partial index on provider_thread_id.
…import A stranded replayed user message survived a torn-down historical turn (e.g. thread/stop racing ahead of the trailing turn/completed) and leaked into the next live turn as a phantom userMessage that was never actually sent; clear the accumulator alongside the other per-turn state. Concurrent thread/import requests for the same provider session both passed the pre-load unbound check (neither was bound yet) and both forwarded replayed history before the loser was rejected. Reserve the provider session id right after the pre-load check, before session/load goes out, so a losing concurrent import fails before it can dispatch session/load at all. The session/load capability cache expired independently of, and earlier than, the model-discovery cache it's learned alongside, silently dropping supportsSessionImport from model/list in the gap. Re-stamp both caches together. Also add negative caching and a bounded timeout to the CLI-agent capability probe so a broken or slow agent isn't re-spawned on every model/list call and can't push an otherwise-fast CLI catalog reply toward the server's command timeout. The capability probe resolved its launch spec from the static known-agent table only, so a custom ACP agent shadowing a built-in provider id was probed against the wrong binary, and a purely custom agent skipped the probe entirely and fell back to the static ACP-family allow. Share the same custom-agent-aware resolution thread.start uses. Fix the @bb/server-contract allowlist claiming importThreadRequestSchema.cwd is optional when the schema requires it, and cover the previously-untested workspace and cross-project branches of the import cwd check.
Collaborator
|
TY for the pr @mgpai22 will take a look tomorrow |
mgpai22
force-pushed
the
mgpai22/acp-session-import
branch
from
August 6, 2026 03:34
1fa96a2 to
d624d6d
Compare
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.
Implements #1028.
Adds
bb thread import, which adopts an existing external agent session as a bb thread: the replayed history lands in the timeline, and the thread continues that same provider session.How it works
The ACP bridge already calls
session/loadwhen resuming a bb-created thread, and drops the replayedsession/updatestream behind thesession.loadinggate. That is correct for resume, because bb already holds that history.Import reuses the same call with a caller-supplied session id, and forwards the replay instead of dropping it. Replayed events are marked
historical: they persist as timeline events but skip turn lifecycle effects, so the thread lands idle without running a turn. Live turns after that behave normally.Also included:
supportsSessionImport, derived fromagentCapabilities.loadSessionat initialize, with a live per-agent probe on top of the static ACP-family default. Providers without it are refused.HOST_DAEMON_PROTOCOL_VERSIONgoes to 75.thread.startgained thesessionImportdescriptor andprovider.list_modelsgained the optionalsupportsSessionImportresult field; an old daemon would drop the descriptor and silently start a fresh, history-less session.Verification
Tests cover the bridge (replay persisted, unsupported agent refused, load failure surfaced), adapter translation, and the route (cwd mismatch, unsupported provider) against real in-memory sqlite.
Live run against omp 17.2.9: a 2,404-message session imported as 1,994 persisted events (993 completed items, including tool calls and reasoning), thread idle, no model calls. Bogus session id and cwd mismatch both refuse with a clear error.
Known limits
session/loadtakes cwd as input and it cannot be probed from the agent, so validation is against the project's own paths.Two commits touch test infrastructure unrelated to the feature: a
@bb/servervitest timeout raise, and hermeticity fixes ininstall-machine-script(a globally installedbb-appon PATH leaks into the test that asserts its absence) andinternal-skill-trees(hardcoded 0644 vs a 0002 umask). Happy to split those out.