mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
fix(terminal): strip opencode's mouse DECSETs so a drag selects text again
opencode's TUI enables mouse tracking. tmux runs with `mouse off`, so it passes the PANE's DECSETs straight through to the tmux client, and the browser's xterm obeyed them: `mouseTrackingMode` flipped to 'any' (measured 62 none / 18 any over 16s) and xterm then reported DRAGS to the TUI instead of selecting locally. In that state marking text produced no selection at all, so copy-on-select silently did nothing (5/5 dead drags while `any`), and the obvious fallback — Ctrl+C — is opencode's `app_exit`, which ended the session. Both were hit here. opencode needs the middle strip: alt-screen toggles AND mouse DECSETs, but NOT `3J` (a TUI is not a `clear` consumer). That is `altScreen: 'strip-mux-and-mouse'` + `isMuxMouseStripMode`, applied to the live stream (session.ts) and the replay of a stored buffer, now the exported `stripReplayBuffer()` (session-routes.ts). The browser's mouse-report gate keeps no mode list any more: `_shouldReportMouseToCli()` reads only `cliMouseTracking`. The server sets that flag solely in the mouse-strip branch (`_recordStrippedMouseMode`, one caller), so it can only be true for a mode whose DECSETs are stripped, and whichever modes the registry strips, the browser follows. Clicks still reach opencode through the hand-encoded SGR tap it gates. The `altScreen` JSDoc gets the decision table its three independent choices need (alt-screen / `3J` / mouse DECSETs), written from the predicates, including that `preserve` and `strip-mux-only` take the same runtime row. The table is pinned for every stock CLI, with and without tmux, on both the live strip and the replay strip, plus the published flag (test/claude-scrollback-strip.test.ts), so the two halves cannot drift and a mis-ordered replay branch fails. Docs and comments that still said opencode keeps its mouse reporting or gets the narrow strip are updated (CLAUDE.md, architecture-invariants, scrollback and copy-shortcut plans, session.ts, terminal-ui.js). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5a0018fc86
commit
5ba729fcbb
@@ -289,7 +289,7 @@ const capabilitiesSchema = z
|
||||
requiresMux: z.boolean(),
|
||||
hooks: z.enum(['none', 'always', 'supervised']),
|
||||
transcript: z.enum(['claude-jsonl', 'codex-rollout', 'deepseek-zstd', 'omp-jsonl', 'none']),
|
||||
altScreen: z.enum(['strip-full', 'strip-mux-only', 'preserve']),
|
||||
altScreen: z.enum(['strip-full', 'strip-mux-only', 'strip-mux-and-mouse', 'preserve']),
|
||||
echo: echoSchema,
|
||||
wheelForward: z
|
||||
.object({ mode: z.enum(['never', 'version-gated']), minVersion: z.string().max(20).optional() })
|
||||
|
||||
@@ -491,7 +491,7 @@ const OPENCODE: CliEntry = {
|
||||
},
|
||||
capabilities: {
|
||||
...agentDefaults(),
|
||||
altScreen: 'strip-mux-only',
|
||||
altScreen: 'strip-mux-and-mouse',
|
||||
echo: { policy: 'buffer', anchor: { kind: 'cursor' }, predictProfile: undefined },
|
||||
// opencode's global config dir is xdg-basedir's `$XDG_CONFIG_HOME/opencode`.
|
||||
mcpConfig: {
|
||||
|
||||
@@ -436,11 +436,40 @@ export interface CliCapabilities {
|
||||
*/
|
||||
transcript: 'claude-jsonl' | 'codex-rollout' | 'deepseek-zstd' | 'omp-jsonl' | 'none';
|
||||
/**
|
||||
* 'strip-full' — alt-screen + erase-scrollback + mouse DECSETs stripped (Ink TUIs).
|
||||
* 'strip-mux-only' — only tmux's own attach-time smcup (the safe default).
|
||||
* 'preserve' — leave everything (a direct-PTY shell running vim/less/htop).
|
||||
* What the server strips from this CLI's output stream before the browser sees it.
|
||||
* The value encodes three independent choices (predicates in session.ts):
|
||||
*
|
||||
* | value | alt-screen toggles | `3J` (erase scrollback) | mouse DECSETs |
|
||||
* |-----------------------|---------------------|-------------------------|---------------------|
|
||||
* | `strip-full` | stripped | stripped | stripped |
|
||||
* | `strip-mux-and-mouse` | stripped under tmux | kept | stripped under tmux |
|
||||
* | `strip-mux-only` | stripped under tmux | kept | kept |
|
||||
* | `preserve` | stripped under tmux | kept | kept |
|
||||
*
|
||||
* `strip-full` is `isAltScreenStripMode`; `strip-mux-and-mouse` is `isMuxMouseStripMode`;
|
||||
* every other value takes `isMuxAltScreenOnlyStripMode`, so at runtime `preserve` and
|
||||
* `strip-mux-only` are the same row — `preserve` only says what such a CLI's pane
|
||||
* holds (terminal-owned scrollback: a shell, pi), not a different strip.
|
||||
*
|
||||
* - alt-screen: the tmux CLIENT emits `smcup` as its first bytes at attach, parking
|
||||
* xterm in the scrollback-less alternate buffer; a pane program's own toggles never
|
||||
* reach the client (tmux repaints instead). "Under tmux" means `useMux`: on a
|
||||
* direct-PTY fallback the `?1049h` is the program's own and must stay.
|
||||
* - `3J`: a user's `clear` is a deliberate scrollback wipe; only an Ink TUI's
|
||||
* redraw-driven `3J` (strip-full) is noise.
|
||||
* - mouse DECSETs: stripping them keeps a drag a local selection instead of a report
|
||||
* to the TUI. The browser then hand-encodes clicks (`_sendSyntheticSgrTap`), gated
|
||||
* on the `cliMouseTracking` the server records as it strips. Kept where a program's
|
||||
* own mouse support must work in the pane (htop/vim in a shell).
|
||||
*
|
||||
* Stock CLIs: `strip-full` = claude, codex, gemini (Ink TUIs); `strip-mux-and-mouse` =
|
||||
* opencode (a full-screen TUI that enables tracking itself); `strip-mux-only` =
|
||||
* antigravity, grok, deepseek, omp; `preserve` = shell, pi.
|
||||
*
|
||||
* A fourth combination is the point to split this into flags; three is still cheaper
|
||||
* as an enum.
|
||||
*/
|
||||
altScreen: 'strip-full' | 'strip-mux-only' | 'preserve';
|
||||
altScreen: 'strip-full' | 'strip-mux-only' | 'strip-mux-and-mouse' | 'preserve';
|
||||
echo: {
|
||||
policy: 'buffer' | 'predict' | 'off';
|
||||
/** How the local-echo overlay locates the composer row. */
|
||||
|
||||
+54
-16
@@ -265,10 +265,11 @@ function cliExportsTruecolor(mode: SessionMode): boolean {
|
||||
* Codex, Claude Code, and Gemini are known, controlled (Ink/React) TUIs that
|
||||
* repaint via cursor positioning, so dropping the alt-screen switch is safe —
|
||||
* content stays in the normal buffer. Excluded: `shell` (arbitrary programs like
|
||||
* vim/less/htop legitimately need the alt screen), `opencode` (renders its own
|
||||
* TUI that may rely on it), `pi` (below) and `grok` (a fullscreen alt-screen TUI
|
||||
* with mouse support, i.e. the opencode case, not the Ink case). Keep parity
|
||||
* with the replay-side strip in session-routes.ts.
|
||||
* vim/less/htop legitimately need the alt screen), `opencode` (its own MIDDLE strip,
|
||||
* isMuxMouseStripMode), `pi` (below) and `grok` (a fullscreen alt-screen TUI with
|
||||
* mouse support). Keep parity with the replay-side strip (`stripReplayBuffer` in
|
||||
* session-routes.ts); the table in `CliCapabilities.altScreen` is pinned for every
|
||||
* stock CLI in test/claude-scrollback-strip.test.ts.
|
||||
*
|
||||
* ⚠️ Being excluded here does NOT preserve the alt screen. Every excluded mode
|
||||
* falls through to isMuxAltScreenOnlyStripMode(), which strips the alt-screen
|
||||
@@ -288,8 +289,10 @@ export function isAltScreenStripMode(mode: SessionMode): boolean {
|
||||
|
||||
/**
|
||||
* Modes that need the NARROW strip: alt-screen toggles only, leaving `\x1b[3J`
|
||||
* and the mouse-tracking DECSETs alone. Applies to every mode `isAltScreenStripMode`
|
||||
* excludes, but ONLY when the session is tmux-backed (`useMux`).
|
||||
* and the mouse-tracking DECSETs alone. Applies to every mode that is neither
|
||||
* `strip-full` (isAltScreenStripMode) nor `strip-mux-and-mouse` (isMuxMouseStripMode),
|
||||
* so `strip-mux-only` and `preserve` alike, but ONLY when the session is tmux-backed
|
||||
* (`useMux`).
|
||||
*
|
||||
* The bug (issue #205): the tmux CLIENT emits `smcup` (`\x1b[?1049h`) as its first
|
||||
* bytes on attach, before any program has run. Unstripped, xterm.js parks in the
|
||||
@@ -314,7 +317,31 @@ export function isAltScreenStripMode(mode: SessionMode): boolean {
|
||||
* `\x1b[3J` from a user's own `clear` is a deliberate "wipe my scrollback".
|
||||
*/
|
||||
export function isMuxAltScreenOnlyStripMode(mode: SessionMode, useMux: boolean): boolean {
|
||||
return useMux && !isAltScreenStripMode(mode);
|
||||
if (!useMux) return false;
|
||||
const altScreen = getCli(mode)?.capabilities.altScreen;
|
||||
return altScreen !== 'strip-full' && altScreen !== 'strip-mux-and-mouse';
|
||||
}
|
||||
|
||||
/**
|
||||
* Modes whose mouse-tracking DECSETs must be stripped, leaving `3J` alone:
|
||||
* `altScreen: 'strip-mux-and-mouse'`, i.e. a mouse-capable full-screen TUI.
|
||||
*
|
||||
* Why this exists (opencode, measured 2026-09-16): the TUI enables tracking
|
||||
* DECSETs, tmux runs with `mouse off` and therefore passes the PANE's DECSETs
|
||||
* straight through to the tmux client, and the browser's xterm obeyed them —
|
||||
* `mouseTrackingMode` flipped to `'any'` and xterm then reported DRAGS to the TUI
|
||||
* instead of selecting locally. "Mark text, copy on select" silently did nothing
|
||||
* (measured 62 `none` / 18 `any` over 16s, and 5/5 dead drags while `any`), and
|
||||
* the obvious fallback — Ctrl+C — is opencode's `app_exit`, so the failure also
|
||||
* ended sessions. Stripping at the source keeps xterm in selection mode; clicks
|
||||
* still reach the CLI through the browser's hand-encoded tap, which this strip
|
||||
* publishes as `cliMouseTracking` (`_recordStrippedMouseMode`).
|
||||
*
|
||||
* Gated on `useMux` for the same reason as the narrow strip: on the direct-PTY
|
||||
* fallback the program's own DECSETs really do reach xterm and must be honoured.
|
||||
*/
|
||||
export function isMuxMouseStripMode(mode: SessionMode, useMux: boolean): boolean {
|
||||
return useMux && getCli(mode)?.capabilities.altScreen === 'strip-mux-and-mouse';
|
||||
}
|
||||
|
||||
// Note: Claude CLI PATH resolution moved to session-cli-builder.ts (buildClaudeEnv)
|
||||
@@ -2489,14 +2516,21 @@ export class Session extends EventEmitter {
|
||||
// redraws overwrite only the cells they target, so non-erased rows keep
|
||||
// their content. Gated to Codex/Claude/Gemini (isAltScreenStripMode).
|
||||
//
|
||||
// Every OTHER mode (shell/opencode/antigravity) gets the NARROW strip when it
|
||||
// is tmux-backed: alt-screen toggles only, because the sequence that breaks
|
||||
// Every OTHER mode (shell/antigravity/pi/grok/deepseek/omp) gets the NARROW strip
|
||||
// when it is tmux-backed: alt-screen toggles only, because the sequence that breaks
|
||||
// scrollback there is tmux's own client-side smcup at attach, not anything the
|
||||
// program in the pane emitted (issue #205, see isMuxAltScreenOnlyStripMode).
|
||||
// 3J and the mouse DECSETs stay, so `clear` and mouse-aware TUIs keep working.
|
||||
//
|
||||
// The MIDDLE case (isMuxMouseStripMode) is a mouse-capable full-screen TUI:
|
||||
// it needs smcup AND the mouse DECSETs gone — otherwise the pane's tracking
|
||||
// reaches xterm and every drag becomes a mouse report instead of a text
|
||||
// selection, which is what killed mark-and-copy in opencode — while 3J stays,
|
||||
// because a TUI is not a `clear` consumer.
|
||||
const fullStrip = isAltScreenStripMode(this.mode);
|
||||
const altOnlyStrip = !fullStrip && isMuxAltScreenOnlyStripMode(this.mode, this._useMux);
|
||||
if (fullStrip || altOnlyStrip) {
|
||||
const mouseStrip = isMuxMouseStripMode(this.mode, this._useMux);
|
||||
const altOnlyStrip = !fullStrip && !mouseStrip && isMuxAltScreenOnlyStripMode(this.mode, this._useMux);
|
||||
if (fullStrip || mouseStrip || altOnlyStrip) {
|
||||
// Reassemble sequences split across PTY chunk boundaries first: a chunk
|
||||
// ending mid-sequence ('\x1b[?104' now, '9h' next) would slip past the
|
||||
// strip below and leave xterm stuck in the scrollback-less alt buffer
|
||||
@@ -2515,14 +2549,18 @@ export class Session extends EventEmitter {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
data = data.replace(/\x1b\[\?(?:47|1047|1049)[hl]/g, '');
|
||||
if (fullStrip) {
|
||||
data = data
|
||||
// eslint-disable-next-line no-control-regex
|
||||
data = data.replace(/\x1b\[3J/g, '');
|
||||
}
|
||||
if (fullStrip || mouseStrip) {
|
||||
data = data.replace(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[3J/g, '')
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/\x1b\[\?(?:1000|1001|1002|1003|1005|1006|1007)[hl]/g, (seq) => {
|
||||
/\x1b\[\?(?:1000|1001|1002|1003|1005|1006|1007)[hl]/g,
|
||||
(seq) => {
|
||||
this._recordStrippedMouseMode(seq);
|
||||
return '';
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1265,7 +1265,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
// A real mouse click normally reaches the PTY through xterm's own mouse
|
||||
// encoder, but that encoder only runs while mouseTrackingMode is ON — and
|
||||
// the server strips the enabling DECSETs from claude/codex/gemini output
|
||||
// (isAltScreenStripMode, session.ts) so the wheel keeps scrolling
|
||||
// (isAltScreenStripMode, session.ts) and from opencode's (isMuxMouseStripMode,
|
||||
// so a drag selects text) so the wheel keeps scrolling
|
||||
// scrollback. Desktop clicks therefore stopped reporting entirely (the
|
||||
// same breakage the mobile touchend tap branch above works around).
|
||||
// Hand-encode the SGR report for plain left-clicks on those sessions.
|
||||
@@ -5271,36 +5272,32 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
// Mirror of the server's isAltScreenStripMode (session.ts): session modes whose
|
||||
// output stream has mouse-tracking DECSET sequences stripped before reaching the
|
||||
// browser. For these, xterm's live mouseTrackingMode is useless as a gate — the
|
||||
// PTY-side TUI keeps tracking enabled, we just never see the enable sequence.
|
||||
/**
|
||||
* True when the browser has to hand-encode a click report for the CLI.
|
||||
* True when the browser has to hand-encode a click report for the CLI: the
|
||||
* server stripped this session's mouse-tracking DECSETs out of the stream (so
|
||||
* xterm's own encoder is permanently idle here and something has to stand in
|
||||
* for it) AND the CLI has a tracking mode on right now.
|
||||
*
|
||||
* Two conditions, and dropping either one is a bug that has already happened:
|
||||
* One flag answers both. The server sets `cliMouseTracking` only as it strips a
|
||||
* tracking DECSET (`_recordStrippedMouseMode` in session.ts, called from the
|
||||
* mouse-strip branch of `_handleTerminalOutput` and nowhere else), so it can
|
||||
* only ever be true for a mode whose DECSETs are stripped: whichever modes the
|
||||
* registry decides to strip, the browser follows, with no mode list here to
|
||||
* keep in step. For a `preserve` / `strip-mux-only` mode the flag stays false
|
||||
* and xterm keeps encoding its own reports. That invariant is pinned server-side
|
||||
* in test/claude-scrollback-strip.test.ts.
|
||||
*
|
||||
* 1. The session's mode is one whose mouse DECSETs the server STRIPS out of
|
||||
* the stream (claude/codex/gemini, `isAltScreenStripMode`), which is why
|
||||
* xterm's own encoder is permanently idle here and something has to stand
|
||||
* in for it.
|
||||
* 2. The CLI actually has a mouse-tracking mode on right now. The server
|
||||
* records that as it strips (`_recordStrippedMouseMode` in session.ts) and
|
||||
* publishes it as `cliMouseTracking`. Without this half the browser
|
||||
* reported EVERY click, so a CLI sitting at its composer with no dialog
|
||||
* open, or a pane that has fallen back to a shell prompt, received mouse
|
||||
* reports it never asked for. A shell prints those as literal text
|
||||
* (`[<0;88;20M`) and they garble the next line typed.
|
||||
* Without the flag the browser reported EVERY click, so a CLI sitting at its
|
||||
* composer with no dialog open, or a pane that has fallen back to a shell
|
||||
* prompt, received mouse reports it never asked for. A shell prints those as
|
||||
* literal text (`[<0;88;20M`) and they garble the next line typed.
|
||||
*
|
||||
* Fails toward silence: an unknown or stale flag reports nothing rather than
|
||||
* injecting bytes. After a server restart the flag is false until the CLI
|
||||
* re-emits its DECSET, which closing and reopening a dialog does.
|
||||
*/
|
||||
_shouldReportMouseToCli() {
|
||||
const session = this.sessions?.get(this.activeSessionId);
|
||||
const mode = session?.mode || 'claude';
|
||||
if (mode !== 'claude' && mode !== 'codex' && mode !== 'gemini') return false;
|
||||
return session?.cliMouseTracking === true;
|
||||
return this.sessions?.get(this.activeSessionId)?.cliMouseTracking === true;
|
||||
},
|
||||
|
||||
// True when xterm's viewport shows the live PTY screen (not scrolled up into
|
||||
@@ -5631,7 +5628,8 @@ Object.assign(CodemanApp.prototype, {
|
||||
* The reason is that the habit and xterm's Shift mean different things once
|
||||
* the DECSETs are stripped. xterm reads Shift as "force selection" ONLY while
|
||||
* the app actually has mouse tracking on; the server strips those DECSETs for
|
||||
* claude/codex/gemini (isAltScreenStripMode), so xterm's mouseTrackingMode is
|
||||
* claude/codex/gemini (isAltScreenStripMode) and opencode (isMuxMouseStripMode),
|
||||
* so xterm's mouseTrackingMode is
|
||||
* permanently `none`, that branch is unreachable, and Shift instead falls into
|
||||
* `_onIncrementalClick` — EXTEND an existing selection. Extending is a no-op
|
||||
* when `selectionStart` is null, so the drag never anchors and no selection is
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
isAltScreenStripMode,
|
||||
isExternalCliMode,
|
||||
isMuxAltScreenOnlyStripMode,
|
||||
isMuxMouseStripMode,
|
||||
} from '../../session.js';
|
||||
import type { PaneCaptureOptions } from '../../mux-interface.js';
|
||||
import { SseEvent } from '../sse-events.js';
|
||||
@@ -224,6 +225,37 @@ const ERASE_SCROLLBACK_PATTERN = /\x1b\[3J/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const MOUSE_TRACKING_PATTERN = /\x1b\[\?(?:1000|1001|1002|1003|1005|1006|1007)[hl]/g;
|
||||
|
||||
/**
|
||||
* The replay half of the strip parity triangle: what `_handleTerminalOutput` (session.ts)
|
||||
* removes from the live stream, removed again from a stored buffer before it is replayed,
|
||||
* because a buffer recorded before the live-side strip existed (or by an older server)
|
||||
* still carries the sequences, and one replayed enable is enough to re-park xterm.
|
||||
*
|
||||
* - `strip-full` (claude/codex/gemini): alt-screen toggles, `3J` and mouse DECSETs.
|
||||
* xterm obeys the toggles by switching to its scrollback-less alt buffer and wiping
|
||||
* saved lines, so history disappeared on tab switch.
|
||||
* - `strip-mux-and-mouse` (opencode, tmux-backed): alt-screen toggles and mouse DECSETs.
|
||||
* A replayed tracking enable parks xterm in report mode, where a drag goes to the CLI
|
||||
* instead of selecting text (and Ctrl+C without a selection is opencode's app_exit).
|
||||
* `3J` stays: a TUI is not a `clear` consumer.
|
||||
* - every other mode, tmux-backed: tmux's own client smcup only (#205).
|
||||
*
|
||||
* Exported so the parity with the live strip is a test (claude-scrollback-strip.test.ts).
|
||||
*/
|
||||
export function stripReplayBuffer(buffer: string, mode: SessionMode, usesMux: boolean): string {
|
||||
if (isAltScreenStripMode(mode)) {
|
||||
return buffer
|
||||
.replace(ALT_SCREEN_TOGGLE_PATTERN, '')
|
||||
.replace(ERASE_SCROLLBACK_PATTERN, '')
|
||||
.replace(MOUSE_TRACKING_PATTERN, '');
|
||||
}
|
||||
if (isMuxMouseStripMode(mode, usesMux)) {
|
||||
return buffer.replace(ALT_SCREEN_TOGGLE_PATTERN, '').replace(MOUSE_TRACKING_PATTERN, '');
|
||||
}
|
||||
if (isMuxAltScreenOnlyStripMode(mode, usesMux)) return buffer.replace(ALT_SCREEN_TOGGLE_PATTERN, '');
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip redundant Ink spinner/status-bar redraw frames from the terminal buffer.
|
||||
* Ink (Claude Code's TUI) uses absolute cursor positioning (CSI n d = VPA) to animate
|
||||
@@ -3087,21 +3119,9 @@ export function registerSessionRoutes(
|
||||
? rawBuffer
|
||||
: stripInkRedrawBloat(rawBuffer);
|
||||
|
||||
// Strip alt-screen toggles and scrollback-erase from Codex/Claude byte
|
||||
// streams. xterm.js obeys them by switching to its scrollback-less alt
|
||||
// buffer and wiping saved lines, so conversation history disappears on tab
|
||||
// switch. Same gate as the live-stream strip in session.ts.
|
||||
if (isAltScreenStripMode(session.mode)) {
|
||||
strippedBuffer = strippedBuffer
|
||||
.replace(ALT_SCREEN_TOGGLE_PATTERN, '')
|
||||
.replace(ERASE_SCROLLBACK_PATTERN, '')
|
||||
.replace(MOUSE_TRACKING_PATTERN, '');
|
||||
} else if (isMuxAltScreenOnlyStripMode(session.mode, session.usesMux)) {
|
||||
// tmux-backed shell/opencode/antigravity: drop tmux's own client smcup only.
|
||||
// A byte buffer recorded before the live-side strip existed can still carry
|
||||
// it, and one replayed `\x1b[?1049h` re-parks xterm in the alt buffer (#205).
|
||||
strippedBuffer = strippedBuffer.replace(ALT_SCREEN_TOGGLE_PATTERN, '');
|
||||
}
|
||||
// Same strip as the live stream (session.ts), so a buffer recorded before the
|
||||
// live-side strip existed cannot re-park xterm on replay. See stripReplayBuffer.
|
||||
strippedBuffer = stripReplayBuffer(strippedBuffer, session.mode, session.usesMux);
|
||||
|
||||
if (tailBytes > 0 && strippedBuffer.length > tailBytes) {
|
||||
// Fast path: tail from the end, skip expensive banner search on full 2MB buffer.
|
||||
|
||||
Reference in New Issue
Block a user