[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,867 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const {
armTerminalInterruptOutputGate,
filterTerminalInterruptOutput,
stashPendingInterruptOutputMeta,
takePendingInterruptOutputMeta,
} = require("./terminalInterruptOutputGate.cjs");
test("pending interrupt metadata clears stale alternate-screen action on later unknown risk", () => {
const session = {};
stashPendingInterruptOutputMeta(session, {
droppedOutputMayAffectTerminalState: true,
droppedOutputAlternateScreenAction: "leave",
});
stashPendingInterruptOutputMeta(session, {
droppedOutputMayAffectTerminalState: true,
});
assert.deepEqual(takePendingInterruptOutputMeta(session), {
droppedOutputMayAffectTerminalState: true,
});
});
test("drops flood output after Ctrl+C and resumes from the interrupt echo", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 1000,
quietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "old output\n", { now: 1001 }),
{ accepted: false, data: "", droppedBytes: 11, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "more old output^C\r\n$ ", { now: 1002 }),
{ accepted: true, data: "^C\r\n$ ", droppedBytes: 15, reason: "interrupt-echo" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "next output", { now: 1003 }),
{ accepted: true, data: "next output", droppedBytes: 0, reason: "inactive" },
);
});
test("resumes output after a quiet gap when no interrupt echo is visible", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 2000,
quietMs: 80,
maxDrainMs: 1000,
});
assert.equal(filterTerminalInterruptOutput(session, "old output", { now: 2001 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 2100 }),
{ accepted: true, data: "$ ", droppedBytes: 0, reason: "prompt-gap" },
);
});
test("accepts an immediate prompt when the remote does not echo Ctrl+C", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 2500,
quietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 2501 }),
{ accepted: true, data: "$ ", droppedBytes: 0, reason: "prompt-candidate" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "next output", { now: 2502 }),
{ accepted: true, data: "next output", droppedBytes: 0, reason: "inactive" },
);
});
test("resumes output when interrupt echo is split across chunks", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3500,
quietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "old output", { now: 3501 }),
{ accepted: false, data: "", droppedBytes: 10, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "^", { now: 3502 }),
{ accepted: false, data: "", droppedBytes: 1, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "C\r\n$ ", { now: 3503 }),
{ accepted: true, data: "^C\r\n$ ", droppedBytes: 0, reason: "interrupt-echo" },
);
});
test("prompt gap keeps only the prompt suffix and drops stale prefix", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3600,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.equal(filterTerminalInterruptOutput(session, "old output", { now: 3601 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "stale flood\r\n$ ", { now: 3700 }),
{ accepted: true, data: "$ ", droppedBytes: 13, reason: "prompt-gap" },
);
});
test("preserves alternate-screen exit controls while draining stale output", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3800,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "stale frame\x1b[?1049l", { now: 3801 }),
{
accepted: true,
data: "\x1b[?1049l",
droppedBytes: "stale frame".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "more stale\x1b[?25h\r\n$ ", { now: 3900 }),
{
accepted: true,
data: "\x1b[?25h$ ",
droppedBytes: "more stale\r\n".length,
reason: "prompt-gap",
},
);
});
test("excludes preserved restore controls from held password prefixes", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3850,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
// Restore sequence is preserved once; password prefix hold must not re-count
// it, or stale bytes report as 0 dropped and the restore is re-emitted.
assert.deepEqual(
filterTerminalInterruptOutput(session, "stale\n\x1b[?1049lPass", { now: 3851 }),
{
accepted: true,
data: "\x1b[?1049l",
droppedBytes: "stale\n".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "word: ", { now: 3852 }),
{
accepted: true,
data: "Password: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("excludes the last of multiple preserved restores from held password prefixes", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3855,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "\x1b[?25hstale\n\x1b[?1049lPass", { now: 3856 }),
{
accepted: true,
data: "\x1b[?25h\x1b[?1049l",
droppedBytes: "stale\n".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "word: ", { now: 3857 }),
{
accepted: true,
data: "Password: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("does not peel preserved restore suffix chars from a later password prefix", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3860,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
// Preserve restore on its own line; next line "login pass" must keep the "l".
assert.deepEqual(
filterTerminalInterruptOutput(session, "\x1b[?1049l\nlogin pass", { now: 3861 }),
{
accepted: true,
data: "\x1b[?1049l",
droppedBytes: 1,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "word: ", { now: 3862 }),
{
accepted: true,
data: "login password: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("does not leak held incomplete SGR CSI into a later shell prompt", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3950,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
// Incomplete SGR without a password prefix must be dropped, not held into "$ ".
assert.deepEqual(
filterTerminalInterruptOutput(session, "old\x1b[31", { now: 3951 }),
{
accepted: false,
data: "",
droppedBytes: "old\x1b[31".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 4100 }),
{
accepted: true,
data: "$ ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("preserves split alternate-screen exit controls while draining stale output", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 4800,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "stale frame\x1b[?104", { now: 4801 }),
{
accepted: false,
data: "",
droppedBytes: "stale frame".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "9l^C\r\n$ ", { now: 4802 }),
{
accepted: true,
data: "\x1b[?1049l^C\r\n$ ",
droppedBytes: 0,
reason: "interrupt-echo",
},
);
});
test("does not preserve unsafe combined private modes while draining stale output", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 4900,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
const unsafeSequence = "\x1b[?1049;25h";
assert.deepEqual(
filterTerminalInterruptOutput(session, `stale frame${unsafeSequence}^C\r\n$ `, {
now: 4901,
}),
{
accepted: true,
data: "^C\r\n$ ",
droppedBytes: "stale frame".length + unsafeSequence.length,
reason: "interrupt-echo",
},
);
});
test("accepts prompt candidates with OSC title and spaces after stale output", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 5000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.equal(filterTerminalInterruptOutput(session, "old output", { now: 5001 }).accepted, false);
const prompt = "\x1b]0;~/My Project\x07~/My Project$ ";
assert.deepEqual(
filterTerminalInterruptOutput(session, prompt, { now: 5100 }),
{
accepted: true,
data: prompt,
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("accepts split OSC prompt candidates after stale output", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 5200,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.deepEqual(
filterTerminalInterruptOutput(session, "old output\x1b]0;~/My ", { now: 5201 }),
{
accepted: false,
data: "",
droppedBytes: "old output".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Project\x07~/My Project$ ", { now: 5300 }),
{
accepted: true,
data: "\x1b]0;~/My Project\x07~/My Project$ ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("keeps draining large chunks after a short quiet gap", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 3000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 1000,
});
assert.equal(filterTerminalInterruptOutput(session, "old output", { now: 3001 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "x".repeat(32768), { now: 3100 }),
{ accepted: false, data: "", droppedBytes: 32768, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 3200 }),
{ accepted: true, data: "$ ", droppedBytes: 0, reason: "prompt-gap" },
);
});
test("accepts a sudo password prompt while draining after Ctrl+C (#2010)", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 6000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(
filterTerminalInterruptOutput(session, "Reading package lists...\n", { now: 6001 }).accepted,
false,
);
// One-chunk password prompts must resume before promptQuietMs — they often
// emit nothing further until the user types (#2010 / Codex P1).
assert.deepEqual(
filterTerminalInterruptOutput(session, "[sudo] password for alice: ", { now: 6010 }),
{
accepted: true,
data: "[sudo] password for alice: ",
droppedBytes: 0,
reason: "password-prompt",
},
);
});
test("accepts bare and localized password prompts while draining (#2010)", () => {
for (const prompt of [
"Password: ",
"[sudo] alice 的密码:",
"输入密码",
"Input Password",
"用户 的密码",
"密码",
]) {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 7000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 7001 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, prompt, { now: 7010 }),
{
accepted: true,
data: prompt,
droppedBytes: 0,
reason: "password-prompt",
},
`expected password prompt to resume drain: ${JSON.stringify(prompt)}`,
);
}
});
test("does not treat password mentions in ordinary output as prompts", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 8000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 8001 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "the password was changed", { now: 8100 }),
{ accepted: false, data: "", droppedBytes: 24, reason: "draining" },
);
});
test("holds a split Password: prompt across chunks while draining (#2010)", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9001 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Pass", { now: 9002 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "word: ", { now: 9003 }),
{
accepted: true,
data: "Password: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("holds a split ANSI-colored Password: prompt across chunks while draining", () => {
const session = {};
const red = "\x1b[31m";
const reset = "\x1b[0m";
armTerminalInterruptOutputGate(session, {
now: 9050,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9051 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, `${red}Pass`, { now: 9052 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, `word:${reset} `, { now: 9053 }),
{
accepted: true,
data: `${red}Password:${reset} `,
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("holds a password prefix split before a trailing ANSI control while draining", () => {
const session = {};
const red = "\x1b[31m";
const reset = "\x1b[0m";
armTerminalInterruptOutputGate(session, {
now: 9070,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9071 }).accepted, false);
// Mid-CSI split: keep both "Pass" and the trailing "\x1b[" so the next chunk
// can complete the reset + "word:" into a password prompt.
assert.deepEqual(
filterTerminalInterruptOutput(session, `${red}Pass\x1b[`, { now: 9072 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, `0mword: `, { now: 9073 }),
{
accepted: true,
data: `${red}Pass${reset}word: `,
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("holds a password prefix split mid CSI parameter while draining", () => {
const session = {};
const red = "\x1b[31m";
const reset = "\x1b[0m";
armTerminalInterruptOutputGate(session, {
now: 9080,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9081 }).accepted, false);
// Incomplete CSI params (ESC[0) must also hold with the password prefix.
assert.deepEqual(
filterTerminalInterruptOutput(session, `${red}Pass\x1b[0`, { now: 9082 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, `mword: `, { now: 9083 }),
{
accepted: true,
data: `${red}Pass${reset}word: `,
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("holds a split [sudo] password prompt across chunks while draining (#2010)", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9100,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9101 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "[sudo] pass", { now: 9102 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "word for alice: ", { now: 9103 }),
{
accepted: true,
data: "[sudo] password for alice: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("discards a held password prefix that does not complete as a password prompt", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9200,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9201 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Pass", { now: 9202 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 9400 }),
{
accepted: true,
data: "$ ",
droppedBytes: 4,
reason: "prompt-gap",
},
);
});
test("discards CSI final bytes when a held mid-CSI password prefix does not complete", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9220,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9221 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Pass\x1b[0", { now: 9222 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
// Discard held prefix and the completing "m" so the shell prompt is clean.
assert.deepEqual(
filterTerminalInterruptOutput(session, "m$ ", { now: 9400 }),
{
accepted: true,
data: "$ ",
droppedBytes: "Pass\x1b[0".length + 1,
reason: "prompt-gap",
},
);
});
test("does not hold ordinary password-related output as a prompt prefix", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9300,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9301 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Password authentication failed", { now: 9302 }),
{
accepted: false,
data: "",
droppedBytes: "Password authentication failed".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 9500 }),
{
accepted: true,
data: "$ ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("holds split password prompts that have leading text before the keyword", () => {
const ssh = {};
armTerminalInterruptOutputGate(ssh, {
now: 9600,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(ssh, "stale\n", { now: 9601 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(ssh, "alice@host's pass", { now: 9602 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(ssh, "word: ", { now: 9603 }),
{
accepted: true,
data: "alice@host's password: ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
const kylin = {};
armTerminalInterruptOutputGate(kylin, {
now: 9700,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(kylin, "stale\n", { now: 9701 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(kylin, "用户 的密", { now: 9702 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
assert.deepEqual(
filterTerminalInterruptOutput(kylin, "码", { now: 9703 }),
{
accepted: true,
data: "用户 的密码",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("accepts a fresh one-chunk password prompt before the quiet gap", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9800,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "old flood\n", { now: 9801 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Password: ", { now: 9810 }),
{
accepted: true,
data: "Password: ",
droppedBytes: 0,
reason: "password-prompt",
},
);
});
test("accepts a last-line password prompt after a large stale prefix", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9820,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
promptCandidateBytes: 512,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9821 }).accepted, false);
const largeChunk = `${"x".repeat(600)}\n[sudo] password for alice: `;
assert.deepEqual(
filterTerminalInterruptOutput(session, largeChunk, { now: 9830 }),
{
accepted: true,
data: "[sudo] password for alice: ",
droppedBytes: 601,
reason: "password-prompt",
},
);
});
test("does not hold short ASCII trailing letters as password prefixes", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 10000,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 10001 }).accepted, false);
// Full-width "password" must not lower ASCII minLen to 1, or "copy p" is held
// and a later "assword:" chunk can bypass quiet-gap.
assert.deepEqual(
filterTerminalInterruptOutput(session, "copy p", { now: 10002 }),
{
accepted: false,
data: "",
droppedBytes: "copy p".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "assword: ", { now: 10010 }),
{
accepted: false,
data: "",
droppedBytes: "assword: ".length,
reason: "draining",
},
);
assert.deepEqual(
filterTerminalInterruptOutput(session, "$ ", { now: 10200 }),
{
accepted: true,
data: "$ ",
droppedBytes: 0,
reason: "prompt-gap",
},
);
});
test("rejects held password-prefix completion across a line break", () => {
const session = {};
armTerminalInterruptOutputGate(session, {
now: 9900,
quietMs: 500,
promptQuietMs: 80,
maxDrainMs: 2500,
});
assert.equal(filterTerminalInterruptOutput(session, "stale\n", { now: 9901 }).accepted, false);
assert.deepEqual(
filterTerminalInterruptOutput(session, "Pass", { now: 9902 }),
{ accepted: false, data: "", droppedBytes: 0, reason: "draining" },
);
// A newline before Password: discards the held "Pass" prefix (not a same-line
// completion), but the fresh complete password line is still preserved.
assert.deepEqual(
filterTerminalInterruptOutput(session, "\nPassword: ", { now: 9910 }),
{
accepted: true,
data: "Password: ",
droppedBytes: 4 + 1,
reason: "password-prompt",
},
);
});