fix(btw): stop the boundary from outliving the session it applies to

Review catch: `promoteBtwSession` removes the btw metadata, but the
boundary instruction is persisted on every message the session sent while
it was a side conversation, and there is no API to delete a message part
after the fact. A promoted session therefore keeps reading "no sub-agents,
do not touch the workspace" out of its own history, in a session that is
no longer a side conversation.

Promotion cannot delete those lines, so it answers them instead:
`withoutBtwSessionMarker` now leaves `openchamber.btwPromoted`, and the
composer sends `BTW_PROMOTION_NOTICE` with every message in a session
carrying it — stating that the session is now the main thread and the
usual tool, sub-agent and workspace permissions are in force.

It rides with every send for the same reason the boundary does: the
instructions it revokes are re-read on every turn, so a one-shot notice
would lose its position relative to them as the conversation grows.

`btwPromoted` is additive and optional. A session that never went through
`/btw` never has it, and one promoted before this change simply keeps the
old behavior.
This commit is contained in:
dibanez
2026-08-27 13:54:29 +02:00
parent b0083f6e0f
commit 7a95efc4ac
5 changed files with 69 additions and 14 deletions
+19
View File
@@ -55,6 +55,25 @@ export const BTW_BOUNDARY_INSTRUCTION = [
'Do not modify files, source, git state, permissions, configuration, or any other workspace state unless the user explicitly asks for that mutation inside this btw session. If they do, keep it minimal, local to the request, and avoid disrupting the main thread.',
].join('\n');
/**
* Sent with every message in a session that was promoted out of `/btw`.
*
* `BTW_BOUNDARY_INSTRUCTION` is persisted on each message the session sent
* while it was a side conversation, and there is no API to remove a message
* part after the fact — so promotion cannot delete those lines, only answer
* them. Without this, a promoted session keeps reading "no sub-agents, do not
* touch the workspace" out of its own history, in a session that is no longer
* a side conversation.
*
* It rides along with every send for the same reason the boundary does: the
* instructions it revokes are re-read on every turn, so a one-shot notice
* would lose its position relative to them as the conversation grows.
*/
export const BTW_PROMOTION_NOTICE =
'This session started as a btw side conversation and has since been promoted to a normal session. '
+ 'The btw constraints in the history above no longer apply: this is now the main thread, and the '
+ 'usual tool, sub-agent and workspace permissions are in force.';
/** The boundary as an `additionalParts` entry for `sendMessage`. */
const btwBoundaryParts = (): Array<{ text: string; synthetic: true }> =>
[{ text: BTW_BOUNDARY_INSTRUCTION, synthetic: true }];