Surface pending interactions in the CLI and add thread wait --until-input - #1698
Surface pending interactions in the CLI and add thread wait --until-input#1698SawyerHood wants to merge 4 commits into
Conversation
…nput Threads that wait for a question, approval, or permission grant looked healthy from the CLI and rejected new prompts with HTTP 409. This change makes the stall visible and resolvable from the CLI and SDK: - bb thread show prints 'active (waiting for input)' plus a Pending interactions section with the exact resolve command; --json includes pendingInteractions. - bb thread list marks such threads with 'waiting for input'. - bb thread wait --until-input (SDK: untilInput) returns the pending interaction, and exits with the unreachable code when the thread went idle or errored first. - bb thread log prints question options and the answer/grant/approve command under pending question and approval rows. - The 409 awaiting_user_interaction message names the CLI commands that resolve or discard the pending interaction. - The bb guide and bb-cli skill document bb thread interactions. Fixes #1655 Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
🚨 SLOP COP 🚨 · I am the Slop Cop. I am reviewing this pull request for security, code quality, performance, architecture, duplication, and practical behavior. |
| return `Inspect it with 'bb thread interactions show ${interaction.id} ${interaction.threadId}'; plugin interactions can only be answered in the app.`; | ||
| } | ||
| const action = verb === "answer" ? "Answer" : verb === "grant" ? "Grant" : "Approve or deny"; | ||
| return `${action} it with 'bb thread interactions ${verb} ${interaction.id} ${interaction.threadId}'.`; |
There was a problem hiding this comment.
🚨 slopcop/review — The generated grant command gives session access by default.
The hint omits --scope. parsePermissionGrantScope(undefined) selects session. A copied command can grant more access than one request needs.
Add --scope turn to generated hints. Another safe option is to require an explicit scope.
There was a problem hiding this comment.
Confirmed and fixed in c4ccb7f: the CLI hint and the timeline hint now append --scope turn.
| while (true) { | ||
| if (target.kind === "status") { | ||
| if (target.kind !== "event") { | ||
| const thread = await getThread({ |
There was a problem hiding this comment.
🚨 slopcop/review — One input wait can send about 9,600 HTTP requests.
This loop sends two requests every 250 milliseconds. The default timeout is 20 minutes.
Add a server wait endpoint that uses interaction-change notifications. As a minimum, add backoff and avoid two requests each cycle.
There was a problem hiding this comment.
Confirmed in part and fixed in c4ccb7f: the wait now polls the interaction list first and checks the thread status only every eighth cycle (about every 2s at the default interval), so a default wait sends about half the requests of before. A server-side wait endpoint for interaction changes is a larger change; I left it out of this PR.
| : []; | ||
| } | ||
| return row.lifecycle === "waiting" | ||
| ? [formatResolveHint("Approve", row, context)] |
There was a problem hiding this comment.
🚨 slopcop/review — The file-edit approval command uses the wrong identifier.
The file-edit row stores message.callId as interactionId. The resolve route requires a pint_* pending-interaction identifier.
Carry the actual interaction identifier through the timeline contract. Add a test where the call and interaction identifiers differ.
There was a problem hiding this comment.
Confirmed and fixed in c4ccb7f: file-edit rows now print Approve or deny with: bb thread interactions list <thread>, which shows the real interaction id, instead of a command built from the tool call id.
| ): string { | ||
| const verb = resolveInteractionVerb(interaction); | ||
| if (verb === null) { | ||
| return `Inspect it with 'bb thread interactions show ${interaction.id} ${interaction.threadId}'; plugin interactions can only be answered in the app.`; |
There was a problem hiding this comment.
🚨 slopcop/review — The plugin hint points to a command that throws.
The hint recommends interactions show. That command treats each non-question payload as an approval, so it rejects plugin payloads.
Make show print plugin data. Update recovery text to name the app or a supported cancel command.
There was a problem hiding this comment.
Confirmed and fixed in c4ccb7f: bb thread interactions show now prints plugin interactions (plugin id, title, data) and says to answer them in the app; a test covers it.
| interaction, | ||
| surface: "cli", | ||
| }); | ||
| console.log( |
There was a problem hiding this comment.
🚨 slopcop/review — The new output permits terminal control characters.
This output writes provider and plugin text without control cleanup. An ESC or OSC string can change the terminal display or clipboard.
Remove C0, C1, ESC, and terminal control sequences from human output. Keep the original data only in JSON output.
There was a problem hiding this comment.
Confirmed and fixed in c4ccb7f: the new bb thread show block strips C0/C1 controls and ESC/CSI/OSC sequences from provider and plugin text; --json keeps the raw data. A test feeds an OSC 52 clipboard sequence through a question prompt.
There was a problem hiding this comment.
🚨 SLOP COP 🚨 · review
Plain English summary: This PR helps CLI users see when an agent needs input. It also adds a wait command for automation.
I found five issues.
- High: Generated permission hints omit
--scope. The copied command grants session access, not access for one turn. - High: New human output permits terminal control characters from provider or plugin text. Such text can change the display or clipboard.
- Medium: One default
--until-inputwait can send about 9,600 HTTP requests. The SDK polls two endpoints every 250 milliseconds. - Medium: File-edit timeline hints use an item call identifier. The approval route requires a pending-interaction identifier, so the command fails.
- Medium: Plugin hints recommend
interactions show. That command rejects plugin payloads because it expects an approval payload.
The architecture scan found duplicate hint logic in the CLI and thread-view packages. A shared formatter would reduce the current scope and identifier differences.
Validation completed:
- The CLI passed 455 tests.
- The SDK passed 92 tests.
- The thread-view package passed 369 tests.
- The focused server interaction file passed 25 tests.
- Six affected packages passed Turbo type checks.
- The live source server returned a real plugin question through
thread wait --until-input. thread showandthread listdisplayedwaiting for inputduring that live test.- GitHub CI passed all required checks.
The complete local server suite had one unrelated timeout. The focused changed server test passed after that result.
I used a comment review only. I did not approve the pull request or request changes.
- Grant hints add --scope turn so a copied command stays narrow. - bb thread show strips terminal control sequences from provider text. - Interaction waits poll the thread status only every eighth cycle. - File-edit approval rows point at 'interactions list' because they carry the tool call id, not the pending-interaction id. - bb thread interactions show prints plugin interactions instead of throwing. Co-Authored-By: Claude <noreply@anthropic.com>
Fixes #1655
Problem
When a provider agent raises a question, approval, or permission request, the thread stays
activebut makes no progress.bb thread tellandbb thread queue sendreturn HTTP 409awaiting_user_interaction, and no CLI surface shows the pending card, so a coordinator sees a silent stall.bb thread interactions list|show|answer|approve|grant|denyalready resolves interactions, but the guide and skill did not document it, the 409 message did not name it, andbb thread shownever mentioned the pending card.Changes
bb thread show: the status line readsactive (waiting for input)and aPending interactions (N):section lists each interaction with its id, kind, summary, question options, and the exact resolve command.--jsonincludespendingInteractions.bb thread list: threads withhasPendingInteraction: trueshowwaiting for inputin the status column.bb thread wait --until-input(SDKthreads.wait({ untilInput: true })): returns the pending interaction with exit code 0, and exits with the unreachable code (4) when the thread is no longer active, because an idle or errored thread cannot ask for input.bb thread log: pending question rows print the option values andAnswer with: bb thread interactions answer <id> <thread>; pending permission and file-edit approval rows print the matchinggrant/approvecommand.thread-send.tsnow namesbb thread interactions list <id>andbb thread stop <id>. The send route queueing behavior is unchanged (owned by Messages to a thread blocked on AskUserQuestion are dropped, and only the sender is told #1650).bb guide threadsand the bb-cli skill document theInteractionscommands and the coordinator recipe.@bb/templatesand the plugin-sdk bundled types.Test plan
@bb/sdk:untilInputpolls thread + interactions and returns the pending one; throwsThreadWaitUnreachableErroron an idle thread.@bb/cli:thread showpending block and JSON field;thread listflag;thread wait --until-inputtext, JSON, and flag conflicts.@bb/thread-view: snapshot for pending question and permission hints.@bb/server: 409 message tests updated.pnpm exec turbo run typecheck test --filter=@bb/cli --filter=@bb/sdk --filter=@bb/thread-view --filter=@bb/templates --filter=@bb/server(one unrelated umask-dependent server test,internal-skill-trees, fails locally on this machine).