codex-multi-auth 2.8.0 → 2.8.2
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/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -1
- package/dist/lib/accounts.d.ts +15 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +126 -21
- package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
- package/dist/lib/codex-manager/account-pool-write.js +4 -0
- package/dist/lib/codex-manager/backend-settings-schema.js +1 -1
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +5 -1
- package/dist/lib/codex-manager/commands/uninstall.d.ts +1 -0
- package/dist/lib/codex-manager/commands/uninstall.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/uninstall.js +12 -1
- package/dist/lib/codex-manager/commands/why-selected.d.ts +1 -1
- package/dist/lib/codex-manager/commands/why-selected.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/why-selected.js +1 -1
- package/dist/lib/codex-manager/health-check.d.ts.map +1 -1
- package/dist/lib/codex-manager/health-check.js +19 -3
- package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
- package/dist/lib/codex-manager/login-oauth.js +13 -7
- package/dist/lib/codex-manager.d.ts +4 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +50 -16
- package/dist/lib/config.d.ts +3 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -1
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +7 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +3 -1
- package/dist/lib/policy/runtime-policy.d.ts +1 -0
- package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
- package/dist/lib/policy/runtime-policy.js +7 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +35 -15
- package/dist/lib/rotation.d.ts +4 -0
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +13 -6
- package/dist/lib/runtime/app-bind.d.ts +40 -0
- package/dist/lib/runtime/app-bind.d.ts.map +1 -1
- package/dist/lib/runtime/app-bind.js +550 -20
- package/dist/lib/runtime/rotation-account-selection.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-account-selection.js +2 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts +2 -0
- package/dist/lib/runtime/rotation-proxy-state.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-token-refresh.d.ts.map +1 -1
- package/dist/lib/runtime/rotation-token-refresh.js +3 -0
- package/dist/lib/runtime-constants.d.ts +2 -0
- package/dist/lib/runtime-constants.d.ts.map +1 -1
- package/dist/lib/runtime-constants.js +2 -0
- package/dist/lib/runtime-rotation-proxy.d.ts +8 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +59 -5
- package/dist/lib/schemas.d.ts +15 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +3 -0
- package/dist/lib/storage/flagged-storage.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage.js +15 -0
- package/dist/lib/storage/path-state.d.ts +1 -0
- package/dist/lib/storage/path-state.d.ts.map +1 -1
- package/dist/lib/storage/path-state.js +30 -3
- package/dist/lib/storage/public-types.d.ts +6 -0
- package/dist/lib/storage/public-types.d.ts.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +17 -4
- package/package.json +7 -5
- package/scripts/codex-app-router.js +47 -3
- package/scripts/codex.js +234 -30
- package/scripts/install-codex-auth-utils.js +9 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/preuninstall.js +11 -0
|
@@ -36,6 +36,7 @@ function parseArgs(argv) {
|
|
|
36
36
|
host: "127.0.0.1",
|
|
37
37
|
port: 0,
|
|
38
38
|
statusPath: "",
|
|
39
|
+
identityToken: "",
|
|
39
40
|
statePath: "",
|
|
40
41
|
logPath: "",
|
|
41
42
|
maxLogBytes: DEFAULT_MAX_LOG_BYTES,
|
|
@@ -58,6 +59,11 @@ function parseArgs(argv) {
|
|
|
58
59
|
index += 1;
|
|
59
60
|
continue;
|
|
60
61
|
}
|
|
62
|
+
if (arg === "--identity-token") {
|
|
63
|
+
result.identityToken = next;
|
|
64
|
+
index += 1;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
61
67
|
if (arg === "--state") {
|
|
62
68
|
result.statePath = next;
|
|
63
69
|
index += 1;
|
|
@@ -133,7 +139,16 @@ function writeStatus(statusPath, payload) {
|
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
|
|
136
|
-
function createStatusPayload({
|
|
142
|
+
function createStatusPayload({
|
|
143
|
+
state,
|
|
144
|
+
proxyServer,
|
|
145
|
+
error,
|
|
146
|
+
stateRecord,
|
|
147
|
+
startedAt,
|
|
148
|
+
statusPath,
|
|
149
|
+
routerScriptPath,
|
|
150
|
+
identityToken,
|
|
151
|
+
}) {
|
|
137
152
|
const proxyStatus =
|
|
138
153
|
typeof proxyServer?.getStatus === "function" ? proxyServer.getStatus() : {};
|
|
139
154
|
const lastAccountIndex = proxyStatus.lastAccountIndex ?? null;
|
|
@@ -149,6 +164,15 @@ function createStatusPayload({ state, proxyServer, error, stateRecord }) {
|
|
|
149
164
|
kind: "codex-app-runtime-rotation-router",
|
|
150
165
|
state,
|
|
151
166
|
pid: process.pid,
|
|
167
|
+
startedAt,
|
|
168
|
+
statusPath: statusPath || readTrimmedString(stateRecord, "statusPath") || null,
|
|
169
|
+
identityToken:
|
|
170
|
+
identityToken || readTrimmedString(stateRecord, "identityToken") || null,
|
|
171
|
+
routerScriptPath:
|
|
172
|
+
routerScriptPath ||
|
|
173
|
+
readTrimmedString(stateRecord, "routerScriptPath") ||
|
|
174
|
+
process.argv[1] ||
|
|
175
|
+
null,
|
|
152
176
|
updatedAt: Date.now(),
|
|
153
177
|
baseUrl: proxyServer?.baseUrl ?? stateRecord?.baseUrl ?? null,
|
|
154
178
|
totalRequests: proxyStatus.totalRequests ?? 0,
|
|
@@ -232,6 +256,7 @@ function installLogBounds(maxBytes, logPath) {
|
|
|
232
256
|
}
|
|
233
257
|
|
|
234
258
|
async function main() {
|
|
259
|
+
const routerStartedAt = Date.now();
|
|
235
260
|
const args = parseArgs(process.argv.slice(2));
|
|
236
261
|
installLogBounds(args.maxLogBytes, args.logPath).unref?.();
|
|
237
262
|
const stateRecord = readState(args.statePath);
|
|
@@ -241,7 +266,16 @@ async function main() {
|
|
|
241
266
|
);
|
|
242
267
|
writeStatus(
|
|
243
268
|
args.statusPath,
|
|
244
|
-
createStatusPayload({
|
|
269
|
+
createStatusPayload({
|
|
270
|
+
state: "error",
|
|
271
|
+
proxyServer: null,
|
|
272
|
+
error,
|
|
273
|
+
stateRecord: null,
|
|
274
|
+
startedAt: routerStartedAt,
|
|
275
|
+
statusPath: args.statusPath,
|
|
276
|
+
routerScriptPath: process.argv[1],
|
|
277
|
+
identityToken: args.identityToken,
|
|
278
|
+
}),
|
|
245
279
|
);
|
|
246
280
|
throw error;
|
|
247
281
|
}
|
|
@@ -272,7 +306,17 @@ async function main() {
|
|
|
272
306
|
const writeCurrentStatus = (state, error) => {
|
|
273
307
|
writeStatus(
|
|
274
308
|
args.statusPath || stateRecord?.statusPath || "",
|
|
275
|
-
createStatusPayload({
|
|
309
|
+
createStatusPayload({
|
|
310
|
+
state,
|
|
311
|
+
proxyServer,
|
|
312
|
+
error,
|
|
313
|
+
stateRecord,
|
|
314
|
+
startedAt: routerStartedAt,
|
|
315
|
+
statusPath: args.statusPath || stateRecord?.statusPath || "",
|
|
316
|
+
routerScriptPath: stateRecord?.routerScriptPath || process.argv[1],
|
|
317
|
+
identityToken:
|
|
318
|
+
args.identityToken || stateRecord?.identityToken || "",
|
|
319
|
+
}),
|
|
276
320
|
);
|
|
277
321
|
};
|
|
278
322
|
|
package/scripts/codex.js
CHANGED
|
@@ -80,6 +80,8 @@ const APP_SERVER_CONFIG_ARGS_ENV =
|
|
|
80
80
|
"CODEX_MULTI_AUTH_APP_SERVER_CONFIG_ARGS_JSON";
|
|
81
81
|
const APP_RUNTIME_HELPER_STATUS_FILE =
|
|
82
82
|
RUNTIME_CONSTANTS.APP_RUNTIME_HELPER_STATUS_FILE;
|
|
83
|
+
const APP_RUNTIME_HELPER_OWNER_FILE =
|
|
84
|
+
RUNTIME_CONSTANTS.APP_RUNTIME_HELPER_OWNER_FILE;
|
|
83
85
|
const DEFAULT_APP_RUNTIME_HELPER_IDLE_MS = 12 * 60 * 60 * 1000;
|
|
84
86
|
const DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS = 5_000;
|
|
85
87
|
const APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS = 15_000;
|
|
@@ -110,6 +112,17 @@ let shadowHomeSyncLockOwnerWriteFailuresRemaining = Number.parseInt(
|
|
|
110
112
|
process.env.CODEX_MULTI_AUTH_TEST_SHADOW_LOCK_OWNER_WRITE_FAILURES ?? "0",
|
|
111
113
|
10,
|
|
112
114
|
);
|
|
115
|
+
let appServerShimFileCleanupBusyFailuresRemaining =
|
|
116
|
+
Number.parseInt(
|
|
117
|
+
process.env.CODEX_MULTI_AUTH_TEST_APP_SERVER_SHIM_FILE_CLEANUP_BUSY_FAILURES ??
|
|
118
|
+
"0",
|
|
119
|
+
10,
|
|
120
|
+
) || 0;
|
|
121
|
+
let appServerShimCopyBusyFailuresRemaining =
|
|
122
|
+
Number.parseInt(
|
|
123
|
+
process.env.CODEX_MULTI_AUTH_TEST_APP_SERVER_SHIM_COPY_BUSY_FAILURES ?? "0",
|
|
124
|
+
10,
|
|
125
|
+
) || 0;
|
|
113
126
|
const shadowHomeCleanupRetryMarkerDir =
|
|
114
127
|
(process.env.CODEX_MULTI_AUTH_TEST_SHADOW_RETRY_MARKER_DIR ?? "").trim();
|
|
115
128
|
let warnedInvalidRuntimeRotationProxyEnv = false;
|
|
@@ -122,6 +135,7 @@ async function loadRuntimeConstants() {
|
|
|
122
135
|
const fallback = {
|
|
123
136
|
RUNTIME_ROTATION_PROXY_PROVIDER_ID: `${APP_SERVER_ACCOUNT_DISPLAY_NAME}-runtime-proxy`,
|
|
124
137
|
APP_RUNTIME_HELPER_STATUS_FILE: "runtime-rotation-app-helper.json",
|
|
138
|
+
APP_RUNTIME_HELPER_OWNER_FILE: "runtime-rotation-app-helper-owner.json",
|
|
125
139
|
};
|
|
126
140
|
try {
|
|
127
141
|
const mod = await import("../dist/lib/runtime-constants.js");
|
|
@@ -134,6 +148,10 @@ async function loadRuntimeConstants() {
|
|
|
134
148
|
typeof mod.APP_RUNTIME_HELPER_STATUS_FILE === "string"
|
|
135
149
|
? mod.APP_RUNTIME_HELPER_STATUS_FILE
|
|
136
150
|
: fallback.APP_RUNTIME_HELPER_STATUS_FILE,
|
|
151
|
+
APP_RUNTIME_HELPER_OWNER_FILE:
|
|
152
|
+
typeof mod.APP_RUNTIME_HELPER_OWNER_FILE === "string"
|
|
153
|
+
? mod.APP_RUNTIME_HELPER_OWNER_FILE
|
|
154
|
+
: fallback.APP_RUNTIME_HELPER_OWNER_FILE,
|
|
137
155
|
};
|
|
138
156
|
} catch {
|
|
139
157
|
// Keep wrapper startup resilient when dist has not been built yet.
|
|
@@ -171,6 +189,39 @@ function removeDirectoryWithRetry(targetPath) {
|
|
|
171
189
|
}
|
|
172
190
|
}
|
|
173
191
|
|
|
192
|
+
function withSynchronousFileOperationRetry(operation) {
|
|
193
|
+
for (
|
|
194
|
+
let attempt = 0;
|
|
195
|
+
attempt <= SHADOW_HOME_CLEANUP_BACKOFF_MS.length;
|
|
196
|
+
attempt += 1
|
|
197
|
+
) {
|
|
198
|
+
try {
|
|
199
|
+
return operation();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (
|
|
202
|
+
!isRetryableShadowHomeCleanupError(error) ||
|
|
203
|
+
attempt === SHADOW_HOME_CLEANUP_BACKOFF_MS.length
|
|
204
|
+
) {
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
sleepSync(SHADOW_HOME_CLEANUP_BACKOFF_MS[attempt]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function maybeThrowSimulatedAppServerShimFileError(kind) {
|
|
213
|
+
const remaining =
|
|
214
|
+
kind === "copy"
|
|
215
|
+
? appServerShimCopyBusyFailuresRemaining
|
|
216
|
+
: appServerShimFileCleanupBusyFailuresRemaining;
|
|
217
|
+
if (remaining <= 0) return;
|
|
218
|
+
if (kind === "copy") appServerShimCopyBusyFailuresRemaining -= 1;
|
|
219
|
+
else appServerShimFileCleanupBusyFailuresRemaining -= 1;
|
|
220
|
+
const error = new Error(`simulated app-server shim ${kind} EBUSY`);
|
|
221
|
+
error.code = "EBUSY";
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
|
|
174
225
|
/**
|
|
175
226
|
* Best-effort async directory removal for hot-path callbacks (e.g. the
|
|
176
227
|
* status-refresh child's close/error handlers, which fire on the PARENT event
|
|
@@ -3653,14 +3704,35 @@ function installRuntimeRotationAppServerCliShim(forwardedEnv, configArgs = []) {
|
|
|
3653
3704
|
const preloadPath = join(shimDir, "codex-multi-auth-app-server-preload.mjs");
|
|
3654
3705
|
try {
|
|
3655
3706
|
try {
|
|
3656
|
-
|
|
3707
|
+
withSynchronousFileOperationRetry(() => {
|
|
3708
|
+
maybeThrowSimulatedAppServerShimFileError("cleanup");
|
|
3709
|
+
rmSync(executablePath, { force: true });
|
|
3710
|
+
});
|
|
3657
3711
|
} catch {
|
|
3658
|
-
// Best-effort stale shim cleanup only
|
|
3712
|
+
// Best-effort stale shim cleanup only; the copy below will report a
|
|
3713
|
+
// persistent failure without leaving a partially-created helper.
|
|
3659
3714
|
}
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3715
|
+
if (
|
|
3716
|
+
process.platform === "win32" ||
|
|
3717
|
+
(process.env.CODEX_MULTI_AUTH_TEST_FORCE_APP_SERVER_SHIM_COPY ?? "") === "1"
|
|
3718
|
+
) {
|
|
3719
|
+
// A Windows hard link to the running node.exe remains locked by the
|
|
3720
|
+
// helper process itself, which prevents the shim directory from being
|
|
3721
|
+
// removed during graceful helper shutdown. Use an independent image so
|
|
3722
|
+
// the helper can clean up its app-server shim before exiting.
|
|
3723
|
+
withSynchronousFileOperationRetry(() => {
|
|
3724
|
+
maybeThrowSimulatedAppServerShimFileError("copy");
|
|
3725
|
+
copyFileSync(process.execPath, executablePath);
|
|
3726
|
+
});
|
|
3727
|
+
} else {
|
|
3728
|
+
try {
|
|
3729
|
+
linkSync(process.execPath, executablePath);
|
|
3730
|
+
} catch {
|
|
3731
|
+
withSynchronousFileOperationRetry(() => {
|
|
3732
|
+
maybeThrowSimulatedAppServerShimFileError("copy");
|
|
3733
|
+
copyFileSync(process.execPath, executablePath);
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3664
3736
|
}
|
|
3665
3737
|
if (process.platform !== "win32") {
|
|
3666
3738
|
chmodSync(executablePath, 0o755);
|
|
@@ -3704,6 +3776,19 @@ function resolveRuntimeRotationAppHelperStatusPath(env = process.env) {
|
|
|
3704
3776
|
return join(multiAuthDir, APP_RUNTIME_HELPER_STATUS_FILE);
|
|
3705
3777
|
}
|
|
3706
3778
|
|
|
3779
|
+
function resolveRuntimeRotationAppHelperOwnerPath(env = process.env, helperPid) {
|
|
3780
|
+
const multiAuthDir =
|
|
3781
|
+
resolveOriginalMultiAuthDir(env) ?? join(resolveCodexHomeDir(env), "multi-auth");
|
|
3782
|
+
const ownerFileName =
|
|
3783
|
+
typeof helperPid === "number" && Number.isInteger(helperPid) && helperPid > 0
|
|
3784
|
+
? APP_RUNTIME_HELPER_OWNER_FILE.replace(
|
|
3785
|
+
/\.json$/i,
|
|
3786
|
+
`.${helperPid}.json`,
|
|
3787
|
+
)
|
|
3788
|
+
: APP_RUNTIME_HELPER_OWNER_FILE;
|
|
3789
|
+
return join(multiAuthDir, ownerFileName);
|
|
3790
|
+
}
|
|
3791
|
+
|
|
3707
3792
|
function writeOwnerOnlyJsonFileAtomicSync(targetPath, payload) {
|
|
3708
3793
|
const targetDir = dirname(targetPath);
|
|
3709
3794
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -3816,9 +3901,38 @@ function writeRuntimeRotationAppHelperStatus(payload, env = process.env) {
|
|
|
3816
3901
|
}
|
|
3817
3902
|
}
|
|
3818
3903
|
|
|
3904
|
+
function writeRuntimeRotationAppHelperOwner(
|
|
3905
|
+
identityToken,
|
|
3906
|
+
helperPid,
|
|
3907
|
+
env = process.env,
|
|
3908
|
+
) {
|
|
3909
|
+
if (
|
|
3910
|
+
typeof helperPid !== "number" ||
|
|
3911
|
+
!Number.isInteger(helperPid) ||
|
|
3912
|
+
helperPid < 1
|
|
3913
|
+
) {
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
try {
|
|
3917
|
+
writeOwnerOnlyJsonFileAtomicSync(
|
|
3918
|
+
resolveRuntimeRotationAppHelperOwnerPath(env, helperPid),
|
|
3919
|
+
{
|
|
3920
|
+
version: 1,
|
|
3921
|
+
kind: "codex-app-runtime-rotation-helper-owner",
|
|
3922
|
+
identityToken,
|
|
3923
|
+
launcherPid: process.pid,
|
|
3924
|
+
createdAt: Date.now(),
|
|
3925
|
+
},
|
|
3926
|
+
);
|
|
3927
|
+
} catch {
|
|
3928
|
+
// Best-effort metadata; an unavailable owner file must never stop the helper.
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3819
3932
|
function createRuntimeRotationAppHelperStatus({
|
|
3820
3933
|
proxyServer,
|
|
3821
3934
|
startedAt,
|
|
3935
|
+
identityToken,
|
|
3822
3936
|
idleTimeoutMs,
|
|
3823
3937
|
lastActivityAt,
|
|
3824
3938
|
state,
|
|
@@ -3838,6 +3952,8 @@ function createRuntimeRotationAppHelperStatus({
|
|
|
3838
3952
|
kind: "codex-app-runtime-rotation-helper",
|
|
3839
3953
|
state,
|
|
3840
3954
|
pid: process.pid,
|
|
3955
|
+
scriptPath: process.argv[1] ?? null,
|
|
3956
|
+
identityToken: identityToken || null,
|
|
3841
3957
|
startedAt,
|
|
3842
3958
|
updatedAt: Date.now(),
|
|
3843
3959
|
baseUrl: proxyServer?.baseUrl ?? null,
|
|
@@ -3855,7 +3971,7 @@ function createRuntimeRotationAppHelperStatus({
|
|
|
3855
3971
|
};
|
|
3856
3972
|
}
|
|
3857
3973
|
|
|
3858
|
-
async function runRuntimeRotationAppHelper() {
|
|
3974
|
+
async function runRuntimeRotationAppHelper(identityToken = "") {
|
|
3859
3975
|
let proxyServer = null;
|
|
3860
3976
|
let runtimeContext = null;
|
|
3861
3977
|
let appServerShimDir = null;
|
|
@@ -3872,6 +3988,7 @@ async function runRuntimeRotationAppHelper() {
|
|
|
3872
3988
|
createRuntimeRotationAppHelperStatus({
|
|
3873
3989
|
proxyServer,
|
|
3874
3990
|
startedAt,
|
|
3991
|
+
identityToken,
|
|
3875
3992
|
idleTimeoutMs,
|
|
3876
3993
|
lastActivityAt,
|
|
3877
3994
|
state,
|
|
@@ -4003,6 +4120,9 @@ function waitForRuntimeRotationAppHelperExit(helper, timeoutMs = 2_000) {
|
|
|
4003
4120
|
if (settled) return;
|
|
4004
4121
|
settled = true;
|
|
4005
4122
|
if (timer) clearTimeout(timer);
|
|
4123
|
+
// Drop the listener when the timeout wins, so a helper that outlives the
|
|
4124
|
+
// graceful window does not keep a live reference back into this process.
|
|
4125
|
+
helper.off?.("close", finish);
|
|
4006
4126
|
resolve();
|
|
4007
4127
|
};
|
|
4008
4128
|
timer = setTimeout(finish, timeoutMs);
|
|
@@ -4010,16 +4130,49 @@ function waitForRuntimeRotationAppHelperExit(helper, timeoutMs = 2_000) {
|
|
|
4010
4130
|
});
|
|
4011
4131
|
}
|
|
4012
4132
|
|
|
4013
|
-
function
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4133
|
+
function hasRuntimeRotationAppHelperExited(helper) {
|
|
4134
|
+
return helper.exitCode !== null || helper.signalCode !== null;
|
|
4135
|
+
}
|
|
4136
|
+
|
|
4137
|
+
// Releases every handle the helper still holds in this process. The helper is
|
|
4138
|
+
// spawned with piped stdio, so those pipes keep the parent event loop alive on
|
|
4139
|
+
// their own — destroying and unref-ing them is what actually lets `mcodex` return
|
|
4140
|
+
// to the shell when the child ignores or outlives SIGTERM (#647).
|
|
4141
|
+
function releaseRuntimeRotationAppHelperResources(helper) {
|
|
4142
|
+
helper.stdout?.destroy();
|
|
4143
|
+
helper.stderr?.destroy();
|
|
4017
4144
|
try {
|
|
4018
|
-
helper.
|
|
4145
|
+
helper.unref();
|
|
4019
4146
|
} catch {
|
|
4020
|
-
|
|
4147
|
+
// Best-effort only.
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
|
|
4151
|
+
async function stopRuntimeRotationAppHelper(helper) {
|
|
4152
|
+
if (!helper) {
|
|
4153
|
+
return;
|
|
4154
|
+
}
|
|
4155
|
+
if (hasRuntimeRotationAppHelperExited(helper)) {
|
|
4156
|
+
releaseRuntimeRotationAppHelperResources(helper);
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
if (!helper.killed) {
|
|
4160
|
+
try {
|
|
4161
|
+
helper.kill("SIGTERM");
|
|
4162
|
+
} catch {
|
|
4163
|
+
releaseRuntimeRotationAppHelperResources(helper);
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
await waitForRuntimeRotationAppHelperExit(helper);
|
|
4168
|
+
if (!hasRuntimeRotationAppHelperExited(helper)) {
|
|
4169
|
+
try {
|
|
4170
|
+
helper.kill("SIGKILL");
|
|
4171
|
+
} catch {
|
|
4172
|
+
// Best-effort force-stop once the graceful shutdown window has elapsed.
|
|
4173
|
+
}
|
|
4021
4174
|
}
|
|
4022
|
-
|
|
4175
|
+
releaseRuntimeRotationAppHelperResources(helper);
|
|
4023
4176
|
}
|
|
4024
4177
|
|
|
4025
4178
|
function startRuntimeRotationAppHelper(baseContext, options = {}) {
|
|
@@ -4030,25 +4183,32 @@ function startRuntimeRotationAppHelper(baseContext, options = {}) {
|
|
|
4030
4183
|
let stdoutBuffer = "";
|
|
4031
4184
|
let stderrBuffer = "";
|
|
4032
4185
|
let settled = false;
|
|
4186
|
+
const identityToken = randomBytes(24).toString("hex");
|
|
4187
|
+
const helperEnv = {
|
|
4188
|
+
...baseContext.env,
|
|
4189
|
+
CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
|
|
4190
|
+
realCodexHome,
|
|
4191
|
+
baseContext.env,
|
|
4192
|
+
),
|
|
4193
|
+
[APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
|
|
4194
|
+
[APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
|
|
4195
|
+
[APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV]:
|
|
4196
|
+
options.useCanonicalHome === true ? "1" : "0",
|
|
4197
|
+
};
|
|
4033
4198
|
const helper = spawn(
|
|
4034
4199
|
process.execPath,
|
|
4035
|
-
[
|
|
4200
|
+
[
|
|
4201
|
+
fileURLToPath(import.meta.url),
|
|
4202
|
+
INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG,
|
|
4203
|
+
identityToken,
|
|
4204
|
+
],
|
|
4036
4205
|
{
|
|
4037
|
-
env:
|
|
4038
|
-
...baseContext.env,
|
|
4039
|
-
CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
|
|
4040
|
-
realCodexHome,
|
|
4041
|
-
baseContext.env,
|
|
4042
|
-
),
|
|
4043
|
-
[APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
|
|
4044
|
-
[APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
|
|
4045
|
-
[APP_RUNTIME_HELPER_USE_CANONICAL_HOME_ENV]:
|
|
4046
|
-
options.useCanonicalHome === true ? "1" : "0",
|
|
4047
|
-
},
|
|
4206
|
+
env: helperEnv,
|
|
4048
4207
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4049
4208
|
detached: true,
|
|
4050
4209
|
},
|
|
4051
4210
|
);
|
|
4211
|
+
writeRuntimeRotationAppHelperOwner(identityToken, helper.pid, helperEnv);
|
|
4052
4212
|
let timeout = null;
|
|
4053
4213
|
const finish = (result) => {
|
|
4054
4214
|
if (settled) return;
|
|
@@ -4114,6 +4274,11 @@ async function createRuntimeRotationAppHelperContext(
|
|
|
4114
4274
|
options,
|
|
4115
4275
|
);
|
|
4116
4276
|
const helperEnv = message.env ?? {};
|
|
4277
|
+
const helperShimDir =
|
|
4278
|
+
typeof helperEnv.CODEX_CLI_PATH === "string" &&
|
|
4279
|
+
helperEnv.CODEX_CLI_PATH.length > 0
|
|
4280
|
+
? helperEnv.CODEX_CLI_PATH
|
|
4281
|
+
: null;
|
|
4117
4282
|
const helperArgs = Array.isArray(message.args)
|
|
4118
4283
|
? message.args.filter((arg) => typeof arg === "string")
|
|
4119
4284
|
: [];
|
|
@@ -4127,7 +4292,22 @@ async function createRuntimeRotationAppHelperContext(
|
|
|
4127
4292
|
helper.unref();
|
|
4128
4293
|
return;
|
|
4129
4294
|
}
|
|
4130
|
-
|
|
4295
|
+
try {
|
|
4296
|
+
await stopRuntimeRotationAppHelper(helper);
|
|
4297
|
+
} finally {
|
|
4298
|
+
// The helper normally removes its app-server shim before exiting. On
|
|
4299
|
+
// Windows, a descendant may still hold the copied codex.exe briefly
|
|
4300
|
+
// after the helper closes, so retry the parent-owned cleanup once the
|
|
4301
|
+
// helper shutdown path has completed as well.
|
|
4302
|
+
if (helperShimDir) {
|
|
4303
|
+
try {
|
|
4304
|
+
removeDirectoryWithRetry(helperShimDir);
|
|
4305
|
+
} catch {
|
|
4306
|
+
// Best-effort cleanup; stale helper directories are swept on the
|
|
4307
|
+
// next runtime app launch.
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4131
4311
|
};
|
|
4132
4312
|
|
|
4133
4313
|
return {
|
|
@@ -4171,7 +4351,10 @@ async function createRuntimeRotationProxyContextIfEnabled(
|
|
|
4171
4351
|
if (isCodexAppCommand(rawArgs)) {
|
|
4172
4352
|
return createRuntimeRotationAppHelperContext(baseContext, configTomlModule);
|
|
4173
4353
|
}
|
|
4174
|
-
if (
|
|
4354
|
+
if (
|
|
4355
|
+
isCodexInteractiveTuiCommand(rawArgs) ||
|
|
4356
|
+
isCodexInteractiveResumeCommand(rawArgs)
|
|
4357
|
+
) {
|
|
4175
4358
|
return createRuntimeRotationAppHelperContext(baseContext, configTomlModule, {
|
|
4176
4359
|
detachOnExit: true,
|
|
4177
4360
|
useCanonicalHome: true,
|
|
@@ -4375,6 +4558,17 @@ function isCodexInteractiveTuiCommand(rawArgs) {
|
|
|
4375
4558
|
return findForwardedCommand(rawArgs) === null;
|
|
4376
4559
|
}
|
|
4377
4560
|
|
|
4561
|
+
// `resume` and `fork` are interactive TUI entry points that happen to carry a
|
|
4562
|
+
// forwarded subcommand, so the bare-invocation predicate above misses them. They
|
|
4563
|
+
// must not take the shadow-home transport: the shadow mirror deliberately omits the
|
|
4564
|
+
// runtime SQLite state (`isRuntimeRotationShadowHomeOmittedEntry`), so the requested
|
|
4565
|
+
// thread is absent from the shadow session index and the resumed TUI hangs on a
|
|
4566
|
+
// blank screen (#647).
|
|
4567
|
+
function isCodexInteractiveResumeCommand(rawArgs) {
|
|
4568
|
+
const command = findForwardedCommand(rawArgs)?.command;
|
|
4569
|
+
return command === "resume" || command === "fork";
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4378
4572
|
function shouldUseRuntimeRoutingForForwardedArgs(rawArgs) {
|
|
4379
4573
|
if (!Array.isArray(rawArgs) || rawArgs.length === 0) {
|
|
4380
4574
|
return true;
|
|
@@ -4414,7 +4608,17 @@ function shouldUseRuntimeRoutingForForwardedArgs(rawArgs) {
|
|
|
4414
4608
|
);
|
|
4415
4609
|
}
|
|
4416
4610
|
|
|
4417
|
-
|
|
4611
|
+
// Request commands still print their help without making a single model
|
|
4612
|
+
// request, so the help form skips the transport entirely: no proxy, no shadow
|
|
4613
|
+
// home, no detached helper. This matters most for the interactive commands,
|
|
4614
|
+
// which detach their helper on a clean exit — help always exits clean, so
|
|
4615
|
+
// `resume --help` would otherwise strand a helper until its idle timeout. It is
|
|
4616
|
+
// keyed off the help flag rather than the command, so a real run still routes
|
|
4617
|
+
// through rotation, and it matches how `app-server` help is handled above (#647).
|
|
4618
|
+
if (
|
|
4619
|
+
requestCommands.has(command.command) &&
|
|
4620
|
+
hasHelpFlagAfterCommand(rawArgs, command.index)
|
|
4621
|
+
) {
|
|
4418
4622
|
return false;
|
|
4419
4623
|
}
|
|
4420
4624
|
if (requestCommands.has(command.command)) {
|
|
@@ -5106,7 +5310,7 @@ async function main() {
|
|
|
5106
5310
|
|
|
5107
5311
|
const rawArgs = process.argv.slice(2);
|
|
5108
5312
|
if (rawArgs[0] === INTERNAL_RUNTIME_ROTATION_APP_HELPER_ARG) {
|
|
5109
|
-
return runRuntimeRotationAppHelper();
|
|
5313
|
+
return runRuntimeRotationAppHelper(rawArgs[1] ?? "");
|
|
5110
5314
|
}
|
|
5111
5315
|
|
|
5112
5316
|
const normalizedArgs = normalizeAuthAlias(rawArgs);
|
|
@@ -6,6 +6,8 @@ import { join } from "node:path";
|
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
|
|
8
8
|
const PLUGIN_NAME = "codex-multi-auth";
|
|
9
|
+
const LEGACY_PLUGIN_NAME = "@ndycode/codex-multi-auth";
|
|
10
|
+
const PLUGIN_NAMES = [PLUGIN_NAME, LEGACY_PLUGIN_NAME];
|
|
9
11
|
|
|
10
12
|
const TRUE_VALUES = new Set(["1", "true", "yes"]);
|
|
11
13
|
const FALSE_VALUES = new Set(["0", "false", "no"]);
|
|
@@ -82,6 +84,7 @@ export function resolveInstallPaths(
|
|
|
82
84
|
configPath,
|
|
83
85
|
cacheDir,
|
|
84
86
|
cacheNodeModules: join(cacheDir, "node_modules", PLUGIN_NAME),
|
|
87
|
+
cacheLegacyNodeModules: join(cacheDir, "node_modules", "@ndycode", "codex-multi-auth"),
|
|
85
88
|
cacheBunLock: join(cacheDir, "bun.lock"),
|
|
86
89
|
cachePackageJson: join(cacheDir, "package.json"),
|
|
87
90
|
};
|
|
@@ -92,7 +95,9 @@ export function removePluginFromList(list) {
|
|
|
92
95
|
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
93
96
|
return entries.filter((entry) => {
|
|
94
97
|
if (typeof entry !== "string") return true;
|
|
95
|
-
return
|
|
98
|
+
return !PLUGIN_NAMES.some(
|
|
99
|
+
(pluginName) => entry === pluginName || entry.startsWith(`${pluginName}@`),
|
|
100
|
+
);
|
|
96
101
|
});
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -101,7 +106,9 @@ export function normalizePluginList(list) {
|
|
|
101
106
|
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
102
107
|
const filtered = entries.filter((entry) => {
|
|
103
108
|
if (typeof entry !== "string") return true;
|
|
104
|
-
return
|
|
109
|
+
return !PLUGIN_NAMES.some(
|
|
110
|
+
(pluginName) => entry === pluginName || entry.startsWith(`${pluginName}@`),
|
|
111
|
+
);
|
|
105
112
|
});
|
|
106
113
|
const deduped = [];
|
|
107
114
|
const seen = new Set();
|
package/scripts/postinstall.js
CHANGED
|
@@ -33,6 +33,8 @@ const CI_ENV_KEYS = [
|
|
|
33
33
|
export const INSTALL_NOTICE = [
|
|
34
34
|
"codex-multi-auth installed. Run `codex-multi-auth --help` to get started.",
|
|
35
35
|
"App integration (Codex app bind + launcher shortcuts) completes automatically on first run.",
|
|
36
|
+
"To remove it later, run `codex-multi-auth uninstall`, then `npm uninstall -g codex-multi-auth`.",
|
|
37
|
+
"`@ndycode/codex-multi-auth` is the legacy scoped package name; it does not remove this package.",
|
|
36
38
|
].join("\n");
|
|
37
39
|
|
|
38
40
|
/**
|
package/scripts/preuninstall.js
CHANGED
|
@@ -189,6 +189,9 @@ export async function runPreuninstallCleanup(deps = {}) {
|
|
|
189
189
|
);
|
|
190
190
|
if (dryRun) {
|
|
191
191
|
log(`[dry-run] Would remove ${paths.cacheNodeModules}`);
|
|
192
|
+
if (paths.cacheLegacyNodeModules) {
|
|
193
|
+
log(`[dry-run] Would remove ${paths.cacheLegacyNodeModules}`);
|
|
194
|
+
}
|
|
192
195
|
if (bunLockSafe) {
|
|
193
196
|
log(`[dry-run] Would remove ${paths.cacheBunLock}`);
|
|
194
197
|
} else {
|
|
@@ -201,6 +204,14 @@ export async function runPreuninstallCleanup(deps = {}) {
|
|
|
201
204
|
await withFileOperationRetry(() =>
|
|
202
205
|
rm(paths.cacheNodeModules, { recursive: true, force: true }),
|
|
203
206
|
);
|
|
207
|
+
if (paths.cacheLegacyNodeModules) {
|
|
208
|
+
await withFileOperationRetry(() =>
|
|
209
|
+
rm(paths.cacheLegacyNodeModules, {
|
|
210
|
+
recursive: true,
|
|
211
|
+
force: true,
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
204
215
|
if (bunLockSafe) {
|
|
205
216
|
await withFileOperationRetry(() =>
|
|
206
217
|
rm(paths.cacheBunLock, { force: true }),
|