@@ -38,6 +38,7 @@ pub const Status = struct {
3838};
3939
4040const service_version_env_name = "CODEX_AUTH_VERSION" ;
41+ const codex_home_env_name = "CODEX_HOME" ;
4142
4243pub const AutoSwitchAttempt = struct {
4344 refreshed_candidates : bool ,
@@ -2036,12 +2037,11 @@ pub fn deleteAbsoluteFileIfExists(path: []const u8) void {
20362037}
20372038
20382039fn installWindowsService (allocator : std.mem.Allocator , codex_home : []const u8 , self_exe : []const u8 ) ! void {
2039- _ = codex_home ;
20402040 const helper_path = try windowsHelperPath (allocator , self_exe );
20412041 defer allocator .free (helper_path );
20422042 try std .fs .cwd ().access (helper_path , .{});
20432043
2044- const register_script = try windowsRegisterTaskScript (allocator , helper_path );
2044+ const register_script = try windowsRegisterTaskScript (allocator , helper_path , codex_home );
20452045 defer allocator .free (register_script );
20462046 const end_script = try windowsEndTaskScript (allocator );
20472047 defer allocator .free (end_script );
@@ -2125,52 +2125,60 @@ fn queryWindowsRuntimeState(allocator: std.mem.Allocator) RuntimeState {
21252125}
21262126
21272127pub fn linuxUnitText (allocator : std.mem.Allocator , self_exe : []const u8 , codex_home : []const u8 ) ! []u8 {
2128- _ = codex_home ;
21292128 const exec = try std .fmt .allocPrint (allocator , "\" {s}\" daemon --watch" , .{self_exe });
21302129 defer allocator .free (exec );
21312130 const escaped_version = try escapeSystemdValue (allocator , version .app_version );
21322131 defer allocator .free (escaped_version );
2132+ const escaped_codex_home = try escapeSystemdValue (allocator , codex_home );
2133+ defer allocator .free (escaped_codex_home );
21332134 return try std .fmt .allocPrint (
21342135 allocator ,
2135- "[Unit]\n Description=codex-auth auto-switch watcher\n\n [Service]\n Type=simple\n Restart=always\n RestartSec=1\n Environment=\" {s}={s}\" \n ExecStart={s}\n\n [Install]\n WantedBy=default.target\n " ,
2136+ "[Unit]\n Description=codex-auth auto-switch watcher\n\n [Service]\n Type=simple\n Restart=always\n RestartSec=1\n Environment=\" {s}={s}\" \n Environment= \" {s}={s} \" \ n ExecStart={s}\n\n [Install]\n WantedBy=default.target\n " ,
21362137 .{
21372138 service_version_env_name ,
21382139 escaped_version ,
2140+ codex_home_env_name ,
2141+ escaped_codex_home ,
21392142 exec ,
21402143 },
21412144 );
21422145}
21432146
21442147pub fn macPlistText (allocator : std.mem.Allocator , self_exe : []const u8 , codex_home : []const u8 ) ! []u8 {
2145- _ = codex_home ;
21462148 const exe = try escapeXml (allocator , self_exe );
21472149 defer allocator .free (exe );
21482150 const current_version = try escapeXml (allocator , version .app_version );
21492151 defer allocator .free (current_version );
2152+ const escaped_codex_home = try escapeXml (allocator , codex_home );
2153+ defer allocator .free (escaped_codex_home );
21502154 return try std .fmt .allocPrint (
21512155 allocator ,
2152- "<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>\n <!DOCTYPE plist PUBLIC \" -//Apple//DTD PLIST 1.0//EN\" \" http://www.apple.com/DTDs/PropertyList-1.0.dtd\" >\n <plist version=\" 1.0\" >\n <dict>\n <key>Label</key>\n <string>{s}</string>\n <key>ProgramArguments</key>\n <array>\n <string>{s}</string>\n <string>daemon</string>\n <string>--watch</string>\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n <key>{s}</key>\n <string>{s}</string>\n </dict>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n </dict>\n </plist>\n " ,
2153- .{ mac_label , exe , service_version_env_name , current_version },
2156+ "<?xml version=\" 1.0\" encoding=\" UTF-8\" ?>\n <!DOCTYPE plist PUBLIC \" -//Apple//DTD PLIST 1.0//EN\" \" http://www.apple.com/DTDs/PropertyList-1.0.dtd\" >\n <plist version=\" 1.0\" >\n <dict>\n <key>Label</key>\n <string>{s}</string>\n <key>ProgramArguments</key>\n <array>\n <string>{s}</string>\n <string>daemon</string>\n <string>--watch</string>\n </array>\n <key>EnvironmentVariables</key>\n <dict>\n <key>{s}</key>\n <string>{s}</string>\n <key>{s}</key> \n <string>{s}</string> \n </dict>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n </dict>\n </plist>\n " ,
2157+ .{ mac_label , exe , service_version_env_name , current_version , codex_home_env_name , escaped_codex_home },
21542158 );
21552159}
21562160
2157- pub fn windowsTaskAction (allocator : std.mem.Allocator , helper_path : []const u8 ) ! []u8 {
2161+ pub fn windowsTaskAction (allocator : std.mem.Allocator , helper_path : []const u8 , codex_home : []const u8 ) ! []u8 {
2162+ const args = try windowsTaskArguments (allocator , codex_home );
2163+ defer allocator .free (args );
21582164 return try std .fmt .allocPrint (
21592165 allocator ,
2160- "\" {s}\" --service-version {s}" ,
2161- .{ helper_path , version . app_version },
2166+ "\" {s}\" {s}" ,
2167+ .{ helper_path , args },
21622168 );
21632169}
21642170
2165- pub fn windowsRegisterTaskScript (allocator : std.mem.Allocator , helper_path : []const u8 ) ! []u8 {
2171+ pub fn windowsRegisterTaskScript (allocator : std.mem.Allocator , helper_path : []const u8 , codex_home : [] const u8 ) ! []u8 {
21662172 const escaped_helper_path = try escapePowerShellSingleQuoted (allocator , helper_path );
21672173 defer allocator .free (escaped_helper_path );
2168- const escaped_version = try escapePowerShellSingleQuoted (allocator , version .app_version );
2169- defer allocator .free (escaped_version );
2174+ const args = try windowsTaskArguments (allocator , codex_home );
2175+ defer allocator .free (args );
2176+ const escaped_args = try escapePowerShellSingleQuoted (allocator , args );
2177+ defer allocator .free (escaped_args );
21702178 return try std .fmt .allocPrint (
21712179 allocator ,
2172- "$action = New-ScheduledTaskAction -Execute '{s}' -Argument '--service-version {s}'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -RestartCount {s} -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Seconds 0); Register-ScheduledTask -TaskName '{s}' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null" ,
2173- .{ escaped_helper_path , escaped_version , windows_task_restart_count , windows_task_name },
2180+ "$action = New-ScheduledTaskAction -Execute '{s}' -Argument '{s}'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -RestartCount {s} -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit (New-TimeSpan -Seconds 0); Register-ScheduledTask -TaskName '{s}' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null" ,
2181+ .{ escaped_helper_path , escaped_args , windows_task_restart_count , windows_task_name },
21742182 );
21752183}
21762184
@@ -2309,10 +2317,9 @@ fn macPlistMatches(allocator: std.mem.Allocator, codex_home: []const u8, self_ex
23092317}
23102318
23112319fn windowsTaskMatches (allocator : std.mem.Allocator , codex_home : []const u8 , self_exe : []const u8 ) ! bool {
2312- _ = codex_home ;
23132320 const helper_path = try windowsHelperPath (allocator , self_exe );
23142321 defer allocator .free (helper_path );
2315- const expected_action = try windowsExpectedTaskFingerprint (allocator , helper_path );
2322+ const expected_action = try windowsExpectedTaskFingerprint (allocator , helper_path , codex_home );
23162323 defer allocator .free (expected_action );
23172324 const expected_fingerprint = try windowsExpectedTaskDefinitionFingerprint (allocator , expected_action );
23182325 defer allocator .free (expected_fingerprint );
@@ -2335,12 +2342,10 @@ fn windowsTaskMatches(allocator: std.mem.Allocator, codex_home: []const u8, self
23352342 };
23362343}
23372344
2338- fn windowsExpectedTaskFingerprint (allocator : std.mem.Allocator , helper_path : []const u8 ) ! []u8 {
2339- return try std .fmt .allocPrint (
2340- allocator ,
2341- "{s} --service-version {s}" ,
2342- .{ helper_path , version .app_version },
2343- );
2345+ fn windowsExpectedTaskFingerprint (allocator : std.mem.Allocator , helper_path : []const u8 , codex_home : []const u8 ) ! []u8 {
2346+ const args = try windowsTaskArguments (allocator , codex_home );
2347+ defer allocator .free (args );
2348+ return try std .fmt .allocPrint (allocator , "{s} {s}" , .{ helper_path , args });
23442349}
23452350
23462351fn windowsExpectedTaskDefinitionFingerprint (allocator : std.mem.Allocator , action : []const u8 ) ! []u8 {
@@ -2443,6 +2448,51 @@ fn escapePowerShellSingleQuoted(allocator: std.mem.Allocator, input: []const u8)
24432448 return std .mem .replaceOwned (u8 , allocator , input , "'" , "''" );
24442449}
24452450
2451+ fn windowsTaskArguments (allocator : std.mem.Allocator , codex_home : []const u8 ) ! []u8 {
2452+ const quoted_codex_home = try quoteWindowsCommandArg (allocator , codex_home );
2453+ defer allocator .free (quoted_codex_home );
2454+ return try std .fmt .allocPrint (
2455+ allocator ,
2456+ "--service-version {s} --codex-home {s}" ,
2457+ .{ version .app_version , quoted_codex_home },
2458+ );
2459+ }
2460+
2461+ fn quoteWindowsCommandArg (allocator : std.mem.Allocator , arg : []const u8 ) ! []u8 {
2462+ const needs_quotes = blk : {
2463+ if (arg .len == 0 ) break :blk true ;
2464+ for (arg ) | ch | {
2465+ if (ch <= ' ' or ch == '"' ) break :blk true ;
2466+ }
2467+ break :blk false ;
2468+ };
2469+ if (! needs_quotes ) return try allocator .dupe (u8 , arg );
2470+
2471+ var out = std .ArrayList (u8 ).empty ;
2472+ defer out .deinit (allocator );
2473+ try out .append (allocator , '"' );
2474+
2475+ var backslash_count : usize = 0 ;
2476+ for (arg ) | byte | {
2477+ switch (byte ) {
2478+ '\\ ' = > backslash_count += 1 ,
2479+ '"' = > {
2480+ try out .appendNTimes (allocator , '\\ ' , backslash_count * 2 + 1 );
2481+ try out .append (allocator , '"' );
2482+ backslash_count = 0 ;
2483+ },
2484+ else = > {
2485+ try out .appendNTimes (allocator , '\\ ' , backslash_count );
2486+ try out .append (allocator , byte );
2487+ backslash_count = 0 ;
2488+ },
2489+ }
2490+ }
2491+ try out .appendNTimes (allocator , '\\ ' , backslash_count * 2 );
2492+ try out .append (allocator , '"' );
2493+ return try out .toOwnedSlice (allocator );
2494+ }
2495+
24462496test "candidate index refreshes cached ranking after a reset window expires" {
24472497 const bdd = @import ("tests/bdd_helpers.zig" );
24482498 const gpa = std .testing .allocator ;
0 commit comments