codex-multi-auth 2.8.4 → 2.8.5
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 +4 -4
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +8 -3
- package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/rotation.js +43 -20
- package/dist/lib/runtime/app-bind.d.ts +7 -0
- package/dist/lib/runtime/app-bind.d.ts.map +1 -1
- package/dist/lib/runtime/app-bind.js +174 -57
- package/dist/lib/runtime/app-helper-selection.d.ts +69 -0
- package/dist/lib/runtime/app-helper-selection.d.ts.map +1 -0
- package/dist/lib/runtime/app-helper-selection.js +100 -0
- package/dist/lib/runtime/rotation-server-types.d.ts +7 -0
- package/dist/lib/runtime/rotation-server-types.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-current-account.d.ts +3 -2
- package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-current-account.js +34 -26
- package/dist/lib/runtime-constants.d.ts +30 -0
- package/dist/lib/runtime-constants.d.ts.map +1 -1
- package/dist/lib/runtime-constants.js +53 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +4 -0
- package/package.json +1 -2
- package/scripts/codex.js +601 -49
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
/**
|
|
3
|
+
* How stale a `running` record may be before it stops counting as live.
|
|
4
|
+
*
|
|
5
|
+
* A running helper republishes its status on every tick, and the publish path
|
|
6
|
+
* heartbeats at least once per `APP_RUNTIME_HELPER_STATUS_HEARTBEAT_MS` (60s)
|
|
7
|
+
* even when nothing in the payload changed. Ten heartbeats of silence is not a
|
|
8
|
+
* helper that is merely quiet — it is a record whose writer is gone.
|
|
9
|
+
*
|
|
10
|
+
* This is the identity check these readers were missing. `kill(pid, 0)` answers
|
|
11
|
+
* "does *a* process hold this integer", so a stale record — classically the
|
|
12
|
+
* legacy shared `runtime-rotation-app-helper.json` left behind by a SIGKILLed
|
|
13
|
+
* pre-upgrade helper — passes liveness as soon as an unrelated process is
|
|
14
|
+
* handed its PID, and can then win selection outright. Freshness is the half of
|
|
15
|
+
* identity available to a synchronous reader: the wrapper verifies identity by
|
|
16
|
+
* probing kernel start times, but that costs a `ps` per candidate, and these
|
|
17
|
+
* two call sites are read-only status paths reached from the interactive menu
|
|
18
|
+
* as well as the CLI. Whoever holds the PID now, they are not the process that
|
|
19
|
+
* last wrote this file.
|
|
20
|
+
*/
|
|
21
|
+
export const RUNTIME_HELPER_STATUS_STALE_MS = 10 * 60 * 1000;
|
|
22
|
+
/** Tolerance for clock skew between the writing helper and the reader. */
|
|
23
|
+
export const RUNTIME_HELPER_CLOCK_TOLERANCE_MS = 60 * 1000;
|
|
24
|
+
/**
|
|
25
|
+
* A PID is a positive integer or it is nothing. Both readers used to accept any
|
|
26
|
+
* finite number, so a corrupt or hand-edited record carrying `-1234` reached
|
|
27
|
+
* `process.kill(-1234, 0)` — which on POSIX probes process *group* 1234 and
|
|
28
|
+
* succeeds on any busy machine, reporting a helper that does not exist as live.
|
|
29
|
+
* Fractional values were accepted the same way.
|
|
30
|
+
*/
|
|
31
|
+
export function readRuntimeHelperPid(value) {
|
|
32
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
33
|
+
? value
|
|
34
|
+
: null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Best-effort liveness probe. `EPERM` means a process exists that this user may
|
|
38
|
+
* not signal, so it counts as alive; every other errno — including the `EINVAL`
|
|
39
|
+
* some platforms raise for a PID above their ceiling — counts as dead.
|
|
40
|
+
*/
|
|
41
|
+
export function isRuntimeHelperProcessAlive(pid) {
|
|
42
|
+
const probePid = readRuntimeHelperPid(pid);
|
|
43
|
+
if (probePid === null)
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
process.kill(probePid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : null;
|
|
51
|
+
return code === "EPERM";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The single definition of "this helper is currently serving": a running state,
|
|
56
|
+
* a live PID, and a record recent enough to have been written by that PID's
|
|
57
|
+
* current occupant.
|
|
58
|
+
*/
|
|
59
|
+
export function isLiveRuntimeHelper(status, now = Date.now()) {
|
|
60
|
+
if (status.state !== "running")
|
|
61
|
+
return false;
|
|
62
|
+
if (!isRuntimeHelperProcessAlive(status.pid))
|
|
63
|
+
return false;
|
|
64
|
+
// A record that claims to have started after the current instant was not
|
|
65
|
+
// written by a process that is running now.
|
|
66
|
+
if (status.startedAt !== null &&
|
|
67
|
+
status.startedAt > now + RUNTIME_HELPER_CLOCK_TOLERANCE_MS) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
// A record with no `updatedAt` at all predates the heartbeat contract and
|
|
71
|
+
// cannot be judged on freshness; it falls back to bare liveness rather than
|
|
72
|
+
// being discarded, which is no worse than the behaviour it replaces.
|
|
73
|
+
if (status.updatedAt === null)
|
|
74
|
+
return true;
|
|
75
|
+
return now - status.updatedAt <= RUNTIME_HELPER_STATUS_STALE_MS;
|
|
76
|
+
}
|
|
77
|
+
/** Every helper that passes {@link isLiveRuntimeHelper}, input order preserved. */
|
|
78
|
+
export function liveRuntimeHelpers(statuses, now = Date.now()) {
|
|
79
|
+
return statuses.filter((status) => isLiveRuntimeHelper(status, now));
|
|
80
|
+
}
|
|
81
|
+
function byRecency(left, right) {
|
|
82
|
+
return (right.updatedAt ?? 0) - (left.updatedAt ?? 0);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Prefer a live helper, and among several the most recently updated. Absent any
|
|
86
|
+
* live helper, fall back to the freshest record so the previous "reports the
|
|
87
|
+
* last helper's final state" behaviour survives.
|
|
88
|
+
*
|
|
89
|
+
* Callers that also need the live count must pass the same `statuses` array and
|
|
90
|
+
* the same `now` to {@link liveRuntimeHelpers}, so the selected helper and the
|
|
91
|
+
* count describe one instant rather than two.
|
|
92
|
+
*/
|
|
93
|
+
export function selectRuntimeHelperStatus(statuses, now = Date.now()) {
|
|
94
|
+
if (statuses.length === 0)
|
|
95
|
+
return null;
|
|
96
|
+
const live = liveRuntimeHelpers(statuses, now).sort(byRecency);
|
|
97
|
+
if (live.length > 0)
|
|
98
|
+
return live[0] ?? null;
|
|
99
|
+
return [...statuses].sort(byRecency)[0] ?? null;
|
|
100
|
+
}
|
|
@@ -6,6 +6,13 @@ export interface RuntimeRotationProxyServer {
|
|
|
6
6
|
baseUrl: string;
|
|
7
7
|
close: () => Promise<void>;
|
|
8
8
|
getStatus: () => RuntimeRotationProxyStatus;
|
|
9
|
+
/**
|
|
10
|
+
* Number of client sockets currently open against the proxy. The detached
|
|
11
|
+
* app helper uses it to tell a handed-off consumer from a stranded process;
|
|
12
|
+
* optional so a proxy shape without it degrades to activity-only accounting
|
|
13
|
+
* rather than failing to start.
|
|
14
|
+
*/
|
|
15
|
+
getOpenConnectionCount?: () => number;
|
|
9
16
|
}
|
|
10
17
|
export interface RuntimeRotationProxyStatus {
|
|
11
18
|
startedAt: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"rotation-server-types.d.ts","sourceRoot":"","sources":["../../../lib/runtime/rotation-server-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,0BAA0B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,0BAA0B,CAAC;IAC5C;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,MAAM,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,0BAA0B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,2BAA2B;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,8BAA8B,CAAC,EAAE,MAAM,CAAC;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,gBAAgB,GACzB,YAAY,GACZ,cAAc,GACd,eAAe,GACf,cAAc,GACd,QAAQ,GACR,aAAa,GACb,YAAY,CAAC;AAChB,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -35,6 +35,7 @@ export interface AppRuntimeHelperAccountStatus {
|
|
|
35
35
|
kind: string | null;
|
|
36
36
|
state: string | null;
|
|
37
37
|
pid: number | null;
|
|
38
|
+
startedAt: number | null;
|
|
38
39
|
lastAccountIndex: number | null;
|
|
39
40
|
lastAccountLabel: string | null;
|
|
40
41
|
lastAccountEmail: string | null;
|
|
@@ -42,8 +43,8 @@ export interface AppRuntimeHelperAccountStatus {
|
|
|
42
43
|
lastAccountUpdatedAt: number | null;
|
|
43
44
|
updatedAt: number | null;
|
|
44
45
|
}
|
|
45
|
-
export declare function readAppRuntimeHelperStatus(): AppRuntimeHelperAccountStatus | null;
|
|
46
|
-
export declare function appRuntimeHelperStatusToSignal(status: AppRuntimeHelperAccountStatus | null): RuntimeAccountSignal | null;
|
|
46
|
+
export declare function readAppRuntimeHelperStatus(now?: number): AppRuntimeHelperAccountStatus | null;
|
|
47
|
+
export declare function appRuntimeHelperStatusToSignal(status: AppRuntimeHelperAccountStatus | null, now?: number): RuntimeAccountSignal | null;
|
|
47
48
|
export declare function readAppRuntimeHelperAccountSignal(): RuntimeAccountSignal | null;
|
|
48
49
|
export declare function resolveRuntimeCurrentAccount(storage: AccountStorageV3, sources: RuntimeCurrentAccountSources, options?: RuntimeCurrentAccountOptions): RuntimeCurrentAccountSelection | null;
|
|
49
50
|
export declare function resolveAccountCurrentMarkers(index: number, storedCurrentIndex: number, runtimeCurrent: RuntimeCurrentAccountSelection | null): AccountCurrentMarker[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-current-account.d.ts","sourceRoot":"","sources":["../../../lib/runtime/runtime-current-account.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime-current-account.d.ts","sourceRoot":"","sources":["../../../lib/runtime/runtime-current-account.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { APP_RUNTIME_HELPER_STATUS_FILE } from "../runtime-constants.js";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { isLiveRuntimeHelper, readRuntimeHelperPid, selectRuntimeHelperStatus, } from "./app-helper-selection.js";
|
|
3
|
+
import { listRuntimeHelperStatusPaths } from "../runtime-constants.js";
|
|
5
4
|
import { getCodexMultiAuthDir } from "../runtime-paths.js";
|
|
6
5
|
import { isRecord } from "../utils.js";
|
|
7
6
|
const RUNTIME_CURRENT_ACCOUNT_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
@@ -46,22 +45,7 @@ function readOptionalString(record, key) {
|
|
|
46
45
|
? value.trim()
|
|
47
46
|
: null;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
// failures for live processes and cannot protect against rare PID reuse.
|
|
51
|
-
function isProcessAlive(pid) {
|
|
52
|
-
if (!pid)
|
|
53
|
-
return false;
|
|
54
|
-
try {
|
|
55
|
-
process.kill(pid, 0);
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
const code = error && typeof error === "object" && "code" in error ? error.code : null;
|
|
60
|
-
return code === "EPERM";
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
export function readAppRuntimeHelperStatus() {
|
|
64
|
-
const statusPath = join(getCodexMultiAuthDir(), APP_RUNTIME_HELPER_STATUS_FILE);
|
|
48
|
+
function readAppRuntimeHelperStatusFile(statusPath) {
|
|
65
49
|
if (!existsSync(statusPath))
|
|
66
50
|
return null;
|
|
67
51
|
try {
|
|
@@ -79,7 +63,8 @@ export function readAppRuntimeHelperStatus() {
|
|
|
79
63
|
return {
|
|
80
64
|
kind: readOptionalString(parsed, "kind"),
|
|
81
65
|
state: readOptionalString(parsed, "state"),
|
|
82
|
-
pid:
|
|
66
|
+
pid: readRuntimeHelperPid(parsed.pid),
|
|
67
|
+
startedAt: readOptionalNumber(parsed, "startedAt"),
|
|
83
68
|
lastAccountIndex: readOptionalNumber(parsed, "lastAccountIndex"),
|
|
84
69
|
lastAccountLabel: readOptionalString(parsed, "lastAccountLabel"),
|
|
85
70
|
lastAccountEmail: readOptionalString(parsed, "lastAccountEmail"),
|
|
@@ -92,14 +77,34 @@ export function readAppRuntimeHelperStatus() {
|
|
|
92
77
|
return null;
|
|
93
78
|
}
|
|
94
79
|
}
|
|
95
|
-
|
|
80
|
+
// Helpers publish per-PID status files (`runtime-rotation-app-helper.<pid>.json`)
|
|
81
|
+
// so N concurrent helpers stop overwriting one shared path; path discovery is
|
|
82
|
+
// shared with every other reader via listRuntimeHelperStatusPaths.
|
|
83
|
+
function listAppRuntimeHelperStatusPaths(multiAuthDir) {
|
|
84
|
+
let entries = [];
|
|
85
|
+
try {
|
|
86
|
+
entries = readdirSync(multiAuthDir);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
entries = [];
|
|
90
|
+
}
|
|
91
|
+
return listRuntimeHelperStatusPaths(multiAuthDir, entries);
|
|
92
|
+
}
|
|
93
|
+
export function readAppRuntimeHelperStatus(now = Date.now()) {
|
|
94
|
+
const statuses = listAppRuntimeHelperStatusPaths(getCodexMultiAuthDir())
|
|
95
|
+
.map(readAppRuntimeHelperStatusFile)
|
|
96
|
+
.filter((status) => status !== null && status.kind === APP_RUNTIME_HELPER_KIND);
|
|
97
|
+
// Selection is shared with `rotation status` so the helper named on the
|
|
98
|
+
// status line and the helper whose account is marked `current` can never be
|
|
99
|
+
// two different helpers (#667).
|
|
100
|
+
return selectRuntimeHelperStatus(statuses, now);
|
|
101
|
+
}
|
|
102
|
+
export function appRuntimeHelperStatusToSignal(status, now = Date.now()) {
|
|
96
103
|
if (!status)
|
|
97
104
|
return null;
|
|
98
105
|
if (status.kind !== APP_RUNTIME_HELPER_KIND)
|
|
99
106
|
return null;
|
|
100
|
-
if (status
|
|
101
|
-
return null;
|
|
102
|
-
if (!isProcessAlive(status.pid))
|
|
107
|
+
if (!isLiveRuntimeHelper(status, now))
|
|
103
108
|
return null;
|
|
104
109
|
return {
|
|
105
110
|
source: "app-helper",
|
|
@@ -112,7 +117,10 @@ export function appRuntimeHelperStatusToSignal(status) {
|
|
|
112
117
|
};
|
|
113
118
|
}
|
|
114
119
|
export function readAppRuntimeHelperAccountSignal() {
|
|
115
|
-
|
|
120
|
+
// One `now` for both the selection and the liveness verdict, so a helper
|
|
121
|
+
// cannot be selected against one instant and judged against another.
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
return appRuntimeHelperStatusToSignal(readAppRuntimeHelperStatus(now), now);
|
|
116
124
|
}
|
|
117
125
|
function runtimeSnapshotToSignal(snapshot) {
|
|
118
126
|
if (!snapshot)
|
|
@@ -2,4 +2,34 @@ export declare const RUNTIME_ROTATION_PROXY_PROVIDER_ID: "codex-multi-auth-runti
|
|
|
2
2
|
export declare const APP_RUNTIME_HELPER_STATUS_FILE: "runtime-rotation-app-helper.json";
|
|
3
3
|
/** Immutable launcher metadata used to verify ownership before stopping a helper. */
|
|
4
4
|
export declare const APP_RUNTIME_HELPER_OWNER_FILE: "runtime-rotation-app-helper-owner.json";
|
|
5
|
+
/**
|
|
6
|
+
* The one place the per-PID filename shape is written down:
|
|
7
|
+
* `<base-without-.json>.<pid>.json`, matched case-insensitively with the PID
|
|
8
|
+
* captured. Status files and owner files share that shape, and so does the
|
|
9
|
+
* launcher-side sweep in `scripts/codex.js` — that file re-derives the pattern
|
|
10
|
+
* from the same two constants because it has to keep working before `dist/` is
|
|
11
|
+
* built, so a change to the shape here is a change there too.
|
|
12
|
+
*/
|
|
13
|
+
export declare function runtimeHelperPerPidPattern(baseName: string): RegExp;
|
|
14
|
+
/**
|
|
15
|
+
* Every path a helper status record can live at: the per-PID files
|
|
16
|
+
* (`runtime-rotation-app-helper.<pid>.json`, one per helper) plus the
|
|
17
|
+
* un-suffixed legacy shared path from before the per-PID change, which is
|
|
18
|
+
* still read so a pre-upgrade helper stays visible. The filename contract
|
|
19
|
+
* lives here, next to the constant it derives from, so every reader agrees
|
|
20
|
+
* on it; callers supply the directory listing so this stays pure and their
|
|
21
|
+
* own error handling for the `readdir` stays theirs.
|
|
22
|
+
*/
|
|
23
|
+
export declare function listRuntimeHelperStatusPaths(baseDir: string, entries: readonly string[]): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Owner files paired with the helper PID they belong to. Unbind used to
|
|
26
|
+
* enumerate status files only, so an owner file whose status record had
|
|
27
|
+
* already been removed could never be rediscovered and accumulated under the
|
|
28
|
+
* multi-auth root forever (#666). Enumerating both is what lets a cleanup pass
|
|
29
|
+
* reclaim an owner file that outlived its status record.
|
|
30
|
+
*/
|
|
31
|
+
export declare function listRuntimeHelperOwnerPaths(baseDir: string, entries: readonly string[]): {
|
|
32
|
+
path: string;
|
|
33
|
+
pid: number;
|
|
34
|
+
}[];
|
|
5
35
|
//# sourceMappingURL=runtime-constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-constants.d.ts","sourceRoot":"","sources":["../../lib/runtime-constants.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime-constants.d.ts","sourceRoot":"","sources":["../../lib/runtime-constants.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kCAAkC,EAC9C,gCAAyC,CAAC;AAE3C,eAAO,MAAM,8BAA8B,EAC1C,kCAA2C,CAAC;AAE7C,qFAAqF;AACrF,eAAO,MAAM,6BAA6B,EACzC,wCAAiD,CAAC;AAEnD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC3C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB,MAAM,EAAE,CAUV;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAC1C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,EAAE,GACxB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,CAcjC"}
|
|
@@ -1,4 +1,57 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
export const RUNTIME_ROTATION_PROXY_PROVIDER_ID = "codex-multi-auth-runtime-proxy";
|
|
2
3
|
export const APP_RUNTIME_HELPER_STATUS_FILE = "runtime-rotation-app-helper.json";
|
|
3
4
|
/** Immutable launcher metadata used to verify ownership before stopping a helper. */
|
|
4
5
|
export const APP_RUNTIME_HELPER_OWNER_FILE = "runtime-rotation-app-helper-owner.json";
|
|
6
|
+
/**
|
|
7
|
+
* The one place the per-PID filename shape is written down:
|
|
8
|
+
* `<base-without-.json>.<pid>.json`, matched case-insensitively with the PID
|
|
9
|
+
* captured. Status files and owner files share that shape, and so does the
|
|
10
|
+
* launcher-side sweep in `scripts/codex.js` — that file re-derives the pattern
|
|
11
|
+
* from the same two constants because it has to keep working before `dist/` is
|
|
12
|
+
* built, so a change to the shape here is a change there too.
|
|
13
|
+
*/
|
|
14
|
+
export function runtimeHelperPerPidPattern(baseName) {
|
|
15
|
+
const prefix = baseName.replace(/\.json$/i, "");
|
|
16
|
+
return new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\.(\\d+)\\.json$`, "i");
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Every path a helper status record can live at: the per-PID files
|
|
20
|
+
* (`runtime-rotation-app-helper.<pid>.json`, one per helper) plus the
|
|
21
|
+
* un-suffixed legacy shared path from before the per-PID change, which is
|
|
22
|
+
* still read so a pre-upgrade helper stays visible. The filename contract
|
|
23
|
+
* lives here, next to the constant it derives from, so every reader agrees
|
|
24
|
+
* on it; callers supply the directory listing so this stays pure and their
|
|
25
|
+
* own error handling for the `readdir` stays theirs.
|
|
26
|
+
*/
|
|
27
|
+
export function listRuntimeHelperStatusPaths(baseDir, entries) {
|
|
28
|
+
const perPidPattern = runtimeHelperPerPidPattern(APP_RUNTIME_HELPER_STATUS_FILE);
|
|
29
|
+
return [
|
|
30
|
+
...entries
|
|
31
|
+
.filter((name) => perPidPattern.test(name))
|
|
32
|
+
.map((name) => join(baseDir, name)),
|
|
33
|
+
join(baseDir, APP_RUNTIME_HELPER_STATUS_FILE),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Owner files paired with the helper PID they belong to. Unbind used to
|
|
38
|
+
* enumerate status files only, so an owner file whose status record had
|
|
39
|
+
* already been removed could never be rediscovered and accumulated under the
|
|
40
|
+
* multi-auth root forever (#666). Enumerating both is what lets a cleanup pass
|
|
41
|
+
* reclaim an owner file that outlived its status record.
|
|
42
|
+
*/
|
|
43
|
+
export function listRuntimeHelperOwnerPaths(baseDir, entries) {
|
|
44
|
+
const perPidPattern = runtimeHelperPerPidPattern(APP_RUNTIME_HELPER_OWNER_FILE);
|
|
45
|
+
const owners = [];
|
|
46
|
+
for (const name of entries) {
|
|
47
|
+
const match = perPidPattern.exec(name);
|
|
48
|
+
const captured = match?.[1];
|
|
49
|
+
if (captured === undefined)
|
|
50
|
+
continue;
|
|
51
|
+
const pid = Number.parseInt(captured, 10);
|
|
52
|
+
if (!Number.isInteger(pid) || pid < 1)
|
|
53
|
+
continue;
|
|
54
|
+
owners.push({ path: join(baseDir, name), pid });
|
|
55
|
+
}
|
|
56
|
+
return owners;
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAGA,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AA8BvB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA2CtE,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAiDtE,kFAAkF;AAClF,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GACnB,MAAM,CAmBR;AA8bD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,
|
|
1
|
+
{"version":3,"file":"runtime-rotation-proxy.d.ts","sourceRoot":"","sources":["../../lib/runtime-rotation-proxy.ts"],"names":[],"mappings":"AAGA,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AA8BvB,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA2CtE,OAAO,KAAK,EAKX,2BAA2B,EAC3B,0BAA0B,EAE1B,MAAM,oCAAoC,CAAC;AAe5C,YAAY,EACX,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,GAC1B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACN,+BAA+B,EAC/B,0BAA0B,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,YAAY,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AACxE,OAAO,EACN,+BAA+B,EAC/B,8BAA8B,EAC9B,uBAAuB,EACvB,uBAAuB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAiDtE,kFAAkF;AAClF,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,OAAO,GAAG,cAAc,CAAC,GAAG;IACvE,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,EACD,MAAM,EAAE,WAAW,EACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GACnB,MAAM,CAmBR;AA8bD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GACvC,MAAM,GAAG,IAAI,CAUf;AAED,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,2BAA2B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA4KrC"}
|
|
@@ -600,6 +600,10 @@ export async function startRuntimeRotationProxy(options) {
|
|
|
600
600
|
await closeServer(server, sockets);
|
|
601
601
|
await state.activeAccountManager.flushPendingSave();
|
|
602
602
|
},
|
|
603
|
+
// Live client connections, which the app helper reads as evidence that a
|
|
604
|
+
// detached consumer is still attached: a helper whose launcher is gone
|
|
605
|
+
// and whose socket set is empty has nobody left to serve.
|
|
606
|
+
getOpenConnectionCount: () => sockets.size,
|
|
603
607
|
getStatus: () => ({
|
|
604
608
|
...state.status,
|
|
605
609
|
// Redact any email/token material that leaked into a raw upstream or
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-multi-auth",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.5",
|
|
4
4
|
"description": "Codex CLI multi-account OAuth manager with account switching, health checks, runtime rotation, diagnostics, and recovery tools for @openai/codex",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -174,7 +174,6 @@
|
|
|
174
174
|
"dependencies": {
|
|
175
175
|
"@codex-ai/plugin": "file:vendor/codex-ai-plugin",
|
|
176
176
|
"@codex-ai/sdk": "file:vendor/codex-ai-sdk",
|
|
177
|
-
"@openauthjs/openauth": "^0.4.3",
|
|
178
177
|
"hono": "4.12.33",
|
|
179
178
|
"undici": "6.28.0",
|
|
180
179
|
"zod": "4.4.3"
|