@bridge4dev/runner 0.66.0 → 0.67.0

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.
@@ -10,7 +10,8 @@ import { applyStoredClaudeToken, environmentCarriesStoredToken, extractOauthToke
10
10
  import { invalidateUsageCache } from './adapters/claude-usage.js';
11
11
  import { AccountError, activeAccountId, adoptLoginResult as adoptClaudeLogin, buildAccountList, clearLoginExpired, discardStagingHome as discardClaudeStagingHome, identityProbeEnv, machineLoginStatus, markLoginExpired, prepareStagingHome as prepareClaudeStagingHome, readActiveAccountSummary, refreshAccountIdentity, savedLoginStatus, setActiveAccount, } from './claude-homes.js';
12
12
  import { MACHINE_ACCOUNT_ID, clearRefusal, noteRefusal, refusalActive } from './login-marks.js';
13
- import { adoptLoginResult, discardStagingHome, prepareStagingHome, repairCodexAuth, stagingCodexHomePath, } from './adapters/codex-home.js';
13
+ import { adoptLoginResult as adoptCodexLogin, discardStagingHome as discardCodexStagingHome, prepareStagingHome as prepareCodexStagingHome, readCodexCredentialFile, repairCodexAuth, } from './adapters/codex-home.js';
14
+ import { activeCodexAccountId, clearCodexLoginExpired, codexActiveAccountSummary, codexRowRefused, markCodexLoginExpired, } from './codex-accounts.js';
14
15
  const execFileAsync = promisify(execFile);
15
16
  /* eslint-disable no-control-regex -- this module parses raw pty output, so
16
17
  matching ANSI/OSC escape bytes (\x1b, \x07) is exactly the point. */
@@ -21,7 +22,13 @@ const CODE_EXCHANGE_TIMEOUT_MS = 60_000;
21
22
  const RELAY_MAX_LIFETIME_MS = 16 * 60_000;
22
23
  const URL_PATTERNS = {
23
24
  claude: /https:\/\/(?:claude\.com|claude\.ai)\/[^\s\x07\x1b"']+/,
24
- codex: /https:\/\/[^\s\x07\x1b"']+/,
25
+ // A page a person opens, never an endpoint of the provider's API (D26, S4 item
26
+ // 5a). When codex fails to get a device code it prints «Error logging in with
27
+ // device code: error sending request for url
28
+ // (https://auth.openai.com/api/accounts/deviceauth/usercode)», and the old
29
+ // any-https pattern handed that address to the window as the sign-in link –
30
+ // without a code, onto a page that answers «Authentication Error».
31
+ codex: /https:\/\/(?![^\s\x07\x1b"']*\/api\/)[^\s\x07\x1b"']+/,
25
32
  };
26
33
  /**
27
34
  * Rejoin a secret the pty split across lines, so the masker can see it.
@@ -219,7 +226,21 @@ export class AuthRelay {
219
226
  * (it prints an inference-only token, D1), so a «saved account» made from it
220
227
  * would be a row with no login. `machine` keeps the fallback it always had.
221
228
  */
222
- async startAccountLogin(target) {
229
+ async startAccountLogin(target, agent = 'claude') {
230
+ if (agent === 'codex') {
231
+ // R13: the machine row of Codex is the host user's own `~/.codex/auth.json`,
232
+ // which DevBridge has never written and does not start writing. A sign-in
233
+ // from the window always adds (or renews) a saved login.
234
+ if (target === 'machine') {
235
+ throw new AccountError('the login of this machine is the Codex login of its own user – sign in again in the terminal on the machine (`codex login`)');
236
+ }
237
+ this.cancel();
238
+ this.assertCanRun('codex');
239
+ return this.startWith('codex', this.commands.codex ?? '', false, {
240
+ flow: 'account',
241
+ target: 'saved',
242
+ });
243
+ }
223
244
  this.cancel();
224
245
  this.assertCanRun('claude');
225
246
  const command = this.commands.claude ?? '';
@@ -258,7 +279,9 @@ export class AuthRelay {
258
279
  // sign-in (or letting it time out) left the server permanently signed out
259
280
  // with no way back except restarting the daemon — and writing through the
260
281
  // link would have overwritten the host user's own account (QA-100 MINOR-5).
261
- const stagingHome = agent === 'codex' ? prepareStagingHome() : null;
282
+ // Whichever door started it, the result is a saved login (#422 S4): Codex
283
+ // has no «machine login» DevBridge may write.
284
+ const stagingHome = agent === 'codex' ? prepareCodexStagingHome() : null;
262
285
  // A saved Claude account signs in inside its own staging home (#422 R2).
263
286
  // `captureToken` never reaches here with `saved` – see `startAccountLogin`.
264
287
  const claudeStaging = agent === 'claude' && door.flow === 'account' && door.target === 'saved'
@@ -266,12 +289,16 @@ export class AuthRelay {
266
289
  : null;
267
290
  const proc = spawn('script', ['-qec', command, '/dev/null'], {
268
291
  stdio: ['pipe', 'pipe', 'pipe'],
269
- env: door.flow === 'account'
270
- ? // The staging home, or the machine's own `~/.claude` (no variable);
271
- // and in both, no operator token and no API key (§8).
272
- identityProbeEnv(claudeStaging, relayEnv())
273
- : // Codex must log in to a home WE control, never the host user's ~/.codex.
274
- relayEnv(stagingHome ? { CODEX_HOME: stagingHome } : {}),
292
+ env: stagingHome
293
+ ? // Codex must log in to a home WE control, never the host user's ~/.codex –
294
+ // whichever door started it (the account door once ran it without this
295
+ // variable, and the CLI wrote its login into the user's own home).
296
+ relayEnv({ CODEX_HOME: stagingHome })
297
+ : door.flow === 'account'
298
+ ? // The staging home, or the machine's own `~/.claude` (no variable);
299
+ // and in both, no operator token and no API key (§8).
300
+ identityProbeEnv(claudeStaging, relayEnv())
301
+ : relayEnv(),
275
302
  });
276
303
  const relay = {
277
304
  agent,
@@ -284,6 +311,7 @@ export class AuthRelay {
284
311
  flow: door.flow,
285
312
  target: door.target,
286
313
  claudeStaging,
314
+ codexStaging: stagingHome,
287
315
  exchanging: false,
288
316
  killTimer: setTimeout(() => {
289
317
  if (this.active === relay)
@@ -304,28 +332,41 @@ export class AuthRelay {
304
332
  // exits once the browser side is confirmed. That exit IS the completion
305
333
  // signal — promote the staging credential, or throw it away.
306
334
  //
307
- // Only when this relay is still the current one: the staging home is a
308
- // single fixed path, so a cancelled login's exit handler firing late
309
- // would otherwise delete the home a NEW login is already writing into.
310
- if (relay.agent !== 'codex' || this.active !== relay)
335
+ // Only when this relay is still the current one: a cancelled login was
336
+ // abandoned (its home removed) by whoever took the slot, and its late exit
337
+ // must not store anything in its name.
338
+ if (relay.agent !== 'codex' || this.active !== relay || !relay.codexStaging)
311
339
  return;
340
+ if (code !== 0) {
341
+ this.discardStaging(relay);
342
+ return;
343
+ }
344
+ const staging = relay.codexStaging;
345
+ relay.codexStaging = null;
312
346
  try {
313
- if (code === 0 && adoptLoginResult(stagingCodexHomePath())) {
314
- log.info('codex: device login completed — credential adopted');
315
- // The ONLY place a Codex sign-in can be reported as finished. Its
316
- // flow never sends `login_code` (device auth needs no paste-back),
317
- // so without this line a refusal we recorded earlier would keep the
318
- // panel demanding a re-login the user has just done — #121's own
319
- // complaint, reproduced on the other half of the panel (QA-117 H1).
320
- clearAgentAuthFailure('codex');
321
- }
322
- else {
323
- discardStagingHome();
324
- }
347
+ // By rename, one subscription – one row, and the signed-in row is the one
348
+ // new sessions start under (S4 item 5, R15). The window learns it from the
349
+ // list (§8: device sign-in finishes without `login_code`).
350
+ // The one-login window's door (`flow: 'legacy'`) replaces the login in
351
+ // use instead of adding a row beside it: with the organization's option
352
+ // off, «a token overwrites the token» is what a sign-in has always meant
353
+ // (D27), and DevBridge writes no machine login for Codex to overwrite.
354
+ const stored = adoptCodexLogin(staging, { replaceActive: relay.flow === 'legacy' });
355
+ log.info('codex: device login completed — credential stored', {
356
+ account: stored.id,
357
+ replaced: Boolean(stored.replaced),
358
+ });
359
+ // The ONLY place a Codex sign-in can be reported as finished. Its
360
+ // flow never sends `login_code` (device auth needs no paste-back),
361
+ // so without this line a refusal we recorded earlier would keep the
362
+ // panel demanding a re-login the user has just done — #121's own
363
+ // complaint, reproduced on the other half of the panel (QA-117 H1).
364
+ clearAgentAuthFailure('codex', stored.id);
325
365
  }
326
366
  catch (error) {
327
- log.warn('codex: could not finish the device login', { error: String(error) });
328
- discardStagingHome();
367
+ log.warn('codex: could not finish the device login', {
368
+ error: maskString(String(error)).slice(0, 300),
369
+ });
329
370
  }
330
371
  });
331
372
  proc.on('error', (error) => {
@@ -336,15 +377,20 @@ export class AuthRelay {
336
377
  const deadline = Date.now() + URL_START_TIMEOUT_MS;
337
378
  for (;;) {
338
379
  const url = extractLoginUrl(agent, relay.buffer);
339
- if (url && this.active !== relay) {
380
+ // Codex: a link is half of the sign-in, the one-time code the other half,
381
+ // and the CLI prints them on two lines that can arrive in two reads. A link
382
+ // without its code sends the person to a page that asks for something the
383
+ // window never showed (D26) – so both, or the CLI's own words about why not.
384
+ const code = agent === 'codex' ? extractDeviceCode(relay.buffer) : null;
385
+ const ready = url !== null && (agent !== 'codex' || code !== null);
386
+ if (ready && this.active !== relay) {
340
387
  // A newer start took the slot while this one waited: its CLI is killed,
341
388
  // and a link to it would lead nowhere – a code pasted for it would reach
342
389
  // the newer CLI (found by the tests of the S2 check).
343
390
  this.abandon(relay);
344
391
  throw new Error('a newer sign-in was started on this server – use its link');
345
392
  }
346
- if (url) {
347
- const code = agent === 'codex' ? extractDeviceCode(relay.buffer) : null;
393
+ if (ready && url) {
348
394
  return { url, ...(code ? { code } : {}), expectsCode: agent === 'claude' };
349
395
  }
350
396
  if (relay.exited) {
@@ -354,7 +400,9 @@ export class AuthRelay {
354
400
  }
355
401
  if (Date.now() > deadline) {
356
402
  this.abandon(relay);
357
- throw new Error(`${agent} login did not print a sign-in URL in time`);
403
+ throw new Error(url
404
+ ? `${agent} login printed a link but no one-time code in time – start again`
405
+ : `${agent} login did not print a sign-in URL in time`);
358
406
  }
359
407
  await sleep(200);
360
408
  }
@@ -581,12 +629,15 @@ export class AuthRelay {
581
629
  }
582
630
  /** A sign-in that will not be adopted leaves nothing behind (§8, S2 item 2). */
583
631
  discardStaging(relay) {
584
- const staging = relay.claudeStaging;
585
- if (!staging)
586
- return;
632
+ const claude = relay.claudeStaging;
633
+ const codex = relay.codexStaging;
587
634
  relay.claudeStaging = null;
635
+ relay.codexStaging = null;
588
636
  try {
589
- discardClaudeStagingHome(staging);
637
+ if (claude)
638
+ discardClaudeStagingHome(claude);
639
+ if (codex)
640
+ discardCodexStagingHome(codex);
590
641
  }
591
642
  catch (error) {
592
643
  log.warn('auth-relay: could not remove an abandoned sign-in home', {
@@ -654,8 +705,19 @@ export async function claudeAuthStatus(homedir = os.homedir(), options = {}) {
654
705
  export function noteAgentAuthFailure(agent, accountId = MACHINE_ACCOUNT_ID) {
655
706
  noteRefusal(agent, accountId);
656
707
  log.warn('auth-relay: agent sign-in refused during a session', { agent, account: accountId });
657
- if (agent !== 'claude')
708
+ if (agent === 'codex') {
709
+ // A saved Codex login keeps the mark on disk, as a Claude one does (D2) –
710
+ // but only while it is the login in use. Codex refuses to refresh a session
711
+ // whose account is no longer the one `auth.json` points at («you have since
712
+ // signed in to another account»), and that refusal says nothing about the
713
+ // login of the account the session started under: marking it would put
714
+ // «sign in again» on a login that is perfectly good (found by the
715
+ // independent check of S4).
716
+ if (accountId !== MACHINE_ACCOUNT_ID && accountId === activeCodexAccountId()) {
717
+ markCodexLoginExpired(accountId);
718
+ }
658
719
  return;
720
+ }
659
721
  if (accountId !== MACHINE_ACCOUNT_ID) {
660
722
  markLoginExpired(accountId);
661
723
  return;
@@ -676,8 +738,11 @@ export function clearAgentAuthFailure(agent, accountId = MACHINE_ACCOUNT_ID) {
676
738
  if (clearRefusal(agent, accountId)) {
677
739
  log.info('auth-relay: agent sign-in is working again', { agent, account: accountId });
678
740
  }
679
- if (agent !== 'claude')
741
+ if (agent === 'codex') {
742
+ if (accountId !== MACHINE_ACCOUNT_ID)
743
+ clearCodexLoginExpired(accountId);
680
744
  return;
745
+ }
681
746
  // A machine session that worked on the credentials FILE says nothing about the
682
747
  // captured token: its mark is lifted only by a session that ran with it.
683
748
  if (accountId !== MACHINE_ACCOUNT_ID || environmentCarriesStoredToken()) {
@@ -714,14 +779,35 @@ function logVerdictChange(agent, status) {
714
779
  * Codex reports its own login state via an exit code (0 signed in / 1 not).
715
780
  * Probed against the RUNNER's home: the host user can be signed in while our
716
781
  * isolated home is not, and it is ours that sessions use.
782
+ *
783
+ * About the login new sessions start under (#422 S4): the machine login through
784
+ * the link, or the saved login the link points at – named in `activeAccount`,
785
+ * read locally from the file, no CLI for it (R14).
717
786
  */
718
787
  export async function codexAuthStatus() {
719
788
  // Re-assert the credential before judging it. The link into the shared store
720
789
  // can disappear under a live daemon (codex has its own auth.json removal
721
790
  // path), and reporting "login expired" about a credential that is merely
722
791
  // unlinked — while the user's own file is valid for another week — is the
723
- // exact complaint this fixes.
792
+ // exact complaint this fixes. Forcing nothing (S4 item 2): a saved login in
793
+ // use stays in use.
724
794
  const home = repairCodexAuth();
795
+ const accountId = home.accountId ?? MACHINE_ACCOUNT_ID;
796
+ const activeAccount = codexActiveAccountSummary(accountId);
797
+ const verdict = await codexHomeVerdict(home);
798
+ // A saved login a session was refused with stays refused until its file is
799
+ // written again (D2) – the same rule as a saved Claude login.
800
+ if (verdict.status === 'ok' && codexRowRefused(accountId)) {
801
+ return {
802
+ ...verdict,
803
+ status: 'expired',
804
+ detail: 'the agent was refused with this account – sign in again',
805
+ activeAccount,
806
+ };
807
+ }
808
+ return { ...verdict, activeAccount };
809
+ }
810
+ async function codexHomeVerdict(home) {
725
811
  const local = readCodexCredential(home.path);
726
812
  if (home.auth === 'missing' && !local) {
727
813
  return { status: 'missing', detail: 'not signed in on this server' };
@@ -733,7 +819,11 @@ export async function codexAuthStatus() {
733
819
  });
734
820
  return {
735
821
  status: 'ok',
736
- detail: home.auth === 'linked' ? 'signed in with ChatGPT (shared login)' : 'signed in with ChatGPT',
822
+ detail: home.auth === 'linked'
823
+ ? 'signed in with ChatGPT (shared login)'
824
+ : home.auth === 'account'
825
+ ? 'signed in with ChatGPT (saved login)'
826
+ : 'signed in with ChatGPT',
737
827
  ...(local?.expiresAt ? { expiresAt: local.expiresAt } : {}),
738
828
  };
739
829
  }
@@ -763,30 +853,19 @@ export async function codexAuthStatus() {
763
853
  /**
764
854
  * Read what the runner's own auth.json says, without ever logging a token.
765
855
  * Used only to tell "no credential" apart from "credential past its date" —
766
- * the CLI probe is local and cannot detect a server-side revocation.
856
+ * the CLI probe is local and cannot detect a server-side revocation. The
857
+ * reading itself lives in `codex-home.ts`, where the saved logins are judged by
858
+ * the same lines.
767
859
  */
768
860
  function readCodexCredential(homePath) {
769
- try {
770
- const raw = JSON.parse(fs.readFileSync(path.join(homePath, 'auth.json'), 'utf8'));
771
- const access = raw.tokens?.access_token;
772
- if (!access)
773
- return null;
774
- // A refresh token means the access token's own expiry is not the whole
775
- // story — codex renews it on its own.
776
- if (raw.tokens?.refresh_token)
777
- return { expired: false };
778
- const payload = access.split('.')[1];
779
- if (!payload)
780
- return { expired: false };
781
- const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
782
- if (typeof claims.exp !== 'number')
783
- return { expired: false };
784
- const expiresAt = new Date(claims.exp * 1000).toISOString();
785
- return { expiresAt, expired: claims.exp * 1000 < Date.now() };
786
- }
787
- catch {
861
+ const credential = readCodexCredentialFile(path.join(homePath, 'auth.json'));
862
+ // Only a ChatGPT login has tokens to date; an API key is left to `codex login status`.
863
+ if (credential.kind !== 'chatgpt')
788
864
  return null;
789
- }
865
+ return {
866
+ ...(credential.expiresAt ? { expiresAt: credential.expiresAt } : {}),
867
+ expired: credential.status === 'expired',
868
+ };
790
869
  }
791
870
  /**
792
871
  * A verdict read off disk, overruled by what a real session experienced.
@@ -807,7 +886,7 @@ function withObservedFailures(agent, status, accountId = MACHINE_ACCOUNT_ID) {
807
886
  export async function agentAuthStatuses() {
808
887
  const [claudeRaw, codexRaw] = await Promise.all([claudeAuthStatus(), codexAuthStatus()]);
809
888
  const claude = withObservedFailures('claude', claudeRaw, claudeRaw.activeAccount?.id ?? MACHINE_ACCOUNT_ID);
810
- const codex = withObservedFailures('codex', codexRaw);
889
+ const codex = withObservedFailures('codex', codexRaw, codexRaw.activeAccount?.id ?? MACHINE_ACCOUNT_ID);
811
890
  logVerdictChange('claude', claude);
812
891
  logVerdictChange('codex', codex);
813
892
  return { claude, codex };
@@ -1,5 +1,6 @@
1
1
  import { type ClaudeIdentity } from './agent-auth.js';
2
2
  import { type UsageReading, type UsageRow, type UsageSignature } from './adapters/claude-usage.js';
3
+ import { AccountError, isAccountId } from './login-marks.js';
3
4
  /**
4
5
  * Several Claude logins on one machine – one HOME per account (#422, D1).
5
6
  *
@@ -41,14 +42,10 @@ export declare function claudeHomesDir(): string;
41
42
  * exactly the definition of «the machine login».
42
43
  */
43
44
  export declare function machineHome(homedir?: string): string;
44
- /** A saved account's id: twelve lowercase letters and digits, never `machine`. */
45
- export declare function isAccountId(id: unknown): id is string;
45
+ /** The id format and the refusal live beside the machine row's id – shared by both agents. */
46
+ export { AccountError, isAccountId };
46
47
  /** The home of a saved account. Throws on anything that is not an account id. */
47
48
  export declare function accountHome(id: string): string;
48
- /** A refusal meant for a person: the wire (S2) sends its message as it is. */
49
- export declare class AccountError extends Error {
50
- constructor(message: string);
51
- }
52
49
  /**
53
50
  * What a saved home reaches through a link rather than owning (R20).
54
51
  *
@@ -1,12 +1,11 @@
1
1
  import { execFile } from 'node:child_process';
2
- import crypto from 'node:crypto';
3
2
  import fs from 'node:fs';
4
3
  import os from 'node:os';
5
4
  import path from 'node:path';
6
5
  import { sessionClaudePath } from './agent-binary.js';
7
6
  import { readAgentAuth, storedClaudeToken, storedClaudeTokenRefused, updateAgentAuth, } from './agent-auth.js';
8
7
  import { invalidateUsageCache, lastUsageRows, } from './adapters/claude-usage.js';
9
- import { MACHINE_ACCOUNT_ID, clearRefusal, refusalActive } from './login-marks.js';
8
+ import { AccountError, MACHINE_ACCOUNT_ID, clearRefusal, isAccountId, newAccountId, refusalActive, } from './login-marks.js';
10
9
  import { log } from './log.js';
11
10
  import { stateDir } from './paths.js';
12
11
  import { lowerPriority } from './process-priority.js';
@@ -66,30 +65,14 @@ function configFileOf(home, homedir = os.homedir()) {
66
65
  function credentialsFileOf(home, homedir = os.homedir()) {
67
66
  return path.join(home ?? machineHome(homedir), '.credentials.json');
68
67
  }
69
- const ACCOUNT_ID = /^[a-z0-9]{12}$/;
70
- /** A saved account's id: twelve lowercase letters and digits, never `machine`. */
71
- export function isAccountId(id) {
72
- return typeof id === 'string' && ACCOUNT_ID.test(id);
73
- }
74
- function newAccountId() {
75
- // base32-ish from random bytes: 12 characters of [a-z0-9].
76
- const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789';
77
- const bytes = crypto.randomBytes(12);
78
- return Array.from(bytes, (byte) => alphabet[byte % alphabet.length]).join('');
79
- }
68
+ /** The id format and the refusal live beside the machine row's id – shared by both agents. */
69
+ export { AccountError, isAccountId };
80
70
  /** The home of a saved account. Throws on anything that is not an account id. */
81
71
  export function accountHome(id) {
82
72
  if (!isAccountId(id))
83
73
  throw new AccountError(`not an account id: ${String(id).slice(0, 40)}`);
84
74
  return path.join(claudeHomesDir(), id);
85
75
  }
86
- /** A refusal meant for a person: the wire (S2) sends its message as it is. */
87
- export class AccountError extends Error {
88
- constructor(message) {
89
- super(message);
90
- this.name = 'AccountError';
91
- }
92
- }
93
76
  function lstatOrNull(target) {
94
77
  try {
95
78
  return fs.lstatSync(target);
@@ -0,0 +1,79 @@
1
+ import { type CodexHome } from './adapters/codex-home.js';
2
+ import type { AgentRateLimitWindow } from './adapters/types.js';
3
+ import type { AccountCard, AccountList } from './claude-homes.js';
4
+ /** Which row the home uses: a saved one only while its directory is there. */
5
+ export declare function activeCodexAccountId(home?: string): string;
6
+ /**
7
+ * The list of Codex logins on this machine (§8 `agent_accounts`).
8
+ *
9
+ * Repairs the home first, without forcing anything (S4 item 2), so a real file
10
+ * the CLI left in place of the link is back in its store before the rows are
11
+ * read. No subprocess anywhere in it.
12
+ */
13
+ export declare function listCodexAccounts(homedir?: string): AccountList;
14
+ /** The card of a row that just signed in – for the relay's log and the tests. */
15
+ export declare function codexAccountCard(id: string): AccountCard;
16
+ /**
17
+ * Make a row the login new Codex sessions start under (§8 `agent_account_activate`).
18
+ *
19
+ * Running sessions keep the tokens they started with; a refreshed login is
20
+ * returned to its own store before the link moves (S4 item 7). The machine row is
21
+ * refused only where the machine's owner wrote `[codex] auth = "own"`: there the
22
+ * host login is kept away from the runner on purpose, and the next repair would
23
+ * take the link out again.
24
+ */
25
+ export declare function activateCodexAccount(id: string): string;
26
+ /**
27
+ * Forget a saved Codex login: its file goes, its record goes (§8 `agent_account_forget`).
28
+ *
29
+ * The machine row cannot be forgotten. A row a live session runs under is
30
+ * refused – its process refreshes into that file until it ends. The row in use is
31
+ * switched to the machine login FIRST, so the link never points at nothing.
32
+ */
33
+ export declare function forgetCodexAccount(id: string): {
34
+ active: string;
35
+ };
36
+ /** The account a Codex session runs under – captured at its start, kept to its end (R14). */
37
+ export interface CodexSessionAccount {
38
+ id: string;
39
+ email?: string;
40
+ orgId?: string;
41
+ plan?: string;
42
+ }
43
+ /**
44
+ * Who the home a session is starting with belongs to: the row it is set to, and
45
+ * the identity of the login the link resolves to right now – read locally.
46
+ */
47
+ export declare function codexSessionAccount(home: CodexHome): CodexSessionAccount;
48
+ /**
49
+ * `owner` is the session object itself: a session can end twice (the boot catch
50
+ * calls `stop()`, and the process exit calls it again), and a bare delete by
51
+ * sessionId then wiped the note of the RELAUNCHED session that had taken the
52
+ * same id – «forget» stopped refusing the account that session was using (found
53
+ * by the independent check of S4).
54
+ */
55
+ export declare function noteCodexSessionAccount(sessionId: string, accountId: string, owner: object): void;
56
+ export declare function releaseCodexSessionAccount(sessionId: string, owner: object): void;
57
+ /** Codex rows some live session process runs under right now. */
58
+ export declare function liveCodexAccountIds(): Set<string>;
59
+ /** Who the active row is, for `auth_status.activeAccount` (§8) – read locally, no CLI. */
60
+ export declare function codexActiveAccountSummary(id: string, homedir?: string): {
61
+ id: string;
62
+ email?: string;
63
+ orgId?: string;
64
+ };
65
+ /** A refusal persisted on a saved row that still stands against its login file. */
66
+ export declare function codexRowRefused(id: string): boolean;
67
+ /** A session under this saved row was refused (D2): mark it, remove nothing. */
68
+ export declare function markCodexLoginExpired(id: string): void;
69
+ /** The row worked again. Writes only when there was a mark to clear. */
70
+ export declare function clearCodexLoginExpired(id: string): void;
71
+ /**
72
+ * A Codex session reported its account's windows. Codex sends these after every
73
+ * model request, for the account the session runs under – so for a card they
74
+ * are that subscription's figures, as fresh as its last working session.
75
+ */
76
+ export declare function noteCodexUsage(orgId: string | undefined, windows: AgentRateLimitWindow[]): void;
77
+ /** Forget every remembered reading – tests. */
78
+ export declare function resetCodexUsage(): void;
79
+ //# sourceMappingURL=codex-accounts.d.ts.map