@bridge4dev/runner 0.65.1 → 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 +43 -0
- package/dist/account-commands.js +142 -0
- package/dist/adapters/claude-usage.d.ts +79 -15
- package/dist/adapters/claude-usage.js +143 -67
- package/dist/adapters/claude.d.ts +30 -1
- package/dist/adapters/claude.js +171 -11
- 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/adapters/types.d.ts +22 -0
- package/dist/agent-auth.d.ts +100 -0
- package/dist/agent-auth.js +273 -15
- package/dist/auth-relay.d.ts +114 -26
- package/dist/auth-relay.js +464 -278
- package/dist/claude-homes.d.ts +433 -0
- package/dist/claude-homes.js +1550 -0
- package/dist/codex-accounts.d.ts +79 -0
- package/dist/codex-accounts.js +386 -0
- package/dist/commit-message.js +4 -1
- package/dist/config.d.ts +16 -16
- package/dist/config.js +4 -1
- package/dist/index.js +78 -10
- package/dist/login-marks.d.ts +42 -0
- package/dist/login-marks.js +70 -0
- package/dist/protocol.d.ts +38 -38
- package/dist/protocol.js +3 -1
- package/dist/supervisor.js +17 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/agent-auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { log } from './log.js';
|
|
3
4
|
import { configDir } from './paths.js';
|
|
4
5
|
/**
|
|
5
6
|
* A Claude login this runner owns, for the one CLI that cannot store one.
|
|
@@ -40,17 +41,235 @@ export function agentAuthPath() {
|
|
|
40
41
|
* Eleven months keeps it inside the token's own life while still expiring.
|
|
41
42
|
*/
|
|
42
43
|
const TOKEN_MAX_AGE_MS = 334 * 24 * 60 * 60 * 1000;
|
|
44
|
+
const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
45
|
+
const shortString = (value, max) => typeof value === 'string' && value.length > 0 && value.length <= max ? value : undefined;
|
|
46
|
+
const isoString = (value) => {
|
|
47
|
+
const text = shortString(value, 40);
|
|
48
|
+
return text !== undefined && Number.isFinite(Date.parse(text)) ? text : undefined;
|
|
49
|
+
};
|
|
50
|
+
function readIdentity(value) {
|
|
51
|
+
if (!isObject(value))
|
|
52
|
+
return undefined;
|
|
53
|
+
const at = isoString(value['at']);
|
|
54
|
+
if (!at)
|
|
55
|
+
return undefined;
|
|
56
|
+
const email = shortString(value['email'], 320);
|
|
57
|
+
const orgId = shortString(value['orgId'], 100);
|
|
58
|
+
const orgName = shortString(value['orgName'], 200);
|
|
59
|
+
const plan = shortString(value['plan'], 60);
|
|
60
|
+
return {
|
|
61
|
+
...(email ? { email } : {}),
|
|
62
|
+
...(orgId ? { orgId } : {}),
|
|
63
|
+
...(orgName ? { orgName } : {}),
|
|
64
|
+
...(plan ? { plan } : {}),
|
|
65
|
+
at,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** A list of account records, each read on its own: one unreadable entry drops only itself. */
|
|
69
|
+
function parseRecords(entries) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
return entries.flatMap((entry) => {
|
|
72
|
+
if (!isObject(entry))
|
|
73
|
+
return [];
|
|
74
|
+
const id = shortString(entry['id'], 64);
|
|
75
|
+
const addedAt = isoString(entry['addedAt']);
|
|
76
|
+
if (!id || !addedAt || seen.has(id))
|
|
77
|
+
return [];
|
|
78
|
+
seen.add(id);
|
|
79
|
+
const identity = readIdentity(entry['lastSeenIdentity']);
|
|
80
|
+
const expiredAt = isoString(entry['loginExpiredAt']);
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
id,
|
|
84
|
+
addedAt,
|
|
85
|
+
...(identity ? { lastSeenIdentity: identity } : {}),
|
|
86
|
+
...(expiredAt ? { loginExpiredAt: expiredAt } : {}),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The file as it was written, field by field.
|
|
93
|
+
*
|
|
94
|
+
* Forgiving one level down, like every JSON this project stores (CLAUDE.md: never
|
|
95
|
+
* `.strict()` on stored JSON): a record we cannot read is dropped, not the file.
|
|
96
|
+
* A person can edit this file, and a pre-#422 runner never wrote the new fields –
|
|
97
|
+
* both have to read as a working machine login, not as «signed out» (К7).
|
|
98
|
+
*/
|
|
99
|
+
function parse(raw) {
|
|
100
|
+
if (!isObject(raw))
|
|
101
|
+
return {};
|
|
102
|
+
const file = {};
|
|
103
|
+
if (typeof raw['claudeOauthToken'] === 'string')
|
|
104
|
+
file.claudeOauthToken = raw['claudeOauthToken'];
|
|
105
|
+
if (typeof raw['updatedAt'] === 'string')
|
|
106
|
+
file.updatedAt = raw['updatedAt'];
|
|
107
|
+
if (Array.isArray(raw['claudeAccounts']))
|
|
108
|
+
file.claudeAccounts = parseRecords(raw['claudeAccounts']);
|
|
109
|
+
if (Array.isArray(raw['codexAccounts']))
|
|
110
|
+
file.codexAccounts = parseRecords(raw['codexAccounts']);
|
|
111
|
+
const active = shortString(raw['claudeActiveAccount'], 64);
|
|
112
|
+
if (active)
|
|
113
|
+
file.claudeActiveAccount = active;
|
|
114
|
+
if (isObject(raw['claudeMachine'])) {
|
|
115
|
+
const identity = readIdentity(raw['claudeMachine']['lastSeenIdentity']);
|
|
116
|
+
const expiredAt = isoString(raw['claudeMachine']['loginExpiredAt']);
|
|
117
|
+
file.claudeMachine = {
|
|
118
|
+
...(identity ? { lastSeenIdentity: identity } : {}),
|
|
119
|
+
...(expiredAt ? { loginExpiredAt: expiredAt } : {}),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return file;
|
|
123
|
+
}
|
|
124
|
+
const KNOWN_KEYS = new Set([
|
|
125
|
+
'claudeOauthToken',
|
|
126
|
+
'updatedAt',
|
|
127
|
+
'claudeAccounts',
|
|
128
|
+
'claudeActiveAccount',
|
|
129
|
+
'claudeMachine',
|
|
130
|
+
'codexAccounts',
|
|
131
|
+
]);
|
|
132
|
+
const RECORD_KEYS = new Set(['id', 'addedAt', 'lastSeenIdentity', 'loginExpiredAt']);
|
|
133
|
+
const MACHINE_KEYS = new Set(['lastSeenIdentity', 'loginExpiredAt']);
|
|
134
|
+
const IDENTITY_KEYS = new Set(['email', 'orgId', 'orgName', 'plan', 'at']);
|
|
135
|
+
/** The fields of `raw` this runner does not know – to be written back untouched. */
|
|
136
|
+
function unknownFields(raw, known) {
|
|
137
|
+
if (!isObject(raw))
|
|
138
|
+
return {};
|
|
139
|
+
return Object.fromEntries(Object.entries(raw).filter(([key]) => !known.has(key)));
|
|
140
|
+
}
|
|
141
|
+
/** A known object written over the unknown fields the same object had on disk. */
|
|
142
|
+
function mergeObject(raw, known, keys, identityRaw) {
|
|
143
|
+
if (!known)
|
|
144
|
+
return undefined;
|
|
145
|
+
const merged = { ...unknownFields(raw, keys), ...known };
|
|
146
|
+
const identity = known.lastSeenIdentity;
|
|
147
|
+
if (identity)
|
|
148
|
+
merged['lastSeenIdentity'] = { ...unknownFields(identityRaw, IDENTITY_KEYS), ...identity };
|
|
149
|
+
return merged;
|
|
150
|
+
}
|
|
151
|
+
/** The lists of account records – both agents keep theirs in the same shape (#422 S4). */
|
|
152
|
+
const RECORD_LISTS = new Set(['claudeAccounts', 'codexAccounts']);
|
|
153
|
+
/**
|
|
154
|
+
* One list of records as it is to be written: every record this runner knows,
|
|
155
|
+
* over the unknown fields the same record had on disk, followed by the records it
|
|
156
|
+
* could not read at all. `undefined` – the list is not written.
|
|
157
|
+
*/
|
|
158
|
+
function mergeRecordList(original, list, known) {
|
|
159
|
+
const rawAccounts = Array.isArray(original[list]) ? original[list] : [];
|
|
160
|
+
const rawById = new Map(rawAccounts.filter(isObject).map((entry) => [String(entry['id']), entry]));
|
|
161
|
+
const readable = new Set((parse(original)[list] ?? []).map((record) => record.id));
|
|
162
|
+
const accounts = (known ?? []).map((record) => {
|
|
163
|
+
const rawEntry = rawById.get(record.id);
|
|
164
|
+
return mergeObject(rawEntry, record, RECORD_KEYS, isObject(rawEntry) ? rawEntry['lastSeenIdentity'] : undefined);
|
|
165
|
+
});
|
|
166
|
+
const unreadable = rawAccounts.filter((entry) => !isObject(entry) || !readable.has(String(entry['id'])));
|
|
167
|
+
if (accounts.length + unreadable.length === 0)
|
|
168
|
+
return undefined;
|
|
169
|
+
if (!known && unreadable.length === 0)
|
|
170
|
+
return undefined;
|
|
171
|
+
return [...accounts, ...unreadable];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The file to write: what this runner knows, over everything a newer runner put
|
|
175
|
+
* there – at the top level, inside each account record and its identity, and
|
|
176
|
+
* whole records this runner could not read at all (a newer id format must not
|
|
177
|
+
* lose its home's record on the first write after a downgrade).
|
|
178
|
+
*/
|
|
179
|
+
function withForeignFields(original, known) {
|
|
180
|
+
const body = unknownFields(original, KNOWN_KEYS);
|
|
181
|
+
for (const [key, value] of Object.entries(known)) {
|
|
182
|
+
if (value === undefined || RECORD_LISTS.has(key))
|
|
183
|
+
continue;
|
|
184
|
+
if (key === 'claudeMachine')
|
|
185
|
+
continue;
|
|
186
|
+
body[key] = value;
|
|
187
|
+
}
|
|
188
|
+
for (const list of RECORD_LISTS) {
|
|
189
|
+
const records = mergeRecordList(original, list, known[list]);
|
|
190
|
+
if (records)
|
|
191
|
+
body[list] = records;
|
|
192
|
+
}
|
|
193
|
+
if (known.claudeMachine) {
|
|
194
|
+
const rawMachine = original['claudeMachine'];
|
|
195
|
+
body['claudeMachine'] = mergeObject(rawMachine, known.claudeMachine, MACHINE_KEYS, isObject(rawMachine) ? rawMachine['lastSeenIdentity'] : undefined);
|
|
196
|
+
}
|
|
197
|
+
return body;
|
|
198
|
+
}
|
|
43
199
|
function read() {
|
|
44
200
|
try {
|
|
45
|
-
|
|
46
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
47
|
-
return {};
|
|
48
|
-
return parsed;
|
|
201
|
+
return parse(JSON.parse(fs.readFileSync(agentAuthPath(), 'utf8')));
|
|
49
202
|
}
|
|
50
203
|
catch {
|
|
51
204
|
return {};
|
|
52
205
|
}
|
|
53
206
|
}
|
|
207
|
+
/** Everything this runner keeps in `agent-auth.json`, read forgivingly. */
|
|
208
|
+
export function readAgentAuth() {
|
|
209
|
+
return read();
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Change some fields of the file and keep every other one.
|
|
213
|
+
*
|
|
214
|
+
* Read → change → write, never «write what I know». The two writers this file
|
|
215
|
+
* had before #422 wrote the WHOLE file (`storeClaudeToken`) or deleted it
|
|
216
|
+
* (`clearStoredClaudeToken`), and with a list of accounts beside the token either
|
|
217
|
+
* would have erased every saved login's record on its way past (S1 item 3).
|
|
218
|
+
*
|
|
219
|
+
* A file that exists but cannot be parsed is moved aside, not overwritten: it may
|
|
220
|
+
* be the only record of which home belongs to which account, and «unreadable»
|
|
221
|
+
* must not quietly become «empty». A file that cannot be READ (EACCES) throws –
|
|
222
|
+
* writing over something we could not look at is the same mistake.
|
|
223
|
+
*
|
|
224
|
+
* Synchronous from read to rename, so two changes inside this process cannot
|
|
225
|
+
* interleave. Same write-then-rename as the config, 0600.
|
|
226
|
+
*/
|
|
227
|
+
export function updateAgentAuth(change) {
|
|
228
|
+
const dir = configDir();
|
|
229
|
+
const target = agentAuthPath();
|
|
230
|
+
let current = {};
|
|
231
|
+
// What a NEWER runner wrote and this one does not know – kept as it is, so a
|
|
232
|
+
// downgrade followed by a login does not erase what the upgrade recorded.
|
|
233
|
+
let original = {};
|
|
234
|
+
let raw = null;
|
|
235
|
+
try {
|
|
236
|
+
raw = fs.readFileSync(target, 'utf8');
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
if (error.code !== 'ENOENT')
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
if (raw !== null) {
|
|
243
|
+
try {
|
|
244
|
+
const parsed = JSON.parse(raw);
|
|
245
|
+
if (!isObject(parsed))
|
|
246
|
+
throw new Error('not an object');
|
|
247
|
+
current = parse(parsed);
|
|
248
|
+
original = parsed;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
const aside = `${target}.unreadable-${Date.now()}`;
|
|
252
|
+
fs.renameSync(target, aside);
|
|
253
|
+
log.warn('agent-auth: the file could not be parsed – moved aside, starting a new one', {
|
|
254
|
+
aside,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
change(current);
|
|
259
|
+
const body = withForeignFields(original, current);
|
|
260
|
+
if (Object.keys(body).length === 0) {
|
|
261
|
+
fs.rmSync(target, { force: true });
|
|
262
|
+
return current;
|
|
263
|
+
}
|
|
264
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
265
|
+
// A torn file here reads as «signed out» and sends somebody through the whole
|
|
266
|
+
// login again.
|
|
267
|
+
const tmp = path.join(dir, `.agent-auth.${process.pid}.tmp`);
|
|
268
|
+
fs.writeFileSync(tmp, `${JSON.stringify(body, null, 2)}\n`, { mode: FILE_MODE });
|
|
269
|
+
fs.renameSync(tmp, target);
|
|
270
|
+
fs.chmodSync(target, FILE_MODE);
|
|
271
|
+
return current;
|
|
272
|
+
}
|
|
54
273
|
/** The token we hold for Claude, or null once it is too old to trust. */
|
|
55
274
|
export function storedClaudeToken() {
|
|
56
275
|
const file = read();
|
|
@@ -62,23 +281,30 @@ export function storedClaudeToken() {
|
|
|
62
281
|
return null;
|
|
63
282
|
return value;
|
|
64
283
|
}
|
|
284
|
+
/** When the stored token was captured, in ms – null without one. */
|
|
285
|
+
export function storedClaudeTokenAtMs() {
|
|
286
|
+
const file = read();
|
|
287
|
+
if (typeof file.claudeOauthToken !== 'string' || !file.claudeOauthToken)
|
|
288
|
+
return null;
|
|
289
|
+
const stamped = typeof file.updatedAt === 'string' ? Date.parse(file.updatedAt) : NaN;
|
|
290
|
+
return Number.isFinite(stamped) ? stamped : null;
|
|
291
|
+
}
|
|
65
292
|
export function storeClaudeToken(token) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// out» and sends somebody through the whole login again.
|
|
71
|
-
const tmp = path.join(dir, `.agent-auth.${process.pid}.tmp`);
|
|
72
|
-
fs.writeFileSync(tmp, `${JSON.stringify({ claudeOauthToken: token, updatedAt: new Date().toISOString() }, null, 2)}\n`, { mode: FILE_MODE });
|
|
73
|
-
fs.renameSync(tmp, file);
|
|
74
|
-
fs.chmodSync(file, FILE_MODE);
|
|
293
|
+
updateAgentAuth((file) => {
|
|
294
|
+
file.claudeOauthToken = token;
|
|
295
|
+
file.updatedAt = new Date().toISOString();
|
|
296
|
+
});
|
|
75
297
|
}
|
|
298
|
+
/** Drop the stored token – and nothing else: the accounts beside it stay (#422). */
|
|
76
299
|
export function clearStoredClaudeToken() {
|
|
77
300
|
try {
|
|
78
|
-
|
|
301
|
+
updateAgentAuth((file) => {
|
|
302
|
+
delete file.claudeOauthToken;
|
|
303
|
+
delete file.updatedAt;
|
|
304
|
+
});
|
|
79
305
|
}
|
|
80
306
|
catch {
|
|
81
|
-
/* nothing to clear */
|
|
307
|
+
/* nothing to clear, or nothing we may touch */
|
|
82
308
|
}
|
|
83
309
|
}
|
|
84
310
|
/**
|
|
@@ -98,9 +324,41 @@ export function applyStoredClaudeToken() {
|
|
|
98
324
|
const token = storedClaudeToken();
|
|
99
325
|
if (!token)
|
|
100
326
|
return false;
|
|
327
|
+
// A token a session was refused with stays on disk (#422, D2), but it is not
|
|
328
|
+
// put back into the environment: there it would outrank a fresh
|
|
329
|
+
// `~/.claude/.credentials.json` for every session, on every daemon start,
|
|
330
|
+
// until somebody found this file by hand.
|
|
331
|
+
if (storedClaudeTokenRefused())
|
|
332
|
+
return false;
|
|
101
333
|
process.env['CLAUDE_CODE_OAUTH_TOKEN'] = token;
|
|
102
334
|
return true;
|
|
103
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Was a session refused with the token this runner captured – after it was
|
|
338
|
+
* captured? A token stored after the refusal is newer evidence and outranks it.
|
|
339
|
+
* A token with no readable capture date is judged by the mark alone.
|
|
340
|
+
*/
|
|
341
|
+
export function storedClaudeTokenRefused() {
|
|
342
|
+
const file = read();
|
|
343
|
+
if (!file.claudeOauthToken)
|
|
344
|
+
return false;
|
|
345
|
+
const marked = file.claudeMachine?.loginExpiredAt;
|
|
346
|
+
if (!marked)
|
|
347
|
+
return false;
|
|
348
|
+
const markedMs = Date.parse(marked);
|
|
349
|
+
if (!Number.isFinite(markedMs))
|
|
350
|
+
return false;
|
|
351
|
+
const capturedMs = typeof file.updatedAt === 'string' ? Date.parse(file.updatedAt) : NaN;
|
|
352
|
+
return !Number.isFinite(capturedMs) || capturedMs <= markedMs;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The daemon's environment carries exactly the token this runner captured – so
|
|
356
|
+
* a machine-login session refused now was refused WITH it.
|
|
357
|
+
*/
|
|
358
|
+
export function environmentCarriesStoredToken() {
|
|
359
|
+
const token = storedClaudeToken();
|
|
360
|
+
return !!token && process.env['CLAUDE_CODE_OAUTH_TOKEN'] === token;
|
|
361
|
+
}
|
|
104
362
|
/**
|
|
105
363
|
* The OAuth token `claude setup-token` printed, reassembled out of pty output.
|
|
106
364
|
*
|
package/dist/auth-relay.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type RelayAgentName } from './agent-registry.js';
|
|
2
|
+
import { type AccountCard } from './claude-homes.js';
|
|
2
3
|
/**
|
|
3
4
|
* The agents the login relay can drive — the registry's wire alphabet (Р15).
|
|
4
5
|
*
|
|
@@ -18,6 +19,23 @@ export interface LoginCodeResult {
|
|
|
18
19
|
ok: boolean;
|
|
19
20
|
detail?: string;
|
|
20
21
|
}
|
|
22
|
+
/** Where a sign-in through the account commands lands (#422 §8). */
|
|
23
|
+
export type AccountLoginTarget = 'machine' | 'saved';
|
|
24
|
+
/**
|
|
25
|
+
* `agent_account_login_code` – the row that signed in, or why not.
|
|
26
|
+
*
|
|
27
|
+
* A wrong code is an answer, not an exception, exactly as in the old flow: the
|
|
28
|
+
* CLI usually asks again, and the relay stays alive for the corrected code.
|
|
29
|
+
*/
|
|
30
|
+
export type AccountLoginCodeResult = {
|
|
31
|
+
ok: true;
|
|
32
|
+
account: AccountCard;
|
|
33
|
+
active: string;
|
|
34
|
+
replaced?: string;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
detail: string;
|
|
38
|
+
};
|
|
21
39
|
export interface AgentAuthStatus {
|
|
22
40
|
/**
|
|
23
41
|
* `missing` — never signed in here (or the CLI is not installed).
|
|
@@ -31,6 +49,16 @@ export interface AgentAuthStatus {
|
|
|
31
49
|
status: 'ok' | 'expired' | 'missing' | 'unknown';
|
|
32
50
|
expiresAt?: string;
|
|
33
51
|
detail?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Which account this verdict is about (#422 §8) – the one the next session
|
|
54
|
+
* starts under. Identity from what the runner last read, never from a CLI call
|
|
55
|
+
* in the poll (R14). Optional on the wire: an older API strips it.
|
|
56
|
+
*/
|
|
57
|
+
activeAccount?: {
|
|
58
|
+
id: string;
|
|
59
|
+
email?: string;
|
|
60
|
+
orgId?: string;
|
|
61
|
+
};
|
|
34
62
|
}
|
|
35
63
|
/**
|
|
36
64
|
* Rejoin a secret the pty split across lines, so the masker can see it.
|
|
@@ -60,9 +88,48 @@ export declare class AuthRelay {
|
|
|
60
88
|
constructor(commands?: Record<RelayAgent, string>, deps?: AuthRelayDeps);
|
|
61
89
|
/** Start (or restart) a login flow and wait until the sign-in URL appears. */
|
|
62
90
|
start(agent: RelayAgent): Promise<LoginStartResult>;
|
|
91
|
+
/**
|
|
92
|
+
* `agent_account_login_start` for Claude (#422 §8, R2).
|
|
93
|
+
*
|
|
94
|
+
* `saved` signs in inside a fresh staging home (`CLAUDE_CONFIG_DIR`), which
|
|
95
|
+
* becomes a saved account only when the code is accepted – the machine's own
|
|
96
|
+
* login is not touched by it at all. `machine` is today's sign-in into
|
|
97
|
+
* `~/.claude`, the one legitimate way to write that file, pressed by a person.
|
|
98
|
+
*
|
|
99
|
+
* Either way the CLI runs WITHOUT the operator's `CLAUDE_CODE_OAUTH_TOKEN`
|
|
100
|
+
* (and API keys): the CLI puts that variable above every login file, so a
|
|
101
|
+
* sign-in started with it could report the token's blind identity instead of
|
|
102
|
+
* the account that just signed in.
|
|
103
|
+
*
|
|
104
|
+
* No `setup-token` fallback for `saved`: that command stores nothing in a home
|
|
105
|
+
* (it prints an inference-only token, D1), so a «saved account» made from it
|
|
106
|
+
* would be a row with no login. `machine` keeps the fallback it always had.
|
|
107
|
+
*/
|
|
108
|
+
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent): Promise<LoginStartResult>;
|
|
109
|
+
/** The CLI and the pty helper are both there – or a sentence saying which is not. */
|
|
110
|
+
private assertCanRun;
|
|
63
111
|
private startWith;
|
|
64
112
|
/** Paste the confirmation code back into the waiting CLI (Claude flow). */
|
|
65
113
|
submitCode(agent: RelayAgent, code: string): Promise<LoginCodeResult>;
|
|
114
|
+
/**
|
|
115
|
+
* `agent_account_login_code` – finish a sign-in the account commands started.
|
|
116
|
+
*
|
|
117
|
+
* `saved`: the staging home is adopted by rename – one subscription, one row,
|
|
118
|
+
* the signed-in row active (`adoptLoginResult`, R15). `machine`: the same
|
|
119
|
+
* check the old door makes, then the machine login becomes active and its
|
|
120
|
+
* identity is asked once, so the answer names who ACTUALLY signed in (§2:
|
|
121
|
+
* «Signed in as B, not A») rather than who the machine was before.
|
|
122
|
+
*/
|
|
123
|
+
submitAccountCode(code: string): Promise<AccountLoginCodeResult>;
|
|
124
|
+
/**
|
|
125
|
+
* Write the code, wait for the CLI's answer.
|
|
126
|
+
*
|
|
127
|
+
* `{ raw }` – the CLI exited 0 and the relay is released (its staging home, if
|
|
128
|
+
* any, is kept for the caller to adopt). Anything else is the answer to hand
|
|
129
|
+
* back: a non-zero exit or a timeout ends the sign-in; a line saying «invalid»
|
|
130
|
+
* keeps it alive, because the CLI usually asks again.
|
|
131
|
+
*/
|
|
132
|
+
private exchangeCode;
|
|
66
133
|
/**
|
|
67
134
|
* The CLI exited 0 — but is the machine actually signed in?
|
|
68
135
|
*
|
|
@@ -73,47 +140,68 @@ export declare class AuthRelay {
|
|
|
73
140
|
*/
|
|
74
141
|
private confirmSignedIn;
|
|
75
142
|
cancel(): void;
|
|
143
|
+
/**
|
|
144
|
+
* End THIS sign-in: its process, its slot if it still holds it, its staging
|
|
145
|
+
* home. A loop driving an older sign-in must never end a newer one that took
|
|
146
|
+
* the slot meanwhile – with `cancel()` in its place, two starts close together
|
|
147
|
+
* killed each other, and a stale code exchange killed the newer start (found by
|
|
148
|
+
* the independent check of S2).
|
|
149
|
+
*/
|
|
150
|
+
private abandon;
|
|
151
|
+
/** Stop driving a relay: its process, its timer, its slot. Its staging home stays. */
|
|
152
|
+
private release;
|
|
153
|
+
/** A sign-in that will not be adopted leaves nothing behind (§8, S2 item 2). */
|
|
154
|
+
private discardStaging;
|
|
76
155
|
}
|
|
77
156
|
/**
|
|
78
|
-
* Claude: the
|
|
79
|
-
* The RUNNER reads it (its own host user's file) — the agent itself is still
|
|
80
|
-
* denied this path by layer-1 policy.
|
|
157
|
+
* Claude: the verdict on the account the next session starts under (#422 R14).
|
|
81
158
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
159
|
+
* The machine login – the pre-#422 rule word for word: the operator's variable,
|
|
160
|
+
* then `~/.claude/.credentials.json`, then a token this runner captured. A
|
|
161
|
+
* saved account – ONLY its own credentials file: a session under it has the
|
|
162
|
+
* operator's token taken out of its environment, so letting the variable or the
|
|
163
|
+
* captured token answer here would paint a green verdict over a switch that
|
|
164
|
+
* does not work (#121, К14). Nothing here runs a CLI; the file judge and its
|
|
165
|
+
* reasons live in `claude-homes.ts` so the account list reads the same lines.
|
|
89
166
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* would have cost a ~800 ms / ~300 MB subprocess per poll under `MemoryMax=2G`
|
|
96
|
-
* (gotcha #100) and echoed the account's e-mail and org name to every member of
|
|
97
|
-
* the organization, in exchange for no truth at all. A revoked login is caught
|
|
98
|
-
* instead by `noteAgentAuthFailure` below — from a real refusal, not a guess.
|
|
167
|
+
* The RUNNER reads these files (its own host user's) — the agent itself is still
|
|
168
|
+
* denied these paths by layer-1 policy.
|
|
169
|
+
*
|
|
170
|
+
* `account: 'machine'` asks about the machine login whatever is active – what the
|
|
171
|
+
* sign-in relay needs after writing `~/.claude`.
|
|
99
172
|
*/
|
|
100
|
-
export declare function claudeAuthStatus(homedir?: string
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/** The agent just worked — whatever was wrong with the sign-in is not. */
|
|
104
|
-
export declare function clearAgentAuthFailure(agent: RelayAgent): void;
|
|
173
|
+
export declare function claudeAuthStatus(homedir?: string, options?: {
|
|
174
|
+
account?: 'active' | 'machine';
|
|
175
|
+
}): Promise<AgentAuthStatus>;
|
|
105
176
|
/**
|
|
106
|
-
*
|
|
177
|
+
* A session just failed to authenticate as this agent – under this account.
|
|
178
|
+
*
|
|
179
|
+
* `accountId` is the account the SESSION started under (R14), not whatever the
|
|
180
|
+
* machine has active now. Nothing is deleted any more (#422 S1 item 3, D2): the
|
|
181
|
+
* old code threw a captured Claude token away on the spot, and that meant
|
|
182
|
+
* erasing the file the saved accounts are listed in. A Claude refusal is kept as
|
|
183
|
+
* a mark on the row instead – it outlives a daemon restart, and a login written
|
|
184
|
+
* after it (a refresh, a new sign-in) outranks it.
|
|
185
|
+
*/
|
|
186
|
+
export declare function noteAgentAuthFailure(agent: RelayAgent, accountId?: string): void;
|
|
187
|
+
/** The agent just worked under this account — whatever was wrong with its sign-in is not. */
|
|
188
|
+
export declare function clearAgentAuthFailure(agent: RelayAgent, accountId?: string): void;
|
|
189
|
+
/**
|
|
190
|
+
* Is a refusal still being held against this agent (and account)?
|
|
107
191
|
*
|
|
108
192
|
* Exported so the wiring in `supervisor.ts` can be pinned by a test without
|
|
109
193
|
* shelling out to the agent CLIs: this predicate IS the mechanism the panel's
|
|
110
194
|
* demotion reads, and three unguarded lines were carrying it (QA-117 M6).
|
|
111
195
|
*/
|
|
112
|
-
export declare function agentAuthFailureActive(agent: RelayAgent): boolean;
|
|
196
|
+
export declare function agentAuthFailureActive(agent: RelayAgent, accountId?: string): boolean;
|
|
113
197
|
/**
|
|
114
198
|
* Codex reports its own login state via an exit code (0 signed in / 1 not).
|
|
115
199
|
* Probed against the RUNNER's home: the host user can be signed in while our
|
|
116
200
|
* isolated home is not, and it is ours that sessions use.
|
|
201
|
+
*
|
|
202
|
+
* About the login new sessions start under (#422 S4): the machine login through
|
|
203
|
+
* the link, or the saved login the link points at – named in `activeAccount`,
|
|
204
|
+
* read locally from the file, no CLI for it (R14).
|
|
117
205
|
*/
|
|
118
206
|
export declare function codexAuthStatus(): Promise<AgentAuthStatus>;
|
|
119
207
|
export declare function agentAuthStatuses(): Promise<{
|