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 { mergeTerminalDataMeta } = require("./terminalDataMeta.cjs");
|
|
|
|
function hasPluginPipelineIngress(meta) {
|
|
return Number.isFinite(meta?.pluginPipelineIngressBytes)
|
|
&& Number(meta.pluginPipelineIngressBytes) > 0;
|
|
}
|
|
|
|
function hasPluginPipelineIngressMarker(meta) {
|
|
return Number.isFinite(meta?.pluginPipelineIngressBytes)
|
|
&& Number(meta.pluginPipelineIngressBytes) >= 0;
|
|
}
|
|
|
|
function createTerminalDataBacklog(options = {}) {
|
|
const maxBytesPerSession = options.maxBytesPerSession ?? 64 * 1024;
|
|
const pendingBySession = new Map();
|
|
|
|
function trimToLimit(value) {
|
|
if (value.length <= maxBytesPerSession) return value;
|
|
return value.slice(value.length - maxBytesPerSession);
|
|
}
|
|
|
|
function append(sessionId, data, meta) {
|
|
if (!sessionId || (!data && !hasPluginPipelineIngressMarker(meta))) return;
|
|
const previous = pendingBySession.get(sessionId) || { data: "", meta: undefined };
|
|
const nextData = trimToLimit(previous.data + data);
|
|
const preserveTerminalPerf = previous.data.length === 0 && nextData === data;
|
|
let previousMeta = previous.meta;
|
|
let nextChunkMeta = meta;
|
|
const previousHasIngress = Number.isFinite(previousMeta?.pluginPipelineIngressBytes);
|
|
const nextChunkHasIngress = Number.isFinite(nextChunkMeta?.pluginPipelineIngressBytes);
|
|
// Once one merged chunk carries explicit original-ingress accounting, the
|
|
// metadata must cover every raw flow unit in the same replay entry. Flow
|
|
// control is intentionally charged in JavaScript string length, not UTF-8
|
|
// bytes, throughout the terminal renderer/worker path. Otherwise a
|
|
// processed chunk followed or preceded by ordinary output would cause the
|
|
// renderer to acknowledge only the annotated subset.
|
|
if (previousHasIngress && !nextChunkHasIngress && data) {
|
|
nextChunkMeta = {
|
|
...(nextChunkMeta || {}),
|
|
pluginPipelineIngressBytes: data.length,
|
|
};
|
|
} else if (!previousHasIngress && nextChunkHasIngress && previous.data) {
|
|
previousMeta = {
|
|
...(previousMeta || {}),
|
|
pluginPipelineIngressBytes: previous.data.length,
|
|
};
|
|
}
|
|
const nextMeta = mergeTerminalDataMeta(previousMeta, nextChunkMeta, { preserveTerminalPerf });
|
|
pendingBySession.set(sessionId, {
|
|
data: nextData,
|
|
meta: nextMeta,
|
|
});
|
|
}
|
|
|
|
function takeEntry(sessionId) {
|
|
const entry = pendingBySession.get(sessionId) || { data: "", meta: undefined };
|
|
pendingBySession.delete(sessionId);
|
|
return entry;
|
|
}
|
|
|
|
function take(sessionId) {
|
|
return takeEntry(sessionId).data;
|
|
}
|
|
|
|
function clear(sessionId) {
|
|
pendingBySession.delete(sessionId);
|
|
}
|
|
|
|
function size(sessionId) {
|
|
return pendingBySession.get(sessionId)?.data.length ?? 0;
|
|
}
|
|
|
|
return {
|
|
append,
|
|
take,
|
|
takeEntry,
|
|
clear,
|
|
size,
|
|
};
|
|
}
|
|
|
|
function hasSessionListeners(listenersBySession, sessionId) {
|
|
return (listenersBySession.get(sessionId)?.size ?? 0) > 0;
|
|
}
|
|
|
|
function createTerminalDataDispatcher({
|
|
dataListeners,
|
|
displayDataListeners,
|
|
terminalDataBacklog,
|
|
onCallbackError = console.error,
|
|
shouldDropSession = () => false,
|
|
}) {
|
|
return function deliverToListeners(sessionId, data, meta) {
|
|
if (!data && !hasPluginPipelineIngressMarker(meta)) return;
|
|
if (shouldDropSession(sessionId)) return;
|
|
|
|
if (!hasSessionListeners(displayDataListeners, sessionId)) {
|
|
terminalDataBacklog?.append?.(sessionId, data, meta);
|
|
}
|
|
|
|
const set = dataListeners.get(sessionId);
|
|
if (!set || set.size === 0) return;
|
|
|
|
set.forEach((cb) => {
|
|
try {
|
|
cb(data, meta);
|
|
} catch (err) {
|
|
onCallbackError("Data callback failed", err);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
function clearTerminalDataSession({
|
|
dataListeners,
|
|
displayDataListeners,
|
|
terminalDataBacklog,
|
|
}, sessionId) {
|
|
dataListeners?.delete?.(sessionId);
|
|
displayDataListeners?.delete?.(sessionId);
|
|
terminalDataBacklog?.clear?.(sessionId);
|
|
}
|
|
|
|
function clearTerminalDataBacklog({
|
|
terminalDataBacklog,
|
|
}, sessionId) {
|
|
terminalDataBacklog?.clear?.(sessionId);
|
|
}
|
|
|
|
module.exports = {
|
|
clearTerminalDataBacklog,
|
|
createTerminalDataBacklog,
|
|
createTerminalDataDispatcher,
|
|
clearTerminalDataSession,
|
|
hasPluginPipelineIngress,
|
|
hasPluginPipelineIngressMarker,
|
|
};
|