"use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const fs = require("fs"); const os = require("os"); const path = require("path"); const globalDictation = require("../electron/lib/global_dictation"); function fixture(state = {}) { const home = fs.mkdtempSync(path.join(os.tmpdir(), "td-global-dictation-")); const file = path.join(home, ".codex-global-state.json"); fs.writeFileSync(file, JSON.stringify(state, null, 2) + "\n"); return { home, file }; } test("disables persistent dictation bar without changing unrelated state", { concurrency: false }, () => { const fx = fixture({ globalDictationHotkey: "LeftCommand", "global-dictation-keep-visible": true, "project-order": ["/work/a"], nested: { enabled: true }, }); try { const result = globalDictation.disableKeepVisible({ codexHome: fx.home }); const saved = JSON.parse(fs.readFileSync(fx.file, "utf8")); const backup = JSON.parse(fs.readFileSync(fx.file + ".td-before-global-dictation.bak", "utf8")); assert.equal(result.ok, true); assert.equal(result.changed, true); assert.equal(saved["global-dictation-keep-visible"], false); assert.equal(saved.globalDictationHotkey, "LeftCommand"); assert.deepEqual(saved["project-order"], ["/work/a"]); assert.deepEqual(saved.nested, { enabled: true }); assert.equal(backup["global-dictation-keep-visible"], true); assert.equal(fs.readdirSync(fx.home).some((name) => name.endsWith(".td-dictation.tmp")), false); } finally { fs.rmSync(fx.home, { recursive: true, force: true }); } }); test("changes only the keep-visible token instead of rewriting the state file", { concurrency: false }, () => { const fx = fixture({ before: "保留", "global-dictation-keep-visible": true, after: { nested: 1 }, }); try { const original = fs.readFileSync(fx.file); const tokenOffset = original.indexOf(Buffer.from(" true")); const result = globalDictation.disableKeepVisible({ codexHome: fx.home }); const saved = fs.readFileSync(fx.file); assert.equal(result.ok, true); assert.equal(saved.subarray(0, tokenOffset).equals(original.subarray(0, tokenOffset)), true); assert.equal(saved.subarray(tokenOffset, tokenOffset + 5).toString("ascii"), "false"); assert.equal(saved.subarray(tokenOffset + 5).equals(original.subarray(tokenOffset + 5)), true); } finally { fs.rmSync(fx.home, { recursive: true, force: true }); } }); test("atomically rewrites minified state while preserving unrelated state", { concurrency: false }, () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "td-global-dictation-minified-")); const file = path.join(home, ".codex-global-state.json"); const original = Buffer.from('{"global-dictation-keep-visible":true,"untouched":1,"nested":{"enabled":true}}'); fs.writeFileSync(file, original); try { const result = globalDictation.disableKeepVisible({ codexHome: home }); const saved = JSON.parse(fs.readFileSync(file, "utf8")); assert.equal(result.ok, true); assert.equal(result.reason, "disabled-atomic"); assert.equal(saved["global-dictation-keep-visible"], false); assert.equal(saved.untouched, 1); assert.deepEqual(saved.nested, { enabled: true }); assert.equal(fs.readFileSync(file).equals(original), false); } finally { fs.rmSync(home, { recursive: true, force: true }); } }); test("persistent lock prevents an unsafe concurrent write", { concurrency: false }, () => { const fx = fixture({ "global-dictation-keep-visible": true, untouched: 1 }); const lock = fx.file + ".td-dictation.lock"; fs.writeFileSync(lock, "busy"); try { const result = globalDictation.disableKeepVisible({ codexHome: fx.home, lockWaitMs: 0 }); const saved = JSON.parse(fs.readFileSync(fx.file, "utf8")); assert.equal(result.ok, false); assert.equal(result.reason, "locked"); assert.equal(saved["global-dictation-keep-visible"], true); assert.equal(saved.untouched, 1); } finally { fs.rmSync(fx.home, { recursive: true, force: true }); } }); test("creates an explicit disabled state when the state file is absent", { concurrency: false }, () => { const home = fs.mkdtempSync(path.join(os.tmpdir(), "td-global-dictation-missing-")); const file = path.join(home, ".codex-global-state.json"); try { const result = globalDictation.disableKeepVisible({ codexHome: home }); const saved = JSON.parse(fs.readFileSync(file, "utf8")); assert.equal(result.ok, true); assert.equal(result.changed, true); assert.equal(result.reason, "created-disabled"); assert.deepEqual(saved, { "global-dictation-keep-visible": false }); } finally { fs.rmSync(home, { recursive: true, force: true }); } }); test("recognizes only the exact global dictation page route", () => { assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/index.html?initialRoute=/global-dictation", }), true); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/index.html?initialRoute=%2Fglobal-dictation", }), true); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/index.html?initialRoute=global-dictation", }), true); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/index.html", }), false); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/index.html?initialRoute=/avatar-overlay", }), false); assert.equal(globalDictation.isGlobalDictationTarget({ type: "worker", url: "app://-/index.html?initialRoute=/global-dictation", }), false); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "https://example.com/index.html?initialRoute=/global-dictation", }), false); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://other/index.html?initialRoute=/global-dictation", }), false); assert.equal(globalDictation.isGlobalDictationTarget({ type: "page", url: "app://-/other.html?initialRoute=/global-dictation", }), false); assert.equal(globalDictation.isCodexMainTarget({ type: "page", url: "app://-/index.html?theme=light", }), true); assert.equal(globalDictation.isCodexMainTarget({ type: "page", url: "app://-/index.html?initialRoute=/avatar-overlay", }), false); }); test("closes only global dictation targets through CDP HTTP", async () => { const calls = []; const initialTargets = [ { id: "main", type: "page", url: "app://-/index.html" }, { id: "avatar", type: "page", url: "app://-/index.html?initialRoute=/avatar-overlay" }, { id: "dictation", type: "page", url: "app://-/index.html?initialRoute=%2Fglobal-dictation" }, ]; const remainingTargets = initialTargets.filter((target) => target.id !== "dictation"); let listCount = 0; const fetchImpl = async (url) => { calls.push(url); if (url.endsWith("/json")) { const targets = listCount++ === 0 ? initialTargets : remainingTargets; return { ok: true, json: async () => targets }; } return { ok: true }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl }); assert.equal(result.ok, true); assert.equal(result.available, true); assert.equal(result.matched, 1); assert.equal(result.closed, 1); assert.equal(result.remaining, 0); assert.equal(result.status, "closed"); assert.deepEqual(calls, [ "http://127.0.0.1:19222/json", "http://127.0.0.1:19222/json/close/dictation", "http://127.0.0.1:19222/json", ]); }); test("CDP unavailable is reported as unavailable instead of success", async () => { const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl: async () => { throw new Error("connection refused"); }, }); assert.equal(result.ok, false); assert.equal(result.available, false); assert.equal(result.status, "port-unavailable"); assert.equal(result.matched, 0); assert.equal(result.closed, 0); assert.equal(result.remaining, null); }); test("does not close a matching route on a foreign debug endpoint", async () => { const calls = []; const fetchImpl = async (url) => { calls.push(url); return { ok: true, json: async () => [{ id: "foreign-dictation", type: "page", url: "app://-/index.html?initialRoute=/global-dictation", }], }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, endpointOwnerVerifier: async () => ({ verified: true, owned: false, reason: "other app" }), }); assert.equal(result.ok, false); assert.equal(result.status, "foreign-endpoint"); assert.equal(result.closed, 0); assert.deepEqual(calls, ["http://127.0.0.1:19222/json"]); }); test("closes an orphaned dictation target only when the port owner is Codex", async () => { const calls = []; let listCount = 0; const fetchImpl = async (url) => { calls.push(url); if (url.endsWith("/json")) { const targets = listCount++ === 0 ? [{ id: "orphan", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }] : []; return { ok: true, json: async () => targets }; } return { ok: true }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, endpointOwnerVerifier: async () => ({ verified: true, owned: true, reason: "Codex" }), }); assert.equal(result.ok, true); assert.equal(result.status, "closed"); assert.equal(result.closed, 1); assert.deepEqual(calls, [ "http://127.0.0.1:19222/json", "http://127.0.0.1:19222/json/close/orphan", "http://127.0.0.1:19222/json", ]); }); test("aborts a slow orphan owner check at the shared deadline", async () => { let verifierSignal; const fetchImpl = async (url) => { assert.equal(url, "http://127.0.0.1:19222/json"); return { ok: true, json: async () => [{ id: "orphan", type: "page", url: "app://-/index.html?initialRoute=/global-dictation", }], }; }; const endpointOwnerVerifier = async (_port, options) => { verifierSignal = options.signal; await new Promise((resolve) => { if (options.signal.aborted) resolve(); else options.signal.addEventListener("abort", resolve, { once: true }); }); return { verified: false, owned: false, reason: "timed out" }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, endpointOwnerVerifier, ownerTimeoutMs: 20, }); assert.equal(result.ok, false); assert.equal(result.status, "unverified-endpoint"); assert.equal(verifierSignal.aborted, true); }); test("reports when keep-visible immediately recreates the target", async () => { const initialTargets = [ { id: "main", type: "page", url: "app://-/index.html" }, { id: "dictation-a", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }, ]; const recreatedTargets = [ initialTargets[0], { id: "dictation-b", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }, ]; let listCount = 0; const fetchImpl = async (url) => { if (url.endsWith("/json")) { return { ok: true, json: async () => listCount++ === 0 ? initialTargets : recreatedTargets, }; } return { ok: true }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, retries: 0, settleMs: 0, }); assert.equal(result.ok, false); assert.equal(result.status, "still-present"); assert.equal(result.closed, 1); assert.equal(result.remaining, 1); assert.deepEqual(result.targetIds, ["dictation-b"]); }); test("retries when Codex immediately recreates the dictation target", async () => { const main = { id: "main", type: "page", url: "app://-/index.html" }; const targetA = { id: "dictation-a", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }; const targetB = { id: "dictation-b", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }; const targetLists = [ [main, targetA], [main, targetB], [main, targetB], [main], ]; let listCount = 0; const closed = []; const fetchImpl = async (url) => { if (url.endsWith("/json")) { const targets = targetLists[Math.min(listCount, targetLists.length - 1)]; listCount += 1; return { ok: true, json: async () => targets }; } closed.push(url.split("/").pop()); return { ok: true }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, settleMs: 0, retryDelayMs: 0, }); assert.equal(result.ok, true); assert.equal(result.status, "closed"); assert.equal(result.closed, 2); assert.equal(result.attempts, 2); assert.deepEqual(closed, ["dictation-a", "dictation-b"]); }); test("target response body parsing cannot outlive the configured timeout", async () => { const result = await globalDictation.closeGlobalDictationTargets(19222, { timeoutMs: 20, fetchImpl: async () => ({ ok: true, json: async () => new Promise(() => {}), }), }); assert.equal(result.ok, false); assert.equal(result.available, false); assert.equal(result.status, "timeout"); assert.equal(result.closed, 0); }); test("failed post-close verification is not reported as success", async () => { const initialTargets = [ { id: "main", type: "page", url: "app://-/index.html" }, { id: "dictation", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }, ]; let listCount = 0; const fetchImpl = async (url) => { if (url.endsWith("/json")) { listCount += 1; if (listCount > 1) throw new Error("verification endpoint disconnected"); return { ok: true, json: async () => initialTargets }; } return { ok: true }; }; const result = await globalDictation.closeGlobalDictationTargets(19222, { fetchImpl }); assert.equal(result.ok, false); assert.equal(result.status, "verification-failed"); assert.equal(result.closed, 1); assert.equal(result.remaining, null); assert.match(result.errors.join(" "), /verification endpoint disconnected/); }); test("custom cleanup calls do not share stale targets or abort signals", async () => { const firstTargets = [ { id: "main-a", type: "page", url: "app://-/index.html" }, { id: "dictation-a", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }, ]; const secondTargets = [ { id: "main-b", type: "page", url: "app://-/index.html" }, { id: "dictation-b", type: "page", url: "app://-/index.html?initialRoute=/global-dictation" }, ]; const closed = []; const fetchImpl = async (url) => { if (url.includes("/json/close/")) { closed.push(url.split("/").pop()); return { ok: true }; } return { ok: true, json: async () => [] }; }; const first = globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, targets: firstTargets, }); const second = globalDictation.closeGlobalDictationTargets(19222, { fetchImpl, targets: secondTargets, }); assert.notEqual(first, second); await Promise.all([first, second]); assert.deepEqual(closed.sort(), ["dictation-a", "dictation-b"]); });