[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,267 @@
"use strict";
const assert = require("node:assert/strict");
const crypto = require("node:crypto");
const { EventEmitter } = require("node:events");
const test = require("node:test");
const { createBackgroundJobApi } = require("./mcpServerBridge/backgroundJobs.cjs");
const { createExecHandlerApi } = require("./mcpServerBridge/execHandlers.cjs");
const { PROBE_OUTPUT_MARKER } = require("./ai/sessionShellKind.cjs");
const {
checkBlocklistForShell,
resolveSessionBlocklistShellKind,
} = require("./ai/commandSafety.cjs");
class FakePty extends EventEmitter {
constructor() {
super();
this.writes = [];
}
write(data) {
this.writes.push(String(data));
}
}
function nextTick() {
return new Promise((resolve) => setImmediate(resolve));
}
function createDeferredShellProbeConn(stdout = `${PROBE_OUTPUT_MARKER}/usr/bin/fish\n`) {
let execCallback = null;
const conn = {
exec(_command, callback) {
execCallback = callback;
},
};
return {
conn,
release() {
assert.equal(typeof execCallback, "function", "probe should be waiting for ssh exec callback");
const stream = new EventEmitter();
stream.stderr = new EventEmitter();
stream.close = () => stream.emit("close");
execCallback(null, stream);
queueMicrotask(() => {
stream.emit("data", Buffer.from(stdout));
stream.emit("close");
});
},
};
}
function createExecHandlerTestContext({ sessions, backgroundJobs }) {
const ctx = {
sessions,
backgroundJobs,
activeSessionSftpOps: new Map(),
closingTerminalSessions: new Map(),
activeSessionExecutions: new Map(),
activePtyExecs: new Map(),
crypto,
sftpBridge: {},
BACKGROUND_JOB_RETENTION_MS: 10 * 60 * 1000,
DEFAULT_BACKGROUND_JOB_POLL_INTERVAL_MS: 30 * 1000,
MAX_BACKGROUND_JOB_OUTPUT_CHARS: 256 * 1024,
SESSION_CLOSE_CLEANUP_TIMEOUT_MS: 20,
DEFAULT_BACKGROUND_JOB_TIMEOUT_MS: 60 * 60 * 1000,
commandTimeoutMs: 5000,
activeSftpOpSeq: 0,
debugLog() {},
getSessionMeta() {
return {};
},
checkCommandSafetyForShell() {
return { blocked: false };
},
resolveSessionBlocklistShellKind() {
return "";
},
beginChatExecution() {
return { ok: true, release() {} };
},
getFreshIdlePrompt() {
return "";
},
echoCommandToSession() {},
validateSessionScope() {
return null;
},
};
Object.assign(ctx, createBackgroundJobApi(ctx));
return ctx;
}
test("SFTP cancellation targets one terminal session and waits for cleanup", async () => {
const ctx = createExecHandlerTestContext({ sessions: new Map(), backgroundJobs: new Map() });
const events = [];
ctx.registerSftpOp("chat-1", "session-1", async () => {
await nextTick();
events.push("session-1-clean");
});
ctx.registerSftpOp("chat-2", "session-1", () => {
events.push("session-1-other-scope-clean");
});
ctx.registerSftpOp("chat-1", "session-2", () => {
events.push("session-2-clean");
});
await ctx.cancelSftpOpsForTerminalSession("session-1");
assert.deepEqual(events, ["session-1-other-scope-clean", "session-1-clean"]);
assert.equal(ctx.activeSessionSftpOps.size, 1);
});
test("terminal close does not hang on stalled SFTP cleanup", async () => {
const ctx = createExecHandlerTestContext({ sessions: new Map(), backgroundJobs: new Map() });
ctx.activeSessionSftpOps.set("sftp-stalled", {
chatSessionId: "chat-1",
sessionId: "session-1",
cancel: () => new Promise(() => {}),
});
const startedAt = Date.now();
await ctx.cancelSftpOpsForTerminalSession("session-1");
assert.ok(Date.now() - startedAt < 200, "session close cleanup should be bounded");
assert.equal(ctx.activeSessionSftpOps.size, 0);
});
test("SFTP operations that start while a terminal is closing are cancelled immediately", () => {
const ctx = createExecHandlerTestContext({ sessions: new Map(), backgroundJobs: new Map() });
let cancelled = false;
ctx.beginTerminalSessionClose("session-1");
ctx.registerSftpOp("chat-1", "session-1", () => {
cancelled = true;
});
assert.equal(cancelled, true);
assert.equal(ctx.activeSessionSftpOps.size, 0);
ctx.endTerminalSessionClose("session-1");
});
test("overlapping closes keep SFTP blocked until the last close finishes", () => {
const ctx = createExecHandlerTestContext({ sessions: new Map(), backgroundJobs: new Map() });
ctx.beginTerminalSessionClose("session-1");
ctx.beginTerminalSessionClose("session-1");
ctx.endTerminalSessionClose("session-1");
let cancelled = false;
ctx.registerSftpOp("chat-1", "session-1", () => {
cancelled = true;
});
assert.equal(cancelled, true);
ctx.endTerminalSessionClose("session-1");
ctx.registerSftpOp("chat-1", "session-1", () => {});
assert.equal(ctx.activeSessionSftpOps.size, 1);
});
test("terminal close cancels and removes background jobs for that terminal", async () => {
let cancelCount = 0;
let settleJob;
const resultPromise = new Promise((resolve) => {
settleJob = resolve;
});
const backgroundJobs = new Map([
["job-1", {
sessionId: "session-1",
status: "running",
handle: {
cancel: () => { cancelCount += 1; },
resultPromise,
},
}],
["job-2", { sessionId: "session-2", status: "running", handle: {} }],
]);
const ctx = createExecHandlerTestContext({ sessions: new Map(), backgroundJobs });
ctx.activeSessionExecutions.set("session-1", { kind: "job", token: "token-1" });
ctx.cancelBackgroundJobsForTerminalSession("session-1");
assert.equal(cancelCount, 1);
assert.equal(backgroundJobs.get("job-1").status, "stopping");
const settling = ctx.settleBackgroundJobsForTerminalSession("session-1");
assert.equal(backgroundJobs.has("job-1"), true);
settleJob({ error: "Cancelled" });
await settling;
assert.equal(backgroundJobs.has("job-1"), false);
assert.equal(backgroundJobs.has("job-2"), true);
assert.equal(ctx.activeSessionExecutions.has("session-1"), false);
});
test("MCP terminal_start chat cancel during shellKind probe aborts before PTY write", async () => {
const pty = new FakePty();
const deferred = createDeferredShellProbeConn();
const sessions = new Map([
["ssh-fish", {
protocol: "ssh",
stream: pty,
conn: deferred.conn,
}],
]);
const backgroundJobs = new Map();
const ctx = createExecHandlerTestContext({ sessions, backgroundJobs });
const api = createExecHandlerApi(ctx);
const pendingStart = api.handleJobStart({
sessionId: "ssh-fish",
command: "sleep 999",
chatSessionId: "chat-cancel-probe",
});
await nextTick();
assert.equal(backgroundJobs.size, 1, "job should be registered while probe is pending");
ctx.cancelBackgroundJobsForSession("chat-cancel-probe");
deferred.release();
const started = await pendingStart;
assert.equal(started.ok, false);
assert.equal(started.error, "Cancelled");
assert.equal(started.status, "cancelled");
assert.equal(
pty.writes.filter((entry) => entry.includes("__NCMCP_")).length,
0,
"cancelled pending start must not type a wrapper into the PTY",
);
const [job] = backgroundJobs.values();
assert.equal(job.status, "cancelled");
assert.equal(job.pendingShellProbe, false);
assert.equal(ctx.activeSessionExecutions.size, 0);
});
test("MCP exec probes an unclassified PowerShell session before the authoritative check", async () => {
const pty = new FakePty();
const session = {
protocol: "ssh",
stream: pty,
shellKind: "",
remoteSshVersion: "OpenSSH_for_Windows_9.5",
lastIdlePrompt: "custom% ",
_promptTrackTail: "custom prompt\r\ncustom% ",
_shellKindExecProbe: async () => (
"DefaultShell REG_SZ C:\\Program Files\\PowerShell\\7\\pwsh.exe\r\n"
),
};
const ctx = createExecHandlerTestContext({
sessions: new Map([["ssh-powershell", session]]),
backgroundJobs: new Map(),
});
ctx.checkCommandSafetyForShell = checkBlocklistForShell;
ctx.resolveSessionBlocklistShellKind = resolveSessionBlocklistShellKind;
ctx.execViaPty = async () => ({ ok: true, stdout: "ok", stderr: "", exitCode: 0 });
const api = createExecHandlerApi(ctx);
const result = await api.handleExec({
sessionId: "ssh-powershell",
command: 'Write-Host "now: $(Get-Date)"',
chatSessionId: "chat-powershell",
});
assert.equal(result.ok, true);
assert.equal(session._loginShellKind, "powershell");
});