@bitkyc08/opencodex 2.65.0 → 2.66.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/README.md +14 -0
- package/gui/dist/assets/App-DyHlFeNB.js +51 -0
- package/gui/dist/assets/App-gkoJIEOV.css +1 -0
- package/gui/dist/assets/{Tray-D4bA1vff.js → Tray-CI8cVKWu.js} +1 -1
- package/gui/dist/assets/index-BiwnVAUm.js +86 -0
- package/gui/dist/assets/index-ComVsdSk.css +1 -0
- package/gui/dist/assets/{tray-data-BZXhB3qz.js → tray-data-_WyYzbMt.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +80 -0
- package/src/adapters/anthropic/passthrough.ts +221 -0
- package/src/adapters/anthropic-image-codec.ts +56 -19
- package/src/adapters/anthropic-image-normalize.ts +20 -10
- package/src/adapters/anthropic.ts +55 -24
- package/src/adapters/coding-agent/protocol.ts +29 -19
- package/src/adapters/cursor/live-transport.ts +18 -3
- package/src/adapters/cursor/native-exec-shell.ts +18 -63
- package/src/adapters/cursor/native-exec.ts +5 -2
- package/src/adapters/cursor/native-foreground-shell.ts +44 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -1
- package/src/adapters/exec-tool-result-normalize.ts +4 -1
- package/src/adapters/google.ts +12 -0
- package/src/adapters/ollama-native.ts +32 -1
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +131 -34
- package/src/adapters/openai-chat.ts +6 -4
- package/src/adapters/openai-responses/passthrough.ts +1 -0
- package/src/adapters/openai-responses/reasoning.ts +45 -1
- package/src/bridge/response-json.ts +10 -2
- package/src/chat/outbound.ts +104 -52
- package/src/claude/claude-code-block.ts +16 -0
- package/src/claude/context-windows.ts +52 -4
- package/src/claude/desktop-first-party.ts +25 -8
- package/src/claude/inbound.ts +22 -0
- package/src/claude/intercept/connect-proxy.ts +37 -4
- package/src/claude/intercept/proxy-auth.ts +166 -0
- package/src/claude/intercept/runtime.ts +21 -0
- package/src/claude/intercept/settings.ts +33 -12
- package/src/claude/outbound.ts +45 -27
- package/src/cli/account-extended.ts +47 -15
- package/src/cli/account-target.ts +115 -0
- package/src/cli/account.ts +20 -28
- package/src/cli/agent.ts +10 -1
- package/src/cli/api-protocols.ts +206 -0
- package/src/cli/capabilities.ts +86 -0
- package/src/cli/combo.ts +2 -2
- package/src/cli/connect.ts +127 -3
- package/src/cli/dispatch.ts +8 -0
- package/src/cli/doctor.ts +33 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/link.ts +288 -0
- package/src/cli/registry.ts +23 -0
- package/src/cli/runtime-api.ts +79 -0
- package/src/cli/system-command.ts +21 -2
- package/src/client/connect.ts +125 -23
- package/src/client/hub-relay.ts +12 -8
- package/src/client/link-join.ts +323 -0
- package/src/client/link-relay.ts +241 -0
- package/src/client/link-state.ts +110 -0
- package/src/client/link-teardown.ts +63 -0
- package/src/client/link-tunnel.ts +465 -0
- package/src/client/machine-listener.ts +14 -5
- package/src/client/runtime.ts +91 -42
- package/src/client/state.ts +4 -0
- package/src/codex/catalog/build-entries.ts +4 -0
- package/src/codex/catalog/derive-entry.ts +5 -0
- package/src/codex/catalog/parsing.ts +4 -0
- package/src/codex/catalog/retained-sync.ts +4 -1
- package/src/codex/desktop-switches.ts +88 -8
- package/src/codex/inject/plan.ts +16 -3
- package/src/codex/inject.ts +3 -0
- package/src/codex/internal/catalog-writer.ts +13 -2
- package/src/combos/failover.ts +3 -2
- package/src/combos/index.ts +12 -0
- package/src/combos/jev.ts +646 -0
- package/src/combos/request.ts +5 -0
- package/src/combos/resolve.ts +17 -15
- package/src/combos/types.ts +47 -3
- package/src/config/atomic-write.ts +3 -0
- package/src/config/live-reconcile.ts +20 -4
- package/src/config/persist-unlocked.ts +20 -7
- package/src/config/schema/config-schema.ts +16 -0
- package/src/config/schema/leaf-validators.ts +38 -1
- package/src/generated/compatibility-version.json +416 -128
- package/src/integrations/omp-yaml-source.ts +109 -4
- package/src/lab/automation/orchestrator.ts +60 -4
- package/src/lab/events/limits.ts +1 -1
- package/src/lib/bounded-body.ts +55 -38
- package/src/lib/config-ownership.ts +11 -22
- package/src/lib/local-desktop-snapshot-capability.ts +56 -0
- package/src/lib/local-management-capability.ts +25 -3
- package/src/lib/optional-shutdown-hooks.ts +17 -0
- package/src/lib/state-store-registrations.ts +2 -2
- package/src/lib/windows-elevation.ts +62 -25
- package/src/lib/windows-secret-acl.ts +114 -23
- package/src/link/admission-wait.ts +73 -0
- package/src/link/compensation.ts +100 -0
- package/src/link/fingerprint.ts +21 -0
- package/src/link/paths.ts +19 -0
- package/src/link/ports.ts +12 -0
- package/src/link/routes.ts +32 -0
- package/src/link/ssh-argv.ts +170 -0
- package/src/link/ssh-config.ts +152 -0
- package/src/link/ssh-runner.ts +161 -0
- package/src/link/status-projection.ts +96 -0
- package/src/link/store.ts +139 -0
- package/src/link/supervisor.ts +404 -0
- package/src/link/tunnel-state.ts +91 -0
- package/src/protocols/baseline.ts +88 -0
- package/src/protocols/codecs/chat.ts +17 -0
- package/src/protocols/codecs/messages.ts +18 -0
- package/src/protocols/codecs/responses.ts +17 -0
- package/src/protocols/contract.ts +185 -0
- package/src/protocols/dto.ts +267 -0
- package/src/protocols/encoders/adapter-events.ts +876 -0
- package/src/protocols/encoders/chat.ts +243 -0
- package/src/protocols/encoders/messages.ts +462 -0
- package/src/protocols/envelope.ts +43 -0
- package/src/protocols/features.ts +295 -0
- package/src/protocols/guard.ts +33 -0
- package/src/protocols/opaque-state.ts +132 -0
- package/src/protocols/path.ts +39 -0
- package/src/protocols/plan-snapshot.ts +226 -0
- package/src/protocols/plan.ts +192 -0
- package/src/protocols/provider-summary.ts +101 -0
- package/src/protocols/settings.ts +118 -0
- package/src/protocols/shadow-plan.ts +36 -0
- package/src/protocols/shadow.ts +62 -0
- package/src/protocols/trace.ts +335 -0
- package/src/providers/model-rename-migration.ts +3 -3
- package/src/providers/registry/entries-core.ts +6 -1
- package/src/providers/registry/entries-extended.ts +15 -0
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/types.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/remote-control/workspace-hub.ts +5 -4
- package/src/responses/apply-patch-envelope.ts +6 -1
- package/src/responses/code-mode-shell-input.ts +4 -2
- package/src/responses/freeform-wrapper-scan.ts +51 -24
- package/src/responses/progressive-freeform-input.ts +14 -20
- package/src/responses/spill-store.ts +187 -33
- package/src/responses/state/snapshot-select.ts +49 -0
- package/src/responses/state/spill-inspect.ts +46 -0
- package/src/responses/state/spill-queue.ts +16 -0
- package/src/responses/state.ts +51 -27
- package/src/server/audio-client.ts +8 -3
- package/src/server/audio-upstream.ts +9 -4
- package/src/server/auth-cors.ts +25 -8
- package/src/server/chat-completions.ts +71 -11
- package/src/server/chat-native-eligibility.ts +74 -0
- package/src/server/chat-native.ts +139 -74
- package/src/server/claude-messages.ts +140 -22
- package/src/server/hub-usage.ts +4 -3
- package/src/server/index/link-listener.ts +195 -0
- package/src/server/index/optional-listeners.ts +115 -0
- package/src/server/index/serve-options.ts +44 -10
- package/src/server/index.ts +10 -9
- package/src/server/inference/attempt.ts +51 -0
- package/src/server/inference/client-encoder-delivery.ts +235 -0
- package/src/server/inference/client-wire-log.ts +49 -0
- package/src/server/inference/client-wire.ts +69 -0
- package/src/server/inference/context.ts +15 -0
- package/src/server/inference/final-log.ts +37 -0
- package/src/server/local-desktop-snapshot-auth.ts +57 -0
- package/src/server/management/agent-settings-routes.ts +15 -13
- package/src/server/management/api-access.ts +11 -1
- package/src/server/management/config-routes.ts +3 -6
- package/src/server/management/context.ts +25 -0
- package/src/server/management/link-routes.ts +535 -0
- package/src/server/management/logs-usage-routes.ts +31 -1
- package/src/server/management/native-integration-routes.ts +6 -11
- package/src/server/management/oauth-account-routes.ts +42 -16
- package/src/server/management/protocol-routes.ts +159 -0
- package/src/server/management/protocol-settings-patch.ts +145 -0
- package/src/server/management/provider-patch-transaction.ts +61 -0
- package/src/server/management/provider-routes.ts +39 -18
- package/src/server/management/route-registry.ts +12 -0
- package/src/server/management/sidebar-routes.ts +8 -3
- package/src/server/management/system-routes.ts +18 -1
- package/src/server/management/usage-aggregate-cache.ts +206 -6
- package/src/server/management-api.ts +26 -1
- package/src/server/management-auth.ts +19 -5
- package/src/server/messages-native-eligibility.ts +161 -0
- package/src/server/messages-native-oauth.ts +149 -0
- package/src/server/messages-native.ts +790 -0
- package/src/server/relay.ts +9 -0
- package/src/server/request-log-filter.ts +92 -0
- package/src/server/request-log.ts +31 -70
- package/src/server/responses/adapter-delivery.ts +43 -16
- package/src/server/responses/agent-task-recovery.ts +90 -22
- package/src/server/responses/codex-ws-exchange.ts +4 -0
- package/src/server/responses/codex-ws-wire.ts +10 -1
- package/src/server/responses/core-combo-native.ts +323 -0
- package/src/server/responses/core-combo.ts +224 -13
- package/src/server/responses/core-options.ts +22 -0
- package/src/server/responses/core.ts +14 -26
- package/src/server/responses/request-sidecar-auth.ts +8 -3
- package/src/server/responses/run-turn-execution.ts +257 -63
- package/src/server/responses/sidecar-execution.ts +7 -4
- package/src/server/system-env.ts +7 -0
- package/src/service/diagnostics.ts +7 -1
- package/src/service/launchd.ts +26 -37
- package/src/service/windows-ops.ts +24 -20
- package/src/types/config.ts +58 -1
- package/src/types.ts +2 -0
- package/src/update/transactional-install.d.mts +0 -1
- package/src/update/transactional-install.mjs +17 -17
- package/src/usage/jev-stats.ts +495 -0
- package/src/usage/log.ts +24 -3
- package/src/web-search/run-turn-loop.ts +568 -0
- package/gui/dist/assets/App-8NMiZxT0.css +0 -1
- package/gui/dist/assets/App-DGLte6IR.js +0 -50
- package/gui/dist/assets/index--EWgGQvZ.css +0 -1
- package/gui/dist/assets/index-Bi1K37Wl.js +0 -86
|
@@ -21,13 +21,19 @@ import { LABEL, TASK, serviceLogPath } from "./state";
|
|
|
21
21
|
* moved, nvm switch, reinstall) — the service manager would restart-loop on a dead path
|
|
22
22
|
* while `schtasks`/`launchctl` still report "installed".
|
|
23
23
|
*/
|
|
24
|
-
export function bakedServicePathsDiagnostic(): string | null {
|
|
24
|
+
export function bakedServicePathsDiagnostic(platform: NodeJS.Platform = process.platform): string | null {
|
|
25
25
|
const state = readServiceInstallState();
|
|
26
26
|
// A launcher install runs the launcher, not the baked pair, so the pair's existence says
|
|
27
27
|
// nothing about whether the service can start. Judging the recorded launcher is both
|
|
28
28
|
// necessary (a deleted launcher IS stale) and sufficient (a replaced version directory
|
|
29
29
|
// is not, which is exactly what #2898 made routine).
|
|
30
30
|
if (state?.launcherPath) {
|
|
31
|
+
// launchd no longer bakes a launcher at all: a recorded one can only have come from an
|
|
32
|
+
// install that handed the service token and proxy environment to a mutable PATH shim,
|
|
33
|
+
// so it is stale whether or not the file it names still exists.
|
|
34
|
+
if (platform === "darwin") {
|
|
35
|
+
return "STALE mutable launchd launcher — run 'ocx service repair' to re-bake trusted package paths";
|
|
36
|
+
}
|
|
31
37
|
if (existsSync(state.launcherPath)) return null;
|
|
32
38
|
return `STALE baked paths (missing: ${state.launcherPath}) — run 'ocx service repair' to re-bake`;
|
|
33
39
|
}
|
package/src/service/launchd.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
|
12
12
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
13
13
|
import { writeServiceApiTokenFile, assertLiveServiceManagerAllowed } from "./guards";
|
|
14
14
|
import { resolveServiceListenPort, buildServiceShellCommand, buildServiceLauncherShellCommand, installedServiceListenPort, resolvedProxyEnv } from "./health";
|
|
15
|
-
import { SERVICE_MANAGED_ENV, LABEL, cliEntry,
|
|
15
|
+
import { SERVICE_MANAGED_ENV, LABEL, cliEntry, logPath, serviceStatePath, currentCodexSqliteHomeAbsolute, type ServiceInstallState, writeServiceInstallState, readServiceInstallState } from "./state";
|
|
16
16
|
import { writeServiceDefinitionFile } from "./windows-ops";
|
|
17
17
|
import { readTextOrNull } from "./windows-taskxml";
|
|
18
18
|
|
|
@@ -26,20 +26,18 @@ function plistString(value: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Render the launchd plist
|
|
30
|
-
*
|
|
31
|
-
* version-manager
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* legacy pair so callers and tests stay hermetic.
|
|
29
|
+
* Render the launchd plist from the package-local Bun + CLI pair. Unlike systemd, launchd
|
|
30
|
+
* must not hand the service token and proxy environment to a PATH-discovered launcher: a
|
|
31
|
+
* mutable version-manager shim could be replaced after installation and run with those
|
|
32
|
+
* secrets. The legacy `launcher` dependency remains accepted for API compatibility but is
|
|
33
|
+
* ignored.
|
|
35
34
|
*/
|
|
36
35
|
export function buildPlist(
|
|
37
36
|
proxyEnv: { name: string; value: string }[] = resolvedProxyEnv(),
|
|
38
|
-
deps: { launcher?: string | null
|
|
37
|
+
deps: { runtime?: DurableBunRuntime; launcher?: string | null } = {},
|
|
39
38
|
): string {
|
|
40
39
|
const runtime = deps.runtime ?? durableBunRuntime();
|
|
41
40
|
const { bun, bunRuntimeSource, cli } = cliEntry(runtime);
|
|
42
|
-
const launcher = deps.launcher ?? null;
|
|
43
41
|
const log = logPath();
|
|
44
42
|
const path = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
45
43
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
@@ -52,16 +50,8 @@ export function buildPlist(
|
|
|
52
50
|
// (src/cli/index.ts preserveRouting). Only the wrapper writes this second marker, so
|
|
53
51
|
// the dashboard-stop refusal below can tell a real launchd job from an ordinary child.
|
|
54
52
|
` <key>${SERVICE_MANAGED_ENV}</key><string>1</string>`,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
` <key>${BUN_RUNTIME_PATH_ENV}</key><string>${plistString(bun)}</string>`,
|
|
58
|
-
]),
|
|
59
|
-
// A launcher resolves the current package's bundled Bun after every upgrade. Preserve
|
|
60
|
-
// only a proof-bound shell override; baking a package-local path here would recreate
|
|
61
|
-
// the version-manager pin that launcher mode exists to remove (same rule as buildUnit).
|
|
62
|
-
launcher && runtime.source === "override"
|
|
63
|
-
? ` <key>${runtime.overrideEnv}</key><string>${plistString(runtime.path)}</string>`
|
|
64
|
-
: null,
|
|
53
|
+
` <key>${BUN_RUNTIME_SOURCE_ENV}</key><string>${bunRuntimeSource}</string>`,
|
|
54
|
+
` <key>${BUN_RUNTIME_PATH_ENV}</key><string>${plistString(bun)}</string>`,
|
|
65
55
|
` <key>PATH</key><string>${plistString(path)}</string>`,
|
|
66
56
|
codexHome ? ` <key>CODEX_HOME</key><string>${plistString(codexHome)}</string>` : null,
|
|
67
57
|
codexSqliteHome ? ` <key>CODEX_SQLITE_HOME</key><string>${plistString(codexSqliteHome)}</string>` : null,
|
|
@@ -69,7 +59,7 @@ export function buildPlist(
|
|
|
69
59
|
...proxyEnv.map(({ name, value }) =>
|
|
70
60
|
` <key>${name}</key><string>${plistString(value)}</string>`),
|
|
71
61
|
].filter((line): line is string => Boolean(line)).join("\n");
|
|
72
|
-
const command = launchdServiceCommand(
|
|
62
|
+
const command = launchdServiceCommand(runtime);
|
|
73
63
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
74
64
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
75
65
|
<plist version="1.0">
|
|
@@ -135,18 +125,16 @@ export function reusePreviousPlistPathVariable(previous: string, rendered: strin
|
|
|
135
125
|
}
|
|
136
126
|
|
|
137
127
|
/**
|
|
138
|
-
* The exec line {@link buildPlist} bakes, for the
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
128
|
+
* The exec line {@link buildPlist} bakes, for the runtime a single install already
|
|
129
|
+
* resolved. Shared so `installLaunchd` can verify the live job against the exact string it
|
|
130
|
+
* just wrote instead of re-deriving it from install state that has not been written yet
|
|
131
|
+
* (a fresh install has no state, so `expectedLaunchdCommand` falls back to the package
|
|
132
|
+
* pair — identical here, but only by accident of the fresh state).
|
|
143
133
|
*/
|
|
144
134
|
function launchdServiceCommand(
|
|
145
|
-
launcher: string | null,
|
|
146
135
|
runtime: DurableBunRuntime = durableBunRuntime(),
|
|
147
136
|
port: number = resolveServiceListenPort(),
|
|
148
137
|
): string {
|
|
149
|
-
if (launcher) return buildServiceLauncherShellCommand(launcher, port);
|
|
150
138
|
const { bun, cli } = cliEntry(runtime);
|
|
151
139
|
return buildServiceShellCommand(bun, cli, port);
|
|
152
140
|
}
|
|
@@ -496,13 +484,14 @@ export function installLaunchd(deps: {
|
|
|
496
484
|
// The previous definition, kept for rollback. An eviction whose bootstrap fails used to
|
|
497
485
|
// end with the new plist on disk, nothing in launchd, and nothing listening.
|
|
498
486
|
const previousPlist = wasInstalled ? readTextOrNull(p) : null;
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
|
|
487
|
+
// Keep launchd bound to the package paths selected by this trusted invocation. A PATH
|
|
488
|
+
// launcher can be replaced later and would inherit the service token and proxy
|
|
489
|
+
// environment.
|
|
502
490
|
// The command THIS install bakes, not the one install state remembers: on a fresh
|
|
503
491
|
// install there is no state yet, and after a lost state file `expectedLaunchdCommand`
|
|
504
|
-
// falls back to the Bun + CLI pair
|
|
505
|
-
|
|
492
|
+
// falls back to the Bun + CLI pair, which happens to be the same answer only by accident
|
|
493
|
+
// of the empty state.
|
|
494
|
+
const expectedCommand = launchdServiceCommand();
|
|
506
495
|
const uid = process.getuid?.() ?? 0;
|
|
507
496
|
const guiDomain = launchdGuiDomain();
|
|
508
497
|
const guiTarget = `${guiDomain}/${LABEL}`;
|
|
@@ -523,7 +512,7 @@ export function installLaunchd(deps: {
|
|
|
523
512
|
);
|
|
524
513
|
}
|
|
525
514
|
|
|
526
|
-
let rendered = buildPlist(resolvedProxyEnv()
|
|
515
|
+
let rendered = buildPlist(resolvedProxyEnv());
|
|
527
516
|
if (previousPlist !== null && previousPlist !== rendered && verdict.state === "loaded-current") {
|
|
528
517
|
// The live job runs exactly the exec line this install baked, so the definition on disk
|
|
529
518
|
// IS the one launchd is running: keep the PATH it already carries instead of replacing
|
|
@@ -553,7 +542,7 @@ export function installLaunchd(deps: {
|
|
|
553
542
|
try { chmodSync(p, 0o600); } catch { /* best-effort */ }
|
|
554
543
|
// Install state is refreshed because it is what `expectedLaunchdCommand` reads, and
|
|
555
544
|
// a repair that leaves it stale re-creates the false "OLDER plist" report.
|
|
556
|
-
writeServiceInstallState("scheduler"
|
|
545
|
+
writeServiceInstallState("scheduler");
|
|
557
546
|
console.log("ℹ️ service is already loaded from the current plist; nothing to do.");
|
|
558
547
|
// The ONLY `reloaded: false` exit: the process launchd was running when this command
|
|
559
548
|
// started is still running, same pid. `ocx service restart` turns that into a kickstart.
|
|
@@ -652,7 +641,7 @@ export function installLaunchd(deps: {
|
|
|
652
641
|
`⚠️ launchctl accepted the bootstrap but the job state could not be verified — ${
|
|
653
642
|
verdict.detail ?? "launchctl could not be asked"}. Check: launchctl print ${guiTarget}`,
|
|
654
643
|
);
|
|
655
|
-
writeServiceInstallState("scheduler"
|
|
644
|
+
writeServiceInstallState("scheduler");
|
|
656
645
|
return { reloaded: true };
|
|
657
646
|
}
|
|
658
647
|
|
|
@@ -691,7 +680,7 @@ export function installLaunchd(deps: {
|
|
|
691
680
|
+ `then re-run '${wasInstalled ? "ocx service repair" : "ocx service install"}'.`,
|
|
692
681
|
);
|
|
693
682
|
}
|
|
694
|
-
writeServiceInstallState("scheduler"
|
|
683
|
+
writeServiceInstallState("scheduler");
|
|
695
684
|
// The rollback copy has done its job: the new definition is verified loaded. Leaving it
|
|
696
685
|
// behind makes the NEXT repair's backup ambiguous (which failure did it come from?) and
|
|
697
686
|
// `uninstall` the only thing that ever cleaned it up.
|
|
@@ -730,7 +719,7 @@ export function restartLaunchdJob(deps: {
|
|
|
730
719
|
const run = deps.launchctl ?? runLaunchctl;
|
|
731
720
|
const target = `${launchdGuiDomain()}/${LABEL}`;
|
|
732
721
|
const expectedCommand = deps.expectedCommand
|
|
733
|
-
?? (() => launchdServiceCommand(
|
|
722
|
+
?? (() => launchdServiceCommand());
|
|
734
723
|
const kicked = run(["kickstart", "-k", target]);
|
|
735
724
|
const verdict = (deps.probe ?? probeLaunchdLoadState)({ expectedCommand });
|
|
736
725
|
if (!kicked.ok || verdict.state === "not-loaded" || verdict.state === "loaded-stale") {
|
|
@@ -12,7 +12,7 @@ import { dirname, join } from "node:path";
|
|
|
12
12
|
import { getConfigDir } from "../config";
|
|
13
13
|
import { OCX_ELEVATED_STAGING_UNREADABLE, runWindowsElevatedScheduledTaskRegistration, WindowsSchtasksError, type StagedWindowsTaskXml } from "../lib/windows-elevation";
|
|
14
14
|
import { defaultWinswEntry, installWinswService, statusWinswRaw, uninstallWinswService, WINSW_SERVICE_ID, type WinswStatus } from "../lib/winsw";
|
|
15
|
-
import { forgetEphemeralSecretDir, forgetEphemeralSecretPath, hardenSecretDir } from "../lib/windows-secret-acl";
|
|
15
|
+
import { forgetEphemeralSecretDir, forgetEphemeralSecretPath, hardenElevatedStageDir, hardenElevatedStagePath, hardenSecretDir } from "../lib/windows-secret-acl";
|
|
16
16
|
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
17
17
|
import { killWindowsSchedulerWrappers } from "../lib/windows-service-wrappers";
|
|
18
18
|
import { isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
@@ -192,19 +192,24 @@ export interface ElevatedSchedulerStagingDeps {
|
|
|
192
192
|
* alone:
|
|
193
193
|
*
|
|
194
194
|
* - **Access.** The directory is created fresh by `mkdtemp`, then ACL-hardened before
|
|
195
|
-
* anything is written into it, so
|
|
196
|
-
* payload while the UAC prompt is open.
|
|
197
|
-
* the
|
|
195
|
+
* anything is written into it, so no non-administrator account can read or replace
|
|
196
|
+
* the payload while the UAC prompt is open. Administrators get read — never write —
|
|
197
|
+
* because an over-the-shoulder UAC prompt answered with a different administrator's
|
|
198
|
+
* credentials runs the elevated process as that admin (#4779); tamper-evidence does
|
|
199
|
+
* not depend on the DACL, it comes from the pinned handles and the digest below.
|
|
200
|
+
* Hardening the directory first is what makes the file private from the moment it
|
|
201
|
+
* exists.
|
|
198
202
|
* - **No reparse point.** Each artifact is inspected with `lstat` and rejected unless it
|
|
199
203
|
* is what it claims to be. `wx` already refuses to create over an existing name, which
|
|
200
204
|
* is the atomic step here — there is no replace path to race, because every path is
|
|
201
205
|
* inside a directory that did not exist a moment ago. The explicit check is what keeps
|
|
202
206
|
* that guarantee from depending on a reading of `O_EXCL` semantics.
|
|
203
|
-
* - **
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
207
|
+
* - **Pinned namespace and content.** Before UAC, the launcher opens every ancestor and
|
|
208
|
+
* payload without following reparse points. Ancestor handles share read/write but
|
|
209
|
+
* deny delete, so the namespace cannot be redirected while sibling payloads can
|
|
210
|
+
* still be written; payload handles deny write/delete outright. Those handles stay
|
|
211
|
+
* open until elevation exits. The digest then verifies the exact,
|
|
212
|
+
* length-bounded bytes read by the elevated process.
|
|
208
213
|
*
|
|
209
214
|
* Payloads are UTF-16LE with no BOM, and the elevated process decodes them straight into
|
|
210
215
|
* `Register-ScheduledTask`. What is hashed is therefore exactly what is registered, with
|
|
@@ -217,11 +222,11 @@ export function stageElevatedSchedulerRegistration(
|
|
|
217
222
|
): StagedElevatedSchedulerRegistration {
|
|
218
223
|
const createStageDir = deps.createStageDir
|
|
219
224
|
?? (() => mkdtempSync(join(tmpdir(), WINDOWS_SCHEDULER_STAGE_PREFIX)));
|
|
220
|
-
const hardenDir = deps.hardenDir ?? ((path: string) => {
|
|
225
|
+
const hardenDir = deps.hardenDir ?? ((path: string) => { hardenElevatedStageDir(path, { required: true }); });
|
|
221
226
|
const writePayload = deps.writePayload ?? ((path: string, bytes: Buffer) => {
|
|
222
227
|
writeFileSync(path, bytes, { flag: "wx", mode: 0o600 });
|
|
223
228
|
});
|
|
224
|
-
const hardenPath = deps.hardenPath ?? ((path: string) => {
|
|
229
|
+
const hardenPath = deps.hardenPath ?? ((path: string) => { hardenElevatedStagePath(path, { required: true }); });
|
|
225
230
|
const inspect = deps.inspect ?? ((path: string) => lstatSync(path));
|
|
226
231
|
const removeStageDir = deps.removeStageDir ?? ((path: string) => { rmdirSync(path); });
|
|
227
232
|
|
|
@@ -266,7 +271,7 @@ export function stageElevatedSchedulerRegistration(
|
|
|
266
271
|
throw new Error(`Refusing to stage an elevated Task Scheduler payload through a redirected path: ${path}`);
|
|
267
272
|
}
|
|
268
273
|
hardenPath(path);
|
|
269
|
-
return { path, sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
274
|
+
return { path, byteLength: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
270
275
|
};
|
|
271
276
|
return {
|
|
272
277
|
xml: stage("register.xml", xml),
|
|
@@ -293,10 +298,9 @@ export function stageElevatedSchedulerRegistration(
|
|
|
293
298
|
*
|
|
294
299
|
* The elevated process runs hidden, so nothing it writes survives; only the exit code
|
|
295
300
|
* crosses back. That makes an unexplained code the whole user-facing error, which is
|
|
296
|
-
* exactly what made the ENAMETOOLONG in #4692 expensive to diagnose. Staging
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
* it — and that one gets named along with its remedy rather than surfacing as a number.
|
|
301
|
+
* exactly what made the ENAMETOOLONG in #4692 expensive to diagnose. Staging has one
|
|
302
|
+
* failure of its own — the elevated process cannot open the payload — and that one
|
|
303
|
+
* gets named along with its remedy rather than surfacing as a number.
|
|
300
304
|
*/
|
|
301
305
|
export function describeElevatedRegistrationFailure(
|
|
302
306
|
failureLabel: string,
|
|
@@ -305,10 +309,10 @@ export function describeElevatedRegistrationFailure(
|
|
|
305
309
|
): string {
|
|
306
310
|
if (exitCode === OCX_ELEVATED_STAGING_UNREADABLE) {
|
|
307
311
|
return `${failureLabel}: the elevated process could not read the staged task definition in `
|
|
308
|
-
+ `${stageDir}. That directory
|
|
309
|
-
+ "
|
|
310
|
-
+ "
|
|
311
|
-
+ "already elevated as
|
|
312
|
+
+ `${stageDir}. That directory grants read access to the staging account and to `
|
|
313
|
+
+ "administrators, so the hardening did not take effect there or the file was "
|
|
314
|
+
+ "replaced. Retry the command; if it keeps failing, run it again from a session "
|
|
315
|
+
+ "already elevated as an administrator.";
|
|
312
316
|
}
|
|
313
317
|
return `${failureLabel} with exit code ${exitCode}.`;
|
|
314
318
|
}
|
package/src/types/config.ts
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "./provider";
|
|
2
2
|
import type { CodexAccount } from "./accounts";
|
|
3
3
|
|
|
4
|
+
/** Public inference API exposure. Responses and Chat Completions are always served. */
|
|
5
|
+
export interface OcxApiSurfacesConfig {
|
|
6
|
+
/**
|
|
7
|
+
* `/v1/messages` and `/v1/messages/count_tokens`. Absent means "inherit
|
|
8
|
+
* `claudeCode.enabled !== false`"; a present non-boolean value disables the surface.
|
|
9
|
+
*/
|
|
10
|
+
messages?: { enabled?: boolean };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Protocol delivery policy (devlog/_plan/260924_protocol_first_class). */
|
|
14
|
+
export interface OcxProtocolsConfig {
|
|
15
|
+
/**
|
|
16
|
+
* What happens when the final upstream wire cannot express a requested feature.
|
|
17
|
+
* `legacy` (default) keeps today's behavior; `reject` refuses before any upstream send.
|
|
18
|
+
*/
|
|
19
|
+
unrepresentable?: "legacy" | "reject";
|
|
20
|
+
/** Staged rollout switches. Every switch defaults off and changes no semantics while off. */
|
|
21
|
+
rollout?: {
|
|
22
|
+
/** Eligible Chat candidates inside combos and policy routes send natively. */
|
|
23
|
+
nativeChatCombos?: boolean;
|
|
24
|
+
/** Proxy-managed key-auth Anthropic targets receive `/v1/messages` natively. */
|
|
25
|
+
managedMessagesNative?: boolean;
|
|
26
|
+
/** Extends managed native Messages to Anthropic OAuth accounts. Requires the switch above. */
|
|
27
|
+
managedMessagesNativeOAuth?: boolean;
|
|
28
|
+
/** Chat and Messages clients are encoded directly from adapter events. */
|
|
29
|
+
directEncoders?: boolean;
|
|
30
|
+
/** Compare the dispatch plan with the observed path; never sends a second request. */
|
|
31
|
+
shadowPlan?: boolean;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
4
35
|
/**
|
|
5
36
|
* Claude Code inbound settings (devlog/260711_claude_inbound). Consumed by the
|
|
6
37
|
* /v1/messages surface, the `ocx claude` launcher, and the GUI Claude page.
|
|
@@ -397,10 +428,17 @@ export interface OcxPrivacyConfig {
|
|
|
397
428
|
maskEmails?: boolean;
|
|
398
429
|
}
|
|
399
430
|
|
|
431
|
+
export interface OcxLinkTransportConfig {
|
|
432
|
+
tunnelPort: number;
|
|
433
|
+
linkId: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
400
436
|
export interface OcxClientConnectionConfig {
|
|
401
437
|
serverUrl: string;
|
|
402
438
|
managementUrl: string;
|
|
403
439
|
managementTransport: "direct" | "relay";
|
|
440
|
+
transport?: "hub" | "link";
|
|
441
|
+
link?: OcxLinkTransportConfig;
|
|
404
442
|
selectedClients: OcxConnectedClientId[];
|
|
405
443
|
tokenEnv: "OPENCODEX_API_AUTH_TOKEN";
|
|
406
444
|
apiKeyId: string;
|
|
@@ -527,6 +565,14 @@ export interface OcxConfig {
|
|
|
527
565
|
googleAntigravityStaticCatalogVersion?: 1 | 2;
|
|
528
566
|
/** Claude Code inbound + launcher settings. */
|
|
529
567
|
claudeCode?: OcxClaudeCodeConfig;
|
|
568
|
+
/**
|
|
569
|
+
* Which public inference APIs this proxy serves. Read only through
|
|
570
|
+
* `resolveApiSurfaceSettings` in `src/protocols/settings.ts`, which fails closed on a
|
|
571
|
+
* malformed value and inherits `claudeCode.enabled` while no explicit value exists.
|
|
572
|
+
*/
|
|
573
|
+
apiSurfaces?: OcxApiSurfacesConfig;
|
|
574
|
+
/** Protocol delivery policy and rollout switches; see `resolveProtocolSettings`. */
|
|
575
|
+
protocols?: OcxProtocolsConfig;
|
|
530
576
|
/**
|
|
531
577
|
* Per-client durable intent. This phase owns only `codex`; later phases extend
|
|
532
578
|
* one key at a time rather than widening a shared union.
|
|
@@ -742,6 +788,12 @@ export interface OcxConfig {
|
|
|
742
788
|
timeoutMs?: number;
|
|
743
789
|
/** Maximum in-memory ciphertext-to-assignment entries. Default: 200. */
|
|
744
790
|
cacheEntries?: number;
|
|
791
|
+
/**
|
|
792
|
+
* Extra recovery sends when ChatGPT rejects with a transient 5xx or the transport
|
|
793
|
+
* fails, sharing the same credential, deadline, and cache flight (#3661).
|
|
794
|
+
* Default: 0 (single attempt); maximum: 2.
|
|
795
|
+
*/
|
|
796
|
+
retries?: number;
|
|
745
797
|
};
|
|
746
798
|
/**
|
|
747
799
|
* Quota-reset detection and notification. Absent means off: no detection, no timer, no sink.
|
|
@@ -1136,7 +1188,7 @@ export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-fir
|
|
|
1136
1188
|
|
|
1137
1189
|
export type OcxAccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization";
|
|
1138
1190
|
|
|
1139
|
-
export type OcxComboStrategy = "failover" | "round-robin" | "random" | "least-used" | "reset-window";
|
|
1191
|
+
export type OcxComboStrategy = "failover" | "round-robin" | "random" | "least-used" | "reset-window" | "jev";
|
|
1140
1192
|
export type OcxComboDefaultEffort = "low" | "medium" | "high" | "xhigh" | "max" | "ultra";
|
|
1141
1193
|
export type OcxComboDefaultEffortMode = "fallback" | "force";
|
|
1142
1194
|
|
|
@@ -1161,6 +1213,11 @@ export interface OcxComboTarget {
|
|
|
1161
1213
|
model: string;
|
|
1162
1214
|
/** Relative target weight for round-robin batches and random selection. Default 1; valid range 1..10000. */
|
|
1163
1215
|
weight?: number;
|
|
1216
|
+
/**
|
|
1217
|
+
* Exact efforts JEV may choose for this target. Omit to allow every effort the
|
|
1218
|
+
* target currently advertises; an explicit list must be non-empty.
|
|
1219
|
+
*/
|
|
1220
|
+
reasoningEfforts?: OcxComboDefaultEffort[];
|
|
1164
1221
|
/**
|
|
1165
1222
|
* Marks an emergency-only target. Inert unless the combo sets
|
|
1166
1223
|
* `cooldownWaitPolicy`, and never makes a target permanently ineligible —
|
package/src/types.ts
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* <scopeDir>/.ocx-recovery.json double-fault marker with a one-line restore
|
|
18
18
|
*
|
|
19
19
|
* A staging directory is created exclusively and immediately carries an ownership marker
|
|
20
|
-
* (`.ocx-update-owner.json`).
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* (`.ocx-update-owner.json`). Later updates report leftovers but do not delete them: a marker
|
|
21
|
+
* proves provenance only while this process owns the fresh path, not after another local
|
|
22
|
+
* process could have replaced it. Other neighbouring entries are likewise left alone (#5624).
|
|
23
23
|
*/
|
|
24
24
|
import { spawnSync } from "node:child_process";
|
|
25
25
|
import { randomBytes } from "node:crypto";
|
|
@@ -169,9 +169,8 @@ function stampedName(prefix) {
|
|
|
169
169
|
export const UPDATE_OWNER_MARKER = ".ocx-update-owner.json";
|
|
170
170
|
const STAGE_PREFIX = ".ocx-staging-";
|
|
171
171
|
/**
|
|
172
|
-
* A marked stage younger than this is
|
|
173
|
-
*
|
|
174
|
-
* home against the same global prefix never loses its in-flight stage.
|
|
172
|
+
* A marked stage younger than this is reported as recent. npm's staging install is bounded at
|
|
173
|
+
* three minutes by the launcher; the floor distinguishes likely in-flight work from leftovers.
|
|
175
174
|
*/
|
|
176
175
|
export const STALE_STAGE_MIN_AGE_MS = 30 * 60 * 1000;
|
|
177
176
|
const RETRYABLE_REMOVE_CODES = new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
@@ -256,14 +255,14 @@ export function removeOwnedStage(stageRoot, deps = {}) {
|
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
258
|
+
* Report what earlier update attempts left next to the package. A marker is forgeable and a
|
|
259
|
+
* pathname can be replaced after inspection, so no later process may recursively delete a
|
|
260
|
+
* leftover by that pathname. Never throws or fails the update: fresh unique stages step around it.
|
|
262
261
|
*/
|
|
263
262
|
export function sweepUpdateLeftovers({ packageDir, pkgName, log = () => {}, deps = {} }) {
|
|
264
263
|
const scopeDir = dirname(packageDir);
|
|
265
264
|
const now = (deps.now ?? Date.now)();
|
|
266
|
-
const result = {
|
|
265
|
+
const result = { inUse: [], recent: [], notOwned: [] };
|
|
267
266
|
let names = [];
|
|
268
267
|
try {
|
|
269
268
|
names = readdirSync(scopeDir);
|
|
@@ -281,9 +280,7 @@ export function sweepUpdateLeftovers({ packageDir, pkgName, log = () => {}, deps
|
|
|
281
280
|
} else if (now - marker.createdAt < STALE_STAGE_MIN_AGE_MS) {
|
|
282
281
|
result.recent.push(full);
|
|
283
282
|
} else {
|
|
284
|
-
|
|
285
|
-
if (removal.removed) result.removed.push(full);
|
|
286
|
-
else result.inUse.push({ path: full, code: removal.code });
|
|
283
|
+
result.inUse.push({ path: full, code: "ESTALE" });
|
|
287
284
|
}
|
|
288
285
|
} else if (name.startsWith(renameAsidePrefix)) {
|
|
289
286
|
result.notOwned.push(full);
|
|
@@ -291,9 +288,10 @@ export function sweepUpdateLeftovers({ packageDir, pkgName, log = () => {}, deps
|
|
|
291
288
|
}
|
|
292
289
|
// Names only: the full path under a user-scoped npm prefix carries the account name, and
|
|
293
290
|
// this logger is the launcher's console.
|
|
294
|
-
for (const path of result.removed) log("Removed a staging directory left by an earlier update: " + basename(path));
|
|
295
291
|
for (const entry of result.inUse) {
|
|
296
|
-
log(
|
|
292
|
+
log(entry.code === "ESTALE"
|
|
293
|
+
? "Left an earlier update's staging directory in place; delete it by hand once no OpenCodex process is running from it: " + basename(entry.path)
|
|
294
|
+
: "Left an earlier update's staging directory in place (could not remove it: " + entry.code + "): " + basename(entry.path));
|
|
297
295
|
}
|
|
298
296
|
for (const path of result.notOwned) {
|
|
299
297
|
log("Not removing " + basename(path) + " next to the package: this updater did not create it. Delete it by hand once no OpenCodex process is running from it.");
|
|
@@ -393,7 +391,7 @@ export function transactionalNpmUpdate({
|
|
|
393
391
|
}) {
|
|
394
392
|
const rename = deps.rename ?? renameSync;
|
|
395
393
|
const scopeDir = dirname(packageDir);
|
|
396
|
-
// Leftovers from earlier attempts are
|
|
394
|
+
// Leftovers from earlier attempts are only reported; fresh unique stages step around them. This never fails.
|
|
397
395
|
try { sweepUpdateLeftovers({ packageDir, pkgName, log, deps }); } catch { /* report-only */ }
|
|
398
396
|
let stageRoot;
|
|
399
397
|
// GLOBAL-style staging (-g --prefix): npm nests the package's dependencies INSIDE the
|
|
@@ -419,7 +417,9 @@ export function transactionalNpmUpdate({
|
|
|
419
417
|
const discardStage = () => {
|
|
420
418
|
const removal = removeOwnedStage(stageRoot, deps);
|
|
421
419
|
if (!removal.removed) {
|
|
422
|
-
|
|
420
|
+
// EACCES/EUNKNOWN do not prove an open file, so the message names the failure
|
|
421
|
+
// without blaming a process; the code stays for whoever reads the log.
|
|
422
|
+
log("Left this update's staging directory in place (could not remove it: " + removal.code + "); later updates will not remove it either — delete it by hand once no OpenCodex process is running from it: " + basename(stageRoot));
|
|
423
423
|
}
|
|
424
424
|
};
|
|
425
425
|
const spec = pkgName + "@" + (targetVersion || tag);
|