Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ja/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ ocx service status
ocx service uninstall
```

Windows では、`ocx service status` はタスク スケジューラの登録状態と、ID を確認済みの
OpenCodex プロキシへの到達性を別々に報告します。ローカライズされた `schtasks` の表は出力
しないため、Windows のコード ページに関係なく概要を読めます。

Windows でタスク スケジューラのエントリを作成するには昇格が必要です。認識できるローカライズ
済みのアクセス拒否テキストは、既存の案内経路をそのまま使用します。そのテキストが読めない場合、
コマンド形状が `/create /tn opencodex-proxy /xml <空でないパス> /f` と正確に一致し、終了 status
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ko/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ ocx service status
ocx service uninstall
```

Windows에서 `ocx service status`는 작업 스케줄러 등록 상태와 신원이 확인된 OpenCodex 프록시의
연결 상태를 따로 보고합니다. 로컬화된 `schtasks` 표를 출력하지 않으므로 Windows 코드 페이지와
관계없이 요약을 읽을 수 있습니다.

Windows에서 작업 스케줄러 항목을 만들려면 권한 상승이 필요합니다. 인식 가능한 현지화 권한 거부
문자열은 기존 안내 경로를 그대로 사용합니다. 문자열을 읽을 수 없을 때는 명령 모양이
`/create /tn opencodex-proxy /xml <비어 있지 않은 경로> /f`와 정확히 일치하고, 종료 상태가 1이며,
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,10 @@ ocx service status
ocx service uninstall
```

On Windows, `ocx service status` reports Task Scheduler registration separately from
identity-verified OpenCodex proxy reachability. It does not print the localized `schtasks` table,
so the summary remains readable across Windows code pages.

On Windows, creating the Task Scheduler entry requires elevation. Recognized localized
access-denied text keeps the existing guidance path. If that text is unreadable, the fallback
requires the owned command shape `/create /tn opencodex-proxy /xml <non-empty-path> /f`, status 1,
Expand Down
4 changes: 4 additions & 0 deletions docs-site/src/content/docs/ru/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ ocx service status
ocx service uninstall
```

В Windows `ocx service status` отдельно сообщает о регистрации задания Планировщика и о доступности
прокси OpenCodex, подтверждённой проверкой его идентичности. Локализованная таблица `schtasks` не
выводится, поэтому сводка остаётся читаемой при любой кодовой странице Windows.

В Windows для создания задания Планировщика требуется повышение прав. Распознанный локализованный
текст об отказе в доступе продолжает использовать прежний путь подсказки. Если текст нечитаем,
независимый от языка fallback требует точную форму команды
Expand Down
3 changes: 3 additions & 0 deletions docs-site/src/content/docs/zh-cn/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ ocx service status
ocx service uninstall
```

在 Windows 上,`ocx service status` 会分别报告任务计划程序注册状态和经过身份验证的 OpenCodex
代理可达性。它不会输出本地化的 `schtasks` 表,因此摘要不受 Windows 代码页影响,始终可读。

在 Windows 上,创建任务计划程序条目需要提升权限。能识别的本地化“拒绝访问”文本继续使用现有
处理路径。如果该文本不可读,则回退路径要求命令形状严格为
`/create /tn opencodex-proxy /xml <非空路径> /f`、退出状态为 1,并确认当前 token 未提升;此时
Expand Down
73 changes: 71 additions & 2 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { isWslRuntime } from "./codex/home";
import { durableBunPath, durableBunRuntime } from "./lib/bun-runtime";
import { isProcessAlive, stopProxy } from "./lib/process-control";
import { serviceApiTokenFilePath } from "./lib/service-secrets";
import { findLiveProxy } from "./server/proxy-liveness";
import { randomUUID } from "node:crypto";
import {
ELEVATION_REQUEST_TIMEOUT_MS,
Expand Down Expand Up @@ -394,6 +395,70 @@ export type WindowsSchedulerTaskProbe =
| { status: "absent" }
| { status: "unknown"; detail: string };

export type WindowsSchedulerProxyProbe =
| { status: "running"; port: number }
| { status: "not-running" }
| { status: "unknown" };

/**
* Render Task Scheduler status without exposing localized `schtasks` table output.
* The task probe answers installation state; the identity-checked health probe answers
* runtime state. Keep probe details out of this user-facing line because they can contain
* incorrectly decoded, locale-specific command output.
*/
export function formatWindowsSchedulerServiceStatus(
task: WindowsSchedulerTaskProbe,
proxy: WindowsSchedulerProxyProbe,
): string {
if (task.status === "present") {
if (proxy.status === "running") {
return `✅ service installed (Task Scheduler); OpenCodex proxy running on port ${proxy.port}.`;
}
if (proxy.status === "not-running") {
return "⚠️ service installed (Task Scheduler); OpenCodex proxy not running.";
}
return "⚠️ service installed (Task Scheduler); OpenCodex proxy status unknown.";
}
if (task.status === "absent") {
if (proxy.status === "running") {
return `❌ service not installed (Task Scheduler); OpenCodex proxy is running independently on port ${proxy.port}.`;
}
if (proxy.status === "unknown") {
return "❌ service not installed (Task Scheduler); OpenCodex proxy status unknown.";
}
return "❌ service not installed (Task Scheduler).";
}
if (proxy.status === "running") {
return `⚠️ Task Scheduler registration unknown; OpenCodex proxy running on port ${proxy.port}.`;
}
if (proxy.status === "not-running") {
return "⚠️ service status unknown (Task Scheduler query failed); OpenCodex proxy not running.";
}
return "⚠️ service status unknown (Task Scheduler and proxy checks failed).";
}

export async function inspectWindowsSchedulerServiceStatus(io: {
probeTask?: () => WindowsSchedulerTaskProbe;
findProxy?: () => Promise<{ port: number } | null>;
} = {}): Promise<string> {
let task: WindowsSchedulerTaskProbe;
try {
task = (io.probeTask ?? probeWindowsSchedulerTask)();
} catch (error) {
task = { status: "unknown", detail: schtasksErrorDetail(error) };
}

let proxy: WindowsSchedulerProxyProbe;
try {
const live = await (io.findProxy ?? findLiveProxy)();
proxy = live ? { status: "running", port: live.port } : { status: "not-running" };
} catch {
proxy = { status: "unknown" };
}

return formatWindowsSchedulerServiceStatus(task, proxy);
}

function schtasksErrorDetail(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
Expand Down Expand Up @@ -1756,8 +1821,12 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
}
break;
case "status": {
const s = ops.status();
console.log(s ? `✅ running:\n${s}` : "❌ service not installed/running.");
if (process.platform === "win32" && backend === "scheduler") {
console.log(await inspectWindowsSchedulerServiceStatus());
} else {
const s = ops.status();
console.log(s ? `✅ running:\n${s}` : "❌ service not installed/running.");
}
console.log(`Diagnostics: ${serviceDiagnosticsSummary()}`);
break;
}
Expand Down
50 changes: 50 additions & 0 deletions tests/windows-scheduler-install-verification.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { afterEach, describe, expect, test } from "bun:test";
import {
buildWindowsTaskXml,
evaluateWindowsSchedulerInstallVerification,
formatWindowsSchedulerServiceStatus,
inspectWindowsSchedulerServiceStatus,
probeWindowsSchedulerTask,
setQuerySchtasksForTests,
windowsSchedulerCsvIncludesTask,
Expand Down Expand Up @@ -44,6 +46,26 @@ describe("probeWindowsSchedulerTask", () => {
expect(windowsSchedulerTaskInstalled("opencodex-proxy")).toBe(true);
});

test("recognizes the task without exposing mojibake from localized table output", () => {
Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
const localizedTable = [
"����: \\",
"�۾� �̸� ���� ���� �ð� ����",
"======================================== ====================== ===============",
"opencodex-proxy N/A �غ�",
].join("\n");
setQuerySchtasksForTests(() => localizedTable);

const result = formatWindowsSchedulerServiceStatus(
probeWindowsSchedulerTask("opencodex-proxy"),
{ status: "running", port: 10100 },
);

expect(result).toBe("✅ service installed (Task Scheduler); OpenCodex proxy running on port 10100.");
expect(result).not.toContain("����");
expect(result).not.toContain("�۾�");
});

test("falls back to CSV listing when the specific query fails", () => {
Object.defineProperty(process, "platform", { configurable: true, value: "win32" });
setQuerySchtasksForTests((args) => {
Expand Down Expand Up @@ -83,6 +105,34 @@ describe("probeWindowsSchedulerTask", () => {
});
});

describe("formatWindowsSchedulerServiceStatus", () => {
test("reports task and identity-checked proxy state independently", () => {
expect(formatWindowsSchedulerServiceStatus({ status: "present" }, { status: "not-running" }))
.toBe("⚠️ service installed (Task Scheduler); OpenCodex proxy not running.");
expect(formatWindowsSchedulerServiceStatus({ status: "present" }, { status: "unknown" }))
.toBe("⚠️ service installed (Task Scheduler); OpenCodex proxy status unknown.");
expect(formatWindowsSchedulerServiceStatus({ status: "absent" }, { status: "running", port: 3593 }))
.toBe("❌ service not installed (Task Scheduler); OpenCodex proxy is running independently on port 3593.");
expect(formatWindowsSchedulerServiceStatus({ status: "absent" }, { status: "not-running" }))
.toBe("❌ service not installed (Task Scheduler).");
expect(formatWindowsSchedulerServiceStatus({ status: "unknown", detail: "����" }, { status: "running", port: 10100 }))
.toBe("⚠️ Task Scheduler registration unknown; OpenCodex proxy running on port 10100.");
expect(formatWindowsSchedulerServiceStatus({ status: "unknown", detail: "����" }, { status: "not-running" }))
.toBe("⚠️ service status unknown (Task Scheduler query failed); OpenCodex proxy not running.");
});

test("keeps scheduler and runtime probe failures locale-independent", async () => {
const status = await inspectWindowsSchedulerServiceStatus({
probeTask: () => { throw new Error("���� ����"); },
findProxy: async () => { throw new Error("connection failure"); },
});

expect(status).toBe("⚠️ service status unknown (Task Scheduler and proxy checks failed).");
expect(status).not.toContain("����");
expect(status).not.toContain("connection failure");
});
});

describe("evaluateWindowsSchedulerInstallVerification", () => {
const wscript = "C:\\Windows\\System32\\wscript.exe";
const launcher = "C:\\Users\\Test\\.opencodex\\opencodex-service-launcher.vbs";
Expand Down
Loading