@bitkyc08/opencodex 2.7.35 → 2.7.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +4 -2
- package/README.ru.md +1 -1
- package/README.zh-CN.md +1 -1
- package/bin/ocx.mjs +52 -0
- package/gui/dist/assets/index-BpX-hoSd.css +1 -0
- package/gui/dist/assets/index-ZmFopEYw.js +52 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/cursor/cursor-errors.ts +38 -1
- package/src/adapters/cursor/discovery.ts +1 -0
- package/src/adapters/cursor/effort-map.ts +1 -0
- package/src/adapters/cursor/live-models.ts +22 -5
- package/src/adapters/cursor/live-transport.ts +82 -7
- package/src/adapters/cursor/transport.ts +2 -0
- package/src/adapters/cursor.ts +5 -2
- package/src/adapters/openai-responses.ts +64 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/help.ts +10 -0
- package/src/cli/index.ts +88 -9
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/status.ts +15 -4
- package/src/cli/tray-proxy.ts +52 -0
- package/src/codex/auth-api.ts +46 -5
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +268 -0
- package/src/codex/catalog/bundled.ts +188 -0
- package/src/codex/catalog/effort.ts +263 -0
- package/src/codex/catalog/metadata.ts +176 -0
- package/src/codex/catalog/parsing.ts +399 -0
- package/src/codex/catalog/provider-fetch.ts +609 -0
- package/src/codex/catalog/sync.ts +540 -0
- package/src/codex/catalog.ts +11 -2426
- package/src/codex/inject.ts +165 -3
- package/src/codex/shim.ts +141 -8
- package/src/codex/sync.ts +17 -2
- package/src/config.ts +23 -0
- package/src/lib/errors.ts +11 -0
- package/src/providers/antigravity-models.ts +33 -0
- package/src/providers/kiro-models.ts +2 -0
- package/src/providers/registry.ts +2 -2
- package/src/responses/state.ts +69 -6
- package/src/server/auth-cors.ts +3 -0
- package/src/server/management/agent-settings-routes.ts +536 -0
- package/src/server/management/combo-routes.ts +210 -0
- package/src/server/management/config-routes.ts +302 -0
- package/src/server/management/context.ts +21 -0
- package/src/server/management/logs-usage-routes.ts +176 -0
- package/src/server/management/model-routes.ts +253 -0
- package/src/server/management/oauth-account-routes.ts +301 -0
- package/src/server/management/provider-routes.ts +408 -0
- package/src/server/management/shared.ts +186 -0
- package/src/server/management-api.ts +23 -1806
- package/src/server/responses/collaboration.ts +300 -0
- package/src/server/responses/compact.ts +342 -0
- package/src/server/responses/core.ts +1498 -0
- package/src/server/responses/encrypted-payload.ts +231 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses.ts +9 -2172
- package/src/server/startup-action-control.ts +41 -0
- package/src/server/startup-health-cache.ts +100 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/service.ts +171 -19
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +290 -0
- package/src/tray/windows.ts +628 -0
- package/src/types.ts +5 -0
- package/src/update/index.ts +43 -0
- package/src/update/job.ts +46 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +9 -2
- package/src/usage/summary.ts +42 -7
- package/gui/dist/assets/index-BunUANVE.js +0 -52
- package/gui/dist/assets/index-Sg-7L_oZ.css +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { durableBunPath } from "../lib/bun-runtime";
|
|
4
|
+
|
|
5
|
+
export type StartupInstallAction = "install-service" | "install-shim";
|
|
6
|
+
let activeInstall: StartupInstallAction | null = null;
|
|
7
|
+
|
|
8
|
+
export function startupInstallArgv(action: StartupInstallAction): string[] {
|
|
9
|
+
return action === "install-service"
|
|
10
|
+
? ["service", "install"]
|
|
11
|
+
: ["codex-shim", "install"];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Execute the existing fixed CLI installer outside the proxy event loop. */
|
|
15
|
+
export function runStartupInstallAction(action: StartupInstallAction): Promise<{ message: string }> {
|
|
16
|
+
if (activeInstall) return Promise.reject(new Error(`Another startup installation is already running: ${activeInstall}`));
|
|
17
|
+
activeInstall = action;
|
|
18
|
+
const bun = durableBunPath();
|
|
19
|
+
const cli = join(import.meta.dir, "..", "cli", "index.ts");
|
|
20
|
+
const operation = new Promise<{ message: string }>((resolve, reject) => {
|
|
21
|
+
execFile(bun, [cli, ...startupInstallArgv(action)], {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
env: process.env,
|
|
24
|
+
timeout: 60_000,
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
maxBuffer: 256 * 1024,
|
|
27
|
+
}, (error, stdout, stderr) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
const detail = stderr.trim() || stdout.trim() || error.message;
|
|
30
|
+
reject(new Error(detail.slice(0, 2_000)));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
resolve({
|
|
34
|
+
message: action === "install-service"
|
|
35
|
+
? "Background service installed."
|
|
36
|
+
: "Codex launcher shim installed.",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
return operation.finally(() => { activeInstall = null; });
|
|
41
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { codexAutoStartEnabled } from "../config";
|
|
4
|
+
import { deriveStartupHealth, type StartupHealth } from "../codex/autostart-health";
|
|
5
|
+
import { getCodexRoutingKind } from "../codex/inject";
|
|
6
|
+
import { diagnoseCodexShim } from "../codex/shim";
|
|
7
|
+
import { durableBunPath } from "../lib/bun-runtime";
|
|
8
|
+
import type { OcxConfig } from "../types";
|
|
9
|
+
|
|
10
|
+
const CACHE_TTL_MS = 30_000;
|
|
11
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
12
|
+
let cached: { timestamp: number; value: StartupHealth } | null = null;
|
|
13
|
+
let inflight: Promise<StartupHealth> | null = null;
|
|
14
|
+
let generation = 0;
|
|
15
|
+
|
|
16
|
+
export function markStartupHealthDiagnosticStale(value: StartupHealth): StartupHealth {
|
|
17
|
+
if (!value.localRoutingDependency) return { ...value, diagnosticStale: true };
|
|
18
|
+
return {
|
|
19
|
+
...value,
|
|
20
|
+
status: "at-risk",
|
|
21
|
+
rebootSafe: false,
|
|
22
|
+
protection: "none",
|
|
23
|
+
diagnosticStale: true,
|
|
24
|
+
recommendedCommand: value.routingKind === "custom-local" || value.routingKind === "unknown"
|
|
25
|
+
? value.commands.restoreNative
|
|
26
|
+
: value.commands.installService,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function conservativeFallback(config: Pick<OcxConfig, "codexAutoStart">): StartupHealth {
|
|
31
|
+
const shim = diagnoseCodexShim();
|
|
32
|
+
return deriveStartupHealth({
|
|
33
|
+
routingKind: getCodexRoutingKind(),
|
|
34
|
+
autostartEnabled: codexAutoStartEnabled(config),
|
|
35
|
+
serviceInstalled: false,
|
|
36
|
+
serviceViable: false,
|
|
37
|
+
serviceEnabled: false,
|
|
38
|
+
serviceRunning: false,
|
|
39
|
+
serviceStale: false,
|
|
40
|
+
serviceConflict: false,
|
|
41
|
+
serviceSupported: process.platform === "win32" || process.platform === "darwin" || process.platform === "linux",
|
|
42
|
+
shimInstalled: shim.installed,
|
|
43
|
+
shimHealthy: shim.healthy,
|
|
44
|
+
platform: process.platform,
|
|
45
|
+
diagnosticStale: true,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function runProbe(config: Pick<OcxConfig, "codexAutoStart">): Promise<StartupHealth> {
|
|
50
|
+
const bun = durableBunPath();
|
|
51
|
+
const cli = join(import.meta.dir, "..", "cli", "index.ts");
|
|
52
|
+
return new Promise(resolve => {
|
|
53
|
+
execFile(bun, [cli, "__startup-health"], {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
env: process.env,
|
|
56
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
maxBuffer: 256 * 1024,
|
|
59
|
+
}, (error, stdout) => {
|
|
60
|
+
if (!error) {
|
|
61
|
+
const lines = stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
62
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(lines[index]) as StartupHealth;
|
|
65
|
+
if (["native", "protected", "at-risk"].includes(parsed.status) && typeof parsed.rebootSafe === "boolean") {
|
|
66
|
+
resolve({ ...parsed, diagnosticStale: false });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
} catch { /* scan earlier output; config repair messages may precede JSON */ }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
resolve(cached ? markStartupHealthDiagnosticStale(cached.value) : conservativeFallback(config));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function refreshInBackground(config: Pick<OcxConfig, "codexAutoStart">): void {
|
|
78
|
+
if (inflight) return;
|
|
79
|
+
const startedGeneration = generation;
|
|
80
|
+
const probe = runProbe(config).then(value => {
|
|
81
|
+
if (startedGeneration === generation) cached = { timestamp: Date.now(), value };
|
|
82
|
+
return value;
|
|
83
|
+
});
|
|
84
|
+
inflight = probe.finally(() => {
|
|
85
|
+
if (inflight === probe || startedGeneration === generation) inflight = null;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Stale-while-revalidate: service-manager probes never hold open a model/UI request. */
|
|
90
|
+
export async function getCachedStartupHealth(config: Pick<OcxConfig, "codexAutoStart">): Promise<StartupHealth> {
|
|
91
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) return cached.value;
|
|
92
|
+
refreshInBackground(config);
|
|
93
|
+
return cached ? markStartupHealthDiagnosticStale(cached.value) : conservativeFallback(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function invalidateStartupHealthCache(): void {
|
|
97
|
+
generation += 1;
|
|
98
|
+
cached = null;
|
|
99
|
+
inflight = null;
|
|
100
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { durableBunPath } from "../lib/bun-runtime";
|
|
4
|
+
import type { WindowsTrayStatus } from "../tray/windows";
|
|
5
|
+
|
|
6
|
+
export type WindowsTrayAction = "install" | "start" | "stop" | "status" | "uninstall";
|
|
7
|
+
|
|
8
|
+
/** Run blocking registry/PowerShell waits outside the proxy event loop. */
|
|
9
|
+
export function runWindowsTrayAction(action: WindowsTrayAction): Promise<WindowsTrayStatus> {
|
|
10
|
+
if (action === "status") {
|
|
11
|
+
return import("../tray/windows").then(({ getWindowsTrayStatusAsync }) => getWindowsTrayStatusAsync());
|
|
12
|
+
}
|
|
13
|
+
const bun = durableBunPath();
|
|
14
|
+
const cli = join(import.meta.dir, "..", "cli", "index.ts");
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
execFile(bun, [cli, "tray", action, "--json"], {
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
env: process.env,
|
|
19
|
+
timeout: 30_000,
|
|
20
|
+
windowsHide: true,
|
|
21
|
+
maxBuffer: 256 * 1024,
|
|
22
|
+
}, (error, stdout, stderr) => {
|
|
23
|
+
const lines = stdout.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
24
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(lines[index]) as WindowsTrayStatus | { error?: unknown };
|
|
27
|
+
if ("supported" in parsed && typeof parsed.supported === "boolean") {
|
|
28
|
+
resolve(parsed);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const failure = parsed as { error?: unknown };
|
|
32
|
+
if (typeof failure.error === "string") {
|
|
33
|
+
reject(new Error(failure.error));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
} catch { /* scan earlier lines */ }
|
|
37
|
+
}
|
|
38
|
+
reject(new Error(error?.message || stderr.trim() || "Windows tray action returned no status"));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
package/src/service.ts
CHANGED
|
@@ -88,7 +88,7 @@ function normalizePathForCompare(path: string): string {
|
|
|
88
88
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
interface ServiceInstallState {
|
|
91
|
+
export interface ServiceInstallState {
|
|
92
92
|
version: 1 | 2;
|
|
93
93
|
codexHome: string;
|
|
94
94
|
opencodexHome: string;
|
|
@@ -101,6 +101,23 @@ interface ServiceInstallState {
|
|
|
101
101
|
winswSha256?: string;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
export function parseServiceInstallState(value: unknown): ServiceInstallState | null {
|
|
105
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
106
|
+
const state = value as Record<string, unknown>;
|
|
107
|
+
if (state.version !== 1 && state.version !== 2) return null;
|
|
108
|
+
if (typeof state.codexHome !== "string" || state.codexHome.length === 0) return null;
|
|
109
|
+
if (typeof state.opencodexHome !== "string" || state.opencodexHome.length === 0) return null;
|
|
110
|
+
for (const key of ["bunPath", "cliPath", "winswVersion", "winswSha256"] as const) {
|
|
111
|
+
if (state[key] !== undefined && (typeof state[key] !== "string" || state[key].length === 0)) return null;
|
|
112
|
+
}
|
|
113
|
+
if (state.version === 1) {
|
|
114
|
+
if (state.backend !== undefined) return null;
|
|
115
|
+
} else if (state.backend !== "scheduler" && state.backend !== "native") {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return state as unknown as ServiceInstallState;
|
|
119
|
+
}
|
|
120
|
+
|
|
104
121
|
function writeServiceInstallState(backend: ServiceBackend = "scheduler"): void {
|
|
105
122
|
const { bun, cli } = cliEntry();
|
|
106
123
|
const state: ServiceInstallState = {
|
|
@@ -124,8 +141,8 @@ function writeServiceInstallState(backend: ServiceBackend = "scheduler"): void {
|
|
|
124
141
|
function readServiceInstallState(): ServiceInstallState | null {
|
|
125
142
|
for (const path of serviceStatePaths()) {
|
|
126
143
|
try {
|
|
127
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"))
|
|
128
|
-
if (parsed
|
|
144
|
+
const parsed = parseServiceInstallState(JSON.parse(readFileSync(path, "utf8")));
|
|
145
|
+
if (parsed) return parsed;
|
|
129
146
|
} catch {
|
|
130
147
|
/* try the next known state path */
|
|
131
148
|
}
|
|
@@ -451,6 +468,30 @@ export function buildWindowsTaskXml(script = windowsServiceScriptPath(), launche
|
|
|
451
468
|
`;
|
|
452
469
|
}
|
|
453
470
|
|
|
471
|
+
function taskXmlSection(xml: string, tag: string): string {
|
|
472
|
+
return new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`, "i").exec(xml)?.[1] ?? "";
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** Validate the security/lifecycle-critical fields of the registered scheduler task. */
|
|
476
|
+
export function windowsTaskRegistrationHealthy(
|
|
477
|
+
xml: string,
|
|
478
|
+
wscript = windowsWscript(),
|
|
479
|
+
launcher = windowsLauncherVbsPath(),
|
|
480
|
+
): boolean {
|
|
481
|
+
const trigger = taskXmlSection(xml, "LogonTrigger");
|
|
482
|
+
const principal = taskXmlSection(xml, "Principal");
|
|
483
|
+
const settings = taskXmlSection(xml, "Settings");
|
|
484
|
+
const action = taskXmlSection(xml, "Exec");
|
|
485
|
+
return /<Enabled>\s*true\s*<\/Enabled>/i.test(trigger)
|
|
486
|
+
&& /<LogonType>\s*InteractiveToken\s*<\/LogonType>/i.test(principal)
|
|
487
|
+
&& /<RunLevel>\s*LeastPrivilege\s*<\/RunLevel>/i.test(principal)
|
|
488
|
+
&& /<Enabled>\s*true\s*<\/Enabled>/i.test(settings)
|
|
489
|
+
&& /<MultipleInstancesPolicy>\s*IgnoreNew\s*<\/MultipleInstancesPolicy>/i.test(settings)
|
|
490
|
+
&& /<ExecutionTimeLimit>\s*PT0S\s*<\/ExecutionTimeLimit>/i.test(settings)
|
|
491
|
+
&& action.includes(`<Command>${taskXmlString(wscript)}</Command>`)
|
|
492
|
+
&& action.includes(`<Arguments>${taskXmlString(`/b /nologo "${launcher}"`)}</Arguments>`);
|
|
493
|
+
}
|
|
494
|
+
|
|
454
495
|
// ── macOS (launchd) ──
|
|
455
496
|
function installLaunchd(): void {
|
|
456
497
|
const dir = join(homedir(), "Library", "LaunchAgents");
|
|
@@ -561,6 +602,7 @@ async function installWindowsNative(): Promise<void> {
|
|
|
561
602
|
function startWindows(): void { schtasks(["/run", "/tn", TASK]); }
|
|
562
603
|
function stopWindows(): void { try { schtasks(["/end", "/tn", TASK]); } catch { /* not running */ } }
|
|
563
604
|
function statusWindows(): string { try { return schtasks(["/query", "/tn", TASK]); } catch { return ""; } }
|
|
605
|
+
function statusWindowsXml(): string { try { return schtasks(["/query", "/tn", TASK, "/xml"]); } catch { return ""; } }
|
|
564
606
|
function uninstallWindows(): void {
|
|
565
607
|
try { schtasks(["/delete", "/tn", TASK, "/f"]); } catch { /* absent */ }
|
|
566
608
|
if (existsSync(windowsServiceScriptPath())) unlinkSync(windowsServiceScriptPath());
|
|
@@ -814,31 +856,141 @@ export function uninstallServiceIfInstalled(): boolean {
|
|
|
814
856
|
|
|
815
857
|
/** True if a background service (launchd/systemd/Task Scheduler) is installed. */
|
|
816
858
|
export function isServiceInstalled(): boolean {
|
|
817
|
-
return
|
|
859
|
+
return diagnoseService().installed;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export interface ServiceDiagnostic {
|
|
863
|
+
supported: boolean;
|
|
864
|
+
installed: boolean;
|
|
865
|
+
enabled: boolean;
|
|
866
|
+
running: boolean;
|
|
867
|
+
viable: boolean;
|
|
868
|
+
startable: boolean;
|
|
869
|
+
stale: boolean;
|
|
870
|
+
conflict: boolean;
|
|
871
|
+
backend: ServiceBackend | "launchd" | "systemd" | null;
|
|
872
|
+
summary: string;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/** Windows tray may restart a healthy-but-stopped native service; stale/conflicting installs remain blocked. */
|
|
876
|
+
export function serviceStartableFromTray(service: ServiceDiagnostic): boolean {
|
|
877
|
+
return service.startable && !service.stale && !service.conflict;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
export interface WindowsServiceDiagnosticInputs {
|
|
881
|
+
schedulerInstalled: boolean;
|
|
882
|
+
schedulerEnabled: boolean;
|
|
883
|
+
schedulerAssetsHealthy: boolean;
|
|
884
|
+
nativeStatus: "started" | "stopped" | "nonexistent" | "unknown";
|
|
885
|
+
recordedBackend: ServiceBackend | null;
|
|
886
|
+
staleBakedPaths: boolean;
|
|
887
|
+
nativeRepairAssetsOnly: boolean;
|
|
888
|
+
diagnostics: string;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export function deriveWindowsServiceDiagnostic(inputs: WindowsServiceDiagnosticInputs): ServiceDiagnostic {
|
|
892
|
+
const nativeInstalled = inputs.nativeStatus !== "nonexistent";
|
|
893
|
+
const conflict = inputs.schedulerInstalled && nativeInstalled;
|
|
894
|
+
const backendStateMismatch = inputs.schedulerInstalled
|
|
895
|
+
? inputs.recordedBackend !== "scheduler"
|
|
896
|
+
: nativeInstalled && inputs.recordedBackend !== "native";
|
|
897
|
+
const stale = inputs.staleBakedPaths
|
|
898
|
+
|| (inputs.schedulerInstalled && !inputs.schedulerAssetsHealthy)
|
|
899
|
+
|| backendStateMismatch
|
|
900
|
+
|| (inputs.nativeStatus === "nonexistent" && inputs.nativeRepairAssetsOnly);
|
|
901
|
+
const backend = inputs.schedulerInstalled ? "scheduler" : nativeInstalled ? "native" : null;
|
|
902
|
+
const enabled = inputs.schedulerInstalled ? inputs.schedulerEnabled : inputs.nativeStatus === "started";
|
|
903
|
+
const running = nativeInstalled ? inputs.nativeStatus === "started" : inputs.schedulerInstalled && inputs.schedulerEnabled;
|
|
904
|
+
const viable = !conflict && !stale
|
|
905
|
+
&& (inputs.schedulerInstalled ? inputs.schedulerEnabled && inputs.schedulerAssetsHealthy : inputs.nativeStatus === "started");
|
|
906
|
+
const startable = !conflict && !stale
|
|
907
|
+
&& (inputs.schedulerInstalled
|
|
908
|
+
? inputs.schedulerEnabled && inputs.schedulerAssetsHealthy
|
|
909
|
+
: inputs.nativeStatus === "started" || inputs.nativeStatus === "stopped");
|
|
910
|
+
const detail = conflict
|
|
911
|
+
? "CONFLICT: Task Scheduler and native WinSW are both present — run 'ocx service uninstall' then reinstall one"
|
|
912
|
+
: stale
|
|
913
|
+
? "stale or missing service assets — run 'ocx service install' to repair"
|
|
914
|
+
: inputs.schedulerInstalled
|
|
915
|
+
? inputs.schedulerEnabled ? "Task Scheduler enabled" : "Task Scheduler disabled"
|
|
916
|
+
: nativeInstalled
|
|
917
|
+
? `native (WinSW ${WINSW_VERSION}): ${inputs.nativeStatus}`
|
|
918
|
+
: "not installed";
|
|
919
|
+
const summary = backend ? `installed, ${detail} (${inputs.diagnostics})` : `not installed (${inputs.diagnostics})`;
|
|
920
|
+
return {
|
|
921
|
+
supported: true,
|
|
922
|
+
installed: inputs.schedulerInstalled || nativeInstalled,
|
|
923
|
+
enabled,
|
|
924
|
+
running,
|
|
925
|
+
viable,
|
|
926
|
+
startable,
|
|
927
|
+
stale,
|
|
928
|
+
conflict,
|
|
929
|
+
backend,
|
|
930
|
+
summary,
|
|
931
|
+
};
|
|
818
932
|
}
|
|
819
933
|
|
|
820
|
-
|
|
934
|
+
/**
|
|
935
|
+
* Fail-closed restart diagnostic. Presence alone is never enough: conflicting
|
|
936
|
+
* managers, stale baked paths, disabled registrations, and unknown/stopped
|
|
937
|
+
* native managers cannot claim that Codex will reconnect after a reboot.
|
|
938
|
+
*/
|
|
939
|
+
export function diagnoseService(): ServiceDiagnostic {
|
|
821
940
|
const diagnostics = serviceDiagnosticsSummary();
|
|
822
941
|
if (process.platform === "darwin") {
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
942
|
+
const installed = existsSync(plistPath());
|
|
943
|
+
const running = installed && Boolean(statusLaunchd());
|
|
944
|
+
const stale = installed && bakedServicePathsDiagnostic() !== null;
|
|
945
|
+
const viable = installed && running && !stale;
|
|
946
|
+
const summary = !installed ? `not installed (${diagnostics})`
|
|
947
|
+
: stale ? `installed, but stale (launchd; ${diagnostics})`
|
|
948
|
+
: running ? `installed and loaded (launchd; ${diagnostics})`
|
|
949
|
+
: `installed, not loaded (launchd; ${diagnostics})`;
|
|
950
|
+
return { supported: true, installed, enabled: running, running, viable, startable: installed && !stale, stale, conflict: false, backend: "launchd", summary };
|
|
826
951
|
}
|
|
827
952
|
if (process.platform === "win32") {
|
|
828
|
-
const
|
|
829
|
-
const
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
953
|
+
const schedulerXml = statusWindowsXml();
|
|
954
|
+
const schedulerInstalled = schedulerXml.length > 0;
|
|
955
|
+
const schedulerSettings = taskXmlSection(schedulerXml, "Settings");
|
|
956
|
+
const schedulerEnabled = schedulerInstalled && /<Enabled>\s*true\s*<\/Enabled>/i.test(schedulerSettings);
|
|
957
|
+
const schedulerAssets = [windowsServiceScriptPath(), windowsLauncherVbsPath(), windowsTaskXmlPath()].every(existsSync)
|
|
958
|
+
&& windowsTaskRegistrationHealthy(schedulerXml);
|
|
959
|
+
const nativeStatus = statusWinswRaw();
|
|
960
|
+
const installState = readServiceInstallState();
|
|
961
|
+
const recordedBackend: ServiceBackend | null = !installState
|
|
962
|
+
? null
|
|
963
|
+
: installState.backend === "native" ? "native" : "scheduler";
|
|
964
|
+
return deriveWindowsServiceDiagnostic({
|
|
965
|
+
schedulerInstalled,
|
|
966
|
+
schedulerEnabled,
|
|
967
|
+
schedulerAssetsHealthy: schedulerAssets,
|
|
968
|
+
nativeStatus,
|
|
969
|
+
recordedBackend,
|
|
970
|
+
staleBakedPaths: bakedServicePathsDiagnostic() !== null,
|
|
971
|
+
nativeRepairAssetsOnly: Boolean(winswStatusSummary()),
|
|
972
|
+
diagnostics,
|
|
973
|
+
});
|
|
833
974
|
}
|
|
834
975
|
if (process.platform === "linux") {
|
|
835
|
-
if (existsSync("/.dockerenv")) return "unsupported in Docker";
|
|
836
|
-
if (!isSystemd()) return "unsupported: systemd not found";
|
|
837
|
-
|
|
838
|
-
const
|
|
839
|
-
|
|
976
|
+
if (existsSync("/.dockerenv")) return { supported: false, installed: false, enabled: false, running: false, viable: false, startable: false, stale: false, conflict: false, backend: null, summary: "unsupported in Docker" };
|
|
977
|
+
if (!isSystemd()) return { supported: false, installed: false, enabled: false, running: false, viable: false, startable: false, stale: false, conflict: false, backend: null, summary: "unsupported: systemd not found" };
|
|
978
|
+
const installed = existsSync(unitPath());
|
|
979
|
+
const enabled = installed && (() => { try { return sh(`systemctl --user is-enabled ${TASK}`) === "enabled"; } catch { return false; } })();
|
|
980
|
+
const running = installed && (() => { try { return sh(`systemctl --user is-active ${TASK}`) === "active"; } catch { return false; } })();
|
|
981
|
+
const stale = installed && bakedServicePathsDiagnostic() !== null;
|
|
982
|
+
const viable = installed && enabled && running && !stale;
|
|
983
|
+
const summary = !installed ? `not installed (${diagnostics})`
|
|
984
|
+
: stale ? `installed, but stale (systemd user; ${diagnostics})`
|
|
985
|
+
: viable ? `installed, enabled and running (systemd user; ${diagnostics})`
|
|
986
|
+
: `installed, but ${!enabled ? "disabled" : "not running"} (systemd user; ${diagnostics})`;
|
|
987
|
+
return { supported: true, installed, enabled, running, viable, startable: installed && !stale, stale, conflict: false, backend: "systemd", summary };
|
|
840
988
|
}
|
|
841
|
-
return `unsupported on ${process.platform}
|
|
989
|
+
return { supported: false, installed: false, enabled: false, running: false, viable: false, startable: false, stale: false, conflict: false, backend: null, summary: `unsupported on ${process.platform}` };
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
export function serviceStatusSummary(): string {
|
|
993
|
+
return diagnoseService().summary;
|
|
842
994
|
}
|
|
843
995
|
|
|
844
996
|
export function normalizeServiceSubcommand(sub?: string): string {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|