@modelprofile.com/authswitch 8.1.0 → 8.2.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-contract.d.ts +56 -0
- package/dist_ts/authority-contract.js +54 -2
- package/dist_ts/authority-import-contract.d.ts +8 -4
- package/dist_ts/authority-import-contract.js +12 -13
- package/dist_ts/classes.authoritybroker.js +3 -2
- package/dist_ts/classes.authorityclient.d.ts +17 -11
- package/dist_ts/classes.authorityclient.js +28 -22
- package/dist_ts/classes.authoritydaemon.d.ts +8 -0
- package/dist_ts/classes.authoritydaemon.js +74 -58
- package/dist_ts/classes.authoritydatabase.d.ts +9 -1
- package/dist_ts/classes.authoritydatabase.js +29 -12
- package/dist_ts/classes.authorityimport.d.ts +17 -2
- package/dist_ts/classes.authorityimport.js +29 -3
- package/dist_ts/classes.authoritymodels.d.ts +4 -1
- package/dist_ts/classes.authoritymodels.js +15 -3
- package/dist_ts/classes.authorityservice.d.ts +10 -0
- package/dist_ts/classes.authorityservice.js +24 -1
- package/dist_ts/classes.claudeauthority.js +19 -11
- package/dist_ts/classes.claudenative.d.ts +63 -3
- package/dist_ts/classes.claudenative.js +68 -8
- package/dist_ts/ts_migration/0003_claude_handoff_proof.d.ts +13 -0
- package/dist_ts/ts_migration/0003_claude_handoff_proof.js +20 -0
- package/dist_ts/ts_migration/index.js +3 -1
- package/dist_ts/ts_migration/legacysources/nativestores.js +15 -8
- package/dist_ts/ts_migration/legacysources/shared.d.ts +3 -2
- package/dist_ts/ts_migration/legacysources/shared.js +3 -5
- package/package.json +5 -1
- package/readme.md +65 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/authority-contract.ts +106 -0
- package/ts/authority-import-contract.ts +12 -11
- package/ts/classes.authoritybroker.ts +2 -1
- package/ts/classes.authorityclient.ts +43 -21
- package/ts/classes.authoritydaemon.ts +71 -56
- package/ts/classes.authoritydatabase.ts +29 -11
- package/ts/classes.authorityimport.ts +30 -2
- package/ts/classes.authoritymodels.ts +11 -2
- package/ts/classes.authorityservice.ts +27 -0
- package/ts/classes.claudeauthority.ts +22 -10
- package/ts/classes.claudenative.ts +104 -10
- package/ts/ts_migration/0003_claude_handoff_proof.ts +19 -0
- package/ts/ts_migration/index.ts +2 -0
- package/ts/ts_migration/legacysources/nativestores.ts +15 -7
- package/ts/ts_migration/legacysources/shared.ts +4 -6
|
@@ -10,10 +10,10 @@ import type {
|
|
|
10
10
|
} from './authority-contract.js';
|
|
11
11
|
import type { IReq_AuthSwitchBindAccount, IReq_AuthSwitchReleaseBinding,
|
|
12
12
|
IReq_AuthSwitchResolveAccess } from './authority-runtime-contract.js';
|
|
13
|
-
import {
|
|
13
|
+
import { AuthSwitchRefusal } from './authority-contract.js';
|
|
14
14
|
import type { IReq_AuthSwitchImportInventory, IReq_AuthSwitchImportStatus, IReq_AuthSwitchImportSubmit,
|
|
15
15
|
TAuthSwitchImportSubmission } from './authority-import-contract.js';
|
|
16
|
-
import { AuthSwitchAuthorityImport,
|
|
16
|
+
import { AuthSwitchAuthorityImport,
|
|
17
17
|
type IAuthSwitchAuthorityImportOptions } from './classes.authorityimport.js';
|
|
18
18
|
import { authSwitchBindingCapabilityHash, AuthSwitchAuthorityBroker,
|
|
19
19
|
type IAuthSwitchAuthorityBrokerOptions } from './classes.authoritybroker.js';
|
|
@@ -44,6 +44,8 @@ export interface IAuthSwitchAuthorityDaemonOptions extends IAuthSwitchAuthorityD
|
|
|
44
44
|
const hasKeys = (value: unknown, keys: readonly string[]): value is Record<string, unknown> =>
|
|
45
45
|
value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
46
46
|
&& Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key));
|
|
47
|
+
// A malformed request is a caller's own bug, not an instruction for an owner, so it stays what it was:
|
|
48
|
+
// its own text, no refusal marker. A client must not present it as something the owner can act on.
|
|
47
49
|
const invalid = (): never => { throw new plugins.typedrequest.TypedResponseError('Invalid authswitch authority request.'); };
|
|
48
50
|
const validBindingId = (value: unknown): value is string => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
49
51
|
const validBindingCapability = (value: unknown): value is string => typeof value === 'string'
|
|
@@ -202,7 +204,7 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
202
204
|
const prepare = async (): Promise<void> => {
|
|
203
205
|
if (this.closing || this.drainingAccounts.has(request.accountId)
|
|
204
206
|
|| this.preuseAccounts.has(request.accountId)) {
|
|
205
|
-
throw new
|
|
207
|
+
throw new AuthSwitchRefusal('account_busy', 'Account runtime drain is already in progress.');
|
|
206
208
|
}
|
|
207
209
|
this.drainingAccounts.add(request.accountId);
|
|
208
210
|
this.deviceDrainOwners.set(request.accountId, request.operationId);
|
|
@@ -229,46 +231,67 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
229
231
|
});
|
|
230
232
|
}
|
|
231
233
|
|
|
234
|
+
/**
|
|
235
|
+
* One handler, with the only place a refusal becomes a marked answer.
|
|
236
|
+
*
|
|
237
|
+
* Both routers register through here, so a handler states its refusal by throwing `AuthSwitchRefusal`
|
|
238
|
+
* and never touches the wire shape; anything else it throws stays the transport's sanitised fault,
|
|
239
|
+
* which is what keeps "the outcome is unknown" distinguishable from "here is what to do instead".
|
|
240
|
+
*/
|
|
241
|
+
private handler<T extends ITypedRequest>(method: T['method'],
|
|
242
|
+
run: plugins.typedrequest.THandlerFunction<T>): plugins.typedrequest.TypedHandler<T> {
|
|
243
|
+
return new plugins.typedrequest.TypedHandler<T>(method, async (request, tools) => {
|
|
244
|
+
try { return await run(request, tools); }
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (error instanceof AuthSwitchRefusal) {
|
|
247
|
+
throw new plugins.typedrequest.TypedResponseError(error.message, error.data);
|
|
248
|
+
}
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
232
254
|
private register(): void {
|
|
233
|
-
this.managementRouter.addTypedHandler(
|
|
255
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchUsage>(
|
|
234
256
|
'authswitch.authority.usage', async request => {
|
|
235
257
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
236
258
|
|| !Object.hasOwn(request, 'accountId') || !Object.hasOwn(request, 'loginId')
|
|
237
259
|
|| Object.keys(request).some(key => !['accountId', 'loginId', 'force'].includes(key))
|
|
238
260
|
|| typeof request.accountId !== 'string' || typeof request.loginId !== 'string'
|
|
239
261
|
|| (request.force !== undefined && typeof request.force !== 'boolean')) invalid();
|
|
240
|
-
if (this.closing) throw new
|
|
262
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
241
263
|
return { usage: await this.usage.get(request.accountId, request.loginId, request.force ?? false) };
|
|
242
264
|
}));
|
|
243
|
-
this.managementRouter.addTypedHandler(
|
|
265
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchSwitchClaudeNative>(
|
|
244
266
|
'authswitch.authority.claude.switch', async request => {
|
|
245
267
|
if (!hasKeys(request, ['accountId', 'loginId', 'purpose'])
|
|
246
268
|
|| typeof request.accountId !== 'string' || typeof request.loginId !== 'string'
|
|
247
269
|
|| request.purpose !== 'claude_host_native') invalid();
|
|
248
|
-
if (this.closing
|
|
270
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
271
|
+
if (!this.claudeNative) throw new AuthSwitchRefusal('claude_home_unregistered', 'Claude native handoff is unavailable.');
|
|
249
272
|
return { handoff: await this.claudeNative.switchTo(request.accountId, request.loginId) };
|
|
250
273
|
}));
|
|
251
|
-
this.managementRouter.addTypedHandler(
|
|
274
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchClaudeNativeHandoff>(
|
|
252
275
|
'authswitch.authority.claude.handoff', async request => {
|
|
253
276
|
if (!hasKeys(request, ['operationId']) || typeof request.operationId !== 'string') invalid();
|
|
254
|
-
if (!this.claudeNative) throw new
|
|
277
|
+
if (!this.claudeNative) throw new AuthSwitchRefusal('claude_home_unregistered', 'Claude native handoff is unavailable.');
|
|
255
278
|
return { handoff: await this.claudeNative.getHandoff(request.operationId) };
|
|
256
279
|
}));
|
|
257
|
-
this.managementRouter.addTypedHandler(
|
|
280
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchClaudeNativeHandoffs>(
|
|
258
281
|
'authswitch.authority.claude.handoffs', async request => {
|
|
259
282
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
260
283
|
|| Object.keys(request).some(key => !['after', 'limit'].includes(key))) invalid();
|
|
261
|
-
if (!this.claudeNative) throw new
|
|
284
|
+
if (!this.claudeNative) throw new AuthSwitchRefusal('claude_home_unregistered', 'Claude native handoff is unavailable.');
|
|
262
285
|
return this.claudeNative.listHandoffs(request.after ?? null, request.limit ?? 128);
|
|
263
286
|
}));
|
|
264
|
-
this.managementRouter.addTypedHandler(
|
|
287
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchSnapshot>(
|
|
265
288
|
'authswitch.authority.snapshot', async request => {
|
|
266
289
|
if (request === null || typeof request !== 'object' || Array.isArray(request)) invalid();
|
|
267
290
|
const keys = Object.keys(request);
|
|
268
291
|
if (keys.some(key => !['accountAfter', 'loginAfter', 'bindingAfter', 'limit'].includes(key))) invalid();
|
|
269
292
|
return { snapshot: await this.broker.snapshot(request) };
|
|
270
293
|
}));
|
|
271
|
-
this.managementRouter.addTypedHandler(
|
|
294
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchDoctor>(
|
|
272
295
|
'authswitch.authority.doctor', async request => {
|
|
273
296
|
if (request === null || typeof request !== 'object' || Array.isArray(request)) invalid();
|
|
274
297
|
const cursorKeys = ['accountAfter', 'loginAfter', 'codexHomeAfter', 'claudeHomeAfter',
|
|
@@ -279,63 +302,63 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
279
302
|
|| request.limit < 1 || request.limit > 128))) invalid();
|
|
280
303
|
return { page: await this.doctorPage(request) };
|
|
281
304
|
}));
|
|
282
|
-
this.managementRouter.addTypedHandler(
|
|
305
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchImportInventory>(
|
|
283
306
|
'authswitch.authority.import.inventory', async request => {
|
|
284
307
|
// A read with no parameters: the inventory is the whole host, and a caller cannot narrow it into
|
|
285
308
|
// believing a source does not exist.
|
|
286
309
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
287
310
|
|| Object.keys(request).length !== 0) invalid();
|
|
288
|
-
if (this.closing) throw new
|
|
311
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
289
312
|
return { inventory: await this.legacyImport.inventory() };
|
|
290
313
|
}));
|
|
291
|
-
this.managementRouter.addTypedHandler(
|
|
314
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchImportStatus>(
|
|
292
315
|
'authswitch.authority.import.status', async request => {
|
|
293
316
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
294
317
|
|| Object.keys(request).some(key => !['after', 'limit'].includes(key))
|
|
295
318
|
|| (request.after !== undefined && !validHash(request.after))
|
|
296
319
|
|| (request.limit !== undefined && (!Number.isSafeInteger(request.limit)
|
|
297
320
|
|| request.limit < 1 || request.limit > 128))) invalid();
|
|
298
|
-
if (this.closing) throw new
|
|
321
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
299
322
|
return { status: await this.legacyImport.status(request) };
|
|
300
323
|
}));
|
|
301
|
-
this.managementRouter.addTypedHandler(
|
|
324
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchEvents>(
|
|
302
325
|
'authswitch.authority.events', async (request, tools) => {
|
|
303
326
|
if (!hasKeys(request, ['epoch', 'afterRevision', 'waitMs'])) invalid();
|
|
304
327
|
return this.broker.events(request.epoch, request.afterRevision, request.waitMs, tools?.abortSignal);
|
|
305
328
|
}));
|
|
306
|
-
this.managementRouter.addTypedHandler(
|
|
329
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchBeginAdd>(
|
|
307
330
|
'authswitch.authority.add', async request => {
|
|
308
331
|
if (!hasKeys(request, ['operationId', 'providerId', 'flow']) || typeof request.operationId !== 'string'
|
|
309
332
|
|| request.providerId !== 'openai' || request.flow !== 'device') invalid();
|
|
310
|
-
if (this.closing) throw new
|
|
333
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
311
334
|
return { operation: await this.broker.beginAddOpenAi(request.operationId) };
|
|
312
335
|
}));
|
|
313
|
-
this.managementRouter.addTypedHandler(
|
|
336
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchBeginReauth>(
|
|
314
337
|
'authswitch.authority.reauth', async request => {
|
|
315
338
|
if (!hasKeys(request, ['operationId', 'accountId', 'loginId', 'purpose', 'flow']) || request.flow !== 'device'
|
|
316
339
|
|| typeof request.operationId !== 'string'
|
|
317
340
|
|| typeof request.accountId !== 'string' || typeof request.loginId !== 'string'
|
|
318
341
|
|| request.purpose !== 'openai_managed') invalid();
|
|
319
|
-
if (this.closing) throw new
|
|
342
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
320
343
|
return { operation: await this.beginReauthOpenAi(request) };
|
|
321
344
|
}));
|
|
322
|
-
this.managementRouter.addTypedHandler(
|
|
345
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchGetOperation>(
|
|
323
346
|
'authswitch.authority.operation', async request => {
|
|
324
347
|
if (!hasKeys(request, ['operationId']) || typeof request.operationId !== 'string') invalid();
|
|
325
348
|
return { operation: await this.broker.getOperation(request.operationId) };
|
|
326
349
|
}));
|
|
327
|
-
this.managementRouter.addTypedHandler(
|
|
350
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchListOperations>(
|
|
328
351
|
'authswitch.authority.operations', async request => {
|
|
329
352
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
330
353
|
|| Object.keys(request).some(key => !['after', 'limit'].includes(key))) invalid();
|
|
331
354
|
return this.broker.listOperations(request.after ?? null, request.limit ?? 128);
|
|
332
355
|
}));
|
|
333
|
-
this.managementRouter.addTypedHandler(
|
|
356
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchCancelOperation>(
|
|
334
357
|
'authswitch.authority.cancel', async request => {
|
|
335
358
|
if (!hasKeys(request, ['operationId']) || typeof request.operationId !== 'string') invalid();
|
|
336
359
|
return { operation: await this.broker.cancelOperation(request.operationId) };
|
|
337
360
|
}));
|
|
338
|
-
this.managementRouter.addTypedHandler(
|
|
361
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchStartPreuse>(
|
|
339
362
|
'authswitch.authority.preuse.start', async request => {
|
|
340
363
|
if (request === null || typeof request !== 'object' || Array.isArray(request)
|
|
341
364
|
|| !['operationId', 'accountId', 'loginId', 'purpose', 'prompt'].every(key => Object.hasOwn(request, key))
|
|
@@ -344,7 +367,7 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
344
367
|
|| typeof request.loginId !== 'string' || request.purpose !== 'openai_managed'
|
|
345
368
|
|| typeof request.prompt !== 'string'
|
|
346
369
|
|| (request.model !== undefined && typeof request.model !== 'string')) invalid();
|
|
347
|
-
if (this.closing) throw new
|
|
370
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
348
371
|
const reservation: IPreuseAccountReservation = { operationId: request.operationId };
|
|
349
372
|
let ownsReservation = false;
|
|
350
373
|
const release = (): void => {
|
|
@@ -371,28 +394,28 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
371
394
|
throw error;
|
|
372
395
|
}
|
|
373
396
|
}));
|
|
374
|
-
this.managementRouter.addTypedHandler(
|
|
397
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchGetPreuse>(
|
|
375
398
|
'authswitch.authority.preuse.operation', async request => {
|
|
376
399
|
if (!hasKeys(request, ['operationId']) || typeof request.operationId !== 'string') invalid();
|
|
377
400
|
return { operation: await this.preuse.get(request.operationId) };
|
|
378
401
|
}));
|
|
379
|
-
this.managementRouter.addTypedHandler(
|
|
402
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchCancelPreuse>(
|
|
380
403
|
'authswitch.authority.preuse.cancel', async request => {
|
|
381
404
|
if (!hasKeys(request, ['operationId']) || typeof request.operationId !== 'string') invalid();
|
|
382
405
|
return { operation: await this.preuse.cancel(request.operationId) };
|
|
383
406
|
}));
|
|
384
|
-
this.managementRouter.addTypedHandler(
|
|
407
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchRenameAccount>(
|
|
385
408
|
'authswitch.authority.rename', async request => {
|
|
386
409
|
if (!hasKeys(request, ['accountId', 'expectedRevision', 'label']) || typeof request.accountId !== 'string'
|
|
387
410
|
|| typeof request.expectedRevision !== 'number' || typeof request.label !== 'string') invalid();
|
|
388
411
|
return { account: await this.broker.renameAccount(request.accountId, request.expectedRevision, request.label) };
|
|
389
412
|
}));
|
|
390
|
-
this.managementRouter.addTypedHandler(
|
|
413
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchRemoveAccount>(
|
|
391
414
|
'authswitch.authority.remove', async request => {
|
|
392
415
|
if (!hasKeys(request, ['accountId', 'expectedRevision']) || typeof request.accountId !== 'string'
|
|
393
416
|
|| typeof request.expectedRevision !== 'number') invalid();
|
|
394
417
|
if (this.drainingAccounts.has(request.accountId) || this.preuseAccounts.has(request.accountId)) {
|
|
395
|
-
throw new
|
|
418
|
+
throw new AuthSwitchRefusal('account_busy', 'Account runtime drain is already in progress.');
|
|
396
419
|
}
|
|
397
420
|
this.drainingAccounts.add(request.accountId);
|
|
398
421
|
try {
|
|
@@ -400,7 +423,7 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
400
423
|
return { account: await this.broker.removeAccount(request.accountId, request.expectedRevision) };
|
|
401
424
|
} finally { this.drainingAccounts.delete(request.accountId); }
|
|
402
425
|
}));
|
|
403
|
-
this.managementRouter.addTypedHandler(
|
|
426
|
+
this.managementRouter.addTypedHandler(this.handler<IReq_AuthSwitchBindAccount>(
|
|
404
427
|
'authswitch.authority.bind', async request => {
|
|
405
428
|
if (!hasKeys(request, ['accountId', 'loginId', 'purpose', 'runtime', 'scopeId', 'incarnationId'])
|
|
406
429
|
|| typeof request.accountId !== 'string' || typeof request.loginId !== 'string'
|
|
@@ -408,7 +431,7 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
408
431
|
|| typeof request.incarnationId !== 'string') invalid();
|
|
409
432
|
return this.broker.bindAccount(request);
|
|
410
433
|
}));
|
|
411
|
-
this.runtimeRouter.addTypedHandler(
|
|
434
|
+
this.runtimeRouter.addTypedHandler(this.handler<IReq_AuthSwitchResolveAccess>(
|
|
412
435
|
'authswitch.authority.resolveAccess', async request => {
|
|
413
436
|
if ((typeof request !== 'object' || request === null || Array.isArray(request))
|
|
414
437
|
|| !['bindingId', 'capability', 'minValidityMs'].every(key => Object.hasOwn(request, key))
|
|
@@ -417,41 +440,33 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
417
440
|
|| !validBindingId(request.bindingId) || !validBindingCapability(request.capability)
|
|
418
441
|
|| typeof request.minValidityMs !== 'number'
|
|
419
442
|
|| (request.rejectedGrantGeneration !== undefined && typeof request.rejectedGrantGeneration !== 'number')) invalid();
|
|
420
|
-
if (this.closing) throw new
|
|
443
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
421
444
|
const finish = this.beginRuntimeBindingAccess(this.runtimeBindingKey(request.bindingId, request.capability));
|
|
422
445
|
try {
|
|
423
446
|
return await this.broker.resolveAccess(request.bindingId, request.capability, request.minValidityMs,
|
|
424
447
|
request.rejectedGrantGeneration);
|
|
425
448
|
} finally { finish(); }
|
|
426
449
|
}));
|
|
427
|
-
this.runtimeRouter.addTypedHandler(
|
|
450
|
+
this.runtimeRouter.addTypedHandler(this.handler<IReq_AuthSwitchReleaseBinding>(
|
|
428
451
|
'authswitch.authority.release', async request => {
|
|
429
452
|
if (!hasKeys(request, ['bindingId', 'capability'])
|
|
430
453
|
|| !validBindingId(request.bindingId) || !validBindingCapability(request.capability)) invalid();
|
|
431
|
-
if (this.closing) throw new
|
|
454
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
432
455
|
return this.releaseRuntimeBinding(request.bindingId, request.capability);
|
|
433
456
|
}));
|
|
434
|
-
this.runtimeRouter.addTypedHandler(
|
|
457
|
+
this.runtimeRouter.addTypedHandler(this.handler<IReq_AuthSwitchImportSubmit>(
|
|
435
458
|
'authswitch.authority.import.submit', async request => {
|
|
436
459
|
// Backend-only: an external submission carries the material of a store this package cannot read.
|
|
437
460
|
if (!hasKeys(request, ['submission', 'callerQuiescent', 'acknowledgeRunOrder'])
|
|
438
461
|
|| request.callerQuiescent !== true || request.acknowledgeRunOrder !== true
|
|
439
462
|
|| !validImportSubmission(request.submission)) invalid();
|
|
440
|
-
if (this.closing) throw new
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// that instruction apart from the transport's sanitised text for an unexpected failure, whose
|
|
448
|
-
// import outcome is unknown. Everything else stays sanitised and unmarked.
|
|
449
|
-
if (error instanceof AuthSwitchImportRefusal) {
|
|
450
|
-
throw new plugins.typedrequest.TypedResponseError(error.message,
|
|
451
|
-
{ reason: authSwitchImportRefusalReason });
|
|
452
|
-
}
|
|
453
|
-
throw error;
|
|
454
|
-
}
|
|
463
|
+
if (this.closing) throw new AuthSwitchRefusal('authority_closing', 'Authswitch authority is closing.');
|
|
464
|
+
// An importer refusal is an `AuthSwitchRefusal` like every other, so the one conversion in
|
|
465
|
+
// `handler()` marks it. What stays special is its meaning: an UNMARKED failure of this route
|
|
466
|
+
// leaves the import outcome unknown, and the source must be read with `import.status` rather
|
|
467
|
+
// than submitted again.
|
|
468
|
+
return { result: await this.legacyImport.submit({ submission: request.submission,
|
|
469
|
+
callerQuiescent: true, acknowledgeRunOrder: true }) };
|
|
455
470
|
}));
|
|
456
471
|
}
|
|
457
472
|
|
|
@@ -593,13 +608,13 @@ export class AuthSwitchAuthorityDaemon {
|
|
|
593
608
|
const run = home.activeRun;
|
|
594
609
|
if (!run) continue;
|
|
595
610
|
const survivor = await findManagedCodexProcess(run.socketPath);
|
|
596
|
-
if (survivor) throw new
|
|
611
|
+
if (survivor) throw new AuthSwitchRefusal('account_busy', 'A managed Codex app-server still owns this account; stop its work before changing the account.');
|
|
597
612
|
if (run.pid === null || run.startedAt === null) {
|
|
598
|
-
throw new
|
|
613
|
+
throw new AuthSwitchRefusal('account_busy', 'A managed Codex launch has uncertain process ownership; reconcile it before changing the account.');
|
|
599
614
|
}
|
|
600
615
|
try {
|
|
601
616
|
process.kill(run.pid, 0);
|
|
602
|
-
throw new
|
|
617
|
+
throw new AuthSwitchRefusal('account_busy', 'A managed Codex process identity is still present; reconcile it before changing the account.');
|
|
603
618
|
} catch (error) {
|
|
604
619
|
if (!(error && typeof error === 'object' && 'code' in error && error.code === 'ESRCH')) throw error;
|
|
605
620
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
+
import { AuthSwitchRefusal } from './authority-contract.js';
|
|
2
3
|
import {
|
|
3
4
|
AuthSwitchAuthorityAccountModel, AuthSwitchAuthorityBindingModel, AuthSwitchAuthorityClaudeHandoffModel,
|
|
4
5
|
AuthSwitchAuthorityClaudeHomeModel, AuthSwitchAuthorityCodexHomeModel, AuthSwitchAuthorityEnrollmentModel,
|
|
@@ -358,7 +359,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
358
359
|
|| account.primaryGrantId !== intent.grantId || !grant || grant.accountId !== intent.accountId
|
|
359
360
|
|| grant.providerId !== 'openai' || grant.purpose !== 'openai_managed'
|
|
360
361
|
|| grant.owner !== 'daemon' || ['removed', 'exchange_may_have_been_sent'].includes(grant.state)) {
|
|
361
|
-
throw new
|
|
362
|
+
throw new AuthSwitchRefusal('login_unavailable', 'Selected OpenAI login is unavailable for reauthentication.');
|
|
362
363
|
}
|
|
363
364
|
expectedGrantGeneration = grant.grantGeneration;
|
|
364
365
|
}
|
|
@@ -1038,9 +1039,9 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1038
1039
|
if (accountStored && accountStored.primaryGrantId !== grantId) {
|
|
1039
1040
|
throw new Error('Primary grant identity cannot be replaced.');
|
|
1040
1041
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
throw new Error(
|
|
1042
|
+
// A device login commits daemon ownership; it is no proof that a native owner has stopped refreshing.
|
|
1043
|
+
if (grantStored && transfersGrantRefresher(grantStored.owner, draft.grant.owner)) {
|
|
1044
|
+
throw new Error(grantRefresherTransferRefusal(grantStored.owner, draft.grant.owner));
|
|
1044
1045
|
}
|
|
1045
1046
|
const completed: IStoredAuthorityDeviceOperation = {
|
|
1046
1047
|
...operation, state: 'complete', prompt: null,
|
|
@@ -1093,10 +1094,14 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1093
1094
|
if (draft.id !== grantId || draft.accountId !== account.id || draft.updateId !== updateId) {
|
|
1094
1095
|
throw new Error('Grant change has an invalid identity.');
|
|
1095
1096
|
}
|
|
1097
|
+
// This writer carries no handoff proof, so it may only move a grant within the ownership it already has.
|
|
1098
|
+
if (transfersGrantRefresher(grant.owner, draft.owner)) {
|
|
1099
|
+
throw new Error(grantRefresherTransferRefusal(grant.owner, draft.owner));
|
|
1100
|
+
}
|
|
1096
1101
|
if (grant.purpose === 'claude_host_native' && draft.state === 'exchange_may_have_been_sent'
|
|
1097
1102
|
&& grant.state !== 'exchange_may_have_been_sent'
|
|
1098
1103
|
&& !(await this.hasVerifiedClaudeGrantReceipt(account.id, grant.id, session))) {
|
|
1099
|
-
throw new
|
|
1104
|
+
throw new AuthSwitchRefusal('claude_receipt_missing', 'Claude refresh requires a verified import receipt.');
|
|
1100
1105
|
}
|
|
1101
1106
|
const accountDraft: IStoredAuthorityAccount = { ...account, revision: account.revision + 1,
|
|
1102
1107
|
statusObservedAt: draft.statusObservedAt, updateId };
|
|
@@ -1166,7 +1171,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1166
1171
|
const metaStored = await AuthSwitchAuthorityMetaModel.exact.findStoredOne({ id: 'authswitch-authority' }, { session });
|
|
1167
1172
|
const accountStored = await AuthSwitchAuthorityAccountModel.exact.findStoredOne({ id: accountId }, { session });
|
|
1168
1173
|
if (!metaStored || !accountStored || accountStored.removed || accountStored.revision !== expectedRevision) {
|
|
1169
|
-
throw new
|
|
1174
|
+
throw new AuthSwitchRefusal('account_changed', 'Account changed; refresh before removing.');
|
|
1170
1175
|
}
|
|
1171
1176
|
const account = AuthSwitchAuthorityAccountModel.exact.toPersisted(accountStored);
|
|
1172
1177
|
const scan = async (visit: (grant: IStoredAuthorityGrant,
|
|
@@ -1185,13 +1190,13 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1185
1190
|
await scan(async grant => {
|
|
1186
1191
|
if (grant.state === 'removed') return;
|
|
1187
1192
|
if (grant.owner === 'claude_native' || grant.owner === 'legacy_native') {
|
|
1188
|
-
throw new
|
|
1193
|
+
throw new AuthSwitchRefusal('native_owner_holds_login', 'Stop the native owner and complete its handoff before removing this account.');
|
|
1189
1194
|
}
|
|
1190
1195
|
if (grant.state === 'exchange_may_have_been_sent') {
|
|
1191
|
-
throw new
|
|
1196
|
+
throw new AuthSwitchRefusal('account_busy', 'Wait for the account refresh to finish before removing it.');
|
|
1192
1197
|
}
|
|
1193
1198
|
if (['handoff_pending', 'handoff_quarantined'].includes(grant.state)) {
|
|
1194
|
-
throw new
|
|
1199
|
+
throw new AuthSwitchRefusal('claude_handoff_pending', 'Resolve the Claude native handoff before removing this account.');
|
|
1195
1200
|
}
|
|
1196
1201
|
});
|
|
1197
1202
|
await scan(async (grant, stored) => {
|
|
@@ -1530,7 +1535,15 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1530
1535
|
return { handoffs, nextCursor: stored.length > limit ? handoffs.at(-1)!.id : null };
|
|
1531
1536
|
}
|
|
1532
1537
|
|
|
1533
|
-
/**
|
|
1538
|
+
/**
|
|
1539
|
+
* Only a verified backend import receipt may adopt a native home. No startup autodiscovery exists.
|
|
1540
|
+
*
|
|
1541
|
+
* `import.submit` is the one route that reaches this, so its two decided outcomes -- a home another grant
|
|
1542
|
+
* holds, and a login another home already holds -- answer with the importer's published code: the
|
|
1543
|
+
* transaction refused, the ledger row stays `verified` and nothing rotating was sent, while an unmarked
|
|
1544
|
+
* failure of that route means the outcome is unknown and the source must be read with `import.status`
|
|
1545
|
+
* instead. The ambiguous write below keeps that unmarked meaning, because it genuinely is unknown.
|
|
1546
|
+
*/
|
|
1534
1547
|
public async registerClaudeHomeFromReceipt(input: { homeId: string; accountId: string; grantId: string;
|
|
1535
1548
|
receiptId: string; sourceDigest: string; accountUuid: string; organizationUuid: string;
|
|
1536
1549
|
updateId: string; now: string }): Promise<IStoredAuthorityClaudeHome> {
|
|
@@ -1558,7 +1571,7 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1558
1571
|
}
|
|
1559
1572
|
if (current) {
|
|
1560
1573
|
if (current.activeAccountId !== accountId || current.activeGrantId !== grantId) {
|
|
1561
|
-
throw new
|
|
1574
|
+
throw new AuthSwitchRefusal('import_refusal', 'Claude native home belongs to another grant.');
|
|
1562
1575
|
}
|
|
1563
1576
|
return AuthSwitchAuthorityClaudeHomeModel.exact.toPersisted(current);
|
|
1564
1577
|
}
|
|
@@ -1575,6 +1588,11 @@ export class AuthSwitchAuthorityDatabase {
|
|
|
1575
1588
|
|| !['ambiguous_write', 'unique_conflict'].includes(error.code)) throw error;
|
|
1576
1589
|
const home = await this.readClaudeHome(homeId);
|
|
1577
1590
|
if (home?.updateId === updateId && home.activeAccountId === accountId && home.activeGrantId === grantId) return home;
|
|
1591
|
+
// One grant, one home: the unique index refused a second home for a login another one already holds.
|
|
1592
|
+
if (error.code === 'unique_conflict') {
|
|
1593
|
+
throw new AuthSwitchRefusal('import_refusal',
|
|
1594
|
+
'This Claude login is already registered to another native home.');
|
|
1595
|
+
}
|
|
1578
1596
|
throw new Error('Claude native home registration outcome is unknown.');
|
|
1579
1597
|
}
|
|
1580
1598
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type { AuthSwitchAuthorityDatabase } from './classes.authoritydatabase.js';
|
|
3
3
|
import { authSwitchEnvironmentId } from './classes.operations.js';
|
|
4
|
+
import { AuthSwitchRefusal } from './authority-contract.js';
|
|
4
5
|
import { AuthSwitchTpmSecretCodec, type IAuthSwitchSecretCodec } from './classes.authoritysecrets.js';
|
|
5
6
|
import { ClaudeAccountStatus, type IClaudeLogin, type IClaudeStatusRead } from './classes.claudestatus.js';
|
|
6
7
|
import { ClaudeTokenRefresh, type TClaudeAuthorityRefreshResult } from './classes.claudetokenrefresh.js';
|
|
@@ -168,8 +169,17 @@ const importAction = (status: TAuthSwitchImportLedgerStatus,
|
|
|
168
169
|
return handoff !== null && spentHandoffPhases.includes(handoff.phase) ? 'device_login' : 'resume';
|
|
169
170
|
};
|
|
170
171
|
|
|
171
|
-
/**
|
|
172
|
-
|
|
172
|
+
/**
|
|
173
|
+
* A refusal the owner can act on. It never leaves a half-written import behind.
|
|
174
|
+
*
|
|
175
|
+
* One class hierarchy for the whole authority: this is the authority's refusal with the importer's code,
|
|
176
|
+
* which is what keeps the marker this route has published since 8.1.0 on the wire.
|
|
177
|
+
*/
|
|
178
|
+
export class AuthSwitchImportRefusal extends AuthSwitchRefusal {
|
|
179
|
+
constructor(instruction: string) {
|
|
180
|
+
super('import_refusal', instruction);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
173
183
|
|
|
174
184
|
// The annotation on the binding, not only on the arrow, is what lets a refusal narrow the code after it.
|
|
175
185
|
const refuse: (reason: string) => never = reason => { throw new AuthSwitchImportRefusal(reason); };
|
|
@@ -504,10 +514,28 @@ export class AuthSwitchAuthorityImport {
|
|
|
504
514
|
throw error;
|
|
505
515
|
}
|
|
506
516
|
await this.moveLedger(record.id, 'verified', identity.accountId, identity.grantId);
|
|
517
|
+
if (record.sourceKind === 'claude_native') await this.adoptClaudeHome(record, identity);
|
|
507
518
|
await this.moveLedger(record.id, 'complete', identity.accountId, identity.grantId);
|
|
508
519
|
return this.result(record, 'complete', identity, []);
|
|
509
520
|
}
|
|
510
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Adopts the native Claude home this source is, so the daemon may serve `claude.*` for it.
|
|
524
|
+
*
|
|
525
|
+
* The verified receipt is the only thing that may adopt a home, and the source id it was earned with is
|
|
526
|
+
* the home id, so nothing else has to be trusted here. Re-submitting the same source returns the same
|
|
527
|
+
* home record rather than writing a second one.
|
|
528
|
+
*/
|
|
529
|
+
private async adoptClaudeHome(record: ILegacySourceRecord,
|
|
530
|
+
identity: { accountId: string; subject: string; workspaceId: string; grantId: string }): Promise<void> {
|
|
531
|
+
await this.database.registerClaudeHomeFromReceipt({
|
|
532
|
+
homeId: record.sourcePathHash, accountId: identity.accountId, grantId: identity.grantId,
|
|
533
|
+
receiptId: record.id, sourceDigest: record.sourceDigest,
|
|
534
|
+
accountUuid: identity.subject, organizationUuid: identity.workspaceId,
|
|
535
|
+
updateId: plugins.crypto.randomUUID(), now: this.stamp(),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
511
539
|
/** The non-rotating proof: the stored access token is asked about itself and must name this account. */
|
|
512
540
|
private async proveWithoutRotation(record: ILegacySourceRecord,
|
|
513
541
|
identity: { accountId: string; subject: string; workspaceId: string },
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import type { TAuthSwitchLoginPrompt } from './authority-contract.js';
|
|
2
|
+
import type { TAuthSwitchClaudeProofFailure, TAuthSwitchLoginPrompt } from './authority-contract.js';
|
|
3
3
|
import type { IAuthSwitchStoredUsage } from './classes.authorityusage.js';
|
|
4
4
|
|
|
5
5
|
export interface IStoredAuthorityMeta {
|
|
@@ -155,6 +155,8 @@ export interface IStoredAuthorityClaudeHandoff {
|
|
|
155
155
|
sealedOutgoing: string | null;
|
|
156
156
|
runningEffectiveAuth: 'no_scoped_sessions' | 'unsupported_effective_auth' | null;
|
|
157
157
|
problem: 'none' | 'native_uncertain' | 'foreign_or_torn' | 'unsupported_effective_auth' | 'database_uncertain';
|
|
158
|
+
/** Which condition of the native-login proof failed; stored, so a lost answer stays diagnosable. */
|
|
159
|
+
proofFailure: TAuthSwitchClaudeProofFailure | null;
|
|
158
160
|
startedAt: string;
|
|
159
161
|
updatedAt: string;
|
|
160
162
|
revision: number;
|
|
@@ -502,7 +504,7 @@ export const assertStoredAuthorityClaudeHandoff: (value: unknown) => asserts val
|
|
|
502
504
|
if (!object(value) || !exactKeys(value, ['id', 'homeId', 'outgoingAccountId', 'outgoingGrantId',
|
|
503
505
|
'incomingAccountId', 'incomingGrantId', 'phase', 'before', 'after', 'sourceIdentity',
|
|
504
506
|
'targetIdentity', 'sourceAccessDigest', 'targetAccessDigest', 'sealedOutgoing',
|
|
505
|
-
'runningEffectiveAuth', 'problem', 'startedAt', 'updatedAt', 'revision', 'updateId'])
|
|
507
|
+
'runningEffectiveAuth', 'problem', 'proofFailure', 'startedAt', 'updatedAt', 'revision', 'updateId'])
|
|
506
508
|
|| !uuid(value.id) || !hash(value.homeId) || !hash(value.outgoingAccountId)
|
|
507
509
|
|| !hash(value.outgoingGrantId) || !hash(value.incomingAccountId) || !hash(value.incomingGrantId)
|
|
508
510
|
|| value.outgoingAccountId === value.incomingAccountId || value.outgoingGrantId === value.incomingGrantId
|
|
@@ -514,8 +516,14 @@ export const assertStoredAuthorityClaudeHandoff: (value: unknown) => asserts val
|
|
|
514
516
|
|| !ciphertext(value.sealedOutgoing)
|
|
515
517
|
|| ![null, 'no_scoped_sessions', 'unsupported_effective_auth'].includes(value.runningEffectiveAuth as null)
|
|
516
518
|
|| !['none', 'native_uncertain', 'foreign_or_torn', 'unsupported_effective_auth', 'database_uncertain'].includes(String(value.problem))
|
|
519
|
+
|| ![null, 'unsupported_release', 'override', 'profile', 'profile_unreadable', 'settings',
|
|
520
|
+
'subscription', 'running_session'].includes(value.proofFailure as null)
|
|
517
521
|
|| !iso(value.startedAt) || !iso(value.updatedAt) || !revision(value.revision)
|
|
518
522
|
|| value.revision === 0 || !uuid(value.updateId)) throw new Error('Invalid Claude native handoff.');
|
|
523
|
+
// The detail belongs to exactly the problem it explains, so a client can trust one without the other.
|
|
524
|
+
if (value.proofFailure !== null && value.problem !== 'unsupported_effective_auth') {
|
|
525
|
+
throw new Error('Claude handoff names a proof failure without the problem it explains.');
|
|
526
|
+
}
|
|
519
527
|
const hasProof = value.before !== null && value.after !== null
|
|
520
528
|
&& value.sourceAccessDigest !== null && value.targetAccessDigest !== null;
|
|
521
529
|
const hasNoProof = value.before === null && value.after === null
|
|
@@ -838,6 +846,7 @@ export class AuthSwitchAuthorityClaudeHandoffModel extends plugins.nosqldb.Smart
|
|
|
838
846
|
@plugins.nosqldb.svDb() public sealedOutgoing!: string | null;
|
|
839
847
|
@plugins.nosqldb.svDb() public runningEffectiveAuth!: IStoredAuthorityClaudeHandoff['runningEffectiveAuth'];
|
|
840
848
|
@plugins.nosqldb.svDb() public problem!: IStoredAuthorityClaudeHandoff['problem'];
|
|
849
|
+
@plugins.nosqldb.svDb() public proofFailure!: IStoredAuthorityClaudeHandoff['proofFailure'];
|
|
841
850
|
@plugins.nosqldb.svDb() public startedAt!: string;
|
|
842
851
|
@plugins.nosqldb.svDb() public updatedAt!: string;
|
|
843
852
|
@plugins.nosqldb.svDb() public revision!: number;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { AuthSwitchAuthorityDaemon } from './classes.authoritydaemon.js';
|
|
3
|
+
import type { IClaudeNativeAuthorityOptions } from './classes.claudeauthority.js';
|
|
4
|
+
import { ClaudeNativeAdapter, claudeNativeUserSettingsFiles,
|
|
5
|
+
resolveClaudeNativeHome } from './classes.claudenative.js';
|
|
3
6
|
|
|
4
7
|
export interface IAuthSwitchAuthorityPaths {
|
|
5
8
|
runtimeDirectory: string;
|
|
@@ -60,12 +63,36 @@ export class AuthSwitchAuthorityService {
|
|
|
60
63
|
public stop(): Promise<plugins.smartdaemon.ISystemdUnitState> { return this.unit.stop(); }
|
|
61
64
|
}
|
|
62
65
|
|
|
66
|
+
/**
|
|
67
|
+
* The Claude native authority this user's daemon serves.
|
|
68
|
+
*
|
|
69
|
+
* It is wired whether or not Claude Code is installed: nothing here touches the filesystem, and every
|
|
70
|
+
* operation that needs the native home refuses until a verified import receipt has registered it. An
|
|
71
|
+
* environment that names a home this daemon cannot resolve is a named startup failure rather than a
|
|
72
|
+
* silently missing capability, because the same environment also decides which file the import reads.
|
|
73
|
+
*/
|
|
74
|
+
export const resolveAuthSwitchClaudeNativeAuthority = (env: NodeJS.ProcessEnv = process.env,
|
|
75
|
+
homeDirectory: string = plugins.os.homedir()): IClaudeNativeAuthorityOptions => {
|
|
76
|
+
const resolution = resolveClaudeNativeHome(env, homeDirectory);
|
|
77
|
+
if (resolution.kind === 'unusable') {
|
|
78
|
+
throw new Error(`Authswitch authority cannot locate Claude Code's native home: ${resolution.defect.problem}.`);
|
|
79
|
+
}
|
|
80
|
+
const { configDir, configFile, homeId } = resolution.home;
|
|
81
|
+
// The adapter re-derives which home Claude Code itself selects; it must read the environment this
|
|
82
|
+
// home was resolved from, or the two could disagree about the same host. Its settings sources are
|
|
83
|
+
// stated from that same home: this daemon's working directory is the service unit's own (the package
|
|
84
|
+
// root, see `AuthSwitchAuthorityService`), so a project settings file beside it selects nothing.
|
|
85
|
+
return { homeId, adapter: new ClaudeNativeAdapter({ configDir, configFile, env,
|
|
86
|
+
settingsFiles: claudeNativeUserSettingsFiles(configDir) }) };
|
|
87
|
+
};
|
|
88
|
+
|
|
63
89
|
export const runAuthSwitchAuthorityDaemon = async (paths = resolveAuthSwitchAuthorityPaths()): Promise<void> => {
|
|
64
90
|
const daemon = new AuthSwitchAuthorityDaemon({
|
|
65
91
|
dataDirectory: paths.dataDirectory,
|
|
66
92
|
socketPath: paths.databaseSocketPath,
|
|
67
93
|
authoritySocketPath: paths.authoritySocketPath,
|
|
68
94
|
runtimeSocketPath: paths.runtimeSocketPath,
|
|
95
|
+
claudeNative: resolveAuthSwitchClaudeNativeAuthority(),
|
|
69
96
|
});
|
|
70
97
|
await daemon.start();
|
|
71
98
|
await new Promise<void>((resolve, reject) => {
|