Skip to content

Commit 2cc2ee3

Browse files
committed
fix(service): make Windows status locale independent
1 parent 0666b41 commit 2cc2ee3

2 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/service.ts

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { isWslRuntime } from "./codex/home";
1717
import { durableBunPath, durableBunRuntime } from "./lib/bun-runtime";
1818
import { isProcessAlive, stopProxy } from "./lib/process-control";
1919
import { serviceApiTokenFilePath } from "./lib/service-secrets";
20+
import { findLiveProxy } from "./server/proxy-liveness";
2021
import { randomUUID } from "node:crypto";
2122
import {
2223
ELEVATION_REQUEST_TIMEOUT_MS,
@@ -394,6 +395,70 @@ export type WindowsSchedulerTaskProbe =
394395
| { status: "absent" }
395396
| { status: "unknown"; detail: string };
396397

398+
export type WindowsSchedulerProxyProbe =
399+
| { status: "running"; port: number }
400+
| { status: "not-running" }
401+
| { status: "unknown" };
402+
403+
/**
404+
* Render Task Scheduler status without exposing localized `schtasks` table output.
405+
* The task probe answers installation state; the identity-checked health probe answers
406+
* runtime state. Keep probe details out of this user-facing line because they can contain
407+
* incorrectly decoded, locale-specific command output.
408+
*/
409+
export function formatWindowsSchedulerServiceStatus(
410+
task: WindowsSchedulerTaskProbe,
411+
proxy: WindowsSchedulerProxyProbe,
412+
): string {
413+
if (task.status === "present") {
414+
if (proxy.status === "running") {
415+
return `✅ service installed (Task Scheduler); OpenCodex proxy running on port ${proxy.port}.`;
416+
}
417+
if (proxy.status === "not-running") {
418+
return "⚠️ service installed (Task Scheduler); OpenCodex proxy not running.";
419+
}
420+
return "⚠️ service installed (Task Scheduler); OpenCodex proxy status unknown.";
421+
}
422+
if (task.status === "absent") {
423+
if (proxy.status === "running") {
424+
return `❌ service not installed (Task Scheduler); OpenCodex proxy is running independently on port ${proxy.port}.`;
425+
}
426+
if (proxy.status === "unknown") {
427+
return "❌ service not installed (Task Scheduler); OpenCodex proxy status unknown.";
428+
}
429+
return "❌ service not installed (Task Scheduler).";
430+
}
431+
if (proxy.status === "running") {
432+
return `⚠️ Task Scheduler registration unknown; OpenCodex proxy running on port ${proxy.port}.`;
433+
}
434+
if (proxy.status === "not-running") {
435+
return "⚠️ service status unknown (Task Scheduler query failed); OpenCodex proxy not running.";
436+
}
437+
return "⚠️ service status unknown (Task Scheduler and proxy checks failed).";
438+
}
439+
440+
export async function inspectWindowsSchedulerServiceStatus(io: {
441+
probeTask?: () => WindowsSchedulerTaskProbe;
442+
findProxy?: () => Promise<{ port: number } | null>;
443+
} = {}): Promise<string> {
444+
let task: WindowsSchedulerTaskProbe;
445+
try {
446+
task = (io.probeTask ?? probeWindowsSchedulerTask)();
447+
} catch (error) {
448+
task = { status: "unknown", detail: schtasksErrorDetail(error) };
449+
}
450+
451+
let proxy: WindowsSchedulerProxyProbe;
452+
try {
453+
const live = await (io.findProxy ?? findLiveProxy)();
454+
proxy = live ? { status: "running", port: live.port } : { status: "not-running" };
455+
} catch {
456+
proxy = { status: "unknown" };
457+
}
458+
459+
return formatWindowsSchedulerServiceStatus(task, proxy);
460+
}
461+
397462
function schtasksErrorDetail(error: unknown): string {
398463
return error instanceof Error ? error.message : String(error);
399464
}
@@ -1756,8 +1821,12 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
17561821
}
17571822
break;
17581823
case "status": {
1759-
const s = ops.status();
1760-
console.log(s ? `✅ running:\n${s}` : "❌ service not installed/running.");
1824+
if (process.platform === "win32" && backend === "scheduler") {
1825+
console.log(await inspectWindowsSchedulerServiceStatus());
1826+
} else {
1827+
const s = ops.status();
1828+
console.log(s ? `✅ running:\n${s}` : "❌ service not installed/running.");
1829+
}
17611830
console.log(`Diagnostics: ${serviceDiagnosticsSummary()}`);
17621831
break;
17631832
}

tests/windows-scheduler-install-verification.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { afterEach, describe, expect, test } from "bun:test";
22
import {
33
buildWindowsTaskXml,
44
evaluateWindowsSchedulerInstallVerification,
5+
formatWindowsSchedulerServiceStatus,
6+
inspectWindowsSchedulerServiceStatus,
57
probeWindowsSchedulerTask,
68
setQuerySchtasksForTests,
79
windowsSchedulerCsvIncludesTask,
@@ -44,6 +46,26 @@ describe("probeWindowsSchedulerTask", () => {
4446
expect(windowsSchedulerTaskInstalled("opencodex-proxy")).toBe(true);
4547
});
4648

49+
test("recognizes the task without exposing mojibake from localized table output", () => {
50+
Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
51+
const localizedTable = [
52+
"����: \\",
53+
"�۾� �̸� ���� ���� �ð� ����",
54+
"======================================== ====================== ===============",
55+
"opencodex-proxy N/A �غ�",
56+
].join("\n");
57+
setQuerySchtasksForTests(() => localizedTable);
58+
59+
const result = formatWindowsSchedulerServiceStatus(
60+
probeWindowsSchedulerTask("opencodex-proxy"),
61+
{ status: "running", port: 10100 },
62+
);
63+
64+
expect(result).toBe("✅ service installed (Task Scheduler); OpenCodex proxy running on port 10100.");
65+
expect(result).not.toContain("����");
66+
expect(result).not.toContain("�۾�");
67+
});
68+
4769
test("falls back to CSV listing when the specific query fails", () => {
4870
Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
4971
setQuerySchtasksForTests((args) => {
@@ -83,6 +105,34 @@ describe("probeWindowsSchedulerTask", () => {
83105
});
84106
});
85107

108+
describe("formatWindowsSchedulerServiceStatus", () => {
109+
test("reports task and identity-checked proxy state independently", () => {
110+
expect(formatWindowsSchedulerServiceStatus({ status: "present" }, { status: "not-running" }))
111+
.toBe("⚠️ service installed (Task Scheduler); OpenCodex proxy not running.");
112+
expect(formatWindowsSchedulerServiceStatus({ status: "present" }, { status: "unknown" }))
113+
.toBe("⚠️ service installed (Task Scheduler); OpenCodex proxy status unknown.");
114+
expect(formatWindowsSchedulerServiceStatus({ status: "absent" }, { status: "running", port: 3593 }))
115+
.toBe("❌ service not installed (Task Scheduler); OpenCodex proxy is running independently on port 3593.");
116+
expect(formatWindowsSchedulerServiceStatus({ status: "absent" }, { status: "not-running" }))
117+
.toBe("❌ service not installed (Task Scheduler).");
118+
expect(formatWindowsSchedulerServiceStatus({ status: "unknown", detail: "����" }, { status: "running", port: 10100 }))
119+
.toBe("⚠️ Task Scheduler registration unknown; OpenCodex proxy running on port 10100.");
120+
expect(formatWindowsSchedulerServiceStatus({ status: "unknown", detail: "����" }, { status: "not-running" }))
121+
.toBe("⚠️ service status unknown (Task Scheduler query failed); OpenCodex proxy not running.");
122+
});
123+
124+
test("keeps scheduler and runtime probe failures locale-independent", async () => {
125+
const status = await inspectWindowsSchedulerServiceStatus({
126+
probeTask: () => { throw new Error("���� ����"); },
127+
findProxy: async () => { throw new Error("connection failure"); },
128+
});
129+
130+
expect(status).toBe("⚠️ service status unknown (Task Scheduler and proxy checks failed).");
131+
expect(status).not.toContain("����");
132+
expect(status).not.toContain("connection failure");
133+
});
134+
});
135+
86136
describe("evaluateWindowsSchedulerInstallVerification", () => {
87137
const wscript = "C:\\Windows\\System32\\wscript.exe";
88138
const launcher = "C:\\Users\\Test\\.opencodex\\opencodex-service-launcher.vbs";

0 commit comments

Comments
 (0)