[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:
179
electron/bridges/ai/codexHelpers.test.cjs
Normal file
179
electron/bridges/ai/codexHelpers.test.cjs
Normal file
@@ -0,0 +1,179 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const {
|
||||
MAX_CODEX_LOGIN_OUTPUT_CHARS,
|
||||
MAX_CODEX_LOGIN_OUTPUT_BYTES,
|
||||
MAX_CODEX_LOGIN_TERMINAL_SESSIONS,
|
||||
CODEX_LOGIN_KILL_GRACE_MS,
|
||||
appendCodexLoginOutput,
|
||||
createCodexLoginOutputDecoder,
|
||||
appendCodexChatGptValidationFailure,
|
||||
codexLoginSessions,
|
||||
extractCodexError,
|
||||
isCodexAuthError,
|
||||
normalizeCodexIntegrationState,
|
||||
recordCodexLoginSession,
|
||||
stopCodexLoginProcess,
|
||||
} = require("./codexHelpers.cjs");
|
||||
|
||||
test("Codex login output keeps a bounded tail", () => {
|
||||
const session = { output: "", url: null };
|
||||
|
||||
appendCodexLoginOutput(session, "a".repeat(MAX_CODEX_LOGIN_OUTPUT_CHARS));
|
||||
appendCodexLoginOutput(session, "tail-marker");
|
||||
|
||||
assert.equal(session.output.length, MAX_CODEX_LOGIN_OUTPUT_CHARS);
|
||||
assert.match(session.output, /tail-marker$/);
|
||||
});
|
||||
|
||||
test("Codex login output limit is measured in UTF-8 bytes without cutting characters", () => {
|
||||
const session = { output: "", url: null };
|
||||
appendCodexLoginOutput(session, "中".repeat(MAX_CODEX_LOGIN_OUTPUT_BYTES));
|
||||
|
||||
assert.ok(Buffer.byteLength(session.output, "utf8") <= MAX_CODEX_LOGIN_OUTPUT_BYTES);
|
||||
assert.doesNotMatch(session.output, /<2F>/u);
|
||||
assert.match(session.output, /^中+$/u);
|
||||
});
|
||||
|
||||
test("Codex login stdout and stderr decode split UTF-8 independently when interleaved", () => {
|
||||
const session = { output: "", url: null };
|
||||
const stdout = createCodexLoginOutputDecoder(session);
|
||||
const stderr = createCodexLoginOutputDecoder(session);
|
||||
const outBytes = Buffer.from("中文", "utf8");
|
||||
const errBytes = Buffer.from("错误", "utf8");
|
||||
stdout.write(outBytes.subarray(0, 2));
|
||||
stderr.write(errBytes.subarray(0, 1));
|
||||
stdout.write(outBytes.subarray(2));
|
||||
stderr.write(errBytes.subarray(1));
|
||||
stdout.end();
|
||||
stderr.end();
|
||||
|
||||
assert.equal(session.output, "中文错误");
|
||||
});
|
||||
|
||||
test("Codex login history retains only bounded terminal sessions", (t) => {
|
||||
t.after(() => codexLoginSessions.clear());
|
||||
codexLoginSessions.clear();
|
||||
recordCodexLoginSession({ id: "running", state: "running", process: { killed: false } });
|
||||
|
||||
for (let index = 0; index < MAX_CODEX_LOGIN_TERMINAL_SESSIONS + 3; index += 1) {
|
||||
recordCodexLoginSession({ id: `done-${index}`, state: "success", process: null });
|
||||
}
|
||||
|
||||
assert.equal(codexLoginSessions.has("running"), true);
|
||||
assert.equal(codexLoginSessions.has("done-0"), false);
|
||||
assert.equal(codexLoginSessions.size, MAX_CODEX_LOGIN_TERMINAL_SESSIONS + 1);
|
||||
});
|
||||
|
||||
test("a newly completed Codex login remains available while older records are pruned", (t) => {
|
||||
t.after(() => codexLoginSessions.clear());
|
||||
codexLoginSessions.clear();
|
||||
for (let index = 0; index < MAX_CODEX_LOGIN_TERMINAL_SESSIONS; index += 1) {
|
||||
recordCodexLoginSession({ id: `old-${index}`, state: "success", process: null });
|
||||
}
|
||||
const current = { id: "current", state: "running", process: { killed: false } };
|
||||
recordCodexLoginSession(current);
|
||||
|
||||
current.state = "success";
|
||||
current.process = null;
|
||||
recordCodexLoginSession(current);
|
||||
|
||||
assert.equal(codexLoginSessions.size, MAX_CODEX_LOGIN_TERMINAL_SESSIONS);
|
||||
assert.equal(codexLoginSessions.has("current"), true);
|
||||
assert.equal(codexLoginSessions.has("old-0"), false);
|
||||
});
|
||||
|
||||
test("live cancelled login processes stay tracked until they close", (t) => {
|
||||
t.after(() => codexLoginSessions.clear());
|
||||
codexLoginSessions.clear();
|
||||
for (let index = 0; index < MAX_CODEX_LOGIN_TERMINAL_SESSIONS + 2; index += 1) {
|
||||
recordCodexLoginSession({
|
||||
id: `cancelled-${index}`,
|
||||
state: "cancelled",
|
||||
process: { kill() {} },
|
||||
});
|
||||
}
|
||||
|
||||
assert.equal(codexLoginSessions.size, MAX_CODEX_LOGIN_TERMINAL_SESSIONS + 2);
|
||||
});
|
||||
|
||||
test("Codex login cancellation escalates from TERM to KILL", () => {
|
||||
const signals = [];
|
||||
const scheduled = [];
|
||||
let escalate;
|
||||
const session = {
|
||||
process: { kill: (signal) => signals.push(signal) },
|
||||
killTimer: null,
|
||||
};
|
||||
stopCodexLoginProcess(session, {
|
||||
setTimeoutFn: (callback, delay) => {
|
||||
scheduled.push(delay);
|
||||
escalate = callback;
|
||||
return { unref() {} };
|
||||
},
|
||||
});
|
||||
escalate();
|
||||
|
||||
assert.deepEqual(signals, ["SIGTERM", "SIGKILL"]);
|
||||
assert.deepEqual(scheduled, [CODEX_LOGIN_KILL_GRACE_MS]);
|
||||
});
|
||||
|
||||
test("normalizeCodexIntegrationState recognizes ChatGPT login status", () => {
|
||||
assert.equal(
|
||||
normalizeCodexIntegrationState("Logged in using ChatGPT"),
|
||||
"connected_chatgpt",
|
||||
);
|
||||
});
|
||||
|
||||
test("appendCodexChatGptValidationFailure preserves the login status output", () => {
|
||||
const output = appendCodexChatGptValidationFailure(
|
||||
"Logged in using ChatGPT",
|
||||
"SDK probe failed",
|
||||
);
|
||||
|
||||
assert.match(output, /Logged in using ChatGPT/);
|
||||
assert.match(output, /ChatGPT auth validation failed:/);
|
||||
assert.match(output, /SDK probe failed/);
|
||||
assert.equal(normalizeCodexIntegrationState(output), "connected_chatgpt");
|
||||
});
|
||||
|
||||
test("isCodexAuthError recognizes auth failures stored in error text", () => {
|
||||
assert.equal(
|
||||
isCodexAuthError({ ok: false, error: "401 Unauthorized: authentication required" }),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("extractCodexError preserves nested error object messages", () => {
|
||||
const normalized = extractCodexError({
|
||||
error: {
|
||||
code: "model_not_found",
|
||||
message: "Model gpt-test is not available",
|
||||
},
|
||||
});
|
||||
|
||||
assert.deepEqual(normalized, {
|
||||
message: "Model gpt-test is not available",
|
||||
code: "model_not_found",
|
||||
});
|
||||
});
|
||||
|
||||
test("extractCodexError stringifies unknown object errors instead of [object Object]", () => {
|
||||
const normalized = extractCodexError({
|
||||
status: 400,
|
||||
detail: "Bad request",
|
||||
});
|
||||
|
||||
assert.equal(normalized.message, '{"status":400,"detail":"Bad request"}');
|
||||
assert.equal(normalized.code, undefined);
|
||||
});
|
||||
|
||||
test("extractCodexError handles circular structured errors", () => {
|
||||
const error = { status: 500 };
|
||||
error.self = error;
|
||||
|
||||
const normalized = extractCodexError(error);
|
||||
|
||||
assert.equal(normalized.message, '{"status":500,"self":"[Circular]"}');
|
||||
});
|
||||
Reference in New Issue
Block a user