Skip to content

feat(btw): add branch-local history and continuity - #880

Open
MoerAI wants to merge 18 commits into
code-yeongyu:mainfrom
MoerAI:feat/btw-history-final
Open

feat(btw): add branch-local history and continuity#880
MoerAI wants to merge 18 commits into
code-yeongyu:mainfrom
MoerAI:feat/btw-history-final

Conversation

@MoerAI

@MoerAI MoerAI commented Aug 14, 2026

Copy link
Copy Markdown

Summary

/btw side questions now keep branch-local history without adding those exchanges to the main model conversation. Completed questions and answers survive session reloads, a bare /btw opens a keyboard-driven history viewer, and new side questions can refer to the newest ten earlier side exchanges.

The change is extension-local and preserves the current model-aware side-query context reducer: main history, prior side answers, and the current question are budgeted together before provider dispatch.

This replaces #746 with a clean branch based directly on current main; the previous branch had absorbed an old main lineage and was no longer a reviewable feature diff.

Motivation

Side questions are useful precisely because they do not derail the main task. Before this change, they were ephemeral: users could not review an earlier answer, and a follow-up side question had no memory of the previous side exchange.

Persisting custom entries at the builtin extension boundary provides continuity and review while keeping the main LLM transcript untouched. Reading history from sessionManager.getBranch() prevents exchanges from sibling branches from leaking into either the viewer or follow-up context.

Behavior

  • /btw <question> stores the completed question and answer as a btw-history custom entry.
  • Only the newest ten branch-local pairs are supplied to a follow-up side query.
  • Prior side questions retain user role and prior model answers retain assistant role in follow-up provider context.
  • Model-aware pruning drops incomplete leading turns, so a prior answer is never retained after its question is removed.
  • Bare /btw opens the history overlay in TUI mode and emits the same list through notify in non-TUI modes without calling a provider.
  • Left/Right selects an entry, Up/Down scrolls its answer, and Escape closes the overlay.
  • Side answers are requested in the language of the current side question.
  • Questions, streamed answers, errors, and persisted history strip CSI, OSC, and non-printing control sequences at every display sink; stored text and follow-up continuity remain unchanged.
  • History navigation resolves through configured TUI keybindings while retaining the default Left/Right, Up/Down, and Escape behavior.
  • Session-tree navigation aborts an in-flight side query before changing leaves, preventing answers from persisting onto the wrong branch.
  • tools: [], isolated provider session IDs, concurrent main-turn behavior, and current context-window budgeting remain unchanged.

Test evidence

Passed locally on Windows on current PR head d73a092b7:

  • Focused Vitest: 6 files, 42/42 tests, including the runtime-provider regression, prior-answer role provenance, atomic budget pruning, live/replay/error terminal-control coverage, remapped overlay keys, one-line configured footer labels, and tree-navigation abort.
  • npm run check: passed (Biome, TypeScript, lock audits, browser smoke).
  • npm run build: passed across all workspace phases.
  • Real source RPC /btw scenario with a localhost model: 20/20, including prior question=user and answer=assistant wire roles, sanitized live output, persisted-history replay, provider-error notification, and unchanged provider continuity.
  • Standalone production bounder drive: exact token boundary retained only the current user question, with no orphaned prior assistant prefix.
  • Real source ConPTY scenario: 15/15, including Korean rendering, live/replay OSC suppression, inert one-line rendering of an OSC/newline-bearing configured shortcut, bare history overlay, Left/Right, Up/Down, Escape, and no viewer provider request.
  • Senpi QA harness: common 9/9, mock loop 48/48, CLI 8/8, TUI smoke 5/5 on the pre-rebase feature commit; the feature-specific gates are rerun after every behavior change.
  • Real auth SHA remained unchanged; all model traffic went to 127.0.0.1.

QA receipt: local-ignore/qa-evidence/20260813-btw-history/.

The package-wide suite remains red on this Windows checkout for unrelated pre-existing platform assumptions (Unix sockets and chmod, POSIX path separators, shell/native publish fixtures, app-server timing). The changed /btw suite passes in isolation and no full-suite failure names a changed /btw path.

MacBook verification

The implementation uses the shared pi-tui key/width helpers and standard Node session APIs, with no Windows-only product path. macOS execution is not claimed as completed in this PR. To verify from this branch on a MacBook:

node scripts/devenv-setup.mjs
npm run check
cd packages/coding-agent
npm test -- test/suite/btw-history.test.ts test/suite/btw-history-view-model.test.ts test/suite/btw-history-layout.test.ts test/suite/btw-panel.test.ts test/suite/btw-side-query.test.ts test/suite/regressions/488-btw-runtime-provider.test.ts

Then run Senpi from source and verify in Terminal.app plus Ghostty or another Kitty-protocol terminal:

  1. Ask two Korean or CJK side questions with /btw <question>.
  2. Ask a follow-up that depends on the first answer.
  3. Run bare /btw and use Left/Right, Up/Down, and Escape.
  4. Confirm CJK columns do not drift and opening history sends no new provider request.
  5. Fork before a side question and confirm sibling-branch history does not appear after switching branches.

Risks and residuals

  • Persistence uses custom session entries and does not migrate or rewrite existing sessions.
  • History is deliberately branch-local and bounded to ten pairs for model continuity; the viewer shows all valid pairs on the active branch.
  • The overlay uses shared TUI key decoding for navigation. The existing streaming /btw widget still uses its documented raw-Escape behavior and is not changed here.
  • A concise entry was added only under packages/coding-agent/CHANGELOG.md ## [Unreleased]; the local changelog gate passes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6649ed0401

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import { formatBtwQuestion, sanitizeBtwDisplayText } from "./display-text.ts";
import { type BtwHistoryViewEntry, BtwHistoryViewModel } from "./history-view-model.ts";

const FOOTER_HINT = "left/right: question up/down: scroll esc: close";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive footer hints from configured keybindings

When any navigation or cancel binding is remapped in keybindings.json, KeybindingsManager replaces its default keys, but this footer still advertises Left/Right, Up/Down, and Escape. The overlay therefore tells users to press keys that no longer work; render the hints from the provided keybinding manager rather than a hardcoded constant.

AGENTS.md reference: AGENTS.md:L99-L99

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c54a3e1. The footer now derives left/right, up/down, and cancel labels from the injected KeybindingsManager via the shared formatKeyText helper. Added a RED->GREEN regression for remapped ctrl+h/l/k/j/x labels. Verification: focused /btw suite 39/39, npm run check, full build, targeted regression PASS, and real ConPTY 15/15 including the effective-key footer.

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

CI and the changelog gate were created but are waiting for maintainer approval before any jobs can start. Please approve these current-head fork workflow runs when convenient:

The published head is d73a092b7. Local gates: focused Vitest 42/42, npm run check, full build, RPC 20/20, standalone exact-boundary production drive, and malicious-config ConPTY 15/15. Prior question/answer roles remain user/assistant, incomplete pruned turns are removed, tools stay empty, and the current question remains last. GitGuardian passed; Cubic completed neutral. All Codex findings have fixes and regression evidence on this head.

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review\n\nPlease re-review current head c54a3e1; the configured-key footer finding from the prior review is fixed and covered by regression + ConPTY evidence.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: c54a3e1772

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review\n\nPlease review current head 5002a07. This follow-up sanitizes configured footer key labels and adds a JSON-boundary OSC 52 regression; focused suite is 40/40 and malicious-config ConPTY is 15/15.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5002a07295

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


function formatFooterHint(keybindings: KeybindingsManager): string {
const keys = (binding: Keybinding): string => formatKeyText(keybindings.getKeys(binding).join("/"));
return sanitizeBtwDisplayText(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Collapse line breaks in the footer hint

When a configured key label contains a JSON-escaped \n or \r (for example, "ctrl+x\nFORGED"), sanitizeBtwDisplayText() preserves the newline and this footer returns it inside a single Component.render() row. The TUI compositor treats each array element as one physical row, so the embedded newline is written directly to the terminal, allowing a malformed or malicious keybindings.json to inject an extra row and desynchronize differential rendering. Use a one-line sanitizer here that removes or collapses CR/LF as well as terminal escape sequences.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f320b69. The configured footer now uses the existing one-line display formatter after terminal-control sanitization, collapsing CR/LF before the component row reaches the TUI compositor. The JSON-boundary regression now combines OSC 52, BEL, and a newline-bearing FORGED label and asserts the footer element contains no CR/LF. Verification: focused suite 40/40, npm run check, full build, and real malicious-config ConPTY 15/15 with auth unchanged.

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Please re-review current head f320b69. The newline footer-row finding is fixed at the JSON display boundary and covered by RED-to-GREEN unit proof plus malicious-config ConPTY 15/15.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f320b69416

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +42 to +43
role: "user",
content: `Earlier side question: ${entry.question}\nYour earlier answer: ${entry.answer}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve assistant provenance in history context

When an earlier side-query answer contains imperative text or a prompt-injection payload, the next /btw request sends that model-generated answer with role: "user", promoting it to user-authority instead of preserving it as an assistant reply. This can incorrectly steer subsequent side answers, especially after switching models or providers; represent each stored question and answer with their original user/assistant roles rather than combining both into a user message.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a6e13e2. Each stored side question is now replayed as user, followed by its model-generated answer as assistant; selected-model metadata supplies the provider-neutral assistant message envelope. A RED-to-GREEN extension regression inspects the real faux-provider call log with an imperative prior answer, and the localhost RPC wire driver confirms the prior answer remains assistant. Verification: focused 41/41, npm run check, full build, RPC 20/20, tools empty, current question last, auth unchanged.

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Please re-review current head a6e13e2. The prior-answer authority finding is fixed with original user/assistant roles, RED-to-GREEN provider-call coverage, and localhost RPC wire proof (20/20).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6e13e28a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +59 to +62
{ role: "user", content: `Earlier side question: ${entry.question}`, timestamp: entry.timestamp },
{
role: "assistant",
content: [{ type: "text", text: `Your earlier answer: ${entry.answer}` }],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prune prior side turns as complete pairs

When context budgeting is triggered and removing an earlier side-question message is enough to fit, pruneOldMessagesToBudget() removes that user message independently and stops, leaving its assistant reply in the provider context without the originating question. Fresh evidence at the current head is this new two-message representation combined with the existing per-message pruning in side-query.ts; the large-context path therefore no longer preserves the turn provenance this fix establishes. Keep each stored question/answer pair atomic during pruning, or remove orphaned side-history messages afterward.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d73a092. After generic model-aware pruning and tool-pair repair, the /btw bounder now drops any incomplete prefix before the first surviving user turn, so a prior assistant answer cannot remain after its question is removed. The RED test calculates the exact token boundary that previously produced [assistant prior answer, user current question]; GREEN yields only the current user question. Verification: focused 42/42, npm run check, full build, standalone production bounder PASS, RPC 20/20.

@MoerAI

MoerAI commented Aug 14, 2026

Copy link
Copy Markdown
Author

@codex review

Please re-review current head d73a092. The pair-pruning finding is fixed with an exact calculated-boundary RED-to-GREEN regression and standalone production-bounder proof; focused suite is 42/42.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: d73a092b7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant