@makaio/client-codex 1.0.0-dev-1781368534585 → 1.0.0-dev-1783973359164
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -3
- package/descriptor.json +1 -1
- package/dist/{codex-client-session-service-C2F6rQDS.mjs → codex-client-session-service-B-E98xgY.mjs} +1066 -63
- package/dist/definition.d.ts +31 -1
- package/dist/index.mjs +2 -2
- package/dist/runtime/codex-client-session-service.d.ts +19 -5
- package/dist/runtime/hook-normalizer.d.ts +5 -1
- package/dist/runtime/namespace.d.ts +16 -0
- package/dist/runtime/native-auth-errors.d.ts +19 -0
- package/dist/runtime/native-auth-source-lock.d.ts +98 -0
- package/dist/runtime/native-auth-store.d.ts +166 -0
- package/dist/runtime/native-keyring-credential-store.d.ts +29 -0
- package/dist/runtime/package.d.ts +2 -0
- package/dist/runtime/package.mjs +3 -3
- package/dist/runtime/session-config-handler.d.ts +45 -33
- package/dist/server.mjs +1 -1
- package/dist/{src-CUFX-QsP.mjs → src-CXxq1vvn.mjs} +1 -1
- package/package.json +13 -33
- package/src/index.ts +0 -39
package/dist/{codex-client-session-service-C2F6rQDS.mjs → codex-client-session-service-B-E98xgY.mjs}
RENAMED
|
@@ -7,9 +7,12 @@ import * as fs$1 from "node:fs/promises";
|
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import * as path$1 from "node:path";
|
|
9
9
|
import path from "node:path";
|
|
10
|
+
import * as os$1 from "node:os";
|
|
10
11
|
import os from "node:os";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
12
|
-
import { ClientConfigPrimeSchema, SessionConfigSetupRequestSchema, SessionConfigSetupResponseSchema } from "@makaio/framework/contracts/client";
|
|
12
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
13
|
+
import { ClientConfigPrimeSchema, SessionConfigSetupRequestSchema, SessionConfigSetupResponseSchema, SessionConfigTeardownRequestSchema, SessionConfigTeardownResponseSchema } from "@makaio/framework/contracts/client";
|
|
14
|
+
import { parse } from "smol-toml";
|
|
15
|
+
import { lock } from "proper-lockfile";
|
|
13
16
|
|
|
14
17
|
//#region src/definition.ts
|
|
15
18
|
/**
|
|
@@ -35,12 +38,12 @@ const clientDefinition = createClientDefinition({
|
|
|
35
38
|
description: "OpenAI Codex CLI — an agentic coding assistant",
|
|
36
39
|
binary: {
|
|
37
40
|
name: "codex",
|
|
38
|
-
supportedVersions: "0.
|
|
41
|
+
supportedVersions: "0.144.1"
|
|
39
42
|
},
|
|
40
43
|
managedInstall: {
|
|
41
44
|
type: "npm",
|
|
42
45
|
package: "@openai/codex",
|
|
43
|
-
version: "0.
|
|
46
|
+
version: "0.144.1"
|
|
44
47
|
},
|
|
45
48
|
versionCommand: {
|
|
46
49
|
executable: {
|
|
@@ -67,7 +70,29 @@ const clientDefinition = createClientDefinition({
|
|
|
67
70
|
capabilities: (codexCapabilityMap.patch ?? []).map((tag) => ({ tag }))
|
|
68
71
|
}],
|
|
69
72
|
defaultApprovalPolicy: "full-access",
|
|
70
|
-
|
|
73
|
+
authMethods: [{
|
|
74
|
+
id: "native",
|
|
75
|
+
mode: "inferred",
|
|
76
|
+
label: "Native account"
|
|
77
|
+
}, {
|
|
78
|
+
id: "access-token",
|
|
79
|
+
mode: "explicit",
|
|
80
|
+
label: "Access token",
|
|
81
|
+
fields: [{
|
|
82
|
+
id: "accessToken",
|
|
83
|
+
label: "Access token",
|
|
84
|
+
required: true,
|
|
85
|
+
secret: true,
|
|
86
|
+
sourceHints: [{
|
|
87
|
+
kind: "environment",
|
|
88
|
+
variable: "CODEX_ACCESS_TOKEN"
|
|
89
|
+
}]
|
|
90
|
+
}]
|
|
91
|
+
}],
|
|
92
|
+
defaultAuth: {
|
|
93
|
+
providerDefinitionId: "openai-codex",
|
|
94
|
+
methodId: "native"
|
|
95
|
+
},
|
|
71
96
|
runtimeCapabilities: {
|
|
72
97
|
supportsHooks: true,
|
|
73
98
|
supportsStatusline: false,
|
|
@@ -994,10 +1019,14 @@ function extractPrompt(payload) {
|
|
|
994
1019
|
* Returns `null` for unknown or not-yet-modeled event names so the caller
|
|
995
1020
|
* skips global emission and keeps the event raw-only in `client:codex.*`.
|
|
996
1021
|
* @param raw - Raw hook payload delivered on `client:codex.hook.received`
|
|
1022
|
+
* @param machineId - Stable runtime identity of the observing machine,
|
|
1023
|
+
* caller-supplied by the owning client runtime. Stamped onto
|
|
1024
|
+
* `client.session.started` so downstream storage receives the owning
|
|
1025
|
+
* machine's identity without deriving it from the writer process.
|
|
997
1026
|
* @returns Normalized event with subject and typed payload, or `null` when
|
|
998
1027
|
* the event name is unknown
|
|
999
1028
|
*/
|
|
1000
|
-
function normalizeCodexHook(raw) {
|
|
1029
|
+
function normalizeCodexHook(raw, machineId) {
|
|
1001
1030
|
const subject = CODEX_EVENT_MAP.get(raw.eventName);
|
|
1002
1031
|
if (subject === void 0) return null;
|
|
1003
1032
|
const base = {
|
|
@@ -1010,7 +1039,10 @@ function normalizeCodexHook(raw) {
|
|
|
1010
1039
|
switch (subject) {
|
|
1011
1040
|
case ClientSubjects.session.started: return {
|
|
1012
1041
|
subject,
|
|
1013
|
-
payload: {
|
|
1042
|
+
payload: {
|
|
1043
|
+
...base,
|
|
1044
|
+
...machineId !== void 0 && { machineId }
|
|
1045
|
+
}
|
|
1014
1046
|
};
|
|
1015
1047
|
case ClientSubjects.session.userPrompt.submitted: return {
|
|
1016
1048
|
subject,
|
|
@@ -1063,6 +1095,7 @@ function normalizeCodexHook(raw) {
|
|
|
1063
1095
|
* - `wiring.apply` — install wiring entries into the target scope
|
|
1064
1096
|
* - `wiring.remove` — uninstall wiring entries from the target scope
|
|
1065
1097
|
* - `sessionConfig.setup` — seed an isolated session config directory
|
|
1098
|
+
* - `sessionConfig.destroy` — reconcile and clear isolated native auth
|
|
1066
1099
|
*
|
|
1067
1100
|
* **Subject conventions:**
|
|
1068
1101
|
* - Raw Codex-native events flow in the `client:codex.*` namespace only.
|
|
@@ -1077,6 +1110,10 @@ const { subjects, namespaceDomain } = createClientNamespace("codex", {
|
|
|
1077
1110
|
"sessionConfig.setup": {
|
|
1078
1111
|
request: SessionConfigSetupRequestSchema,
|
|
1079
1112
|
response: SessionConfigSetupResponseSchema
|
|
1113
|
+
},
|
|
1114
|
+
"sessionConfig.destroy": {
|
|
1115
|
+
request: SessionConfigTeardownRequestSchema,
|
|
1116
|
+
response: SessionConfigTeardownResponseSchema
|
|
1080
1117
|
}
|
|
1081
1118
|
});
|
|
1082
1119
|
/**
|
|
@@ -1094,6 +1131,7 @@ const { subjects, namespaceDomain } = createClientNamespace("codex", {
|
|
|
1094
1131
|
* - `CodexClientSubjects.wiring.apply` → `client:codex.wiring.apply`
|
|
1095
1132
|
* - `CodexClientSubjects.wiring.remove` → `client:codex.wiring.remove`
|
|
1096
1133
|
* - `CodexClientSubjects.sessionConfig.setup` → `client:codex.sessionConfig.setup`
|
|
1134
|
+
* - `CodexClientSubjects.sessionConfig.destroy` → `client:codex.sessionConfig.destroy`
|
|
1097
1135
|
*/
|
|
1098
1136
|
const CodexClientSubjects = subjects;
|
|
1099
1137
|
/**
|
|
@@ -1104,70 +1142,1017 @@ const CodexClientSubjects = subjects;
|
|
|
1104
1142
|
const CODEX_CLIENT_NAMESPACE = namespaceDomain;
|
|
1105
1143
|
|
|
1106
1144
|
//#endregion
|
|
1107
|
-
//#region src/runtime/
|
|
1145
|
+
//#region src/runtime/native-auth-errors.ts
|
|
1108
1146
|
/**
|
|
1109
|
-
* Codex
|
|
1110
|
-
*
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
*
|
|
1147
|
+
* Secret-safe error helpers for Codex native authentication storage.
|
|
1148
|
+
* @packageDocumentation
|
|
1149
|
+
*/
|
|
1150
|
+
/**
|
|
1151
|
+
* Build an error that preserves a safe discriminator but never the secret-bearing message.
|
|
1152
|
+
* @param operation - Safe operation label.
|
|
1153
|
+
* @param error - Original failure, whose message is deliberately discarded.
|
|
1154
|
+
* @returns Sanitized error safe for bus propagation and logs.
|
|
1155
|
+
*/
|
|
1156
|
+
function sanitizedNativeAuthError(operation, error) {
|
|
1157
|
+
const errorName = error instanceof Error && error.name ? error.name : "UnknownError";
|
|
1158
|
+
const code = error?.code;
|
|
1159
|
+
const safeCode = typeof code === "string" || typeof code === "number" ? `/${String(code)}` : "";
|
|
1160
|
+
return /* @__PURE__ */ new Error(`Codex native-auth ${operation} failed (${errorName}${safeCode})`);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Preserve both sanitized failures when cleanup follows an earlier error.
|
|
1164
|
+
* @param existing - Earlier failure, when present.
|
|
1165
|
+
* @param next - Newly observed sanitized failure.
|
|
1166
|
+
* @param message - Safe aggregate description.
|
|
1167
|
+
* @returns The new failure or an aggregate retaining both failures.
|
|
1168
|
+
*/
|
|
1169
|
+
function mergeNativeAuthErrors(existing, next, message) {
|
|
1170
|
+
return existing === void 0 ? next : new AggregateError([existing, next], message);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
//#endregion
|
|
1174
|
+
//#region src/runtime/native-keyring-credential-store.ts
|
|
1175
|
+
/** Load the optional native keyring addon only when a keyring operation runs. */
|
|
1176
|
+
let nativeKeyringModule;
|
|
1177
|
+
/**
|
|
1178
|
+
* Create one native keyring entry without evaluating the addon on file-only paths.
|
|
1179
|
+
* @param service - Keyring service name.
|
|
1180
|
+
* @param account - Keyring account name.
|
|
1181
|
+
* @returns Native asynchronous keyring entry.
|
|
1182
|
+
*/
|
|
1183
|
+
async function createNativeKeyringEntry(service, account) {
|
|
1184
|
+
nativeKeyringModule ??= import("@napi-rs/keyring");
|
|
1185
|
+
const { AsyncEntry } = await nativeKeyringModule;
|
|
1186
|
+
return new AsyncEntry(service, account);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Check the portable keyring binding's missing-entry discriminator.
|
|
1190
|
+
* @param error - Keyring operation failure.
|
|
1191
|
+
* @returns Whether the keyring reports an absent entry.
|
|
1192
|
+
*/
|
|
1193
|
+
function isMissingKeyringEntry(error) {
|
|
1194
|
+
return error instanceof Error && error.name === "NoEntry";
|
|
1195
|
+
}
|
|
1196
|
+
/** Native cross-platform keyring implementation matching Codex's keyring-rs store. */
|
|
1197
|
+
const nativeKeyringCredentialStore = {
|
|
1198
|
+
async read(service, account) {
|
|
1199
|
+
try {
|
|
1200
|
+
return await (await createNativeKeyringEntry(service, account)).getPassword() ?? null;
|
|
1201
|
+
} catch (error) {
|
|
1202
|
+
if (isMissingKeyringEntry(error)) return null;
|
|
1203
|
+
throw error;
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
async write(service, account, value) {
|
|
1207
|
+
await (await createNativeKeyringEntry(service, account)).setPassword(value);
|
|
1208
|
+
},
|
|
1209
|
+
async delete(service, account) {
|
|
1210
|
+
try {
|
|
1211
|
+
await (await createNativeKeyringEntry(service, account)).deleteCredential();
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
if (!isMissingKeyringEntry(error)) throw error;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/runtime/native-auth-source-lock.ts
|
|
1220
|
+
/**
|
|
1221
|
+
* Canonical Codex native-auth identity and source-lock primitives.
|
|
1222
|
+
* @packageDocumentation
|
|
1223
|
+
*/
|
|
1224
|
+
/** Secret-free cross-process lock-directory suffix beside canonical CODEX_HOME. */
|
|
1225
|
+
const CODEX_AUTH_CAS_LOCK_FILE = ".makaio-codex-auth-cas.lock";
|
|
1226
|
+
/** Lock finalization failed after the guarded native-auth operation committed. */
|
|
1227
|
+
var CodexNativeAuthSourceLockFinalizationError = class extends Error {
|
|
1228
|
+
constructor() {
|
|
1229
|
+
super("Codex native-auth source lock is uncertain after committed operation");
|
|
1230
|
+
this.name = "CodexNativeAuthSourceLockFinalizationError";
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
/** Lock freshness and retry policy shared by every Codex native-auth CAS. */
|
|
1234
|
+
const CODEX_AUTH_CAS_LOCK_OPTIONS = {
|
|
1235
|
+
stale: 3e4,
|
|
1236
|
+
update: 1e4,
|
|
1237
|
+
retries: {
|
|
1238
|
+
retries: 50,
|
|
1239
|
+
factor: 1,
|
|
1240
|
+
minTimeout: 20,
|
|
1241
|
+
maxTimeout: 100
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
/**
|
|
1245
|
+
* Resolve the canonical identity shared by Codex file and keyring auth stores.
|
|
1246
|
+
* @param codexHome - CODEX_HOME path.
|
|
1247
|
+
* @returns Canonical path plus Codex's derived keyring account.
|
|
1248
|
+
*/
|
|
1249
|
+
async function identifyCodexAuthHome(codexHome) {
|
|
1250
|
+
return createCodexAuthHomeIdentity(await canonicalizeCodexHome(codexHome));
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Derive a cleanup-only identity from the normalized input path.
|
|
1114
1254
|
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1255
|
+
* This deliberately performs no `realpath` lookup. A lease directory may have
|
|
1256
|
+
* disappeared or been replaced by a symlink before restart cleanup; following
|
|
1257
|
+
* that new target could delete an unrelated canonical keyring credential.
|
|
1258
|
+
* @param codexHome - Original lease CODEX_HOME path.
|
|
1259
|
+
* @returns Lexically normalized identity safe for conservative keyring cleanup.
|
|
1260
|
+
*/
|
|
1261
|
+
function identifyCodexAuthHomeLexically(codexHome) {
|
|
1262
|
+
return createCodexAuthHomeIdentity(path$1.resolve(codexHome));
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Inspect a lease target while rejecting a symlink or non-directory final path.
|
|
1266
|
+
* @param codexHome - Isolated lease CODEX_HOME path.
|
|
1267
|
+
* @returns Stable canonical identity, or a cleanup-only lexical fallback.
|
|
1268
|
+
*/
|
|
1269
|
+
async function inspectCodexLeaseTarget(codexHome) {
|
|
1270
|
+
const fallbackIdentity = identifyCodexAuthHomeLexically(codexHome);
|
|
1271
|
+
let before;
|
|
1272
|
+
try {
|
|
1273
|
+
before = await fs$1.lstat(codexHome);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
if (error.code === "ENOENT") return {
|
|
1276
|
+
status: "missing",
|
|
1277
|
+
fallbackIdentity
|
|
1278
|
+
};
|
|
1279
|
+
throw sanitizedNativeAuthError("lease target inspection", error);
|
|
1280
|
+
}
|
|
1281
|
+
if (before.isSymbolicLink() || !before.isDirectory()) return {
|
|
1282
|
+
status: "unsafe",
|
|
1283
|
+
fallbackIdentity
|
|
1284
|
+
};
|
|
1285
|
+
const identity = await identifyCodexAuthHome(codexHome);
|
|
1286
|
+
let after;
|
|
1287
|
+
try {
|
|
1288
|
+
after = await fs$1.lstat(codexHome);
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
if (error.code === "ENOENT") return {
|
|
1291
|
+
status: "unsafe",
|
|
1292
|
+
fallbackIdentity
|
|
1293
|
+
};
|
|
1294
|
+
throw sanitizedNativeAuthError("lease target verification", error);
|
|
1295
|
+
}
|
|
1296
|
+
if (after.isSymbolicLink() || !after.isDirectory() || before.dev !== after.dev || before.ino !== after.ino) return {
|
|
1297
|
+
status: "unsafe",
|
|
1298
|
+
fallbackIdentity
|
|
1299
|
+
};
|
|
1300
|
+
return {
|
|
1301
|
+
status: "safe",
|
|
1302
|
+
identity
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Serialize one canonical Codex native-auth mutation by CODEX_HOME.
|
|
1118
1307
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1308
|
+
* Account integrations and session-lease compare-and-swap operations must use
|
|
1309
|
+
* this same lock so a lease cannot overwrite a concurrently selected account.
|
|
1310
|
+
* @param codexHome - Canonical Codex config home whose auth source is mutated.
|
|
1311
|
+
* @param operation - Source operation that must retain exclusive ownership.
|
|
1312
|
+
* @returns Operation result after the source lock is released.
|
|
1313
|
+
*/
|
|
1314
|
+
async function withCodexNativeAuthSourceLock(codexHome, operation) {
|
|
1315
|
+
const result = await executeCodexNativeAuthSourceLock(codexHome, operation);
|
|
1316
|
+
if (result.coordination === "uncertain") throw new CodexNativeAuthSourceLockFinalizationError();
|
|
1317
|
+
return result.value;
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Execute against one CODEX_HOME while preserving committed operation results
|
|
1321
|
+
* when lock finalization becomes uncertain.
|
|
1322
|
+
* @param codexHome - Canonical Codex config home.
|
|
1323
|
+
* @param operation - Native-auth operation guarded by the source lock.
|
|
1324
|
+
* @returns Operation value plus released/uncertain coordination state.
|
|
1325
|
+
*/
|
|
1326
|
+
async function executeCodexNativeAuthSourceLock(codexHome, operation) {
|
|
1327
|
+
return executeIdentifiedCodexNativeAuthSourceLock(await identifyCodexAuthHome(codexHome), operation);
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Serialize one operation against an already-pinned Codex auth identity.
|
|
1331
|
+
* @param identity - Canonical source CODEX_HOME identity.
|
|
1332
|
+
* @param operation - Source operation that must retain exclusive ownership.
|
|
1333
|
+
* @returns Operation result after the source lock is released.
|
|
1334
|
+
*/
|
|
1335
|
+
async function withIdentifiedCodexNativeAuthSourceLock(identity, operation) {
|
|
1336
|
+
const result = await executeIdentifiedCodexNativeAuthSourceLock(identity, operation);
|
|
1337
|
+
if (result.coordination === "uncertain") throw new CodexNativeAuthSourceLockFinalizationError();
|
|
1338
|
+
return result.value;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Execute against an already-pinned identity and retain post-commit lock state.
|
|
1342
|
+
* @param identity - Canonical source CODEX_HOME identity.
|
|
1343
|
+
* @param operation - Native-auth operation guarded by the source lock.
|
|
1344
|
+
* @returns Operation value plus released/uncertain coordination state.
|
|
1345
|
+
*/
|
|
1346
|
+
async function executeIdentifiedCodexNativeAuthSourceLock(identity, operation) {
|
|
1347
|
+
if (!inspectionMatchesIdentity(await inspectCodexLeaseTarget(identity.canonicalPath), identity)) throw new Error("Codex native-auth source identity is no longer a stable directory");
|
|
1348
|
+
let compromisedError;
|
|
1349
|
+
let release;
|
|
1350
|
+
try {
|
|
1351
|
+
release = await lock(identity.canonicalPath, {
|
|
1352
|
+
realpath: false,
|
|
1353
|
+
lockfilePath: buildCodexNativeAuthSourceLockPath(identity.canonicalPath),
|
|
1354
|
+
...CODEX_AUTH_CAS_LOCK_OPTIONS,
|
|
1355
|
+
onCompromised: (error) => {
|
|
1356
|
+
compromisedError = sanitizedNativeAuthError("source lock compromised", error);
|
|
1357
|
+
}
|
|
1358
|
+
});
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
throw sanitizedNativeAuthError("source lock acquisition", error);
|
|
1361
|
+
}
|
|
1362
|
+
let outcome;
|
|
1363
|
+
if (compromisedError !== void 0) outcome = {
|
|
1364
|
+
status: "rejected",
|
|
1365
|
+
reason: compromisedError
|
|
1366
|
+
};
|
|
1367
|
+
else try {
|
|
1368
|
+
if (!inspectionMatchesIdentity(await inspectCodexLeaseTarget(identity.canonicalPath), identity)) throw new Error("Codex native-auth source identity changed before operation");
|
|
1369
|
+
outcome = {
|
|
1370
|
+
status: "fulfilled",
|
|
1371
|
+
value: await operation()
|
|
1372
|
+
};
|
|
1373
|
+
} catch (reason) {
|
|
1374
|
+
outcome = {
|
|
1375
|
+
status: "rejected",
|
|
1376
|
+
reason
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
let releaseError;
|
|
1380
|
+
try {
|
|
1381
|
+
await release();
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
releaseError = sanitizedNativeAuthError("source lock release", error);
|
|
1384
|
+
}
|
|
1385
|
+
if (outcome.status === "rejected") {
|
|
1386
|
+
let operationError = outcome.reason;
|
|
1387
|
+
if (compromisedError !== void 0 && operationError !== compromisedError) operationError = mergeNativeAuthErrors(operationError, compromisedError, "Codex native-auth operation and source lock compromise both failed");
|
|
1388
|
+
if (releaseError !== void 0) operationError = mergeNativeAuthErrors(operationError, releaseError, "Codex native-auth operation and source lock release both failed");
|
|
1389
|
+
throw operationError;
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
value: outcome.value,
|
|
1393
|
+
coordination: compromisedError !== void 0 || releaseError !== void 0 ? "uncertain" : "released"
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Build a sibling lock anchor that survives CODEX_HOME rename/replacement.
|
|
1398
|
+
* @param canonicalPath - Canonical CODEX_HOME path.
|
|
1399
|
+
* @returns Secret-free sibling lock directory path.
|
|
1400
|
+
*/
|
|
1401
|
+
function buildCodexNativeAuthSourceLockPath(canonicalPath) {
|
|
1402
|
+
return `${canonicalPath}${CODEX_AUTH_CAS_LOCK_FILE}`;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Verify a present or absent source still represents the pinned lexical identity.
|
|
1406
|
+
* @param inspection - Current safety inspection for the source path.
|
|
1407
|
+
* @param identity - Identity pinned before lock acquisition.
|
|
1408
|
+
* @returns Whether the inspected path still represents the pinned identity.
|
|
1409
|
+
*/
|
|
1410
|
+
function inspectionMatchesIdentity(inspection, identity) {
|
|
1411
|
+
if (inspection.status === "unsafe") return false;
|
|
1412
|
+
const inspectedIdentity = inspection.status === "safe" ? inspection.identity : inspection.fallbackIdentity;
|
|
1413
|
+
return inspectedIdentity.canonicalPath === identity.canonicalPath && inspectedIdentity.keyringAccount === identity.keyringAccount;
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Build the exact Codex keyring identity for one already-normalized path.
|
|
1417
|
+
* @param canonicalPath - Canonical or deliberately lexical absolute path.
|
|
1418
|
+
* @returns Path plus `cli|sha256(path).slice(0, 16)` account.
|
|
1419
|
+
*/
|
|
1420
|
+
function createCodexAuthHomeIdentity(canonicalPath) {
|
|
1421
|
+
return {
|
|
1422
|
+
canonicalPath,
|
|
1423
|
+
keyringAccount: `cli|${createHash("sha256").update(canonicalPath).digest("hex").slice(0, 16)}`
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Canonicalize CODEX_HOME exactly as Codex does, falling back when absent.
|
|
1428
|
+
* @param codexHome - Candidate CODEX_HOME path.
|
|
1429
|
+
* @returns Canonical path, or the absolute input path when absent.
|
|
1430
|
+
*/
|
|
1431
|
+
async function canonicalizeCodexHome(codexHome) {
|
|
1432
|
+
try {
|
|
1433
|
+
return await fs$1.realpath(codexHome);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
if (error.code === "ENOENT") return path$1.resolve(codexHome);
|
|
1436
|
+
throw sanitizedNativeAuthError("CODEX_HOME canonicalization", error);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/runtime/native-auth-store.ts
|
|
1442
|
+
/**
|
|
1443
|
+
* Codex native authentication storage primitives.
|
|
1122
1444
|
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1445
|
+
* Mirrors Codex App Server 0.130's `file`, `keyring`, and `auto` credential
|
|
1446
|
+
* store semantics without interpreting credential payloads. Credential values
|
|
1447
|
+
* remain inside this client-owned runtime boundary; the only persisted lease
|
|
1448
|
+
* state contains store identities and SHA-256 digests.
|
|
1125
1449
|
* @packageDocumentation
|
|
1126
1450
|
*/
|
|
1451
|
+
/** Codex's fixed keyring service name for CLI authentication. */
|
|
1452
|
+
const CODEX_AUTH_KEYRING_SERVICE = "Codex Auth";
|
|
1453
|
+
/** Non-secret lease metadata file stored inside the isolated CODEX_HOME. */
|
|
1454
|
+
const CODEX_AUTH_LEASE_METADATA_FILE = ".makaio-codex-auth-lease.json";
|
|
1455
|
+
const CodexAuthHomeIdentitySchema = z.object({
|
|
1456
|
+
canonicalPath: z.string().refine((value) => path$1.isAbsolute(value), "Expected an absolute path"),
|
|
1457
|
+
keyringAccount: z.string().regex(/^cli\|[0-9a-f]{16}$/)
|
|
1458
|
+
}).strict().superRefine((identity, ctx) => {
|
|
1459
|
+
const expected = `cli|${createHash("sha256").update(identity.canonicalPath).digest("hex").slice(0, 16)}`;
|
|
1460
|
+
if (identity.keyringAccount !== expected) ctx.addIssue({
|
|
1461
|
+
code: "custom",
|
|
1462
|
+
message: "Keyring account does not match canonical CODEX_HOME"
|
|
1463
|
+
});
|
|
1464
|
+
});
|
|
1465
|
+
const DigestSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
|
1466
|
+
/** Strict schema for the secret-free native auth lease generation. */
|
|
1467
|
+
const CodexAuthLeaseMetadataSchema = z.object({
|
|
1468
|
+
version: z.literal(1),
|
|
1469
|
+
backend: z.object({
|
|
1470
|
+
configured: z.enum([
|
|
1471
|
+
"none",
|
|
1472
|
+
"file",
|
|
1473
|
+
"keyring",
|
|
1474
|
+
"auto"
|
|
1475
|
+
]),
|
|
1476
|
+
effective: z.enum([
|
|
1477
|
+
"none",
|
|
1478
|
+
"file",
|
|
1479
|
+
"keyring"
|
|
1480
|
+
])
|
|
1481
|
+
}).strict(),
|
|
1482
|
+
canonicalIdentity: CodexAuthHomeIdentitySchema.nullable(),
|
|
1483
|
+
targetIdentity: CodexAuthHomeIdentitySchema,
|
|
1484
|
+
sourceGenerationDigest: DigestSchema.nullable(),
|
|
1485
|
+
initialTargetDigest: DigestSchema.nullable()
|
|
1486
|
+
}).strict().superRefine((metadata, ctx) => {
|
|
1487
|
+
if (metadata.backend.effective !== "none" !== (metadata.canonicalIdentity !== null && metadata.sourceGenerationDigest !== null && metadata.initialTargetDigest !== null)) ctx.addIssue({
|
|
1488
|
+
code: "custom",
|
|
1489
|
+
message: "Materialized metadata must carry one complete generation"
|
|
1490
|
+
});
|
|
1491
|
+
if (metadata.sourceGenerationDigest !== null && metadata.initialTargetDigest !== null && metadata.sourceGenerationDigest !== metadata.initialTargetDigest) ctx.addIssue({
|
|
1492
|
+
code: "custom",
|
|
1493
|
+
message: "The initial target generation must equal the source generation"
|
|
1494
|
+
});
|
|
1495
|
+
if (metadata.backend.configured === "none" && metadata.backend.effective !== "none") ctx.addIssue({
|
|
1496
|
+
code: "custom",
|
|
1497
|
+
message: "An empty lease cannot materialize native authentication"
|
|
1498
|
+
});
|
|
1499
|
+
if (metadata.backend.configured === "file" && metadata.backend.effective === "keyring") ctx.addIssue({
|
|
1500
|
+
code: "custom",
|
|
1501
|
+
message: "File mode cannot select keyring authentication"
|
|
1502
|
+
});
|
|
1503
|
+
if (metadata.backend.configured === "keyring" && metadata.backend.effective === "file") ctx.addIssue({
|
|
1504
|
+
code: "custom",
|
|
1505
|
+
message: "Keyring mode cannot select file authentication"
|
|
1506
|
+
});
|
|
1507
|
+
});
|
|
1508
|
+
/**
|
|
1509
|
+
* Compute a stable SHA-256 digest without interpreting the credential value.
|
|
1510
|
+
* @param value - Opaque credential payload.
|
|
1511
|
+
* @returns Lowercase hexadecimal SHA-256 digest.
|
|
1512
|
+
*/
|
|
1513
|
+
function digestCodexCredential(value) {
|
|
1514
|
+
return createHash("sha256").update(value).digest("hex");
|
|
1515
|
+
}
|
|
1127
1516
|
/**
|
|
1128
|
-
*
|
|
1129
|
-
* @param src - Source file path.
|
|
1130
|
-
* @param dst - Destination file path.
|
|
1517
|
+
* Local native auth store used by the session config handler.
|
|
1131
1518
|
*/
|
|
1132
|
-
|
|
1519
|
+
var CodexNativeAuthStore = class {
|
|
1520
|
+
keyring;
|
|
1521
|
+
/**
|
|
1522
|
+
* @param keyring - Cross-platform keyring implementation.
|
|
1523
|
+
*/
|
|
1524
|
+
constructor(keyring = nativeKeyringCredentialStore) {
|
|
1525
|
+
this.keyring = keyring;
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
1528
|
+
* Resolve `cli_auth_credentials_store` from the canonical config.
|
|
1529
|
+
* Codex 0.130 defaults this setting to `file`.
|
|
1530
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1531
|
+
* @returns Configured credential store mode.
|
|
1532
|
+
*/
|
|
1533
|
+
async resolveMode(identity) {
|
|
1534
|
+
const content = await readTextIfPresent(path$1.join(identity.canonicalPath, "config.toml"), "configuration read");
|
|
1535
|
+
if (content === null) return "file";
|
|
1536
|
+
let parsed;
|
|
1537
|
+
try {
|
|
1538
|
+
parsed = parse(content);
|
|
1539
|
+
} catch {
|
|
1540
|
+
throw new Error("Codex native-auth configuration is invalid");
|
|
1541
|
+
}
|
|
1542
|
+
const configured = parsed["cli_auth_credentials_store"];
|
|
1543
|
+
if (configured === void 0) return "file";
|
|
1544
|
+
if (configured === "file" || configured === "keyring" || configured === "auto") return configured;
|
|
1545
|
+
throw new Error("Codex native-auth configuration selects an unsupported credential store");
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Load a credential using Codex 0.130's configured store order.
|
|
1549
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1550
|
+
* @param mode - Configured store mode.
|
|
1551
|
+
* @returns Effective credential plus keyring-fallback status.
|
|
1552
|
+
*/
|
|
1553
|
+
async readEffective(identity, mode) {
|
|
1554
|
+
if (mode === "file") return {
|
|
1555
|
+
credential: await this.readFileCredential(identity),
|
|
1556
|
+
keyringUnavailable: false
|
|
1557
|
+
};
|
|
1558
|
+
if (mode === "keyring") return {
|
|
1559
|
+
credential: await this.readKeyringCredential(identity),
|
|
1560
|
+
keyringUnavailable: false
|
|
1561
|
+
};
|
|
1562
|
+
try {
|
|
1563
|
+
const keyringCredential = await this.readKeyringCredential(identity);
|
|
1564
|
+
if (keyringCredential !== null) return {
|
|
1565
|
+
credential: keyringCredential,
|
|
1566
|
+
keyringUnavailable: false
|
|
1567
|
+
};
|
|
1568
|
+
} catch {
|
|
1569
|
+
return {
|
|
1570
|
+
credential: await this.readFileCredential(identity),
|
|
1571
|
+
keyringUnavailable: true
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return {
|
|
1575
|
+
credential: await this.readFileCredential(identity),
|
|
1576
|
+
keyringUnavailable: false
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Read the credential generation that should be considered for lease refresh.
|
|
1581
|
+
*
|
|
1582
|
+
* `auto` normally prefers keyring. During a running process, however, a
|
|
1583
|
+
* transient keyring save failure can put a fresher generation in `auth.json`
|
|
1584
|
+
* while the unchanged keyring entry remains. Detect that exact fallback by
|
|
1585
|
+
* comparing both stores to the lease's initial generation.
|
|
1586
|
+
* @param identity - Isolated target CODEX_HOME identity.
|
|
1587
|
+
* @param mode - Configured store mode.
|
|
1588
|
+
* @param initialDigest - Digest materialized when the lease was created.
|
|
1589
|
+
* @returns Refresh candidate plus keyring availability status.
|
|
1590
|
+
*/
|
|
1591
|
+
async readRefreshCandidate(identity, mode, initialDigest) {
|
|
1592
|
+
if (mode !== "auto") return this.readEffective(identity, mode);
|
|
1593
|
+
let keyringCredential;
|
|
1594
|
+
try {
|
|
1595
|
+
keyringCredential = await this.readKeyringCredential(identity);
|
|
1596
|
+
} catch {
|
|
1597
|
+
return {
|
|
1598
|
+
credential: await this.readFileCredential(identity),
|
|
1599
|
+
keyringUnavailable: true
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
const fileCredential = await this.readFileCredential(identity);
|
|
1603
|
+
if (keyringCredential !== null && digestCodexCredential(keyringCredential.value) !== initialDigest) return {
|
|
1604
|
+
credential: keyringCredential,
|
|
1605
|
+
keyringUnavailable: false
|
|
1606
|
+
};
|
|
1607
|
+
if (fileCredential !== null && digestCodexCredential(fileCredential.value) !== initialDigest) return {
|
|
1608
|
+
credential: fileCredential,
|
|
1609
|
+
keyringUnavailable: false
|
|
1610
|
+
};
|
|
1611
|
+
return {
|
|
1612
|
+
credential: keyringCredential ?? fileCredential,
|
|
1613
|
+
keyringUnavailable: false
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
/**
|
|
1617
|
+
* Read one concrete backend without fallback.
|
|
1618
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1619
|
+
* @param backend - Concrete store to read.
|
|
1620
|
+
* @returns Credential value, or `null` when absent.
|
|
1621
|
+
*/
|
|
1622
|
+
async readBackend(identity, backend) {
|
|
1623
|
+
return (backend === "file" ? await this.readFileCredential(identity) : await this.readKeyringCredential(identity))?.value ?? null;
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Clone an opaque value into one concrete target backend.
|
|
1627
|
+
* @param identity - Target CODEX_HOME identity.
|
|
1628
|
+
* @param backend - Concrete target store.
|
|
1629
|
+
* @param value - Opaque credential payload.
|
|
1630
|
+
*/
|
|
1631
|
+
async writeBackend(identity, backend, value) {
|
|
1632
|
+
if (backend === "file") {
|
|
1633
|
+
await writePrivateFileAtomically(path$1.join(identity.canonicalPath, "auth.json"), value);
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
await this.writeKeyring(identity, value);
|
|
1637
|
+
await removeFileIfPresent(path$1.join(identity.canonicalPath, "auth.json"), "credential fallback removal").catch(() => void 0);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Persist a refreshed credential using Codex 0.130's save semantics.
|
|
1641
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1642
|
+
* @param mode - Configured store mode.
|
|
1643
|
+
* @param value - Refreshed opaque credential payload.
|
|
1644
|
+
*/
|
|
1645
|
+
async saveConfigured(identity, mode, value) {
|
|
1646
|
+
if (mode === "file") {
|
|
1647
|
+
await this.writeBackend(identity, "file", value);
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
if (mode === "keyring") {
|
|
1651
|
+
await this.writeBackend(identity, "keyring", value);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
try {
|
|
1655
|
+
await this.writeBackend(identity, "keyring", value);
|
|
1656
|
+
} catch {
|
|
1657
|
+
await this.writeBackend(identity, "file", value);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Run a generation check and conditional write under one cross-process lock.
|
|
1662
|
+
* @param identity - Canonical source CODEX_HOME identity.
|
|
1663
|
+
* @param operation - CAS operation to execute while the lock is held.
|
|
1664
|
+
* @returns Operation result.
|
|
1665
|
+
*/
|
|
1666
|
+
async withSourceLock(identity, operation) {
|
|
1667
|
+
return withIdentifiedCodexNativeAuthSourceLock(identity, operation);
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Remove a target keyring credential.
|
|
1671
|
+
* @param identity - Target CODEX_HOME identity.
|
|
1672
|
+
*/
|
|
1673
|
+
async deleteKeyring(identity) {
|
|
1674
|
+
try {
|
|
1675
|
+
await this.keyring.delete(CODEX_AUTH_KEYRING_SERVICE, identity.keyringAccount);
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
throw sanitizedNativeAuthError("keyring cleanup", error);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
/**
|
|
1681
|
+
* Remove the file credential from a target CODEX_HOME.
|
|
1682
|
+
* @param identity - Target CODEX_HOME identity.
|
|
1683
|
+
*/
|
|
1684
|
+
async deleteFile(identity) {
|
|
1685
|
+
await removeFileIfPresent(path$1.join(identity.canonicalPath, "auth.json"), "credential cleanup");
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Atomically persist strict secret-free lease metadata with mode `0600`.
|
|
1689
|
+
* @param sessionDir - Isolated CODEX_HOME.
|
|
1690
|
+
* @param metadata - Validated metadata generation.
|
|
1691
|
+
*/
|
|
1692
|
+
async writeLeaseMetadata(sessionDir, metadata) {
|
|
1693
|
+
const parsed = CodexAuthLeaseMetadataSchema.parse(metadata);
|
|
1694
|
+
await writePrivateFileAtomically(path$1.join(sessionDir, CODEX_AUTH_LEASE_METADATA_FILE), `${JSON.stringify(parsed)}\n`);
|
|
1695
|
+
}
|
|
1696
|
+
/**
|
|
1697
|
+
* Read and strictly parse secret-free lease metadata.
|
|
1698
|
+
* @param sessionDir - Isolated CODEX_HOME.
|
|
1699
|
+
* @returns Found, missing, or invalid outcome without echoing file contents.
|
|
1700
|
+
*/
|
|
1701
|
+
async readLeaseMetadata(sessionDir) {
|
|
1702
|
+
let content;
|
|
1703
|
+
try {
|
|
1704
|
+
content = await fs$1.readFile(path$1.join(sessionDir, CODEX_AUTH_LEASE_METADATA_FILE), "utf-8");
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
if (error.code === "ENOENT") return { status: "missing" };
|
|
1707
|
+
throw sanitizedNativeAuthError("lease metadata read", error);
|
|
1708
|
+
}
|
|
1709
|
+
try {
|
|
1710
|
+
return {
|
|
1711
|
+
status: "found",
|
|
1712
|
+
metadata: CodexAuthLeaseMetadataSchema.parse(JSON.parse(content))
|
|
1713
|
+
};
|
|
1714
|
+
} catch {
|
|
1715
|
+
return { status: "invalid" };
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
/**
|
|
1719
|
+
* Read a file-backed credential.
|
|
1720
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1721
|
+
* @returns Opaque file credential, or `null` when absent.
|
|
1722
|
+
*/
|
|
1723
|
+
async readFileCredential(identity) {
|
|
1724
|
+
const value = await readTextIfPresent(path$1.join(identity.canonicalPath, "auth.json"), "credential file read");
|
|
1725
|
+
return value === null ? null : {
|
|
1726
|
+
backend: "file",
|
|
1727
|
+
value
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Read a keyring-backed credential.
|
|
1732
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1733
|
+
* @returns Opaque keyring credential, or `null` when absent.
|
|
1734
|
+
*/
|
|
1735
|
+
async readKeyringCredential(identity) {
|
|
1736
|
+
try {
|
|
1737
|
+
const value = await this.keyring.read(CODEX_AUTH_KEYRING_SERVICE, identity.keyringAccount);
|
|
1738
|
+
return value === null ? null : {
|
|
1739
|
+
backend: "keyring",
|
|
1740
|
+
value
|
|
1741
|
+
};
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
throw sanitizedNativeAuthError("keyring read", error);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Write a keyring-backed credential.
|
|
1748
|
+
* @param identity - Canonical CODEX_HOME identity.
|
|
1749
|
+
* @param value - Opaque credential payload.
|
|
1750
|
+
*/
|
|
1751
|
+
async writeKeyring(identity, value) {
|
|
1752
|
+
try {
|
|
1753
|
+
await this.keyring.write(CODEX_AUTH_KEYRING_SERVICE, identity.keyringAccount, value);
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
throw sanitizedNativeAuthError("keyring write", error);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
/**
|
|
1760
|
+
* Read a text file with ENOENT represented as `null`.
|
|
1761
|
+
* @param filePath - File path to read.
|
|
1762
|
+
* @param operation - Sanitized operation label for failures.
|
|
1763
|
+
* @returns File contents, or `null` when absent.
|
|
1764
|
+
*/
|
|
1765
|
+
async function readTextIfPresent(filePath, operation) {
|
|
1133
1766
|
try {
|
|
1134
|
-
await fs$1.
|
|
1767
|
+
return await fs$1.readFile(filePath, "utf-8");
|
|
1135
1768
|
} catch (error) {
|
|
1136
|
-
if (error.code
|
|
1769
|
+
if (error.code === "ENOENT") return null;
|
|
1770
|
+
throw sanitizedNativeAuthError(operation, error);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Write one private file via create/write/sync/rename.
|
|
1775
|
+
* @param filePath - Destination path.
|
|
1776
|
+
* @param value - File contents.
|
|
1777
|
+
*/
|
|
1778
|
+
async function writePrivateFileAtomically(filePath, value) {
|
|
1779
|
+
await fs$1.mkdir(path$1.dirname(filePath), { recursive: true });
|
|
1780
|
+
const temporaryPath = path$1.join(path$1.dirname(filePath), `.${path$1.basename(filePath)}.${randomUUID()}.tmp`);
|
|
1781
|
+
let handle;
|
|
1782
|
+
let operationError;
|
|
1783
|
+
try {
|
|
1784
|
+
handle = await fs$1.open(temporaryPath, "wx", 384);
|
|
1785
|
+
await handle.writeFile(value, "utf-8");
|
|
1786
|
+
await handle.sync();
|
|
1787
|
+
await handle.close();
|
|
1788
|
+
handle = void 0;
|
|
1789
|
+
await fs$1.rename(temporaryPath, filePath);
|
|
1790
|
+
await fs$1.chmod(filePath, 384);
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
operationError = sanitizedNativeAuthError("private file write", error);
|
|
1137
1793
|
}
|
|
1794
|
+
if (handle !== void 0) try {
|
|
1795
|
+
await handle.close();
|
|
1796
|
+
} catch (error) {
|
|
1797
|
+
operationError = mergeNativeAuthErrors(operationError, sanitizedNativeAuthError("private file close", error), "Codex native-auth private file write and close both failed");
|
|
1798
|
+
}
|
|
1799
|
+
try {
|
|
1800
|
+
await fs$1.rm(temporaryPath, { force: true });
|
|
1801
|
+
} catch (error) {
|
|
1802
|
+
operationError = mergeNativeAuthErrors(operationError, sanitizedNativeAuthError("private temporary file cleanup", error), "Codex native-auth private file operation and cleanup both failed");
|
|
1803
|
+
}
|
|
1804
|
+
if (operationError !== void 0) throw operationError;
|
|
1138
1805
|
}
|
|
1139
1806
|
/**
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1807
|
+
* Remove a file without exposing filesystem details or contents.
|
|
1808
|
+
* @param filePath - File path to remove.
|
|
1809
|
+
* @param operation - Sanitized operation label for failures.
|
|
1810
|
+
*/
|
|
1811
|
+
async function removeFileIfPresent(filePath, operation) {
|
|
1812
|
+
try {
|
|
1813
|
+
await fs$1.rm(filePath, { force: true });
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
throw sanitizedNativeAuthError(operation, error);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
//#endregion
|
|
1820
|
+
//#region src/runtime/session-config-handler.ts
|
|
1821
|
+
/**
|
|
1822
|
+
* Codex session config lease handler.
|
|
1142
1823
|
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
*
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1824
|
+
* Materializes file/keyring native auth into an isolated CODEX_HOME and
|
|
1825
|
+
* reconciles refreshes on teardown with a generation-checked write-back.
|
|
1826
|
+
* `empty` inheritance never reads or clones canonical native credentials.
|
|
1827
|
+
* @packageDocumentation
|
|
1828
|
+
*/
|
|
1829
|
+
/**
|
|
1830
|
+
* Codex-owned setup and teardown component for isolated config leases.
|
|
1831
|
+
*/
|
|
1832
|
+
var CodexSessionConfigHandler = class {
|
|
1833
|
+
authStore;
|
|
1834
|
+
nativeConfigDir;
|
|
1835
|
+
/** Runtime copy of metadata used when a lease directory disappeared early. */
|
|
1836
|
+
activeMetadata = /* @__PURE__ */ new Map();
|
|
1837
|
+
/**
|
|
1838
|
+
* @param authStore - Native file/keyring storage implementation.
|
|
1839
|
+
* @param nativeConfigDir - Native CODEX_HOME used when no profile source exists.
|
|
1840
|
+
*/
|
|
1841
|
+
constructor(authStore = new CodexNativeAuthStore(), nativeConfigDir = path$1.join(os$1.homedir(), ".codex")) {
|
|
1842
|
+
this.authStore = authStore;
|
|
1843
|
+
this.nativeConfigDir = nativeConfigDir;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Seed an isolated CODEX_HOME according to the requested inheritance policy.
|
|
1847
|
+
* @param payload - Validated setup delegation payload.
|
|
1848
|
+
* @returns Process environment and truthful native-auth materialization status.
|
|
1849
|
+
*/
|
|
1850
|
+
async setup(payload) {
|
|
1851
|
+
const { sessionDir, configInheritance, projectDir } = payload;
|
|
1852
|
+
await fs$1.mkdir(sessionDir, { recursive: true });
|
|
1853
|
+
const targetInspection = await inspectCodexLeaseTarget(sessionDir);
|
|
1854
|
+
if (targetInspection.status !== "safe") throw new Error("Codex native-auth lease target must be a stable directory");
|
|
1855
|
+
const targetIdentity = targetInspection.identity;
|
|
1856
|
+
const sourceConfigDir = this.resolveSourceConfigDir(payload);
|
|
1857
|
+
const sourceIdentity = configInheritance === "empty" ? void 0 : await identifyCodexAuthHome(sourceConfigDir);
|
|
1858
|
+
if (sourceIdentity !== void 0 && identitiesEqual(sourceIdentity, targetIdentity)) throw new Error("Codex native-auth source and lease target must be different directories");
|
|
1859
|
+
const emptyMetadata = createEmptyMetadata(targetIdentity);
|
|
1860
|
+
await this.recordMetadata(sessionDir, emptyMetadata);
|
|
1861
|
+
try {
|
|
1862
|
+
let authMaterialized = false;
|
|
1863
|
+
let mode;
|
|
1864
|
+
if (sourceIdentity !== void 0) {
|
|
1865
|
+
const result = await this.authStore.withSourceLock(sourceIdentity, async () => {
|
|
1866
|
+
await this.materializeGeneralConfig(sourceIdentity.canonicalPath, sessionDir, configInheritance);
|
|
1867
|
+
return this.materializeNativeAuthLocked(sourceIdentity, targetIdentity, sessionDir);
|
|
1868
|
+
});
|
|
1869
|
+
authMaterialized = result.materialized;
|
|
1870
|
+
mode = result.mode;
|
|
1871
|
+
} else {
|
|
1872
|
+
await this.materializeGeneralConfig(sourceConfigDir, sessionDir, configInheritance);
|
|
1873
|
+
await this.authStore.deleteFile(targetIdentity);
|
|
1874
|
+
}
|
|
1875
|
+
if (configInheritance === "auth-only" && mode !== void 0) await fs$1.writeFile(path$1.join(sessionDir, "config.toml"), `cli_auth_credentials_store = "${mode}"\n`, { mode: 384 });
|
|
1876
|
+
await handleCodexConfigPrime({
|
|
1877
|
+
clientId: "codex",
|
|
1878
|
+
configDir: sessionDir,
|
|
1879
|
+
phase: "session-create",
|
|
1880
|
+
...projectDir !== void 0 ? { projectDir } : {}
|
|
1881
|
+
});
|
|
1882
|
+
return {
|
|
1883
|
+
env: { CODEX_HOME: sessionDir },
|
|
1884
|
+
authMaterialized
|
|
1885
|
+
};
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
await this.rollbackFailedSetup(sessionDir).catch((cleanupError) => {
|
|
1888
|
+
throw new AggregateError([error, cleanupError], "Codex session config setup and cleanup both failed");
|
|
1889
|
+
});
|
|
1890
|
+
throw error;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Compare-and-swap refreshed native auth back to its canonical store, then
|
|
1895
|
+
* remove the session-specific keyring entry.
|
|
1896
|
+
* @param payload - Validated teardown delegation payload.
|
|
1897
|
+
* @returns Successful client-owned teardown result.
|
|
1898
|
+
*/
|
|
1899
|
+
async teardown(payload) {
|
|
1900
|
+
const leaseKey = path$1.resolve(payload.sessionDir);
|
|
1901
|
+
const activeMetadata = this.activeMetadata.get(leaseKey);
|
|
1902
|
+
const inspection = await this.inspectLeaseForTeardown(payload.sessionDir);
|
|
1903
|
+
const inspectedIdentity = inspection.targetInspection.status === "safe" ? inspection.targetInspection.identity : inspection.targetInspection.fallbackIdentity;
|
|
1904
|
+
const cleanupIdentity = activeMetadata?.targetIdentity ?? inspectedIdentity;
|
|
1905
|
+
let operationError = inspection.operationError;
|
|
1906
|
+
if (operationError === void 0) try {
|
|
1907
|
+
await this.reconcileInspectedLease(payload.sessionDir, inspection, activeMetadata);
|
|
1908
|
+
} catch (error) {
|
|
1909
|
+
operationError = error;
|
|
1910
|
+
}
|
|
1911
|
+
try {
|
|
1912
|
+
for (const cleanupError of await this.collectTargetCleanupErrors(payload.sessionDir, cleanupIdentity)) operationError = mergeOperationErrors(operationError, cleanupError, "Codex native-auth write-back and target cleanup both failed");
|
|
1913
|
+
} finally {
|
|
1914
|
+
this.activeMetadata.delete(leaseKey);
|
|
1915
|
+
}
|
|
1916
|
+
if (operationError !== void 0) throw operationError;
|
|
1917
|
+
return { success: true };
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Inspect the lease target and its metadata without starting write-back.
|
|
1921
|
+
* @param sessionDir - Original isolated CODEX_HOME path.
|
|
1922
|
+
* @returns Target state, metadata state, and any sanitized inspection failure.
|
|
1923
|
+
*/
|
|
1924
|
+
async inspectLeaseForTeardown(sessionDir) {
|
|
1925
|
+
let operationError;
|
|
1926
|
+
let targetInspection;
|
|
1927
|
+
try {
|
|
1928
|
+
targetInspection = await inspectCodexLeaseTarget(sessionDir);
|
|
1929
|
+
} catch (error) {
|
|
1930
|
+
operationError = error;
|
|
1931
|
+
targetInspection = {
|
|
1932
|
+
status: "missing",
|
|
1933
|
+
fallbackIdentity: identifyCodexAuthHomeLexically(sessionDir)
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
let metadataRead = { status: "missing" };
|
|
1937
|
+
if (targetInspection.status === "safe") try {
|
|
1938
|
+
metadataRead = await this.authStore.readLeaseMetadata(sessionDir);
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
operationError = mergeOperationErrors(operationError, error, "Codex native-auth lease inspection encountered multiple failures");
|
|
1941
|
+
}
|
|
1942
|
+
else if (targetInspection.status === "unsafe") operationError = mergeOperationErrors(operationError, /* @__PURE__ */ new Error("Codex native-auth lease target is unsafe; write-back was skipped"), "Codex native-auth lease inspection encountered multiple failures");
|
|
1943
|
+
return {
|
|
1944
|
+
metadataRead,
|
|
1945
|
+
operationError,
|
|
1946
|
+
targetInspection
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* Validate one inspected lease generation and reconcile a refreshed target.
|
|
1951
|
+
* @param sessionDir - Original isolated CODEX_HOME path.
|
|
1952
|
+
* @param inspection - Verified target and metadata inspection result.
|
|
1953
|
+
* @param activeMetadata - In-memory setup generation, when this process created the lease.
|
|
1954
|
+
*/
|
|
1955
|
+
async reconcileInspectedLease(sessionDir, inspection, activeMetadata) {
|
|
1956
|
+
if (inspection.metadataRead.status === "invalid") throw new Error("Codex native-auth lease metadata is invalid; write-back was skipped");
|
|
1957
|
+
if (inspection.metadataRead.status === "found") {
|
|
1958
|
+
const inspectedIdentity = inspection.targetInspection.status === "safe" ? inspection.targetInspection.identity : inspection.targetInspection.fallbackIdentity;
|
|
1959
|
+
const expectedTargetIdentity = activeMetadata?.targetIdentity ?? inspectedIdentity;
|
|
1960
|
+
const diskMetadata = inspection.metadataRead.metadata;
|
|
1961
|
+
if (!identitiesEqual(diskMetadata.targetIdentity, expectedTargetIdentity)) throw new Error("Codex native-auth lease target identity is invalid; write-back was skipped");
|
|
1962
|
+
if (activeMetadata !== void 0 && !metadataEqual(diskMetadata, activeMetadata)) throw new Error("Codex native-auth lease generation metadata changed; write-back was skipped");
|
|
1963
|
+
const currentTarget = await inspectCodexLeaseTarget(sessionDir);
|
|
1964
|
+
if (currentTarget.status !== "safe" || !identitiesEqual(currentTarget.identity, expectedTargetIdentity)) throw new Error("Codex native-auth lease target changed during teardown; write-back was skipped");
|
|
1965
|
+
await this.reconcileRefresh(diskMetadata);
|
|
1966
|
+
} else if (activeMetadata?.backend.effective !== void 0 && activeMetadata.backend.effective !== "none") throw new Error("Codex native-auth lease metadata is missing; write-back was skipped");
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Resolve profile source or the native CODEX_HOME fallback.
|
|
1970
|
+
* @param payload - Setup payload carrying source and target paths.
|
|
1971
|
+
* @returns Canonical source candidate selected for this lease.
|
|
1972
|
+
*/
|
|
1973
|
+
resolveSourceConfigDir(payload) {
|
|
1974
|
+
return path$1.resolve(payload.sessionDir) === path$1.resolve(payload.baseConfigDir) ? this.nativeConfigDir : payload.baseConfigDir;
|
|
1975
|
+
}
|
|
1976
|
+
/**
|
|
1977
|
+
* Materialize general config independently from native authentication.
|
|
1978
|
+
* @param sourceConfigDir - Resolved source CODEX_HOME.
|
|
1979
|
+
* @param sessionDir - Isolated target CODEX_HOME.
|
|
1980
|
+
* @param inheritance - Requested config inheritance policy.
|
|
1981
|
+
*/
|
|
1982
|
+
async materializeGeneralConfig(sourceConfigDir, sessionDir, inheritance) {
|
|
1983
|
+
const target = path$1.join(sessionDir, "config.toml");
|
|
1984
|
+
await fs$1.rm(target, { force: true });
|
|
1985
|
+
if (inheritance !== "full") return;
|
|
1986
|
+
try {
|
|
1987
|
+
await fs$1.copyFile(path$1.join(sourceConfigDir, "config.toml"), target);
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
if (error.code !== "ENOENT") throw error;
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Resolve and clone native auth while the caller owns the canonical source lock.
|
|
1994
|
+
* @param canonicalIdentity - Canonical auth source pinned during setup.
|
|
1995
|
+
* @param targetIdentity - Isolated target store identity.
|
|
1996
|
+
* @param sessionDir - Isolated target CODEX_HOME.
|
|
1997
|
+
* @returns Materialization status and configured storage mode.
|
|
1998
|
+
*/
|
|
1999
|
+
async materializeNativeAuthLocked(canonicalIdentity, targetIdentity, sessionDir) {
|
|
2000
|
+
const mode = await this.authStore.resolveMode(canonicalIdentity);
|
|
2001
|
+
const sourceRead = await this.authStore.readEffective(canonicalIdentity, mode);
|
|
2002
|
+
const sourceCredential = sourceRead.credential;
|
|
2003
|
+
if (sourceCredential === null) {
|
|
2004
|
+
await this.authStore.deleteFile(targetIdentity);
|
|
2005
|
+
if (mode === "keyring" || mode === "auto" && !sourceRead.keyringUnavailable) await this.authStore.deleteKeyring(targetIdentity);
|
|
2006
|
+
await this.recordMetadata(sessionDir, {
|
|
2007
|
+
version: 1,
|
|
2008
|
+
backend: {
|
|
2009
|
+
configured: mode,
|
|
2010
|
+
effective: "none"
|
|
2011
|
+
},
|
|
2012
|
+
canonicalIdentity,
|
|
2013
|
+
targetIdentity,
|
|
2014
|
+
sourceGenerationDigest: null,
|
|
2015
|
+
initialTargetDigest: null
|
|
2016
|
+
});
|
|
2017
|
+
return {
|
|
2018
|
+
materialized: false,
|
|
2019
|
+
mode
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
const digest = digestCodexCredential(sourceCredential.value);
|
|
2023
|
+
const metadata = {
|
|
2024
|
+
version: 1,
|
|
2025
|
+
backend: {
|
|
2026
|
+
configured: mode,
|
|
2027
|
+
effective: sourceCredential.backend
|
|
2028
|
+
},
|
|
2029
|
+
canonicalIdentity,
|
|
2030
|
+
targetIdentity,
|
|
2031
|
+
sourceGenerationDigest: digest,
|
|
2032
|
+
initialTargetDigest: digest
|
|
2033
|
+
};
|
|
2034
|
+
await this.recordMetadata(sessionDir, metadata);
|
|
2035
|
+
if (sourceCredential.backend === "file" && mode === "auto" && !sourceRead.keyringUnavailable) await this.authStore.deleteKeyring(targetIdentity);
|
|
2036
|
+
await this.authStore.writeBackend(targetIdentity, sourceCredential.backend, sourceCredential.value);
|
|
2037
|
+
const targetValue = await this.authStore.readBackend(targetIdentity, sourceCredential.backend);
|
|
2038
|
+
if (targetValue === null || digestCodexCredential(targetValue) !== digest) throw new Error("Codex native-auth target generation verification failed");
|
|
2039
|
+
return {
|
|
2040
|
+
materialized: true,
|
|
2041
|
+
mode
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
/**
|
|
2045
|
+
* Atomically record metadata on disk and retain it for missing-directory teardown.
|
|
2046
|
+
* @param sessionDir - Isolated target CODEX_HOME.
|
|
2047
|
+
* @param metadata - Secret-free lease generation metadata.
|
|
2048
|
+
*/
|
|
2049
|
+
async recordMetadata(sessionDir, metadata) {
|
|
2050
|
+
await this.authStore.writeLeaseMetadata(sessionDir, metadata);
|
|
2051
|
+
this.activeMetadata.set(path$1.resolve(sessionDir), metadata);
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Remove only resources materialized before a setup failure.
|
|
2055
|
+
* @param sessionDir - Isolated target CODEX_HOME.
|
|
2056
|
+
*/
|
|
2057
|
+
async rollbackFailedSetup(sessionDir) {
|
|
2058
|
+
const leaseKey = path$1.resolve(sessionDir);
|
|
2059
|
+
const metadata = this.activeMetadata.get(leaseKey);
|
|
2060
|
+
if (metadata === void 0) return;
|
|
2061
|
+
try {
|
|
2062
|
+
const cleanupErrors = await this.collectTargetCleanupErrors(sessionDir, metadata.targetIdentity);
|
|
2063
|
+
if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, "Codex native-auth setup rollback failed");
|
|
2064
|
+
} finally {
|
|
2065
|
+
this.activeMetadata.delete(leaseKey);
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Attempt keyring cleanup unconditionally and file cleanup only for the
|
|
2070
|
+
* still-verified lease directory.
|
|
2071
|
+
* @param sessionDir - Original isolated CODEX_HOME path.
|
|
2072
|
+
* @param trustedIdentity - Target identity captured before materialization.
|
|
2073
|
+
* @returns Every sanitized inspection or cleanup failure.
|
|
2074
|
+
*/
|
|
2075
|
+
async collectTargetCleanupErrors(sessionDir, trustedIdentity) {
|
|
2076
|
+
const errors = [];
|
|
2077
|
+
let inspection;
|
|
2078
|
+
try {
|
|
2079
|
+
inspection = await inspectCodexLeaseTarget(sessionDir);
|
|
2080
|
+
} catch (error) {
|
|
2081
|
+
errors.push(error);
|
|
2082
|
+
}
|
|
2083
|
+
const cleanupOperations = [() => this.authStore.deleteKeyring(trustedIdentity)];
|
|
2084
|
+
if (inspection?.status === "safe" && identitiesEqual(inspection.identity, trustedIdentity)) cleanupOperations.push(() => this.authStore.deleteFile(trustedIdentity));
|
|
2085
|
+
else if (inspection?.status === "unsafe" || inspection?.status === "safe" && !identitiesEqual(inspection.identity, trustedIdentity)) errors.push(/* @__PURE__ */ new Error("Codex native-auth lease target became unsafe; file cleanup was skipped"));
|
|
2086
|
+
const cleanupResults = await Promise.allSettled(cleanupOperations.map(async (operation) => operation()));
|
|
2087
|
+
errors.push(...cleanupResults.flatMap((result) => result.status === "rejected" ? [result.reason] : []));
|
|
2088
|
+
return errors;
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Reconcile one changed target under the canonical source's cross-process lock.
|
|
2092
|
+
* @param metadata - Validated on-disk lease generation.
|
|
2093
|
+
*/
|
|
2094
|
+
async reconcileRefresh(metadata) {
|
|
2095
|
+
const canonicalIdentity = metadata.canonicalIdentity;
|
|
2096
|
+
const mode = metadata.backend.configured;
|
|
2097
|
+
const sourceGenerationDigest = metadata.sourceGenerationDigest;
|
|
2098
|
+
const initialTargetDigest = metadata.initialTargetDigest;
|
|
2099
|
+
if (mode === "none" || metadata.backend.effective === "none" || canonicalIdentity === null || sourceGenerationDigest === null || initialTargetDigest === null) return;
|
|
2100
|
+
const targetCredential = (await this.authStore.readRefreshCandidate(metadata.targetIdentity, mode, initialTargetDigest)).credential;
|
|
2101
|
+
if (targetCredential === null) return;
|
|
2102
|
+
if (digestCodexCredential(targetCredential.value) === initialTargetDigest) return;
|
|
2103
|
+
await this.authStore.withSourceLock(canonicalIdentity, async () => {
|
|
2104
|
+
const currentMode = await this.authStore.resolveMode(canonicalIdentity);
|
|
2105
|
+
if (currentMode !== mode) return;
|
|
2106
|
+
const canonicalCredential = (await this.authStore.readEffective(canonicalIdentity, currentMode)).credential;
|
|
2107
|
+
if (canonicalCredential !== null && canonicalCredential.backend === metadata.backend.effective && digestCodexCredential(canonicalCredential.value) === sourceGenerationDigest) await this.authStore.saveConfigured(canonicalIdentity, mode, targetCredential.value);
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
/**
|
|
2112
|
+
* Compare canonical identities without trusting metadata aliases.
|
|
2113
|
+
* @param left - First canonical identity.
|
|
2114
|
+
* @param right - Second canonical identity.
|
|
2115
|
+
* @returns Whether path and derived keyring account both match.
|
|
2116
|
+
*/
|
|
2117
|
+
function identitiesEqual(left, right) {
|
|
2118
|
+
return left.canonicalPath === right.canonicalPath && left.keyringAccount === right.keyringAccount;
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Compare the complete trusted lease generation with its on-disk marker.
|
|
2122
|
+
* @param left - First strict metadata generation.
|
|
2123
|
+
* @param right - Second strict metadata generation.
|
|
2124
|
+
* @returns Whether every source, target, backend, and digest field matches.
|
|
2125
|
+
*/
|
|
2126
|
+
function metadataEqual(left, right) {
|
|
2127
|
+
return left.version === right.version && left.backend.configured === right.backend.configured && left.backend.effective === right.backend.effective && (left.canonicalIdentity === null && right.canonicalIdentity === null || left.canonicalIdentity !== null && right.canonicalIdentity !== null && identitiesEqual(left.canonicalIdentity, right.canonicalIdentity)) && identitiesEqual(left.targetIdentity, right.targetIdentity) && left.sourceGenerationDigest === right.sourceGenerationDigest && left.initialTargetDigest === right.initialTargetDigest;
|
|
2128
|
+
}
|
|
2129
|
+
/**
|
|
2130
|
+
* Create the explicit no-auth marker written before setup begins.
|
|
2131
|
+
* @param targetIdentity - Isolated target CODEX_HOME identity.
|
|
2132
|
+
* @returns Initial secret-free lease metadata.
|
|
2133
|
+
*/
|
|
2134
|
+
function createEmptyMetadata(targetIdentity) {
|
|
2135
|
+
return {
|
|
2136
|
+
version: 1,
|
|
2137
|
+
backend: {
|
|
2138
|
+
configured: "none",
|
|
2139
|
+
effective: "none"
|
|
2140
|
+
},
|
|
2141
|
+
canonicalIdentity: null,
|
|
2142
|
+
targetIdentity,
|
|
2143
|
+
sourceGenerationDigest: null,
|
|
2144
|
+
initialTargetDigest: null
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Preserve every failure observed across write-back and mandatory cleanup.
|
|
2149
|
+
* @param existing - Earlier failure, when present.
|
|
2150
|
+
* @param next - Newly observed failure.
|
|
2151
|
+
* @param message - Safe aggregate description.
|
|
2152
|
+
* @returns The single failure or an aggregate retaining both.
|
|
2153
|
+
*/
|
|
2154
|
+
function mergeOperationErrors(existing, next, message) {
|
|
2155
|
+
return existing === void 0 ? next : new AggregateError([existing, next], message);
|
|
1171
2156
|
}
|
|
1172
2157
|
|
|
1173
2158
|
//#endregion
|
|
@@ -1312,7 +2297,7 @@ const MANAGED_SESSION_CAP = 1e4;
|
|
|
1312
2297
|
* `client.runtime.started` for the adapter-managed session gate, and
|
|
1313
2298
|
* registers request handlers for `config.hooks.list`, `config.hooks.add`,
|
|
1314
2299
|
* `config.hooks.remove`, `config.prime`, `wiring.list`, `wiring.apply`,
|
|
1315
|
-
* `wiring.remove`, and `sessionConfig.
|
|
2300
|
+
* `wiring.remove`, `sessionConfig.setup`, and `sessionConfig.destroy`.
|
|
1316
2301
|
* 2. On each incoming raw event, calls {@link normalizeCodexHook}.
|
|
1317
2302
|
* 3. Emits the normalized subject when the event is recognized; silently
|
|
1318
2303
|
* ignores unknown events. Normalized `client.session.*` events are
|
|
@@ -1326,6 +2311,15 @@ var CodexClientSessionService = class extends BaseService {
|
|
|
1326
2311
|
/** Cached active config-dir resolution; reset when the active Codex version changes. */
|
|
1327
2312
|
cachedConfigDir;
|
|
1328
2313
|
/**
|
|
2314
|
+
* Stable runtime identity of the machine that owns the client sessions
|
|
2315
|
+
* observed by this service. Stamped onto `client.session.started` payloads
|
|
2316
|
+
* so downstream storage receives the owning machine's identity without
|
|
2317
|
+
* deriving it from the writer process.
|
|
2318
|
+
*/
|
|
2319
|
+
machineId;
|
|
2320
|
+
/** Client-owned native auth materialization and refresh reconciliation. */
|
|
2321
|
+
sessionConfigHandler;
|
|
2322
|
+
/**
|
|
1329
2323
|
* Set of `adapterSessionId` values known to be owned by an adapter-managed
|
|
1330
2324
|
* Codex runtime. Populated by {@link handleRuntimeStarted} when a
|
|
1331
2325
|
* `client.runtime.started` event arrives with `clientId === CLIENT_ID` and
|
|
@@ -1344,15 +2338,21 @@ var CodexClientSessionService = class extends BaseService {
|
|
|
1344
2338
|
* @param settings - Optional {@link CodexClientSettings} instance for tests
|
|
1345
2339
|
* that need exact filesystem paths. Production callers should omit it so
|
|
1346
2340
|
* the service can resolve the active managed config dir via the bus.
|
|
2341
|
+
* @param machineId - Stable runtime identity of the observing machine,
|
|
2342
|
+
* caller-supplied from the extension context. Omit in tests or when the
|
|
2343
|
+
* identity is unavailable.
|
|
2344
|
+
* @param sessionConfigHandler - Optional native auth lease handler override.
|
|
1347
2345
|
*/
|
|
1348
|
-
constructor(bus = MakaioBus, settings) {
|
|
2346
|
+
constructor(bus = MakaioBus, settings, machineId, sessionConfigHandler = new CodexSessionConfigHandler()) {
|
|
1349
2347
|
super(bus);
|
|
1350
2348
|
this.settingsOverride = settings;
|
|
2349
|
+
this.machineId = machineId;
|
|
2350
|
+
this.sessionConfigHandler = sessionConfigHandler;
|
|
1351
2351
|
}
|
|
1352
2352
|
/**
|
|
1353
2353
|
* Register the raw hook ingress handler, config management request handlers,
|
|
1354
2354
|
* wiring management request handlers, the config-prime lifecycle handler,
|
|
1355
|
-
* and the session config setup
|
|
2355
|
+
* and the session config setup/teardown handlers on the bus.
|
|
1356
2356
|
*
|
|
1357
2357
|
* Also subscribes to `client.runtime.started` to track adapter-managed
|
|
1358
2358
|
* sessions for the {@link handleHookReceived} suppression gate.
|
|
@@ -1394,7 +2394,10 @@ var CodexClientSessionService = class extends BaseService {
|
|
|
1394
2394
|
ctx.setResult(await handleCodexConfigPrime(ctx.payload));
|
|
1395
2395
|
});
|
|
1396
2396
|
this.registerHandler(CodexClientSubjects.sessionConfig.setup, async (ctx) => {
|
|
1397
|
-
ctx.setResult(await
|
|
2397
|
+
ctx.setResult(await this.sessionConfigHandler.setup(ctx.payload));
|
|
2398
|
+
});
|
|
2399
|
+
this.registerHandler(CodexClientSubjects.sessionConfig.destroy, async (ctx) => {
|
|
2400
|
+
ctx.setResult(await this.sessionConfigHandler.teardown(ctx.payload));
|
|
1398
2401
|
});
|
|
1399
2402
|
}
|
|
1400
2403
|
/**
|
|
@@ -1485,7 +2488,7 @@ var CodexClientSessionService = class extends BaseService {
|
|
|
1485
2488
|
* @param raw - Raw hook payload delivered on `client:codex.hook.received`
|
|
1486
2489
|
*/
|
|
1487
2490
|
async handleHookReceived(raw) {
|
|
1488
|
-
const normalized = normalizeCodexHook(raw);
|
|
2491
|
+
const normalized = normalizeCodexHook(raw, this.machineId);
|
|
1489
2492
|
if (normalized === null) return;
|
|
1490
2493
|
if (this.isAdapterManagedSession(normalized.payload.adapterSessionId)) return;
|
|
1491
2494
|
switch (normalized.subject) {
|
|
@@ -1547,4 +2550,4 @@ function isResolveBinaryMissingGlobalBinary(error) {
|
|
|
1547
2550
|
}
|
|
1548
2551
|
|
|
1549
2552
|
//#endregion
|
|
1550
|
-
export {
|
|
2553
|
+
export { clientDefinition as C, CodexScopeSchema as S, CodexHookEntrySchema as _, CODEX_CLIENT_NAMESPACE as a, CodexNativeHooksFileSchema as b, CodexWiringSchemas as c, CodexConfigHooksAddResponseSchema as d, CodexConfigHooksListRequestSchema as f, CodexConfigSchemas as g, CodexConfigHooksRemoveResponseSchema as h, withCodexNativeAuthSourceLock as i, AbsolutePathSchema as l, CodexConfigHooksRemoveRequestSchema as m, buildCodexNativeAuthSourceLockPath as n, CodexClientSubjects as o, CodexConfigHooksListResponseSchema as p, executeCodexNativeAuthSourceLock as r, normalizeCodexHook as s, CodexClientSessionService as t, CodexConfigHooksAddRequestSchema as u, CodexNativeCommandHookSchema as v, CodexScopeHookRecordSchema as x, CodexNativeHookMatcherGroupSchema as y };
|