@modelprofile.com/authswitch 2.1.2
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/.smartconfig.json +34 -0
- package/cli.js +21 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/accounts.d.ts +31 -0
- package/dist_ts/accounts.js +74 -0
- package/dist_ts/classes.accountlist.d.ts +17 -0
- package/dist_ts/classes.accountlist.js +214 -0
- package/dist_ts/classes.claudecodeharness.d.ts +34 -0
- package/dist_ts/classes.claudecodeharness.js +123 -0
- package/dist_ts/classes.claudestatus.d.ts +8 -0
- package/dist_ts/classes.claudestatus.js +139 -0
- package/dist_ts/classes.cli.d.ts +31 -0
- package/dist_ts/classes.cli.js +584 -0
- package/dist_ts/classes.codexauth.d.ts +35 -0
- package/dist_ts/classes.codexauth.js +184 -0
- package/dist_ts/classes.codexdaemon.d.ts +38 -0
- package/dist_ts/classes.codexdaemon.js +122 -0
- package/dist_ts/classes.codexharness.d.ts +35 -0
- package/dist_ts/classes.codexharness.js +199 -0
- package/dist_ts/classes.codexhome.d.ts +25 -0
- package/dist_ts/classes.codexhome.js +60 -0
- package/dist_ts/classes.codexpreuse.d.ts +8 -0
- package/dist_ts/classes.codexpreuse.js +90 -0
- package/dist_ts/classes.codexstatus.d.ts +20 -0
- package/dist_ts/classes.codexstatus.js +304 -0
- package/dist_ts/classes.codexswitcher.d.ts +61 -0
- package/dist_ts/classes.codexswitcher.js +226 -0
- package/dist_ts/classes.credentialstore.d.ts +32 -0
- package/dist_ts/classes.credentialstore.js +115 -0
- package/dist_ts/classes.fileharness.d.ts +47 -0
- package/dist_ts/classes.fileharness.js +119 -0
- package/dist_ts/classes.login.d.ts +42 -0
- package/dist_ts/classes.login.js +102 -0
- package/dist_ts/classes.opencodeharness.d.ts +34 -0
- package/dist_ts/classes.opencodeharness.js +125 -0
- package/dist_ts/classes.operations.d.ts +44 -0
- package/dist_ts/classes.operations.js +97 -0
- package/dist_ts/classes.remotecontrol.d.ts +46 -0
- package/dist_ts/classes.remotecontrol.js +169 -0
- package/dist_ts/classes.service.d.ts +47 -0
- package/dist_ts/classes.service.js +142 -0
- package/dist_ts/classes.stashstore.d.ts +63 -0
- package/dist_ts/classes.stashstore.js +192 -0
- package/dist_ts/classes.tui.d.ts +11 -0
- package/dist_ts/classes.tui.js +195 -0
- package/dist_ts/formatting.d.ts +8 -0
- package/dist_ts/formatting.js +13 -0
- package/dist_ts/helpers.d.ts +39 -0
- package/dist_ts/helpers.js +92 -0
- package/dist_ts/index.d.ts +22 -0
- package/dist_ts/index.js +27 -0
- package/dist_ts/interfaces.d.ts +145 -0
- package/dist_ts/interfaces.harness.d.ts +130 -0
- package/dist_ts/interfaces.harness.js +2 -0
- package/dist_ts/interfaces.js +2 -0
- package/dist_ts/interfaces.list.d.ts +19 -0
- package/dist_ts/interfaces.list.js +2 -0
- package/dist_ts/plugins.d.ts +14 -0
- package/dist_ts/plugins.js +18 -0
- package/dist_ts/preuse.d.ts +6 -0
- package/dist_ts/preuse.js +13 -0
- package/license.md +21 -0
- package/package.json +53 -0
- package/readme.md +502 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/accounts.ts +78 -0
- package/ts/classes.accountlist.ts +211 -0
- package/ts/classes.claudecodeharness.ts +111 -0
- package/ts/classes.claudestatus.ts +99 -0
- package/ts/classes.cli.ts +479 -0
- package/ts/classes.codexauth.ts +199 -0
- package/ts/classes.codexdaemon.ts +137 -0
- package/ts/classes.codexharness.ts +233 -0
- package/ts/classes.codexhome.ts +68 -0
- package/ts/classes.codexpreuse.ts +76 -0
- package/ts/classes.codexstatus.ts +257 -0
- package/ts/classes.codexswitcher.ts +272 -0
- package/ts/classes.credentialstore.ts +101 -0
- package/ts/classes.fileharness.ts +120 -0
- package/ts/classes.login.ts +103 -0
- package/ts/classes.opencodeharness.ts +114 -0
- package/ts/classes.operations.ts +101 -0
- package/ts/classes.remotecontrol.ts +208 -0
- package/ts/classes.service.ts +152 -0
- package/ts/classes.stashstore.ts +223 -0
- package/ts/classes.tui.ts +138 -0
- package/ts/formatting.ts +14 -0
- package/ts/helpers.ts +109 -0
- package/ts/index.ts +28 -0
- package/ts/interfaces.harness.ts +112 -0
- package/ts/interfaces.list.ts +19 -0
- package/ts/interfaces.ts +158 -0
- package/ts/plugins.ts +20 -0
- package/ts/preuse.ts +15 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { FileHarness } from './classes.fileharness.js';
|
|
3
|
+
import { CredentialStore, credentialHash, credentialRecord, credentialText, readCredentialDocument, requireHarnessStopped } from './classes.credentialstore.js';
|
|
4
|
+
import { CodexAccountStatus } from './classes.codexstatus.js';
|
|
5
|
+
import { writeSecretFileAtomically } from './helpers.js';
|
|
6
|
+
import { beginSavedOpenAiLogin, openCodeCredentialFromLogin } from './classes.login.js';
|
|
7
|
+
import { plainText } from './formatting.js';
|
|
8
|
+
/** OpenCode owns one login per provider. Replacing a slot never replaces the whole provider set. */
|
|
9
|
+
export class OpenCodeHarness extends FileHarness {
|
|
10
|
+
id = 'opencode';
|
|
11
|
+
label = 'OpenCode';
|
|
12
|
+
loginHint = 'Use authswitch opencode login for OpenAI; use opencode auth login for other providers.';
|
|
13
|
+
loginProviders = [{ providerId: 'openai', label: 'OpenAI', flows: ['device'] }];
|
|
14
|
+
store;
|
|
15
|
+
file;
|
|
16
|
+
env;
|
|
17
|
+
status;
|
|
18
|
+
checkStopped;
|
|
19
|
+
fetcher;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.env = options.env ?? process.env;
|
|
23
|
+
this.file = plugins.path.join(options.dataHome ?? (this.env.XDG_DATA_HOME || plugins.path.join(plugins.os.homedir(), '.local', 'share')), 'opencode', 'auth.json');
|
|
24
|
+
this.store = new CredentialStore(this.id, options.stashRoot);
|
|
25
|
+
this.status = new CodexAccountStatus(options.fetch);
|
|
26
|
+
this.fetcher = options.fetch;
|
|
27
|
+
this.checkStopped = options.checkStopped ?? (() => requireHarnessStopped('opencode'));
|
|
28
|
+
}
|
|
29
|
+
beforeMutation() { this.checkStopped(); }
|
|
30
|
+
/** Public identity only, for reconciling a host's OpenAI association after native refresh/restart. */
|
|
31
|
+
activeOpenAiAccountId() {
|
|
32
|
+
const account = this.snapshot().accounts.find(item => item.slotId === 'openai');
|
|
33
|
+
return account ? this.openAiInfo(account.credential).accountId : undefined;
|
|
34
|
+
}
|
|
35
|
+
beginLogin(options = { providerId: 'openai', flow: 'device' }) {
|
|
36
|
+
return beginSavedOpenAiLogin(result => {
|
|
37
|
+
const saved = this.importCredential('openai', openCodeCredentialFromLogin(result.credential));
|
|
38
|
+
const account = this.readState().accounts.find(account => account.id === saved.accountId);
|
|
39
|
+
return { outcome: { lines: [`Saved OpenAI login for ${plainText(account.label)}. Select it to switch OpenCode.`], problems: [] }, account: { id: account.id, label: account.label, slotId: account.slotId } };
|
|
40
|
+
}, options, this.fetcher);
|
|
41
|
+
}
|
|
42
|
+
openAiInfo(entry) {
|
|
43
|
+
if (entry.type !== 'oauth')
|
|
44
|
+
return {};
|
|
45
|
+
let claims;
|
|
46
|
+
try {
|
|
47
|
+
claims = plugins.flexAuth.parseOpenAiChatGptTokenInfo(credentialText(entry.access));
|
|
48
|
+
}
|
|
49
|
+
catch { /* Opaque tokens can still be preserved. */ }
|
|
50
|
+
const accountId = credentialText(entry.accountId) ?? claims?.chatgptAccountId;
|
|
51
|
+
if (entry.accountId && claims?.chatgptAccountId && entry.accountId !== claims.chatgptAccountId)
|
|
52
|
+
throw new Error('OpenCode OpenAI account ID does not match the credential.');
|
|
53
|
+
return { accountId, email: claims?.email?.toLowerCase(), plan: claims?.chatgptPlanType };
|
|
54
|
+
}
|
|
55
|
+
inspect(credential, slotId) {
|
|
56
|
+
if (!slotId || slotId.length > 256)
|
|
57
|
+
throw new Error('Unsupported OpenCode provider ID.');
|
|
58
|
+
const type = credential.type;
|
|
59
|
+
let secret;
|
|
60
|
+
if (type === 'oauth') {
|
|
61
|
+
if (!credentialText(credential.access) || !credentialText(credential.refresh) || typeof credential.expires !== 'number' || !Number.isFinite(credential.expires))
|
|
62
|
+
throw new Error('Unsupported OpenCode OAuth credential.');
|
|
63
|
+
secret = credentialText(credential.refresh);
|
|
64
|
+
}
|
|
65
|
+
else if (type === 'api')
|
|
66
|
+
secret = credentialText(credential.key);
|
|
67
|
+
else if (type === 'wellknown' && credentialText(credential.key))
|
|
68
|
+
secret = credentialText(credential.token);
|
|
69
|
+
if (!secret)
|
|
70
|
+
throw new Error('Unsupported OpenCode credential; the provider entry was preserved.');
|
|
71
|
+
const info = slotId === 'openai' ? this.openAiInfo(credential) : {};
|
|
72
|
+
const fingerprint = credentialHash(secret);
|
|
73
|
+
const identity = info.accountId ? `oauth:${info.accountId}` : `${type}:${fingerprint}`;
|
|
74
|
+
const label = info.email ?? (info.accountId ? `openai:${info.accountId}` : `${slotId} ${type === 'api' ? 'API key' : type} ${fingerprint.slice(0, 12)}`);
|
|
75
|
+
return { slotId, identity, label, credential };
|
|
76
|
+
}
|
|
77
|
+
snapshot() {
|
|
78
|
+
if (this.env.OPENCODE_AUTH_CONTENT !== undefined)
|
|
79
|
+
return { accounts: [], unavailable: 'OPENCODE_AUTH_CONTENT overrides the credential file. Unset it before managing file logins.' };
|
|
80
|
+
const data = readCredentialDocument(this.file).data;
|
|
81
|
+
const accounts = [];
|
|
82
|
+
const unsupported = [];
|
|
83
|
+
for (const [provider, value] of Object.entries(data)) {
|
|
84
|
+
try {
|
|
85
|
+
accounts.push(this.inspect(credentialRecord(value), provider));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
unsupported.push(provider);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { accounts, unavailable: unsupported.length ? 'OpenCode contains unsupported credential entries. Resolve them with opencode auth before modifying logins.' : null };
|
|
92
|
+
}
|
|
93
|
+
apply(slotId, previous, target) {
|
|
94
|
+
const snapshot = readCredentialDocument(this.file);
|
|
95
|
+
if (JSON.stringify(snapshot.data[slotId]) !== JSON.stringify(previous?.credential))
|
|
96
|
+
throw new Error('OpenCode login changed during the operation; no credential was replaced.');
|
|
97
|
+
const next = { ...snapshot.data };
|
|
98
|
+
if (target)
|
|
99
|
+
Object.defineProperty(next, slotId, { value: target.credential, enumerable: true, configurable: true, writable: true });
|
|
100
|
+
else
|
|
101
|
+
delete next[slotId];
|
|
102
|
+
const raw = JSON.stringify(next, null, 2) + '\n';
|
|
103
|
+
if (readCredentialDocument(this.file).raw !== snapshot.raw)
|
|
104
|
+
throw new Error('OpenCode credential file changed during the operation. Try again after OpenCode exits.');
|
|
105
|
+
plugins.fs.mkdirSync(plugins.path.dirname(this.file), { recursive: true, mode: 0o700 });
|
|
106
|
+
writeSecretFileAtomically(this.file, raw);
|
|
107
|
+
if (readCredentialDocument(this.file).raw !== raw)
|
|
108
|
+
throw new Error('Active login verification failed. The outgoing login remains saved.');
|
|
109
|
+
}
|
|
110
|
+
async readAccountStatus(id) {
|
|
111
|
+
const account = this.readCredential(id);
|
|
112
|
+
if (account.slotId === 'openai' && account.credential.type === 'oauth') {
|
|
113
|
+
const info = this.openAiInfo(account.credential);
|
|
114
|
+
if (!info.accountId)
|
|
115
|
+
return { facts: [], problems: ['The OpenAI login has no account ID for a status lookup.'] };
|
|
116
|
+
return this.status.readCredential(credentialText(account.credential.access), info.accountId, info.plan);
|
|
117
|
+
}
|
|
118
|
+
return { facts: [
|
|
119
|
+
{ section: 'Authentication', label: 'Provider', value: account.slotId },
|
|
120
|
+
{ section: 'Authentication', label: 'Login type', value: String(account.credential.type) },
|
|
121
|
+
{ section: 'Availability', label: 'Account usage and billing', value: 'This provider login does not expose a supported subscription or quota API.' },
|
|
122
|
+
], problems: [] };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5vcGVuY29kZWhhcm5lc3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLm9wZW5jb2RlaGFybmVzcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsV0FBVyxFQUFnRCxNQUFNLDBCQUEwQixDQUFDO0FBQ3JHLE9BQU8sRUFBRSxlQUFlLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLGNBQWMsRUFBRSxzQkFBc0IsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBQ2hLLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzlELE9BQU8sRUFBRSx5QkFBeUIsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUV6RCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN4RixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFXNUMsb0dBQW9HO0FBQ3BHLE1BQU0sT0FBTyxlQUFnQixTQUFRLFdBQVc7SUFDOUIsRUFBRSxHQUFHLFVBQVUsQ0FBQztJQUNoQixLQUFLLEdBQUcsVUFBVSxDQUFDO0lBQ25CLFNBQVMsR0FBRyx3RkFBd0YsQ0FBQztJQUNyRyxjQUFjLEdBQTRCLENBQUMsRUFBRSxVQUFVLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3RHLEtBQUssQ0FBa0I7SUFDekIsSUFBSSxDQUFTO0lBQ2IsR0FBRyxDQUFvQjtJQUN2QixNQUFNLENBQXFCO0lBQzNCLFlBQVksQ0FBYTtJQUN6QixPQUFPLENBQWdCO0lBRXhDLFlBQVksVUFBbUMsRUFBRTtRQUMvQyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxHQUFHLEdBQUcs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
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { IAuthHarness, IHarnessOutcome } from './interfaces.harness.js';
|
|
2
|
+
export type TAuthSwitchMutation = {
|
|
3
|
+
harnessId: string;
|
|
4
|
+
} & ({
|
|
5
|
+
action: 'save';
|
|
6
|
+
accountId?: string;
|
|
7
|
+
keepActive: boolean;
|
|
8
|
+
} | {
|
|
9
|
+
action: 'switch' | 'remove';
|
|
10
|
+
accountId: string;
|
|
11
|
+
});
|
|
12
|
+
export interface IAuthSwitchCoordinationRequest {
|
|
13
|
+
protocolVersion: 1;
|
|
14
|
+
/** Identifies credential locations; a host must refuse a different environment. */
|
|
15
|
+
contextId: string;
|
|
16
|
+
mutation: TAuthSwitchMutation;
|
|
17
|
+
/** Consent to wait for current work to finish; never permission to kill an active session. */
|
|
18
|
+
waitForIdle: boolean;
|
|
19
|
+
}
|
|
20
|
+
export type TAuthSwitchCoordinationResult = {
|
|
21
|
+
status: 'unavailable';
|
|
22
|
+
} | {
|
|
23
|
+
status: 'busy';
|
|
24
|
+
message: string;
|
|
25
|
+
} | {
|
|
26
|
+
status: 'complete';
|
|
27
|
+
outcome: IHarnessOutcome;
|
|
28
|
+
};
|
|
29
|
+
export type TAuthSwitchCoordinator = (request: IAuthSwitchCoordinationRequest) => Promise<TAuthSwitchCoordinationResult>;
|
|
30
|
+
export type TAuthSwitchConfirmation = (message: string) => Promise<boolean>;
|
|
31
|
+
export declare const authSwitchEnvironmentId: (env?: NodeJS.ProcessEnv) => string;
|
|
32
|
+
/** Validate the secret-free command boundary, including callers that do not use TypeScript. */
|
|
33
|
+
export declare const assertAuthSwitchMutation: (value: unknown) => asserts value is TAuthSwitchMutation;
|
|
34
|
+
export declare const runAuthSwitchMutation: (harness: IAuthHarness, mutation: TAuthSwitchMutation) => Promise<IHarnessOutcome>;
|
|
35
|
+
/** One mutation path for command, guide and TUI. Supervisors own their complete restart transaction. */
|
|
36
|
+
export declare class AuthSwitchOperations {
|
|
37
|
+
private readonly coordinator?;
|
|
38
|
+
constructor(coordinator?: TAuthSwitchCoordinator | undefined);
|
|
39
|
+
run(harness: IAuthHarness, mutation: TAuthSwitchMutation, confirm?: TAuthSwitchConfirmation): Promise<IHarnessOutcome>;
|
|
40
|
+
}
|
|
41
|
+
/** AGL owns authenticated controller discovery. No AGL dependency, tokens or credential payloads cross this bridge. */
|
|
42
|
+
export declare class AglAuthSwitchCoordinator {
|
|
43
|
+
coordinate(request: IAuthSwitchCoordinationRequest): Promise<TAuthSwitchCoordinationResult>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
export const authSwitchEnvironmentId = (env = process.env) => plugins.crypto.createHash('sha256').update(JSON.stringify({
|
|
3
|
+
home: plugins.os.homedir(),
|
|
4
|
+
stash: plugins.path.resolve(env.AUTHSWITCH_HOME || plugins.path.join(plugins.os.homedir(), '.authswitch')),
|
|
5
|
+
codex: plugins.path.resolve(env.CODEX_HOME || plugins.path.join(plugins.os.homedir(), '.codex')),
|
|
6
|
+
data: plugins.path.resolve(env.XDG_DATA_HOME || plugins.path.join(plugins.os.homedir(), '.local/share')),
|
|
7
|
+
claude: plugins.path.resolve(env.CLAUDE_CONFIG_DIR || plugins.path.join(plugins.os.homedir(), '.claude')),
|
|
8
|
+
openCodeOverride: env.OPENCODE_AUTH_CONTENT !== undefined,
|
|
9
|
+
})).digest('hex');
|
|
10
|
+
/** Validate the secret-free command boundary, including callers that do not use TypeScript. */
|
|
11
|
+
export const assertAuthSwitchMutation = (value) => {
|
|
12
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
13
|
+
throw new Error('Invalid account operation.');
|
|
14
|
+
const item = value;
|
|
15
|
+
const keys = item.action === 'save' ? ['harnessId', 'action', 'accountId', 'keepActive'] : ['harnessId', 'action', 'accountId'];
|
|
16
|
+
if (Object.keys(item).some(key => !keys.includes(key))
|
|
17
|
+
|| typeof item.harnessId !== 'string' || !/^[a-z][a-z0-9-]{0,63}$/.test(item.harnessId)
|
|
18
|
+
|| !['save', 'switch', 'remove'].includes(String(item.action))
|
|
19
|
+
|| (item.accountId !== undefined && (typeof item.accountId !== 'string' || !item.accountId.length || item.accountId.length > 512))
|
|
20
|
+
|| (item.action === 'save' ? typeof item.keepActive !== 'boolean' : typeof item.accountId !== 'string')) {
|
|
21
|
+
throw new Error('Invalid account operation.');
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export const runAuthSwitchMutation = async (harness, mutation) => {
|
|
25
|
+
assertAuthSwitchMutation(mutation);
|
|
26
|
+
if (harness.id !== mutation.harnessId)
|
|
27
|
+
throw new Error('Account operation targets a different harness.');
|
|
28
|
+
switch (mutation.action) {
|
|
29
|
+
case 'save': return harness.saveCurrent({ keepActive: mutation.keepActive, accountId: mutation.accountId });
|
|
30
|
+
case 'switch': return harness.switchAccount(mutation.accountId);
|
|
31
|
+
case 'remove': return harness.removeAccount(mutation.accountId);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/** One mutation path for command, guide and TUI. Supervisors own their complete restart transaction. */
|
|
35
|
+
export class AuthSwitchOperations {
|
|
36
|
+
coordinator;
|
|
37
|
+
constructor(coordinator) {
|
|
38
|
+
this.coordinator = coordinator;
|
|
39
|
+
}
|
|
40
|
+
async run(harness, mutation, confirm) {
|
|
41
|
+
assertAuthSwitchMutation(mutation);
|
|
42
|
+
if (harness.id !== mutation.harnessId)
|
|
43
|
+
throw new Error('Account operation targets a different harness.');
|
|
44
|
+
if (this.coordinator && mutation.action !== 'remove') {
|
|
45
|
+
const request = { protocolVersion: 1, contextId: authSwitchEnvironmentId(), mutation, waitForIdle: false };
|
|
46
|
+
let result = await this.coordinator(request);
|
|
47
|
+
if (result.status === 'busy') {
|
|
48
|
+
if (!confirm || !await confirm(result.message))
|
|
49
|
+
return { lines: [], problems: [result.message] };
|
|
50
|
+
result = await this.coordinator({ ...request, waitForIdle: true });
|
|
51
|
+
}
|
|
52
|
+
if (result.status === 'complete')
|
|
53
|
+
return result.outcome;
|
|
54
|
+
if (result.status === 'busy')
|
|
55
|
+
return { lines: [], problems: [result.message] };
|
|
56
|
+
}
|
|
57
|
+
return runAuthSwitchMutation(harness, mutation);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** AGL owns authenticated controller discovery. No AGL dependency, tokens or credential payloads cross this bridge. */
|
|
61
|
+
export class AglAuthSwitchCoordinator {
|
|
62
|
+
async coordinate(request) {
|
|
63
|
+
assertAuthSwitchMutation(request.mutation);
|
|
64
|
+
const output = await new Promise((resolve, reject) => {
|
|
65
|
+
plugins.childProcess.execFile('agl', ['authswitch', '--request', JSON.stringify(request)], {
|
|
66
|
+
encoding: 'utf8', timeout: 360_000, maxBuffer: 1024 * 1024, windowsHide: true,
|
|
67
|
+
}, (error, stdout) => {
|
|
68
|
+
if (error?.code === 'ENOENT') {
|
|
69
|
+
resolve(undefined);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (error) {
|
|
73
|
+
reject(new Error('AGL coordination did not complete. Check AGL and account status before retrying; no local credential operation was attempted. Install an AGL version with authswitch support.'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
resolve(stdout);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
if (output === undefined)
|
|
80
|
+
return { status: 'unavailable' };
|
|
81
|
+
try {
|
|
82
|
+
const result = JSON.parse(output);
|
|
83
|
+
if (result.status === 'unavailable')
|
|
84
|
+
return { status: 'unavailable' };
|
|
85
|
+
if (result.status === 'busy' && typeof result.message === 'string' && result.message.length <= 4096)
|
|
86
|
+
return { status: 'busy', message: result.message };
|
|
87
|
+
if (result.status === 'complete' && result.outcome && ['lines', 'problems'].every(key => {
|
|
88
|
+
const items = result.outcome[key];
|
|
89
|
+
return Array.isArray(items) && items.length <= 100 && items.every(item => typeof item === 'string' && item.length <= 4096);
|
|
90
|
+
}))
|
|
91
|
+
return { status: 'complete', outcome: { lines: result.outcome.lines, problems: result.outcome.problems } };
|
|
92
|
+
}
|
|
93
|
+
catch { /* Never print raw transport output, which may contain native diagnostics. */ }
|
|
94
|
+
throw new Error('AGL returned an unsupported coordination response. Check account status before retrying.');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5vcGVyYXRpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5vcGVyYXRpb25zLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBc0J4QyxNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxDQUFDLE1BQXlCLE9BQU8sQ0FBQyxHQUFHLEVBQVUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO0lBQ2pKLElBQUksRUFBRSxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRTtJQUMxQixLQUFLLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQzFHLEtBQUssRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDaEcsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxjQUFjLENBQUMsQ0FBQztJQUN4RyxNQUFNLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FB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
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { IRemoteControlEnrollment } from './interfaces.js';
|
|
2
|
+
import { CodexHome } from './classes.codexhome.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads and restores Codex' remote-control enrollments.
|
|
5
|
+
*
|
|
6
|
+
* An enrollment is the record that binds this machine's Codex app-server to a
|
|
7
|
+
* paired ChatGPT client. Its primary key includes the account id, so enrollments
|
|
8
|
+
* for several accounts can coexist -- but a credential swap can still leave an
|
|
9
|
+
* account without its row, which is exactly what breaks remote control after a
|
|
10
|
+
* naive auth switch. authswitch captures the rows next to the credential and
|
|
11
|
+
* puts them back when the credential is restored.
|
|
12
|
+
*/
|
|
13
|
+
export declare class RemoteControlStore {
|
|
14
|
+
readonly codexHome: CodexHome;
|
|
15
|
+
constructor(codexHomeArg: CodexHome);
|
|
16
|
+
/** True when a Codex state database exists and carries the enrollment table. */
|
|
17
|
+
isAvailable(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the enrollments for one account, or every enrollment when no
|
|
20
|
+
* account id is given.
|
|
21
|
+
*/
|
|
22
|
+
read(accountIdArg?: string | null): IRemoteControlEnrollment[];
|
|
23
|
+
/**
|
|
24
|
+
* Puts enrollments back, updating any row with the same primary key.
|
|
25
|
+
*
|
|
26
|
+
* Throws rather than reporting zero when the database or the table is not
|
|
27
|
+
* there: restoring remote control is the reason this tool exists, so failing
|
|
28
|
+
* to do it must surface instead of looking like "there was nothing to do".
|
|
29
|
+
*/
|
|
30
|
+
restore(enrollmentsArg: IRemoteControlEnrollment[]): number;
|
|
31
|
+
private hasTable;
|
|
32
|
+
/**
|
|
33
|
+
* Refuses to write when Codex has added columns this version does not know
|
|
34
|
+
* about. `CodexHome` deliberately follows the newest state generation, so a
|
|
35
|
+
* Codex upgrade can widen this table -- and writing only the columns we know
|
|
36
|
+
* would quietly reset the rest to their defaults.
|
|
37
|
+
*/
|
|
38
|
+
private assertKnownColumns;
|
|
39
|
+
private toEnrollment;
|
|
40
|
+
/**
|
|
41
|
+
* Codex keeps the state database in WAL mode and holds it open from the
|
|
42
|
+
* app-server, so every access takes a busy timeout rather than failing on the
|
|
43
|
+
* first lock contention.
|
|
44
|
+
*/
|
|
45
|
+
private withDatabase;
|
|
46
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { CodexHome } from './classes.codexhome.js';
|
|
3
|
+
const TABLE_NAME = 'remote_control_enrollments';
|
|
4
|
+
/** The columns authswitch knows how to carry across a switch. */
|
|
5
|
+
const COLUMNS = [
|
|
6
|
+
'websocket_url',
|
|
7
|
+
'account_id',
|
|
8
|
+
'app_server_client_name',
|
|
9
|
+
'server_id',
|
|
10
|
+
'environment_id',
|
|
11
|
+
'server_name',
|
|
12
|
+
'updated_at',
|
|
13
|
+
'remote_control_enabled',
|
|
14
|
+
];
|
|
15
|
+
/** The columns that make up the table's primary key. */
|
|
16
|
+
const KEY_COLUMNS = ['websocket_url', 'account_id', 'app_server_client_name'];
|
|
17
|
+
/**
|
|
18
|
+
* Reads and restores Codex' remote-control enrollments.
|
|
19
|
+
*
|
|
20
|
+
* An enrollment is the record that binds this machine's Codex app-server to a
|
|
21
|
+
* paired ChatGPT client. Its primary key includes the account id, so enrollments
|
|
22
|
+
* for several accounts can coexist -- but a credential swap can still leave an
|
|
23
|
+
* account without its row, which is exactly what breaks remote control after a
|
|
24
|
+
* naive auth switch. authswitch captures the rows next to the credential and
|
|
25
|
+
* puts them back when the credential is restored.
|
|
26
|
+
*/
|
|
27
|
+
export class RemoteControlStore {
|
|
28
|
+
codexHome;
|
|
29
|
+
constructor(codexHomeArg) {
|
|
30
|
+
this.codexHome = codexHomeArg;
|
|
31
|
+
}
|
|
32
|
+
/** True when a Codex state database exists and carries the enrollment table. */
|
|
33
|
+
isAvailable() {
|
|
34
|
+
const statePath = this.codexHome.statePath;
|
|
35
|
+
if (!statePath) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return this.withDatabase(statePath, true, (databaseArg) => this.hasTable(databaseArg));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// A read-only connection cannot create the -shm file a WAL database needs,
|
|
43
|
+
// so an unclean shutdown can make even the probe fail. Report "not
|
|
44
|
+
// available" here; the restore path probes over its writable connection.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns the enrollments for one account, or every enrollment when no
|
|
50
|
+
* account id is given.
|
|
51
|
+
*/
|
|
52
|
+
read(accountIdArg) {
|
|
53
|
+
const statePath = this.codexHome.statePath;
|
|
54
|
+
if (!statePath) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return this.withDatabase(statePath, true, (databaseArg) => {
|
|
59
|
+
if (!this.hasTable(databaseArg)) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const selection = COLUMNS.join(', ');
|
|
63
|
+
const statement = accountIdArg
|
|
64
|
+
? databaseArg.prepare(`select ${selection} from ${TABLE_NAME} where account_id = ?`)
|
|
65
|
+
: databaseArg.prepare(`select ${selection} from ${TABLE_NAME}`);
|
|
66
|
+
const rows = accountIdArg ? statement.all(accountIdArg) : statement.all();
|
|
67
|
+
return rows.map((rowArg) => this.toEnrollment(rowArg));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Puts enrollments back, updating any row with the same primary key.
|
|
76
|
+
*
|
|
77
|
+
* Throws rather than reporting zero when the database or the table is not
|
|
78
|
+
* there: restoring remote control is the reason this tool exists, so failing
|
|
79
|
+
* to do it must surface instead of looking like "there was nothing to do".
|
|
80
|
+
*/
|
|
81
|
+
restore(enrollmentsArg) {
|
|
82
|
+
if (enrollmentsArg.length === 0) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
const statePath = this.codexHome.statePath;
|
|
86
|
+
if (!statePath) {
|
|
87
|
+
throw new Error(`no Codex state database found in ${this.codexHome.dir}; cannot restore remote-control enrollments`);
|
|
88
|
+
}
|
|
89
|
+
// Opened read-write: the read-only probe cannot create the -shm file a WAL
|
|
90
|
+
// database needs, and failing there would leave the switch half-applied.
|
|
91
|
+
return this.withDatabase(statePath, false, (databaseArg) => {
|
|
92
|
+
if (!this.hasTable(databaseArg)) {
|
|
93
|
+
throw new Error(`${statePath} has no ${TABLE_NAME} table; this Codex version stores remote-control enrollments elsewhere`);
|
|
94
|
+
}
|
|
95
|
+
this.assertKnownColumns(databaseArg, statePath);
|
|
96
|
+
const placeholders = COLUMNS.map(() => '?').join(', ');
|
|
97
|
+
const updates = COLUMNS.filter((columnArg) => !KEY_COLUMNS.includes(columnArg))
|
|
98
|
+
.map((columnArg) => `${columnArg} = excluded.${columnArg}`)
|
|
99
|
+
.join(', ');
|
|
100
|
+
const statement = databaseArg.prepare(`insert into ${TABLE_NAME} (${COLUMNS.join(', ')}) values (${placeholders})
|
|
101
|
+
on conflict(${KEY_COLUMNS.join(', ')}) do update set ${updates}`);
|
|
102
|
+
databaseArg.exec('begin immediate');
|
|
103
|
+
try {
|
|
104
|
+
for (const enrollmentArg of enrollmentsArg) {
|
|
105
|
+
statement.run(enrollmentArg.websocketUrl, enrollmentArg.accountId, enrollmentArg.appServerClientName, enrollmentArg.serverId, enrollmentArg.environmentId, enrollmentArg.serverName, enrollmentArg.updatedAt, enrollmentArg.remoteControlEnabled);
|
|
106
|
+
}
|
|
107
|
+
databaseArg.exec('commit');
|
|
108
|
+
}
|
|
109
|
+
catch (errorArg) {
|
|
110
|
+
databaseArg.exec('rollback');
|
|
111
|
+
throw errorArg;
|
|
112
|
+
}
|
|
113
|
+
return enrollmentsArg.length;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
hasTable(databaseArg) {
|
|
117
|
+
const row = databaseArg
|
|
118
|
+
.prepare(`select name from sqlite_master where type = 'table' and name = ?`)
|
|
119
|
+
.get(TABLE_NAME);
|
|
120
|
+
return row !== undefined;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Refuses to write when Codex has added columns this version does not know
|
|
124
|
+
* about. `CodexHome` deliberately follows the newest state generation, so a
|
|
125
|
+
* Codex upgrade can widen this table -- and writing only the columns we know
|
|
126
|
+
* would quietly reset the rest to their defaults.
|
|
127
|
+
*/
|
|
128
|
+
assertKnownColumns(databaseArg, statePathArg) {
|
|
129
|
+
const rows = databaseArg.prepare(`pragma table_info(${TABLE_NAME})`).all();
|
|
130
|
+
const actual = rows.map((rowArg) => String(rowArg.name));
|
|
131
|
+
const unexpected = actual.filter((nameArg) => !COLUMNS.includes(nameArg));
|
|
132
|
+
const missing = COLUMNS.filter((nameArg) => !actual.includes(nameArg));
|
|
133
|
+
if (unexpected.length > 0 || missing.length > 0) {
|
|
134
|
+
throw new Error(`${statePathArg} has an unexpected ${TABLE_NAME} schema ` +
|
|
135
|
+
`(unexpected: ${unexpected.join(', ') || 'none'}; missing: ${missing.join(', ') || 'none'}). ` +
|
|
136
|
+
'Refusing to restore rather than write a partial row - authswitch needs an update for this Codex version.');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
toEnrollment(rowArg) {
|
|
140
|
+
return {
|
|
141
|
+
websocketUrl: String(rowArg.websocket_url ?? ''),
|
|
142
|
+
accountId: String(rowArg.account_id ?? ''),
|
|
143
|
+
appServerClientName: String(rowArg.app_server_client_name ?? ''),
|
|
144
|
+
serverId: String(rowArg.server_id ?? ''),
|
|
145
|
+
environmentId: String(rowArg.environment_id ?? ''),
|
|
146
|
+
serverName: String(rowArg.server_name ?? ''),
|
|
147
|
+
updatedAt: Number(rowArg.updated_at ?? 0),
|
|
148
|
+
remoteControlEnabled: rowArg.remote_control_enabled === null || rowArg.remote_control_enabled === undefined
|
|
149
|
+
? null
|
|
150
|
+
: Number(rowArg.remote_control_enabled),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Codex keeps the state database in WAL mode and holds it open from the
|
|
155
|
+
* app-server, so every access takes a busy timeout rather than failing on the
|
|
156
|
+
* first lock contention.
|
|
157
|
+
*/
|
|
158
|
+
withDatabase(pathArg, readOnlyArg, handlerArg) {
|
|
159
|
+
const database = new plugins.DatabaseSync(pathArg, { readOnly: readOnlyArg });
|
|
160
|
+
try {
|
|
161
|
+
database.exec('pragma busy_timeout = 5000');
|
|
162
|
+
return handlerArg(database);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
database.close();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVjb250cm9sLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVjb250cm9sLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUVuRCxNQUFNLFVBQVUsR0FBRyw0QkFBNEIsQ0FBQztBQUVoRCxpRUFBaUU7QUFDakUsTUFBTSxPQUFPLEdBQUc7SUFDZCxlQUFlO0lBQ2YsWUFBWTtJQUNaLHdCQUF3QjtJQUN4QixXQUFXO0lBQ1gsZ0JBQWdCO0lBQ2hCLGFBQWE7SUFDYixZQUFZO0lBQ1osd0JBQXdCO0NBQ2hCLENBQUM7QUFFWCx3REFBd0Q7QUFDeEQsTUFBTSxXQUFXLEdBQUcsQ0FBQyxlQUFlLEVBQUUsWUFBWSxFQUFFLHdCQUF3QixDQUFVLENBQUM7QUFFdkY7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxPQUFPLGtCQUFrQjtJQUNiLFNBQVMsQ0FBWTtJQUVyQyxZQUFZLFlBQXVCO1FBQ2pDLElBQUksQ0FBQyxTQUFTLEdBQUcsWUFBWSxDQUFDO0lBQ2hDLENBQUM7SUFFRCxnRkFBZ0Y7SUFDekUsV0FBVztRQUNoQixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQztRQUMzQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDZixPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFDRCxJQUFJLENBQUM7WUFDSCxPQUFPLElBQUksQ0FBQyxZQUFZLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDO1FBQ3pGLENBQUM7UUFBQyxNQUFNLENBQUM7WUFDUCwyRUFBMkU7WUFDM0UsbUVBQW1FO1lBQ25FLHlFQUF5RTtZ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
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type IAuthSwitchCoordinationRequest, type TAuthSwitchCoordinationResult, type TAuthSwitchCoordinator } from './classes.operations.js';
|
|
2
|
+
import type { IAuthHarness, IHarnessLoginOptions, IHarnessLoginProvider, IHarnessLoginResult } from './interfaces.harness.js';
|
|
3
|
+
import type { IAccountList } from './interfaces.list.js';
|
|
4
|
+
import type { TAuthLoginPrompt } from './classes.login.js';
|
|
5
|
+
export type TAuthSwitchServiceRequest = {
|
|
6
|
+
protocolVersion: 1;
|
|
7
|
+
} & ({
|
|
8
|
+
action: 'list';
|
|
9
|
+
harnessId?: string;
|
|
10
|
+
} | {
|
|
11
|
+
action: 'mutate';
|
|
12
|
+
coordination: IAuthSwitchCoordinationRequest;
|
|
13
|
+
} | ({
|
|
14
|
+
action: 'login';
|
|
15
|
+
harnessId: string;
|
|
16
|
+
} & IHarnessLoginOptions) | {
|
|
17
|
+
action: 'get' | 'cancel';
|
|
18
|
+
operationId: string;
|
|
19
|
+
});
|
|
20
|
+
/** Disposable operation status. Hosts authenticate callers; neither prompts nor results contain credentials. */
|
|
21
|
+
export interface IAuthSwitchServiceOperation {
|
|
22
|
+
id: string;
|
|
23
|
+
contextId: string;
|
|
24
|
+
state: 'pending' | 'complete' | 'failed';
|
|
25
|
+
prompt?: TAuthLoginPrompt;
|
|
26
|
+
accounts?: IAccountList;
|
|
27
|
+
loginCapabilities?: {
|
|
28
|
+
harnessId: string;
|
|
29
|
+
providers: IHarnessLoginProvider[];
|
|
30
|
+
}[];
|
|
31
|
+
account?: IHarnessLoginResult['account'];
|
|
32
|
+
result?: TAuthSwitchCoordinationResult;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const assertAuthSwitchServiceRequest: (value: unknown) => asserts value is TAuthSwitchServiceRequest;
|
|
36
|
+
/** Hosted account manager. Operations outlive HTTP connections and are retained briefly for polling. */
|
|
37
|
+
export declare class AuthSwitchService {
|
|
38
|
+
private readonly harnesses;
|
|
39
|
+
private readonly coordinator?;
|
|
40
|
+
private readonly operations;
|
|
41
|
+
private closed;
|
|
42
|
+
constructor(harnesses?: IAuthHarness[], coordinator?: TAuthSwitchCoordinator | undefined);
|
|
43
|
+
private harness;
|
|
44
|
+
private snapshot;
|
|
45
|
+
request(request: TAuthSwitchServiceRequest): Promise<IAuthSwitchServiceOperation>;
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}
|