@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.
- package/dist/account-commands.d.ts +6 -7
- package/dist/account-commands.js +53 -10
- package/dist/adapters/codex-home.d.ts +143 -21
- package/dist/adapters/codex-home.js +623 -77
- package/dist/adapters/codex.d.ts +25 -3
- package/dist/adapters/codex.js +149 -11
- package/dist/agent-auth.d.ts +13 -0
- package/dist/agent-auth.js +57 -33
- package/dist/auth-relay.d.ts +5 -1
- package/dist/auth-relay.js +141 -62
- package/dist/claude-homes.d.ts +3 -6
- package/dist/claude-homes.js +3 -20
- package/dist/codex-accounts.d.ts +79 -0
- package/dist/codex-accounts.js +386 -0
- package/dist/config.d.ts +3 -3
- package/dist/config.js +4 -1
- package/dist/index.js +26 -10
- package/dist/login-marks.d.ts +11 -0
- package/dist/login-marks.js +23 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { AccountLoginCodeResult, AccountLoginTarget, LoginStartResult } from './auth-relay.js';
|
|
1
|
+
import type { AccountLoginCodeResult, AccountLoginTarget, LoginStartResult, RelayAgent } from './auth-relay.js';
|
|
2
2
|
/**
|
|
3
3
|
* Several logins of one agent, on the wire (#422 S2, plan §8).
|
|
4
4
|
*
|
|
5
|
-
* Five commands, each a thin door over what `claude-homes.ts`
|
|
6
|
-
* the machine. New NAMES rather than new arguments of `login_start`: an older
|
|
5
|
+
* Five commands, each a thin door over what `claude-homes.ts` (Claude) and
|
|
6
|
+
* `codex-accounts.ts` (Codex, S4) already do on the machine. New NAMES rather than new arguments of `login_start`: an older
|
|
7
7
|
* runner silently ignores an argument it does not know and would sign in over
|
|
8
8
|
* the machine login while the API believed a saved account was being added; an
|
|
9
9
|
* unknown name it answers «Unknown command» at once.
|
|
@@ -21,10 +21,9 @@ export declare const ACCOUNT_COMMANDS: readonly ["agent_accounts", "agent_accoun
|
|
|
21
21
|
export type AccountCommand = (typeof ACCOUNT_COMMANDS)[number];
|
|
22
22
|
/**
|
|
23
23
|
* The agents this runner keeps several logins for (`capabilities.accounts`,
|
|
24
|
-
* R11). Codex
|
|
25
|
-
* sentence rather than half-served.
|
|
24
|
+
* R11). Codex joined in S4 – the same five commands and the same card.
|
|
26
25
|
*/
|
|
27
|
-
export declare const ACCOUNT_AGENTS: readonly ["claude"];
|
|
26
|
+
export declare const ACCOUNT_AGENTS: readonly ["claude", "codex"];
|
|
28
27
|
/** What `supervisor.ts` sends back as `command_result`, minus the envelope. */
|
|
29
28
|
export type AccountCommandReply = {
|
|
30
29
|
ok: true;
|
|
@@ -36,7 +35,7 @@ export type AccountCommandReply = {
|
|
|
36
35
|
};
|
|
37
36
|
/** The two sign-in steps the commands drive – `AuthRelay`, or a test double. */
|
|
38
37
|
export interface AccountSignIn {
|
|
39
|
-
startAccountLogin(target: AccountLoginTarget): Promise<LoginStartResult>;
|
|
38
|
+
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent): Promise<LoginStartResult>;
|
|
40
39
|
submitAccountCode(code: string): Promise<AccountLoginCodeResult>;
|
|
41
40
|
}
|
|
42
41
|
export declare function isAccountCommand(name: string): name is AccountCommand;
|
package/dist/account-commands.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { forgetAccount, listAccounts, setActiveAccount } from './claude-homes.js';
|
|
2
|
+
import { activateCodexAccount, forgetCodexAccount, listCodexAccounts } from './codex-accounts.js';
|
|
3
|
+
import { AccountError, MACHINE_ACCOUNT_ID, isAccountId } from './login-marks.js';
|
|
3
4
|
import { maskString } from './policy.js';
|
|
4
5
|
/**
|
|
5
6
|
* Several logins of one agent, on the wire (#422 S2, plan §8).
|
|
6
7
|
*
|
|
7
|
-
* Five commands, each a thin door over what `claude-homes.ts`
|
|
8
|
-
* the machine. New NAMES rather than new arguments of `login_start`: an older
|
|
8
|
+
* Five commands, each a thin door over what `claude-homes.ts` (Claude) and
|
|
9
|
+
* `codex-accounts.ts` (Codex, S4) already do on the machine. New NAMES rather than new arguments of `login_start`: an older
|
|
9
10
|
* runner silently ignores an argument it does not know and would sign in over
|
|
10
11
|
* the machine login while the API believed a saved account was being added; an
|
|
11
12
|
* unknown name it answers «Unknown command» at once.
|
|
@@ -28,10 +29,9 @@ export const ACCOUNT_COMMANDS = [
|
|
|
28
29
|
];
|
|
29
30
|
/**
|
|
30
31
|
* The agents this runner keeps several logins for (`capabilities.accounts`,
|
|
31
|
-
* R11). Codex
|
|
32
|
-
* sentence rather than half-served.
|
|
32
|
+
* R11). Codex joined in S4 – the same five commands and the same card.
|
|
33
33
|
*/
|
|
34
|
-
export const ACCOUNT_AGENTS = ['claude'];
|
|
34
|
+
export const ACCOUNT_AGENTS = ['claude', 'codex'];
|
|
35
35
|
export function isAccountCommand(name) {
|
|
36
36
|
return ACCOUNT_COMMANDS.includes(name);
|
|
37
37
|
}
|
|
@@ -48,9 +48,8 @@ function accountIdArg(value) {
|
|
|
48
48
|
}
|
|
49
49
|
export async function runAccountCommand(name, args, signIn) {
|
|
50
50
|
const agent = args['agent'];
|
|
51
|
-
if (agent === 'codex')
|
|
52
|
-
return
|
|
53
|
-
}
|
|
51
|
+
if (agent === 'codex')
|
|
52
|
+
return runCodexAccountCommand(name, args, signIn);
|
|
54
53
|
if (agent !== 'claude')
|
|
55
54
|
return { ok: false, error: 'agent must be claude or codex' };
|
|
56
55
|
try {
|
|
@@ -96,4 +95,48 @@ export async function runAccountCommand(name, args, signIn) {
|
|
|
96
95
|
return refusal(error);
|
|
97
96
|
}
|
|
98
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The same five doors for Codex (#422 S4). What differs is said where it differs:
|
|
100
|
+
* the machine login is the host user's own file (no sign-in into it, R13), and a
|
|
101
|
+
* device sign-in finishes on the provider's page – there is no code to send; the
|
|
102
|
+
* window learns the result from the list (§8).
|
|
103
|
+
*/
|
|
104
|
+
async function runCodexAccountCommand(name, args, signIn) {
|
|
105
|
+
try {
|
|
106
|
+
switch (name) {
|
|
107
|
+
case 'agent_accounts':
|
|
108
|
+
return { ok: true, result: listCodexAccounts() };
|
|
109
|
+
case 'agent_account_login_start': {
|
|
110
|
+
const target = args['target'];
|
|
111
|
+
if (target !== 'machine' && target !== 'saved') {
|
|
112
|
+
return { ok: false, error: 'target must be machine or saved' };
|
|
113
|
+
}
|
|
114
|
+
return { ok: true, result: await signIn.startAccountLogin(target, 'codex') };
|
|
115
|
+
}
|
|
116
|
+
case 'agent_account_login_code': {
|
|
117
|
+
const result = {
|
|
118
|
+
ok: false,
|
|
119
|
+
detail: 'a Codex sign-in finishes on the provider page – there is no code to send; the account list shows it once it is done',
|
|
120
|
+
};
|
|
121
|
+
return { ok: false, error: result.detail, result };
|
|
122
|
+
}
|
|
123
|
+
case 'agent_account_activate': {
|
|
124
|
+
const id = accountIdArg(args['id']);
|
|
125
|
+
if (!id)
|
|
126
|
+
return { ok: false, error: 'no such account on this server' };
|
|
127
|
+
return { ok: true, result: { active: activateCodexAccount(id) } };
|
|
128
|
+
}
|
|
129
|
+
case 'agent_account_forget': {
|
|
130
|
+
const id = accountIdArg(args['id']);
|
|
131
|
+
if (!id)
|
|
132
|
+
return { ok: false, error: 'no such account on this server' };
|
|
133
|
+
const { active } = forgetCodexAccount(id);
|
|
134
|
+
return { ok: true, result: { ok: true, active } };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return refusal(error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
99
142
|
//# sourceMappingURL=account-commands.js.map
|
|
@@ -1,24 +1,90 @@
|
|
|
1
1
|
export type CodexAuthMode = 'link' | 'own';
|
|
2
2
|
export interface CodexHome {
|
|
3
3
|
path: string;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* How the home is authenticated — reported in notices, not a secret.
|
|
6
|
+
* `account` – a saved login is linked (`accountId`); `own` – a real file the
|
|
7
|
+
* runner could not move into the store and keeps using where it is.
|
|
8
|
+
*/
|
|
9
|
+
auth: 'linked' | 'account' | 'own' | 'missing';
|
|
10
|
+
/** The saved login the home is set to – present with `account`, and with `missing` under it. */
|
|
11
|
+
accountId?: string;
|
|
6
12
|
}
|
|
7
13
|
export declare function codexHomePath(): string;
|
|
14
|
+
/** Where the saved Codex logins live – inside the one home, beside what they share. */
|
|
15
|
+
export declare function codexAccountsDir(home?: string): string;
|
|
16
|
+
/** The directory of one saved login. Throws on anything that is not an account id. */
|
|
17
|
+
export declare function codexAccountDir(id: string, home?: string): string;
|
|
18
|
+
/** The login file of one saved account. */
|
|
19
|
+
export declare function codexAccountAuthFile(id: string, home?: string): string;
|
|
20
|
+
/** The machine login of Codex – the host user's own file, never written by DevBridge (R13). */
|
|
21
|
+
export declare function hostCodexAuthFile(homedir?: string): string;
|
|
8
22
|
/**
|
|
9
|
-
* Throwaway
|
|
10
|
-
* into the
|
|
11
|
-
* cannot destroy a credential that was working.
|
|
23
|
+
* Throwaway homes for device-code logins, one per attempt. The flow runs there
|
|
24
|
+
* and its login is moved into the store only on success, so an abandoned or
|
|
25
|
+
* timed-out sign-in cannot destroy a credential that was working.
|
|
12
26
|
*/
|
|
13
27
|
export declare function stagingCodexHomePath(): string;
|
|
28
|
+
/**
|
|
29
|
+
* `[codex] auth` as the machine's owner WROTE it – `undefined` when the key is
|
|
30
|
+
* not in `config.toml`. Set once at daemon start.
|
|
31
|
+
*
|
|
32
|
+
* Only a mode that is really written may be forced, and even then not over a
|
|
33
|
+
* saved login (S4 item 2): the runner used to pass `link` by default from three
|
|
34
|
+
* places – the daemon start, every session start and the minute probe – and each
|
|
35
|
+
* of them would have undone a switch of account within a minute (К4).
|
|
36
|
+
*/
|
|
37
|
+
export declare function configureCodexAuth(mode: CodexAuthMode | undefined): void;
|
|
38
|
+
export declare function configuredCodexAuth(): CodexAuthMode | undefined;
|
|
39
|
+
/** Which saved login the home is set to, if any – the mark, read only. */
|
|
40
|
+
export declare function markedCodexAccount(home?: string): string | null;
|
|
41
|
+
/** Who a Codex login is, read locally from its own file – never a token. */
|
|
42
|
+
export interface CodexIdentity {
|
|
43
|
+
email?: string;
|
|
44
|
+
/** `tokens.account_id` – the key of a Codex subscription (plan §8, S4 item 5). */
|
|
45
|
+
orgId?: string;
|
|
46
|
+
/** `chatgpt_plan_type` – `plus`, `pro`, `team`, … */
|
|
47
|
+
plan?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface CodexCredential {
|
|
50
|
+
/**
|
|
51
|
+
* `ok` – a login the CLI can use; `expired` – a login past its date with no
|
|
52
|
+
* refresh token; `missing` – no file; `unreadable` – a file that is not a login
|
|
53
|
+
* (torn, hand-edited); `unknown` – we could not look (EACCES).
|
|
54
|
+
*/
|
|
55
|
+
status: 'ok' | 'expired' | 'missing' | 'unreadable' | 'unknown';
|
|
56
|
+
/** A ChatGPT login (tokens) or an API key – only for a file that is a login. */
|
|
57
|
+
kind?: 'chatgpt' | 'apikey';
|
|
58
|
+
identity: CodexIdentity;
|
|
59
|
+
/** Only when the access token's own date is the login's date (no refresh token). */
|
|
60
|
+
expiresAt?: string;
|
|
61
|
+
/** Last write of the file, whole ms – a refusal mark older than this no longer holds. */
|
|
62
|
+
writtenMs?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read a Codex login file without letting a token out of this function.
|
|
66
|
+
*
|
|
67
|
+
* The identity comes from the claims of `id_token`, decoded locally (S4 item 8):
|
|
68
|
+
* `email`, and under `https://api.openai.com/auth` the plan and the ChatGPT
|
|
69
|
+
* account; `tokens.account_id` first for the key, the claim when it is absent. An
|
|
70
|
+
* internal file of the CLI (§11): whatever cannot be read is simply not there –
|
|
71
|
+
* no identity is invented, and a row without a key merges with nothing.
|
|
72
|
+
*
|
|
73
|
+
* The verdict is the pre-S4 one (`readCodexCredential`): a refresh token means
|
|
74
|
+
* the CLI renews the access token on its own, so only a lone access token past
|
|
75
|
+
* its `exp` is `expired`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readCodexCredentialFile(file: string): CodexCredential;
|
|
14
78
|
/**
|
|
15
79
|
* Create (or refresh) the runner's CODEX_HOME and return it.
|
|
16
80
|
*
|
|
17
|
-
* `
|
|
18
|
-
* runner uses their ChatGPT subscription and —
|
|
19
|
-
* credential store with their own CLI, so a token
|
|
20
|
-
* both working. `
|
|
21
|
-
* device-code login
|
|
81
|
+
* `link` (the default when nothing else is decided) symlinks the host user's
|
|
82
|
+
* `~/.codex/auth.json` so the runner uses their ChatGPT subscription and —
|
|
83
|
+
* importantly — shares one credential store with their own CLI, so a token
|
|
84
|
+
* refresh on either side keeps both working. `own` leaves the home
|
|
85
|
+
* unauthenticated until a device-code login is stored for it, which is the
|
|
86
|
+
* choice for full isolation. A saved login (`account:<id>`) is kept whatever
|
|
87
|
+
* the configured mode says (S4 item 2).
|
|
22
88
|
*
|
|
23
89
|
* A home under the OS temp dir still works, but codex then refuses to install
|
|
24
90
|
* its helper binaries ("Refusing to create helper binaries under temporary
|
|
@@ -35,6 +101,9 @@ export declare function ensureCodexHome(options?: {
|
|
|
35
101
|
* it only answers "is the credential still where we left it, and if not, can we
|
|
36
102
|
* put it back". This is what makes a credential disappearing under a running
|
|
37
103
|
* daemon self-healing instead of permanent.
|
|
104
|
+
*
|
|
105
|
+
* Forces nothing it was not told to by the machine's owner (S4 item 2): with no
|
|
106
|
+
* `[codex] auth` in `config.toml` the mark decides, and a saved login stays.
|
|
38
107
|
*/
|
|
39
108
|
export declare function repairCodexAuth(options?: {
|
|
40
109
|
auth?: CodexAuthMode;
|
|
@@ -42,20 +111,73 @@ export declare function repairCodexAuth(options?: {
|
|
|
42
111
|
}): CodexHome;
|
|
43
112
|
/**
|
|
44
113
|
* Drop a linked auth.json so a device-code login writes our own file instead.
|
|
45
|
-
*
|
|
46
|
-
* Only ever called AFTER a login has actually succeeded in the staging home —
|
|
47
|
-
* never as a pre-step. Detaching first meant an abandoned or timed-out sign-in
|
|
48
|
-
* left the server permanently "not signed in", recoverable only by restarting
|
|
49
|
-
* the daemon.
|
|
114
|
+
* Never a real credential: only a symlink is removed.
|
|
50
115
|
*/
|
|
51
116
|
export declare function detachLinkedAuth(dir?: string): void;
|
|
117
|
+
export interface StoredCodexLogin {
|
|
118
|
+
id: string;
|
|
119
|
+
/** The saved row whose login this one replaced – the same subscription (D19). */
|
|
120
|
+
replaced?: string;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Move a login file into the store (§8 `agent_account_login_code` for Codex, S4
|
|
124
|
+
* items 4 and 5) – by rename, and one subscription, one row.
|
|
125
|
+
*
|
|
126
|
+
* A known `account_id` that a saved row already has REPLACES that row's login:
|
|
127
|
+
* the row keeps its id (refusal marks and a live session are keyed by it, R15)
|
|
128
|
+
* and its `addedAt` becomes now – the date is that of the login the row holds,
|
|
129
|
+
* and a second sign-in of the same subscription must change something the
|
|
130
|
+
* window can see (S3 hands this over: `deviceSignInResult`). An unknown key
|
|
131
|
+
* merges with nothing. The machine row takes no part: it is the host's own file.
|
|
132
|
+
*
|
|
133
|
+
* The record is written BEFORE the file moves, so a failure never leaves a login
|
|
134
|
+
* nobody can list or forget; a move that fails takes its new record back.
|
|
135
|
+
*/
|
|
136
|
+
export declare function storeCodexLogin(source: string, options: {
|
|
137
|
+
activate: boolean;
|
|
138
|
+
home?: string;
|
|
139
|
+
replaceActive?: boolean;
|
|
140
|
+
}): StoredCodexLogin;
|
|
52
141
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
142
|
+
* Make a row the one the home uses: the mark first, then the link.
|
|
143
|
+
*
|
|
144
|
+
* In that order on purpose – a daemon that dies between the two leaves a mark
|
|
145
|
+
* the next repair finishes (it points the link where the mark says), never a
|
|
146
|
+
* link that the next repair would undo.
|
|
147
|
+
*
|
|
148
|
+
* A real file in place of the link is dealt with FIRST (§8 `activate`, S4 item
|
|
149
|
+
* 7): the CLI may have refreshed the login in use a moment ago, and repointing
|
|
150
|
+
* over it would throw that token away.
|
|
151
|
+
*/
|
|
152
|
+
export declare function markActiveCodexLogin(id: string, dir?: string): void;
|
|
153
|
+
export declare function setCodexActiveLogin(id: string, dir?: string): CodexHome;
|
|
154
|
+
/**
|
|
155
|
+
* A fresh throwaway home for one device-code sign-in (config only, no credential).
|
|
156
|
+
*
|
|
157
|
+
* One directory per attempt, and every other one is removed first: only one
|
|
158
|
+
* sign-in runs on a machine at a time (the relay cancels the previous one before
|
|
159
|
+
* it gets here), and the pre-S4 runner kept a single fixed staging home that was
|
|
160
|
+
* never cleaned up – on this machine it had lain there since 01.08.2026.
|
|
55
161
|
*/
|
|
56
|
-
export declare function adoptLoginResult(stagingDir: string, dir?: string): boolean;
|
|
57
|
-
/** Seed a throwaway home for the device-code flow (config only, no credential). */
|
|
58
162
|
export declare function prepareStagingHome(): string;
|
|
59
|
-
/** Remove
|
|
60
|
-
export declare function discardStagingHome(): void;
|
|
163
|
+
/** Remove one sign-in's home whatever the outcome — it may hold a credential. */
|
|
164
|
+
export declare function discardStagingHome(dir: string): void;
|
|
165
|
+
/**
|
|
166
|
+
* Remove every sign-in home – at daemon start (nothing can be signing in yet)
|
|
167
|
+
* and before a new sign-in. Includes the fixed-path staging home of runners
|
|
168
|
+
* before S4.
|
|
169
|
+
*/
|
|
170
|
+
export declare function discardAbandonedCodexStagingHomes(): number;
|
|
171
|
+
/**
|
|
172
|
+
* A device-code sign-in in `stagingDir` finished: its login becomes a saved
|
|
173
|
+
* account – by rename, deduplicated by subscription – and the one in use (R15).
|
|
174
|
+
* The staging home is removed either way.
|
|
175
|
+
*
|
|
176
|
+
* `replaceActive` – the sign-in came through the one-login window, where it has
|
|
177
|
+
* always meant «this login from now on»: it takes the place of the login in use
|
|
178
|
+
* rather than adding a row (D27, for an organization without several logins).
|
|
179
|
+
*/
|
|
180
|
+
export declare function adoptLoginResult(stagingDir: string, options?: {
|
|
181
|
+
replaceActive?: boolean;
|
|
182
|
+
}): StoredCodexLogin;
|
|
61
183
|
//# sourceMappingURL=codex-home.d.ts.map
|