From 017a7bbaab3cb63a242969d26f38eade12a26680 Mon Sep 17 00:00:00 2001 From: matthewdias Date: Sun, 13 Sep 2026 14:33:40 -0500 Subject: [PATCH 1/2] Record thread-badges at the version it was released as The v0.2.0 release bumped its package.json without regenerating the lockfile, so every install rewrote the workspace entry. Co-Authored-By: Claude Opus 5 (1M context) --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 617ffb8..ba8a2d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2715,7 +2715,7 @@ }, "plugins/thread-badges": { "name": "bb-plugin-thread-badges", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "dependencies": { "@hugeicons/core-free-icons": "^4.1.3", From 7fe247184f1b3580e1b3baa0e87893727ec3296b Mon Sep 17 00:00:00 2001 From: matthewdias Date: Sun, 13 Sep 2026 14:33:40 -0500 Subject: [PATCH 2/2] File a thread under Planning when a message asks for a plan Filing is the agent's job: it reads the stage table and runs `bb stages set`. Plan mode is the one moment it cannot, because that is a mutating shell call, so a thread started with /plan sat in Inbox through its whole planning session and was only filed once the plan was approved, by which point the honest stage is Building. No event or thread field says a turn is a plan. The message does: bb's plan action is a provider-declared slash command, carried on the send as a structured command mention. So a message.dispatch hook watches for that mention opening the message and files the thread under planStageId. It always proceeds, decides synchronously and moves off the pass, because the hook is fail-closed and holds a server-wide lock. Command names come from each provider's composer actions on a background refresh, falling back to "plan". Matching the mention rather than the text matters: over 400 recorded prompts it caught all four plan sends with no false positives, where a text match on "/plan" flagged 43 messages that only discussed it. This trigger ships on, unlike the other three. They guess a stage from a thread's runtime; asking for a plan is the user saying what comes next. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/workflow-stages/PLUGIN_OVERVIEW.md | 7 ++ plugins/workflow-stages/README.md | 14 +++ plugins/workflow-stages/app.tsx | 17 +++- plugins/workflow-stages/server.ts | 99 ++++++++++++++++++- .../skills/workflow-stages/SKILL.md | 6 ++ plugins/workflow-stages/stages.ts | 10 ++ 6 files changed, 149 insertions(+), 4 deletions(-) diff --git a/plugins/workflow-stages/PLUGIN_OVERVIEW.md b/plugins/workflow-stages/PLUGIN_OVERVIEW.md index 994bb07..8215f4a 100644 --- a/plugins/workflow-stages/PLUGIN_OVERVIEW.md +++ b/plugins/workflow-stages/PLUGIN_OVERVIEW.md @@ -25,6 +25,13 @@ turn needs you. Every automatic move skips sticky stages, and "work stops" only undoes the move "a turn starts" made, so a thread you filed by hand while it was running stays filed. +One automatic move ships on: a message that asks for a plan files its thread +under Planning. Filing is otherwise the agent's job, and plan mode is the one +moment an agent may not do it — `bb stages set` is a mutating call, and plan +mode forbids those — so without this a `/plan` thread sits unfiled for the whole +planning session. It reads the composer's own command mention rather than the +message text, so a thread that merely discusses `/plan` is not filed. + ## How it works Ribbon owns the sidebar — rendering, drag-and-drop, manual order, and the stored diff --git a/plugins/workflow-stages/README.md b/plugins/workflow-stages/README.md index eb0e05a..ab5083f 100644 --- a/plugins/workflow-stages/README.md +++ b/plugins/workflow-stages/README.md @@ -70,6 +70,7 @@ Configured in the same editor: | Trigger | Default | | --- | --- | | A thread with no placement | Inbox | +| A message asks for a plan | Planning | | A turn starts | off | | Work stops | off | | A question, an approval, or a failed turn | off | @@ -77,6 +78,19 @@ Configured in the same editor: "Work stops" only undoes the move "a turn starts" made, so a thread you filed by hand while it was running stays filed. Every automatic move skips sticky stages. +"A message asks for a plan" is the one that ships **on**, because it is the one +trigger the agent cannot answer for itself. Filing is otherwise the agent's job: +it reads the stage table below and runs `bb stages set`. In plan mode it may not +— that is a mutating shell call, and plan mode is the one moment an agent is not +allowed to make those — so a thread you started with `/plan` would sit in Inbox +through the whole planning session and only get filed once the plan was +approved, by which point the honest stage is Building. + +Detection is the composer's, not a string match: bb's plan action is a +provider-declared slash command, and the send carries it as a structured mention +rather than as text. So a message that merely *talks* about `/plan` is not one, +and a provider that calls its plan action something else is still recognised. + The last one ships off on purpose. A stage says where the work is; whether it needs you is a different axis, and Ribbon already draws that on the row itself. Pointing it at a stage means a thread bounces there every time an agent asks a diff --git a/plugins/workflow-stages/app.tsx b/plugins/workflow-stages/app.tsx index 087aaf8..fd94b0b 100644 --- a/plugins/workflow-stages/app.tsx +++ b/plugins/workflow-stages/app.tsx @@ -447,6 +447,15 @@ function WorkflowSettings() { {options(false)} + + +