From fb195b364e1ca6a7e41b866e0017814f36c604e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Thu, 9 Jul 2026 15:32:15 -0300 Subject: [PATCH 1/4] fix: preserve upstream Electron entrypoint --- electron-builder.config.mjs | 3 --- test/electron-builder-config.test.mjs | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 test/electron-builder-config.test.mjs diff --git a/electron-builder.config.mjs b/electron-builder.config.mjs index f4a3843..6ec7797 100644 --- a/electron-builder.config.mjs +++ b/electron-builder.config.mjs @@ -30,9 +30,6 @@ export default { electronVersion, npmRebuild: false, buildDependenciesFromSource: false, - extraMetadata: { - main: ".vite/build/bootstrap.js" - }, asar: true, files: [ { diff --git a/test/electron-builder-config.test.mjs b/test/electron-builder-config.test.mjs new file mode 100644 index 0000000..8ceaf8b --- /dev/null +++ b/test/electron-builder-config.test.mjs @@ -0,0 +1,16 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +test("electron-builder preserves the upstream package entry point", async () => { + const config = await fs.readFile( + path.join(__dirname, "..", "electron-builder.config.mjs"), + "utf8" + ); + + assert.doesNotMatch(config, /extraMetadata\s*:\s*\{\s*main\s*:/); +}); From 89cd5cbad191e0bb7970c922bb8a0114ec4d0497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Thu, 9 Jul 2026 15:38:57 -0300 Subject: [PATCH 2/4] fix: omit macOS code mode host from Linux resources --- scripts/lib/build.mjs | 1 + test/build.test.mjs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/lib/build.mjs b/scripts/lib/build.mjs index ae71b4a..5dfc949 100644 --- a/scripts/lib/build.mjs +++ b/scripts/lib/build.mjs @@ -23,6 +23,7 @@ const skippedLinuxResourceNames = new Set([ "app.asar", "app.asar.unpacked", "codex", + "codex-code-mode-host", "codex_chronicle", "cua_node", "native", diff --git a/test/build.test.mjs b/test/build.test.mjs index ce38a76..a3f7b36 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -91,6 +91,7 @@ test("stagePackagedResources preserves Linux-safe upstream resources", async () "mach-o-arm64" ); await fs.writeFile(path.join(resourcesDir, "codex"), "darwin-codex"); + await fs.writeFile(path.join(resourcesDir, "codex-code-mode-host"), "darwin-code-mode-host"); await fs.mkdir(path.join(resourcesDir, "cua_node", "bin"), { recursive: true }); await fs.writeFile(path.join(resourcesDir, "cua_node", "bin", "node_repl"), "darwin-node-repl"); await fs.writeFile(path.join(resourcesDir, "node"), "darwin-node"); @@ -104,6 +105,7 @@ test("stagePackagedResources preserves Linux-safe upstream resources", async () await assert.rejects(fs.access(path.join(targetDir, "app.asar"))); await assert.rejects(fs.access(path.join(targetDir, "app.asar.unpacked"))); await assert.rejects(fs.access(path.join(targetDir, "codex"))); + await assert.rejects(fs.access(path.join(targetDir, "codex-code-mode-host"))); await assert.rejects(fs.access(path.join(targetDir, "cua_node"))); await assert.rejects(fs.access(path.join(targetDir, "node"))); await assert.rejects(fs.access(path.join(targetDir, "rg"))); From a000c92a52d655b1df7585b5d27784f15af67a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Fri, 10 Jul 2026 08:15:14 -0300 Subject: [PATCH 3/4] chore: update bundled Codex CLI to 0.144.1 --- scripts/lib/build.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/lib/build.mjs b/scripts/lib/build.mjs index 5dfc949..a7307bc 100644 --- a/scripts/lib/build.mjs +++ b/scripts/lib/build.mjs @@ -45,10 +45,10 @@ const primaryRuntime = { const codexCliRuntime = { url: process.env.CODEX_CLI_RUNTIME_URL || - "https://github.com/openai/codex/releases/download/rust-v0.140.0/codex-package-x86_64-unknown-linux-musl.tar.gz", + "https://github.com/openai/codex/releases/download/rust-v0.144.1/codex-package-x86_64-unknown-linux-musl.tar.gz", sha256: process.env.CODEX_CLI_RUNTIME_SHA256 || - "9620e798900c6fb289199a9e0a8ed0c3a8cb7e3561048498ebc2dac354a1627b", + "3fd50cf96809b1eea294bbfba0a5c3a576871b4876a1f0e91226e520c1923be1", codexEntry: "bin/codex" }; From 8a4d1470475d36ffcd442f53884c9602e3b22ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas?= Date: Fri, 10 Jul 2026 09:40:10 -0300 Subject: [PATCH 4/4] fix: emit canonical dynamic tool schemas --- scripts/lib/upstream-patches.mjs | 12 ++++---- test/upstream-patches.test.mjs | 51 +++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/scripts/lib/upstream-patches.mjs b/scripts/lib/upstream-patches.mjs index 6577a2e..d3f7c41 100644 --- a/scripts/lib/upstream-patches.mjs +++ b/scripts/lib/upstream-patches.mjs @@ -568,7 +568,7 @@ function patchDynamicToolStartResponse(source, patch = findDynamicToolStartRespo const helper = source.includes(dynamicToolStartResponseMarker) ? "" : [ - `function ${dynamicToolStartResponseMarker}(e){if(!Array.isArray(e))return[];return e.map(e=>{if(e?.type!==\`namespace\`||!Array.isArray(e.tools))return e;let t=e.tools.flatMap(e=>{if(e?.type!==\`function\`)return[e];if(e.inputSchema!=null)return[e];if(e.input_schema!=null)return[{...e,inputSchema:e.input_schema}];if(e.parameters!=null)return[{...e,inputSchema:e.parameters}];return[]});return{...e,tools:t}})}` + `function ${dynamicToolStartResponseMarker}(e){return __codexLinuxCanonicalDynamicTools(e)}function __codexLinuxCanonicalDynamicToolFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters;if(t==null)return null;let n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}function __codexLinuxCanonicalDynamicTools(e){if(!Array.isArray(e))return[];let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalDynamicToolFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let n of e)r(n);return t}` ].join(""); const replacements = patch.patches.map(target => { const argumentSource = source.slice(target.argumentStart, target.argumentEnd); @@ -675,9 +675,9 @@ function patchDynamicToolThreadStartBridge(source, patch = findDynamicToolThread const helper = source.includes(dynamicToolThreadStartBridgeMarker) ? "" : [ - `function ${dynamicToolThreadStartBridgeMarker}(e){if(e?.method!==\`thread/start\`||e.params==null)return e;let t=__codexLinuxNormalizeThreadStartBridgeValue(e.params);return{...e,params:{...t,...Array.isArray(t.dynamicTools)?{dynamicTools:t.dynamicTools.flatMap(e=>__codexLinuxNormalizeThreadStartBridgeDynamicTool(e))}:{}}}}`, - `function __codexLinuxNormalizeThreadStartBridgeValue(e){if(Array.isArray(e))return e.map(__codexLinuxNormalizeThreadStartBridgeValue);if(e==null||typeof e!==\`object\`)return e;let t={...e};for(let n of Object.keys(t))t[n]=__codexLinuxNormalizeThreadStartBridgeValue(t[n]);if(t.inputSchema==null){if(t.input_schema!=null)t.inputSchema=t.input_schema;else if(t.parameters!=null)t.inputSchema=t.parameters;else if(t.type===\`function\`)t.inputSchema={type:\`object\`,properties:{},additionalProperties:!1}}return t}`, - `function __codexLinuxNormalizeThreadStartBridgeDynamicTool(e,t=null){e=__codexLinuxNormalizeThreadStartBridgeValue(e);if(e==null||typeof e!==\`object\`)return[];if(e.type===\`namespace\`&&Array.isArray(e.tools)){let n=typeof e.name===\`string\`?e.name:t;return e.tools.flatMap(e=>__codexLinuxNormalizeThreadStartBridgeDynamicTool(e,n))}let n=t==null?e:{...e,namespace:e.namespace??t};if(n.type==null&&typeof n.name===\`string\`)return[{...n,type:\`function\`,inputSchema:n.inputSchema??{type:\`object\`,properties:{},additionalProperties:!1}}];return n.type===\`function\`?[n]:[]}` + `function ${dynamicToolThreadStartBridgeMarker}(e){if(e?.method!==\`thread/start\`||e.params==null||!Array.isArray(e.params.dynamicTools))return e;return{...e,params:{...e.params,dynamicTools:__codexLinuxCanonicalThreadStartBridgeDynamicTools(e.params.dynamicTools)}}}`, + `function __codexLinuxCanonicalThreadStartBridgeFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters??{type:\`object\`,properties:{},additionalProperties:!1},n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}`, + `function __codexLinuxCanonicalThreadStartBridgeDynamicTools(e){let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalThreadStartBridgeFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let t of e)r(t);return t}` ].join(""); const replacements = patch.patches.map(target => { const requestSource = source.slice(target.requestStart, target.requestEnd); @@ -824,7 +824,7 @@ function patchDynamicToolThreadStartRequest(source, patch = findDynamicToolThrea const helper = source.includes(dynamicToolThreadStartRequestMarker) ? "" : [ - `function ${dynamicToolThreadStartRequestMarker}(e){if(e==null||!Array.isArray(e.dynamicTools))return e;return{...e,dynamicTools:e.dynamicTools.flatMap(e=>__codexLinuxNormalizeThreadStartRequestDynamicTool(e))}}function __codexLinuxNormalizeThreadStartRequestDynamicTool(e,t=null){if(e==null||typeof e!==\`object\`)return[];if(e.type===\`namespace\`&&Array.isArray(e.tools)){let n=typeof e.name===\`string\`?e.name:t;return e.tools.flatMap(e=>__codexLinuxNormalizeThreadStartRequestDynamicTool(e,n))}let n=t==null?e:{...e,namespace:e.namespace??t};if(n.type==null&&typeof n.name===\`string\`)return[{...n,type:\`function\`,inputSchema:n.inputSchema??n.input_schema??n.parameters??{type:\`object\`,properties:{},additionalProperties:!1}}];if(n.type!==\`function\`)return[];if(n.inputSchema!=null)return[n];if(n.input_schema!=null)return[{...n,inputSchema:n.input_schema}];if(n.parameters!=null)return[{...n,inputSchema:n.parameters}];return[{...n,inputSchema:{type:\`object\`,properties:{},additionalProperties:!1}}]}` + `function ${dynamicToolThreadStartRequestMarker}(e){if(e==null||!Array.isArray(e.dynamicTools))return e;return{...e,dynamicTools:__codexLinuxCanonicalThreadStartRequestDynamicTools(e.dynamicTools)}}function __codexLinuxCanonicalThreadStartRequestFunction(e){if(e==null||typeof e!==\`object\`||typeof e.name!==\`string\`||e.type!=null&&e.type!==\`function\`)return null;let t=e.inputSchema??e.input_schema??e.parameters??{type:\`object\`,properties:{},additionalProperties:!1},n=e.deferLoading??(e.exposeToContext==null?!1:!e.exposeToContext);return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...n?{deferLoading:!0}:{}}}function __codexLinuxCanonicalThreadStartRequestDynamicTools(e){let t=[],n=new Map,r=(e,o=null,i=\`\`)=>{if(e?.type===\`namespace\`&&Array.isArray(e.tools)){let a=typeof e.name===\`string\`?e.name:o;if(a==null)return;for(let t of e.tools)r(t,a,typeof e.description===\`string\`?e.description:\`\`);return}let a=__codexLinuxCanonicalThreadStartRequestFunction(e);if(a==null)return;let s=o??(typeof e.namespace===\`string\`?e.namespace:null);if(s==null){t.push(a);return}let l=n.get(s);if(l==null){l={type:\`namespace\`,name:s,description:i,tools:[]},n.set(s,l),t.push(l)}l.tools.push(a)};for(let t of e)r(t);return t}` ].join(""); const replacements = patch.patches.map(target => { const paramsSource = source.slice(target.paramsStart, target.paramsEnd); @@ -993,7 +993,7 @@ function patchDynamicToolSchemaContract(source, patch = findDynamicToolSchemaCon const helper = source.includes(dynamicToolSchemaContractMarker) ? "" : [ - `function ${dynamicToolSchemaContractMarker}(e){if(e==null||e.type!==\`function\`)return e;if(e.inputSchema!=null)return e;if(e.input_schema!=null)return{...e,inputSchema:e.input_schema};if(e.parameters!=null)return{...e,inputSchema:e.parameters};return null}` + `function ${dynamicToolSchemaContractMarker}(e){if(e==null||e.type!==\`function\`||typeof e.name!==\`string\`)return null;let t=e.inputSchema??e.input_schema??e.parameters;if(t==null)return null;return{type:\`function\`,name:e.name,description:typeof e.description===\`string\`?e.description:\`\`,inputSchema:t,...e.deferLoading?{deferLoading:!0}:{}}}` ].join(""); const replacements = []; diff --git a/test/upstream-patches.test.mjs b/test/upstream-patches.test.mjs index c25dd24..ae5c307 100644 --- a/test/upstream-patches.test.mjs +++ b/test/upstream-patches.test.mjs @@ -30,6 +30,25 @@ const openTargetResolverSource = "function W(e){let t=which.default.sync(e,{nothrow:!0});return typeof t==`string`&&fs.existsSync(t)?t:null}"; const withOpenTargetResolver = parts => [openTargetResolverSource, ...parts].join(";"); +function assertCanonicalDynamicTools(tools) { + for (const tool of tools) { + assert.equal(typeof tool.description, "string"); + assert.equal("namespace" in tool, false); + assert.equal("exposeToContext" in tool, false); + assert.equal("input_schema" in tool, false); + assert.equal("parameters" in tool, false); + + if (tool.type === "namespace") { + assert.equal(Array.isArray(tool.tools), true); + assertCanonicalDynamicTools(tool.tools); + continue; + } + + assert.equal(tool.type, "function"); + assert.equal(tool.inputSchema?.type, "object"); + } +} + test("patchLinuxOpenTargetsSource adds Linux editor targets and exposes app paths", () => { const source = withOpenTargetResolver([ "prefix", @@ -461,11 +480,15 @@ test("patchDynamicToolStartResponseSource normalizes desktop thread-start dynami vm.runInNewContext(patched, context); - const tools = context.globalThis.result[0].tools; + const dynamicTools = context.globalThis.result; + const tools = dynamicTools[0].tools; assert.equal(hasUnguardedDynamicToolStartResponseSource(patched), false); - assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params", "untouched"])); + assert.equal(dynamicTools[0].type, "namespace"); + assert.equal(dynamicTools[0].name, "codex_app"); + assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params"])); assert.equal(tools.filter(tool => tool.type === "function").every(tool => tool.inputSchema?.type === "object"), true); + assertCanonicalDynamicTools(dynamicTools); }); test("patchDynamicToolStartResponseSource is idempotent", () => { @@ -497,15 +520,14 @@ test("patchDynamicToolThreadStartBridgeSource normalizes final Electron bridge r await vm.runInNewContext(`(async()=>{${patched}})()`, context); const [normal, prewarm] = context.globalThis.bridge.requests; - const tools = normal.params.dynamicTools; + const dynamicTools = normal.params.dynamicTools; + const tools = dynamicTools[0].tools; assert.equal(hasUnguardedDynamicToolThreadStartBridgeSource(patched), false); - assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "plainSnake", "plainParams", "bad", "top", "topBad"])); - assert.equal(tools.every(tool => tool.type === "function"), true); - assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true); - assert.equal(tools.slice(0, 4).every(tool => tool.namespace === "codex_app"), true); - assert.equal(tools.slice(4).every(tool => tool.namespace == null), true); - assert.deepEqual(prewarm.params.dynamicTools, tools); + assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "plainSnake", "plainParams", "bad"])); + assert.equal(JSON.stringify(dynamicTools.slice(1).map(tool => tool.name)), JSON.stringify(["top", "topBad"])); + assertCanonicalDynamicTools(dynamicTools); + assert.deepEqual(prewarm.params.dynamicTools, dynamicTools); }); test("patchDynamicToolThreadStartBridgeSource is idempotent", () => { @@ -539,12 +561,14 @@ test("patchDynamicToolSchemaContractSource normalizes thread-start dynamic tool await vm.runInNewContext(`(async()=>{${patched}})()`, context); - const tools = context.globalThis.result[0].tools; + const dynamicTools = context.globalThis.result; + const tools = dynamicTools[0].tools; assert.equal(hasUnguardedDynamicToolSchemaContractSource(patched), false); assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "parameters"])); assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true); assert.equal(tools.every(tool => tool.deferLoading === true), true); + assertCanonicalDynamicTools(dynamicTools); }); test("patchDynamicToolThreadStartRequestSource normalizes final thread/start params", () => { @@ -563,13 +587,12 @@ test("patchDynamicToolThreadStartRequestSource normalizes final thread/start par vm.runInNewContext(patched, context); - const tools = context.globalThis.sent.payload.request.params.dynamicTools; + const dynamicTools = context.globalThis.sent.payload.request.params.dynamicTools; + const tools = dynamicTools[0].tools; assert.equal(hasUnguardedDynamicToolThreadStartRequestSource(patched), false); assert.equal(JSON.stringify(tools.map(tool => tool.name)), JSON.stringify(["good", "snake", "params", "bad"])); - assert.equal(tools.every(tool => tool.type === "function"), true); - assert.equal(tools.every(tool => tool.inputSchema?.type === "object"), true); - assert.equal(tools.every(tool => tool.namespace === "codex_app"), true); + assertCanonicalDynamicTools(dynamicTools); }); test("patchDynamicToolThreadStartRequestSource is idempotent", () => {