Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
"use strict";
|
|
|
|
const { trackEmitted } = require("./terminalFlowAck.cjs");
|
|
const {
|
|
attachTerminalOutputPerfMeta,
|
|
createTerminalOutputPerfMeta,
|
|
logTerminalOutputPerf,
|
|
} = require("./terminalPerformanceDiagnostics.cjs");
|
|
|
|
let getSession = null;
|
|
let outputChannel = null;
|
|
let onSessionActivity = null;
|
|
/** @type {Set<(sessionId: string, data: string) => void>} */
|
|
const dataTaps = new Set();
|
|
const emitPerfLogStateBySession = new Map();
|
|
const MAX_EMIT_PERF_SESSION_STATES = 512;
|
|
|
|
function retainTerminalSessionPerformanceState(key, state) {
|
|
emitPerfLogStateBySession.delete(key);
|
|
emitPerfLogStateBySession.set(key, state);
|
|
while (emitPerfLogStateBySession.size > MAX_EMIT_PERF_SESSION_STATES) {
|
|
const oldestKey = emitPerfLogStateBySession.keys().next().value;
|
|
if (oldestKey === undefined) break;
|
|
emitPerfLogStateBySession.delete(oldestKey);
|
|
}
|
|
}
|
|
|
|
function clearTerminalSessionPerformanceState(sessionId) {
|
|
if (!sessionId) return;
|
|
emitPerfLogStateBySession.delete(sessionId);
|
|
}
|
|
|
|
function configureTerminalSessionDataEmitter(options = {}) {
|
|
getSession = typeof options.getSession === "function" ? options.getSession : null;
|
|
outputChannel = options.outputChannel || null;
|
|
onSessionActivity = typeof options.onSessionActivity === "function"
|
|
? options.onSessionActivity
|
|
: null;
|
|
}
|
|
|
|
function addTerminalDataTap(listener) {
|
|
if (typeof listener !== "function") return () => {};
|
|
dataTaps.add(listener);
|
|
return () => dataTaps.delete(listener);
|
|
}
|
|
|
|
function getEmitPerfLogDetails(sessionId, terminalPerf, options = {}) {
|
|
if (!terminalPerf) return null;
|
|
const key = sessionId || "__unknown__";
|
|
const now = Date.now();
|
|
const state = emitPerfLogStateBySession.get(key) || {
|
|
lastLoggedAt: 0,
|
|
batchChunks: 0,
|
|
batchChars: 0,
|
|
batchLineFeeds: 0,
|
|
};
|
|
|
|
state.batchChunks += 1;
|
|
state.batchChars += terminalPerf.chars;
|
|
state.batchLineFeeds += terminalPerf.lineFeeds;
|
|
|
|
const shouldLog =
|
|
state.lastLoggedAt === 0
|
|
|| state.batchChars >= 512 * 1024
|
|
|| state.batchLineFeeds >= 200
|
|
|| now - state.lastLoggedAt >= 1000;
|
|
|
|
if (!shouldLog) {
|
|
retainTerminalSessionPerformanceState(key, state);
|
|
return null;
|
|
}
|
|
|
|
const details = {
|
|
id: terminalPerf.id,
|
|
sessionId,
|
|
chars: terminalPerf.chars,
|
|
lineFeeds: terminalPerf.lineFeeds,
|
|
batchChunks: state.batchChunks,
|
|
batchChars: state.batchChars,
|
|
batchLineFeeds: state.batchLineFeeds,
|
|
cols: options?.cols,
|
|
rows: options?.rows,
|
|
};
|
|
|
|
retainTerminalSessionPerformanceState(key, {
|
|
lastLoggedAt: now,
|
|
batchChunks: 0,
|
|
batchChars: 0,
|
|
batchLineFeeds: 0,
|
|
});
|
|
return details;
|
|
}
|
|
|
|
function emitTerminalSessionData(contents, sessionId, data, options = {}) {
|
|
const currentSession = getSession && sessionId ? getSession(sessionId) : null;
|
|
if (options.session && currentSession !== options.session) return false;
|
|
if (sessionId && data && onSessionActivity) {
|
|
try {
|
|
onSessionActivity({ sessionId, phase: "touch" });
|
|
} catch {
|
|
// Session activity tracking is best-effort and must not break output.
|
|
}
|
|
}
|
|
if (currentSession && sessionId && data) {
|
|
trackEmitted(currentSession, typeof data === "string" ? data.length : 0, sessionId);
|
|
}
|
|
if (sessionId && data) {
|
|
for (const tap of dataTaps) {
|
|
try {
|
|
tap(sessionId, data);
|
|
} catch (err) {
|
|
console.warn("[emitTerminalSessionData] tap failed", err);
|
|
}
|
|
}
|
|
}
|
|
const terminalPerf = createTerminalOutputPerfMeta(sessionId, data);
|
|
const meta = attachTerminalOutputPerfMeta(options?.meta, terminalPerf);
|
|
const emitPerfDetails = getEmitPerfLogDetails(sessionId, terminalPerf, options);
|
|
if (emitPerfDetails) {
|
|
logTerminalOutputPerf("backend-emit", emitPerfDetails);
|
|
}
|
|
if (outputChannel?.send?.(sessionId, data, meta)) return true;
|
|
const generation = options.session?._terminalSessionGeneration;
|
|
contents?.send("netcatty:data", {
|
|
sessionId,
|
|
data,
|
|
...(meta ? { meta } : {}),
|
|
...(Number.isSafeInteger(generation) ? { _terminalSessionGeneration: generation } : {}),
|
|
});
|
|
return true;
|
|
}
|
|
|
|
module.exports = {
|
|
configureTerminalSessionDataEmitter,
|
|
emitTerminalSessionData,
|
|
addTerminalDataTap,
|
|
clearTerminalSessionPerformanceState,
|
|
_getTerminalSessionPerformanceStateCountForTests: () => emitPerfLogStateBySession.size,
|
|
};
|