Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ loosely while pre-1.0 (breaking changes can land on minor bumps).
## [Unreleased]

### Fixed
- **TUI replay QUERY boundary.** `replay_user_echo_text` now strips the
first `\n\nQUERY: ` after the AGENTS roster (not the last). User text
that embeds that marker no longer replays as only the suffix after it.
- **Bearer scheme matches RFC 6750 / RFC 9110.** `extract_bearer` now
treats the auth-scheme as case-insensitive and skips `1*SP` after it,
so `bearer` / `BEARER` and extra spaces no longer 401 a valid token.
Expand Down
6 changes: 5 additions & 1 deletion src/repl/transcript_replay_predicates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ std::string_view replay_user_echo_text(const Message& m) {
// AGENTS …\n\nQUERY: <user text>
// for the master ("index") agent. Live UI echoes only <user text>.
static constexpr std::string_view kQueryMark = "\n\nQUERY: ";
const auto q = content.rfind(kQueryMark);
// Orchestrator::send_streaming / send_internal emit exactly one
// "\n\nQUERY: " after global_status() (always starts with AGENTS).
// find — not rfind — so user text that itself contains that marker
// is not truncated to the suffix after the last occurrence.
const auto q = content.find(kQueryMark);
if (q == std::string_view::npos) return content;
const std::string_view head = content.substr(0, q);
// Require an AGENTS roster line in the preamble so a user who literally
Expand Down
21 changes: 21 additions & 0 deletions tests/test_transcript_replay_predicates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,27 @@ TEST_CASE("replay_user_echo_text strips master AGENTS/QUERY preamble") {
// QUERY marker without an AGENTS roster is not stripped.
m.content = "see\n\nQUERY: docs";
CHECK(replay_user_echo_text(m) == "see\n\nQUERY: docs");

// User text after the orchestrator marker may embed the same
// substring. The first QUERY (after AGENTS) is the boundary;
// rfind would echo only the suffix.
m.content =
"AGENTS — delegate with /agent <id> <task>:\n"
" research [research-analyst]\n"
"\n"
"QUERY: compare\n\nQUERY: docs vs the README";
CHECK(replay_user_echo_text(m) == "compare\n\nQUERY: docs vs the README");

m.content =
"[OPEN TODOS] (mark progress as you go):\n"
"1. ship it\n"
"[END OPEN TODOS]\n"
"\n"
"AGENTS: none loaded\n"
"\n"
"QUERY: \n"
"see\n\nQUERY: docs";
CHECK(replay_user_echo_text(m) == "see\n\nQUERY: docs");
}

TEST_CASE("claim_pane_transcript_replay dedupes shared conversation siblings") {
Expand Down
Loading