From 0931b76cfdcc651cfd0ce17960336db94b2679d5 Mon Sep 17 00:00:00 2001 From: Mohit Sahoo Date: Sun, 24 May 2026 09:42:32 +0530 Subject: [PATCH] fix: honor multi-time automation RRULEs - Add a core extracted-app patch for automation schedule helpers - Expand weekly and daily RRULE evaluation across BYHOUR and BYMINUTE combinations - Cover multi-hour weekday schedules with idempotent patch tests --- scripts/patch-linux-window-ui.js | 6 + scripts/patch-linux-window-ui.test.js | 63 +++++++++ scripts/patches/automation-schedule.js | 123 ++++++++++++++++++ .../automation-schedule/patch.js | 23 ++++ 4 files changed, 215 insertions(+) create mode 100644 scripts/patches/automation-schedule.js create mode 100644 scripts/patches/core/all-linux/extracted-app/automation-schedule/patch.js diff --git a/scripts/patch-linux-window-ui.js b/scripts/patch-linux-window-ui.js index 33a792ae..fc8a259e 100644 --- a/scripts/patch-linux-window-ui.js +++ b/scripts/patch-linux-window-ui.js @@ -26,6 +26,10 @@ const { applyLinuxMultiInstanceBootstrapPatch, patchLinuxMultiInstanceBootstrap, } = require("./patches/bootstrap.js"); +const { + applyAutomationScheduleMultiTimePatch, + patchAutomationScheduleAssets, +} = require("./patches/automation-schedule.js"); const { applyLinuxChromePluginAutoInstallPatch, } = require("./patches/chrome-plugin.js"); @@ -144,6 +148,7 @@ function applyLinuxBrowserUseIabVisibleOnCreatePatch(currentSource) { module.exports = { COMPUTER_USE_UI_ENV_VAR, COMPUTER_USE_UI_SETTINGS_KEY, + applyAutomationScheduleMultiTimePatch, applyBrowserAnnotationScreenshotPatch, applyBrowserUseNodeReplApprovalPatch, applyKeybindsSettingsIndexPatch, @@ -200,6 +205,7 @@ module.exports = { normalizePatchDescriptors, parseOsRelease, patchCommentPreloadBundle, + patchAutomationScheduleAssets, patchExtractedApp, patchKeybindsSettingsAssets, patchLinuxMultiInstanceBootstrap, diff --git a/scripts/patch-linux-window-ui.test.js b/scripts/patch-linux-window-ui.test.js index ef48087a..5de91127 100644 --- a/scripts/patch-linux-window-ui.test.js +++ b/scripts/patch-linux-window-ui.test.js @@ -12,6 +12,7 @@ const vm = require("node:vm"); const { COMPUTER_USE_UI_ENV_VAR, COMPUTER_USE_UI_SETTINGS_KEY, + applyAutomationScheduleMultiTimePatch, applyKeybindsSettingsIndexPatch, applyLinuxComputerUseFeaturePatch, applyLinuxComputerUseInstallFlowPatch, @@ -53,6 +54,7 @@ const { patchExtractedApp, patchPackageJson, patchLinuxAppUpdaterBridge, + patchAutomationScheduleAssets, createPatchReport, corePatchDescriptors, detectLinuxTargetContext, @@ -114,6 +116,66 @@ function captureWarns(fn) { } } +function automationScheduleBundleFixture() { + return [ + "var Cc={MO:1,TU:2,WE:3,TH:4,FR:5,SA:6,SU:0};", + "function wc(e){let t=Tc(e.byhour),n=Tc(e.byminute);return t!=null&&n!=null?{hour:t,minute:n}:e.dtstart?{hour:e.dtstart.getHours(),minute:e.dtstart.getMinutes()}:null}", + "function Tc(e){return Array.isArray(e)?typeof e[0]==`number`?e[0]:null:typeof e==`number`?e:null}", + "function Ec(e,t){let n=new Date(e),r=new Date(n.getFullYear(),n.getMonth(),n.getDate(),t.hour,t.minute,0,0);return r.getTime()<=e&&r.setDate(r.getDate()+1),r.getTime()}", + "function Dc(e,t,n){let r=new Date(e),i=r.getDay(),a=n.length>0?n:[0,1,2,3,4,5,6];for(let n=0;n<=7;n+=1){let o=(i+n)%7;if(!a.includes(o))continue;let s=new Date(r.getFullYear(),r.getMonth(),r.getDate()+n,t.hour,t.minute,0,0);if(s.getTime()>e)return s.getTime()}return e}", + "function Oc(e){return e?(Array.isArray(e)?e:[e]).map(e=>{if(typeof e==`number`)return Ac(e);if(kc(e))return Ac(e.weekday);let t=String(e);return t in Cc?Cc[t]:null}).filter(e=>e!=null):[]}", + "function kc(e){return typeof e!=`object`||!e||!(`weekday`in e)?!1:typeof e.weekday==`number`}", + "function Ac(e){return!Number.isInteger(e)||e<0||e>6?null:(e+1)%7}", + "var jc=`codex_chronicle`;", + ].join(""); +} + +function evaluateAutomationSchedule(source, now, options) { + const context = { now, options, result: null }; + vm.runInNewContext( + `${source};result=Dc(now,wc(options),Oc(options.byweekday));`, + context, + ); + return context.result; +} + +test("automation schedule patch honors multiple BYHOUR values", () => { + const patched = applyPatchTwice(applyAutomationScheduleMultiTimePatch, automationScheduleBundleFixture()); + const options = { + byhour: [11, 14, 17, 20], + byminute: [0], + byweekday: ["MO", "TU", "WE", "TH", "FR"], + dtstart: new Date(2026, 4, 22, 16, 27, 0, 0), + }; + + assert.match(patched, /function codexLinuxNormalizeRruleNumbers/); + assert.equal( + evaluateAutomationSchedule(patched, new Date(2026, 4, 22, 16, 27, 0, 0).getTime(), options), + new Date(2026, 4, 22, 17, 0, 0, 0).getTime(), + ); + assert.equal( + evaluateAutomationSchedule(patched, new Date(2026, 4, 22, 20, 1, 0, 0).getTime(), options), + new Date(2026, 4, 25, 11, 0, 0, 0).getTime(), + ); +}); + +test("automation schedule asset patch updates workspace-root bundle", () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "codex-automation-schedule-")); + try { + const buildDir = path.join(tempRoot, ".vite", "build"); + fs.mkdirSync(buildDir, { recursive: true }); + const bundlePath = path.join(buildDir, "workspace-root-drop-handler-test.js"); + fs.writeFileSync(bundlePath, automationScheduleBundleFixture(), "utf8"); + + assert.deepEqual(patchAutomationScheduleAssets(tempRoot), { matched: 1, changed: 1 }); + const patched = fs.readFileSync(bundlePath, "utf8"); + assert.match(patched, /function codexLinuxRruleTimes/); + assert.deepEqual(patchAutomationScheduleAssets(tempRoot), { matched: 1, changed: 0 }); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } +}); + test("asset patch helpers match every file when passed a global regex", () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "codex-asset-global-")); try { @@ -445,6 +507,7 @@ test("default core patch descriptors are grouped and unique", () => { "linux-launch-actions", "linux-hotkey-window-prewarm", "linux-git-origins-source-fallback", + "automation-schedule-multi-time-rrule", "linux-app-sunset-gate", "linux-app-server-feature-enablement", "opaque-window-default-general-settings", diff --git a/scripts/patches/automation-schedule.js b/scripts/patches/automation-schedule.js new file mode 100644 index 00000000..ead66bed --- /dev/null +++ b/scripts/patches/automation-schedule.js @@ -0,0 +1,123 @@ +"use strict"; + +const fs = require("node:fs"); +const path = require("node:path"); + +const { + findMatchingBrace, + readDirectoryNames, +} = require("./shared.js"); + +function findWorkspaceRootDropHandlerBundles(extractedDir) { + const buildDir = path.join(extractedDir, ".vite", "build"); + return readDirectoryNames(buildDir) + .filter((name) => /^workspace-root-drop-handler(?:-[^.]+)?\.js$/.test(name)) + .sort() + .map((name) => path.join(buildDir, name)); +} + +function findAutomationScheduleHelperBlock(source) { + const marker = + "return t!=null&&n!=null?{hour:t,minute:n}:e.dtstart?{hour:e.dtstart.getHours(),minute:e.dtstart.getMinutes()}:null"; + const markerIndex = source.indexOf(marker); + if (markerIndex === -1) { + return null; + } + + const start = source.lastIndexOf("var ", markerIndex); + if (start === -1) { + return null; + } + + let cursor = start; + for (let index = 0; index < 7; index += 1) { + const functionIndex = source.indexOf("function ", cursor); + if (functionIndex === -1) { + return null; + } + const openBrace = source.indexOf("{", functionIndex); + if (openBrace === -1) { + return null; + } + const closeBrace = findMatchingBrace(source, openBrace); + if (closeBrace === -1) { + return null; + } + cursor = closeBrace + 1; + } + + const text = source.slice(start, cursor); + const dayMapVar = text.match(/^var ([A-Za-z_$][\w$]*)=\{MO:1,TU:2,WE:3,TH:4,FR:5,SA:6,SU:0\};/)?.[1]; + const functions = [...text.matchAll(/function ([A-Za-z_$][\w$]*)\(/g)].map((match) => match[1]); + if (dayMapVar == null || functions.length < 7) { + return null; + } + + return { + start, + end: cursor, + dayMapVar, + timeOfDayFn: functions[0], + singleNumberFn: functions[1], + dailyFn: functions[2], + weeklyFn: functions[3], + weekdaysFn: functions[4], + weekdayObjectFn: functions[5], + normalizeWeekdayFn: functions[6], + }; +} + +function automationScheduleReplacement(block) { + return [ + `var ${block.dayMapVar}={MO:1,TU:2,WE:3,TH:4,FR:5,SA:6,SU:0};`, + `function ${block.timeOfDayFn}(e){let t=codexLinuxNormalizeRruleNumbers(e.byhour,0,23),n=codexLinuxNormalizeRruleNumbers(e.byminute,0,59);return e.dtstart&&(t.length!==0||(t=[e.dtstart.getHours()]),n.length!==0||(n=[e.dtstart.getMinutes()])),t.length!==0&&n.length!==0?{hours:t,minutes:n}:null}`, + `function ${block.singleNumberFn}(e){let t=codexLinuxNormalizeRruleNumbers(e,-9007199254740991,9007199254740991);return t.length>0?t[0]:null}`, + "function codexLinuxNormalizeRruleNumbers(e,t,n){let r=Array.isArray(e)?e:[e];return Array.from(new Set(r.filter(e=>typeof e==`number`&&Number.isInteger(e)&&e>=t&&e<=n))).sort((e,t)=>e-t)}", + "function codexLinuxRruleTimes(e){let t=[];for(let n of e.hours)for(let r of e.minutes)t.push(n*60+r);return Array.from(new Set(t)).sort((e,t)=>e-t)}", + `function ${block.dailyFn}(e,t){return ${block.weeklyFn}(e,t,[])}`, + `function ${block.weeklyFn}(e,t,n){let r=new Date(e),i=r.getDay(),a=n.length>0?n:[0,1,2,3,4,5,6],o=codexLinuxRruleTimes(t);if(o.length===0)return e;for(let t=0;t<=7;t+=1){let n=(i+t)%7;if(!a.includes(n))continue;for(let n of o){let i=Math.floor(n/60),a=n%60,s=new Date(r.getFullYear(),r.getMonth(),r.getDate()+t,i,a,0,0);if(s.getTime()>e)return s.getTime()}}return e}`, + `function ${block.weekdaysFn}(e){return e?(Array.isArray(e)?e:[e]).map(e=>{if(typeof e==\`number\`)return ${block.normalizeWeekdayFn}(e);if(${block.weekdayObjectFn}(e))return ${block.normalizeWeekdayFn}(e.weekday);let t=String(e);return t in ${block.dayMapVar}?${block.dayMapVar}[t]:null}).filter(e=>e!=null):[]}`, + `function ${block.weekdayObjectFn}(e){return typeof e!=\`object\`||!e||!(\`weekday\`in e)?!1:typeof e.weekday==\`number\`}`, + `function ${block.normalizeWeekdayFn}(e){return!Number.isInteger(e)||e<0||e>6?null:(e+1)%7}`, + ].join(""); +} + +function applyAutomationScheduleMultiTimePatch(source) { + if (source.includes("function codexLinuxNormalizeRruleNumbers(")) { + return source; + } + + const block = findAutomationScheduleHelperBlock(source); + if (block == null) { + console.warn("WARN: Could not find automation schedule helper block — skipping RRULE multi-time patch"); + return source; + } + + return source.slice(0, block.start) + automationScheduleReplacement(block) + source.slice(block.end); +} + +function patchAutomationScheduleAssets(extractedDir) { + const candidates = findWorkspaceRootDropHandlerBundles(extractedDir); + if (candidates.length === 0) { + const reason = `Could not find workspace-root-drop-handler bundle in ${path.join(extractedDir, ".vite", "build")}`; + console.warn(`WARN: ${reason} — skipping RRULE multi-time patch`); + return { matched: 0, changed: 0, reason }; + } + + let changed = 0; + for (const candidate of candidates) { + const source = fs.readFileSync(candidate, "utf8"); + const patched = applyAutomationScheduleMultiTimePatch(source); + if (patched !== source) { + fs.writeFileSync(candidate, patched, "utf8"); + changed += 1; + } + } + + return { matched: candidates.length, changed }; +} + +module.exports = { + applyAutomationScheduleMultiTimePatch, + patchAutomationScheduleAssets, +}; diff --git a/scripts/patches/core/all-linux/extracted-app/automation-schedule/patch.js b/scripts/patches/core/all-linux/extracted-app/automation-schedule/patch.js new file mode 100644 index 00000000..651ca99c --- /dev/null +++ b/scripts/patches/core/all-linux/extracted-app/automation-schedule/patch.js @@ -0,0 +1,23 @@ +"use strict"; + +const { + patchAutomationScheduleAssets, +} = require("../../../../automation-schedule.js"); + +module.exports = { + id: "automation-schedule-multi-time-rrule", + phase: "extracted-app", + order: 240, + ciPolicy: "optional", + apply: patchAutomationScheduleAssets, + status: (result, warnings) => ({ + status: result?.changed + ? "applied" + : warnings.length > 0 + ? "skipped-optional" + : result?.matched + ? "already-applied" + : "skipped-optional", + reason: result?.reason ?? warnings[0] ?? null, + }), +};