@bridge4dev/runner 0.67.0 → 0.68.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 +22 -2
- package/dist/account-commands.js +56 -9
- package/dist/adapters/codex-home.d.ts +1 -0
- package/dist/adapters/codex-home.js +4 -1
- package/dist/auth-relay.d.ts +3 -1
- package/dist/auth-relay.js +23 -7
- package/dist/claude-homes.d.ts +3 -1
- package/dist/claude-homes.js +17 -6
- package/dist/supervisor.d.ts +30 -0
- package/dist/supervisor.js +97 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -33,11 +33,31 @@ export type AccountCommandReply = {
|
|
|
33
33
|
error: string;
|
|
34
34
|
result?: unknown;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* What became of the sessions running when an account was switched (#422 S4.1).
|
|
38
|
+
*
|
|
39
|
+
* `switched` – parked right now, so their next message starts them on the new
|
|
40
|
+
* account with their conversation kept; `pending` – in the middle of a turn,
|
|
41
|
+
* marked and parked the moment that turn ends. A turn is never killed for a
|
|
42
|
+
* switch.
|
|
43
|
+
*/
|
|
44
|
+
export interface AccountApplyReport {
|
|
45
|
+
switched: number;
|
|
46
|
+
pending: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The supervisor's side of «apply to the open sessions» – it is the one that
|
|
50
|
+
* holds them. Absent in the tests that drive the commands alone; then the
|
|
51
|
+
* answer carries no `applied` and nothing was moved.
|
|
52
|
+
*/
|
|
53
|
+
export type AccountApply = (agent: RelayAgent, accountId: string, label?: string) => AccountApplyReport;
|
|
36
54
|
/** The two sign-in steps the commands drive – `AuthRelay`, or a test double. */
|
|
37
55
|
export interface AccountSignIn {
|
|
38
|
-
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent
|
|
56
|
+
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent, options?: {
|
|
57
|
+
activate?: boolean;
|
|
58
|
+
}): Promise<LoginStartResult>;
|
|
39
59
|
submitAccountCode(code: string): Promise<AccountLoginCodeResult>;
|
|
40
60
|
}
|
|
41
61
|
export declare function isAccountCommand(name: string): name is AccountCommand;
|
|
42
|
-
export declare function runAccountCommand(name: AccountCommand, args: Record<string, unknown>, signIn: AccountSignIn): Promise<AccountCommandReply>;
|
|
62
|
+
export declare function runAccountCommand(name: AccountCommand, args: Record<string, unknown>, signIn: AccountSignIn, apply?: AccountApply): Promise<AccountCommandReply>;
|
|
43
63
|
//# sourceMappingURL=account-commands.d.ts.map
|
package/dist/account-commands.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { forgetAccount, listAccounts, setActiveAccount } from './claude-homes.js';
|
|
2
|
-
import { activateCodexAccount, forgetCodexAccount, listCodexAccounts } from './codex-accounts.js';
|
|
1
|
+
import { forgetAccount, listAccounts, readActiveAccountSummary, setActiveAccount, } from './claude-homes.js';
|
|
2
|
+
import { activateCodexAccount, codexActiveAccountSummary, forgetCodexAccount, listCodexAccounts, } from './codex-accounts.js';
|
|
3
3
|
import { AccountError, MACHINE_ACCOUNT_ID, isAccountId } from './login-marks.js';
|
|
4
4
|
import { maskString } from './policy.js';
|
|
5
5
|
/**
|
|
@@ -32,6 +32,15 @@ export const ACCOUNT_COMMANDS = [
|
|
|
32
32
|
* R11). Codex joined in S4 – the same five commands and the same card.
|
|
33
33
|
*/
|
|
34
34
|
export const ACCOUNT_AGENTS = ['claude', 'codex'];
|
|
35
|
+
/**
|
|
36
|
+
* «And make it the one new sessions start on», the window's tick (S4.1).
|
|
37
|
+
*
|
|
38
|
+
* Absent – true: that is what every sign-in did before the tick existed, and an
|
|
39
|
+
* older API sends no such argument.
|
|
40
|
+
*/
|
|
41
|
+
function activateArg(args) {
|
|
42
|
+
return args['activate'] === false ? { activate: false } : {};
|
|
43
|
+
}
|
|
35
44
|
export function isAccountCommand(name) {
|
|
36
45
|
return ACCOUNT_COMMANDS.includes(name);
|
|
37
46
|
}
|
|
@@ -46,10 +55,40 @@ function refusal(error) {
|
|
|
46
55
|
function accountIdArg(value) {
|
|
47
56
|
return value === MACHINE_ACCOUNT_ID || isAccountId(value) ? value : null;
|
|
48
57
|
}
|
|
49
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Who the account now active is, for the note the parked sessions are given.
|
|
60
|
+
*
|
|
61
|
+
* Read locally and never allowed to fail the switch itself: a label is a
|
|
62
|
+
* courtesy, the switch is the work.
|
|
63
|
+
*/
|
|
64
|
+
function labelOf(agent, id) {
|
|
65
|
+
try {
|
|
66
|
+
const summary = agent === 'codex' ? codexActiveAccountSummary(id) : readActiveAccountSummary(id);
|
|
67
|
+
return summary.email;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* «Apply to the sessions already running», when the caller asked for it (S4.1).
|
|
75
|
+
*
|
|
76
|
+
* An ARGUMENT of an existing command rather than a name of its own, which §8
|
|
77
|
+
* otherwise forbids – and safe here for one reason: a runner that does not know
|
|
78
|
+
* it still switches the account, it simply leaves the open sessions where they
|
|
79
|
+
* are. The API tells the two apart by this very field: an answer without
|
|
80
|
+
* `applied` to a request that asked for it IS «that machine is older», and the
|
|
81
|
+
* window says so instead of claiming sessions were moved.
|
|
82
|
+
*/
|
|
83
|
+
function appliedTo(agent, active, args, apply) {
|
|
84
|
+
if (args['applySessions'] !== true || !apply)
|
|
85
|
+
return {};
|
|
86
|
+
return { applied: apply(agent, active, labelOf(agent, active)) };
|
|
87
|
+
}
|
|
88
|
+
export async function runAccountCommand(name, args, signIn, apply) {
|
|
50
89
|
const agent = args['agent'];
|
|
51
90
|
if (agent === 'codex')
|
|
52
|
-
return runCodexAccountCommand(name, args, signIn);
|
|
91
|
+
return runCodexAccountCommand(name, args, signIn, apply);
|
|
53
92
|
if (agent !== 'claude')
|
|
54
93
|
return { ok: false, error: 'agent must be claude or codex' };
|
|
55
94
|
try {
|
|
@@ -63,7 +102,10 @@ export async function runAccountCommand(name, args, signIn) {
|
|
|
63
102
|
if (target !== 'machine' && target !== 'saved') {
|
|
64
103
|
return { ok: false, error: 'target must be machine or saved' };
|
|
65
104
|
}
|
|
66
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
ok: true,
|
|
107
|
+
result: await signIn.startAccountLogin(target, 'claude', activateArg(args)),
|
|
108
|
+
};
|
|
67
109
|
}
|
|
68
110
|
case 'agent_account_login_code': {
|
|
69
111
|
const code = args['code'];
|
|
@@ -79,7 +121,8 @@ export async function runAccountCommand(name, args, signIn) {
|
|
|
79
121
|
return { ok: false, error: 'no such account on this server' };
|
|
80
122
|
// The usage cache is keyed by subscription, so a switch leaves it alone:
|
|
81
123
|
// the new row's figures are already its own (R16, §8).
|
|
82
|
-
|
|
124
|
+
const active = setActiveAccount(id);
|
|
125
|
+
return { ok: true, result: { active, ...appliedTo('claude', active, args, apply) } };
|
|
83
126
|
}
|
|
84
127
|
case 'agent_account_forget': {
|
|
85
128
|
const id = accountIdArg(args['id']);
|
|
@@ -101,7 +144,7 @@ export async function runAccountCommand(name, args, signIn) {
|
|
|
101
144
|
* device sign-in finishes on the provider's page – there is no code to send; the
|
|
102
145
|
* window learns the result from the list (§8).
|
|
103
146
|
*/
|
|
104
|
-
async function runCodexAccountCommand(name, args, signIn) {
|
|
147
|
+
async function runCodexAccountCommand(name, args, signIn, apply) {
|
|
105
148
|
try {
|
|
106
149
|
switch (name) {
|
|
107
150
|
case 'agent_accounts':
|
|
@@ -111,7 +154,10 @@ async function runCodexAccountCommand(name, args, signIn) {
|
|
|
111
154
|
if (target !== 'machine' && target !== 'saved') {
|
|
112
155
|
return { ok: false, error: 'target must be machine or saved' };
|
|
113
156
|
}
|
|
114
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
result: await signIn.startAccountLogin(target, 'codex', activateArg(args)),
|
|
160
|
+
};
|
|
115
161
|
}
|
|
116
162
|
case 'agent_account_login_code': {
|
|
117
163
|
const result = {
|
|
@@ -124,7 +170,8 @@ async function runCodexAccountCommand(name, args, signIn) {
|
|
|
124
170
|
const id = accountIdArg(args['id']);
|
|
125
171
|
if (!id)
|
|
126
172
|
return { ok: false, error: 'no such account on this server' };
|
|
127
|
-
|
|
173
|
+
const active = activateCodexAccount(id);
|
|
174
|
+
return { ok: true, result: { active, ...appliedTo('codex', active, args, apply) } };
|
|
128
175
|
}
|
|
129
176
|
case 'agent_account_forget': {
|
|
130
177
|
const id = accountIdArg(args['id']);
|
|
@@ -179,5 +179,6 @@ export declare function discardAbandonedCodexStagingHomes(): number;
|
|
|
179
179
|
*/
|
|
180
180
|
export declare function adoptLoginResult(stagingDir: string, options?: {
|
|
181
181
|
replaceActive?: boolean;
|
|
182
|
+
activate?: boolean;
|
|
182
183
|
}): StoredCodexLogin;
|
|
183
184
|
//# sourceMappingURL=codex-home.d.ts.map
|
|
@@ -769,7 +769,10 @@ export function adoptLoginResult(stagingDir, options = {}) {
|
|
|
769
769
|
throw new AccountError('no sign-in in progress – start again');
|
|
770
770
|
try {
|
|
771
771
|
return storeCodexLogin(path.join(stagingDir, AUTH_FILENAME), {
|
|
772
|
-
activate
|
|
772
|
+
// `activate` is the window's «and make it the one in use», on unless the
|
|
773
|
+
// person turned it off (S4.1); `replaceActive` is the one-login door,
|
|
774
|
+
// where a sign-in has always taken the place of the login in use (D27).
|
|
775
|
+
activate: options.activate !== false,
|
|
773
776
|
...(options.replaceActive ? { replaceActive: true } : {}),
|
|
774
777
|
});
|
|
775
778
|
}
|
package/dist/auth-relay.d.ts
CHANGED
|
@@ -105,7 +105,9 @@ export declare class AuthRelay {
|
|
|
105
105
|
* (it prints an inference-only token, D1), so a «saved account» made from it
|
|
106
106
|
* would be a row with no login. `machine` keeps the fallback it always had.
|
|
107
107
|
*/
|
|
108
|
-
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent
|
|
108
|
+
startAccountLogin(target: AccountLoginTarget, agent?: RelayAgent, options?: {
|
|
109
|
+
activate?: boolean;
|
|
110
|
+
}): Promise<LoginStartResult>;
|
|
109
111
|
/** The CLI and the pty helper are both there – or a sentence saying which is not. */
|
|
110
112
|
private assertCanRun;
|
|
111
113
|
private startWith;
|
package/dist/auth-relay.js
CHANGED
|
@@ -226,7 +226,10 @@ export class AuthRelay {
|
|
|
226
226
|
* (it prints an inference-only token, D1), so a «saved account» made from it
|
|
227
227
|
* would be a row with no login. `machine` keeps the fallback it always had.
|
|
228
228
|
*/
|
|
229
|
-
async startAccountLogin(target, agent = 'claude') {
|
|
229
|
+
async startAccountLogin(target, agent = 'claude', options = {}) {
|
|
230
|
+
// Default true: that is what a sign-in has always done, and it is what an
|
|
231
|
+
// older API – which sends no such argument – must keep getting.
|
|
232
|
+
const activate = options.activate !== false;
|
|
230
233
|
if (agent === 'codex') {
|
|
231
234
|
// R13: the machine row of Codex is the host user's own `~/.codex/auth.json`,
|
|
232
235
|
// which DevBridge has never written and does not start writing. A sign-in
|
|
@@ -239,13 +242,14 @@ export class AuthRelay {
|
|
|
239
242
|
return this.startWith('codex', this.commands.codex ?? '', false, {
|
|
240
243
|
flow: 'account',
|
|
241
244
|
target: 'saved',
|
|
245
|
+
activate,
|
|
242
246
|
});
|
|
243
247
|
}
|
|
244
248
|
this.cancel();
|
|
245
249
|
this.assertCanRun('claude');
|
|
246
250
|
const command = this.commands.claude ?? '';
|
|
247
251
|
try {
|
|
248
|
-
return await this.startWith('claude', command, false, { flow: 'account', target });
|
|
252
|
+
return await this.startWith('claude', command, false, { flow: 'account', target, activate });
|
|
249
253
|
}
|
|
250
254
|
catch (error) {
|
|
251
255
|
if (!looksLikeUnsupportedSubcommand(String(error)))
|
|
@@ -254,7 +258,11 @@ export class AuthRelay {
|
|
|
254
258
|
throw new Error('the Claude CLI on this server is too old to sign in to a saved account – update Claude Code and try again', { cause: error });
|
|
255
259
|
}
|
|
256
260
|
log.warn('auth-relay: this claude has no `auth login` — falling back to setup-token');
|
|
257
|
-
return this.startWith('claude', CLAUDE_LEGACY_LOGIN, true, {
|
|
261
|
+
return this.startWith('claude', CLAUDE_LEGACY_LOGIN, true, {
|
|
262
|
+
flow: 'account',
|
|
263
|
+
target,
|
|
264
|
+
activate,
|
|
265
|
+
});
|
|
258
266
|
}
|
|
259
267
|
}
|
|
260
268
|
/** The CLI and the pty helper are both there – or a sentence saying which is not. */
|
|
@@ -310,6 +318,7 @@ export class AuthRelay {
|
|
|
310
318
|
captureToken,
|
|
311
319
|
flow: door.flow,
|
|
312
320
|
target: door.target,
|
|
321
|
+
activate: door.activate !== false,
|
|
313
322
|
claudeStaging,
|
|
314
323
|
codexStaging: stagingHome,
|
|
315
324
|
exchanging: false,
|
|
@@ -351,7 +360,12 @@ export class AuthRelay {
|
|
|
351
360
|
// use instead of adding a row beside it: with the organization's option
|
|
352
361
|
// off, «a token overwrites the token» is what a sign-in has always meant
|
|
353
362
|
// (D27), and DevBridge writes no machine login for Codex to overwrite.
|
|
354
|
-
const stored = adoptCodexLogin(staging, {
|
|
363
|
+
const stored = adoptCodexLogin(staging, {
|
|
364
|
+
replaceActive: relay.flow === 'legacy',
|
|
365
|
+
// «Log in, but do not switch to it» (S4.1): the row is stored and
|
|
366
|
+
// listed, the link the sessions start from stays where it is.
|
|
367
|
+
activate: relay.activate,
|
|
368
|
+
});
|
|
355
369
|
log.info('codex: device login completed — credential stored', {
|
|
356
370
|
account: stored.id,
|
|
357
371
|
replaced: Boolean(stored.replaced),
|
|
@@ -452,7 +466,7 @@ export class AuthRelay {
|
|
|
452
466
|
};
|
|
453
467
|
}
|
|
454
468
|
try {
|
|
455
|
-
const adopted = await adoptClaudeLogin(relay.claudeStaging);
|
|
469
|
+
const adopted = await adoptClaudeLogin(relay.claudeStaging, { activate: relay.activate });
|
|
456
470
|
// Adopted: the staging path no longer exists, nothing left to remove.
|
|
457
471
|
relay.claudeStaging = null;
|
|
458
472
|
clearAgentAuthFailure('claude', adopted.account.id);
|
|
@@ -574,7 +588,8 @@ export class AuthRelay {
|
|
|
574
588
|
log.info('auth-relay: stored a long-lived Claude token for this runner');
|
|
575
589
|
clearAgentAuthFailure('claude');
|
|
576
590
|
forgetClaudeUsage();
|
|
577
|
-
|
|
591
|
+
if (relay.activate)
|
|
592
|
+
activateMachineLogin();
|
|
578
593
|
return { ok: true, detail: 'signed in with a long-lived token stored on this server' };
|
|
579
594
|
}
|
|
580
595
|
// `claude auth login` writes the credential just before it exits; give the
|
|
@@ -588,7 +603,8 @@ export class AuthRelay {
|
|
|
588
603
|
if (status.status === 'ok') {
|
|
589
604
|
clearAgentAuthFailure('claude');
|
|
590
605
|
forgetClaudeUsage();
|
|
591
|
-
|
|
606
|
+
if (relay.activate)
|
|
607
|
+
activateMachineLogin();
|
|
592
608
|
return { ok: true };
|
|
593
609
|
}
|
|
594
610
|
await sleep(300);
|
package/dist/claude-homes.d.ts
CHANGED
|
@@ -407,7 +407,9 @@ export interface AdoptResult {
|
|
|
407
407
|
* BEFORE a new home is moved into place, so a failed write never leaves a login
|
|
408
408
|
* nobody can list or forget. The signed-in row becomes active (R15).
|
|
409
409
|
*/
|
|
410
|
-
export declare function adoptLoginResult(stagingDir: string
|
|
410
|
+
export declare function adoptLoginResult(stagingDir: string, options?: {
|
|
411
|
+
activate?: boolean;
|
|
412
|
+
}): Promise<AdoptResult>;
|
|
411
413
|
/**
|
|
412
414
|
* Bring a home that already holds a login into the store – by rename (D17, R7).
|
|
413
415
|
*
|
package/dist/claude-homes.js
CHANGED
|
@@ -1347,7 +1347,12 @@ export function discardStagingHome(dir) {
|
|
|
1347
1347
|
* BEFORE a new home is moved into place, so a failed write never leaves a login
|
|
1348
1348
|
* nobody can list or forget. The signed-in row becomes active (R15).
|
|
1349
1349
|
*/
|
|
1350
|
-
export async function adoptLoginResult(stagingDir) {
|
|
1350
|
+
export async function adoptLoginResult(stagingDir, options = {}) {
|
|
1351
|
+
// «And make it the one new sessions start on» – the window's own tick, on
|
|
1352
|
+
// unless the person turned it off (S4.1). Off: the row is stored and listed,
|
|
1353
|
+
// and the active account does not move. Default true, because that is what a
|
|
1354
|
+
// sign-in has always done and what an older API still asks for.
|
|
1355
|
+
const activate = options.activate !== false;
|
|
1351
1356
|
if (!isStagingDir(stagingDir) || !lstatOrNull(stagingDir)?.isDirectory()) {
|
|
1352
1357
|
throw new AccountError('no sign-in in progress – start again');
|
|
1353
1358
|
}
|
|
@@ -1395,10 +1400,12 @@ export async function adoptLoginResult(stagingDir) {
|
|
|
1395
1400
|
});
|
|
1396
1401
|
throw error;
|
|
1397
1402
|
}
|
|
1398
|
-
|
|
1399
|
-
file
|
|
1400
|
-
|
|
1401
|
-
|
|
1403
|
+
if (activate) {
|
|
1404
|
+
updateAgentAuth((file) => {
|
|
1405
|
+
file.claudeActiveAccount = id;
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
log.info('claude-homes: a new account was added', { account: id, active: activate });
|
|
1402
1409
|
return { account: cardOf(id), active: activeAccountId() };
|
|
1403
1410
|
}
|
|
1404
1411
|
const home = accountHome(same.id);
|
|
@@ -1425,7 +1432,11 @@ export async function adoptLoginResult(stagingDir) {
|
|
|
1425
1432
|
record.lastSeenIdentity = stamped;
|
|
1426
1433
|
delete record.loginExpiredAt;
|
|
1427
1434
|
}
|
|
1428
|
-
|
|
1435
|
+
// The row that signed in again is the SAME row: leaving the pointer alone
|
|
1436
|
+
// when the tick is off keeps whatever was active active – including this
|
|
1437
|
+
// row, when it already was.
|
|
1438
|
+
if (activate)
|
|
1439
|
+
file.claudeActiveAccount = same.id;
|
|
1429
1440
|
});
|
|
1430
1441
|
// A fresh login outranks anything remembered about the one it replaced – on
|
|
1431
1442
|
// disk above, and in this process's memory of refusals here.
|
package/dist/supervisor.d.ts
CHANGED
|
@@ -1144,6 +1144,36 @@ export declare class Supervisor {
|
|
|
1144
1144
|
* rewound (ticket #126).
|
|
1145
1145
|
*/
|
|
1146
1146
|
private park;
|
|
1147
|
+
/**
|
|
1148
|
+
* Move the sessions running right now onto the account just made active (#422 S4.1).
|
|
1149
|
+
*
|
|
1150
|
+
* A process reads its account ONCE, at its start: `CLAUDE_CONFIG_DIR` for
|
|
1151
|
+
* Claude, the link `codex-home/auth.json` resolves to for Codex. Neither can
|
|
1152
|
+
* be changed under a live process, so «apply to the open sessions» is parking
|
|
1153
|
+
* them – the conversation is kept (`--resume` / `thread/resume`) and the next
|
|
1154
|
+
* message starts a process on the account the machine is now set to.
|
|
1155
|
+
*
|
|
1156
|
+
* A session in the middle of a turn is not killed for this: it is marked and
|
|
1157
|
+
* parked at the end of its turn (`applyPendingAccountSwitch`). Which is why
|
|
1158
|
+
* the answer counts the two separately – the window says so in words.
|
|
1159
|
+
*/
|
|
1160
|
+
private applyAccountToSessions;
|
|
1161
|
+
/**
|
|
1162
|
+
* Park a session because the account under it changed – its own words.
|
|
1163
|
+
*
|
|
1164
|
+
* `quiet`, then a note of its own: «the runner switched to another session»
|
|
1165
|
+
* is simply untrue here, and this is the one thing the person needs to read
|
|
1166
|
+
* to know why their session is waiting for a message (the same reason #126
|
|
1167
|
+
* gave the rewind its own wording).
|
|
1168
|
+
*/
|
|
1169
|
+
private parkForAccount;
|
|
1170
|
+
/**
|
|
1171
|
+
* The turn is over – carry out a switch that arrived while it was running.
|
|
1172
|
+
*
|
|
1173
|
+
* Still not parkable (a card is open, a question is waiting): the mark stays
|
|
1174
|
+
* and the next end of a turn tries again. Nothing here ever kills a turn.
|
|
1175
|
+
*/
|
|
1176
|
+
private applyPendingAccountSwitch;
|
|
1147
1177
|
/**
|
|
1148
1178
|
* The agent is producing output while this runner still says a human is
|
|
1149
1179
|
* expected — put `lastReported` back to RUNNING (ticket #185).
|
package/dist/supervisor.js
CHANGED
|
@@ -19,7 +19,7 @@ import { applySession, gitBranches, gitCommit, gitDiff, gitLog, gitPush, gitRefs
|
|
|
19
19
|
import { fsView } from './fsview.js';
|
|
20
20
|
import { publishFile } from './file-publish.js';
|
|
21
21
|
import { agentAuthStatuses, AuthRelay, clearAgentAuthFailure, noteAgentAuthFailure, } from './auth-relay.js';
|
|
22
|
-
import { isAccountCommand, runAccountCommand } from './account-commands.js';
|
|
22
|
+
import { isAccountCommand, runAccountCommand, } from './account-commands.js';
|
|
23
23
|
import { isSupervisedProcess, selfUpdate } from './self-update.js';
|
|
24
24
|
import { installAgent } from './agent-install.js';
|
|
25
25
|
import { autoUpdateRefusal, claimAgentAutoUpdate } from './agent-auto-update.js';
|
|
@@ -3337,7 +3337,11 @@ export class Supervisor {
|
|
|
3337
3337
|
activeMs: Supervisor.spentMs(running),
|
|
3338
3338
|
errorMessage: event.errorMessage ?? 'Agent turn failed',
|
|
3339
3339
|
});
|
|
3340
|
+
return;
|
|
3340
3341
|
}
|
|
3342
|
+
// The turn is over – so an account switch that arrived while it was running
|
|
3343
|
+
// can be carried out now, without costing that turn (#422 S4.1).
|
|
3344
|
+
this.applyPendingAccountSwitch(running);
|
|
3341
3345
|
}
|
|
3342
3346
|
/**
|
|
3343
3347
|
* Hold a turn that produced nothing, in case it was never a turn (#300).
|
|
@@ -3578,6 +3582,93 @@ export class Supervisor {
|
|
|
3578
3582
|
}
|
|
3579
3583
|
running.session.stop('session_parked');
|
|
3580
3584
|
}
|
|
3585
|
+
/**
|
|
3586
|
+
* Move the sessions running right now onto the account just made active (#422 S4.1).
|
|
3587
|
+
*
|
|
3588
|
+
* A process reads its account ONCE, at its start: `CLAUDE_CONFIG_DIR` for
|
|
3589
|
+
* Claude, the link `codex-home/auth.json` resolves to for Codex. Neither can
|
|
3590
|
+
* be changed under a live process, so «apply to the open sessions» is parking
|
|
3591
|
+
* them – the conversation is kept (`--resume` / `thread/resume`) and the next
|
|
3592
|
+
* message starts a process on the account the machine is now set to.
|
|
3593
|
+
*
|
|
3594
|
+
* A session in the middle of a turn is not killed for this: it is marked and
|
|
3595
|
+
* parked at the end of its turn (`applyPendingAccountSwitch`). Which is why
|
|
3596
|
+
* the answer counts the two separately – the window says so in words.
|
|
3597
|
+
*/
|
|
3598
|
+
applyAccountToSessions(agent, accountId, label) {
|
|
3599
|
+
let switched = 0;
|
|
3600
|
+
let pending = 0;
|
|
3601
|
+
for (const running of this.sessions.values()) {
|
|
3602
|
+
if (agentByDbValue(running.descriptor.agent)?.wireKey !== agent)
|
|
3603
|
+
continue;
|
|
3604
|
+
const session = running.session;
|
|
3605
|
+
// Parked already: whatever starts it next reads the active account then.
|
|
3606
|
+
if (!session)
|
|
3607
|
+
continue;
|
|
3608
|
+
// Already there, or an adapter that does not name an account at all – a
|
|
3609
|
+
// switch has nothing to do to it, and a stale mark must not outlive it.
|
|
3610
|
+
if (!session.accountId || session.accountId === accountId) {
|
|
3611
|
+
delete running.accountSwitchPending;
|
|
3612
|
+
continue;
|
|
3613
|
+
}
|
|
3614
|
+
if (this.isParkable(running)) {
|
|
3615
|
+
delete running.accountSwitchPending;
|
|
3616
|
+
this.parkForAccount(running, label);
|
|
3617
|
+
switched += 1;
|
|
3618
|
+
}
|
|
3619
|
+
else {
|
|
3620
|
+
running.accountSwitchPending = { id: accountId, ...(label ? { label } : {}) };
|
|
3621
|
+
pending += 1;
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
if (switched || pending) {
|
|
3625
|
+
log.info('supervisor: the account switch reached the open sessions', {
|
|
3626
|
+
agent,
|
|
3627
|
+
account: accountId,
|
|
3628
|
+
switched,
|
|
3629
|
+
pending,
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
return { switched, pending };
|
|
3633
|
+
}
|
|
3634
|
+
/**
|
|
3635
|
+
* Park a session because the account under it changed – its own words.
|
|
3636
|
+
*
|
|
3637
|
+
* `quiet`, then a note of its own: «the runner switched to another session»
|
|
3638
|
+
* is simply untrue here, and this is the one thing the person needs to read
|
|
3639
|
+
* to know why their session is waiting for a message (the same reason #126
|
|
3640
|
+
* gave the rewind its own wording).
|
|
3641
|
+
*/
|
|
3642
|
+
parkForAccount(running, label) {
|
|
3643
|
+
this.sendEvent(running, 'system_note', {
|
|
3644
|
+
// The word the dashboard reads to tell «parked» from «your turn» (#124) –
|
|
3645
|
+
// the state IS parked; only the reason differs.
|
|
3646
|
+
code: 'session_parked',
|
|
3647
|
+
text: label
|
|
3648
|
+
? `The agent account on this server was switched to ${label} — send a message and this session carries on under it, with everything above kept.`
|
|
3649
|
+
: 'The agent account on this server was switched — send a message and this session carries on under the new one, with everything above kept.',
|
|
3650
|
+
});
|
|
3651
|
+
this.park(running, { quiet: true });
|
|
3652
|
+
}
|
|
3653
|
+
/**
|
|
3654
|
+
* The turn is over – carry out a switch that arrived while it was running.
|
|
3655
|
+
*
|
|
3656
|
+
* Still not parkable (a card is open, a question is waiting): the mark stays
|
|
3657
|
+
* and the next end of a turn tries again. Nothing here ever kills a turn.
|
|
3658
|
+
*/
|
|
3659
|
+
applyPendingAccountSwitch(running) {
|
|
3660
|
+
const wanted = running.accountSwitchPending;
|
|
3661
|
+
if (!wanted)
|
|
3662
|
+
return;
|
|
3663
|
+
if (!running.session || running.session.accountId === wanted.id) {
|
|
3664
|
+
delete running.accountSwitchPending;
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
if (!this.isParkable(running))
|
|
3668
|
+
return;
|
|
3669
|
+
delete running.accountSwitchPending;
|
|
3670
|
+
this.parkForAccount(running, wanted.label);
|
|
3671
|
+
}
|
|
3581
3672
|
/**
|
|
3582
3673
|
* The agent is producing output while this runner still says a human is
|
|
3583
3674
|
* expected — put `lastReported` back to RUNNING (ticket #185).
|
|
@@ -7494,7 +7585,11 @@ export class Supervisor {
|
|
|
7494
7585
|
// #422: the five account commands – their own module, one door each
|
|
7495
7586
|
// over `claude-homes.ts` (plan §8).
|
|
7496
7587
|
if (isAccountCommand(frame.name)) {
|
|
7497
|
-
return void reply(
|
|
7588
|
+
return void reply(
|
|
7589
|
+
// The fourth argument is «apply to the sessions already running»
|
|
7590
|
+
// (#422 S4.1): the commands know the accounts, this class knows
|
|
7591
|
+
// the sessions, so the switch reaches them from here.
|
|
7592
|
+
await runAccountCommand(frame.name, frame.args ?? {}, this.authRelay, (...args) => this.applyAccountToSessions(...args)));
|
|
7498
7593
|
}
|
|
7499
7594
|
return void reply({ ok: false, error: `Unknown command` });
|
|
7500
7595
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const RUNNER_VERSION = "0.
|
|
1
|
+
export declare const RUNNER_VERSION = "0.68.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
package/package.json
CHANGED