COD-80 centralize terminal history/scrollback/buffer limits into config

Introduce src/config/terminal-history.ts: one place for terminal scrollback,
tmux history-limit, and PTY buffer byte caps, each overridable via env var or
the settings object and bounds-clamped via resolveTerminalHistoryConfig().
Defaults match the prior hardcoded values, so this is behavior-neutral. Wires
the resolver through buffer-limits, tmux-manager (incl. a setHistoryLimit so a
settings change applies live), session, server, system-routes, session-routes,
schemas, and the config port. Adds 4 optional settings keys (terminalScrollback
Lines, tmuxHistoryLimit, terminalBufferMaxBytes, terminalBufferTrimBytes) with
bounds + a trim<=max cross-check.
This commit is contained in:
Aamer Akhter
2026-06-30 19:52:49 -04:00
parent abb6447f66
commit 8c0c94540c
13 changed files with 355 additions and 6 deletions
+36 -1
View File
@@ -65,6 +65,7 @@ import type {
// ============================================================================
import { EXEC_TIMEOUT_MS } from './config/exec-timeout.js';
import { DEFAULT_TMUX_HISTORY_LIMIT } from './config/terminal-history.js';
/** Delay after tmux session creation — enough for detached tmux to be queryable */
const TMUX_CREATION_WAIT_MS = 100;
@@ -1057,6 +1058,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
resumeSessionId,
envOverrides,
effort,
historyLimit = DEFAULT_TMUX_HISTORY_LIMIT,
} = options;
const muxName = `codeman-${sessionId.slice(0, 8)}`;
@@ -1201,7 +1203,9 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
}),
// Raise tmux scrollback from its 2000-line default so re-attach preserves
// more context. Matches the xterm-side default in constants.js.
execAsync(`${this.tmux()} set-option -t "${muxName}" history-limit 50000`, { timeout: EXEC_TIMEOUT_MS })
execAsync(`${this.tmux()} set-option -t "${muxName}" history-limit ${historyLimit}`, {
timeout: EXEC_TIMEOUT_MS,
})
.then(() => {})
.catch(() => {
/* Non-critical — falls back to tmux default */
@@ -1324,6 +1328,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
resumeSessionId,
envOverrides,
effort,
historyLimit = DEFAULT_TMUX_HISTORY_LIMIT,
} = options;
const session = this.sessions.get(sessionId);
if (!session) return null;
@@ -1331,6 +1336,16 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
if (!isValidMuxName(muxName) || !isValidPath(workingDir)) return null;
// Re-apply the configured tmux history-limit after respawn (kept in sync
// with the live setting via setHistoryLimit()).
if (!IS_TEST_MODE) {
await execAsync(`${this.tmux()} set-option -t ${shellescape(muxName)} history-limit ${historyLimit}`, {
timeout: EXEC_TIMEOUT_MS,
}).catch(() => {
/* Non-critical — keeps existing tmux history-limit */
});
}
// Resolve CLI binary directory based on mode
const { pathExport } = this.buildPathExport(mode);
@@ -1935,6 +1950,26 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
}
}
/**
* Apply a tmux history-limit to all tracked sessions (e.g. when the user
* changes the terminal-history setting). Invalid limits fall back to the
* default. Best-effort per session.
*/
async setHistoryLimit(limit: number): Promise<void> {
const safeLimit = Number.isSafeInteger(limit) && limit > 0 ? Math.trunc(limit) : DEFAULT_TMUX_HISTORY_LIMIT;
if (IS_TEST_MODE) {
return;
}
const updates = Array.from(this.sessions.values()).map((session) =>
execAsync(`${this.tmux()} set-option -t ${shellescape(session.muxName)} history-limit ${safeLimit}`, {
timeout: EXEC_TIMEOUT_MS,
})
);
await Promise.allSettled(updates);
}
/**
* Send input directly to a tmux session using `send-keys`.
*