@bitkyc08/opencodex 2.52.0 → 2.53.0-preview.20260913
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/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { restoreNativeCodexAsync } from "../codex/inject";
|
|
4
|
+
import { stripGrokConfig } from "../grok/inject";
|
|
5
|
+
import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
6
|
+
import { statusWinswRaw, type WinswStatus } from "../lib/winsw";
|
|
7
|
+
import { withWindowsServiceMutationLock } from "../lib/windows-service-mutation-lock";
|
|
8
|
+
import { maybeShowStarPrompt } from "../cli/star-prompt";
|
|
9
|
+
import { serviceStatusReport } from "./diagnostics";
|
|
10
|
+
import { assertServiceEnvironmentMatchesInstall, assertServiceAuthEnvironment } from "./guards";
|
|
11
|
+
import { resolveServiceListenPort, reportServiceServing } from "./health";
|
|
12
|
+
import { platformOps, proxyStillLiveAfterStop, stopTrackedProxyForServiceCommand, installServiceSafely, installFreshWindowsSchedulerSafely, removeServiceInstallState, isServiceInstalled } from "./orchestration";
|
|
13
|
+
import { repairService } from "./repair";
|
|
14
|
+
import type { ServiceRepairVerb } from "./repair";
|
|
15
|
+
import { TASK, plistPath, readServiceBackend } from "./state";
|
|
16
|
+
import type { ServiceBackend } from "./state";
|
|
17
|
+
import { unitPath } from "./systemd";
|
|
18
|
+
import { inspectWindowsSchedulerServiceStatus, schtasksErrorDetail, probeWindowsSchedulerTask } from "./windows-scheduler";
|
|
19
|
+
import type { WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
20
|
+
import { win32 } from "node:path";
|
|
21
|
+
import { serviceDiagnosticsSummary } from "./diagnostics";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* `restart` is NO LONGER folded into `repair`.
|
|
25
|
+
*
|
|
26
|
+
* It used to be, and on macOS that made it a lie: `repair` now returns early when the plist
|
|
27
|
+
* is already current and the job is loaded from it (#4236), so `ocx service restart` of a
|
|
28
|
+
* healthy service restarted nothing and the operator had to run `launchctl kickstart -k` by
|
|
29
|
+
* hand. The two verbs share the whole repair path and diverge only in `repairService`, which
|
|
30
|
+
* kicks the launchd job the no-op left running. A BARE `ocx service` still maps to `repair`
|
|
31
|
+
* (see {@link selectServiceSubcommand}): it is an idempotent "make it current", not a
|
|
32
|
+
* request to bounce a healthy hub.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeServiceSubcommand(sub?: string): string {
|
|
35
|
+
return sub ?? "install";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ParsedServiceArgs {
|
|
39
|
+
sub: string;
|
|
40
|
+
backend: ServiceBackend | null;
|
|
41
|
+
invalid: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type ServiceInstallationState = "installed" | "absent" | "unknown";
|
|
45
|
+
|
|
46
|
+
export interface ServiceInstallationProbe {
|
|
47
|
+
state: ServiceInstallationState;
|
|
48
|
+
detail?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ServiceInstallationProbeHooks {
|
|
52
|
+
platform?: NodeJS.Platform;
|
|
53
|
+
exists?: (path: string) => boolean;
|
|
54
|
+
probeWindowsTask?: () => WindowsSchedulerTaskProbe;
|
|
55
|
+
nativeStatus?: () => WinswStatus;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read only enough registration state to choose between install and repair.
|
|
60
|
+
* Windows must keep query failure distinct from proven absence: treating an
|
|
61
|
+
* unreadable scheduler/SCM as absent would send a bare command into the
|
|
62
|
+
* elevated registration path and recreate the original #2287 failure.
|
|
63
|
+
*/
|
|
64
|
+
export function probeServiceInstallation(
|
|
65
|
+
hooks: ServiceInstallationProbeHooks = {},
|
|
66
|
+
): ServiceInstallationProbe {
|
|
67
|
+
const platform = hooks.platform ?? process.platform;
|
|
68
|
+
const exists = hooks.exists ?? existsSync;
|
|
69
|
+
if (platform === "darwin") {
|
|
70
|
+
return { state: exists(plistPath()) ? "installed" : "absent" };
|
|
71
|
+
}
|
|
72
|
+
if (platform === "linux") {
|
|
73
|
+
return { state: exists(unitPath()) ? "installed" : "absent" };
|
|
74
|
+
}
|
|
75
|
+
if (platform !== "win32") return { state: "absent" };
|
|
76
|
+
|
|
77
|
+
let scheduler: WindowsSchedulerTaskProbe;
|
|
78
|
+
try {
|
|
79
|
+
scheduler = (hooks.probeWindowsTask ?? probeWindowsSchedulerTask)();
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
scheduler = { status: "unknown", detail: schtasksErrorDetail(cause) };
|
|
82
|
+
}
|
|
83
|
+
let native: WinswStatus;
|
|
84
|
+
try {
|
|
85
|
+
native = (hooks.nativeStatus ?? statusWinswRaw)();
|
|
86
|
+
} catch {
|
|
87
|
+
native = "unknown";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (scheduler.status === "present" || native === "started" || native === "stopped") {
|
|
91
|
+
return { state: "installed" };
|
|
92
|
+
}
|
|
93
|
+
if (scheduler.status === "unknown" || native === "unknown") {
|
|
94
|
+
const parts = [
|
|
95
|
+
scheduler.status === "unknown" ? `Task Scheduler: ${scheduler.detail}` : null,
|
|
96
|
+
native === "unknown" ? "WinSW status could not be determined" : null,
|
|
97
|
+
].filter((part): part is string => Boolean(part));
|
|
98
|
+
return { state: "unknown", detail: parts.join("; ") };
|
|
99
|
+
}
|
|
100
|
+
return { state: "absent" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* A bare invocation is an idempotent "make the installed service current"
|
|
105
|
+
* operation. First-time setup still installs, but an existing registration must
|
|
106
|
+
* use the repair path so Windows avoids unconditional elevated registration; repair may
|
|
107
|
+
* still refresh a stale scheduler definition.
|
|
108
|
+
* Backend flags remain an explicit install request because they select which
|
|
109
|
+
* registration mechanism to create.
|
|
110
|
+
*/
|
|
111
|
+
export function selectServiceSubcommand(
|
|
112
|
+
parsed: ParsedServiceArgs,
|
|
113
|
+
options: { hasExplicitSubcommand: boolean; installed: boolean },
|
|
114
|
+
): string {
|
|
115
|
+
if (!options.hasExplicitSubcommand && parsed.backend === null && options.installed) return "repair";
|
|
116
|
+
return parsed.sub;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type ServiceCommandPlan =
|
|
120
|
+
| { ok: true; parsed: ParsedServiceArgs; command: string }
|
|
121
|
+
| { ok: false; message: string };
|
|
122
|
+
|
|
123
|
+
export function planServiceCommand(
|
|
124
|
+
args: string[],
|
|
125
|
+
options: { platform?: NodeJS.Platform; probeInstallation?: () => ServiceInstallationProbe } = {},
|
|
126
|
+
): ServiceCommandPlan {
|
|
127
|
+
const parsed = parseServiceArgs(args);
|
|
128
|
+
if (parsed.invalid.length > 0) {
|
|
129
|
+
return { ok: false, message: `Unknown service option: ${parsed.invalid.join(" ")}` };
|
|
130
|
+
}
|
|
131
|
+
if (parsed.backend && parsed.sub !== "install") {
|
|
132
|
+
return { ok: false, message: "--native/--scheduler apply to `ocx service install` only; other subcommands use the installed backend." };
|
|
133
|
+
}
|
|
134
|
+
if (parsed.backend === "native" && (options.platform ?? process.platform) !== "win32") {
|
|
135
|
+
return { ok: false, message: "--native (WinSW) is Windows-only." };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hasExplicitSubcommand = args.some(arg => !arg.startsWith("--"));
|
|
139
|
+
let installed = false;
|
|
140
|
+
if (!hasExplicitSubcommand && parsed.backend === null) {
|
|
141
|
+
const probe = (options.probeInstallation ?? probeServiceInstallation)();
|
|
142
|
+
if (probe.state === "unknown") {
|
|
143
|
+
const suffix = probe.detail ? ` (${probe.detail})` : "";
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
message: `Could not safely determine whether the service is installed${suffix}. Run 'ocx service status' and retry; use explicit 'ocx service install' only after confirming it is absent.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
installed = probe.state === "installed";
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
parsed,
|
|
154
|
+
command: selectServiceSubcommand(parsed, { hasExplicitSubcommand, installed }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* `ocx service [sub] [--native|--scheduler]`. The first non-flag token is the
|
|
160
|
+
* subcommand; backend flags are only meaningful for `install` (validated by the caller).
|
|
161
|
+
*/
|
|
162
|
+
export function parseServiceArgs(args: string[]): ParsedServiceArgs {
|
|
163
|
+
let sub: string | undefined;
|
|
164
|
+
let backend: ServiceBackend | null = null;
|
|
165
|
+
const invalid: string[] = [];
|
|
166
|
+
for (const arg of args) {
|
|
167
|
+
if (arg === "--native") {
|
|
168
|
+
if (backend === "scheduler") { invalid.push("--native (conflicts with --scheduler)"); continue; }
|
|
169
|
+
backend = "native";
|
|
170
|
+
}
|
|
171
|
+
else if (arg === "--scheduler") {
|
|
172
|
+
if (backend === "native") { invalid.push("--scheduler (conflicts with --native)"); continue; }
|
|
173
|
+
backend = "scheduler";
|
|
174
|
+
}
|
|
175
|
+
else if (arg.startsWith("--")) invalid.push(arg);
|
|
176
|
+
else if (sub === undefined) sub = arg;
|
|
177
|
+
else invalid.push(arg);
|
|
178
|
+
}
|
|
179
|
+
return { sub: normalizeServiceSubcommand(sub), backend, invalid };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function serviceCommand(...args: (string | undefined)[]): Promise<void> {
|
|
183
|
+
const filteredArgs = args.filter((a): a is string => Boolean(a));
|
|
184
|
+
const execute = async (): Promise<void> => {
|
|
185
|
+
// Planning reads manager state. Repeat it only after the writer lock is held, otherwise a
|
|
186
|
+
// bare command can choose install from a snapshot another service command already changed.
|
|
187
|
+
const plan = planServiceCommand(filteredArgs);
|
|
188
|
+
if (!plan.ok) {
|
|
189
|
+
console.error(plan.message);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
const { parsed, command } = plan;
|
|
193
|
+
if (command === "repair" || command === "restart") {
|
|
194
|
+
const verb: ServiceRepairVerb = command === "restart" ? "restart" : "repair";
|
|
195
|
+
assertServiceEnvironmentMatchesInstall();
|
|
196
|
+
assertServiceAuthEnvironment();
|
|
197
|
+
// A throw used to escape straight to the top level, so the one command that can
|
|
198
|
+
// leave a macOS hub evicted never reached its own serving check (#4236, defect 1f).
|
|
199
|
+
// Report the failure, then still ask whether anything is listening: on darwin the
|
|
200
|
+
// rollback inside installLaunchd may have brought the previous job back, and on
|
|
201
|
+
// Windows the preserve/restart protocol may have done the same. The operator needs
|
|
202
|
+
// both halves of that answer, and the exit code stays non-zero either way.
|
|
203
|
+
let repairError: unknown;
|
|
204
|
+
try {
|
|
205
|
+
await repairService({ verb });
|
|
206
|
+
} catch (error) {
|
|
207
|
+
repairError = error;
|
|
208
|
+
console.error(`❌ Service ${verb} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
209
|
+
process.exitCode = 1;
|
|
210
|
+
}
|
|
211
|
+
// All three platforms: a repair that reports success while nothing serves is the
|
|
212
|
+
// defect class this unit exists to close. Windows bakes its port into the
|
|
213
|
+
// scheduler wrapper or the WinSW XML, both of which installedServiceListenPort()
|
|
214
|
+
// now reads.
|
|
215
|
+
await reportServiceServing(verb === "restart" ? "restarted" : "repaired");
|
|
216
|
+
if (repairError !== undefined) process.exitCode = 1;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
// Non-install subcommands follow the backend recorded at install time (state v2).
|
|
220
|
+
const backend: ServiceBackend = parsed.backend ?? (process.platform === "win32" ? readServiceBackend() : "scheduler");
|
|
221
|
+
const ops = platformOps(backend);
|
|
222
|
+
if (!ops) {
|
|
223
|
+
console.error("ocx service supports macOS (launchd), Windows (Task Scheduler), and Linux (systemd).");
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
switch (command) {
|
|
227
|
+
case "install":
|
|
228
|
+
assertServiceEnvironmentMatchesInstall();
|
|
229
|
+
assertServiceAuthEnvironment();
|
|
230
|
+
// A manually started proxy can still own the configured port while the service
|
|
231
|
+
// registration is absent or unloaded. Stop both the registered manager and any
|
|
232
|
+
// tracked standalone listener before loading the freshly written service assets.
|
|
233
|
+
// Otherwise launchd/Task Scheduler can register successfully while its child
|
|
234
|
+
// restart-loops on EADDRINUSE, and the old standalone process makes the install
|
|
235
|
+
// verification report a false success.
|
|
236
|
+
try {
|
|
237
|
+
if (process.platform === "win32" && backend === "scheduler") {
|
|
238
|
+
const scheduler = probeWindowsSchedulerTask(TASK);
|
|
239
|
+
if (scheduler.status === "unknown") {
|
|
240
|
+
throw new Error(`Task Scheduler state could not be verified before install: ${scheduler.detail}`);
|
|
241
|
+
}
|
|
242
|
+
if (scheduler.status === "absent") {
|
|
243
|
+
await installFreshWindowsSchedulerSafely();
|
|
244
|
+
} else {
|
|
245
|
+
await installServiceSafely(backend, ops.install);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
await installServiceSafely(backend, ops.install);
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`❌ Service install cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
// The wrapper was written moments ago in this process, so the configured port
|
|
256
|
+
// and the baked one cannot have diverged yet — unlike `start`, which reads the
|
|
257
|
+
// installed artifact instead.
|
|
258
|
+
await reportServiceServing("installed", { port: resolveServiceListenPort() });
|
|
259
|
+
if (process.platform === "linux") console.log(" For auto-start on boot: loginctl enable-linger $USER");
|
|
260
|
+
// Service users never reach the `ocx start` prompt: the proxy they run is the
|
|
261
|
+
// supervised child, which always carries OCX_SERVICE=1. This command, though, is
|
|
262
|
+
// hand-typed in a real terminal, so it is the one interactive moment they get.
|
|
263
|
+
// Same one-time marker and same guards (TTY, gh auth, agent deferral) apply.
|
|
264
|
+
await maybeShowStarPrompt();
|
|
265
|
+
break;
|
|
266
|
+
case "start":
|
|
267
|
+
ops.start();
|
|
268
|
+
await reportServiceServing("started");
|
|
269
|
+
break;
|
|
270
|
+
case "stop": {
|
|
271
|
+
assertServiceEnvironmentMatchesInstall();
|
|
272
|
+
// Only stop what is actually installed. The unguarded call ran a real `launchctl unload`
|
|
273
|
+
// (and its Windows/Linux twins) even with nothing installed.
|
|
274
|
+
if (ops.status() !== null || isServiceInstalled()) {
|
|
275
|
+
ops.stop();
|
|
276
|
+
}
|
|
277
|
+
await stopTrackedProxyForServiceCommand();
|
|
278
|
+
{
|
|
279
|
+
// Verify rather than trust the stop command: a surviving wrapper respawns its child
|
|
280
|
+
// seconds later, and restoring native Codex on top of a live proxy is the failure #764
|
|
281
|
+
// reports as "stop reports success without stopping the proxy".
|
|
282
|
+
const survivor = await proxyStillLiveAfterStop();
|
|
283
|
+
if (survivor) {
|
|
284
|
+
console.error(
|
|
285
|
+
`❌ service stop did not take effect: a proxy is still listening on port ${survivor.port}.`
|
|
286
|
+
+ "\nNative Codex was NOT restored, because doing so while the proxy is running leaves"
|
|
287
|
+
+ " both pointing at each other. Check for a second service backend (`ocx service status`)"
|
|
288
|
+
+ " or a manually started proxy, then re-run `ocx service stop`.",
|
|
289
|
+
);
|
|
290
|
+
process.exitCode = 1;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
const restore = await restoreNativeCodexAsync();
|
|
294
|
+
if (restore.success) console.log("✅ service stopped + native Codex restored.");
|
|
295
|
+
else console.error(`⚠️ service stopped, but native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` (or check $CODEX_HOME/config.toml) before using native Codex.`);
|
|
296
|
+
if (!restore.success) process.exitCode = 1;
|
|
297
|
+
// The Grok fence is the other managed config this command owns. Leaving it behind
|
|
298
|
+
// pointed grok at a dead endpoint while native Codex was already restored.
|
|
299
|
+
const grok = stripGrokConfig();
|
|
300
|
+
if (grok.changed) console.log(`↩️ ${grok.message}`);
|
|
301
|
+
else if (!grok.ok) {
|
|
302
|
+
// A failed strip leaves Grok aimed at a proxy this command just stopped. Exiting
|
|
303
|
+
// 0 tells a script the teardown finished when half of it did not.
|
|
304
|
+
console.error(`⚠️ ${grok.message}`);
|
|
305
|
+
process.exitCode = 1;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "status": {
|
|
311
|
+
if (process.platform === "win32" && backend === "scheduler") {
|
|
312
|
+
console.log(await inspectWindowsSchedulerServiceStatus());
|
|
313
|
+
} else {
|
|
314
|
+
// Replaces raw `ops.status()` output, which on darwin is a `launchctl list`
|
|
315
|
+
// line: registration reported as if it were service. serviceStatusReport
|
|
316
|
+
// subsumes the not-installed case and adds the serving / stale-plist split.
|
|
317
|
+
console.log(await serviceStatusReport());
|
|
318
|
+
}
|
|
319
|
+
console.log(`Diagnostics: ${serviceDiagnosticsSummary()}`);
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case "uninstall":
|
|
323
|
+
case "remove":
|
|
324
|
+
assertServiceEnvironmentMatchesInstall();
|
|
325
|
+
try { ops.stop(); } catch (err) {
|
|
326
|
+
console.warn(`⚠️ Service stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
327
|
+
}
|
|
328
|
+
await stopTrackedProxyForServiceCommand();
|
|
329
|
+
try {
|
|
330
|
+
ops.uninstall();
|
|
331
|
+
} catch (err) {
|
|
332
|
+
console.error(`❌ Service uninstall failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
333
|
+
console.error("The service may still be installed. Check with 'ocx service status' or remove manually.");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
{
|
|
337
|
+
const restore = await restoreNativeCodexAsync();
|
|
338
|
+
if (!restore.success) {
|
|
339
|
+
console.error(`⚠️ native Codex restore FAILED: ${restore.message}\nRun \`ocx restore\` before using native Codex.`);
|
|
340
|
+
process.exitCode = 1;
|
|
341
|
+
}
|
|
342
|
+
const grok = stripGrokConfig();
|
|
343
|
+
if (grok.changed) console.log(`↩️ ${grok.message}`);
|
|
344
|
+
else if (!grok.ok) {
|
|
345
|
+
console.error(`⚠️ ${grok.message}`);
|
|
346
|
+
process.exitCode = 1;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
removeServiceInstallState();
|
|
350
|
+
try { if (existsSync(serviceApiTokenFilePath())) unlinkSync(serviceApiTokenFilePath()); } catch { /* best-effort */ }
|
|
351
|
+
console.log("✅ service uninstalled.");
|
|
352
|
+
break;
|
|
353
|
+
default:
|
|
354
|
+
console.error("Usage: ocx service [install|repair|restart|start|stop|status|uninstall|remove] [--native|--scheduler]");
|
|
355
|
+
console.error(" With no subcommand, installs when absent or repairs/restarts an existing service.");
|
|
356
|
+
console.error(" repair: refresh the installed backend, reloading it only when the definition changed; stale Windows tasks may request admin approval.");
|
|
357
|
+
console.error(" restart: the same refresh, but always restarts the service — on macOS a healthy job is kickstarted in place.");
|
|
358
|
+
console.error(" --native (Windows only): register a real SCM service via WinSW instead of Task Scheduler.");
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const preliminary = parseServiceArgs(filteredArgs);
|
|
364
|
+
const windowsMutation = process.platform === "win32"
|
|
365
|
+
&& preliminary.invalid.length === 0
|
|
366
|
+
&& preliminary.sub !== "status";
|
|
367
|
+
if (windowsMutation) {
|
|
368
|
+
await withWindowsServiceMutationLock(execute);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
await execute();
|
|
372
|
+
}
|