@modelprofile.com/authswitch 6.4.0 → 6.5.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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/authority-runtime-contract.d.ts +2 -0
- package/dist_ts/classes.authoritybroker.d.ts +4 -1
- package/dist_ts/classes.authoritybroker.js +32 -10
- package/dist_ts/classes.authorityclient.d.ts +15 -2
- package/dist_ts/classes.authorityclient.js +86 -25
- package/dist_ts/classes.authoritydaemon.d.ts +20 -0
- package/dist_ts/classes.authoritydaemon.js +249 -6
- package/dist_ts/classes.authoritydatabase.d.ts +10 -1
- package/dist_ts/classes.authoritydatabase.js +104 -5
- package/dist_ts/classes.authoritymodels.d.ts +24 -0
- package/dist_ts/classes.authoritymodels.js +76 -1
- package/dist_ts/classes.codexmanaged.d.ts +80 -0
- package/dist_ts/classes.codexmanaged.js +343 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +9 -2
- package/readme.md +27 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/authority-runtime-contract.ts +7 -1
- package/ts/classes.authoritybroker.ts +31 -9
- package/ts/classes.authorityclient.ts +89 -20
- package/ts/classes.authoritydaemon.ts +189 -5
- package/ts/classes.authoritydatabase.ts +98 -5
- package/ts/classes.authoritymodels.ts +40 -0
- package/ts/classes.codexmanaged.ts +339 -0
- package/ts/index.ts +1 -0
- package/ts/plugins.ts +2 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { commitinfo } from './00_commitinfo_data.js';
|
|
3
|
+
const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
const isAlive = (child) => child.exitCode === null && child.signalCode === null;
|
|
5
|
+
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
6
|
+
/** Same-user process inspection: a unique private --listen socket is the pre-PID crash identity. */
|
|
7
|
+
export const findManagedCodexProcess = async (socketPath) => {
|
|
8
|
+
if (process.platform === 'win32')
|
|
9
|
+
throw new Error('Managed Codex Unix socket process inspection is unavailable on Windows.');
|
|
10
|
+
const output = await new Promise((resolve, reject) => {
|
|
11
|
+
plugins.childProcess.execFile('ps', ['-ww', '-u', String(process.getuid?.()), '-o', 'pid=,lstart=,args='], { timeout: 5_000, maxBuffer: 8 * 1024 * 1024, env: { ...process.env, LC_ALL: 'C' } }, (error, stdout) => error ? reject(new Error('Managed Codex process inspection failed.')) : resolve(stdout));
|
|
12
|
+
});
|
|
13
|
+
const marker = `app-server --listen unix://${socketPath}`;
|
|
14
|
+
const matches = [];
|
|
15
|
+
for (const line of output.split('\n')) {
|
|
16
|
+
const parts = line.trim().split(/\s+/);
|
|
17
|
+
if (parts.length < 8)
|
|
18
|
+
continue;
|
|
19
|
+
const pid = Number(parts[0]);
|
|
20
|
+
const date = Date.parse(parts.slice(1, 6).join(' '));
|
|
21
|
+
const args = parts.slice(6).join(' ');
|
|
22
|
+
if (Number.isSafeInteger(pid) && pid > 0 && Number.isFinite(date) && args.includes(marker)) {
|
|
23
|
+
matches.push({ pid, startedAt: new Date(date).toISOString() });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (matches.length > 1)
|
|
27
|
+
throw new Error('Multiple managed Codex processes claim one private socket.');
|
|
28
|
+
return matches[0] ?? null;
|
|
29
|
+
};
|
|
30
|
+
/** Daemon-owned Codex app-server. Only access tokens, never refresh grants, enter Codex. */
|
|
31
|
+
export class AuthSwitchManagedCodex {
|
|
32
|
+
options;
|
|
33
|
+
child;
|
|
34
|
+
childExited;
|
|
35
|
+
client;
|
|
36
|
+
runDirectory;
|
|
37
|
+
homeDirectory;
|
|
38
|
+
socketPath;
|
|
39
|
+
workspaceId;
|
|
40
|
+
grantedGeneration;
|
|
41
|
+
startPromise;
|
|
42
|
+
closePromise;
|
|
43
|
+
closed = false;
|
|
44
|
+
ready = false;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.options = options;
|
|
47
|
+
if (!plugins.path.isAbsolute(options.runtimeDirectory) || !plugins.path.isAbsolute(options.workspaceDirectory)
|
|
48
|
+
|| !plugins.path.isAbsolute(options.codexHomeDirectory)) {
|
|
49
|
+
throw new Error('Managed Codex requires absolute runtime, home and workspace directories.');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
get isReady() { return this.ready && this.client?.state === 'connected' && !this.closed; }
|
|
53
|
+
environment() {
|
|
54
|
+
const picked = {};
|
|
55
|
+
for (const key of ['PATH', 'HOME', 'USER', 'LOGNAME', 'LANG', 'LC_ALL', 'TERM', 'TMPDIR', 'XDG_RUNTIME_DIR']) {
|
|
56
|
+
if (process.env[key] !== undefined)
|
|
57
|
+
picked[key] = process.env[key];
|
|
58
|
+
}
|
|
59
|
+
picked.CODEX_HOME = this.homeDirectory;
|
|
60
|
+
return picked;
|
|
61
|
+
}
|
|
62
|
+
async requireVersion() {
|
|
63
|
+
const executable = this.options.executable ?? 'codex';
|
|
64
|
+
const output = await new Promise((resolve, reject) => {
|
|
65
|
+
plugins.childProcess.execFile(executable, ['--version'], { timeout: 5_000, maxBuffer: 4096,
|
|
66
|
+
windowsHide: true, env: this.environment() }, (error, stdout) => {
|
|
67
|
+
if (error)
|
|
68
|
+
reject(new Error('Managed Codex version probe failed.'));
|
|
69
|
+
else
|
|
70
|
+
resolve(stdout.trim());
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
if (output !== 'codex-cli 0.155.1')
|
|
74
|
+
throw new Error('Managed Codex requires verified codex-cli 0.155.1.');
|
|
75
|
+
}
|
|
76
|
+
start() {
|
|
77
|
+
if (this.closed || this.startPromise)
|
|
78
|
+
throw new Error('Managed Codex can start only once.');
|
|
79
|
+
this.startPromise = this.performStart();
|
|
80
|
+
return this.startPromise;
|
|
81
|
+
}
|
|
82
|
+
async performStart() {
|
|
83
|
+
try {
|
|
84
|
+
const root = this.options.runtimeDirectory;
|
|
85
|
+
await plugins.fs.promises.mkdir(root, { recursive: true, mode: 0o700 });
|
|
86
|
+
const rootStat = await plugins.fs.promises.lstat(root);
|
|
87
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink() || rootStat.uid !== process.getuid?.()) {
|
|
88
|
+
throw new Error('Managed Codex runtime directory is not private.');
|
|
89
|
+
}
|
|
90
|
+
await plugins.fs.promises.chmod(root, 0o700);
|
|
91
|
+
this.runDirectory = await plugins.fs.promises.mkdtemp(plugins.path.join(root, 'codex-'));
|
|
92
|
+
await plugins.fs.promises.chmod(this.runDirectory, 0o700);
|
|
93
|
+
this.homeDirectory = this.options.codexHomeDirectory;
|
|
94
|
+
this.socketPath = plugins.path.join(this.runDirectory, 'app.sock');
|
|
95
|
+
if (Buffer.byteLength(this.socketPath, 'utf8') > 100)
|
|
96
|
+
throw new Error('Managed Codex socket path is too long.');
|
|
97
|
+
const homeStat = await plugins.fs.promises.lstat(this.homeDirectory);
|
|
98
|
+
if (!homeStat.isDirectory() || homeStat.isSymbolicLink() || homeStat.uid !== process.getuid?.()
|
|
99
|
+
|| (homeStat.mode & 0o077) !== 0 || await plugins.fs.promises.realpath(this.homeDirectory) !== this.homeDirectory) {
|
|
100
|
+
throw new Error('Managed Codex home is not a private owned directory.');
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
await plugins.fs.promises.lstat(plugins.path.join(this.homeDirectory, 'auth.json'));
|
|
104
|
+
throw new Error('Managed Codex home contains a native credential file.');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!isRecord(error) || error.code !== 'ENOENT')
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
await this.requireVersion();
|
|
111
|
+
if (this.closed)
|
|
112
|
+
throw new Error('Managed Codex startup was cancelled.');
|
|
113
|
+
const initial = await this.options.broker.resolveAccess(this.options.bindingId, this.options.capability, 60_000);
|
|
114
|
+
if (this.closed)
|
|
115
|
+
throw new Error('Managed Codex startup was cancelled.');
|
|
116
|
+
this.workspaceId = initial.accountId;
|
|
117
|
+
this.grantedGeneration = initial.grantGeneration;
|
|
118
|
+
await this.options.onPreparedRun(this.socketPath);
|
|
119
|
+
if (this.closed)
|
|
120
|
+
throw new Error('Managed Codex startup was cancelled.');
|
|
121
|
+
const child = plugins.childProcess.spawn(this.options.executable ?? 'codex', [
|
|
122
|
+
'app-server', '--listen', `unix://${this.socketPath}`, '--strict-config',
|
|
123
|
+
'-c', 'cli_auth_credentials_store="ephemeral"',
|
|
124
|
+
], { cwd: this.options.workspaceDirectory, env: this.environment(), shell: false,
|
|
125
|
+
detached: process.platform !== 'win32', stdio: 'ignore', windowsHide: true });
|
|
126
|
+
this.child = child;
|
|
127
|
+
this.childExited = new Promise(resolve => {
|
|
128
|
+
child.once('error', () => resolve());
|
|
129
|
+
child.once('exit', () => resolve());
|
|
130
|
+
});
|
|
131
|
+
if (!child.pid)
|
|
132
|
+
throw new Error('Managed Codex app-server process has no PID.');
|
|
133
|
+
let observed = null;
|
|
134
|
+
for (let attempt = 0; attempt < 20 && !observed; attempt++) {
|
|
135
|
+
observed = await findManagedCodexProcess(this.socketPath);
|
|
136
|
+
if (!observed)
|
|
137
|
+
await wait(25);
|
|
138
|
+
}
|
|
139
|
+
if (!observed || observed.pid !== child.pid)
|
|
140
|
+
throw new Error('Managed Codex app-server process identity could not be verified.');
|
|
141
|
+
await this.options.onChildSpawned(observed.pid, observed.startedAt);
|
|
142
|
+
const deadline = Date.now() + 10_000;
|
|
143
|
+
while (true) {
|
|
144
|
+
if (this.closed)
|
|
145
|
+
throw new Error('Managed Codex startup was cancelled.');
|
|
146
|
+
if (!isAlive(child))
|
|
147
|
+
throw new Error('Managed Codex app-server exited before accepting connections.');
|
|
148
|
+
try {
|
|
149
|
+
if ((await plugins.fs.promises.lstat(this.socketPath)).isSocket())
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
if (!isRecord(error) || error.code !== 'ENOENT')
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
if (Date.now() >= deadline)
|
|
157
|
+
throw new Error('Managed Codex app-server socket startup timed out.');
|
|
158
|
+
await wait(25);
|
|
159
|
+
}
|
|
160
|
+
const client = new plugins.crossharness.CodexAppServerClient({
|
|
161
|
+
transport: { type: 'unix', socketPath: this.socketPath },
|
|
162
|
+
clientInfo: { name: 'authswitch', title: 'Authswitch', version: commitinfo.version },
|
|
163
|
+
experimentalApi: true, handshakeTimeoutMs: 5_000, initializeTimeoutMs: 5_000,
|
|
164
|
+
onNotification: notification => this.options.onNotification?.(notification),
|
|
165
|
+
onServerRequest: request => { void this.handleServerRequest(request); },
|
|
166
|
+
onClose: () => {
|
|
167
|
+
const wasReady = this.ready;
|
|
168
|
+
this.ready = false;
|
|
169
|
+
if (wasReady && !this.closed)
|
|
170
|
+
this.options.onUnavailable?.();
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
this.client = client;
|
|
174
|
+
await client.connect();
|
|
175
|
+
if (client.serverVersion !== '0.155.1')
|
|
176
|
+
throw new Error('Managed Codex app-server version changed.');
|
|
177
|
+
const login = await client.request('account/login/start', {
|
|
178
|
+
type: 'chatgptAuthTokens', accessToken: initial.accessToken, chatgptAccountId: initial.accountId,
|
|
179
|
+
}, 5_000);
|
|
180
|
+
if (!isRecord(login) || login.type !== 'chatgptAuthTokens') {
|
|
181
|
+
throw new Error('Managed Codex external-token login was not acknowledged.');
|
|
182
|
+
}
|
|
183
|
+
if (this.closed)
|
|
184
|
+
throw new Error('Managed Codex startup was cancelled.');
|
|
185
|
+
this.ready = true;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
try {
|
|
189
|
+
await this.close();
|
|
190
|
+
}
|
|
191
|
+
catch (cleanupError) {
|
|
192
|
+
throw new AggregateError([error, cleanupError], 'Managed Codex startup and cleanup failed.');
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async handleServerRequest(request) {
|
|
198
|
+
const client = this.client;
|
|
199
|
+
if (!client || this.closed || !this.ready)
|
|
200
|
+
return;
|
|
201
|
+
if (request.method !== 'account/chatgptAuthTokens/refresh') {
|
|
202
|
+
if (!this.options.onServerRequest) {
|
|
203
|
+
client.rejectRequest(request.id, -32601, 'Managed Codex request requires an owner handler.');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
this.options.onServerRequest(request, {
|
|
208
|
+
respond: result => client.respond(request.id, result),
|
|
209
|
+
reject: (code, message) => client.rejectRequest(request.id, code, message),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
client.rejectRequest(request.id, -32603, 'Managed Codex owner request failed.');
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const prior = this.grantedGeneration;
|
|
218
|
+
const workspaceId = this.workspaceId;
|
|
219
|
+
const params = request.params;
|
|
220
|
+
if (params.reason !== 'unauthorized' || !workspaceId || prior === undefined
|
|
221
|
+
|| (params.previousAccountId !== undefined && params.previousAccountId !== null
|
|
222
|
+
&& params.previousAccountId !== workspaceId)) {
|
|
223
|
+
client.rejectRequest(request.id, -32602, 'Managed Codex account identity is invalid.');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const refreshed = await this.options.broker.resolveAccess(this.options.bindingId, this.options.capability, 60_000, prior);
|
|
228
|
+
if (this.closed || request.signal.aborted)
|
|
229
|
+
return;
|
|
230
|
+
if (refreshed.accountId !== workspaceId || refreshed.grantGeneration <= prior) {
|
|
231
|
+
client.rejectRequest(request.id, -32603, 'Managed Codex account refresh did not advance.');
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.grantedGeneration = refreshed.grantGeneration;
|
|
235
|
+
client.respond(request.id, { accessToken: refreshed.accessToken,
|
|
236
|
+
chatgptAccountId: refreshed.accountId, chatgptPlanType: null });
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
if (!request.signal.aborted)
|
|
240
|
+
client.rejectRequest(request.id, -32603, 'Managed Codex account needs reauthentication.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** The interactive CLI attaches to the owned app-server; the daemon remains its auth control peer. */
|
|
244
|
+
interactiveCommand() {
|
|
245
|
+
if (!this.isReady || !this.socketPath)
|
|
246
|
+
throw new Error('Managed Codex is unavailable.');
|
|
247
|
+
return { executable: this.options.executable ?? 'codex', args: ['--remote', `unix://${this.socketPath}`],
|
|
248
|
+
cwd: this.options.workspaceDirectory, env: this.environment() };
|
|
249
|
+
}
|
|
250
|
+
/** Native app-server thread/turn API for noninteractive consumers; notifications go to onNotification. */
|
|
251
|
+
async startThread(options) {
|
|
252
|
+
if (!this.isReady || !this.client || !options || !['never', 'on-request'].includes(options.approvalPolicy)
|
|
253
|
+
|| (options.model !== undefined && (typeof options.model !== 'string' || options.model.length > 128))) {
|
|
254
|
+
throw new Error('Managed Codex thread request is unavailable or invalid.');
|
|
255
|
+
}
|
|
256
|
+
const started = await this.client.request('thread/start', {
|
|
257
|
+
cwd: this.options.workspaceDirectory, approvalPolicy: options.approvalPolicy,
|
|
258
|
+
...(options.model ? { model: options.model } : {}),
|
|
259
|
+
}, 30_000, options.signal);
|
|
260
|
+
if (!isRecord(started) || !isRecord(started.thread) || typeof started.thread.id !== 'string') {
|
|
261
|
+
throw new Error('Managed Codex returned an invalid thread.');
|
|
262
|
+
}
|
|
263
|
+
return started.thread.id;
|
|
264
|
+
}
|
|
265
|
+
async resumeThread(threadId, options) {
|
|
266
|
+
if (!this.isReady || !this.client || typeof threadId !== 'string' || !threadId || threadId.length > 256
|
|
267
|
+
|| !options || !['never', 'on-request'].includes(options.approvalPolicy)
|
|
268
|
+
|| (options.model !== undefined && (typeof options.model !== 'string' || options.model.length > 128))) {
|
|
269
|
+
throw new Error('Managed Codex resume request is unavailable or invalid.');
|
|
270
|
+
}
|
|
271
|
+
const resumed = await this.client.request('thread/resume', { threadId,
|
|
272
|
+
cwd: this.options.workspaceDirectory, approvalPolicy: options.approvalPolicy,
|
|
273
|
+
...(options.model ? { model: options.model } : {}),
|
|
274
|
+
}, 30_000, options.signal);
|
|
275
|
+
if (!isRecord(resumed) || !isRecord(resumed.thread) || resumed.thread.id !== threadId) {
|
|
276
|
+
throw new Error('Managed Codex could not resume the requested thread from its assigned home.');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async startTextTurn(text, options) {
|
|
280
|
+
if (!this.isReady || !this.client || typeof text !== 'string' || !text.trim() || text.length > 1_000_000
|
|
281
|
+
|| !options || !['never', 'on-request'].includes(options.approvalPolicy)
|
|
282
|
+
|| (options.model !== undefined && (typeof options.model !== 'string' || options.model.length > 128))
|
|
283
|
+
|| (options.threadId !== undefined && (typeof options.threadId !== 'string' || options.threadId.length > 256))) {
|
|
284
|
+
throw new Error('Managed Codex turn request is unavailable or invalid.');
|
|
285
|
+
}
|
|
286
|
+
let threadId = options.threadId;
|
|
287
|
+
if (threadId) {
|
|
288
|
+
await this.resumeThread(threadId, options);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
threadId = await this.startThread(options);
|
|
292
|
+
}
|
|
293
|
+
const turn = await this.client.request('turn/start', { threadId,
|
|
294
|
+
input: [{ type: 'text', text, text_elements: [] }],
|
|
295
|
+
approvalPolicy: options.approvalPolicy,
|
|
296
|
+
...(options.model ? { model: options.model } : {}),
|
|
297
|
+
}, 30_000, options.signal);
|
|
298
|
+
if (!isRecord(turn) || !isRecord(turn.turn) || typeof turn.turn.id !== 'string') {
|
|
299
|
+
throw new Error('Managed Codex returned an invalid turn.');
|
|
300
|
+
}
|
|
301
|
+
return { threadId, turnId: turn.turn.id };
|
|
302
|
+
}
|
|
303
|
+
close() {
|
|
304
|
+
if (this.closePromise)
|
|
305
|
+
return this.closePromise;
|
|
306
|
+
const wasReady = this.ready;
|
|
307
|
+
this.closed = true;
|
|
308
|
+
this.ready = false;
|
|
309
|
+
this.closePromise = this.performClose();
|
|
310
|
+
if (wasReady)
|
|
311
|
+
this.options.onUnavailable?.();
|
|
312
|
+
return this.closePromise;
|
|
313
|
+
}
|
|
314
|
+
async performClose() {
|
|
315
|
+
this.client?.close('Managed Codex stopped.');
|
|
316
|
+
const child = this.child;
|
|
317
|
+
if (child && isAlive(child) && child.pid) {
|
|
318
|
+
try {
|
|
319
|
+
process.kill(process.platform === 'win32' ? child.pid : -child.pid, 'SIGTERM');
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
if (!isRecord(error) || error.code !== 'ESRCH')
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
await Promise.race([this.childExited, wait(3_000)]);
|
|
326
|
+
if (isAlive(child)) {
|
|
327
|
+
try {
|
|
328
|
+
process.kill(process.platform === 'win32' ? child.pid : -child.pid, 'SIGKILL');
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
if (!isRecord(error) || error.code !== 'ESRCH')
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
await Promise.race([this.childExited, wait(3_000)]);
|
|
335
|
+
}
|
|
336
|
+
if (isAlive(child))
|
|
337
|
+
throw new Error('Managed Codex process did not stop.');
|
|
338
|
+
}
|
|
339
|
+
if (this.runDirectory)
|
|
340
|
+
await plugins.fs.promises.rm(this.runDirectory, { recursive: true, force: true });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jb2RleG1hbmFnZWQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmNvZGV4bWFuYWdlZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUE2QnJELE1BQU0sUUFBUSxHQUFHLENBQUMsS0FBYyxFQUFvQyxFQUFFLENBQ3BFLEtBQUssS0FBSyxJQUFJLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUN2RSxNQUFNLE9BQU8sR0FBRyxDQUFDLEtBQXdDLEVBQVcsRUFBRSxDQUFDLEtBQUssQ0FBQyxRQUFRLEtBQUssSUFBSSxJQUFJLEtBQUssQ0FBQyxVQUFVLEtBQUssSUFBSSxDQUFDO0FBQzVILE1BQU0sSUFBSSxHQUFHLENBQUMsRUFBVSxFQUFpQixFQUFFLENBQUMsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFFNUYsb0dBQW9HO0FBQ3BHLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEtBQUssRUFBRSxVQUFrQixFQUFzRCxFQUFFO0lBQ3RILElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPO1FBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyx5RUFBeUUsQ0FBQyxDQUFDO0lBQzdILE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxPQUFPLENBQVMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDM0QsT0FBTyxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxFQUFFLElBQUksRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxFQUFFLENB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QUFDLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxDQUFDLElBQUksS0FBSyxPQUFPO3dCQUFFLE1BQU0sS0FBSyxDQUFDO2dCQUFDLENBQUM7Z0JBQzlFLE1BQU0sT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0RCxDQUFDO1lBQ0QsSUFBSSxPQUFPLENBQUMsS0FBSyxDQUFDO2dCQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMscUNBQXFDLENBQUMsQ0FBQztRQUM3RSxDQUFDO1FBQ0QsSUFBSSxJQUFJLENBQUMsWUFBWTtZQUFFLE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0lBQzNHLENBQUM7Q0FDRiJ9
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -24,5 +24,6 @@ export * from './classes.watch.js';
|
|
|
24
24
|
export * from './classes.watchlock.js';
|
|
25
25
|
export * from './watchpolicy.js';
|
|
26
26
|
export * from './classes.authorityclient.js';
|
|
27
|
+
export * from './classes.authoritydaemon.js';
|
|
27
28
|
export * from './classes.authorityservice.js';
|
|
28
29
|
export declare const runCli: (argvArg?: string[]) => Promise<void>;
|
package/dist_ts/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export * from './classes.watch.js';
|
|
|
24
24
|
export * from './classes.watchlock.js';
|
|
25
25
|
export * from './watchpolicy.js';
|
|
26
26
|
export * from './classes.authorityclient.js';
|
|
27
|
+
export * from './classes.authoritydaemon.js';
|
|
27
28
|
export * from './classes.authorityservice.js';
|
|
28
29
|
import { AuthSwitchCli } from './classes.cli.js';
|
|
29
30
|
import { AuthSwitchAuthorityService, resolveAuthSwitchAuthorityPaths, runAuthSwitchAuthorityDaemon } from './classes.authorityservice.js';
|
|
@@ -53,4 +54,4 @@ export const runCli = async (argvArg = process.argv.slice(2)) => {
|
|
|
53
54
|
const exitCode = await new AuthSwitchCli().run(argvArg);
|
|
54
55
|
process.exitCode = exitCode;
|
|
55
56
|
};
|
|
56
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
57
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGlCQUFpQixDQUFDO0FBQ2hDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYyxzQkFBc0IsQ0FBQztBQUNyQyxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsOEJBQThCLENBQUM7QUFDN0MsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLCtCQUErQixDQUFDO0FBQzlDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDBCQUEwQixDQUFDO0FBQ3pDLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsY0FBYyxDQUFDO0FBQzdCLGNBQWMsd0JBQXdCLENBQUM7QUFDdkMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMseUJBQXlCLENBQUM7QUFDeEMsY0FBYywwQkFBMEIsQ0FBQztBQUN6QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHlCQUF5QixDQUFDO0FBQ3hDLGNBQWMsc0JBQXNCLENBQUM7QUFDckMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHdCQUF3QixDQUFDO0FBQ3ZDLGNBQWMsa0JBQWtCLENBQUM7QUFDakMsY0FBYyw4QkFBOEIsQ0FBQztBQUM3QyxjQUFjLDhCQUE4QixDQUFDO0FBQzdDLGNBQWMsK0JBQStCLENBQUM7QUFFOUMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pELE9BQU8sRUFBRSwwQkFBMEIsRUFBRSwrQkFBK0IsRUFBRSw0QkFBNEIsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRTFJLE1BQU0sQ0FBQyxNQUFNLE1BQU0sR0FBRyxLQUFLLEVBQUUsVUFBb0IsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQWlCLEVBQUU7SUFDdkYsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssV0FBVyxFQUFFLENBQUM7UUFDL0IsTUFBTSxLQUFLLEdBQUcsK0JBQStCLEVBQUUsQ0FBQztRQUNoRCxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsS0FBSyxRQUFRLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNwRCxNQUFNLDRCQUE0QixDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQzFDLE9BQU87UUFDVCxDQUFDO1FBQ0QsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDckQsTUFBTSxPQUFPLEdBQUcsSUFBSSwwQkFBMEIsQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztZQUN2RSxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDMUIsTUFBTSxNQUFNLEdBQUcsTUFBTSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFO2dCQUMzRCxDQUFDLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFO29CQUMvQyxDQUFDLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsTUFBTSxPQUFPLENBQUMsTUFBTSxFQUFFO3dCQUM5QyxDQUFDLENBQUMsTUFBTSxLQUFLLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxPQUFPLENBQUMsS0FBSyxFQUFFOzRCQUM1QyxDQUFDLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxDQUFDLENBQUMsTUFBTSxPQUFPLENBQUMsSUFBSSxFQUFFO2dDQUMxQyxDQUFDLENBQUMsSUFBSSxDQUFDO1lBQ1QsSUFBSSxNQUFNLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ3BCLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUM7Z0JBQ3BELE9BQU87WUFDVCxDQUFDO1FBQ0gsQ0FBQztRQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsK0VBQStFLENBQUMsQ0FBQztJQUNuRyxDQUFDO0lBQ0QsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLGFBQWEsRUFBRSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUN4RCxPQUFPLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztBQUM5QixDQUFDLENBQUMifQ==
|
package/dist_ts/plugins.d.ts
CHANGED
|
@@ -28,4 +28,5 @@ import * as flexModels from '@modelprofile.com/flexharness-models';
|
|
|
28
28
|
import * as flexOpenAi from '@modelprofile.com/flexharness-providers/openai';
|
|
29
29
|
import * as flexAuth from '@modelprofile.com/flexharness-providers/auth';
|
|
30
30
|
import * as flexAccounts from '@modelprofile.com/flexharness-providers/accounts';
|
|
31
|
-
|
|
31
|
+
import * as crossharness from '@modelprofile.com/mcp-crossharness';
|
|
32
|
+
export { flexModels, flexOpenAi, flexAuth, flexAccounts, crossharness };
|
package/dist_ts/plugins.js
CHANGED
|
@@ -29,5 +29,6 @@ import * as flexModels from '@modelprofile.com/flexharness-models';
|
|
|
29
29
|
import * as flexOpenAi from '@modelprofile.com/flexharness-providers/openai';
|
|
30
30
|
import * as flexAuth from '@modelprofile.com/flexharness-providers/auth';
|
|
31
31
|
import * as flexAccounts from '@modelprofile.com/flexharness-providers/accounts';
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
import * as crossharness from '@modelprofile.com/mcp-crossharness';
|
|
33
|
+
export { flexModels, flexOpenAi, flexAuth, flexAccounts, crossharness };
|
|
34
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsc0JBQXNCO0FBQ3RCLE9BQU8sS0FBSyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQzlCLE9BQU8sS0FBSyxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQzlCLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFDO0FBQ2xDLE9BQU8sS0FBSyxZQUFZLE1BQU0sb0JBQW9CLENBQUM7QUFDbkQsT0FBTyxLQUFLLE1BQU0sTUFBTSxhQUFhLENBQUM7QUFDdEMsT0FBTyxLQUFLLEdBQUcsTUFBTSxVQUFVLENBQUM7QUFDaEMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM1QyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBR3pDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLGFBQWEsRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUVsRSxNQUFNLGFBQWEsR0FBRyxhQUFhLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNyRCxrR0FBa0c7QUFDbEcsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHLEdBQWlDLEVBQUUsQ0FBQyxhQUFhLENBQUMsYUFBYSxDQUFDLENBQUM7QUFLM0Y7OztHQUdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsR0FBNkIsRUFBRSxDQUFDLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRTVGLHNCQUFzQjtBQUN0QixPQUFPLEtBQUssWUFBWSxNQUFNLDBCQUEwQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLE9BQU8sTUFBTSxxQkFBcUIsQ0FBQztBQUMvQyxPQUFPLEtBQUssV0FBVyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZELE9BQU8sS0FBSyxXQUFXLE1BQU0seUJBQXlCLENBQUM7QUFDdkQsT0FBTyxLQUFLLFlBQVksTUFBTSwwQkFBMEIsQ0FBQztBQUN6RCxPQUFPLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsQ0FBQztBQUVwRiw0QkFBNEI7QUFDNUIsT0FBTyxLQUFLLFVBQVUsTUFBTSxzQ0FBc0MsQ0FBQztBQUNuRSxPQUFPLEtBQUssVUFBVSxNQUFNLGdEQUFnRCxDQUFDO0FBQzdFLE9BQU8sS0FBSyxRQUFRLE1BQU0sOENBQThDLENBQUM7QUFDekUsT0FBTyxLQUFLLFlBQVksTUFBTSxrREFBa0QsQ0FBQztBQUNqRixPQUFPLEtBQUssWUFBWSxNQUFNLG9DQUFvQyxDQUFDO0FBQ25FLE9BQU8sRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLFFBQVEsRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/authswitch",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"./authority-client": {
|
|
22
22
|
"types": "./dist_ts/classes.authorityclient.d.ts",
|
|
23
23
|
"import": "./dist_ts/classes.authorityclient.js"
|
|
24
|
+
},
|
|
25
|
+
"./authority-daemon": {
|
|
26
|
+
"types": "./dist_ts/classes.authoritydaemon.d.ts",
|
|
27
|
+
"import": "./dist_ts/classes.authoritydaemon.js"
|
|
24
28
|
}
|
|
25
29
|
},
|
|
26
30
|
"type": "module",
|
|
@@ -45,13 +49,16 @@
|
|
|
45
49
|
"@git.zone/tsrun": "^2.0.6",
|
|
46
50
|
"@git.zone/tstest": "^6.1.1",
|
|
47
51
|
"@types/node": "26.5.1",
|
|
48
|
-
"
|
|
52
|
+
"@types/ws": "8.18.1",
|
|
53
|
+
"esbuild": "^0.28.2",
|
|
54
|
+
"ws": "8.21.2"
|
|
49
55
|
},
|
|
50
56
|
"dependencies": {
|
|
51
57
|
"@api.global/typedrequest": "8.1.0",
|
|
52
58
|
"@api.global/typedrequest-interfaces": "7.1.0",
|
|
53
59
|
"@modelprofile.com/flexharness-models": "^8.0.0",
|
|
54
60
|
"@modelprofile.com/flexharness-providers": "^8.0.0",
|
|
61
|
+
"@modelprofile.com/mcp-crossharness": "5.4.0",
|
|
55
62
|
"@push.rocks/smartconsole": "^1.3.0",
|
|
56
63
|
"@push.rocks/smartdaemon": "2.7.0",
|
|
57
64
|
"@push.rocks/smartdata": "11.14.2",
|
package/readme.md
CHANGED
|
@@ -46,8 +46,33 @@ Browser code can import the credential-free DTOs from
|
|
|
46
46
|
`@modelprofile.com/authswitch/authority-contract`. A backend binds an account to a
|
|
47
47
|
runtime incarnation and keeps the returned capability private. The runtime-only socket
|
|
48
48
|
resolves that binding to a current access token; its directory can be mounted into a
|
|
49
|
-
container without exposing the management socket.
|
|
50
|
-
|
|
49
|
+
container without exposing the management socket. A provider that rejects a specific
|
|
50
|
+
access-token generation can request a newer one through `rejectedGrantGeneration`; the
|
|
51
|
+
authority coalesces concurrent rejection callbacks and never replays an uncertain refresh.
|
|
52
|
+
|
|
53
|
+
`AuthSwitchClient.subscribe(onSnapshot, onEvent, signal, { onStatus })` reports `current`
|
|
54
|
+
after a fresh snapshot or verified heartbeat, `unavailable` on disconnect or resync, and
|
|
55
|
+
`closed` on abort. Keep cached account actions disabled while status is not `current`.
|
|
56
|
+
Every change-event batch supplies a fresh snapshot before `onEvent`, so a consumer does
|
|
57
|
+
not need to reconstruct account state from events. `lastVerifiedAt` records the last
|
|
58
|
+
successful daemon response, including an empty heartbeat.
|
|
59
|
+
|
|
60
|
+
A trusted in-process daemon host can call `AuthSwitchAuthorityDaemon.startManagedCodex()`
|
|
61
|
+
for a bound account and workspace. It owns a private Codex 0.155.1 app-server, passes only
|
|
62
|
+
the authority's current access token through Codex's external-token login, answers its
|
|
63
|
+
unauthorized callback through the authority, and revokes the binding on stop. The returned
|
|
64
|
+
runtime exposes an interactive `codex --remote unix://…` launch descriptor and native
|
|
65
|
+
`thread/start`, `thread/resume` and `turn/start` methods for noninteractive consumers.
|
|
66
|
+
Callers supply a private, durable `codexHomeDirectory` for Codex's own configuration and
|
|
67
|
+
conversation state. SmartData binds that home immutably to one account and scope, and the
|
|
68
|
+
daemon records its active process/socket identity before launching it. Admission refuses
|
|
69
|
+
an existing nonempty native home or a surviving app-server owner; a fresh empty home is
|
|
70
|
+
created on first use. Account removal drains its managed Codex processes before the
|
|
71
|
+
grant is removed, and targeted reauthentication holds new admissions until the device
|
|
72
|
+
operation settles. Only the private socket directory is removed on close. Codex's
|
|
73
|
+
credential store remains ephemeral and does not read or write `auth.json` in that home.
|
|
74
|
+
Codex 0.155.1 does not persist an unused thread created by `thread/start`; resume applies
|
|
75
|
+
after Codex has committed a rollout. The existing CLI has not yet been moved to this path.
|
|
51
76
|
|
|
52
77
|
This SDK is additive at this stage. The commands documented below still use the existing
|
|
53
78
|
credential stores. The authority daemon does not import those stores automatically,
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -16,7 +16,13 @@ export interface IReq_AuthSwitchBindAccount extends ITypedRequest {
|
|
|
16
16
|
/** Backend-only access operation. Refresh grants never cross this interface. */
|
|
17
17
|
export interface IReq_AuthSwitchResolveAccess extends ITypedRequest {
|
|
18
18
|
method: 'authswitch.authority.resolveAccess';
|
|
19
|
-
request: {
|
|
19
|
+
request: {
|
|
20
|
+
bindingId: string;
|
|
21
|
+
capability: string;
|
|
22
|
+
minValidityMs: number;
|
|
23
|
+
/** The provider rejected this exact access-token generation. */
|
|
24
|
+
rejectedGrantGeneration?: number;
|
|
25
|
+
};
|
|
20
26
|
response: {
|
|
21
27
|
accessToken: string;
|
|
22
28
|
accountId: string;
|
|
@@ -317,6 +317,13 @@ export class AuthSwitchAuthorityBroker {
|
|
|
317
317
|
return structuredClone(operation.public);
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/** Trusted daemon lifecycle hook: hold new managed runtimes out until targeted reauth settles. */
|
|
321
|
+
public async waitOperation(operationId: string): Promise<void> {
|
|
322
|
+
const operation = this.operations.get(operationId);
|
|
323
|
+
if (!isUuid(operationId) || !operation) throw new Error('Account operation expired or was not found.');
|
|
324
|
+
await operation.task;
|
|
325
|
+
}
|
|
326
|
+
|
|
320
327
|
private pruneOperations(): void {
|
|
321
328
|
for (const [id, operation] of this.operations) {
|
|
322
329
|
if (operation.finishedAt !== null && this.now() - operation.finishedAt > 600_000) this.operations.delete(id);
|
|
@@ -379,9 +386,19 @@ export class AuthSwitchAuthorityBroker {
|
|
|
379
386
|
return { binding: publicBinding(updated.binding), capability };
|
|
380
387
|
}
|
|
381
388
|
|
|
382
|
-
public async
|
|
389
|
+
public async revokeBinding(bindingId: string, capability: string): Promise<boolean> {
|
|
390
|
+
if (!isId(bindingId) || !/^[A-Za-z0-9_-]{43}$/.test(capability)) throw new Error('Invalid runtime binding revocation.');
|
|
391
|
+
const revoked = await this.database.revokeBinding(plugins.crypto.randomUUID(), bindingId, idHash(capability));
|
|
392
|
+
if (revoked) this.publish();
|
|
393
|
+
return revoked;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
public async resolveAccess(bindingId: string, capability: string, minValidityMs: number,
|
|
397
|
+
rejectedGrantGeneration?: number): Promise<IAuthSwitchResolvedAccess> {
|
|
383
398
|
if (!isId(bindingId) || !/^[A-Za-z0-9_-]{43}$/.test(capability)
|
|
384
|
-
|| !Number.isSafeInteger(minValidityMs) || minValidityMs < 0 || minValidityMs > 3_600_000
|
|
399
|
+
|| !Number.isSafeInteger(minValidityMs) || minValidityMs < 0 || minValidityMs > 3_600_000
|
|
400
|
+
|| (rejectedGrantGeneration !== undefined && (!Number.isSafeInteger(rejectedGrantGeneration)
|
|
401
|
+
|| rejectedGrantGeneration < 1))) throw new Error('Invalid runtime credential request.');
|
|
385
402
|
const supplied = Buffer.from(idHash(capability), 'hex');
|
|
386
403
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
387
404
|
const initial = await this.database.readBindingContext(bindingId);
|
|
@@ -395,6 +412,9 @@ export class AuthSwitchAuthorityBroker {
|
|
|
395
412
|
|| binding.grantAuthorizationGeneration !== grant.authorizationGeneration) {
|
|
396
413
|
throw new Error('Account needs reauthentication.');
|
|
397
414
|
}
|
|
415
|
+
if (rejectedGrantGeneration !== undefined && rejectedGrantGeneration > grant.grantGeneration) {
|
|
416
|
+
throw new Error('Provider rejected a future account grant generation.');
|
|
417
|
+
}
|
|
398
418
|
if (grant.state === 'exchange_may_have_been_sent') {
|
|
399
419
|
const inFlight = this.refreshes.get(account.id);
|
|
400
420
|
if (!inFlight) throw new Error('Account refresh is unresolved; reauthentication is required.');
|
|
@@ -405,9 +425,10 @@ export class AuthSwitchAuthorityBroker {
|
|
|
405
425
|
|| !['ready', 'retry_wait'].includes(grant.state)) {
|
|
406
426
|
throw new Error('Account needs reauthentication.');
|
|
407
427
|
}
|
|
408
|
-
|
|
428
|
+
const rejectedCurrent = rejectedGrantGeneration === grant.grantGeneration;
|
|
429
|
+
if (this.isDue(grant, minValidityMs) || rejectedCurrent) {
|
|
409
430
|
if (grant.state === 'ready' || (grant.retryAt !== null && Date.parse(grant.retryAt) <= this.now())) {
|
|
410
|
-
await this.refreshAccount(account.id);
|
|
431
|
+
await this.refreshAccount(account.id, rejectedCurrent ? rejectedGrantGeneration : undefined);
|
|
411
432
|
continue;
|
|
412
433
|
}
|
|
413
434
|
throw new Error('Account access credential is not fresh enough.');
|
|
@@ -431,7 +452,8 @@ export class AuthSwitchAuthorityBroker {
|
|
|
431
452
|
}
|
|
432
453
|
if (latest.grant.grantGeneration !== grant.grantGeneration
|
|
433
454
|
|| latest.grant.state === 'exchange_may_have_been_sent') continue;
|
|
434
|
-
if (!['ready', 'retry_wait'].includes(latest.grant.state) || this.isDue(latest.grant, minValidityMs)
|
|
455
|
+
if (!['ready', 'retry_wait'].includes(latest.grant.state) || this.isDue(latest.grant, minValidityMs)
|
|
456
|
+
|| (rejectedGrantGeneration !== undefined && latest.grant.grantGeneration === rejectedGrantGeneration)) {
|
|
435
457
|
throw new Error('Account access credential is not fresh enough.');
|
|
436
458
|
}
|
|
437
459
|
return { accessToken: credential.accessToken, accountId: account.workspaceId,
|
|
@@ -441,20 +463,20 @@ export class AuthSwitchAuthorityBroker {
|
|
|
441
463
|
throw new Error('Account credential rotated repeatedly during resolution; retry the request.');
|
|
442
464
|
}
|
|
443
465
|
|
|
444
|
-
private async refreshAccount(accountId: string): Promise<void> {
|
|
466
|
+
private async refreshAccount(accountId: string, rejectedGrantGeneration?: number): Promise<void> {
|
|
445
467
|
const prior = this.refreshes.get(accountId);
|
|
446
468
|
if (prior) return prior;
|
|
447
|
-
const task = this.performRefresh(accountId).finally(() => { if (this.refreshes.get(accountId) === task) this.refreshes.delete(accountId); });
|
|
469
|
+
const task = this.performRefresh(accountId, rejectedGrantGeneration).finally(() => { if (this.refreshes.get(accountId) === task) this.refreshes.delete(accountId); });
|
|
448
470
|
this.refreshes.set(accountId, task);
|
|
449
471
|
return task;
|
|
450
472
|
}
|
|
451
473
|
|
|
452
|
-
private async performRefresh(accountId: string): Promise<void> {
|
|
474
|
+
private async performRefresh(accountId: string, rejectedGrantGeneration?: number): Promise<void> {
|
|
453
475
|
const initial = await this.database.readAccountAndPrimaryGrant(accountId);
|
|
454
476
|
const initialGrant = initial.grant;
|
|
455
477
|
if (!initial.account || initial.account.removed || !initialGrant || initialGrant.purpose !== 'openai_managed'
|
|
456
478
|
|| !['ready', 'retry_wait'].includes(initialGrant.state) || initialGrant.owner !== 'daemon'
|
|
457
|
-
|| !this.isDue(initialGrant, 0)
|
|
479
|
+
|| (!this.isDue(initialGrant, 0) && initialGrant.grantGeneration !== rejectedGrantGeneration)
|
|
458
480
|
|| (initialGrant.state === 'retry_wait'
|
|
459
481
|
&& (initialGrant.retryAt === null || Date.parse(initialGrant.retryAt) > this.now()))) return;
|
|
460
482
|
const credential = await this.unsealCredential(initial.account, initialGrant);
|