[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

This commit is contained in:
2026-09-13 18:24:01 +08:00
commit 3c72efcb7f
3255 changed files with 907009 additions and 0 deletions

View File

@@ -0,0 +1,166 @@
import { createRequire } from "node:module";
import assert from "node:assert/strict";
import test from "node:test";
import terminalFlowConstantsJson from "../../../infrastructure/config/terminalFlowConstants.json";
import {
FLOW_CHAR_COUNT_ACK_SIZE,
FLOW_HIGH_WATER_MARK,
FLOW_LOW_WATER_MARK,
MAX_PENDING_WRITE_COALESCE_BYTES,
MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
TERMINAL_LONG_LINE_PRESSURE_BYTES,
XTERM_WRITE_CALLBACK_BATCH_BYTES,
XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
} from "./terminalFlowConstants.ts";
import { createOutputFlowController } from "./outputFlowController.ts";
const require = createRequire(import.meta.url);
const sharedConstantsCjs = require("../../../infrastructure/config/terminalFlowConstants.cjs") as typeof terminalFlowConstantsJson;
test("renderer flow constants match shared terminalFlowConstants.json", () => {
assert.equal(FLOW_HIGH_WATER_MARK, terminalFlowConstantsJson.FLOW_HIGH_WATER_MARK);
assert.equal(FLOW_LOW_WATER_MARK, terminalFlowConstantsJson.FLOW_LOW_WATER_MARK);
assert.equal(FLOW_CHAR_COUNT_ACK_SIZE, terminalFlowConstantsJson.FLOW_CHAR_COUNT_ACK_SIZE);
assert.equal(
MAX_PENDING_WRITE_COALESCE_BYTES,
terminalFlowConstantsJson.MAX_PENDING_WRITE_COALESCE_BYTES,
);
assert.equal(
MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
terminalFlowConstantsJson.MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD,
);
assert.equal(
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
terminalFlowConstantsJson.MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
);
assert.equal(
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
terminalFlowConstantsJson.MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
);
assert.equal(
MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
terminalFlowConstantsJson.MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES,
);
assert.equal(
TERMINAL_LONG_LINE_PRESSURE_BYTES,
terminalFlowConstantsJson.TERMINAL_LONG_LINE_PRESSURE_BYTES,
);
assert.equal(
TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
terminalFlowConstantsJson.TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS,
);
assert.equal(
XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
terminalFlowConstantsJson.XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES,
);
assert.equal(
XTERM_WRITE_CALLBACK_BATCH_BYTES,
terminalFlowConstantsJson.XTERM_WRITE_CALLBACK_BATCH_BYTES,
);
assert.deepEqual(sharedConstantsCjs, terminalFlowConstantsJson);
assert.ok(FLOW_CHAR_COUNT_ACK_SIZE <= FLOW_LOW_WATER_MARK);
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD < MAX_PENDING_WRITE_COALESCE_BYTES);
});
test("terminal flood limits keep interactive acks responsive", () => {
assert.ok(FLOW_LOW_WATER_MARK <= 8 * 1024);
assert.ok(FLOW_CHAR_COUNT_ACK_SIZE <= 4 * 1024);
// Flood coalesce must stay below bulk so TUI frames can interleave, but stay
// large enough that plain-text dumps (#1961) do not collapse into 8KB shards.
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD <= 256 * 1024);
assert.ok(MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD >= 64 * 1024);
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES <= FLOW_HIGH_WATER_MARK);
// Unbroken-line shards should stay near Tabby's ~100KB PTY chunk size so
// long dumps stream smoothly instead of 4KB + setTimeout(0) stuttering.
assert.ok(MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES >= 64 * 1024);
assert.ok(MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES <= 256 * 1024);
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES <= FLOW_HIGH_WATER_MARK);
// Drain enough per event-loop turn that a 1MB high-water backlog does not
// require dozens of setTimeout(0) yields before SSH can resume.
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES >= 256 * 1024);
assert.ok(MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES >= MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES);
// Long-line pressure can trip earlier than the write shard size so highlight
// / gutter work throttles before bulk parse cost peaks.
assert.ok(TERMINAL_LONG_LINE_PRESSURE_BYTES >= 32 * 1024);
assert.ok(TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS >= TERMINAL_LONG_LINE_PRESSURE_BYTES);
assert.ok(XTERM_WRITE_CALLBACK_BATCH_BYTES <= FLOW_HIGH_WATER_MARK);
});
test("terminal bulk output keeps large IPC coalesce but Tabby-sized xterm shards", () => {
const bulkCoalesceFloorBytes = 1024 * 1024;
assert.ok(
MAX_PENDING_WRITE_COALESCE_BYTES >= bulkCoalesceFloorBytes,
`MAX_PENDING_WRITE_COALESCE_BYTES (${MAX_PENDING_WRITE_COALESCE_BYTES}) should still batch multi-MB IPC into large flushes`,
);
// xterm write shards stay near Tabby's ~128KB FlowControl threshold so
// multi-line floods (seq/logs) leave the event loop between slices.
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES >= 64 * 1024);
assert.ok(MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES <= 256 * 1024);
assert.equal(
MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES,
MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES,
);
});
test("terminal flow allows a large TUI repaint before applying back-pressure", () => {
const events: string[] = [];
const controller = createOutputFlowController({
highWaterMark: FLOW_HIGH_WATER_MARK,
lowWaterMark: FLOW_LOW_WATER_MARK,
onPause: () => events.push("pause"),
onResume: () => events.push("resume"),
});
const tuiFrameBytes = 80 * 1024;
const chunkBytes = 4 * 1024;
for (let received = 0; received < tuiFrameBytes; received += chunkBytes) {
controller.received(chunkBytes);
}
assert.deepEqual(events, []);
});
test("flow high-water mark stays clear of the ssh2 channel window (issue #1961)", () => {
// Pausing the source stream for SSH means calling ssh2 channel pause(),
// which stops the remote from sending until resume() + a full round-trip.
// ssh2's own channel window is 2MB (WINDOW_THRESHOLD 1MB), so a small
// high-water mark makes Netcatty pause/resume dozens of times during a
// multi-MB dump (e.g. `tail -2000f big.log`). Each cycle costs ~1 RTT, so
// on a WAN link the dump crawls (reported ~20s vs ~2s in other clients).
// Keep the high-water mark near the ssh2 window so bulk output flows in a
// handful of pause cycles instead of dozens.
const SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES = 1024 * 1024;
assert.ok(
FLOW_HIGH_WATER_MARK >= SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES,
`FLOW_HIGH_WATER_MARK (${FLOW_HIGH_WATER_MARK}) should be at least the ssh2 window threshold (${SSH2_CHANNEL_WINDOW_THRESHOLD_BYTES})`,
);
// A 4MB bulk dump should trigger only a few pause cycles, not dozens.
const events: string[] = [];
const controller = createOutputFlowController({
highWaterMark: FLOW_HIGH_WATER_MARK,
lowWaterMark: FLOW_LOW_WATER_MARK,
onPause: () => events.push("pause"),
onResume: () => events.push("resume"),
});
const totalBytes = 4 * 1024 * 1024;
const chunkBytes = 32 * 1024; // ssh2 PACKET_SIZE
let delivered = 0;
let pauses = 0;
for (let sent = 0; sent < totalBytes; sent += chunkBytes) {
controller.received(chunkBytes);
if (controller.isPaused()) {
pauses += 1;
// Renderer catches up and drains the backlog before the source resumes.
controller.written(controller.pendingBytes());
delivered = sent + chunkBytes;
}
}
controller.written(totalBytes - delivered);
assert.ok(pauses <= 8, `expected few pause cycles for a 4MB dump, got ${pauses}`);
});