@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.
Files changed (38) hide show
  1. package/dist/client.d.ts +6 -1
  2. package/dist/client.js +44 -5
  3. package/dist/client.js.map +1 -1
  4. package/dist/command-directory.js +2 -2
  5. package/dist/command-directory.js.map +1 -1
  6. package/dist/commands.js +181 -255
  7. package/dist/commands.js.map +1 -1
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/managed-credential-v3-assignment.d.ts +38 -0
  12. package/dist/managed-credential-v3-assignment.js +68 -0
  13. package/dist/managed-credential-v3-assignment.js.map +1 -0
  14. package/dist/managed-credential-v3-envelope.d.ts +18 -1
  15. package/dist/managed-credential-v3-envelope.js +38 -5
  16. package/dist/managed-credential-v3-envelope.js.map +1 -1
  17. package/dist/managed-credential-v3-flow.d.ts +9 -0
  18. package/dist/managed-credential-v3-flow.js +56 -4
  19. package/dist/managed-credential-v3-flow.js.map +1 -1
  20. package/dist/managed-credential-v3-journal.d.ts +17 -0
  21. package/dist/managed-credential-v3-journal.js +43 -0
  22. package/dist/managed-credential-v3-journal.js.map +1 -1
  23. package/dist/profile.d.ts +2 -0
  24. package/dist/profile.js +31 -5
  25. package/dist/profile.js.map +1 -1
  26. package/dist/protocol.d.ts +165 -13
  27. package/dist/protocol.js +117 -13
  28. package/dist/protocol.js.map +1 -1
  29. package/dist/selection.d.ts +1 -1
  30. package/dist/selection.js +3 -3
  31. package/dist/selection.js.map +1 -1
  32. package/dist/token-pool-v4-upload-flow.d.ts +29 -0
  33. package/dist/token-pool-v4-upload-flow.js +162 -0
  34. package/dist/token-pool-v4-upload-flow.js.map +1 -0
  35. package/dist/token-pool-v4-upload-journal.d.ts +36 -0
  36. package/dist/token-pool-v4-upload-journal.js +76 -0
  37. package/dist/token-pool-v4-upload-journal.js.map +1 -0
  38. 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 { executeManagedCodexV3Login, executeManagedCodexV3Upload, reportManagedCodexV3Credentials } from "./managed-credential-v3-flow.js";
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 { profileManagedCredentialsV3Enabled } from "./profile.js";
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 managedCredentialsV3Enabled(config) {
101
- return profileManagedCredentialsV3Enabled(config.profile);
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 (managedCredentialsV3Enabled(deps.config)) {
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: "CLIENT_MANAGED_V3",
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 (managedCredentialsV3Enabled(deps.config)) {
262
- const state = await readManagedCodexV3State(deps.env);
263
- if (!state || state.phase !== "INSTALLED" || state.authorityMode !== "CLIENT_MANAGED"
264
- || selection.account.distributionMode !== "CLIENT_MANAGED_V3"
265
- || selection.account.assignmentId !== state.assignmentId
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 current = findV3Assignment({ assignments, ...state });
272
- if (!current || current.assignmentId !== state.assignmentId || current.lineageId !== state.lineageId
273
- || current.authorityMode !== "CLIENT_MANAGED") {
274
- throw new CodexServiceError("The v3 credential assignment is no longer active.", "codex_assignment_not_found", 404);
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
- return { expiresAt: Date.parse(state.credentialExpiresAt), selection };
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 (managedCredentialsV3Enabled(deps.config)) {
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
- throw new CodexServiceError("No acknowledged v3 Codex credential is installed; run `numa codex login` or `numa codex upload`.", "managed_codex_v3_login_required", 409);
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
- throw new CodexServiceError("The requested account is not the active v3 lineage.", "codex_account_not_found", 404);
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 v3 lineage no longer matches the server assignment.", "lineage_version_conflict", 409);
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: "CLIENT_MANAGED_V3",
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 (managedCredentialsV3Enabled(deps.config)) {
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 && managedCredentialsV3Enabled(deps.config))
463
+ if (!includeAuthCommands && accountTokenPoolV4Enabled(deps.config))
483
464
  serviceOptions(codex.command("login")
484
- .description("Run an isolated official Codex login and install client-managed credentials"))
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 (!managedCredentialsV3Enabled(deps.config)) {
488
- throw new CodexServiceError("Managed Codex credential v3 is not enabled by the verified platform profile.", "managed_codex_v3_not_enabled", 409);
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 recovered = await recoverManagedV3Selection(deps, opts);
491
- if (recovered) {
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
- enrollmentId: result.enrollmentId,
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
- lineageId: result.lineageId,
525
- generationVersion: result.generationVersion,
526
- authorityMode: result.authorityMode,
527
- credentialExpiresAt: result.credentialExpiresAt
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 login installed and acknowledged for ${result.providerAccountId}; generation=${result.generationVersion}.\n`);
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 (managedCredentialsV3Enabled(deps.config)) {
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("Enroll official Codex credentials (v3) or upload the legacy local pool"))
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 (managedCredentialsV3Enabled(deps.config)) {
745
- if (opts.storage || opts.source) {
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
- const enrollment = codex.command("enrollment")
849
- .description("Execute administrator-created one-shot Codex login intents");
850
- serviceOptions(enrollment.command("status <intent>").description("Show redacted enrollment intent status"))
851
- .action(async (intent, opts, command) => {
852
- const result = await deps.clientFor(serviceConnection(opts)).getEnrollmentIntent(intent);
853
- if (await json(command, deps)) {
854
- print(deps.output, { ok: true, ...result });
855
- return;
856
- }
857
- deps.output.write(`${result.intent.id} ${result.intent.type} ${result.intent.status}`
858
- + ` revision=${result.intent.revision}\n`);
859
- deps.output.write(`expires=${result.intent.expiresAt}`
860
- + `${result.intent.assignmentId ? ` assignment=${result.intent.assignmentId}` : ""}`
861
- + `${result.intent.managedAccountId ? ` account=${result.intent.managedAccountId}` : ""}\n`);
862
- if (!result.capabilities.candidateUpload) {
863
- deps.output.write("安全候选上传尚未启用;当前任务只能查询或取消,不会启动 OpenAI 登录。\n");
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
- if (await json(command, deps))
877
- print(deps.output, { ok: true, ...result });
878
- else {
879
- deps.output.write(`${result.intent.id} 登录候选已处理;状态=${result.intent.status}`
880
- + `${result.result.managedAccountId ? `,账号=${result.result.managedAccountId}` : ""}`
881
- + `${result.result.lineageId ? `,lineage=${result.result.lineageId}` : ""}`
882
- + "。\n");
883
- if (result.result.outcome === "TEMPORARILY_UNAVAILABLE") {
884
- deps.output.write("同一进程内的安全重试已耗尽;请取消并新建任务,不能用另一次登录覆盖已保留的候选。\n");
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
- const failed = batch.counts.failed > 0
910
- || ["TARGET_UNCERTAIN", "AUTHENTICATION_ERROR", "SERVICE_ERROR", "LOGIN_ERROR"]
911
- .includes(batch.stopReason);
912
- if (await json(command, deps)) {
913
- print(deps.output, { ok: !failed, ...batch });
914
- }
915
- else {
916
- deps.output.write(`migration=${batch.plan.id} status=${batch.plan.status} revision=${batch.plan.revision}`
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
- deps.output.write(`summary: attempted=${batch.counts.attempted}, completed=${batch.counts.completed},`
928
- + ` failed=${batch.counts.failed}, skipped=${batch.counts.skipped}.\n`);
929
- }
930
- if (failed)
931
- process.exitCode = 1;
932
- });
933
- serviceOptions(enrollment.command("cancel <intent>").description("Cancel an enrollment intent"))
934
- .option("--idempotency-key <key>", "reuse a prior cancellation idempotency key")
935
- .action(async (intent, opts, command) => {
936
- const client = deps.clientFor(serviceConnection(opts));
937
- const current = await client.getEnrollmentIntent(intent);
938
- const result = await client.cancelEnrollmentIntent(intent, {
939
- idempotencyKey: opts.idempotencyKey?.trim() || randomUUID(),
940
- revision: current.intent.revision
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
- if (await json(command, deps))
943
- print(deps.output, { ok: true, ...result });
944
- else
945
- deps.output.write(`${result.intent.id} 已取消;状态=${result.intent.status}。\n`);
946
- });
947
- const retirement = codex.command("retirement")
948
- .description("Remove an exact locally installed lineage for a Server retirement request");
949
- serviceOptions(retirement.command("acknowledge <retirement>")
950
- .description("Delete the matching managed short-lived credentials and append local evidence")
951
- .option("--idempotency-key <key>", "reuse a prior local deletion acknowledgement key"))
952
- .action(async (retirementId, opts, command) => {
953
- const result = await executeCodexAssignmentRetirementLocalDelete({
954
- client: deps.clientFor(serviceConnection(opts)),
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
- if (await json(command, deps))
960
- print(deps.output, { ok: true, ...result });
961
- else
962
- deps.output.write(`本机匹配的短效凭据已删除并记录证据;retirement=${result.retirement.id}`
963
- + `,revision=${result.retirement.revision},状态=${result.retirement.status}。\n`
964
- + "该证据不代表其他终端已清理;最终退役仍等待全部已披露短效凭据自然过期。\n");
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")