[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
214
domain/terminalContextRead.ts
Normal file
214
domain/terminalContextRead.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
export type TerminalContextReadRange = "viewport" | "tail" | "head" | "lines";
|
||||
|
||||
export type TerminalContextReadSource = "live" | "snapshot";
|
||||
|
||||
export const TERMINAL_CONTEXT_DEFAULT_MAX_LINES = 80;
|
||||
export const TERMINAL_CONTEXT_MAX_LINES = 300;
|
||||
|
||||
export interface TerminalContextReadRequest {
|
||||
sessionId?: string;
|
||||
range?: TerminalContextReadRange;
|
||||
startLine?: number;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
export interface TerminalContextLineWindowInput {
|
||||
range?: TerminalContextReadRange;
|
||||
totalLines: number;
|
||||
viewportStartLine?: number;
|
||||
viewportEndLine?: number;
|
||||
startLine?: number;
|
||||
maxLines?: number;
|
||||
}
|
||||
|
||||
export interface TerminalContextLineWindow {
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
maxLines: number;
|
||||
}
|
||||
|
||||
export interface TerminalContextReadResult {
|
||||
ok: true;
|
||||
sessionId: string;
|
||||
label?: string;
|
||||
range: TerminalContextReadRange;
|
||||
content: string;
|
||||
totalLines: number;
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
returnedLines: number;
|
||||
hasMoreBefore: boolean;
|
||||
hasMoreAfter: boolean;
|
||||
source: TerminalContextReadSource;
|
||||
alternateScreen?: boolean;
|
||||
}
|
||||
|
||||
export type TerminalContextReader = (
|
||||
request: TerminalContextReadRequest & { sessionId: string },
|
||||
) => Promise<TerminalContextReadResult | { ok: false; error: string }>;
|
||||
|
||||
export interface BuildTerminalContextReadResultInput {
|
||||
sessionId: string;
|
||||
label?: string;
|
||||
fullText: string;
|
||||
range?: TerminalContextReadRange;
|
||||
startLine?: number;
|
||||
maxLines?: number;
|
||||
source: TerminalContextReadSource;
|
||||
alternateScreen?: boolean;
|
||||
viewportStartLine?: number;
|
||||
viewportEndLine?: number;
|
||||
}
|
||||
|
||||
export interface TerminalContextSnapshotTextInput {
|
||||
scrollbackText?: string;
|
||||
viewportText?: string;
|
||||
pendingText?: string;
|
||||
}
|
||||
|
||||
export interface TerminalContextSnapshotText {
|
||||
fullText: string;
|
||||
viewportStartLine?: number;
|
||||
viewportEndLine?: number;
|
||||
}
|
||||
|
||||
export function normalizeTerminalContextMaxLines(value: unknown): number {
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) {
|
||||
return TERMINAL_CONTEXT_DEFAULT_MAX_LINES;
|
||||
}
|
||||
return Math.min(
|
||||
TERMINAL_CONTEXT_MAX_LINES,
|
||||
Math.max(1, Math.round(value)),
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeTerminalContextRange(value: unknown): TerminalContextReadRange {
|
||||
if (
|
||||
value === "viewport" ||
|
||||
value === "tail" ||
|
||||
value === "head" ||
|
||||
value === "lines"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
return "viewport";
|
||||
}
|
||||
|
||||
export function resolveTerminalContextLineWindow(
|
||||
input: TerminalContextLineWindowInput,
|
||||
): TerminalContextLineWindow {
|
||||
const range = normalizeTerminalContextRange(input.range);
|
||||
const maxLines = normalizeTerminalContextMaxLines(input.maxLines);
|
||||
const totalLines = Math.max(0, Math.floor(input.totalLines));
|
||||
|
||||
if (totalLines === 0) {
|
||||
return { startLine: 0, endLine: -1, maxLines };
|
||||
}
|
||||
|
||||
if (range === "head") {
|
||||
return {
|
||||
startLine: 0,
|
||||
endLine: Math.min(totalLines - 1, maxLines - 1),
|
||||
maxLines,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === "tail") {
|
||||
return {
|
||||
startLine: Math.max(0, totalLines - maxLines),
|
||||
endLine: totalLines - 1,
|
||||
maxLines,
|
||||
};
|
||||
}
|
||||
|
||||
if (range === "lines") {
|
||||
const start = clampLine(input.startLine, totalLines);
|
||||
return {
|
||||
startLine: start,
|
||||
endLine: Math.min(totalLines - 1, start + maxLines - 1),
|
||||
maxLines,
|
||||
};
|
||||
}
|
||||
|
||||
const viewportStart = clampLine(input.viewportStartLine, totalLines);
|
||||
const viewportEnd = clampLine(input.viewportEndLine, totalLines);
|
||||
const rawStart = Math.min(viewportStart, viewportEnd);
|
||||
const end = Math.max(viewportStart, viewportEnd);
|
||||
const start = Math.max(rawStart, end - maxLines + 1);
|
||||
return { startLine: start, endLine: end, maxLines };
|
||||
}
|
||||
|
||||
export function buildTerminalContextReadResult(
|
||||
input: BuildTerminalContextReadResultInput,
|
||||
): TerminalContextReadResult {
|
||||
const range = normalizeTerminalContextRange(input.range);
|
||||
const lines = splitTerminalContextLines(input.fullText);
|
||||
const totalLines = lines.length;
|
||||
const window = resolveTerminalContextLineWindow({
|
||||
range,
|
||||
totalLines,
|
||||
viewportStartLine: input.viewportStartLine,
|
||||
viewportEndLine: input.viewportEndLine,
|
||||
startLine: input.startLine,
|
||||
maxLines: input.maxLines,
|
||||
});
|
||||
const selectedLines = window.endLine >= window.startLine
|
||||
? lines.slice(window.startLine, window.endLine + 1)
|
||||
: [];
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
sessionId: input.sessionId,
|
||||
label: input.label,
|
||||
range,
|
||||
content: selectedLines.join("\n"),
|
||||
totalLines,
|
||||
startLine: window.startLine,
|
||||
endLine: window.endLine,
|
||||
returnedLines: selectedLines.length,
|
||||
hasMoreBefore: window.startLine > 0,
|
||||
hasMoreAfter: window.endLine >= 0 && window.endLine < totalLines - 1,
|
||||
source: input.source,
|
||||
alternateScreen: input.alternateScreen,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTerminalContextSnapshotText(
|
||||
input: TerminalContextSnapshotTextInput,
|
||||
): TerminalContextSnapshotText {
|
||||
const parts: Array<{ kind: "scrollback" | "viewport" | "pending"; text: string }> = [];
|
||||
if (input.scrollbackText) parts.push({ kind: "scrollback", text: input.scrollbackText });
|
||||
if (input.viewportText) parts.push({ kind: "viewport", text: input.viewportText });
|
||||
if (input.pendingText) parts.push({ kind: "pending", text: input.pendingText });
|
||||
|
||||
let lineOffset = 0;
|
||||
let viewportStartLine: number | undefined;
|
||||
let viewportEndLine: number | undefined;
|
||||
for (const part of parts) {
|
||||
const lineCount = splitTerminalContextLines(part.text).length;
|
||||
if (part.kind === "viewport") {
|
||||
viewportStartLine = lineOffset;
|
||||
viewportEndLine = lineOffset + lineCount - 1;
|
||||
} else if (part.kind === "pending" && viewportStartLine !== undefined) {
|
||||
viewportEndLine = lineOffset + lineCount - 1;
|
||||
}
|
||||
lineOffset += lineCount;
|
||||
}
|
||||
|
||||
return {
|
||||
fullText: parts.map((part) => part.text).join("\n"),
|
||||
viewportStartLine,
|
||||
viewportEndLine,
|
||||
};
|
||||
}
|
||||
|
||||
function splitTerminalContextLines(text: string): string[] {
|
||||
if (!text) return [];
|
||||
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
||||
}
|
||||
|
||||
function clampLine(value: unknown, totalLines: number): number {
|
||||
if (totalLines <= 0) return 0;
|
||||
if (typeof value !== "number" || !Number.isFinite(value)) return 0;
|
||||
return Math.min(totalLines - 1, Math.max(0, Math.floor(value)));
|
||||
}
|
||||
Reference in New Issue
Block a user