@bitkyc08/opencodex 2.52.0 → 2.53.0
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-BlO4Yl6q.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,334 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { defaultWinswEntry, installWinswService } from "../lib/winsw";
|
|
3
|
+
import { resolveWindowsTaskDiagnosticUserId, diagnoseService } from "./diagnostics";
|
|
4
|
+
import type { ServiceDiagnostic } from "./diagnostics";
|
|
5
|
+
import { assertServiceEnvironmentMatchesInstall, assertServiceAuthEnvironment } from "./guards";
|
|
6
|
+
import { installLaunchd, restartLaunchdJob } from "./launchd";
|
|
7
|
+
import type { LaunchdInstallOutcome } from "./launchd";
|
|
8
|
+
import { TASK, writeServiceInstallState, serviceSourceDir } from "./state";
|
|
9
|
+
import { installSystemd } from "./systemd";
|
|
10
|
+
import { writeWindowsSchedulerAssets, reregisterWindowsSchedulerTask, windowsSchedulerRegistrationMatchesSnapshot, restoreWindowsSchedulerTaskIfAbsent, startWindows, stopWindows, statusWindowsXml } from "./windows-ops";
|
|
11
|
+
import { probeWindowsSchedulerTask, SCHEDULER_SETTLE_DELAYS_MS, settleDelay } from "./windows-scheduler";
|
|
12
|
+
import type { WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
13
|
+
import { taskXmlSection, taskXmlWithoutCommentsAndCdata, taskXmlElementCount, taskXmlOptionalValueEquals, windowsTaskRegistrationOwnedByAttempt, windowsTaskHasSessionRecoveryTriggers, windowsTaskRegistrationHealthy, windowsTaskRegistrationRefreshableLegacy } from "./windows-taskxml";
|
|
14
|
+
import type { ExpectedWindowsTaskUserId } from "./windows-taskxml";
|
|
15
|
+
import { win32 } from "node:path";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The two CLI verbs `repairService` serves. They differ on ONE platform and ONE case: a
|
|
19
|
+
* macOS job that is already loaded from the current plist, which `repair` must not touch and
|
|
20
|
+
* `restart` must restart.
|
|
21
|
+
*/
|
|
22
|
+
export type ServiceRepairVerb = "repair" | "restart";
|
|
23
|
+
|
|
24
|
+
export interface RepairServiceDeps {
|
|
25
|
+
diagnose?: () => ServiceDiagnostic;
|
|
26
|
+
assertEnv?: () => void;
|
|
27
|
+
assertAuth?: () => void;
|
|
28
|
+
writeSchedulerAssets?: () => void;
|
|
29
|
+
stopScheduler?: () => void;
|
|
30
|
+
startScheduler?: () => void;
|
|
31
|
+
writeSchedulerState?: () => void;
|
|
32
|
+
writeNativeState?: () => void;
|
|
33
|
+
repairNative?: () => void | Promise<void>;
|
|
34
|
+
repairLaunchd?: () => LaunchdInstallOutcome | void;
|
|
35
|
+
repairSystemd?: () => void;
|
|
36
|
+
/** Restarts a launchd job the install path deliberately left alone. `restart` only. */
|
|
37
|
+
restartLaunchd?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Which CLI verb is being served. `repair` must leave a healthy service alone — that no-op
|
|
40
|
+
* IS the #4236 fix — while `restart` promises a new process, so on darwin it kicks the job
|
|
41
|
+
* the no-op path did not touch. Windows (stop + start) and Linux
|
|
42
|
+
* (`systemctl --user restart`) already restart unconditionally, so neither reads this.
|
|
43
|
+
*/
|
|
44
|
+
verb?: ServiceRepairVerb;
|
|
45
|
+
/** Reads live registered task XML; may be called again after failure, empty when unreadable. */
|
|
46
|
+
readSchedulerXml?: () => string;
|
|
47
|
+
/** Bounded wait before retrying an unreadable live registration snapshot. */
|
|
48
|
+
settleSchedulerRead?: (delayMs: number) => void | Promise<void>;
|
|
49
|
+
/** Proves fixed-name task presence when its live XML is empty or unreadable. */
|
|
50
|
+
probeScheduler?: () => WindowsSchedulerTaskProbe;
|
|
51
|
+
/** Re-registers the task from freshly staged XML. Used only when the definition is stale. */
|
|
52
|
+
reregisterScheduler?: (attemptNonce: string, expectedExistingXml: string) => Promise<void>;
|
|
53
|
+
/** Publishes the captured registration only when the fixed task name remains absent. */
|
|
54
|
+
restoreSchedulerIfAbsent?: (registeredXml: string) => Promise<void>;
|
|
55
|
+
/** Resolves the account the registered triggers must match; null when it cannot be resolved. */
|
|
56
|
+
resolveExpectedUserId?: (registeredXml: string) => ExpectedWindowsTaskUserId | null;
|
|
57
|
+
/** Exact scheduler action values used by validation; defaults to the installed paths. */
|
|
58
|
+
schedulerWscript?: string;
|
|
59
|
+
schedulerLauncher?: string;
|
|
60
|
+
/** Test seam — defaults to process.platform so Linux CI cannot hit real installSystemd. */
|
|
61
|
+
platform?: NodeJS.Platform;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function assertSchedulerSnapshotBeforeStart(
|
|
65
|
+
readSchedulerXml: () => string,
|
|
66
|
+
expectedXml: string,
|
|
67
|
+
settle: (delayMs: number) => void | Promise<void>,
|
|
68
|
+
changedMessage: string,
|
|
69
|
+
unreadableMessage: string,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
await assertSchedulerRegistrationBeforeStart(
|
|
72
|
+
readSchedulerXml,
|
|
73
|
+
settle,
|
|
74
|
+
currentXml => windowsSchedulerRegistrationMatchesSnapshot(currentXml, expectedXml),
|
|
75
|
+
changedMessage,
|
|
76
|
+
unreadableMessage,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function assertSchedulerRegistrationBeforeStart(
|
|
81
|
+
readSchedulerXml: () => string,
|
|
82
|
+
settle: (delayMs: number) => void | Promise<void>,
|
|
83
|
+
matchesExpected: (currentXml: string) => boolean,
|
|
84
|
+
changedMessage: string,
|
|
85
|
+
unreadableMessage: string,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
for (let attempt = 0; attempt <= SCHEDULER_SETTLE_DELAYS_MS.length; attempt += 1) {
|
|
88
|
+
let beforeStartXml = "";
|
|
89
|
+
try {
|
|
90
|
+
beforeStartXml = readSchedulerXml();
|
|
91
|
+
} catch {
|
|
92
|
+
// Treat query errors like the default reader's empty result and retry below.
|
|
93
|
+
}
|
|
94
|
+
if (beforeStartXml.trim()) {
|
|
95
|
+
if (!matchesExpected(beforeStartXml)) {
|
|
96
|
+
throw new Error(changedMessage);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const delayMs = SCHEDULER_SETTLE_DELAYS_MS[attempt];
|
|
101
|
+
if (delayMs === undefined) break;
|
|
102
|
+
await settle(delayMs);
|
|
103
|
+
}
|
|
104
|
+
throw new Error(unreadableMessage);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Repair the already-installed background-service backend without switching managers.
|
|
109
|
+
*
|
|
110
|
+
* Windows scheduler: rewrite assets + stop/start; stale definitions are refreshed and may elevate.
|
|
111
|
+
* Windows native: WinSW asset rewrite + restart (skips `install /p` when present).
|
|
112
|
+
* macOS/Linux: re-run the user-level install/reload path.
|
|
113
|
+
*/
|
|
114
|
+
export async function repairService(deps: RepairServiceDeps = {}): Promise<void> {
|
|
115
|
+
const diagnose = deps.diagnose ?? diagnoseService;
|
|
116
|
+
const platform = deps.platform ?? process.platform;
|
|
117
|
+
const diag = diagnose();
|
|
118
|
+
if (!diag.supported) {
|
|
119
|
+
throw new Error(`Background service is unsupported (${diag.summary}).`);
|
|
120
|
+
}
|
|
121
|
+
if (diag.conflict) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"Cannot repair while Task Scheduler and native WinSW are both present. "
|
|
124
|
+
+ "Run 'ocx service uninstall' then reinstall one backend with 'ocx service install'.",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (!diag.installed) {
|
|
128
|
+
throw new Error("Background service is not installed. Run 'ocx service install' first.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
(deps.assertEnv ?? assertServiceEnvironmentMatchesInstall)();
|
|
132
|
+
(deps.assertAuth ?? assertServiceAuthEnvironment)();
|
|
133
|
+
|
|
134
|
+
if (platform === "win32") {
|
|
135
|
+
if (diag.backend === "native") {
|
|
136
|
+
await (deps.repairNative ?? (() => installWinswService(defaultWinswEntry(serviceSourceDir))))();
|
|
137
|
+
(deps.writeNativeState ?? (() => writeServiceInstallState("native")))();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const readSchedulerXml = deps.readSchedulerXml ?? statusWindowsXml;
|
|
141
|
+
let registeredXml = "";
|
|
142
|
+
try {
|
|
143
|
+
registeredXml = readSchedulerXml();
|
|
144
|
+
} catch {
|
|
145
|
+
throw new Error(
|
|
146
|
+
"Task Scheduler registration could not be read; repair stopped before changing or starting the service.",
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
if (!registeredXml.trim()) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
"Task Scheduler registration is empty or unreadable; repair stopped before changing or starting the service.",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
// Judge the definition against the same effective account the diagnostic uses. Relying on
|
|
155
|
+
// the cached identity alone would make a scoped task this very version wrote look foreign
|
|
156
|
+
// in a fresh process, and the message below would then name the wrong cause.
|
|
157
|
+
const expectedUserId = (deps.resolveExpectedUserId ?? resolveWindowsTaskDiagnosticUserId)(registeredXml);
|
|
158
|
+
const registrationHealthy = windowsTaskRegistrationHealthy(
|
|
159
|
+
registeredXml,
|
|
160
|
+
deps.schedulerWscript,
|
|
161
|
+
deps.schedulerLauncher,
|
|
162
|
+
expectedUserId,
|
|
163
|
+
);
|
|
164
|
+
const expectedValues = expectedUserId === null
|
|
165
|
+
? []
|
|
166
|
+
: typeof expectedUserId === "string" ? [expectedUserId] : expectedUserId;
|
|
167
|
+
const preferredSid = expectedValues[0];
|
|
168
|
+
const triggers = taskXmlSection(taskXmlWithoutCommentsAndCdata(registeredXml), "Triggers");
|
|
169
|
+
// An exact legacy account name is safe to recognize, but rewrite it to the
|
|
170
|
+
// locale-independent SID while repair already owns the mutation boundary.
|
|
171
|
+
const identityUpgradeNeeded = registrationHealthy
|
|
172
|
+
&& preferredSid !== undefined
|
|
173
|
+
&& !windowsTaskHasSessionRecoveryTriggers(triggers, preferredSid);
|
|
174
|
+
// Omitted Priority also defaults to 7; background priority can starve health probes under CPU load.
|
|
175
|
+
const priorityUpgradeNeeded = registrationHealthy && taskXmlOptionalValueEquals(
|
|
176
|
+
taskXmlSection(taskXmlWithoutCommentsAndCdata(registeredXml), "Settings"), "Priority", "7",
|
|
177
|
+
);
|
|
178
|
+
const refreshableLegacy = windowsTaskRegistrationRefreshableLegacy(
|
|
179
|
+
registeredXml,
|
|
180
|
+
deps.schedulerWscript,
|
|
181
|
+
deps.schedulerLauncher,
|
|
182
|
+
);
|
|
183
|
+
if (!registrationHealthy && !refreshableLegacy) {
|
|
184
|
+
const scopedButUnresolved = expectedUserId === null
|
|
185
|
+
&& taskXmlElementCount(
|
|
186
|
+
taskXmlSection(taskXmlWithoutCommentsAndCdata(registeredXml), "Triggers"),
|
|
187
|
+
"UserId",
|
|
188
|
+
) > 0;
|
|
189
|
+
throw new Error(
|
|
190
|
+
scopedButUnresolved
|
|
191
|
+
? "The registered Task Scheduler triggers name an account, but the current Windows identity could not be resolved, so the registration could not be verified. "
|
|
192
|
+
+ "It was preserved and not replaced; re-run repair once the account can be resolved."
|
|
193
|
+
: "Task Scheduler registration is not a recognized legacy OpenCodex definition; it was preserved for manual review.",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
try { (deps.stopScheduler ?? stopWindows)(); } catch { /* not running */ }
|
|
197
|
+
(deps.writeSchedulerAssets ?? writeWindowsSchedulerAssets)();
|
|
198
|
+
// Rewriting the on-disk assets does not touch the definition Task Scheduler holds, so a
|
|
199
|
+
// task registered by an older version keeps its old triggers forever: status reports it
|
|
200
|
+
// stale, tells the user to run repair, and repair changes nothing it complains about.
|
|
201
|
+
// Re-register only when the registered XML is actually stale, so the ordinary repair
|
|
202
|
+
// stays free of `schtasks /create` and its UAC prompt.
|
|
203
|
+
let startExpectedXml = registeredXml;
|
|
204
|
+
if (!registrationHealthy || identityUpgradeNeeded || priorityUpgradeNeeded) {
|
|
205
|
+
// The task was stopped above, so a failed replacement must not exit here: `/create /f`
|
|
206
|
+
// can be rejected, elevation can be cancelled, and staging or verification can fail.
|
|
207
|
+
// Any of those would leave a previously runnable proxy stopped and the user worse off
|
|
208
|
+
// than before the repair. Restart the definition still registered and surface the
|
|
209
|
+
// original failure instead.
|
|
210
|
+
const attemptNonce = randomUUID();
|
|
211
|
+
try {
|
|
212
|
+
await (deps.reregisterScheduler ?? reregisterWindowsSchedulerTask)(attemptNonce, registeredXml);
|
|
213
|
+
let replacementXml = "";
|
|
214
|
+
try {
|
|
215
|
+
replacementXml = readSchedulerXml();
|
|
216
|
+
} catch {
|
|
217
|
+
throw new Error("The refreshed Task Scheduler registration could not be read back.");
|
|
218
|
+
}
|
|
219
|
+
if (
|
|
220
|
+
!windowsTaskRegistrationHealthy(replacementXml)
|
|
221
|
+
|| !windowsTaskRegistrationOwnedByAttempt(replacementXml, attemptNonce)
|
|
222
|
+
) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"The refreshed Task Scheduler registration failed live shape or attempt-ownership verification.",
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
startExpectedXml = replacementXml;
|
|
228
|
+
} catch (err) {
|
|
229
|
+
const recoveryErrors: unknown[] = [];
|
|
230
|
+
let restartExpectedXml: string | null = null;
|
|
231
|
+
let currentXml: string | null = null;
|
|
232
|
+
try {
|
|
233
|
+
currentXml = readSchedulerXml();
|
|
234
|
+
} catch {
|
|
235
|
+
recoveryErrors.push(new Error(
|
|
236
|
+
"Task Scheduler state became unreadable after the failed replacement; it was preserved and not started.",
|
|
237
|
+
));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (currentXml !== null) {
|
|
241
|
+
if (windowsSchedulerRegistrationMatchesSnapshot(currentXml, registeredXml)) {
|
|
242
|
+
restartExpectedXml = registeredXml;
|
|
243
|
+
} else if (currentXml.trim()) {
|
|
244
|
+
const attemptOwned = windowsTaskRegistrationOwnedByAttempt(currentXml, attemptNonce);
|
|
245
|
+
if (attemptOwned && windowsTaskRegistrationHealthy(currentXml)) {
|
|
246
|
+
restartExpectedXml = currentXml;
|
|
247
|
+
} else {
|
|
248
|
+
recoveryErrors.push(new Error(
|
|
249
|
+
attemptOwned
|
|
250
|
+
? "The failed repair left an unhealthy attempt-owned registration; it was preserved and not started."
|
|
251
|
+
: windowsTaskRegistrationHealthy(currentXml)
|
|
252
|
+
? "A different healthy OpenCodex Task Scheduler registration appeared during repair; it was preserved and not started."
|
|
253
|
+
: "A different or unhealthy Task Scheduler registration appeared during repair; it was preserved and not started.",
|
|
254
|
+
));
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
let probe: WindowsSchedulerTaskProbe;
|
|
258
|
+
try {
|
|
259
|
+
probe = (deps.probeScheduler ?? (() => probeWindowsSchedulerTask(TASK)))();
|
|
260
|
+
} catch {
|
|
261
|
+
probe = { status: "unknown", detail: "presence probe failed" };
|
|
262
|
+
}
|
|
263
|
+
if (probe.status === "absent") {
|
|
264
|
+
try {
|
|
265
|
+
await (deps.restoreSchedulerIfAbsent ?? restoreWindowsSchedulerTaskIfAbsent)(registeredXml);
|
|
266
|
+
restartExpectedXml = registeredXml;
|
|
267
|
+
} catch (error) {
|
|
268
|
+
recoveryErrors.push(error);
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
recoveryErrors.push(new Error(probe.status === "present"
|
|
272
|
+
? "A Task Scheduler registration is present but its XML is unreadable; it was preserved and not started."
|
|
273
|
+
: `Task Scheduler state is unknown after the failed replacement (${probe.detail}); no registration was overwritten or started.`));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (restartExpectedXml !== null) {
|
|
279
|
+
try {
|
|
280
|
+
await assertSchedulerSnapshotBeforeStart(
|
|
281
|
+
readSchedulerXml,
|
|
282
|
+
restartExpectedXml,
|
|
283
|
+
deps.settleSchedulerRead ?? settleDelay,
|
|
284
|
+
"The Task Scheduler registration changed again before restart; the newer definition was preserved and not started.",
|
|
285
|
+
"Task Scheduler state remained unreadable before restart; the registration was preserved and not started.",
|
|
286
|
+
);
|
|
287
|
+
(deps.startScheduler ?? startWindows)();
|
|
288
|
+
} catch (error) {
|
|
289
|
+
recoveryErrors.push(error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (recoveryErrors.length > 0) {
|
|
293
|
+
throw new AggregateError(
|
|
294
|
+
[err, ...recoveryErrors],
|
|
295
|
+
"Task Scheduler repair failed; concurrent or unverified scheduler state was preserved.",
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
throw err;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// The final live read is the proof that `/run` still targets the definition this repair
|
|
302
|
+
// verified. A failed `schtasks /query` becomes an empty string, so allow only a bounded
|
|
303
|
+
// retry for that unreadable state. A readable mismatch is authoritative and fails
|
|
304
|
+
// immediately; presence alone cannot prove that the fixed-name task still has our XML.
|
|
305
|
+
await assertSchedulerSnapshotBeforeStart(
|
|
306
|
+
readSchedulerXml,
|
|
307
|
+
startExpectedXml,
|
|
308
|
+
deps.settleSchedulerRead ?? settleDelay,
|
|
309
|
+
"Task Scheduler registration changed before restart; the current definition was preserved and not started.",
|
|
310
|
+
"Task Scheduler registration became unreadable before restart; it was preserved and not started.",
|
|
311
|
+
);
|
|
312
|
+
(deps.startScheduler ?? startWindows)();
|
|
313
|
+
(deps.writeSchedulerState ?? (() => writeServiceInstallState("scheduler")))();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (platform === "darwin") {
|
|
317
|
+
const outcome = (deps.repairLaunchd ?? installLaunchd)();
|
|
318
|
+
// `installLaunchd` is the only function that knows whether it published anything, and its
|
|
319
|
+
// no-op path leaves the live process running on purpose. `repair` wants exactly that;
|
|
320
|
+
// `restart` would otherwise restart NOTHING on a healthy hub and send the operator to run
|
|
321
|
+
// `launchctl kickstart -k` by hand, which is the opposite of what these verbs are for.
|
|
322
|
+
if ((deps.verb ?? "repair") === "restart" && outcome?.reloaded === false) {
|
|
323
|
+
(deps.restartLaunchd ?? restartLaunchdJob)();
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (platform === "linux") {
|
|
328
|
+
// `installSystemd` ends in `systemctl --user restart`, unconditionally, so the unit is
|
|
329
|
+
// restarted whichever verb asked — there is no no-op path here to compensate for.
|
|
330
|
+
(deps.repairSystemd ?? installSystemd)();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
throw new Error(`Background service repair is unsupported on ${platform}.`);
|
|
334
|
+
}
|