Commit Graph
6 Commits
Author SHA1 Message Date
dibanez 0389af6cb3 refactor(btw): read the promoted flag from the panel state, not a second subscription
`useBtwPanelState` already subscribes to the composer's own session — it is
what the fork link is derived from — so asking `useSession` for the same
session and directory a second time in `ChatInput` was a duplicate
subscription for a value already in hand. Expose it as `parentSession` and
read the promoted flag from there.

No behavior change.
2026-08-27 14:04:12 +02:00
dibanez 7a95efc4ac 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.
2026-08-27 13:54:29 +02:00
dibanez b0083f6e0f feat(btw): fork at the last completed turn, and never leak the inherited tail
Two related fixes to what a btw session inherits and shows.

**Fork point.** `/btw` is typically typed *while* the main thread is
working — that is the moment a side question comes up. Forking at HEAD
then clones a turn that is still streaming, so the fork inherits a
truncated assistant message and the user instruction that provoked it as
the newest, most salient thing in its context. Fork at the parent's last
completed assistant turn instead, read from the sync store with no extra
round-trip. A parent with no completed turn yet keeps the previous
fork-at-HEAD behavior.

**Boundary.** `filterBtwTailMessages` shows every inherited message when
the boundary is `null`, and the boundary is `null` whenever the
newest-cloned read comes back empty. That is correct for a fork of an
empty parent, but it also turns an empty read into "nothing was
inherited" for a fork that demonstrably did inherit history — the panel
then opens with the parent's whole transcript in it. Having picked a
fork point proves the parent had turns, so fall back to that id instead
of `null`. The fork's own messages are created later and still sort
after it, so its tail stays complete either way.
2026-08-27 09:37:43 +02:00
dibanez 92c3e0d5e5 feat(btw): keep the inherited thread as reference, not an active plan
`/btw` forks the session, so the model receives the parent's whole
conversation — including whatever plan was in flight when the user typed
the command. Nothing tells it that this history is context rather than
its own task, so the fork frequently carries on with the parent's work
instead of answering the side question, which is the opposite of what
`/btw` is for.

Send a boundary instruction as a synthetic part with every message in a
btw session: with the first question in `startBtwSession`, and with each
later send while the panel is expanded and the composer is talking to the
fork.

The wording is deliberately position-independent — it names the history
inherited from the parent thread rather than "everything before this
boundary". The instruction rides along with each send instead of being
pinned once at fork time, so a positional phrasing would be re-anchored
every turn and would end up telling the model to disregard the btw
session's own earlier turns.

The part is synthetic, so it is filtered out of the rendered transcript
whenever the message also carries user text — which is always the case
here. No visual change.
2026-08-27 09:21:42 +02:00
dibanez 450d30b3ee docs(changelog): credit the context meter fix contributor
Requested by review: the changelog-authoring skill requires inline
contributor credit for non-owner contributors.
2026-08-15 21:47:02 +02:00
dibanez 9e1a9b59b1 fix(ui): stop the context meter from counting every internal round-trip
The token breakdown of an assistant message accumulates across every API
round-trip inside the turn: each tool call re-reads the whole cached
prompt, so input/cache.read add up to several times the context window.
Every context-usage surface summed those fields, which is why the meter
could read 330% of a 1M window whose real fill was 232,872 tokens
(23.3%), and why reopening an older session jumps the readout (#2562).

The server reports the final round-trip's window as tokens.total
(optional in the message schema; opencode 1.18.18 returns it, verified
against its live /session/:id/message API). Prefer it everywhere the
window fill is displayed and fall back to summing only when the server
did not send it: contextTokensFromBreakdown in tokenUtils now owns that
rule, and the context store extractor, sync store getter, work status
panel, context sidebar, VS Code layout, mini chat, and mobile metadata
all use it instead of their own inline sums.

Fixes #2562
2026-08-15 21:46:49 +02:00