* fix(native-chat): enforce each pending send's own boundary in glue matching
Glue matching filtered candidate rows against the OLDEST still-open send and
then matched the entire open queue against them. A prompt queued after a glued
row landed could therefore be judged "already delivered" by that older row and
pruned — the queued prompt disappeared with no bubble and no transcript turn.
Each send now carries its own transcript boundary into the match:
`gluedCandidateRows` tags every candidate row with the set of pending indices
it actually landed after, and the matcher stops a run at the first send the row
predates rather than skipping over it (adjacency is what makes a row glue).
Exact single matches still belong to the occurrence path, unchanged.
`native-chat-pending.ts` sat at 299 of its 300 effective-line budget, so the
slash-command marker cache — a separate rule that never took part in pending
pruning — moves verbatim to `native-chat-command-marker.ts`. Pure move: no
behavior change, imports only. (max-lines is never bumped or disabled.)
Refs STA-4477. Original PR #14663.
* test(native-chat): cover the glue adjacency break and unmask the render path
The `break` on a send the row cannot represent is the fix's central semantic
choice, and swapping it for `continue` was passing the whole suite: nothing
exercised a queue whose middle send is unrepresentable. Add that case.
The mixed-age case also asserted both call sites in one `it`, so a prune-path
failure masked the render-path assertion — and the render path is the one that
makes a queued bubble visually vanish. Split it.
Skip the per-send boundary scans when fewer than two sends are open: the glue
matcher already returns nothing there, so a lone queued echo was walking the
transcript twice per render for a discarded result.
* fix(native-chat): migrate the live-session benchmark off the renamed glue exports
Renaming the glue matcher's exports left this caller behind, and it crashed at
runtime after printing six result rows:
TypeError: matchingNativeChatUserTexts is not a function
No gate caught it. config/scripts/** is in no tsconfig include and the file is
not a *.test.ts, so neither typecheck nor vitest ever loads it.
The empty-pending arm passes no pending sends, so the matcher takes its
empty-queue exit without ever reading the rows — which is also why the renderer
skips candidate-row construction entirely in that case. Escaping the row scan
directly keeps what this arm actually measures identical to before, rather than
fabricating per-row boundary sets that no production path builds.