[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:
@@ -0,0 +1,476 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
const {
|
||||
createTerminalInterceptorEnvelope,
|
||||
} = require("../terminalInterceptorTransport.cjs");
|
||||
|
||||
class FakePort {
|
||||
constructor() {
|
||||
this.listeners = new Set();
|
||||
this.messages = [];
|
||||
this.closed = false;
|
||||
}
|
||||
|
||||
addEventListener(type, listener) {
|
||||
if (type === "message") this.listeners.add(listener);
|
||||
}
|
||||
|
||||
removeEventListener(type, listener) {
|
||||
if (type === "message") this.listeners.delete(listener);
|
||||
}
|
||||
|
||||
postMessage(message, transfer = []) {
|
||||
this.messages.push({ message, transfer });
|
||||
}
|
||||
|
||||
start() {}
|
||||
|
||||
close() { this.closed = true; }
|
||||
|
||||
emit(data, ports = []) {
|
||||
for (const listener of this.listeners) listener({ data, ports });
|
||||
}
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
}
|
||||
|
||||
test("utility runtime dispatches dedicated terminal ports to the exact registered interceptor", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({
|
||||
default: {
|
||||
activate(context) {
|
||||
context.providers.register(
|
||||
"com.example.input",
|
||||
"terminal.interceptor.input",
|
||||
({ data }) => Uint8Array.from([...data].map((byte) => byte >= 97 && byte <= 122 ? byte - 32 : byte)),
|
||||
);
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "com.example.input",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1", protocol: "ssh", status: "connected" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
assert.equal(
|
||||
control.messages.some(({ message }) => (
|
||||
message.jsonrpc === "2.0"
|
||||
&& message.id === 3
|
||||
&& message.result?.accepted === true
|
||||
)),
|
||||
true,
|
||||
);
|
||||
dataPort.emit(createTerminalInterceptorEnvelope({
|
||||
type: "netcatty:terminal-interceptor:ready",
|
||||
sessionId: "session-1",
|
||||
direction: "input",
|
||||
windowBytes: 64 * 1024,
|
||||
}));
|
||||
assert.equal(dataPort.closed, false);
|
||||
const data = Uint8Array.from(Buffer.from("hello")).buffer;
|
||||
dataPort.emit(createTerminalInterceptorEnvelope({
|
||||
type: "netcatty:terminal-interceptor:chunk",
|
||||
sequence: 1,
|
||||
direction: "input",
|
||||
creditBytes: 64 * 1024,
|
||||
byteLength: data.byteLength,
|
||||
}, data));
|
||||
await tick();
|
||||
assert.equal(dataPort.messages.length, 1);
|
||||
const result = createTerminalInterceptorEnvelope(
|
||||
dataPort.messages[0].message.frame,
|
||||
dataPort.messages[0].message.transfer,
|
||||
);
|
||||
assert.equal(result.frame.status, "ok");
|
||||
assert.equal(result.frame.creditBytes, 5);
|
||||
assert.equal(Buffer.from(result.transfer).toString("utf8"), "HELLO");
|
||||
assert.deepEqual(dataPort.messages[0].transfer, [result.transfer]);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("utility runtime closes a terminal port when provider ownership or kind is invalid", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({ default: { activate() {} } }),
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "com.example.missing",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
assert.equal(dataPort.closed, true);
|
||||
assert.equal(
|
||||
control.messages.some(({ message }) => (
|
||||
message.jsonrpc === "2.0"
|
||||
&& message.id === 3
|
||||
&& message.error?.code === -32009
|
||||
)),
|
||||
true,
|
||||
);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("utility runtime closes the transferred terminal port when the provider belongs to another plugin", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({ default: { activate() {} } }),
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "other.plugin.input",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1", protocol: "ssh", status: "connected" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
assert.equal(dataPort.closed, true);
|
||||
assert.equal(
|
||||
control.messages.some(({ message }) => (
|
||||
message.jsonrpc === "2.0"
|
||||
&& message.id === 3
|
||||
&& message.error?.code === -32003
|
||||
)),
|
||||
true,
|
||||
);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("utility runtime closes unexpected transferred ports on every lifecycle request", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({ default: { activate() {} } }),
|
||||
});
|
||||
const lifecycleRequests = [
|
||||
{ id: 1, method: "plugin.initialize", params: {} },
|
||||
{ id: 2, method: "plugin.activate", params: {} },
|
||||
{ id: 3, method: "plugin.deactivate", params: {} },
|
||||
];
|
||||
for (const request of lifecycleRequests) {
|
||||
const unexpectedPort = new FakePort();
|
||||
control.emit({ jsonrpc: "2.0", ...request }, [unexpectedPort]);
|
||||
await tick();
|
||||
assert.equal(unexpectedPort.closed, true, `${request.method} retained an unexpected port`);
|
||||
assert.equal(
|
||||
control.messages.some(({ message }) => (
|
||||
message.jsonrpc === "2.0"
|
||||
&& message.id === request.id
|
||||
&& Object.hasOwn(message, "result")
|
||||
)),
|
||||
true,
|
||||
`${request.method} did not complete after closing its unexpected port`,
|
||||
);
|
||||
}
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("utility runtime rejects the retired private terminal attachment protocol", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({ default: { activate() {} } }),
|
||||
});
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
type: "netcatty-plugin:terminal-interceptor:attach",
|
||||
attachmentId: 1,
|
||||
descriptor: { providerId: "com.example.input", direction: "input" },
|
||||
}, [dataPort]);
|
||||
assert.equal(dataPort.closed, true);
|
||||
assert.equal(control.messages.length, 0);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("terminal ports convert synchronous throws to failures and stop using disposed handlers", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
let registration;
|
||||
let calls = 0;
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({
|
||||
default: {
|
||||
activate(context) {
|
||||
registration = context.providers.register(
|
||||
"com.example.input",
|
||||
"terminal.interceptor.input",
|
||||
() => {
|
||||
calls += 1;
|
||||
throw new Error("synchronous failure");
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "com.example.input",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1", protocol: "ssh", status: "connected" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
const send = (sequence) => {
|
||||
const data = Uint8Array.from([sequence]).buffer;
|
||||
dataPort.emit(createTerminalInterceptorEnvelope({
|
||||
type: "netcatty:terminal-interceptor:chunk",
|
||||
sequence,
|
||||
direction: "input",
|
||||
creditBytes: 64 * 1024,
|
||||
byteLength: data.byteLength,
|
||||
}, data));
|
||||
};
|
||||
send(1);
|
||||
await tick();
|
||||
assert.equal(dataPort.messages[0].message.frame.status, "failed");
|
||||
assert.equal(calls, 1);
|
||||
|
||||
registration.dispose();
|
||||
send(2);
|
||||
await tick();
|
||||
assert.equal(dataPort.messages[1].message.frame.status, "failed");
|
||||
assert.equal(calls, 1);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("the utility peer closes a port whose chunk bypasses the canonical terminal frame schema", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({
|
||||
default: {
|
||||
activate(context) {
|
||||
context.providers.register(
|
||||
"com.example.input",
|
||||
"terminal.interceptor.input",
|
||||
({ data }) => data,
|
||||
);
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "com.example.input",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1", protocol: "ssh", status: "connected" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
const data = Uint8Array.from(Buffer.from("unsafe")).buffer;
|
||||
dataPort.emit({
|
||||
frame: {
|
||||
type: "netcatty:terminal-interceptor:chunk",
|
||||
sequence: 1,
|
||||
direction: "input",
|
||||
creditBytes: 64 * 1024,
|
||||
byteLength: data.byteLength,
|
||||
extra: true,
|
||||
},
|
||||
transfer: data,
|
||||
});
|
||||
assert.equal(dataPort.closed, true);
|
||||
assert.equal(dataPort.messages.length, 0);
|
||||
await runtime.dispose();
|
||||
});
|
||||
|
||||
test("utility runtime disposal closes a terminal port while its transport helper is loading", async () => {
|
||||
const { startPluginRuntime } = await import("./runtimePeer.mjs");
|
||||
const control = new FakePort();
|
||||
let resolveTransport;
|
||||
const transportLoading = new Promise((resolve) => { resolveTransport = resolve; });
|
||||
const runtime = await startPluginRuntime({
|
||||
port: control,
|
||||
config: {
|
||||
pluginId: "com.example",
|
||||
pluginVersion: "1.0.0",
|
||||
netcattyVersion: "1.0.0",
|
||||
apiVersion: "1.0.0",
|
||||
enabledFeatures: [],
|
||||
environment: {},
|
||||
entryUrl: "file:///plugin.js",
|
||||
},
|
||||
loadPlugin: async () => ({
|
||||
default: {
|
||||
activate(context) {
|
||||
context.providers.register(
|
||||
"com.example.input",
|
||||
"terminal.interceptor.input",
|
||||
({ data }) => data,
|
||||
);
|
||||
},
|
||||
},
|
||||
}),
|
||||
loadTerminalInterceptorTransport: () => transportLoading,
|
||||
});
|
||||
control.emit({ jsonrpc: "2.0", id: 1, method: "plugin.initialize", params: {} });
|
||||
await tick();
|
||||
control.emit({ jsonrpc: "2.0", id: 2, method: "plugin.activate", params: {} });
|
||||
await tick();
|
||||
|
||||
const dataPort = new FakePort();
|
||||
control.emit({
|
||||
jsonrpc: "2.0",
|
||||
id: 3,
|
||||
method: "plugin.terminal.interceptor.attach",
|
||||
params: {
|
||||
descriptor: {
|
||||
providerId: "com.example.input",
|
||||
direction: "input",
|
||||
session: { sessionId: "session-1", protocol: "ssh", status: "connected" },
|
||||
},
|
||||
},
|
||||
}, [dataPort]);
|
||||
await tick();
|
||||
assert.equal(dataPort.closed, false);
|
||||
|
||||
await runtime.dispose();
|
||||
assert.equal(dataPort.closed, true);
|
||||
resolveTransport({
|
||||
TERMINAL_INTERCEPTOR_MAX_CHUNK_BYTES: 64 * 1024,
|
||||
createTerminalInterceptorEnvelope,
|
||||
});
|
||||
await tick();
|
||||
assert.equal(
|
||||
control.messages.some(({ message }) => message.id === 3 && message.result?.accepted === true),
|
||||
false,
|
||||
);
|
||||
assert.equal(dataPort.closed, true);
|
||||
});
|
||||
Reference in New Issue
Block a user