@bitkyc08/opencodex 2.63.0 → 2.64.0-preview.20260923
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/AGENTS_INSTALL.md +4 -3
- package/README.md +46 -33
- package/assets/download-linux.svg +10 -0
- package/assets/download-macos.svg +10 -0
- package/assets/download-windows.svg +10 -0
- package/bin/ocx.mjs +12 -4
- package/gui/dist/assets/App-D5eN1TID.js +50 -0
- package/gui/dist/assets/App-I5AnaSLh.css +1 -0
- package/gui/dist/assets/{Tray-CncKDBTp.js → Tray-Bh_ErQDh.js} +1 -1
- package/gui/dist/assets/index-BEq4OCOz.js +86 -0
- package/gui/dist/assets/index-DiBRuK-d.css +1 -0
- package/gui/dist/assets/usage-companion-chart-DBG_37kQ.js +1 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/src/protocol.rs +5 -0
- package/package.json +4 -1
- package/src/adapters/anthropic.ts +66 -7
- package/src/adapters/codebuddy/adapter.ts +112 -16
- package/src/adapters/codebuddy/mcp-server.ts +180 -0
- package/src/adapters/codebuddy/scaffold-guard.ts +25 -8
- package/src/adapters/codebuddy/tool-bridge.ts +597 -0
- package/src/adapters/coding-agent/protocol.ts +123 -10
- package/src/adapters/coding-agent/turn.ts +324 -2
- package/src/adapters/command-code-restored-schema.ts +112 -0
- package/src/adapters/command-code-tool-text.ts +589 -0
- package/src/adapters/command-code.ts +80 -6
- package/src/adapters/cursor/catalog.ts +12 -0
- package/src/adapters/cursor/current-request.ts +46 -0
- package/src/adapters/cursor/discovery.ts +1 -1
- package/src/adapters/cursor/effort-map.ts +4 -0
- package/src/adapters/cursor/native-exec.ts +15 -0
- package/src/adapters/cursor/protobuf-events.ts +40 -12
- package/src/adapters/cursor/protobuf-request.ts +83 -18
- package/src/adapters/cursor/request-builder.ts +5 -4
- package/src/adapters/cursor/tool-guidance.ts +1 -1
- package/src/adapters/devin/live-models.ts +7 -0
- package/src/adapters/inline-think-tags.ts +251 -0
- package/src/adapters/kiro/adapter.ts +1 -0
- package/src/adapters/kiro/stream.ts +5 -3
- package/src/adapters/kiro/usage.ts +4 -3
- package/src/adapters/mimo-free.ts +37 -18
- package/src/adapters/openai-chat/messages.ts +5 -5
- package/src/adapters/openai-chat/tool-schema.ts +124 -2
- package/src/adapters/openai-chat.ts +13 -3
- package/src/adapters/openai-responses/passthrough.ts +15 -2
- package/src/adapters/openai-responses/reasoning.ts +14 -1
- package/src/adapters/openai-responses/request-strips.ts +29 -3
- package/src/adapters/openai-responses/tool-output-recovery.ts +9 -3
- package/src/adapters/qoder/adapter.ts +15 -12
- package/src/adapters/qoder/scaffold-guard.ts +45 -13
- package/src/bridge/internal.ts +20 -0
- package/src/bridge/sse.ts +14 -5
- package/src/claude/agents-inject.ts +2 -1
- package/src/claude/desktop-applied-marker.ts +44 -0
- package/src/claude/desktop-profile.ts +22 -12
- package/src/claude/inbound.ts +8 -3
- package/src/cli/access.ts +22 -5
- package/src/cli/account-api.ts +6 -0
- package/src/cli/account-auth.ts +5 -2
- package/src/cli/account.ts +5 -6
- package/src/cli/aside-profiles.ts +61 -1
- package/src/cli/claude-desktop.ts +3 -2
- package/src/cli/claude.ts +3 -1
- package/src/cli/codex-cli-update.ts +2 -2
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +12 -4
- package/src/cli/index.ts +98 -10
- package/src/cli/registry.ts +1 -1
- package/src/cli/resolve.ts +113 -1
- package/src/cli/root.ts +5 -0
- package/src/cli/stop-approval.ts +186 -0
- package/src/cli/stop-report.ts +1 -1
- package/src/cli/system-restart-client.ts +19 -4
- package/src/client/hub-client.ts +25 -18
- package/src/clients/config-export.ts +12 -4
- package/src/codex/account-auto-switch.ts +59 -0
- package/src/codex/account-lifecycle.ts +7 -0
- package/src/codex/account-priority.ts +10 -0
- package/src/codex/account-usability.ts +9 -0
- package/src/codex/auth-api/account-list.ts +5 -0
- package/src/codex/auth-api/login-flow.ts +15 -5
- package/src/codex/auth-api/login-state.ts +5 -16
- package/src/codex/auth-api/pool-mode-gate.ts +30 -7
- package/src/codex/auth-api/routes.ts +39 -3
- package/src/codex/auth-context.ts +145 -17
- package/src/codex/catalog/build-entries.ts +2 -1
- package/src/codex/catalog/effort.ts +8 -2
- package/src/codex/catalog/metadata.ts +33 -7
- package/src/codex/catalog/native-models.ts +102 -5
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +19 -6
- package/src/codex/cli-installation-targets.ts +44 -40
- package/src/codex/codex-write-lock.ts +32 -2
- package/src/codex/inject/multi-agent-v2.ts +90 -0
- package/src/codex/inject/plan.ts +365 -0
- package/src/codex/inject.ts +237 -362
- package/src/codex/log-guard/maintenance.ts +30 -13
- package/src/codex/project-config-warnings.ts +47 -7
- package/src/codex/prompt-layers/encoding.ts +41 -0
- package/src/codex/prompt-layers/toml-read.ts +1 -1
- package/src/codex/prompt-layers.ts +17 -13
- package/src/codex/prompt-text-probe.ts +20 -10
- package/src/codex/quota-observation-freshness.ts +34 -0
- package/src/codex/quota-rejection.ts +8 -4
- package/src/codex/quota.ts +4 -2
- package/src/codex/routing/selection.ts +32 -9
- package/src/codex/routing.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +4 -3
- package/src/codex/sync.ts +11 -0
- package/src/codex/windows-installation-files.ts +33 -11
- package/src/codex/write-coordination.ts +4 -0
- package/src/combos/request.ts +6 -2
- package/src/companion/settings.ts +6 -1
- package/src/config/atomic-write.ts +12 -1
- package/src/config/derived-registries.ts +29 -0
- package/src/config/diagnostics.ts +17 -0
- package/src/config/live-reconcile.ts +11 -2
- package/src/config/load-degrade.ts +9 -2
- package/src/config/persist-unlocked.ts +4 -3
- package/src/config/persisted-mutation.ts +94 -0
- package/src/config/provider-validation.ts +1 -1
- package/src/config/proxy-env.ts +62 -12
- package/src/config/rebase-provenance.ts +80 -1
- package/src/config/schema/config-schema.ts +5 -0
- package/src/config/schema/leaf-validators.ts +50 -1
- package/src/config/subagent-models.ts +41 -15
- package/src/config.ts +18 -95
- package/src/generated/compatibility-version.json +330 -218
- package/src/generated/model-metadata.ts +8 -5
- package/src/github/star-state.ts +46 -6
- package/src/grok/inject.ts +74 -40
- package/src/grok/status.ts +11 -4
- package/src/integrations/cursor-effort-table.ts +50 -13
- package/src/integrations/raycast-detect.ts +19 -4
- package/src/integrations/serialize.ts +11 -1
- package/src/lib/bounded-body.ts +30 -0
- package/src/lib/crash-guard.ts +52 -4
- package/src/lib/local-aside-sync-contract.ts +41 -0
- package/src/lib/package-tree-integrity.ts +181 -9
- package/src/lib/proxy-env.ts +18 -1
- package/src/lib/request-failure-attribution.ts +1 -0
- package/src/lib/request-failure-model.ts +3 -0
- package/src/lib/service-secrets.ts +99 -2
- package/src/lib/socks5-fetch.ts +43 -14
- package/src/lib/token-estimate.ts +17 -2
- package/src/oauth/command-code.ts +3 -2
- package/src/oauth/generic-account-failover.ts +29 -1
- package/src/oauth/index.ts +24 -16
- package/src/oauth/login-flow-state.ts +5 -5
- package/src/oauth/meta-muse-device.ts +49 -7
- package/src/oauth/store.ts +74 -9
- package/src/providers/alibaba-region-backup.ts +16 -1
- package/src/providers/anthropic-fast.ts +89 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +288 -0
- package/src/providers/anthropic-reset-grants.ts +329 -0
- package/src/providers/claude-cli-identity.ts +12 -0
- package/src/providers/command-code-efforts.ts +184 -102
- package/src/providers/derive.ts +11 -3
- package/src/providers/fastwire.ts +16 -3
- package/src/providers/label.ts +8 -3
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/quota/vendor-probes-oauth.ts +2 -1
- package/src/providers/reasoning-metadata.ts +38 -15
- package/src/providers/registry/entries-core.ts +95 -7
- package/src/providers/registry/entries-extended.ts +21 -10
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +31 -3
- package/src/providers/registry/types.ts +3 -1
- package/src/providers/resolved-model-policy-merge.ts +38 -8
- package/src/providers/resolved-model-policy.ts +4 -2
- package/src/providers/service-tier.ts +3 -1
- package/src/reasoning-effort.ts +6 -5
- package/src/responses/code-mode-helper-compat.ts +14 -4
- package/src/responses/code-mode-shell-input.ts +54 -0
- package/src/responses/custom-tool-compat.ts +173 -5
- package/src/responses/parser.ts +38 -2
- package/src/responses/reasoning-replay-cache.ts +26 -0
- package/src/router.ts +11 -1
- package/src/routing/history/indexer.ts +7 -2
- package/src/routing/history/schema.ts +3 -1
- package/src/server/adapter-resolve.ts +2 -2
- package/src/server/auth-cors.ts +3 -0
- package/src/server/chat-completions.ts +14 -1
- package/src/server/chat-native.ts +17 -0
- package/src/server/claude-messages.ts +4 -3
- package/src/server/direct-local-http.ts +45 -19
- package/src/server/gui-session.ts +6 -15
- package/src/server/index/package-tree-guard.ts +53 -0
- package/src/server/index/serve-options.ts +17 -3
- package/src/server/index/startup-warnings.ts +17 -0
- package/src/server/index.ts +15 -17
- package/src/server/lifecycle.ts +8 -0
- package/src/server/live.ts +54 -4
- package/src/server/management/agent-settings-routes.ts +44 -8
- package/src/server/management/anthropic-reset-grant-routes.ts +252 -0
- package/src/server/management/codex-prompt-routes.ts +5 -1
- package/src/server/management/config-routes.ts +14 -4
- package/src/server/management/oauth-account-routes.ts +17 -1
- package/src/server/management/provider-overwrite-carry.ts +164 -0
- package/src/server/management/provider-routes.ts +36 -6
- package/src/server/management/remote-workspace-routes.ts +2 -2
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/management/shadow-call-validation.ts +39 -0
- package/src/server/management/system-restart.ts +72 -6
- package/src/server/management-api.ts +16 -2
- package/src/server/management-auth.ts +52 -8
- package/src/server/proxy-liveness.ts +110 -4
- package/src/server/request-log.ts +26 -9
- package/src/server/request-metrics.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +6 -3
- package/src/server/responses/adapter-dispatch.ts +65 -1
- package/src/server/responses/compact.ts +31 -1
- package/src/server/responses/compaction-routing.ts +28 -3
- package/src/server/responses/core-codex-account.ts +85 -26
- package/src/server/responses/core-combo-failure.ts +16 -15
- package/src/server/responses/core-normalize.ts +5 -1
- package/src/server/responses/core-opaque-recovery.ts +50 -2
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core-replay.ts +7 -0
- package/src/server/responses/core.ts +1 -1
- package/src/server/responses/encrypted-payload.ts +23 -9
- package/src/server/responses/passthrough-delivery.ts +55 -35
- package/src/server/responses/passthrough-dispatch.ts +12 -13
- package/src/server/responses/policy-fallback.ts +12 -1
- package/src/server/responses/request-prepare.ts +46 -7
- package/src/server/responses/request-spend.ts +31 -15
- package/src/server/responses/run-turn-execution.ts +6 -5
- package/src/server/responses/shadow-target-availability.ts +61 -0
- package/src/server/responses-custom-tool-repair.ts +2 -0
- package/src/service/claim.ts +185 -0
- package/src/service/cli.ts +10 -1
- package/src/service/guarded-manager-target.ts +151 -0
- package/src/service/guards.ts +47 -48
- package/src/service/managing-cli.ts +170 -0
- package/src/service/orchestration.ts +3 -1
- package/src/service/state.ts +4 -0
- package/src/service/systemd.ts +16 -1
- package/src/service.ts +1 -1
- package/src/types/config.ts +8 -0
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +10 -0
- package/src/types/tools.ts +9 -1
- package/src/types/wire.ts +68 -4
- package/src/types.ts +1 -0
- package/src/update/job.ts +10 -16
- package/src/update/npm-invocation.mjs +17 -16
- package/src/update/transactional-install.d.mts +22 -1
- package/src/update/transactional-install.mjs +176 -19
- package/src/update/update-failure-guidance.d.mts +8 -0
- package/src/update/update-failure-guidance.mjs +47 -0
- package/src/usage/expected-prices.ts +54 -12
- package/src/usage/log.ts +117 -3
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/timeline.ts +34 -6
- package/src/usage/user-cost-overlay-reconciler.ts +3 -3
- package/src/vision/reasoning.ts +2 -4
- package/src/web-search/xai-executor.ts +14 -4
- package/gui/dist/assets/App-BqrsSrIR.js +0 -50
- package/gui/dist/assets/index-C6SJrh0N.js +0 -86
- package/gui/dist/assets/index-DdDunwDb.css +0 -1
- package/gui/dist/assets/usage-companion-chart-CzAAAB1o.js +0 -1
- package/src/adapters/kiro-thinking.ts +0 -112
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/** Read-only manager-to-proxy binding for consent-bound desktop stops. */
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { verifyPidIdentity } from "../config/process-state";
|
|
5
|
+
import { statusWinswRaw } from "../lib/winsw";
|
|
6
|
+
import { systemdProperty } from "../service-manager-probe";
|
|
7
|
+
import { expectedLaunchdCommand, launchdEvictionTargets, probeLaunchdLoadState, runLaunchctl } from "./launchd";
|
|
8
|
+
import { probeSystemdUnitInactive, unitPath } from "./systemd";
|
|
9
|
+
import { probeWindowsSchedulerTask } from "./windows-scheduler";
|
|
10
|
+
import { TASK } from "./state";
|
|
11
|
+
|
|
12
|
+
export type GuardedManagerTarget =
|
|
13
|
+
| { kind: "absent" }
|
|
14
|
+
| { kind: "bound"; pid: number; managerPid: number; backend: "launchd" | "systemd" }
|
|
15
|
+
| { kind: "unknown"; reason: string };
|
|
16
|
+
|
|
17
|
+
export type GuardedManagerStopped = "inactive" | "active" | "unknown";
|
|
18
|
+
|
|
19
|
+
export function managerOwnsApprovedPid(
|
|
20
|
+
managerPid: number,
|
|
21
|
+
approvedPid: number,
|
|
22
|
+
parentOf: (pid: number) => number | null,
|
|
23
|
+
): boolean {
|
|
24
|
+
let current = approvedPid;
|
|
25
|
+
const seen = new Set<number>();
|
|
26
|
+
for (let depth = 0; depth < 16; depth += 1) {
|
|
27
|
+
if (current === managerPid) return true;
|
|
28
|
+
if (!Number.isSafeInteger(current) || current <= 0 || seen.has(current)) return false;
|
|
29
|
+
seen.add(current);
|
|
30
|
+
const parent = parentOf(current);
|
|
31
|
+
if (parent === null || !Number.isSafeInteger(parent) || parent <= 0) return false;
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parentPid(pid: number): number | null {
|
|
38
|
+
try {
|
|
39
|
+
const raw = execFileSync("ps", ["-o", "ppid=", "-p", String(pid)], {
|
|
40
|
+
encoding: "utf8", timeout: 2_000, stdio: ["ignore", "pipe", "pipe"],
|
|
41
|
+
}).trim();
|
|
42
|
+
const value = Number(raw);
|
|
43
|
+
return /^\d+$/.test(raw) && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
44
|
+
} catch { return null; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function positivePid(raw: string | null | undefined): number | null {
|
|
48
|
+
if (!raw || !/^\d+$/.test(raw)) return null;
|
|
49
|
+
const pid = Number(raw);
|
|
50
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface GuardedManagerDeps {
|
|
54
|
+
platform?: NodeJS.Platform;
|
|
55
|
+
launchctl?: typeof runLaunchctl;
|
|
56
|
+
expectedCommand?: (port: number) => string;
|
|
57
|
+
systemdShow?: () => string;
|
|
58
|
+
parentOf?: (pid: number) => number | null;
|
|
59
|
+
verifyPid?: (pid: number) => number | null;
|
|
60
|
+
scheduler?: typeof probeWindowsSchedulerTask;
|
|
61
|
+
winsw?: typeof statusWinswRaw;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Only one current manager with a verified parent chain may be stopped by name. */
|
|
65
|
+
export function inspectGuardedManagerTarget(
|
|
66
|
+
approvedPid: number,
|
|
67
|
+
approvedPort: number,
|
|
68
|
+
deps: GuardedManagerDeps = {},
|
|
69
|
+
): GuardedManagerTarget {
|
|
70
|
+
const platform = deps.platform ?? process.platform;
|
|
71
|
+
if ((deps.verifyPid ?? verifyPidIdentity)(approvedPid) !== approvedPid) {
|
|
72
|
+
return { kind: "unknown", reason: "approved process identity could not be verified" };
|
|
73
|
+
}
|
|
74
|
+
const parentOf = deps.parentOf ?? parentPid;
|
|
75
|
+
if (platform === "darwin") {
|
|
76
|
+
const run = deps.launchctl ?? runLaunchctl;
|
|
77
|
+
let managerPid: number | null = null;
|
|
78
|
+
try {
|
|
79
|
+
const expected = (deps.expectedCommand ?? expectedLaunchdCommand)(approvedPort);
|
|
80
|
+
for (const target of launchdEvictionTargets()) {
|
|
81
|
+
const result = run(["print", target]);
|
|
82
|
+
if (result.status === 112 || result.status === 113) continue;
|
|
83
|
+
if (result.status !== 0) return { kind: "unknown", reason: "launchd state is unreadable" };
|
|
84
|
+
if (managerPid !== null) return { kind: "unknown", reason: "multiple launchd jobs are loaded" };
|
|
85
|
+
const output = `${result.stdout}\n${result.stderr}`;
|
|
86
|
+
if (!output.includes(expected)) return { kind: "unknown", reason: "launchd command differs from registration" };
|
|
87
|
+
const pids = [...output.matchAll(/^\s*pid\s*=\s*(\d+)\s*$/gm)];
|
|
88
|
+
if (pids.length !== 1) return { kind: "unknown", reason: "launchd PID is ambiguous" };
|
|
89
|
+
managerPid = positivePid(pids[0]?.[1]);
|
|
90
|
+
if (managerPid === null) return { kind: "unknown", reason: "launchd PID is invalid" };
|
|
91
|
+
}
|
|
92
|
+
} catch { return { kind: "unknown", reason: "launchd state could not be verified" }; }
|
|
93
|
+
if (managerPid === null) return { kind: "absent" };
|
|
94
|
+
return managerOwnsApprovedPid(managerPid, approvedPid, parentOf)
|
|
95
|
+
? { kind: "bound", pid: approvedPid, managerPid, backend: "launchd" }
|
|
96
|
+
: { kind: "unknown", reason: "launchd job does not own the approved process" };
|
|
97
|
+
}
|
|
98
|
+
if (platform === "linux") {
|
|
99
|
+
try {
|
|
100
|
+
const output = (deps.systemdShow ?? (() => execFileSync("systemctl", ["--user", "show", "-p", "LoadState", "-p", "ActiveState", "-p", "MainPID", "-p", "FragmentPath", "-p", "NeedDaemonReload", TASK], {
|
|
101
|
+
encoding: "utf8", timeout: 2_000, stdio: ["ignore", "pipe", "pipe"],
|
|
102
|
+
})))();
|
|
103
|
+
const loaded = systemdProperty(output, "LoadState");
|
|
104
|
+
const active = systemdProperty(output, "ActiveState");
|
|
105
|
+
const pid = positivePid(systemdProperty(output, "MainPID"));
|
|
106
|
+
if (loaded === "not-found" && active === "inactive") return { kind: "absent" };
|
|
107
|
+
if (loaded === "loaded" && active === "inactive" && systemdProperty(output, "MainPID") === "0") return { kind: "absent" };
|
|
108
|
+
if (loaded !== "loaded" || active !== "active" || pid === null
|
|
109
|
+
|| systemdProperty(output, "NeedDaemonReload") !== "no"
|
|
110
|
+
|| resolve(systemdProperty(output, "FragmentPath") ?? "") !== resolve(unitPath())) {
|
|
111
|
+
return { kind: "unknown", reason: "systemd unit state is not current and active" };
|
|
112
|
+
}
|
|
113
|
+
return managerOwnsApprovedPid(pid, approvedPid, parentOf)
|
|
114
|
+
? { kind: "bound", pid: approvedPid, managerPid: pid, backend: "systemd" }
|
|
115
|
+
: { kind: "unknown", reason: "systemd unit does not own the approved process" };
|
|
116
|
+
} catch { return { kind: "unknown", reason: "systemd state could not be verified" }; }
|
|
117
|
+
}
|
|
118
|
+
if (platform === "win32") {
|
|
119
|
+
const scheduler = (deps.scheduler ?? probeWindowsSchedulerTask)();
|
|
120
|
+
const native = (deps.winsw ?? statusWinswRaw)();
|
|
121
|
+
return scheduler.status === "absent" && native === "nonexistent"
|
|
122
|
+
? { kind: "absent" }
|
|
123
|
+
: { kind: "unknown", reason: "Windows manager child PID cannot be proven" };
|
|
124
|
+
}
|
|
125
|
+
return { kind: "unknown", reason: "unsupported service manager platform" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A guarded success needs manager absence after settlement and once more at publication. */
|
|
129
|
+
export async function observeGuardedManagerStopped(
|
|
130
|
+
manager: Exclude<GuardedManagerTarget, { kind: "unknown" }>,
|
|
131
|
+
deps: Pick<GuardedManagerDeps, "platform" | "scheduler" | "winsw"> & {
|
|
132
|
+
launchd?: typeof probeLaunchdLoadState;
|
|
133
|
+
systemd?: typeof probeSystemdUnitInactive;
|
|
134
|
+
} = {},
|
|
135
|
+
): Promise<GuardedManagerStopped> {
|
|
136
|
+
try {
|
|
137
|
+
const platform = deps.platform ?? process.platform;
|
|
138
|
+
if (platform === "darwin") {
|
|
139
|
+
const state = (deps.launchd ?? probeLaunchdLoadState)().state;
|
|
140
|
+
return state === "not-loaded" ? "inactive" : state === "unknown" ? "unknown" : "active";
|
|
141
|
+
}
|
|
142
|
+
if (platform === "linux") return (deps.systemd ?? probeSystemdUnitInactive)();
|
|
143
|
+
if (platform === "win32" && manager.kind === "absent") {
|
|
144
|
+
const scheduler = (deps.scheduler ?? probeWindowsSchedulerTask)();
|
|
145
|
+
const native = (deps.winsw ?? statusWinswRaw)();
|
|
146
|
+
if (scheduler.status === "absent" && native === "nonexistent") return "inactive";
|
|
147
|
+
return scheduler.status === "unknown" || native === "unknown" ? "unknown" : "active";
|
|
148
|
+
}
|
|
149
|
+
} catch { return "unknown"; }
|
|
150
|
+
return "unknown";
|
|
151
|
+
}
|
package/src/service/guards.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { getConfigDir, loadConfig } from "../config";
|
|
4
|
-
import {
|
|
4
|
+
import { withConfigMutationLockSync } from "../config/mutation-lock";
|
|
5
|
+
import { hardenReusedServiceApiToken, readServiceApiTokenState, serviceApiTokenFilePath } from "../lib/service-secrets";
|
|
5
6
|
import { tokenCollidesWithAdmin } from "../lib/admin-secrets";
|
|
6
7
|
import { randomBytes } from "node:crypto";
|
|
7
8
|
import { hardenSecretDir, hardenSecretPath } from "../lib/windows-secret-acl";
|
|
@@ -9,8 +10,10 @@ import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
|
9
10
|
import { isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
10
11
|
import { diagnoseService } from "./diagnostics";
|
|
11
12
|
import type { ServiceDiagnostic } from "./diagnostics";
|
|
12
|
-
import { currentCodexHome, currentOpenCodexHome, normalizePathForCompare,
|
|
13
|
+
import { currentCodexHome, currentOpenCodexHome, normalizePathForCompare, resolveServiceState, serviceCodexHomeMatchesInstall } from "./state";
|
|
13
14
|
import { resolveCodexSqliteHome } from "../codex/paths";
|
|
15
|
+
import type { CodexHomeDeps } from "../codex/home";
|
|
16
|
+
import { isLoopbackHostname } from "../codex/loopback-target";
|
|
14
17
|
import { win32 } from "node:path";
|
|
15
18
|
|
|
16
19
|
/**
|
|
@@ -42,16 +45,17 @@ export function serviceEnvironmentOwnedHere(): boolean {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
export function assertServiceEnvironmentMatchesInstall(): void {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
48
|
+
export function assertServiceEnvironmentMatchesInstall(deps: CodexHomeDeps = {}): void {
|
|
49
|
+
const resolution = resolveServiceState();
|
|
50
|
+
// Unreadable state keeps its existing interactive semantics (see assertNativeTeardownOwned);
|
|
51
|
+
// unattended writes use the tri-state inspector, which reports it as unknown.
|
|
52
|
+
if (resolution.kind !== "state") return;
|
|
53
|
+
const state = resolution.state;
|
|
54
|
+
const actualCodexHome = currentCodexHome(deps);
|
|
55
|
+
if (!serviceCodexHomeMatchesInstall(state.codexHome, deps)) {
|
|
52
56
|
throw new ServiceOwnershipError(
|
|
53
57
|
`Service was installed with CODEX_HOME=${state.codexHome}, but current CODEX_HOME=${actualCodexHome}. ` +
|
|
54
|
-
|
|
58
|
+
`Rerun with CODEX_HOME=${state.codexHome} so native Codex restore updates the recorded home.`,
|
|
55
59
|
);
|
|
56
60
|
}
|
|
57
61
|
const expectedOpenCodexHome = normalizePathForCompare(state.opencodexHome);
|
|
@@ -73,11 +77,6 @@ export function assertServiceEnvironmentMatchesInstall(): void {
|
|
|
73
77
|
}
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
function isLoopbackHostname(hostname: string | undefined): boolean {
|
|
77
|
-
const normalized = (hostname ?? "127.0.0.1").trim().toLowerCase();
|
|
78
|
-
return normalized === "" || normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
80
|
/**
|
|
82
81
|
* The `ocx` command a user should rerun for the service state they actually have.
|
|
83
82
|
*
|
|
@@ -228,42 +227,42 @@ function persistServiceApiToken(token: string): string {
|
|
|
228
227
|
* connected to a hub the same file holds that hub's issued client key, which must not be
|
|
229
228
|
* overwritten by a local install.
|
|
230
229
|
*
|
|
230
|
+
* Provisioning runs inside the cross-process config mutation lock: client-key rotation
|
|
231
|
+
* replaces `service-api-token` and records the new fingerprint under the same lock, so a
|
|
232
|
+
* reuse republish or a fresh write here can never interleave with a committed rotation and
|
|
233
|
+
* silently roll its bytes back.
|
|
234
|
+
*
|
|
231
235
|
* The PATH is logged; the value never is, and never reaches argv, a unit file or a plist.
|
|
232
236
|
*/
|
|
233
237
|
export function writeServiceApiTokenFile(): ProvisionedServiceApiToken | null {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return { path, origin: "
|
|
261
|
-
}
|
|
262
|
-
if (existing.kind === "unsafe") throw new Error(`${existing.reason}: ${serviceApiTokenFilePath()}`);
|
|
263
|
-
const path = persistServiceApiToken(randomBytes(32).toString("hex"));
|
|
264
|
-
console.log(`🔐 Provisioned an owner-only data-plane token at ${path}; nothing needs to be exported by hand.`);
|
|
265
|
-
console.log(" Remote machines get their own per-client key — run 'ocx hub invite' instead of copying this file.");
|
|
266
|
-
return { path, origin: "generated" };
|
|
238
|
+
return withConfigMutationLockSync(() => {
|
|
239
|
+
const token = process.env.OPENCODEX_API_AUTH_TOKEN?.trim();
|
|
240
|
+
if (token) {
|
|
241
|
+
// Last line of defence: every install/repair path funnels through here, so a
|
|
242
|
+
// collision cannot reach disk regardless of which caller ran (#2696).
|
|
243
|
+
assertNotAdminToken(token);
|
|
244
|
+
const path = persistServiceApiToken(token);
|
|
245
|
+
console.log(`🔐 Data-plane token taken from OPENCODEX_API_AUTH_TOKEN and stored at ${path} (owner-only).`);
|
|
246
|
+
return { path, origin: "env" };
|
|
247
|
+
}
|
|
248
|
+
if (isLoopbackHostname(loadConfig().hostname)) return null;
|
|
249
|
+
const existing = hardenReusedServiceApiToken(token => assertNotAdminToken(token, process.env, "file"));
|
|
250
|
+
if (existing.kind === "present") {
|
|
251
|
+
// The collision check is NOT only for the env branch. A file that already holds the admin
|
|
252
|
+
// token -- hand-pasted before #2696, or written by the very incident this unit closes --
|
|
253
|
+
// was silently accepted here, so `ocx status` reported `present (file)` and the hub
|
|
254
|
+
// crash-looped at boot with no command pointing at the cause.
|
|
255
|
+
const path = serviceApiTokenFilePath();
|
|
256
|
+
// No log line: repair/restart hit this on every run and an unconditional notice about a
|
|
257
|
+
// credential file trains operators to ignore the one that matters.
|
|
258
|
+
return { path, origin: "file" };
|
|
259
|
+
}
|
|
260
|
+
if (existing.kind === "unsafe") throw new Error(`${existing.reason}: ${serviceApiTokenFilePath()}`);
|
|
261
|
+
const path = persistServiceApiToken(randomBytes(32).toString("hex"));
|
|
262
|
+
console.log(`🔐 Provisioned an owner-only data-plane token at ${path}; nothing needs to be exported by hand.`);
|
|
263
|
+
console.log(" Remote machines get their own per-client key — run 'ocx hub invite' instead of copying this file.");
|
|
264
|
+
return { path, origin: "generated" };
|
|
265
|
+
});
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
export function sh(cmd: string): string {
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observe the two CLIs that can manage this runtime: the one baked into the preserved
|
|
3
|
+
* service registration, and the `ocx` the PATH resolves today.
|
|
4
|
+
*
|
|
5
|
+
* Both answers feed `assessServiceTakeoverCompatibility`, which refuses a takeover when
|
|
6
|
+
* either observation is unknown — a managing binary that cannot be named cannot be held to
|
|
7
|
+
* the ownership-aware floor. `absent` is a trustworthy answer; `unknown` is not.
|
|
8
|
+
*
|
|
9
|
+
* The PATH lookup is an in-module directory scan rather than `where`/`which`: spawning a
|
|
10
|
+
* shell builtin to find a binary makes the answer depend on the host's PATH handling, and
|
|
11
|
+
* this module exists so the resolve/claim path is deterministic and testable. And the one
|
|
12
|
+
* thing it must never do is spawn `process.execPath`: that binary is THIS CLI, and asking
|
|
13
|
+
* it for its version under `ocx --version` semantics is the recursion #5418 closed. When
|
|
14
|
+
* the located file IS this binary, its version is already known.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, statSync } from "node:fs";
|
|
17
|
+
import { spawnSync, type SpawnSyncReturns } from "node:child_process";
|
|
18
|
+
import { delimiter, posix, win32 } from "node:path";
|
|
19
|
+
import { parseStrictSemver } from "../lib/strict-semver";
|
|
20
|
+
import { packageVersion } from "../lib/package-version";
|
|
21
|
+
import {
|
|
22
|
+
registeredManagingCliInvocation,
|
|
23
|
+
type ManagingCliObservation,
|
|
24
|
+
type ManagingCliRole,
|
|
25
|
+
} from "./ownership-compatibility";
|
|
26
|
+
import type { ServiceInstallState } from "./state";
|
|
27
|
+
|
|
28
|
+
const VERSION_PROBE_TIMEOUT_MS = 5000;
|
|
29
|
+
|
|
30
|
+
export interface ManagingCliDeps {
|
|
31
|
+
/** Environment to scan PATH/PATHEXT in. Defaults to `process.env`. */
|
|
32
|
+
env?: Record<string, string | undefined>;
|
|
33
|
+
/** Spawn seam. Defaults to `spawnSync`. */
|
|
34
|
+
spawn?: typeof spawnSync;
|
|
35
|
+
/** The running binary, for the never-spawn-yourself rule. Defaults to `process.execPath`. */
|
|
36
|
+
execPath?: string;
|
|
37
|
+
/** This binary's own version, for the self-observation shortcut. */
|
|
38
|
+
ownVersion?: () => string;
|
|
39
|
+
/** Filesystem existence seam. */
|
|
40
|
+
exists?: (path: string) => boolean;
|
|
41
|
+
/** Validate the selected candidate as a regular file. */
|
|
42
|
+
isFile?: (path: string) => boolean;
|
|
43
|
+
/** Host platform override for tests. */
|
|
44
|
+
platform?: NodeJS.Platform;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The first strict-semver token in a `--version` line (`opencodex 2.61.0` → `2.61.0`). */
|
|
48
|
+
function versionFromOutput(stdout: string): string | null {
|
|
49
|
+
for (const token of stdout.trim().split(/\s+/)) {
|
|
50
|
+
if (parseStrictSemver(token)) return token;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function probeVersion(
|
|
56
|
+
executable: string,
|
|
57
|
+
args: readonly string[],
|
|
58
|
+
deps: Required<Pick<ManagingCliDeps, "spawn" | "platform" | "env">>,
|
|
59
|
+
): ManagingCliObservation {
|
|
60
|
+
const identity = [executable, ...args].join(" ");
|
|
61
|
+
let result: SpawnSyncReturns<string>;
|
|
62
|
+
try {
|
|
63
|
+
const windowsShim =
|
|
64
|
+
deps.platform === "win32" && /\.(cmd|bat)$/i.test(executable);
|
|
65
|
+
if (windowsShim && [executable, ...args].some(part => /[&|<>^%!"()]/.test(part))) {
|
|
66
|
+
return { status: "unknown", reason: "the selected Windows command shim invocation cannot be probed safely" };
|
|
67
|
+
}
|
|
68
|
+
result = deps.spawn(
|
|
69
|
+
windowsShim ? (deps.env?.ComSpec ?? "cmd.exe") : executable,
|
|
70
|
+
windowsShim ? ["/c", executable, ...args, "--version"] : [...args, "--version"],
|
|
71
|
+
{ timeout: VERSION_PROBE_TIMEOUT_MS, encoding: "utf8", stdio: "pipe", windowsHide: true },
|
|
72
|
+
) as SpawnSyncReturns<string>;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
status: "unknown",
|
|
76
|
+
reason: `${identity} --version could not run: ${error instanceof Error ? error.message : String(error)}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (result.error) {
|
|
80
|
+
return {
|
|
81
|
+
status: "unknown",
|
|
82
|
+
reason: `${identity} --version did not finish: ${result.error.message}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (result.status !== 0) {
|
|
86
|
+
return { status: "unknown", reason: `${identity} --version exited ${result.status ?? "without a code"}` };
|
|
87
|
+
}
|
|
88
|
+
const version = versionFromOutput(result.stdout ?? "");
|
|
89
|
+
return version === null
|
|
90
|
+
? { status: "unknown", reason: `${identity} --version printed no semver` }
|
|
91
|
+
: { status: "observed", version, identity };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Where `ocx` resolves on PATH without spawning `where`/`which`.
|
|
96
|
+
*
|
|
97
|
+
* Returns the absolute candidate or null. On Windows every PATHEXT extension is tried (and
|
|
98
|
+
* the bare name, for extensionless shims); elsewhere the bare name only.
|
|
99
|
+
*/
|
|
100
|
+
function findOcxOnPath(
|
|
101
|
+
env: Record<string, string | undefined>,
|
|
102
|
+
exists: (path: string) => boolean,
|
|
103
|
+
platform: NodeJS.Platform,
|
|
104
|
+
): string | null {
|
|
105
|
+
const pathValue = env.PATH ?? env.Path ?? env.path;
|
|
106
|
+
if (!pathValue) return null;
|
|
107
|
+
const pathApi = platform === "win32" ? win32 : posix;
|
|
108
|
+
const extensions = platform === "win32"
|
|
109
|
+
? [...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(ext => /^\.[A-Za-z0-9]+$/.test(ext)), ""]
|
|
110
|
+
: [""];
|
|
111
|
+
for (const directory of pathValue.split(platform === "win32" ? ";" : delimiter)) {
|
|
112
|
+
if (!directory) continue;
|
|
113
|
+
for (const extension of extensions) {
|
|
114
|
+
const candidate = pathApi.join(directory, `ocx${extension}`);
|
|
115
|
+
if (exists(candidate)) return pathApi.resolve(candidate);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function observeServiceRegistration(
|
|
122
|
+
state: ServiceInstallState | null,
|
|
123
|
+
deps: Required<Pick<ManagingCliDeps, "spawn" | "platform" | "env">>,
|
|
124
|
+
): ManagingCliObservation {
|
|
125
|
+
const invocation = registeredManagingCliInvocation(state);
|
|
126
|
+
if (invocation.status !== "resolved") return invocation;
|
|
127
|
+
return probeVersion(invocation.executable, invocation.args, deps);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function observePathCli(
|
|
131
|
+
deps: Required<Pick<ManagingCliDeps, "spawn" | "platform" | "env" | "execPath" | "ownVersion" | "exists" | "isFile">>,
|
|
132
|
+
): ManagingCliObservation {
|
|
133
|
+
const found = findOcxOnPath(deps.env, deps.exists, deps.platform);
|
|
134
|
+
if (!found) return { status: "absent" };
|
|
135
|
+
if (!deps.isFile(found)) {
|
|
136
|
+
return { status: "unknown", reason: "the selected managing CLI is not a readable file" };
|
|
137
|
+
}
|
|
138
|
+
const self = (deps.platform === "win32" ? win32 : posix).resolve(deps.execPath);
|
|
139
|
+
if (found === self || (deps.platform === "win32" && found.toLowerCase() === self.toLowerCase())) {
|
|
140
|
+
// Never spawn ourselves for our own version: the answer is already in hand, and the
|
|
141
|
+
// recursion that produced it is the #5418 regression.
|
|
142
|
+
return { status: "observed", version: deps.ownVersion(), identity: found };
|
|
143
|
+
}
|
|
144
|
+
return probeVersion(found, [], deps);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Both managing-CLI observations, for `assessServiceTakeoverCompatibility` and for the
|
|
149
|
+
* revalidation `recordServiceOwner` runs inside its lock.
|
|
150
|
+
*/
|
|
151
|
+
export function observeManagingClis(
|
|
152
|
+
state: ServiceInstallState | null,
|
|
153
|
+
deps: ManagingCliDeps = {},
|
|
154
|
+
): Readonly<Record<ManagingCliRole, ManagingCliObservation>> {
|
|
155
|
+
const resolved = {
|
|
156
|
+
spawn: deps.spawn ?? spawnSync,
|
|
157
|
+
platform: deps.platform ?? process.platform,
|
|
158
|
+
env: deps.env ?? (process.env as Record<string, string | undefined>),
|
|
159
|
+
execPath: deps.execPath ?? process.execPath,
|
|
160
|
+
ownVersion: deps.ownVersion ?? packageVersion,
|
|
161
|
+
exists: deps.exists ?? existsSync,
|
|
162
|
+
isFile: deps.isFile ?? ((path: string) => {
|
|
163
|
+
try { return statSync(path).isFile(); } catch { return false; }
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
"service-registration": observeServiceRegistration(state, resolved),
|
|
168
|
+
path: observePathCli(resolved),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -172,7 +172,8 @@ export async function proxyStillLiveAfterStop(deps: {
|
|
|
172
172
|
const probeDeadline = canRespawn
|
|
173
173
|
? deadline
|
|
174
174
|
: now() + (SERVICE_STOP_LIVENESS.timeoutMs! * SERVICE_STOP_LIVENESS.attempts! + 250);
|
|
175
|
-
|
|
175
|
+
// A package-tree-fenced proxy (#5496) still holds the port; stop must not call it gone.
|
|
176
|
+
return findLiveProxy({ ...SERVICE_STOP_LIVENESS, deadlineAt: probeDeadline, nowFn: now, acceptPackageTreeFenced: true });
|
|
176
177
|
});
|
|
177
178
|
for (;;) {
|
|
178
179
|
try {
|
|
@@ -205,6 +206,7 @@ async function stopTrackedProxyIfRunning(): Promise<TrackedProxyCleanupResult> {
|
|
|
205
206
|
const live = await findLiveProxy({
|
|
206
207
|
...SERVICE_STOP_LIVENESS,
|
|
207
208
|
deadlineAt: Date.now() + 7000,
|
|
209
|
+
acceptPackageTreeFenced: true,
|
|
208
210
|
});
|
|
209
211
|
const liveKillPid = verifiedKillTarget(live?.pid);
|
|
210
212
|
if (liveKillPid !== null) {
|
package/src/service/state.ts
CHANGED
|
@@ -857,6 +857,10 @@ export function serviceHomeMatches(a: string, b: string): boolean {
|
|
|
857
857
|
return normalizePathForCompare(a) === normalizePathForCompare(b);
|
|
858
858
|
}
|
|
859
859
|
|
|
860
|
+
export function serviceCodexHomeMatchesInstall(recordedHome: string, deps: CodexHomeDeps = {}): boolean {
|
|
861
|
+
return serviceHomeMatches(recordedHome, currentCodexHome(deps));
|
|
862
|
+
}
|
|
863
|
+
|
|
860
864
|
/** Single accessor for backend-sensitive service code — v1/legacy state maps to scheduler. */
|
|
861
865
|
export function readServiceBackend(): ServiceBackend {
|
|
862
866
|
return readServiceInstallState()?.backend === "native" ? "native" : "scheduler";
|
package/src/service/systemd.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
2
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
3
3
|
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -199,6 +199,21 @@ export function stopSystemd(): void { try { sh(`systemctl --user stop ${TASK}`);
|
|
|
199
199
|
|
|
200
200
|
export function statusSystemd(): string { try { return sh(`systemctl --user status ${TASK}`); } catch { return ""; } }
|
|
201
201
|
|
|
202
|
+
/** Strict user-unit inactivity proof for guarded desktop takeover. */
|
|
203
|
+
export function probeSystemdUnitInactive(deps: { show?: () => string } = {}): "inactive" | "active" | "unknown" {
|
|
204
|
+
try {
|
|
205
|
+
const output = (deps.show ?? (() => execFileSync("systemctl", ["--user", "show", "-p", "ActiveState", "-p", "MainPID", TASK], {
|
|
206
|
+
encoding: "utf8", timeout: 2_000, stdio: ["ignore", "pipe", "pipe"],
|
|
207
|
+
})))();
|
|
208
|
+
const active = systemdProperty(output, "ActiveState")?.toLowerCase();
|
|
209
|
+
const pid = systemdProperty(output, "MainPID");
|
|
210
|
+
if (active === "inactive" && pid === "0") return "inactive";
|
|
211
|
+
if (["active", "activating", "deactivating", "reloading"].includes(active ?? "")
|
|
212
|
+
|| (pid !== null && /^\d+$/.test(pid) && Number(pid) > 0)) return "active";
|
|
213
|
+
return "unknown";
|
|
214
|
+
} catch { return "unknown"; }
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
export function uninstallSystemd(deps: {
|
|
203
218
|
run?: (command: string) => string;
|
|
204
219
|
unitExists?: () => boolean;
|
package/src/service.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export type { ServiceBackend, ServiceInstallState, ServiceStateEvidence, ServiceStateResolution, ServiceOwner, ServiceOwnership, ServiceOwnershipSubject, ServiceOwnershipResolution, ServiceStateSwapDeps, RecordServiceOwnerRequest, RecordServiceOwnerDeps, ReleaseServiceOwnerDeps, RemoveServiceStateDeps } from "./service/state";
|
|
10
|
-
export { SERVICE_MANAGED_ENV, SERVICE_OWNERSHIP_PROTOCOL_VERSION, SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION, stableLauncherEntry, serviceLogPath, serviceStatePaths, serviceStatePathsForOpenCodexHome, parseServiceInstallState, parseServiceOwnership, inspectServiceStateEvidence, resolveServiceState, currentServiceHomes, serviceHomeMatches, readServiceBackend, serviceReinstallArgs, serviceInstallArgs, ServiceStateConflictError, ServiceOwnershipSubjectMismatchError, ServiceOwnershipSubjectUnknownError, ServiceTakeoverCompatibilityChangedError, swapServiceInstallState, removeServiceInstallStateRecords, serviceOwnership, resolveServiceOwnership, sameServiceOwnershipSubject, desktopOwnsService, ownershipGrantedTo, recordServiceOwner, releaseServiceOwner } from "./service/state";
|
|
10
|
+
export { SERVICE_MANAGED_ENV, SERVICE_OWNERSHIP_PROTOCOL_VERSION, SERVICE_OWNERSHIP_MINIMUM_CLI_VERSION, stableLauncherEntry, serviceLogPath, serviceStatePaths, serviceStatePathsForOpenCodexHome, parseServiceInstallState, parseServiceOwnership, inspectServiceStateEvidence, resolveServiceState, currentServiceHomes, serviceHomeMatches, serviceCodexHomeMatchesInstall, readServiceBackend, serviceReinstallArgs, serviceInstallArgs, ServiceStateConflictError, ServiceOwnershipSubjectMismatchError, ServiceOwnershipSubjectUnknownError, ServiceTakeoverCompatibilityChangedError, swapServiceInstallState, removeServiceInstallStateRecords, serviceOwnership, resolveServiceOwnership, sameServiceOwnershipSubject, desktopOwnsService, ownershipGrantedTo, recordServiceOwner, releaseServiceOwner } from "./service/state";
|
|
11
11
|
export type { OwnershipMutationLeaseOptions, OwnershipMutationLease } from "./service/ownership-mutation-lease.mjs";
|
|
12
12
|
export { acquireOwnershipMutationLease, withOwnershipMutationLease } from "./service/ownership-mutation-lease.mjs";
|
|
13
13
|
export type { ManagingCliRole, ManagingCliObservation, RegisteredManagingCliInvocation, ServiceTakeoverCompatibilityInput, ServiceTakeoverCompatibility } from "./service/ownership-compatibility";
|
package/src/types/config.ts
CHANGED
|
@@ -924,6 +924,12 @@ export interface OcxConfig {
|
|
|
924
924
|
* which has no row, can be ordered too. Range -100..100.
|
|
925
925
|
*/
|
|
926
926
|
codexAccountPriorities?: Record<string, number>;
|
|
927
|
+
/**
|
|
928
|
+
* Per-account proactive-switch threshold overrides. Missing account entry inherits
|
|
929
|
+
* `autoSwitchThreshold`; 0 disables usage-driven switching only for that account.
|
|
930
|
+
* Includes the synthetic `__main__` Desktop account. Range 0..100.
|
|
931
|
+
*/
|
|
932
|
+
codexAccountAutoSwitchThresholds?: Record<string, number>;
|
|
927
933
|
/**
|
|
928
934
|
* Account id the operator last selected by hand. Suppresses upward priority
|
|
929
935
|
* preemption until that account crosses the auto-switch threshold. Stores the
|
|
@@ -1017,6 +1023,8 @@ export interface OcxConfig {
|
|
|
1017
1023
|
activeCodexAccountId?: string;
|
|
1018
1024
|
/** Auto-switch threshold (0-100). Default 80. 0 = disabled. */
|
|
1019
1025
|
autoSwitchThreshold?: number;
|
|
1026
|
+
/** Opt-in: return bound quota-strategy tasks to recovered higher-priority accounts. */
|
|
1027
|
+
codexAccountPriorityFailback?: boolean;
|
|
1020
1028
|
/** New-session account rotation strategy for the Codex pool. Default quota (today's behaviour). */
|
|
1021
1029
|
accountPoolStrategy?: OcxAccountPoolRotationStrategy | "reset-first";
|
|
1022
1030
|
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
package/src/types/provider.ts
CHANGED
|
@@ -242,6 +242,12 @@ export interface TierObservationContext {
|
|
|
242
242
|
* preserving the behaviour for the public API where the echo does mean what it says.
|
|
243
243
|
*/
|
|
244
244
|
responseTierAuthoritative?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Set when the upstream refused the fast wire earlier in this request and the proxy resent at
|
|
247
|
+
* standard speed (Anthropic `speed: "fast"` without entitlement). The resend's outcome is then
|
|
248
|
+
* a `response-declined` downgrade rather than an unavailable wire.
|
|
249
|
+
*/
|
|
250
|
+
upstreamDeclinedFast?: boolean;
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
export type TierDecision =
|
|
@@ -950,6 +956,16 @@ export interface OcxProviderConfig {
|
|
|
950
956
|
* thinking separately in `reasoning_content` / `reasoning_details` instead of visible content.
|
|
951
957
|
*/
|
|
952
958
|
reasoningSplitModels?: string[];
|
|
959
|
+
/**
|
|
960
|
+
* Model ids served by a gateway that runs no server-side reasoning parser, so a thinking model
|
|
961
|
+
* leaves its chain of thought inline in `content` as `<think>` / `<thinking>` / `<reasoning>`
|
|
962
|
+
* blocks and never sends `reasoning_content` or `reasoning_details`. Without this the whole
|
|
963
|
+
* chain of thought renders as the answer. The openai-chat adapter then splits those blocks back
|
|
964
|
+
* into reasoning. Off by default and narrow on purpose: 66 registry providers share this
|
|
965
|
+
* adapter, and a gateway that does parse reasoning must not have its visible content rewritten.
|
|
966
|
+
* Prefer a provider-side parser or `reasoningSplitModels` when the upstream supports either.
|
|
967
|
+
*/
|
|
968
|
+
inlineThinkTagModels?: string[];
|
|
953
969
|
/**
|
|
954
970
|
* Model ids whose chat endpoint carries thinking as a structured `reasoning_details` array
|
|
955
971
|
* (MiniMax M-series with `reasoning_split`): stream deltas repeat each detail's `text` as a
|
package/src/types/request.ts
CHANGED
|
@@ -90,6 +90,16 @@ export interface OcxParsedRequest {
|
|
|
90
90
|
* prepareOpaqueBlobRecovery after an authoritative rejection; consumers strip replayed blobs.
|
|
91
91
|
*/
|
|
92
92
|
_stripReasoningEncryptedContent?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Set when replayed reasoning item ids name items in a store this destination cannot read: by
|
|
95
|
+
* prepareOpaqueBlobRecovery before the one recovery rebuild, and by bindRouteReasoningReplayScope
|
|
96
|
+
* while the rejection memo is live or after a proven switch to a different destination or
|
|
97
|
+
* credential. The Responses passthrough then removes the `id` of every replayed reasoning item,
|
|
98
|
+
* whether or not it carries a blob. A stateful destination resolves a replayed id against its own
|
|
99
|
+
* store, so keeping it turns the send into `Item with id 'rs_…' not found` (#5583). A model
|
|
100
|
+
* change on the same destination and credential does not set this.
|
|
101
|
+
*/
|
|
102
|
+
_dropForeignReasoningItemIds?: boolean;
|
|
93
103
|
/** Final-route opt-in: emit v2 collaboration message arguments as plaintext on ChatGPT. */
|
|
94
104
|
_plaintextV2AgentMessages?: boolean;
|
|
95
105
|
/**
|
package/src/types/tools.ts
CHANGED
|
@@ -129,6 +129,8 @@ export const NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES: ReadonlySet<string> = new Set
|
|
|
129
129
|
*
|
|
130
130
|
* Rewrites invented `default.<name>` prefixes back to a declared bare tool when that bare tool
|
|
131
131
|
* is declared and neither `default.<name>` nor `default__<name>` was explicitly declared (#4176).
|
|
132
|
+
* The same wrapper may surround an already-flattened namespace identity; accept that exact
|
|
133
|
+
* declared suffix without treating its child name as a bare declaration.
|
|
132
134
|
* Also normalizes legacy helper names (`exec_command`, `shell_command`, `apply_patch`, `view_image`) to
|
|
133
135
|
* `exec` when code-mode `exec` is declared in the request catalog.
|
|
134
136
|
*
|
|
@@ -151,7 +153,13 @@ export function normalizeDeclaredToolName(
|
|
|
151
153
|
const bareDeclared = declaredBare ?? declared;
|
|
152
154
|
if (
|
|
153
155
|
bare.length > 0
|
|
154
|
-
&&
|
|
156
|
+
&& (
|
|
157
|
+
bareDeclared.has(bare)
|
|
158
|
+
// Muse can wrap the complete `namespace__tool` identity in `default.`. Requiring the
|
|
159
|
+
// exact flattened identity to be declared preserves the #4176 provenance boundary:
|
|
160
|
+
// `default.tool` still cannot borrow a namespaced tool's manufactured bare alias.
|
|
161
|
+
|| (bare.includes("__") && declared.has(bare))
|
|
162
|
+
)
|
|
155
163
|
&& !declared.has("default." + bare)
|
|
156
164
|
&& !declared.has("default__" + bare)
|
|
157
165
|
) {
|