@tokensrc/codex 1.14.3 → 1.14.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/dist/client.d.ts +6 -1
- package/dist/client.js +44 -5
- package/dist/client.js.map +1 -1
- package/dist/command-directory.js +2 -2
- package/dist/command-directory.js.map +1 -1
- package/dist/commands.js +181 -255
- package/dist/commands.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/managed-credential-v3-assignment.d.ts +38 -0
- package/dist/managed-credential-v3-assignment.js +68 -0
- package/dist/managed-credential-v3-assignment.js.map +1 -0
- package/dist/managed-credential-v3-envelope.d.ts +18 -1
- package/dist/managed-credential-v3-envelope.js +38 -5
- package/dist/managed-credential-v3-envelope.js.map +1 -1
- package/dist/managed-credential-v3-flow.d.ts +9 -0
- package/dist/managed-credential-v3-flow.js +56 -4
- package/dist/managed-credential-v3-flow.js.map +1 -1
- package/dist/managed-credential-v3-journal.d.ts +17 -0
- package/dist/managed-credential-v3-journal.js +43 -0
- package/dist/managed-credential-v3-journal.js.map +1 -1
- package/dist/profile.d.ts +2 -0
- package/dist/profile.js +31 -5
- package/dist/profile.js.map +1 -1
- package/dist/protocol.d.ts +165 -13
- package/dist/protocol.js +117 -13
- package/dist/protocol.js.map +1 -1
- package/dist/selection.d.ts +1 -1
- package/dist/selection.js +3 -3
- package/dist/selection.js.map +1 -1
- package/dist/token-pool-v4-upload-flow.d.ts +29 -0
- package/dist/token-pool-v4-upload-flow.js +162 -0
- package/dist/token-pool-v4-upload-flow.js.map +1 -0
- package/dist/token-pool-v4-upload-journal.d.ts +36 -0
- package/dist/token-pool-v4-upload-journal.js +76 -0
- package/dist/token-pool-v4-upload-journal.js.map +1 -0
- package/package.json +2 -2
package/dist/commands.js
CHANGED
|
@@ -12,9 +12,11 @@ import { cleanupLegacyCodexProvider } from "./legacy-cleanup.js";
|
|
|
12
12
|
import { executeCodexEnrollmentLogin } from "./enrollment-flow.js";
|
|
13
13
|
import { executeCodexMigrationLoginBatch } from "./migration-login-batch.js";
|
|
14
14
|
import { executeCodexShortLivedCredentialDelivery } from "./short-lived-delivery-flow.js";
|
|
15
|
-
import {
|
|
15
|
+
import { executeCodexTokenPoolV4Delivery, reportManagedCodexV3Credentials } from "./managed-credential-v3-flow.js";
|
|
16
|
+
import { resolveManagedCodexV3AssignmentTarget } from "./managed-credential-v3-assignment.js";
|
|
17
|
+
import { executeCodexTokenPoolV4Login } from "./token-pool-v4-upload-flow.js";
|
|
16
18
|
import { readManagedCodexV3State } from "./managed-credential-v3.js";
|
|
17
|
-
import {
|
|
19
|
+
import { profileAccountTokenPoolV4Enabled } from "./profile.js";
|
|
18
20
|
import { executeCodexAssignmentRetirementLocalDelete } from "./retirement-flow.js";
|
|
19
21
|
import { clearCodexSelection, loadCodexSelection, rememberCodexSelectionProviderAccount, saveCodexSelection } from "./selection.js";
|
|
20
22
|
import { installCodexShortcuts, removeCodexShortcuts } from "./shortcuts.js";
|
|
@@ -33,43 +35,6 @@ function findV3Assignment(input) {
|
|
|
33
35
|
&& item.providerAccountId === input.providerAccountId
|
|
34
36
|
&& item.lineageId === input.lineageId);
|
|
35
37
|
}
|
|
36
|
-
async function saveManagedV3Selection(input) {
|
|
37
|
-
await saveCodexSelection(input.deps.config.persistence.configDirectory, {
|
|
38
|
-
serviceUrl: input.options.server ?? input.deps.config.connection.serverUrl,
|
|
39
|
-
allowInsecurePoc: Boolean(input.options.insecurePoc || input.options.insecureHttp
|
|
40
|
-
|| input.deps.config.connection.allowInsecureHttp),
|
|
41
|
-
account: {
|
|
42
|
-
id: input.managedAccountId,
|
|
43
|
-
accountId: input.providerAccountId,
|
|
44
|
-
label: input.label,
|
|
45
|
-
distributionMode: "CLIENT_MANAGED_V3",
|
|
46
|
-
assignmentId: input.assignmentId,
|
|
47
|
-
managedAccountId: input.managedAccountId,
|
|
48
|
-
providerAccountId: input.providerAccountId
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
async function recoverManagedV3Selection(deps, options) {
|
|
53
|
-
const state = await readManagedCodexV3State(deps.env);
|
|
54
|
-
if (!state || state.phase !== "INSTALLED" || state.authorityMode !== "CLIENT_MANAGED")
|
|
55
|
-
return undefined;
|
|
56
|
-
const current = await loadCodexSelection(deps.config.persistence.configDirectory);
|
|
57
|
-
if (current?.account.distributionMode === "CLIENT_MANAGED_V3"
|
|
58
|
-
&& current.account.assignmentId === state.assignmentId
|
|
59
|
-
&& current.account.managedAccountId === state.managedAccountId
|
|
60
|
-
&& current.account.providerAccountId === state.providerAccountId)
|
|
61
|
-
return undefined;
|
|
62
|
-
const assignments = await deps.clientFor(serviceConnection(options)).listV3CredentialAssignments();
|
|
63
|
-
const assignment = findV3Assignment({ assignments, ...state });
|
|
64
|
-
if (!assignment || assignment.authorityMode !== "CLIENT_MANAGED") {
|
|
65
|
-
throw new CodexServiceError("Installed v3 credentials have no matching server assignment.", "lineage_version_conflict", 409);
|
|
66
|
-
}
|
|
67
|
-
await saveManagedV3Selection({
|
|
68
|
-
deps, options, assignmentId: state.assignmentId, managedAccountId: state.managedAccountId,
|
|
69
|
-
providerAccountId: state.providerAccountId, label: assignment.label
|
|
70
|
-
});
|
|
71
|
-
return state;
|
|
72
|
-
}
|
|
73
38
|
export function effectiveCodexCredentialRefreshBefore(requestedMs, serverManagedShortLived) {
|
|
74
39
|
return serverManagedShortLived
|
|
75
40
|
? Math.min(requestedMs, SERVER_MANAGED_SHORT_LIVED_REFRESH_WINDOW_MS)
|
|
@@ -97,8 +62,8 @@ function schemaV2LifecycleEnabled(config) {
|
|
|
97
62
|
const capabilities = config.profile?.userBoundRefreshTokens;
|
|
98
63
|
return capabilities?.delivery === true || capabilities?.migrationPlans === true;
|
|
99
64
|
}
|
|
100
|
-
function
|
|
101
|
-
return
|
|
65
|
+
function accountTokenPoolV4Enabled(config) {
|
|
66
|
+
return profileAccountTokenPoolV4Enabled(config.profile);
|
|
102
67
|
}
|
|
103
68
|
function codexConfigPathForEnv(env) {
|
|
104
69
|
const codexHome = env.CODEX_HOME?.trim();
|
|
@@ -149,7 +114,7 @@ function formatAdminAccount(account) {
|
|
|
149
114
|
}
|
|
150
115
|
async function loadAccountData(deps, options = {}) {
|
|
151
116
|
const previous = await loadCodexSelection(deps.config.persistence.configDirectory);
|
|
152
|
-
if (
|
|
117
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
153
118
|
const v3Assignments = await deps.clientFor(serviceConnection(options)).listV3CredentialAssignments();
|
|
154
119
|
const accounts = v3Assignments.items.map((assignment) => ({
|
|
155
120
|
id: assignment.managedAccountId,
|
|
@@ -232,7 +197,7 @@ async function chooseAccount(deps, requested, options = {}) {
|
|
|
232
197
|
id: v3Assignment.managedAccountId,
|
|
233
198
|
accountId: v3Assignment.providerAccountId,
|
|
234
199
|
label: accountLabel,
|
|
235
|
-
distributionMode: "
|
|
200
|
+
distributionMode: "CLIENT_MANAGED_V4",
|
|
236
201
|
assignmentId: v3Assignment.assignmentId,
|
|
237
202
|
managedAccountId: v3Assignment.managedAccountId,
|
|
238
203
|
providerAccountId: v3Assignment.providerAccountId
|
|
@@ -258,22 +223,36 @@ async function chooseAccount(deps, requested, options = {}) {
|
|
|
258
223
|
async function installCredentials(deps, selection, service) {
|
|
259
224
|
const client = deps.clientFor(service);
|
|
260
225
|
await cleanupLegacyCodexProvider(codexConfigPathForEnv(deps.env));
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|| selection.account.
|
|
265
|
-
|
|
266
|
-
|| selection.account.managedAccountId !== state.managedAccountId
|
|
267
|
-
|| selection.account.providerAccountId !== state.providerAccountId) {
|
|
268
|
-
throw new CodexServiceError("The selected v3 assignment does not match acknowledged local credentials; run `numa codex login` or `numa codex upload`.", "lineage_version_conflict", 409);
|
|
226
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
227
|
+
if (selection.account.distributionMode !== "CLIENT_MANAGED_V4"
|
|
228
|
+
|| !selection.account.assignmentId || !selection.account.managedAccountId
|
|
229
|
+
|| !selection.account.providerAccountId) {
|
|
230
|
+
throw new CodexServiceError("The selected account is not a v4 token-pool assignment.", "codex_assignment_not_found", 404);
|
|
269
231
|
}
|
|
270
232
|
const assignments = await client.listV3CredentialAssignments();
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
233
|
+
const assignment = assignments.items.find((item) => item.assignmentId === selection.account.assignmentId
|
|
234
|
+
&& item.managedAccountId === selection.account.managedAccountId
|
|
235
|
+
&& item.providerAccountId === selection.account.providerAccountId);
|
|
236
|
+
if (!assignment) {
|
|
237
|
+
throw new CodexServiceError("The v4 token-pool assignment is no longer active.", "codex_assignment_not_found", 404);
|
|
275
238
|
}
|
|
276
|
-
|
|
239
|
+
const state = await readManagedCodexV3State(deps.env);
|
|
240
|
+
if (state?.phase === "INSTALLED" && state.authorityMode === "CLIENT_MANAGED"
|
|
241
|
+
&& selection.account.assignmentId === state.assignmentId
|
|
242
|
+
&& selection.account.managedAccountId === state.managedAccountId
|
|
243
|
+
&& selection.account.providerAccountId === state.providerAccountId
|
|
244
|
+
&& assignment.lineageId === state.lineageId
|
|
245
|
+
&& assignment.currentGenerationVersion === state.generationVersion
|
|
246
|
+
&& assignment.authorityMode === "CLIENT_MANAGED") {
|
|
247
|
+
return { expiresAt: Date.parse(state.credentialExpiresAt), selection };
|
|
248
|
+
}
|
|
249
|
+
const delivered = await executeCodexTokenPoolV4Delivery({
|
|
250
|
+
client,
|
|
251
|
+
tenantId: deps.config.connection.tenantId,
|
|
252
|
+
assignment,
|
|
253
|
+
env: deps.env
|
|
254
|
+
});
|
|
255
|
+
return { expiresAt: Date.parse(delivered.credentialExpiresAt), selection };
|
|
277
256
|
}
|
|
278
257
|
if (deps.config.profile?.userBoundRefreshTokens?.delivery === true) {
|
|
279
258
|
await discardCodexFileCredentialBackup(deps.env);
|
|
@@ -318,10 +297,11 @@ async function installCredentials(deps, selection, service) {
|
|
|
318
297
|
return { expiresAt: response.expiresAt, selection };
|
|
319
298
|
}
|
|
320
299
|
async function selectedCredentials(deps, requested, options = {}) {
|
|
321
|
-
if (
|
|
300
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
322
301
|
const state = await readManagedCodexV3State(deps.env);
|
|
323
302
|
if (!state || state.phase !== "INSTALLED" || state.authorityMode !== "CLIENT_MANAGED") {
|
|
324
|
-
|
|
303
|
+
const selection = (await chooseAccount(deps, requested, options)).selection;
|
|
304
|
+
return installCredentials(deps, selection, serviceConnection(options));
|
|
325
305
|
}
|
|
326
306
|
if (requested && ![
|
|
327
307
|
state.assignmentId,
|
|
@@ -329,7 +309,8 @@ async function selectedCredentials(deps, requested, options = {}) {
|
|
|
329
309
|
state.providerAccountId,
|
|
330
310
|
state.lineageId
|
|
331
311
|
].includes(requested)) {
|
|
332
|
-
|
|
312
|
+
const selection = (await chooseAccount(deps, requested, options)).selection;
|
|
313
|
+
return installCredentials(deps, selection, serviceConnection(options));
|
|
333
314
|
}
|
|
334
315
|
const assignments = await deps.clientFor(serviceConnection(options)).listV3CredentialAssignments();
|
|
335
316
|
const assignment = findV3Assignment({ assignments, ...state });
|
|
@@ -338,7 +319,7 @@ async function selectedCredentials(deps, requested, options = {}) {
|
|
|
338
319
|
|| assignment.providerAccountId !== state.providerAccountId
|
|
339
320
|
|| assignment.lineageId !== state.lineageId
|
|
340
321
|
|| assignment.authorityMode !== "CLIENT_MANAGED") {
|
|
341
|
-
throw new CodexServiceError("The local
|
|
322
|
+
throw new CodexServiceError("The local v4 lineage no longer matches the server assignment.", "lineage_version_conflict", 409);
|
|
342
323
|
}
|
|
343
324
|
const selection = await saveCodexSelection(deps.config.persistence.configDirectory, {
|
|
344
325
|
serviceUrl: options.server ?? deps.config.connection.serverUrl,
|
|
@@ -347,7 +328,7 @@ async function selectedCredentials(deps, requested, options = {}) {
|
|
|
347
328
|
id: state.managedAccountId,
|
|
348
329
|
accountId: state.providerAccountId,
|
|
349
330
|
label: assignment.label,
|
|
350
|
-
distributionMode: "
|
|
331
|
+
distributionMode: "CLIENT_MANAGED_V4",
|
|
351
332
|
assignmentId: state.assignmentId,
|
|
352
333
|
managedAccountId: state.managedAccountId,
|
|
353
334
|
providerAccountId: state.providerAccountId
|
|
@@ -420,7 +401,7 @@ async function selectedCredentials(deps, requested, options = {}) {
|
|
|
420
401
|
return { selection, expiresAt: local.expiresAt };
|
|
421
402
|
}
|
|
422
403
|
async function launchWithSelectedCredentials(deps, selected, launch) {
|
|
423
|
-
if (
|
|
404
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
424
405
|
try {
|
|
425
406
|
return await launch();
|
|
426
407
|
}
|
|
@@ -479,57 +460,43 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
479
460
|
else
|
|
480
461
|
deps.output.write("Logged out.\n");
|
|
481
462
|
});
|
|
482
|
-
if (!includeAuthCommands &&
|
|
463
|
+
if (!includeAuthCommands && accountTokenPoolV4Enabled(deps.config))
|
|
483
464
|
serviceOptions(codex.command("login")
|
|
484
|
-
.description("Run an isolated official Codex login and
|
|
465
|
+
.description("Run an isolated official Codex login and enroll an unbound or explicitly assigned credential"))
|
|
485
466
|
.option("--device-auth", "use the official Codex device authorization flow")
|
|
467
|
+
.option("--assign-to <username-or-email>", "assign by exact username or email instead of adding to the unbound pool")
|
|
468
|
+
.option("--assign-to-subject <uuid>", "assign directly to a canonical Keycloak subject UUID")
|
|
469
|
+
.option("--yes", "confirm direct subject assignment")
|
|
486
470
|
.action(async (opts, command) => {
|
|
487
|
-
if (!
|
|
488
|
-
throw new CodexServiceError("
|
|
471
|
+
if (!accountTokenPoolV4Enabled(deps.config)) {
|
|
472
|
+
throw new CodexServiceError("The Codex v4 account token pool is not enabled by the verified platform profile.", "codex_token_pool_v4_not_enabled", 409);
|
|
489
473
|
}
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
const output = {
|
|
493
|
-
enrollmentId: recovered.enrollmentId,
|
|
494
|
-
assignmentId: recovered.assignmentId,
|
|
495
|
-
managedAccountId: recovered.managedAccountId,
|
|
496
|
-
providerAccountId: recovered.providerAccountId,
|
|
497
|
-
lineageId: recovered.lineageId,
|
|
498
|
-
generationVersion: recovered.generationVersion,
|
|
499
|
-
authorityMode: recovered.authorityMode,
|
|
500
|
-
credentialExpiresAt: recovered.credentialExpiresAt,
|
|
501
|
-
selectionRecovered: true
|
|
502
|
-
};
|
|
503
|
-
if (await json(command, deps))
|
|
504
|
-
print(deps.output, success(output));
|
|
505
|
-
else
|
|
506
|
-
deps.output.write(`Codex login was already installed; recovered its local selection; generation=${recovered.generationVersion}.\n`);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
const result = await executeManagedCodexV3Login({
|
|
474
|
+
const target = resolveManagedCodexV3AssignmentTarget(opts);
|
|
475
|
+
const result = await executeCodexTokenPoolV4Login({
|
|
510
476
|
client: deps.clientFor(serviceConnection(opts)),
|
|
511
477
|
tenantId: deps.config.connection.tenantId,
|
|
478
|
+
target,
|
|
512
479
|
env: deps.env,
|
|
513
480
|
deviceAuth: opts.deviceAuth
|
|
514
481
|
});
|
|
515
|
-
await saveManagedV3Selection({
|
|
516
|
-
deps, options: opts, assignmentId: result.assignmentId, managedAccountId: result.managedAccountId,
|
|
517
|
-
providerAccountId: result.providerAccountId, label: "Codex account"
|
|
518
|
-
});
|
|
519
482
|
const output = {
|
|
520
|
-
|
|
483
|
+
uploadId: result.uploadId,
|
|
484
|
+
status: result.status,
|
|
485
|
+
placement: result.placement,
|
|
521
486
|
assignmentId: result.assignmentId,
|
|
522
487
|
managedAccountId: result.managedAccountId,
|
|
523
488
|
providerAccountId: result.providerAccountId,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
489
|
+
poolEntryId: result.poolEntryId,
|
|
490
|
+
assignedUserSubject: result.assignedUserSubject,
|
|
491
|
+
reservedLineageId: result.reservedLineageId,
|
|
492
|
+
target: result.target,
|
|
493
|
+
completedAt: result.completedAt,
|
|
494
|
+
replayed: result.replayed
|
|
528
495
|
};
|
|
529
496
|
if (await json(command, deps))
|
|
530
497
|
print(deps.output, success(output));
|
|
531
498
|
else
|
|
532
|
-
deps.output.write(`Codex
|
|
499
|
+
deps.output.write(`Codex credential uploaded to token pool; status=${result.status}, placement=${result.placement}.\n`);
|
|
533
500
|
});
|
|
534
501
|
serviceOptions(codex.command("list").description("List available Codex accounts without downloading credentials"))
|
|
535
502
|
.action(async (opts, command) => {
|
|
@@ -664,7 +631,7 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
664
631
|
throw new Error("auth-agent replaces the current login; add --replace-login to confirm.");
|
|
665
632
|
if (opts.credentialStore !== "file")
|
|
666
633
|
throw new Error("ChatGPT auth.json 同步目前仅支持 --credential-store file。");
|
|
667
|
-
if (
|
|
634
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
668
635
|
const interval = Number(opts.interval) * 1_000;
|
|
669
636
|
if (!Number.isFinite(interval) || interval <= 0)
|
|
670
637
|
throw new Error("--interval must be positive.");
|
|
@@ -737,55 +704,12 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
737
704
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
738
705
|
} while (true);
|
|
739
706
|
});
|
|
740
|
-
serviceOptions(codex.command("upload").description("
|
|
707
|
+
serviceOptions(codex.command("upload").description("Upload the legacy v1 local account pool"))
|
|
741
708
|
.option("--storage <path>", "account storage path")
|
|
742
709
|
.option("--source <name>", "upload source")
|
|
743
710
|
.action(async (opts, command) => {
|
|
744
|
-
if (
|
|
745
|
-
|
|
746
|
-
throw new CodexServiceError("Managed credential v3 upload reads only the official CODEX_HOME/auth.json.", "invalid_configuration");
|
|
747
|
-
}
|
|
748
|
-
const recovered = await recoverManagedV3Selection(deps, opts);
|
|
749
|
-
if (recovered) {
|
|
750
|
-
if (await json(command, deps))
|
|
751
|
-
print(deps.output, success({
|
|
752
|
-
assignmentId: recovered.assignmentId,
|
|
753
|
-
managedAccountId: recovered.managedAccountId,
|
|
754
|
-
providerAccountId: recovered.providerAccountId,
|
|
755
|
-
lineageId: recovered.lineageId,
|
|
756
|
-
generationVersion: recovered.generationVersion,
|
|
757
|
-
authorityMode: recovered.authorityMode,
|
|
758
|
-
credentialExpiresAt: recovered.credentialExpiresAt,
|
|
759
|
-
selectionRecovered: true
|
|
760
|
-
}));
|
|
761
|
-
else
|
|
762
|
-
deps.output.write(`Official Codex credentials were already installed; recovered their local selection; generation=${recovered.generationVersion}.\n`);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
const result = await executeManagedCodexV3Upload({
|
|
766
|
-
client: deps.clientFor(serviceConnection(opts)),
|
|
767
|
-
tenantId: deps.config.connection.tenantId,
|
|
768
|
-
env: deps.env
|
|
769
|
-
});
|
|
770
|
-
await saveManagedV3Selection({
|
|
771
|
-
deps, options: opts, assignmentId: result.assignmentId, managedAccountId: result.managedAccountId,
|
|
772
|
-
providerAccountId: result.providerAccountId, label: "Codex account"
|
|
773
|
-
});
|
|
774
|
-
const output = {
|
|
775
|
-
enrollmentId: result.enrollmentId,
|
|
776
|
-
assignmentId: result.assignmentId,
|
|
777
|
-
managedAccountId: result.managedAccountId,
|
|
778
|
-
providerAccountId: result.providerAccountId,
|
|
779
|
-
lineageId: result.lineageId,
|
|
780
|
-
generationVersion: result.generationVersion,
|
|
781
|
-
authorityMode: result.authorityMode,
|
|
782
|
-
credentialExpiresAt: result.credentialExpiresAt
|
|
783
|
-
};
|
|
784
|
-
if (await json(command, deps))
|
|
785
|
-
print(deps.output, success(output));
|
|
786
|
-
else
|
|
787
|
-
deps.output.write(`Official Codex credentials enrolled, installed, and acknowledged; generation=${result.generationVersion}.\n`);
|
|
788
|
-
return;
|
|
711
|
+
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
712
|
+
throw new CodexServiceError("This tenant uses the v4 RT pool. Administrators must run `numa codex login`; v1 pool upload is compatibility-only.", "codex_token_pool_v4_login_required", 409);
|
|
789
713
|
}
|
|
790
714
|
assertLegacyManagementEnabled(deps.config);
|
|
791
715
|
if (opts.storage)
|
|
@@ -845,124 +769,126 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
845
769
|
if (failures.length > 0)
|
|
846
770
|
process.exitCode = 1;
|
|
847
771
|
});
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
serviceOptions(enrollment.command("login <intent>")
|
|
867
|
-
.description("Run one isolated official Codex login for an enrollment intent"))
|
|
868
|
-
.option("--device-auth", "use the official Codex device authorization flow")
|
|
869
|
-
.action(async (intent, _opts, command) => {
|
|
870
|
-
const result = await executeCodexEnrollmentLogin({
|
|
871
|
-
client: deps.clientFor(serviceConnection(_opts)),
|
|
872
|
-
tenantId: deps.config.connection.tenantId,
|
|
873
|
-
intentId: intent,
|
|
874
|
-
deviceAuth: _opts.deviceAuth
|
|
772
|
+
if (schemaV2LifecycleEnabled(deps.config)) {
|
|
773
|
+
const enrollment = codex.command("enrollment")
|
|
774
|
+
.description("Execute administrator-created one-shot Codex login intents");
|
|
775
|
+
serviceOptions(enrollment.command("status <intent>").description("Show redacted enrollment intent status"))
|
|
776
|
+
.action(async (intent, opts, command) => {
|
|
777
|
+
const result = await deps.clientFor(serviceConnection(opts)).getEnrollmentIntent(intent);
|
|
778
|
+
if (await json(command, deps)) {
|
|
779
|
+
print(deps.output, { ok: true, ...result });
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
deps.output.write(`${result.intent.id} ${result.intent.type} ${result.intent.status}`
|
|
783
|
+
+ ` revision=${result.intent.revision}\n`);
|
|
784
|
+
deps.output.write(`expires=${result.intent.expiresAt}`
|
|
785
|
+
+ `${result.intent.assignmentId ? ` assignment=${result.intent.assignmentId}` : ""}`
|
|
786
|
+
+ `${result.intent.managedAccountId ? ` account=${result.intent.managedAccountId}` : ""}\n`);
|
|
787
|
+
if (!result.capabilities.candidateUpload) {
|
|
788
|
+
deps.output.write("安全候选上传尚未启用;当前任务只能查询或取消,不会启动 OpenAI 登录。\n");
|
|
789
|
+
}
|
|
875
790
|
});
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
791
|
+
serviceOptions(enrollment.command("login <intent>")
|
|
792
|
+
.description("Run one isolated official Codex login for an enrollment intent"))
|
|
793
|
+
.option("--device-auth", "use the official Codex device authorization flow")
|
|
794
|
+
.action(async (intent, _opts, command) => {
|
|
795
|
+
const result = await executeCodexEnrollmentLogin({
|
|
796
|
+
client: deps.clientFor(serviceConnection(_opts)),
|
|
797
|
+
tenantId: deps.config.connection.tenantId,
|
|
798
|
+
intentId: intent,
|
|
799
|
+
deviceAuth: _opts.deviceAuth
|
|
800
|
+
});
|
|
801
|
+
if (await json(command, deps))
|
|
802
|
+
print(deps.output, { ok: true, ...result });
|
|
803
|
+
else {
|
|
804
|
+
deps.output.write(`${result.intent.id} 登录候选已处理;状态=${result.intent.status}`
|
|
805
|
+
+ `${result.result.managedAccountId ? `,账号=${result.result.managedAccountId}` : ""}`
|
|
806
|
+
+ `${result.result.lineageId ? `,lineage=${result.result.lineageId}` : ""}`
|
|
807
|
+
+ "。\n");
|
|
808
|
+
if (result.result.outcome === "TEMPORARILY_UNAVAILABLE") {
|
|
809
|
+
deps.output.write("同一进程内的安全重试已耗尽;请取消并新建任务,不能用另一次登录覆盖已保留的候选。\n");
|
|
810
|
+
}
|
|
885
811
|
}
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
serviceOptions(enrollment.command("login-batch <planId>")
|
|
889
|
-
.description("Sequentially execute isolated logins from a migration plan queue")
|
|
890
|
-
.option("--device-auth", "use the official Codex device authorization flow")
|
|
891
|
-
.option("--dry-run", "show the redacted sequential plan without starting login")
|
|
892
|
-
.option("--page-size <count>", "migration queue page size", "100"))
|
|
893
|
-
.action(async (planId, opts, command) => {
|
|
894
|
-
if (deps.config.profile?.userBoundRefreshTokens?.migrationPlans !== true) {
|
|
895
|
-
throw new CodexServiceError("The selected tenant has not enabled schema-v2 migration login plans.", "capability_not_enabled", 409);
|
|
896
|
-
}
|
|
897
|
-
const pageSize = Number(opts.pageSize);
|
|
898
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
899
|
-
throw new CodexServiceError("--page-size must be an integer from 1 to 100.", "invalid_configuration");
|
|
900
|
-
}
|
|
901
|
-
const batch = await executeCodexMigrationLoginBatch({
|
|
902
|
-
client: deps.clientFor(serviceConnection(opts)),
|
|
903
|
-
tenantId: deps.config.connection.tenantId,
|
|
904
|
-
planId,
|
|
905
|
-
deviceAuth: opts.deviceAuth,
|
|
906
|
-
dryRun: opts.dryRun,
|
|
907
|
-
pageSize
|
|
908
812
|
});
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
+ ` stop=${batch.stopReason}${batch.dryRun ? " dry-run" : ""}\n`);
|
|
918
|
-
deps.output.write(`capacity: verifiedMinimum=${batch.capacity.verifiedMinimum}, verifiedLimit=unknown,`
|
|
919
|
-
+ ` sequentialRequired=${batch.capacity.sequentialRequired}; verifiedMinimum 不是上限。\n`);
|
|
920
|
-
for (const item of batch.items) {
|
|
921
|
-
deps.output.write(`${item.assignmentId} ${item.action}/${item.status}`
|
|
922
|
-
+ `${item.intentId ? ` intent=${item.intentId}` : ""}`
|
|
923
|
-
+ `${item.outcome ? ` outcome=${item.outcome}` : ""}`
|
|
924
|
-
+ `${item.skipReason ? ` skip=${item.skipReason}` : ""}`
|
|
925
|
-
+ `${item.safeErrorCode ? ` error=${item.safeErrorCode}` : ""}\n`);
|
|
813
|
+
serviceOptions(enrollment.command("login-batch <planId>")
|
|
814
|
+
.description("Sequentially execute isolated logins from a migration plan queue")
|
|
815
|
+
.option("--device-auth", "use the official Codex device authorization flow")
|
|
816
|
+
.option("--dry-run", "show the redacted sequential plan without starting login")
|
|
817
|
+
.option("--page-size <count>", "migration queue page size", "100"))
|
|
818
|
+
.action(async (planId, opts, command) => {
|
|
819
|
+
if (deps.config.profile?.userBoundRefreshTokens?.migrationPlans !== true) {
|
|
820
|
+
throw new CodexServiceError("The selected tenant has not enabled schema-v2 migration login plans.", "capability_not_enabled", 409);
|
|
926
821
|
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
822
|
+
const pageSize = Number(opts.pageSize);
|
|
823
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
824
|
+
throw new CodexServiceError("--page-size must be an integer from 1 to 100.", "invalid_configuration");
|
|
825
|
+
}
|
|
826
|
+
const batch = await executeCodexMigrationLoginBatch({
|
|
827
|
+
client: deps.clientFor(serviceConnection(opts)),
|
|
828
|
+
tenantId: deps.config.connection.tenantId,
|
|
829
|
+
planId,
|
|
830
|
+
deviceAuth: opts.deviceAuth,
|
|
831
|
+
dryRun: opts.dryRun,
|
|
832
|
+
pageSize
|
|
833
|
+
});
|
|
834
|
+
const failed = batch.counts.failed > 0
|
|
835
|
+
|| ["TARGET_UNCERTAIN", "AUTHENTICATION_ERROR", "SERVICE_ERROR", "LOGIN_ERROR"]
|
|
836
|
+
.includes(batch.stopReason);
|
|
837
|
+
if (await json(command, deps)) {
|
|
838
|
+
print(deps.output, { ok: !failed, ...batch });
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
deps.output.write(`migration=${batch.plan.id} status=${batch.plan.status} revision=${batch.plan.revision}`
|
|
842
|
+
+ ` stop=${batch.stopReason}${batch.dryRun ? " dry-run" : ""}\n`);
|
|
843
|
+
deps.output.write(`capacity: verifiedMinimum=${batch.capacity.verifiedMinimum}, verifiedLimit=unknown,`
|
|
844
|
+
+ ` sequentialRequired=${batch.capacity.sequentialRequired}; verifiedMinimum 不是上限。\n`);
|
|
845
|
+
for (const item of batch.items) {
|
|
846
|
+
deps.output.write(`${item.assignmentId} ${item.action}/${item.status}`
|
|
847
|
+
+ `${item.intentId ? ` intent=${item.intentId}` : ""}`
|
|
848
|
+
+ `${item.outcome ? ` outcome=${item.outcome}` : ""}`
|
|
849
|
+
+ `${item.skipReason ? ` skip=${item.skipReason}` : ""}`
|
|
850
|
+
+ `${item.safeErrorCode ? ` error=${item.safeErrorCode}` : ""}\n`);
|
|
851
|
+
}
|
|
852
|
+
deps.output.write(`summary: attempted=${batch.counts.attempted}, completed=${batch.counts.completed},`
|
|
853
|
+
+ ` failed=${batch.counts.failed}, skipped=${batch.counts.skipped}.\n`);
|
|
854
|
+
}
|
|
855
|
+
if (failed)
|
|
856
|
+
process.exitCode = 1;
|
|
941
857
|
});
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
deps.
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
retirementId,
|
|
956
|
-
env: deps.env,
|
|
957
|
-
...(opts.idempotencyKey?.trim() ? { idempotencyKey: opts.idempotencyKey.trim() } : {})
|
|
858
|
+
serviceOptions(enrollment.command("cancel <intent>").description("Cancel an enrollment intent"))
|
|
859
|
+
.option("--idempotency-key <key>", "reuse a prior cancellation idempotency key")
|
|
860
|
+
.action(async (intent, opts, command) => {
|
|
861
|
+
const client = deps.clientFor(serviceConnection(opts));
|
|
862
|
+
const current = await client.getEnrollmentIntent(intent);
|
|
863
|
+
const result = await client.cancelEnrollmentIntent(intent, {
|
|
864
|
+
idempotencyKey: opts.idempotencyKey?.trim() || randomUUID(),
|
|
865
|
+
revision: current.intent.revision
|
|
866
|
+
});
|
|
867
|
+
if (await json(command, deps))
|
|
868
|
+
print(deps.output, { ok: true, ...result });
|
|
869
|
+
else
|
|
870
|
+
deps.output.write(`${result.intent.id} 已取消;状态=${result.intent.status}。\n`);
|
|
958
871
|
});
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
872
|
+
const retirement = codex.command("retirement")
|
|
873
|
+
.description("Remove an exact locally installed lineage for a Server retirement request");
|
|
874
|
+
serviceOptions(retirement.command("acknowledge <retirement>")
|
|
875
|
+
.description("Delete the matching managed short-lived credentials and append local evidence")
|
|
876
|
+
.option("--idempotency-key <key>", "reuse a prior local deletion acknowledgement key"))
|
|
877
|
+
.action(async (retirementId, opts, command) => {
|
|
878
|
+
const result = await executeCodexAssignmentRetirementLocalDelete({
|
|
879
|
+
client: deps.clientFor(serviceConnection(opts)),
|
|
880
|
+
retirementId,
|
|
881
|
+
env: deps.env,
|
|
882
|
+
...(opts.idempotencyKey?.trim() ? { idempotencyKey: opts.idempotencyKey.trim() } : {})
|
|
883
|
+
});
|
|
884
|
+
if (await json(command, deps))
|
|
885
|
+
print(deps.output, { ok: true, ...result });
|
|
886
|
+
else
|
|
887
|
+
deps.output.write(`本机匹配的短效凭据已删除并记录证据;retirement=${result.retirement.id}`
|
|
888
|
+
+ `,revision=${result.retirement.revision},状态=${result.retirement.status}。\n`
|
|
889
|
+
+ "该证据不代表其他终端已清理;最终退役仍等待全部已披露短效凭据自然过期。\n");
|
|
890
|
+
});
|
|
891
|
+
}
|
|
966
892
|
const admin = codex.command("admin")
|
|
967
893
|
.description("Administer server-managed Codex accounts and assignments");
|
|
968
894
|
const adminAccounts = admin.command("accounts")
|