@serviceme/devtools-core 0.2.0 → 0.2.1
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/auth.d.mts +2 -0
- package/dist/auth.d.ts +2 -0
- package/dist/auth.js +89 -45
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +89 -45
- package/dist/auth.mjs.map +1 -1
- package/dist/index.d.mts +13 -9
- package/dist/index.d.ts +13 -9
- package/dist/index.js +184 -128
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -127
- package/dist/index.mjs.map +1 -1
- package/dist/skill-linker.js.map +1 -1
- package/dist/skill-linker.mjs.map +1 -1
- package/dist/skill-store.js +1 -8
- package/dist/skill-store.js.map +1 -1
- package/dist/skill-store.mjs +1 -8
- package/dist/skill-store.mjs.map +1 -1
- package/package.json +3 -2
package/dist/auth.d.mts
CHANGED
|
@@ -501,6 +501,8 @@ interface GitHubAuthProviderConfig {
|
|
|
501
501
|
fetchImpl?: typeof fetch;
|
|
502
502
|
/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */
|
|
503
503
|
maxWaitMs?: number;
|
|
504
|
+
/** Base delay (ms) before retrying the device-code request after a transient network error. */
|
|
505
|
+
deviceCodeRetryBaseDelayMs?: number;
|
|
504
506
|
}
|
|
505
507
|
declare class GitHubAuthProvider implements IAuthProvider {
|
|
506
508
|
readonly providerId: AuthProvider;
|
package/dist/auth.d.ts
CHANGED
|
@@ -501,6 +501,8 @@ interface GitHubAuthProviderConfig {
|
|
|
501
501
|
fetchImpl?: typeof fetch;
|
|
502
502
|
/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */
|
|
503
503
|
maxWaitMs?: number;
|
|
504
|
+
/** Base delay (ms) before retrying the device-code request after a transient network error. */
|
|
505
|
+
deviceCodeRetryBaseDelayMs?: number;
|
|
504
506
|
}
|
|
505
507
|
declare class GitHubAuthProvider implements IAuthProvider {
|
|
506
508
|
readonly providerId: AuthProvider;
|
package/dist/auth.js
CHANGED
|
@@ -569,6 +569,11 @@ var DEFAULT_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
|
569
569
|
var DEFAULT_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
570
570
|
var DEFAULT_USER_URL = "https://api.github.com/user";
|
|
571
571
|
var DEFAULT_SCOPE = "read:user user:email";
|
|
572
|
+
var DEVICE_CODE_MAX_ATTEMPTS = 3;
|
|
573
|
+
var DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;
|
|
574
|
+
function isTransientNetworkError(error) {
|
|
575
|
+
return error instanceof TypeError;
|
|
576
|
+
}
|
|
572
577
|
var GitHubAuthProvider = class {
|
|
573
578
|
constructor(config) {
|
|
574
579
|
this.providerId = "github";
|
|
@@ -584,7 +589,8 @@ var GitHubAuthProvider = class {
|
|
|
584
589
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
585
590
|
maxPollIntervalMs: config.maxPollIntervalMs ?? 15e3,
|
|
586
591
|
maxWaitMs: config.maxWaitMs,
|
|
587
|
-
fetchImpl: config.fetchImpl ?? fetch
|
|
592
|
+
fetchImpl: config.fetchImpl ?? fetch,
|
|
593
|
+
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
588
594
|
};
|
|
589
595
|
}
|
|
590
596
|
async requestDeviceFlow(opts) {
|
|
@@ -592,29 +598,45 @@ var GitHubAuthProvider = class {
|
|
|
592
598
|
client_id: this.cfg.clientId,
|
|
593
599
|
scope: opts?.scope ?? this.cfg.scope
|
|
594
600
|
});
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
601
|
+
let lastNetworkError;
|
|
602
|
+
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
603
|
+
let resp;
|
|
604
|
+
try {
|
|
605
|
+
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
606
|
+
method: "POST",
|
|
607
|
+
headers: {
|
|
608
|
+
Accept: "application/json",
|
|
609
|
+
"Content-Type": "application/json",
|
|
610
|
+
"User-Agent": "serviceme-core"
|
|
611
|
+
},
|
|
612
|
+
body
|
|
613
|
+
});
|
|
614
|
+
} catch (error) {
|
|
615
|
+
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
lastNetworkError = error;
|
|
619
|
+
await sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
if (!resp.ok) {
|
|
623
|
+
throw new Error(
|
|
624
|
+
`GitHub device-code request failed: HTTP ${resp.status}`
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const data = await resp.json();
|
|
628
|
+
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
629
|
+
throw new Error("GitHub device-code response missing required fields");
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
provider: this.providerId,
|
|
633
|
+
userCode: data.user_code,
|
|
634
|
+
verificationUrl: data.verification_uri,
|
|
635
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
636
|
+
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
637
|
+
};
|
|
610
638
|
}
|
|
611
|
-
|
|
612
|
-
provider: this.providerId,
|
|
613
|
-
userCode: data.user_code,
|
|
614
|
-
verificationUrl: data.verification_uri,
|
|
615
|
-
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
616
|
-
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
617
|
-
};
|
|
639
|
+
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
618
640
|
}
|
|
619
641
|
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
620
642
|
const start = Date.now();
|
|
@@ -632,21 +654,36 @@ var GitHubAuthProvider = class {
|
|
|
632
654
|
if (shouldContinue && !shouldContinue()) {
|
|
633
655
|
throw new Error("GitHub device flow cancelled by caller");
|
|
634
656
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
657
|
+
let resp;
|
|
658
|
+
try {
|
|
659
|
+
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
660
|
+
method: "POST",
|
|
661
|
+
headers: {
|
|
662
|
+
Accept: "application/json",
|
|
663
|
+
"Content-Type": "application/json",
|
|
664
|
+
"User-Agent": "serviceme-core"
|
|
665
|
+
},
|
|
666
|
+
body: JSON.stringify({
|
|
667
|
+
client_id: this.cfg.clientId,
|
|
668
|
+
device_code: deviceCode,
|
|
669
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
670
|
+
})
|
|
671
|
+
});
|
|
672
|
+
} catch (error) {
|
|
673
|
+
if (!isTransientNetworkError(error)) {
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
pollIntervalMs = Math.min(
|
|
677
|
+
Math.round(pollIntervalMs * 1.5),
|
|
678
|
+
this.cfg.maxPollIntervalMs
|
|
679
|
+
);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
648
682
|
if (!resp.ok) {
|
|
649
|
-
pollIntervalMs = Math.min(
|
|
683
|
+
pollIntervalMs = Math.min(
|
|
684
|
+
Math.round(pollIntervalMs * 1.5),
|
|
685
|
+
this.cfg.maxPollIntervalMs
|
|
686
|
+
);
|
|
650
687
|
continue;
|
|
651
688
|
}
|
|
652
689
|
const data = await resp.json();
|
|
@@ -672,7 +709,9 @@ var GitHubAuthProvider = class {
|
|
|
672
709
|
if (data.error === "slow_down") {
|
|
673
710
|
consecutiveSlowDown++;
|
|
674
711
|
pollIntervalMs = Math.min(
|
|
675
|
-
Math.round(
|
|
712
|
+
Math.round(
|
|
713
|
+
pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2e3)
|
|
714
|
+
),
|
|
676
715
|
this.cfg.maxPollIntervalMs
|
|
677
716
|
);
|
|
678
717
|
continue;
|
|
@@ -708,7 +747,9 @@ var GitHubAuthProvider = class {
|
|
|
708
747
|
}
|
|
709
748
|
const data = await resp.json();
|
|
710
749
|
if (!data.access_token) {
|
|
711
|
-
throw new Error(
|
|
750
|
+
throw new Error(
|
|
751
|
+
`GitHub refresh failed: ${data.error ?? "no_access_token"}`
|
|
752
|
+
);
|
|
712
753
|
}
|
|
713
754
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
714
755
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
@@ -778,13 +819,16 @@ var GitHubAuthProvider = class {
|
|
|
778
819
|
*/
|
|
779
820
|
async resolveEmail(token, user) {
|
|
780
821
|
if (user.email) return user.email;
|
|
781
|
-
const emailsResp = await this.cfg.fetchImpl(
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
822
|
+
const emailsResp = await this.cfg.fetchImpl(
|
|
823
|
+
"https://api.github.com/user/emails",
|
|
824
|
+
{
|
|
825
|
+
headers: {
|
|
826
|
+
Accept: "application/vnd.github+json",
|
|
827
|
+
Authorization: `Bearer ${token}`,
|
|
828
|
+
"User-Agent": "serviceme-core"
|
|
829
|
+
}
|
|
786
830
|
}
|
|
787
|
-
|
|
831
|
+
);
|
|
788
832
|
if (!emailsResp.ok) {
|
|
789
833
|
return null;
|
|
790
834
|
}
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth/index.ts","../src/auth/AccessControl.ts","../src/auth/AuthStateManager.ts","../src/auth/ProviderRegistry.ts","../src/auth/AuthCore.ts","../src/auth/KeychainAuthTokenStore.ts","../src/auth/utils/githubUserEmail.ts","../src/auth/providers/GitHubAuthProvider.ts","../src/auth/providers/MicrosoftAuthProvider.ts"],"sourcesContent":["/**\n * AuthCore barrel — Phase 5.2 public surface.\n *\n * Consumers (CLI / Extension / Bridge) should import from\n * `@serviceme/devtools-core/auth` (the subpath export added in\n * `package.json` `exports` map) rather than reaching into\n * individual files.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `auth/index.ts`\n */\n\nexport {\n\ttype AccessCheckResult,\n\tAccessControl,\n\ttype AccessControlOptions,\n\ttype KeyValueStore,\n\ttype OrgMembershipFetcher,\n} from \"./AccessControl\";\nexport {\n\tAuthCore,\n\ttype AuthCoreOptions,\n\ttype CancellationCheck,\n\ttype DeviceFlowUiCallback,\n} from \"./AuthCore\";\nexport { AuthStateManager, type AuthStateManagerOptions } from \"./AuthStateManager\";\nexport {\n\tInMemoryKeychainAuthTokenStore,\n\ttype KeychainAccountKey,\n\ttype KeychainAuthTokenStore,\n\ttype KeychainTokenEnvelope,\n\ttype KeychainTokenMetadata,\n\tKeychainUnavailableError,\n} from \"./KeychainAuthTokenStore\";\nexport { ProviderRegistry } from \"./ProviderRegistry\";\nexport {\n\tGitHubAuthProvider,\n\ttype GitHubAuthProviderConfig,\n} from \"./providers/GitHubAuthProvider\";\nexport type {\n\tIAuthProvider,\n\tIAuthProviderSession,\n\tIAuthProviderUserInfo,\n} from \"./providers/IAuthProvider\";\nexport {\n\tMicrosoftAuthProvider,\n\tMicrosoftProviderNotHostedError,\n} from \"./providers/MicrosoftAuthProvider\";\nexport {\n\tbuildGitHubLocalEmail,\n\tisGitHubLocalEmail,\n\tresolvePrimaryEmail,\n} from \"./utils/githubUserEmail\";\n","/**\n * AccessControl — Pure logic for org-membership gating.\n *\n * Ported from `apps/extension/src/services/auth/AccessControlService.ts`\n * but stripped of all VSCode dependencies:\n * - No `vscode.window.showWarningMessage` — callers render the notice.\n * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.\n * - No `vscode.Event` — uses a plain `onDidChange` callback.\n *\n * The class is generic over the membership-fetcher function so it can\n * run identically with the CLI's `@serviceme/shared` `getGitHubOrgMembership`\n * or with the Extension's bundled copy. ADL-003 forbids Core from\n * importing `@serviceme/shared` directly, so the org membership\n * result type is defined locally and the runtime adapter wraps the\n * shared helper at the boundary (Phase 5.3+).\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`\n * - ADL-003 — `@serviceme/shared` boundary (Core MUST NOT import shared)\n */\n\nimport type { AuthAccountMeta, AuthProvider } from \"@serviceme/devtools-protocol\";\n\n/** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/shared`). */\nexport interface CoreOrgMembershipResult {\n\tstatus: \"active\" | \"pending\" | \"not_member\" | \"unknown\";\n\thttpStatus?: number;\n\trole?: string;\n\tdirectMembership?: boolean;\n}\n\n/**\n * Upstream org-membership fetcher. CLI / Extension adapters wrap\n * `getGitHubOrgMembership` (or the local extension copy) to match\n * this signature.\n */\nexport type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;\n\n/** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */\nexport interface KeyValueStore {\n\tget<T>(key: string): T | undefined;\n\tupdate<T>(key: string, value: T): Promise<void>;\n}\n\nexport interface AccessCheckResult {\n\tallowed: boolean;\n\treason?: \"not_authenticated\" | \"not_member\";\n\tusername?: string;\n}\n\nexport interface AccessControlOptions {\n\torg: string;\n\t/** Domains that auto-qualify Microsoft users without a GitHub link. */\n\tmicrosoftEmailDomains?: readonly string[];\n\t/** TTL for in-memory membership cache (ms). */\n\tcacheTtlMs?: number;\n\t/** TTL for persisted membership cache (ms). */\n\tpersistentCacheTtlMs?: number;\n\t/** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */\n\ttokenFetcher: (provider: AuthProvider) => Promise<string | null>;\n\t/** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/shared`). */\n\torgFetcher: OrgMembershipFetcher;\n\t/** Clock seam for tests. */\n\tnow?: () => number;\n}\n\nconst DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst KEY_GRANTED_USERS = \"msDevTools.accessControl.grantedUsers\";\nconst KEY_MEMBERSHIP_CACHE = \"msDevTools.accessControl.membershipCache\";\nconst KEY_SERVER_IN_ORG = \"msDevTools.accessControl.serverInOrg\";\n\ninterface CacheEntry {\n\tisMember: boolean;\n\tat: number;\n}\n\ninterface PersistentCache {\n\t[username: string]: CacheEntry;\n}\n\nexport class AccessControl {\n\tprivate readonly memCache = new Map<string, CacheEntry>();\n\tprivate readonly serverInOrgMem = new Map<string, boolean>();\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly opts: Required<\n\t\tOmit<AccessControlOptions, \"now\" | \"tokenFetcher\" | \"microsoftEmailDomains\" | \"orgFetcher\">\n\t> & {\n\t\ttokenFetcher: AccessControlOptions[\"tokenFetcher\"];\n\t\torgFetcher: OrgMembershipFetcher;\n\t\tmicrosoftEmailDomains: readonly string[];\n\t\tnow: () => number;\n\t};\n\n\tconstructor(\n\t\tprivate readonly kv: KeyValueStore,\n\t\toptions: AccessControlOptions\n\t) {\n\t\tthis.opts = {\n\t\t\torg: options.org,\n\t\t\ttokenFetcher: options.tokenFetcher,\n\t\t\torgFetcher: options.orgFetcher,\n\t\t\tmicrosoftEmailDomains: options.microsoftEmailDomains ?? [],\n\t\t\tcacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n\t\t\tpersistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t};\n\t\t// Hydrate `serverInOrg` from persistent storage so the gate can run instantly on restart.\n\t\tconst persistedServerInOrg = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG);\n\t\tif (persistedServerInOrg) {\n\t\t\tfor (const [key, value] of Object.entries(persistedServerInOrg)) {\n\t\t\t\tthis.serverInOrgMem.set(key, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/** Full check: requires authentication + org membership / admin grant. */\n\tasync checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult> {\n\t\tif (!user) {\n\t\t\treturn { allowed: false, reason: \"not_authenticated\" };\n\t\t}\n\n\t\t// Microsoft users: server-confirmed inOrg takes priority over GitHub link.\n\t\tif (user.provider === \"microsoft\" && user.login) {\n\t\t\tconst inOrg = this.serverInOrgMem.get(user.login.toLowerCase());\n\t\t\tif (inOrg === true) return { allowed: true };\n\t\t\tif (inOrg === false) {\n\t\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t\t}\n\t\t}\n\n\t\t// Find a GitHub account if available (linked for Microsoft users).\n\t\tconst githubAccount = user.provider === \"github\" ? user : null;\n\t\tif (!githubAccount && user.provider === \"microsoft\" && user.login) {\n\t\t\tconst [, domain = \"\"] = user.login.split(\"@\");\n\t\t\tconst isDomainMember =\n\t\t\t\tdomain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());\n\t\t\tif (isDomainMember) return { allowed: true };\n\t\t\t// No GitHub link + unknown domain — deny until the next server-confirmed claim.\n\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t}\n\t\tif (!githubAccount) {\n\t\t\t// No identity at all — treat as authenticated for now (caller may have other gating).\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst accessUsername = githubAccount.login ?? user.login ?? \"\";\n\t\tif (accessUsername.length === 0) {\n\t\t\treturn { allowed: false, reason: \"not_member\", username: \"\" };\n\t\t}\n\n\t\t// Admin-granted users bypass the org check.\n\t\tconst granted = this.getGrantedUsers();\n\t\tif (granted.includes(accessUsername.toLowerCase())) {\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst isMember = await this.checkOrgMembership(accessUsername);\n\t\treturn isMember\n\t\t\t? { allowed: true }\n\t\t\t: { allowed: false, reason: \"not_member\", username: accessUsername };\n\t}\n\n\t/** Admin: grant access to a GitHub username (bypasses org check). */\n\tasync grantAccess(username: string): Promise<void> {\n\t\tconst list = this.getGrantedUsers();\n\t\tconst key = username.toLowerCase();\n\t\tif (!list.includes(key)) {\n\t\t\tlist.push(key);\n\t\t\tawait this.kv.update(KEY_GRANTED_USERS, list);\n\t\t}\n\t}\n\n\t/** Admin: revoke a previously granted access. */\n\tasync revokeAccess(username: string): Promise<void> {\n\t\tconst updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());\n\t\tawait this.kv.update(KEY_GRANTED_USERS, updated);\n\t}\n\n\t/** Snapshot of all admin-granted usernames (lowercased). */\n\tgetGrantedUsers(): string[] {\n\t\treturn this.kv.get<string[]>(KEY_GRANTED_USERS) ?? [];\n\t}\n\n\t/** Cached membership lookup with both in-memory + persistent layers. */\n\tasync checkOrgMembership(username: string): Promise<boolean> {\n\t\tconst now = this.opts.now();\n\n\t\t// 1. In-memory cache.\n\t\tconst inMem = this.memCache.get(username);\n\t\tif (inMem && now - inMem.at < this.opts.cacheTtlMs) {\n\t\t\treturn inMem.isMember;\n\t\t}\n\n\t\t// 2. Persistent cache.\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tconst persistentEntry = persistent[username];\n\t\tif (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {\n\t\t\tthis.memCache.set(username, persistentEntry);\n\t\t\treturn persistentEntry.isMember;\n\t\t}\n\n\t\t// 3. Hit upstream GitHub API via the injected fetcher.\n\t\tconst token = await this.opts.tokenFetcher(\"github\");\n\t\tif (!token) return false;\n\n\t\ttry {\n\t\t\tconst result = await this.opts.orgFetcher(token, this.opts.org);\n\t\t\tconst decision = this.resolveDecision(username, result);\n\t\t\tif (typeof decision === \"boolean\") {\n\t\t\t\tthis.updateCache(username, decision);\n\t\t\t\treturn decision;\n\t\t\t}\n\t\t\t// Indeterminate — fail-open.\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// Fail-open on transient network errors.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/** Server-confirmed inOrg status (set by device claim flow). */\n\tsetServerInOrg(username: string, inOrg: boolean): void {\n\t\tconst key = username.toLowerCase();\n\t\tthis.serverInOrgMem.set(key, inOrg);\n\t\tconst persisted = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG) ?? {};\n\t\tpersisted[key] = inOrg;\n\t\tvoid this.kv.update(KEY_SERVER_IN_ORG, persisted);\n\t\tthis.notifyListeners();\n\t}\n\n\t/** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */\n\tgetServerInOrg(username: string): boolean | undefined {\n\t\treturn this.serverInOrgMem.get(username.toLowerCase());\n\t}\n\n\tprivate resolveDecision(\n\t\t_username: string,\n\t\tmembership: CoreOrgMembershipResult\n\t): boolean | undefined {\n\t\tif (membership.status === \"active\" || membership.status === \"pending\") return true;\n\t\tif (membership.status === \"not_member\") return false;\n\t\treturn undefined;\n\t}\n\n\tprivate updateCache(username: string, isMember: boolean): void {\n\t\tconst at = this.opts.now();\n\t\tthis.memCache.set(username, { isMember, at });\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tpersistent[username] = { isMember, at };\n\t\tvoid this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// listener errors must not propagate; AccessControl is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n","/**\n * AuthStateManager — In-memory mirror of the persisted auth state.\n *\n * Holds the multi-account map (provider + accountId keyed) plus the\n * \"active\" provider/account pair. Emits change events via Node's\n * built-in `events.EventEmitter` so listeners stay decoupled.\n *\n * Important: this class does NOT touch `vscode.SecretStorage` or any\n * file — it only keeps the metadata (`AuthAccountMeta`) and the\n * `activeProvider` selection. Token bytes are looked up via the\n * `KeychainAuthTokenStore` on demand. This separation is what lets\n * `AuthCore` run identically in CLI + Extension.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`\n * - ADL-004 — token bytes never enter Core state\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport type { AuthAccountMeta, AuthProvider, AuthStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface AuthStateManagerEvents {\n\tchange: () => void;\n}\n\nexport interface AuthStateManagerOptions {\n\t/** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */\n\tmaxListeners?: number;\n}\n\nexport class AuthStateManager {\n\tprivate readonly emitter = new EventEmitter();\n\tprivate accounts: AuthAccountMeta[] = [];\n\tprivate activeProvider: AuthProvider | null = null;\n\tprivate activeAccountId: string | null = null;\n\tprivate lastError: string | null = null;\n\n\tconstructor(opts: AuthStateManagerOptions = {}) {\n\t\tif (opts.maxListeners !== undefined) {\n\t\t\tthis.emitter.setMaxListeners(opts.maxListeners);\n\t\t}\n\t}\n\n\t/** Subscribe to state-change events. Returns a disposer. */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => this.emitter.off(\"change\", listener);\n\t}\n\n\t/** Snapshot of all known accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn [...this.accounts];\n\t}\n\n\t/** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */\n\tfindAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;\n\t}\n\n\t/** First account for the requested provider — used for \"switch to GitHub\" UX. */\n\tfindFirstForProvider(provider: AuthProvider): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider) ?? null;\n\t}\n\n\t/** Insert or update an account entry. New accounts land at the head of the list. */\n\tupsertAccount(meta: AuthAccountMeta): void {\n\t\tconst idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);\n\t\tif (idx >= 0) {\n\t\t\tthis.accounts[idx] = meta;\n\t\t} else {\n\t\t\tthis.accounts.unshift(meta);\n\t\t}\n\t\tthis.fire();\n\t}\n\n\t/** Remove an account entry. Returns `true` when an entry was removed. */\n\tremoveAccount(provider: AuthProvider, accountId: string): boolean {\n\t\tconst before = this.accounts.length;\n\t\tthis.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));\n\t\tconst removed = this.accounts.length !== before;\n\t\t// If we removed the active provider's account, clear the active marker.\n\t\tif (removed && this.activeProvider === provider && this.activeAccountId === accountId) {\n\t\t\tconst stillHasSameProvider = this.accounts.some((a) => a.provider === provider);\n\t\t\tif (!stillHasSameProvider) {\n\t\t\t\tthis.activeProvider = null;\n\t\t\t\tthis.activeAccountId = null;\n\t\t\t} else {\n\t\t\t\tconst next = this.accounts.find((a) => a.provider === provider);\n\t\t\t\tif (next) {\n\t\t\t\t\tthis.activeAccountId = next.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (removed) this.fire();\n\t\treturn removed;\n\t}\n\n\t/** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */\n\tsetActive(provider: AuthProvider, accountId?: string): boolean {\n\t\tconst match =\n\t\t\taccountId !== undefined\n\t\t\t\t? this.accounts.find((a) => a.provider === provider && a.id === accountId)\n\t\t\t\t: this.accounts.find((a) => a.provider === provider);\n\t\tif (!match) return false;\n\t\tthis.activeProvider = provider;\n\t\tthis.activeAccountId = match.id;\n\t\tthis.fire();\n\t\treturn true;\n\t}\n\n\t/** Currently active provider — `null` when no session is active. */\n\tgetActiveProvider(): AuthProvider | null {\n\t\treturn this.activeProvider;\n\t}\n\n\t/** Currently active account metadata — `null` when none. */\n\tgetActiveAccount(): AuthAccountMeta | null {\n\t\tif (!this.activeProvider || !this.activeAccountId) return null;\n\t\treturn (\n\t\t\tthis.accounts.find(\n\t\t\t\t(a) => a.provider === this.activeProvider && a.id === this.activeAccountId\n\t\t\t) ?? null\n\t\t);\n\t}\n\n\t/** True when at least one provider has an account entry. */\n\thasAnySession(): boolean {\n\t\treturn this.accounts.length > 0 && this.activeProvider !== null;\n\t}\n\n\t/** Snapshot of the state for `auth.status` and bridge serialization. */\n\tgetStatus(): AuthStatus {\n\t\tconst grouped: Partial<Record<AuthProvider, AuthAccountMeta>> = {};\n\t\tfor (const account of this.accounts) {\n\t\t\tgrouped[account.provider] = account;\n\t\t}\n\t\treturn {\n\t\t\tactiveProvider: this.activeProvider,\n\t\t\taccounts: grouped,\n\t\t\thasAnySession: this.hasAnySession(),\n\t\t\tlastError: this.lastError ?? undefined,\n\t\t};\n\t}\n\n\t/** Record a non-fatal error from the last login/refresh attempt. */\n\trecordError(message: string): void {\n\t\tthis.lastError = message;\n\t\tthis.fire();\n\t}\n\n\t/** Clear the recorded error (e.g. after a successful login). */\n\tclearError(): void {\n\t\tif (this.lastError === null) return;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\t/** Drop every account — used by `auth.logout` with no provider. */\n\tclearAll(): void {\n\t\tthis.accounts = [];\n\t\tthis.activeProvider = null;\n\t\tthis.activeAccountId = null;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\tprivate fire(): void {\n\t\tthis.emitter.emit(\"change\");\n\t}\n}\n","/**\n * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.\n *\n * `AuthCore` looks up providers by `AuthProvider` enum (string union\n * per the protocol). The registry is mutable so that callers can\n * replace a provider (e.g. swap a real keyring-backed implementation\n * in tests), but providers must be registered before `AuthCore.login()`\n * is called.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`\n */\n\nimport type { AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider } from \"./providers/IAuthProvider\";\n\nexport class ProviderRegistry {\n\tprivate readonly providers = new Map<AuthProvider, IAuthProvider>();\n\n\t/** Register or replace a provider implementation. */\n\tregister(provider: IAuthProvider): void {\n\t\tthis.providers.set(provider.providerId, provider);\n\t}\n\n\t/** Look up a registered provider by id; throws when missing. */\n\tget(providerId: AuthProvider): IAuthProvider {\n\t\tconst p = this.providers.get(providerId);\n\t\tif (!p) {\n\t\t\tthrow new Error(`Auth provider not registered: ${providerId}`);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/** Non-throwing lookup; returns `undefined` when the provider is unknown. */\n\ttryGet(providerId: AuthProvider): IAuthProvider | undefined {\n\t\treturn this.providers.get(providerId);\n\t}\n\n\t/** Return all registered provider ids — used by `auth.status` and bridge capability hints. */\n\tlist(): AuthProvider[] {\n\t\treturn [...this.providers.keys()];\n\t}\n\n\t/** True when a provider has been registered for this id. */\n\thas(providerId: AuthProvider): boolean {\n\t\treturn this.providers.has(providerId);\n\t}\n}\n","/**\n * AuthCore — Main entry for the auth domain.\n *\n * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.\n * Provides a small, side-effectful surface that CLI / Extension / Bridge\n * handlers can call:\n *\n * - `login(provider)` → drives Device Flow, persists token via store.\n * - `logout(provider?)` → clears the active session (and token bytes).\n * - `status()` → snapshot for `auth.status`.\n * - `whoami()` → minimal identity for `auth.whoami`.\n * - `switchProvider(p)` → flip the active provider (multi-account).\n *\n * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`\n * boundary — callers pass the token to the store immediately, then\n * drop the local reference. Per ADL-004 the token bytes only live in\n * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`\n * - ADL-002 — Device Flow OAuth\n * - ADL-004 — Auth token storage\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n\tAuthStatus,\n\tAuthSwitchResult,\n\tAuthWhoamiResult,\n} from \"@serviceme/devtools-protocol\";\n\nimport type { AccessCheckResult, AccessControl } from \"./AccessControl\";\nimport { AuthStateManager } from \"./AuthStateManager\";\nimport type { KeychainAuthTokenStore } from \"./KeychainAuthTokenStore\";\nimport { ProviderRegistry } from \"./ProviderRegistry\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./providers/IAuthProvider\";\n\nexport interface AuthCoreOptions {\n\tproviders: IAuthProvider[];\n\ttokenStore: KeychainAuthTokenStore;\n\taccessControl?: AccessControl;\n\tstateManager?: AuthStateManager;\n}\n\n/**\n * Callback invoked once the user finishes the device-flow handshake but\n * BEFORE the token is written to the keychain. Lets the caller display\n * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to\n * stdout; Phase 5.5 Extension pops a webview notification).\n */\nexport type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;\n\n/**\n * Optional cancellation handle — when `shouldContinue()` returns false,\n * the polling loop exits with a clear error so callers can render\n * \"Login cancelled\" UX.\n */\nexport type CancellationCheck = () => boolean;\n\nexport class AuthCore {\n\tprivate readonly registry: ProviderRegistry;\n\tprivate readonly state: AuthStateManager;\n\tprivate readonly tokenStore: KeychainAuthTokenStore;\n\tprivate readonly accessControl?: AccessControl;\n\n\tconstructor(opts: AuthCoreOptions) {\n\t\tthis.registry = new ProviderRegistry();\n\t\tfor (const provider of opts.providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\t\tthis.state = opts.stateManager ?? new AuthStateManager();\n\t\tthis.tokenStore = opts.tokenStore;\n\t\tthis.accessControl = opts.accessControl;\n\t}\n\n\t/** Snapshot of every account, the active provider, and the last error. */\n\tstatus(): AuthStatus {\n\t\treturn this.state.getStatus();\n\t}\n\n\t/** List accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn this.state.listAccounts();\n\t}\n\n\t/**\n\t * Drive the device-flow login for `provider`.\n\t *\n\t * Sequence:\n\t * 1. Ask the provider for the user code + verification URL.\n\t * 2. Surface the code to the user (via `ui`).\n\t * 3. Poll until the user authorizes (or `shouldContinue` aborts).\n\t * 4. Persist the token via `KeychainAuthTokenStore.set()`.\n\t * 5. Insert the resulting `AuthAccountMeta` into state and mark active.\n\t */\n\tasync login(\n\t\tprovider: AuthProvider,\n\t\tui: DeviceFlowUiCallback,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst initial = await providerImpl.requestDeviceFlow();\n\t\t\tawait ui(initial);\n\t\t\tconst session = await providerImpl.completeDeviceFlow(\n\t\t\t\tinitial.userCode ? ((initial as unknown as { deviceCode?: string }).deviceCode ?? \"\") : \"\",\n\t\t\t\tshouldContinue\n\t\t\t);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/**\n\t * Complete the device-flow handshake given a pre-fetched user code.\n\t * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,\n\t * print the URL, and then poll on a subsequent invocation.\n\t */\n\tasync completeLogin(\n\t\tprovider: AuthProvider,\n\t\tdeviceCode: string,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Resolve the provider + token for the active session; returns null when no session is active. */\n\tasync resolveActiveToken(): Promise<{\n\t\tprovider: AuthProvider;\n\t\taccount: AuthAccountMeta;\n\t\ttoken: string;\n\t} | null> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tif (!provider) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!account) return null;\n\t\tconst envelope = await this.tokenStore.get({ provider, accountId: account.id });\n\t\tif (!envelope) return null;\n\t\treturn { provider, account, token: envelope.token };\n\t}\n\n\t/**\n\t * Logout: removes the token bytes from the keychain + drops the\n\t * account entry from state. When `provider` is omitted, clears\n\t * every account.\n\t */\n\tasync logout(\n\t\tprovider?: AuthProvider\n\t): Promise<{ provider: AuthProvider | null; success: boolean }> {\n\t\tif (!provider) {\n\t\t\t// Clear every account's token bytes + metadata.\n\t\t\tfor (const account of this.state.listAccounts()) {\n\t\t\t\tawait this.tokenStore.delete({ provider: account.provider, accountId: account.id });\n\t\t\t\tthis.state.removeAccount(account.provider, account.id);\n\t\t\t}\n\t\t\tthis.state.clearAll();\n\t\t\treturn { provider: null, success: true };\n\t\t}\n\n\t\tconst accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);\n\t\tlet removed = false;\n\t\tfor (const account of accountsForProvider) {\n\t\t\tawait this.tokenStore.delete({ provider, accountId: account.id });\n\t\t\tconst didRemove = this.state.removeAccount(provider, account.id);\n\t\t\tremoved = removed || didRemove;\n\t\t}\n\t\tif (this.state.getActiveProvider() === provider) {\n\t\t\t// Reset active if we just removed the active provider's account.\n\t\t\tconst firstRemaining = this.state.listAccounts()[0];\n\t\t\tif (firstRemaining) {\n\t\t\t\tthis.state.setActive(firstRemaining.provider);\n\t\t\t}\n\t\t}\n\t\treturn { provider, success: removed };\n\t}\n\n\t/** Minimal identity for `auth.whoami`. */\n\tasync whoami(): Promise<AuthWhoamiResult> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!provider || !account) {\n\t\t\treturn { provider: null };\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tlogin: account.login,\n\t\t\tname: account.displayName,\n\t\t\tavatarUrl: account.avatarUrl,\n\t\t\temail: account.email,\n\t\t};\n\t}\n\n\t/** Switch the active provider. Returns the resulting active account. */\n\tswitchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult {\n\t\tconst ok = this.state.setActive(provider, accountId);\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn {\n\t\t\tactiveProvider: ok ? provider : this.state.getActiveProvider(),\n\t\t\taccount,\n\t\t};\n\t}\n\n\t/** Run the access-control check against the active account (optional, requires AccessControl). */\n\tasync checkAccess(): Promise<AccessCheckResult | null> {\n\t\tif (!this.accessControl) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn this.accessControl.checkAccess(account);\n\t}\n\n\t/** Expose the state manager for test inspection (not for mutation). */\n\tgetStateManager(): AuthStateManager {\n\t\treturn this.state;\n\t}\n\n\t/** Expose the provider registry for test inspection. */\n\tgetProviderRegistry(): ProviderRegistry {\n\t\treturn this.registry;\n\t}\n\n\t/** Expose the access control (or undefined when not configured). */\n\tgetAccessControl(): AccessControl | undefined {\n\t\treturn this.accessControl;\n\t}\n\n\tprivate async persistSession(\n\t\tproviderImpl: IAuthProvider,\n\t\tsession: IAuthProviderSession\n\t): Promise<AuthLoginResult> {\n\t\tconst expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1000 : null;\n\t\tconst meta = await providerImpl.fetchAccountMeta(session.token);\n\t\tconst accountMeta: AuthAccountMeta = {\n\t\t\tid: meta.id,\n\t\t\tprovider: meta.provider,\n\t\t\tdisplayName: meta.displayName,\n\t\t\tlogin: meta.login,\n\t\t\temail: meta.email,\n\t\t\tavatarUrl: meta.avatarUrl,\n\t\t\texpiresAt: meta.expiresAt ?? expiresAt,\n\t\t};\n\n\t\tawait this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {\n\t\t\texpiresAt,\n\t\t});\n\t\tthis.state.upsertAccount(accountMeta);\n\t\tthis.state.setActive(meta.provider, meta.id);\n\t\tthis.state.clearError();\n\t\treturn {\n\t\t\tprovider: meta.provider,\n\t\t\tmessage: `Logged in as ${meta.login ?? meta.id}`,\n\t\t};\n\t}\n}\n","/**\n * KeychainAuthTokenStore — abstract adapter for token byte persistence.\n *\n * Per ADL-004 the OAuth token bytes live in one of three places:\n * (a) VSCode `SecretStorage` (Extension process)\n * (b) `@napi-rs/keyring` Entry (CLI process)\n * (c) HTTP request `Authorization` header\n *\n * `AuthCore` MUST NOT call any of these directly. Instead it receives\n * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`\n * adapter, Extension provides a `SecretStorage` adapter. This keeps\n * Core free of native-binding concerns and lets the same business\n * logic power both runtimes.\n *\n * Implementations:\n * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)\n * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)\n *\n * Failure semantics — `get()` returns `null` when no token is stored\n * (cold-start), throws when the underlying keychain is unavailable\n * (rare on Linux without libsecret). Callers should surface the throw\n * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to\n * in-memory storage (per ADL-004 §不变量).\n *\n * Refs:\n * - ADL-004 — CLI 端 Auth Token 存储选型\n * - 4.功能规划.md §2.1 — \"Core MUST NOT directly depend on `@napi-rs/keyring`\"\n */\n\n/** Opaque account identifier (provider + upstream user id). */\nexport interface KeychainAccountKey {\n\tprovider: string;\n\taccountId: string;\n}\n\n/** Non-secret metadata returned alongside a `get()` so callers can audit. */\nexport interface KeychainTokenMetadata {\n\tprovider: string;\n\taccountId: string;\n\texpiresAt?: number | null;\n\tstoredAt?: number;\n}\n\n/**\n * Result envelope — callers receive the token bytes (for immediate use\n * in an HTTP `Authorization` header) plus optional non-secret metadata.\n *\n * IMPORTANT: the `token` field is plain `string` here, not the\n * `SecretToken` brand. The provider -> store -> HTTP-header pipeline\n * runs inside a single trust boundary; crossing that boundary requires\n * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`\n * capability gate (see ADL-004 §不变量).\n */\nexport interface KeychainTokenEnvelope {\n\ttoken: string;\n\tmetadata: KeychainTokenMetadata;\n}\n\n/**\n * Abstract token-storage adapter. All methods are async because the\n * keyring / SecretStorage backends are async-by-nature.\n *\n * Thread-safety: implementations MUST be safe for concurrent calls;\n * `AuthCore` will multiplex over multiple providers on the same\n * runtime.\n */\nexport interface KeychainAuthTokenStore {\n\t/** Persist token bytes for the given provider + account pair. */\n\tset(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null },\n\t): Promise<void>;\n\n\t/** Fetch the token bytes; returns `null` when none is stored. */\n\tget(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;\n\n\t/** Remove the token entry. Idempotent — removing a missing entry is not an error. */\n\tdelete(key: KeychainAccountKey): Promise<void>;\n\n\t/**\n\t * List all stored token keys (metadata only — never the token bytes).\n\t * Useful for multi-account enumeration and bridge `auth.status`.\n\t */\n\tlist(): Promise<KeychainAccountKey[]>;\n\n\t/**\n\t * Health probe — returns `false` when the keychain is unreachable\n\t * (e.g. Linux without libsecret). Callers SHOULD probe on first\n\t * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.\n\t */\n\tisAvailable(): Promise<boolean>;\n}\n\n/**\n * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`\n * when the underlying keychain is unavailable. Callers map this to\n * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).\n */\nexport class KeychainUnavailableError extends Error {\n\tconstructor(message: string, cause?: unknown) {\n\t\t// Use the native ES2022 `Error` two-arg form instead of redeclaring\n\t\t// `cause` as a class field — a field redeclaration shadows the\n\t\t// inherited member and requires an `override` modifier under\n\t\t// `noImplicitOverride` (downstream consumers like the Extension),\n\t\t// which in turn fails THIS package's own build if its `lib` doesn't\n\t\t// also include `cause` on `Error`. Relying on the built-in\n\t\t// constructor option avoids the mismatch entirely (this package's\n\t\t// `tsconfig.json` targets `lib: [\"ES2022\", \"DOM\"]`, so `cause` is\n\t\t// recognized here too).\n\t\tsuper(message, cause !== undefined ? { cause } : undefined);\n\t\tthis.name = \"KeychainUnavailableError\";\n\t}\n}\n\n/**\n * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use\n * in production: tokens live in process memory and disappear on exit.\n * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`\n * (Extension).\n */\nexport class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {\n\tprivate readonly entries = new Map<\n\t\tstring,\n\t\t{ token: string; expiresAt: number | null; storedAt: number }\n\t>();\n\n\tprivate compositeKey(key: KeychainAccountKey): string {\n\t\treturn `${key.provider}:${key.accountId}`;\n\t}\n\n\tasync set(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null },\n\t): Promise<void> {\n\t\tthis.entries.set(this.compositeKey(key), {\n\t\t\ttoken,\n\t\t\texpiresAt: opts?.expiresAt ?? null,\n\t\t\tstoredAt: Date.now(),\n\t\t});\n\t}\n\n\tasync get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null> {\n\t\tconst entry = this.entries.get(this.compositeKey(key));\n\t\tif (!entry) return null;\n\t\treturn {\n\t\t\ttoken: entry.token,\n\t\t\tmetadata: {\n\t\t\t\tprovider: key.provider,\n\t\t\t\taccountId: key.accountId,\n\t\t\t\texpiresAt: entry.expiresAt,\n\t\t\t\tstoredAt: entry.storedAt,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(key: KeychainAccountKey): Promise<void> {\n\t\tthis.entries.delete(this.compositeKey(key));\n\t}\n\n\tasync list(): Promise<KeychainAccountKey[]> {\n\t\tconst out: KeychainAccountKey[] = [];\n\t\tfor (const composite of this.entries.keys()) {\n\t\t\tconst sepIdx = composite.indexOf(\":\");\n\t\t\tif (sepIdx <= 0) continue;\n\t\t\tout.push({\n\t\t\t\tprovider: composite.slice(0, sepIdx),\n\t\t\t\taccountId: composite.slice(sepIdx + 1),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn true;\n\t}\n}\n","/**\n * AuthCore — Pure local utilities for GitHub local email handling.\n *\n * Copied verbatim from `@serviceme/shared/src/github-user-email.ts` (15 LOC)\n * because ADL-003 forbids Core from depending on `@serviceme/shared`. These\n * helpers are pure functions with zero side effects, so the duplication is\n * trivial to keep in sync.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — \"utils/githubUserEmail.ts 纯函数,直接搬\"\n * - ADL-003 — `@serviceme/shared` boundary decision\n */\n\n/**\n * Returns true when the supplied address is a synthetic GitHub \"local\" email\n * (suffixed with `@github.local`). Real GitHub OAuth clients often return\n * `null` for `email` (user kept it private) and the application substitutes\n * `<login>@github.local` to keep the field non-null.\n */\nexport function isGitHubLocalEmail(email: string | null | undefined): boolean {\n\treturn typeof email === \"string\" && email.trim().toLowerCase().endsWith(\"@github.local\");\n}\n\n/** Build a synthetic `<login>@github.local` address. */\nexport function buildGitHubLocalEmail(login: string): string {\n\treturn `${login}@github.local`;\n}\n\n/**\n * Resolve the user's primary email address — falling back to the synthetic\n * `<login>@github.local` form when the upstream email is missing or blank.\n */\nexport function resolvePrimaryEmail(login: string, email: string | null | undefined): string {\n\tconst normalizedEmail = email?.trim();\n\treturn normalizedEmail || buildGitHubLocalEmail(login);\n}\n","/**\n * GitHubAuthProvider — Device Flow OAuth implementation.\n *\n * Implements the GitHub OAuth Device Flow per the official spec\n * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).\n *\n * Per ADL-002 the device flow is the locked authentication strategy for\n * SERVICEME — no localhost callback server, no PAT. This class is a\n * pure HTTP client (uses native `fetch`, no vscode dependency) that\n * returns the token bytes for the caller's `KeychainAuthTokenStore` to\n * persist immediately.\n *\n * Token bytes NEVER enter logs / errors / debug output. The provider\n * exposes only the user-visible code + verification URL during the\n * device-flow handshake.\n *\n * Refs:\n * - ADL-002 — Device Flow OAuth (locked)\n * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\nimport { buildGitHubLocalEmail } from \"../utils/githubUserEmail\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\nconst DEFAULT_DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst DEFAULT_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst DEFAULT_USER_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPE = \"read:user user:email\";\n\n/** Tunable provider config — exposed for tests + forks. */\nexport interface GitHubAuthProviderConfig {\n\tclientId: string;\n\tdeviceCodeUrl?: string;\n\ttokenUrl?: string;\n\tuserUrl?: string;\n\tscope?: string;\n\t/** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */\n\tminPollIntervalMs?: number;\n\t/** Maximum poll interval after exponential back-off. */\n\tmaxPollIntervalMs?: number;\n\t/** Fetch override (for tests + DI). */\n\tfetchImpl?: typeof fetch;\n\t/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */\n\tmaxWaitMs?: number;\n}\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\ninterface TokenPollResponse {\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_in?: number;\n\ttoken_type?: string;\n\tscope?: string;\n\terror?: string;\n\terror_description?: string;\n\terror_uri?: string;\n}\n\ninterface GitHubUserResponse {\n\tid: number;\n\tlogin: string;\n\tname?: string | null;\n\temail?: string | null;\n\tavatar_url?: string | null;\n}\n\nexport class GitHubAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"github\";\n\tprivate readonly cfg: Required<Omit<GitHubAuthProviderConfig, \"maxWaitMs\" | \"fetchImpl\">> & {\n\t\tmaxWaitMs?: number;\n\t\tfetchImpl: typeof fetch;\n\t};\n\n\tconstructor(config: GitHubAuthProviderConfig) {\n\t\tif (!config.clientId || config.clientId.length === 0) {\n\t\t\tthrow new Error(\"GitHubAuthProvider requires a non-empty `clientId`\");\n\t\t}\n\t\tthis.cfg = {\n\t\t\tclientId: config.clientId,\n\t\t\tdeviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,\n\t\t\ttokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,\n\t\t\tuserUrl: config.userUrl ?? DEFAULT_USER_URL,\n\t\t\tscope: config.scope ?? DEFAULT_SCOPE,\n\t\t\tminPollIntervalMs: config.minPollIntervalMs ?? 1000,\n\t\t\tmaxPollIntervalMs: config.maxPollIntervalMs ?? 15000,\n\t\t\tmaxWaitMs: config.maxWaitMs,\n\t\t\tfetchImpl: config.fetchImpl ?? fetch,\n\t\t};\n\t}\n\n\tasync requestDeviceFlow(opts?: { scope?: string }): Promise<AuthLoginResult> {\n\t\tconst body = JSON.stringify({\n\t\t\tclient_id: this.cfg.clientId,\n\t\t\tscope: opts?.scope ?? this.cfg.scope,\n\t\t});\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody,\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub device-code request failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as DeviceCodeResponse;\n\t\tif (!data.device_code || !data.user_code || !data.verification_uri) {\n\t\t\tthrow new Error(\"GitHub device-code response missing required fields\");\n\t\t}\n\t\treturn {\n\t\t\tprovider: this.providerId,\n\t\t\tuserCode: data.user_code,\n\t\t\tverificationUrl: data.verification_uri,\n\t\t\texpiresAt: Date.now() + data.expires_in * 1000,\n\t\t\tmessage: `Open ${data.verification_uri} and enter ${data.user_code}`,\n\t\t};\n\t}\n\n\tasync completeDeviceFlow(\n\t\tdeviceCode: string,\n\t\tshouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tconst start = Date.now();\n\t\tlet pollIntervalMs = this.cfg.minPollIntervalMs;\n\t\tlet consecutiveSlowDown = 0;\n\n\t\twhile (true) {\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\t\t\tconst elapsed = Date.now() - start;\n\t\t\tif (this.cfg.maxWaitMs !== undefined && elapsed > this.cfg.maxWaitMs) {\n\t\t\t\tthrow new Error(\"GitHub device flow exceeded max wait time\");\n\t\t\t}\n\t\t\tawait sleep(pollIntervalMs);\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\t\tdevice_code: deviceCode,\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t}),\n\t\t\t});\n\t\t\tif (!resp.ok) {\n\t\t\t\t// Transient HTTP failure — back off and retry until the user acts.\n\t\t\t\tpollIntervalMs = Math.min(Math.round(pollIntervalMs * 1.5), this.cfg.maxPollIntervalMs);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\t\tif (data.access_token) {\n\t\t\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\t\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\t\t\treturn {\n\t\t\t\t\ttoken: data.access_token,\n\t\t\t\t\trefreshToken: data.refresh_token,\n\t\t\t\t\texpiresIn: data.expires_in,\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tid: String(rawUser.id),\n\t\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\t\t\temail: email ?? null,\n\t\t\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (data.error === \"authorization_pending\") {\n\t\t\t\t// User hasn't acted yet — keep polling without back-off inflation.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"slow_down\") {\n\t\t\t\tconsecutiveSlowDown++;\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2000)),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"access_denied\") {\n\t\t\t\tthrow new Error(\"User denied authorization\");\n\t\t\t}\n\t\t\tif (data.error === \"expired_token\") {\n\t\t\t\tthrow new Error(\"GitHub device code expired — restart the flow\");\n\t\t\t}\n\t\t\tthrow new Error(`GitHub device flow error: ${data.error ?? \"unknown\"}`);\n\t\t}\n\t}\n\n\tasync refreshAccessToken(refreshToken: string): Promise<IAuthProviderSession> {\n\t\tif (!refreshToken) {\n\t\t\tthrow new Error(\"refreshAccessToken requires a non-empty refresh token\");\n\t\t}\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t}),\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub refresh failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\tif (!data.access_token) {\n\t\t\tthrow new Error(`GitHub refresh failed: ${data.error ?? \"no_access_token\"}`);\n\t\t}\n\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\treturn {\n\t\t\ttoken: data.access_token,\n\t\t\trefreshToken: data.refresh_token ?? refreshToken,\n\t\t\texpiresIn: data.expires_in,\n\t\t\tuser: {\n\t\t\t\tid: String(rawUser.id),\n\t\t\t\tlogin: rawUser.login,\n\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\temail: email ?? null,\n\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync validateToken(token: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (resp.status === 401) return false;\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub /user returned HTTP ${resp.status}`);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Network / parse errors are surfaced; only 401 means invalid.\n\t\t\tif (err instanceof Error && err.message.includes(\"401\")) return false;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync fetchAccountMeta(token: string): Promise<AuthAccountMeta> {\n\t\tconst user = await this.fetchGitHubUser(token);\n\t\tconst email = await this.resolveEmail(token, user);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\tprovider: this.providerId,\n\t\t\tdisplayName: user.name ?? user.login,\n\t\t\tlogin: user.login,\n\t\t\temail: email ?? buildGitHubLocalEmail(user.login),\n\t\t\tavatarUrl: user.avatar_url ?? undefined,\n\t\t\texpiresAt: null,\n\t\t};\n\t}\n\n\tprivate async fetchGitHubUser(token: string): Promise<GitHubUserResponse> {\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub /user failed: HTTP ${resp.status}`);\n\t\t}\n\t\treturn (await resp.json()) as GitHubUserResponse;\n\t}\n\n\t/**\n\t * GitHub's `/user` endpoint returns `null` when the user kept their\n\t * email private. The `/user/emails` endpoint reveals verified emails;\n\t * we pick the primary one, falling back to the synthetic\n\t * `<login>@github.local` form so the account is never email-less.\n\t */\n\tprivate async resolveEmail(token: string, user: GitHubUserResponse): Promise<string | null> {\n\t\tif (user.email) return user.email;\n\t\tconst emailsResp = await this.cfg.fetchImpl(\"https://api.github.com/user/emails\", {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!emailsResp.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst emails = (await emailsResp.json()) as Array<{\n\t\t\temail: string;\n\t\t\tprimary: boolean;\n\t\t\tvisibility: string | null;\n\t\t\tverified: boolean;\n\t\t}>;\n\t\tconst primary = emails.find((e) => e.primary && e.verified);\n\t\treturn primary?.email ?? null;\n\t}\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.\n *\n * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)\n * delegates to VSCode's built-in `vscode.authentication.getSession()` API\n * — there's no direct Microsoft device-flow endpoint exposed for\n * first-party apps. To keep Core usable from the CLI without VSCode,\n * we expose a minimal stub that throws \"not implemented in Core\" so\n * callers can detect and route back to the Extension adapter.\n *\n * The reason this lives in Core at all (instead of being purely\n * Extension-side) is the cross-runtime registry: `AuthCore` needs to\n * know which providers it MIGHT support, even if only the Extension\n * process can actually fulfil the Microsoft login.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`\n * - ADL-002 — Device Flow OAuth (locked, GitHub-only)\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\n/**\n * Error thrown when callers invoke Microsoft auth from a non-Extension\n * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`\n * so the user is prompted to retry inside VSCode.\n */\nexport class MicrosoftProviderNotHostedError extends Error {\n\tconstructor(\n\t\tmessage = \"Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MicrosoftProviderNotHostedError\";\n\t}\n}\n\nexport class MicrosoftAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"microsoft\";\n\n\tasync requestDeviceFlow(): Promise<AuthLoginResult> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync completeDeviceFlow(\n\t\t_deviceCode: string,\n\t\t_shouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync validateToken(_token: string): Promise<boolean> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync fetchAccountMeta(_token: string): Promise<AuthAccountMeta> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/auth/index.ts","../src/auth/AccessControl.ts","../src/auth/AuthStateManager.ts","../src/auth/ProviderRegistry.ts","../src/auth/AuthCore.ts","../src/auth/KeychainAuthTokenStore.ts","../src/auth/utils/githubUserEmail.ts","../src/auth/providers/GitHubAuthProvider.ts","../src/auth/providers/MicrosoftAuthProvider.ts"],"sourcesContent":["/**\n * AuthCore barrel — Phase 5.2 public surface.\n *\n * Consumers (CLI / Extension / Bridge) should import from\n * `@serviceme/devtools-core/auth` (the subpath export added in\n * `package.json` `exports` map) rather than reaching into\n * individual files.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `auth/index.ts`\n */\n\nexport {\n\ttype AccessCheckResult,\n\tAccessControl,\n\ttype AccessControlOptions,\n\ttype KeyValueStore,\n\ttype OrgMembershipFetcher,\n} from \"./AccessControl\";\nexport {\n\tAuthCore,\n\ttype AuthCoreOptions,\n\ttype CancellationCheck,\n\ttype DeviceFlowUiCallback,\n} from \"./AuthCore\";\nexport { AuthStateManager, type AuthStateManagerOptions } from \"./AuthStateManager\";\nexport {\n\tInMemoryKeychainAuthTokenStore,\n\ttype KeychainAccountKey,\n\ttype KeychainAuthTokenStore,\n\ttype KeychainTokenEnvelope,\n\ttype KeychainTokenMetadata,\n\tKeychainUnavailableError,\n} from \"./KeychainAuthTokenStore\";\nexport { ProviderRegistry } from \"./ProviderRegistry\";\nexport {\n\tGitHubAuthProvider,\n\ttype GitHubAuthProviderConfig,\n} from \"./providers/GitHubAuthProvider\";\nexport type {\n\tIAuthProvider,\n\tIAuthProviderSession,\n\tIAuthProviderUserInfo,\n} from \"./providers/IAuthProvider\";\nexport {\n\tMicrosoftAuthProvider,\n\tMicrosoftProviderNotHostedError,\n} from \"./providers/MicrosoftAuthProvider\";\nexport {\n\tbuildGitHubLocalEmail,\n\tisGitHubLocalEmail,\n\tresolvePrimaryEmail,\n} from \"./utils/githubUserEmail\";\n","/**\n * AccessControl — Pure logic for org-membership gating.\n *\n * Ported from `apps/extension/src/services/auth/AccessControlService.ts`\n * but stripped of all VSCode dependencies:\n * - No `vscode.window.showWarningMessage` — callers render the notice.\n * - No `vscode.context.globalState` — callers inject a `KeyValueStore`.\n * - No `vscode.Event` — uses a plain `onDidChange` callback.\n *\n * The class is generic over the membership-fetcher function so it can\n * run identically with the CLI's `@serviceme/shared` `getGitHubOrgMembership`\n * or with the Extension's bundled copy. ADL-003 forbids Core from\n * importing `@serviceme/shared` directly, so the org membership\n * result type is defined locally and the runtime adapter wraps the\n * shared helper at the boundary (Phase 5.3+).\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AccessControl.ts 从 AccessControlService 改写`\n * - ADL-003 — `@serviceme/shared` boundary (Core MUST NOT import shared)\n */\n\nimport type { AuthAccountMeta, AuthProvider } from \"@serviceme/devtools-protocol\";\n\n/** Core-local mirror of `GitHubOrgMembershipCheckResult` (defined in `@serviceme/shared`). */\nexport interface CoreOrgMembershipResult {\n\tstatus: \"active\" | \"pending\" | \"not_member\" | \"unknown\";\n\thttpStatus?: number;\n\trole?: string;\n\tdirectMembership?: boolean;\n}\n\n/**\n * Upstream org-membership fetcher. CLI / Extension adapters wrap\n * `getGitHubOrgMembership` (or the local extension copy) to match\n * this signature.\n */\nexport type OrgMembershipFetcher = (token: string, org: string) => Promise<CoreOrgMembershipResult>;\n\n/** Pluggable key/value store. Extension injects `globalState`-backed impl; CLI injects a file-backed impl. */\nexport interface KeyValueStore {\n\tget<T>(key: string): T | undefined;\n\tupdate<T>(key: string, value: T): Promise<void>;\n}\n\nexport interface AccessCheckResult {\n\tallowed: boolean;\n\treason?: \"not_authenticated\" | \"not_member\";\n\tusername?: string;\n}\n\nexport interface AccessControlOptions {\n\torg: string;\n\t/** Domains that auto-qualify Microsoft users without a GitHub link. */\n\tmicrosoftEmailDomains?: readonly string[];\n\t/** TTL for in-memory membership cache (ms). */\n\tcacheTtlMs?: number;\n\t/** TTL for persisted membership cache (ms). */\n\tpersistentCacheTtlMs?: number;\n\t/** Tokens whose bearer can be supplied to `OrgMembershipFetcher`. */\n\ttokenFetcher: (provider: AuthProvider) => Promise<string | null>;\n\t/** Upstream org-membership fetcher (typically `getGitHubOrgMembership` from `@serviceme/shared`). */\n\torgFetcher: OrgMembershipFetcher;\n\t/** Clock seam for tests. */\n\tnow?: () => number;\n}\n\nconst DEFAULT_CACHE_TTL_MS = 10 * 60 * 1000;\nconst DEFAULT_PERSISTENT_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nconst KEY_GRANTED_USERS = \"msDevTools.accessControl.grantedUsers\";\nconst KEY_MEMBERSHIP_CACHE = \"msDevTools.accessControl.membershipCache\";\nconst KEY_SERVER_IN_ORG = \"msDevTools.accessControl.serverInOrg\";\n\ninterface CacheEntry {\n\tisMember: boolean;\n\tat: number;\n}\n\ninterface PersistentCache {\n\t[username: string]: CacheEntry;\n}\n\nexport class AccessControl {\n\tprivate readonly memCache = new Map<string, CacheEntry>();\n\tprivate readonly serverInOrgMem = new Map<string, boolean>();\n\tprivate readonly listeners = new Set<() => void>();\n\tprivate readonly opts: Required<\n\t\tOmit<AccessControlOptions, \"now\" | \"tokenFetcher\" | \"microsoftEmailDomains\" | \"orgFetcher\">\n\t> & {\n\t\ttokenFetcher: AccessControlOptions[\"tokenFetcher\"];\n\t\torgFetcher: OrgMembershipFetcher;\n\t\tmicrosoftEmailDomains: readonly string[];\n\t\tnow: () => number;\n\t};\n\n\tconstructor(\n\t\tprivate readonly kv: KeyValueStore,\n\t\toptions: AccessControlOptions\n\t) {\n\t\tthis.opts = {\n\t\t\torg: options.org,\n\t\t\ttokenFetcher: options.tokenFetcher,\n\t\t\torgFetcher: options.orgFetcher,\n\t\t\tmicrosoftEmailDomains: options.microsoftEmailDomains ?? [],\n\t\t\tcacheTtlMs: options.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS,\n\t\t\tpersistentCacheTtlMs: options.persistentCacheTtlMs ?? DEFAULT_PERSISTENT_CACHE_TTL_MS,\n\t\t\tnow: options.now ?? (() => Date.now()),\n\t\t};\n\t\t// Hydrate `serverInOrg` from persistent storage so the gate can run instantly on restart.\n\t\tconst persistedServerInOrg = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG);\n\t\tif (persistedServerInOrg) {\n\t\t\tfor (const [key, value] of Object.entries(persistedServerInOrg)) {\n\t\t\t\tthis.serverInOrgMem.set(key, value);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Subscribe to inOrg changes (used by extension to refresh access-gated UI). */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n\n\t/** Full check: requires authentication + org membership / admin grant. */\n\tasync checkAccess(user: AuthAccountMeta | null): Promise<AccessCheckResult> {\n\t\tif (!user) {\n\t\t\treturn { allowed: false, reason: \"not_authenticated\" };\n\t\t}\n\n\t\t// Microsoft users: server-confirmed inOrg takes priority over GitHub link.\n\t\tif (user.provider === \"microsoft\" && user.login) {\n\t\t\tconst inOrg = this.serverInOrgMem.get(user.login.toLowerCase());\n\t\t\tif (inOrg === true) return { allowed: true };\n\t\t\tif (inOrg === false) {\n\t\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t\t}\n\t\t}\n\n\t\t// Find a GitHub account if available (linked for Microsoft users).\n\t\tconst githubAccount = user.provider === \"github\" ? user : null;\n\t\tif (!githubAccount && user.provider === \"microsoft\" && user.login) {\n\t\t\tconst [, domain = \"\"] = user.login.split(\"@\");\n\t\t\tconst isDomainMember =\n\t\t\t\tdomain.length > 0 && this.opts.microsoftEmailDomains.includes(domain.toLowerCase());\n\t\t\tif (isDomainMember) return { allowed: true };\n\t\t\t// No GitHub link + unknown domain — deny until the next server-confirmed claim.\n\t\t\treturn { allowed: false, reason: \"not_member\", username: user.login };\n\t\t}\n\t\tif (!githubAccount) {\n\t\t\t// No identity at all — treat as authenticated for now (caller may have other gating).\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst accessUsername = githubAccount.login ?? user.login ?? \"\";\n\t\tif (accessUsername.length === 0) {\n\t\t\treturn { allowed: false, reason: \"not_member\", username: \"\" };\n\t\t}\n\n\t\t// Admin-granted users bypass the org check.\n\t\tconst granted = this.getGrantedUsers();\n\t\tif (granted.includes(accessUsername.toLowerCase())) {\n\t\t\treturn { allowed: true };\n\t\t}\n\n\t\tconst isMember = await this.checkOrgMembership(accessUsername);\n\t\treturn isMember\n\t\t\t? { allowed: true }\n\t\t\t: { allowed: false, reason: \"not_member\", username: accessUsername };\n\t}\n\n\t/** Admin: grant access to a GitHub username (bypasses org check). */\n\tasync grantAccess(username: string): Promise<void> {\n\t\tconst list = this.getGrantedUsers();\n\t\tconst key = username.toLowerCase();\n\t\tif (!list.includes(key)) {\n\t\t\tlist.push(key);\n\t\t\tawait this.kv.update(KEY_GRANTED_USERS, list);\n\t\t}\n\t}\n\n\t/** Admin: revoke a previously granted access. */\n\tasync revokeAccess(username: string): Promise<void> {\n\t\tconst updated = this.getGrantedUsers().filter((u) => u !== username.toLowerCase());\n\t\tawait this.kv.update(KEY_GRANTED_USERS, updated);\n\t}\n\n\t/** Snapshot of all admin-granted usernames (lowercased). */\n\tgetGrantedUsers(): string[] {\n\t\treturn this.kv.get<string[]>(KEY_GRANTED_USERS) ?? [];\n\t}\n\n\t/** Cached membership lookup with both in-memory + persistent layers. */\n\tasync checkOrgMembership(username: string): Promise<boolean> {\n\t\tconst now = this.opts.now();\n\n\t\t// 1. In-memory cache.\n\t\tconst inMem = this.memCache.get(username);\n\t\tif (inMem && now - inMem.at < this.opts.cacheTtlMs) {\n\t\t\treturn inMem.isMember;\n\t\t}\n\n\t\t// 2. Persistent cache.\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tconst persistentEntry = persistent[username];\n\t\tif (persistentEntry && now - persistentEntry.at < this.opts.persistentCacheTtlMs) {\n\t\t\tthis.memCache.set(username, persistentEntry);\n\t\t\treturn persistentEntry.isMember;\n\t\t}\n\n\t\t// 3. Hit upstream GitHub API via the injected fetcher.\n\t\tconst token = await this.opts.tokenFetcher(\"github\");\n\t\tif (!token) return false;\n\n\t\ttry {\n\t\t\tconst result = await this.opts.orgFetcher(token, this.opts.org);\n\t\t\tconst decision = this.resolveDecision(username, result);\n\t\t\tif (typeof decision === \"boolean\") {\n\t\t\t\tthis.updateCache(username, decision);\n\t\t\t\treturn decision;\n\t\t\t}\n\t\t\t// Indeterminate — fail-open.\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// Fail-open on transient network errors.\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/** Server-confirmed inOrg status (set by device claim flow). */\n\tsetServerInOrg(username: string, inOrg: boolean): void {\n\t\tconst key = username.toLowerCase();\n\t\tthis.serverInOrgMem.set(key, inOrg);\n\t\tconst persisted = this.kv.get<Record<string, boolean>>(KEY_SERVER_IN_ORG) ?? {};\n\t\tpersisted[key] = inOrg;\n\t\tvoid this.kv.update(KEY_SERVER_IN_ORG, persisted);\n\t\tthis.notifyListeners();\n\t}\n\n\t/** Inspect server-confirmed inOrg for a username; returns undefined when unknown. */\n\tgetServerInOrg(username: string): boolean | undefined {\n\t\treturn this.serverInOrgMem.get(username.toLowerCase());\n\t}\n\n\tprivate resolveDecision(\n\t\t_username: string,\n\t\tmembership: CoreOrgMembershipResult\n\t): boolean | undefined {\n\t\tif (membership.status === \"active\" || membership.status === \"pending\") return true;\n\t\tif (membership.status === \"not_member\") return false;\n\t\treturn undefined;\n\t}\n\n\tprivate updateCache(username: string, isMember: boolean): void {\n\t\tconst at = this.opts.now();\n\t\tthis.memCache.set(username, { isMember, at });\n\t\tconst persistent = this.kv.get<PersistentCache>(KEY_MEMBERSHIP_CACHE) ?? {};\n\t\tpersistent[username] = { isMember, at };\n\t\tvoid this.kv.update(KEY_MEMBERSHIP_CACHE, persistent);\n\t}\n\n\tprivate notifyListeners(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\ttry {\n\t\t\t\tlistener();\n\t\t\t} catch {\n\t\t\t\t// listener errors must not propagate; AccessControl is best-effort.\n\t\t\t}\n\t\t}\n\t}\n}\n","/**\n * AuthStateManager — In-memory mirror of the persisted auth state.\n *\n * Holds the multi-account map (provider + accountId keyed) plus the\n * \"active\" provider/account pair. Emits change events via Node's\n * built-in `events.EventEmitter` so listeners stay decoupled.\n *\n * Important: this class does NOT touch `vscode.SecretStorage` or any\n * file — it only keeps the metadata (`AuthAccountMeta`) and the\n * `activeProvider` selection. Token bytes are looked up via the\n * `KeychainAuthTokenStore` on demand. This separation is what lets\n * `AuthCore` run identically in CLI + Extension.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthStateManager.ts events.EventEmitter, NO VSCode dep`\n * - ADL-004 — token bytes never enter Core state\n */\n\nimport { EventEmitter } from \"node:events\";\n\nimport type { AuthAccountMeta, AuthProvider, AuthStatus } from \"@serviceme/devtools-protocol\";\n\nexport interface AuthStateManagerEvents {\n\tchange: () => void;\n}\n\nexport interface AuthStateManagerOptions {\n\t/** EventEmitter listener cap; defaults to 32 (Node default) but raised for hot test paths. */\n\tmaxListeners?: number;\n}\n\nexport class AuthStateManager {\n\tprivate readonly emitter = new EventEmitter();\n\tprivate accounts: AuthAccountMeta[] = [];\n\tprivate activeProvider: AuthProvider | null = null;\n\tprivate activeAccountId: string | null = null;\n\tprivate lastError: string | null = null;\n\n\tconstructor(opts: AuthStateManagerOptions = {}) {\n\t\tif (opts.maxListeners !== undefined) {\n\t\t\tthis.emitter.setMaxListeners(opts.maxListeners);\n\t\t}\n\t}\n\n\t/** Subscribe to state-change events. Returns a disposer. */\n\tonDidChange(listener: () => void): () => void {\n\t\tthis.emitter.on(\"change\", listener);\n\t\treturn () => this.emitter.off(\"change\", listener);\n\t}\n\n\t/** Snapshot of all known accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn [...this.accounts];\n\t}\n\n\t/** Find an account by `(provider, accountId)` tuple; returns `null` when absent. */\n\tfindAccount(provider: AuthProvider, accountId: string): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider && a.id === accountId) ?? null;\n\t}\n\n\t/** First account for the requested provider — used for \"switch to GitHub\" UX. */\n\tfindFirstForProvider(provider: AuthProvider): AuthAccountMeta | null {\n\t\treturn this.accounts.find((a) => a.provider === provider) ?? null;\n\t}\n\n\t/** Insert or update an account entry. New accounts land at the head of the list. */\n\tupsertAccount(meta: AuthAccountMeta): void {\n\t\tconst idx = this.accounts.findIndex((a) => a.provider === meta.provider && a.id === meta.id);\n\t\tif (idx >= 0) {\n\t\t\tthis.accounts[idx] = meta;\n\t\t} else {\n\t\t\tthis.accounts.unshift(meta);\n\t\t}\n\t\tthis.fire();\n\t}\n\n\t/** Remove an account entry. Returns `true` when an entry was removed. */\n\tremoveAccount(provider: AuthProvider, accountId: string): boolean {\n\t\tconst before = this.accounts.length;\n\t\tthis.accounts = this.accounts.filter((a) => !(a.provider === provider && a.id === accountId));\n\t\tconst removed = this.accounts.length !== before;\n\t\t// If we removed the active provider's account, clear the active marker.\n\t\tif (removed && this.activeProvider === provider && this.activeAccountId === accountId) {\n\t\t\tconst stillHasSameProvider = this.accounts.some((a) => a.provider === provider);\n\t\t\tif (!stillHasSameProvider) {\n\t\t\t\tthis.activeProvider = null;\n\t\t\t\tthis.activeAccountId = null;\n\t\t\t} else {\n\t\t\t\tconst next = this.accounts.find((a) => a.provider === provider);\n\t\t\t\tif (next) {\n\t\t\t\t\tthis.activeAccountId = next.id;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (removed) this.fire();\n\t\treturn removed;\n\t}\n\n\t/** Set the active provider. When `accountId` is omitted, picks the first account for that provider. */\n\tsetActive(provider: AuthProvider, accountId?: string): boolean {\n\t\tconst match =\n\t\t\taccountId !== undefined\n\t\t\t\t? this.accounts.find((a) => a.provider === provider && a.id === accountId)\n\t\t\t\t: this.accounts.find((a) => a.provider === provider);\n\t\tif (!match) return false;\n\t\tthis.activeProvider = provider;\n\t\tthis.activeAccountId = match.id;\n\t\tthis.fire();\n\t\treturn true;\n\t}\n\n\t/** Currently active provider — `null` when no session is active. */\n\tgetActiveProvider(): AuthProvider | null {\n\t\treturn this.activeProvider;\n\t}\n\n\t/** Currently active account metadata — `null` when none. */\n\tgetActiveAccount(): AuthAccountMeta | null {\n\t\tif (!this.activeProvider || !this.activeAccountId) return null;\n\t\treturn (\n\t\t\tthis.accounts.find(\n\t\t\t\t(a) => a.provider === this.activeProvider && a.id === this.activeAccountId\n\t\t\t) ?? null\n\t\t);\n\t}\n\n\t/** True when at least one provider has an account entry. */\n\thasAnySession(): boolean {\n\t\treturn this.accounts.length > 0 && this.activeProvider !== null;\n\t}\n\n\t/** Snapshot of the state for `auth.status` and bridge serialization. */\n\tgetStatus(): AuthStatus {\n\t\tconst grouped: Partial<Record<AuthProvider, AuthAccountMeta>> = {};\n\t\tfor (const account of this.accounts) {\n\t\t\tgrouped[account.provider] = account;\n\t\t}\n\t\treturn {\n\t\t\tactiveProvider: this.activeProvider,\n\t\t\taccounts: grouped,\n\t\t\thasAnySession: this.hasAnySession(),\n\t\t\tlastError: this.lastError ?? undefined,\n\t\t};\n\t}\n\n\t/** Record a non-fatal error from the last login/refresh attempt. */\n\trecordError(message: string): void {\n\t\tthis.lastError = message;\n\t\tthis.fire();\n\t}\n\n\t/** Clear the recorded error (e.g. after a successful login). */\n\tclearError(): void {\n\t\tif (this.lastError === null) return;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\t/** Drop every account — used by `auth.logout` with no provider. */\n\tclearAll(): void {\n\t\tthis.accounts = [];\n\t\tthis.activeProvider = null;\n\t\tthis.activeAccountId = null;\n\t\tthis.lastError = null;\n\t\tthis.fire();\n\t}\n\n\tprivate fire(): void {\n\t\tthis.emitter.emit(\"change\");\n\t}\n}\n","/**\n * ProviderRegistry — Maps `AuthProvider` ids to `IAuthProvider` instances.\n *\n * `AuthCore` looks up providers by `AuthProvider` enum (string union\n * per the protocol). The registry is mutable so that callers can\n * replace a provider (e.g. swap a real keyring-backed implementation\n * in tests), but providers must be registered before `AuthCore.login()`\n * is called.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `ProviderRegistry.ts`\n */\n\nimport type { AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider } from \"./providers/IAuthProvider\";\n\nexport class ProviderRegistry {\n\tprivate readonly providers = new Map<AuthProvider, IAuthProvider>();\n\n\t/** Register or replace a provider implementation. */\n\tregister(provider: IAuthProvider): void {\n\t\tthis.providers.set(provider.providerId, provider);\n\t}\n\n\t/** Look up a registered provider by id; throws when missing. */\n\tget(providerId: AuthProvider): IAuthProvider {\n\t\tconst p = this.providers.get(providerId);\n\t\tif (!p) {\n\t\t\tthrow new Error(`Auth provider not registered: ${providerId}`);\n\t\t}\n\t\treturn p;\n\t}\n\n\t/** Non-throwing lookup; returns `undefined` when the provider is unknown. */\n\ttryGet(providerId: AuthProvider): IAuthProvider | undefined {\n\t\treturn this.providers.get(providerId);\n\t}\n\n\t/** Return all registered provider ids — used by `auth.status` and bridge capability hints. */\n\tlist(): AuthProvider[] {\n\t\treturn [...this.providers.keys()];\n\t}\n\n\t/** True when a provider has been registered for this id. */\n\thas(providerId: AuthProvider): boolean {\n\t\treturn this.providers.has(providerId);\n\t}\n}\n","/**\n * AuthCore — Main entry for the auth domain.\n *\n * Owns the `ProviderRegistry` + `AuthStateManager` + `KeychainAuthTokenStore`.\n * Provides a small, side-effectful surface that CLI / Extension / Bridge\n * handlers can call:\n *\n * - `login(provider)` → drives Device Flow, persists token via store.\n * - `logout(provider?)` → clears the active session (and token bytes).\n * - `status()` → snapshot for `auth.status`.\n * - `whoami()` → minimal identity for `auth.whoami`.\n * - `switchProvider(p)` → flip the active provider (multi-account).\n *\n * Core NEVER holds token bytes past the `KeychainAuthTokenStore.set()`\n * boundary — callers pass the token to the store immediately, then\n * drop the local reference. Per ADL-004 the token bytes only live in\n * (a) SecretStorage / (b) keyring / (c) HTTP Authorization header.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `AuthCore.ts 主入口`\n * - ADL-002 — Device Flow OAuth\n * - ADL-004 — Auth token storage\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n\tAuthStatus,\n\tAuthSwitchResult,\n\tAuthWhoamiResult,\n} from \"@serviceme/devtools-protocol\";\n\nimport type { AccessCheckResult, AccessControl } from \"./AccessControl\";\nimport { AuthStateManager } from \"./AuthStateManager\";\nimport type { KeychainAuthTokenStore } from \"./KeychainAuthTokenStore\";\nimport { ProviderRegistry } from \"./ProviderRegistry\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./providers/IAuthProvider\";\n\nexport interface AuthCoreOptions {\n\tproviders: IAuthProvider[];\n\ttokenStore: KeychainAuthTokenStore;\n\taccessControl?: AccessControl;\n\tstateManager?: AuthStateManager;\n}\n\n/**\n * Callback invoked once the user finishes the device-flow handshake but\n * BEFORE the token is written to the keychain. Lets the caller display\n * the `userCode` + `verificationUrl` to the user (Phase 5.3 CLI prints to\n * stdout; Phase 5.5 Extension pops a webview notification).\n */\nexport type DeviceFlowUiCallback = (result: AuthLoginResult) => void | Promise<void>;\n\n/**\n * Optional cancellation handle — when `shouldContinue()` returns false,\n * the polling loop exits with a clear error so callers can render\n * \"Login cancelled\" UX.\n */\nexport type CancellationCheck = () => boolean;\n\nexport class AuthCore {\n\tprivate readonly registry: ProviderRegistry;\n\tprivate readonly state: AuthStateManager;\n\tprivate readonly tokenStore: KeychainAuthTokenStore;\n\tprivate readonly accessControl?: AccessControl;\n\n\tconstructor(opts: AuthCoreOptions) {\n\t\tthis.registry = new ProviderRegistry();\n\t\tfor (const provider of opts.providers) {\n\t\t\tthis.registry.register(provider);\n\t\t}\n\t\tthis.state = opts.stateManager ?? new AuthStateManager();\n\t\tthis.tokenStore = opts.tokenStore;\n\t\tthis.accessControl = opts.accessControl;\n\t}\n\n\t/** Snapshot of every account, the active provider, and the last error. */\n\tstatus(): AuthStatus {\n\t\treturn this.state.getStatus();\n\t}\n\n\t/** List accounts (immutable copy). */\n\tlistAccounts(): AuthAccountMeta[] {\n\t\treturn this.state.listAccounts();\n\t}\n\n\t/**\n\t * Drive the device-flow login for `provider`.\n\t *\n\t * Sequence:\n\t * 1. Ask the provider for the user code + verification URL.\n\t * 2. Surface the code to the user (via `ui`).\n\t * 3. Poll until the user authorizes (or `shouldContinue` aborts).\n\t * 4. Persist the token via `KeychainAuthTokenStore.set()`.\n\t * 5. Insert the resulting `AuthAccountMeta` into state and mark active.\n\t */\n\tasync login(\n\t\tprovider: AuthProvider,\n\t\tui: DeviceFlowUiCallback,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst initial = await providerImpl.requestDeviceFlow();\n\t\t\tawait ui(initial);\n\t\t\tconst session = await providerImpl.completeDeviceFlow(\n\t\t\t\tinitial.userCode ? ((initial as unknown as { deviceCode?: string }).deviceCode ?? \"\") : \"\",\n\t\t\t\tshouldContinue\n\t\t\t);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/**\n\t * Complete the device-flow handshake given a pre-fetched user code.\n\t * Useful when the caller (Phase 5.3 CLI) wants to fetch the code,\n\t * print the URL, and then poll on a subsequent invocation.\n\t */\n\tasync completeLogin(\n\t\tprovider: AuthProvider,\n\t\tdeviceCode: string,\n\t\tshouldContinue?: CancellationCheck\n\t): Promise<AuthLoginResult> {\n\t\tconst providerImpl = this.registry.get(provider);\n\t\ttry {\n\t\t\tconst session = await providerImpl.completeDeviceFlow(deviceCode, shouldContinue);\n\t\t\treturn await this.persistSession(providerImpl, session);\n\t\t} catch (err) {\n\t\t\tthis.state.recordError(err instanceof Error ? err.message : String(err));\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\t/** Resolve the provider + token for the active session; returns null when no session is active. */\n\tasync resolveActiveToken(): Promise<{\n\t\tprovider: AuthProvider;\n\t\taccount: AuthAccountMeta;\n\t\ttoken: string;\n\t} | null> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tif (!provider) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!account) return null;\n\t\tconst envelope = await this.tokenStore.get({ provider, accountId: account.id });\n\t\tif (!envelope) return null;\n\t\treturn { provider, account, token: envelope.token };\n\t}\n\n\t/**\n\t * Logout: removes the token bytes from the keychain + drops the\n\t * account entry from state. When `provider` is omitted, clears\n\t * every account.\n\t */\n\tasync logout(\n\t\tprovider?: AuthProvider\n\t): Promise<{ provider: AuthProvider | null; success: boolean }> {\n\t\tif (!provider) {\n\t\t\t// Clear every account's token bytes + metadata.\n\t\t\tfor (const account of this.state.listAccounts()) {\n\t\t\t\tawait this.tokenStore.delete({ provider: account.provider, accountId: account.id });\n\t\t\t\tthis.state.removeAccount(account.provider, account.id);\n\t\t\t}\n\t\t\tthis.state.clearAll();\n\t\t\treturn { provider: null, success: true };\n\t\t}\n\n\t\tconst accountsForProvider = this.state.listAccounts().filter((a) => a.provider === provider);\n\t\tlet removed = false;\n\t\tfor (const account of accountsForProvider) {\n\t\t\tawait this.tokenStore.delete({ provider, accountId: account.id });\n\t\t\tconst didRemove = this.state.removeAccount(provider, account.id);\n\t\t\tremoved = removed || didRemove;\n\t\t}\n\t\tif (this.state.getActiveProvider() === provider) {\n\t\t\t// Reset active if we just removed the active provider's account.\n\t\t\tconst firstRemaining = this.state.listAccounts()[0];\n\t\t\tif (firstRemaining) {\n\t\t\t\tthis.state.setActive(firstRemaining.provider);\n\t\t\t}\n\t\t}\n\t\treturn { provider, success: removed };\n\t}\n\n\t/** Minimal identity for `auth.whoami`. */\n\tasync whoami(): Promise<AuthWhoamiResult> {\n\t\tconst provider = this.state.getActiveProvider();\n\t\tconst account = this.state.getActiveAccount();\n\t\tif (!provider || !account) {\n\t\t\treturn { provider: null };\n\t\t}\n\t\treturn {\n\t\t\tprovider,\n\t\t\tlogin: account.login,\n\t\t\tname: account.displayName,\n\t\t\tavatarUrl: account.avatarUrl,\n\t\t\temail: account.email,\n\t\t};\n\t}\n\n\t/** Switch the active provider. Returns the resulting active account. */\n\tswitchProvider(provider: AuthProvider, accountId?: string): AuthSwitchResult {\n\t\tconst ok = this.state.setActive(provider, accountId);\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn {\n\t\t\tactiveProvider: ok ? provider : this.state.getActiveProvider(),\n\t\t\taccount,\n\t\t};\n\t}\n\n\t/** Run the access-control check against the active account (optional, requires AccessControl). */\n\tasync checkAccess(): Promise<AccessCheckResult | null> {\n\t\tif (!this.accessControl) return null;\n\t\tconst account = this.state.getActiveAccount();\n\t\treturn this.accessControl.checkAccess(account);\n\t}\n\n\t/** Expose the state manager for test inspection (not for mutation). */\n\tgetStateManager(): AuthStateManager {\n\t\treturn this.state;\n\t}\n\n\t/** Expose the provider registry for test inspection. */\n\tgetProviderRegistry(): ProviderRegistry {\n\t\treturn this.registry;\n\t}\n\n\t/** Expose the access control (or undefined when not configured). */\n\tgetAccessControl(): AccessControl | undefined {\n\t\treturn this.accessControl;\n\t}\n\n\tprivate async persistSession(\n\t\tproviderImpl: IAuthProvider,\n\t\tsession: IAuthProviderSession\n\t): Promise<AuthLoginResult> {\n\t\tconst expiresAt = session.expiresIn ? Date.now() + session.expiresIn * 1000 : null;\n\t\tconst meta = await providerImpl.fetchAccountMeta(session.token);\n\t\tconst accountMeta: AuthAccountMeta = {\n\t\t\tid: meta.id,\n\t\t\tprovider: meta.provider,\n\t\t\tdisplayName: meta.displayName,\n\t\t\tlogin: meta.login,\n\t\t\temail: meta.email,\n\t\t\tavatarUrl: meta.avatarUrl,\n\t\t\texpiresAt: meta.expiresAt ?? expiresAt,\n\t\t};\n\n\t\tawait this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {\n\t\t\texpiresAt,\n\t\t});\n\t\tthis.state.upsertAccount(accountMeta);\n\t\tthis.state.setActive(meta.provider, meta.id);\n\t\tthis.state.clearError();\n\t\treturn {\n\t\t\tprovider: meta.provider,\n\t\t\tmessage: `Logged in as ${meta.login ?? meta.id}`,\n\t\t};\n\t}\n}\n","/**\n * KeychainAuthTokenStore — abstract adapter for token byte persistence.\n *\n * Per ADL-004 the OAuth token bytes live in one of three places:\n * (a) VSCode `SecretStorage` (Extension process)\n * (b) `@napi-rs/keyring` Entry (CLI process)\n * (c) HTTP request `Authorization` header\n *\n * `AuthCore` MUST NOT call any of these directly. Instead it receives\n * a `KeychainAuthTokenStore` via DI; CLI provides a `@napi-rs/keyring`\n * adapter, Extension provides a `SecretStorage` adapter. This keeps\n * Core free of native-binding concerns and lets the same business\n * logic power both runtimes.\n *\n * Implementations:\n * - CLI: `apps/serviceme-cli/src/.../KeyringTokenStore.ts` (Phase 5.3)\n * - Extension: `apps/extension/src/.../SecretStorageTokenStore.ts` (Phase 5.5)\n *\n * Failure semantics — `get()` returns `null` when no token is stored\n * (cold-start), throws when the underlying keychain is unavailable\n * (rare on Linux without libsecret). Callers should surface the throw\n * as `AUTH_KEYRING_UNAVAILABLE` rather than silently falling back to\n * in-memory storage (per ADL-004 §不变量).\n *\n * Refs:\n * - ADL-004 — CLI 端 Auth Token 存储选型\n * - 4.功能规划.md §2.1 — \"Core MUST NOT directly depend on `@napi-rs/keyring`\"\n */\n\n/** Opaque account identifier (provider + upstream user id). */\nexport interface KeychainAccountKey {\n\tprovider: string;\n\taccountId: string;\n}\n\n/** Non-secret metadata returned alongside a `get()` so callers can audit. */\nexport interface KeychainTokenMetadata {\n\tprovider: string;\n\taccountId: string;\n\texpiresAt?: number | null;\n\tstoredAt?: number;\n}\n\n/**\n * Result envelope — callers receive the token bytes (for immediate use\n * in an HTTP `Authorization` header) plus optional non-secret metadata.\n *\n * IMPORTANT: the `token` field is plain `string` here, not the\n * `SecretToken` brand. The provider -> store -> HTTP-header pipeline\n * runs inside a single trust boundary; crossing that boundary requires\n * `KeychainTokenEnvelope<SecretToken>` and the `auth.tokenRead`\n * capability gate (see ADL-004 §不变量).\n */\nexport interface KeychainTokenEnvelope {\n\ttoken: string;\n\tmetadata: KeychainTokenMetadata;\n}\n\n/**\n * Abstract token-storage adapter. All methods are async because the\n * keyring / SecretStorage backends are async-by-nature.\n *\n * Thread-safety: implementations MUST be safe for concurrent calls;\n * `AuthCore` will multiplex over multiple providers on the same\n * runtime.\n */\nexport interface KeychainAuthTokenStore {\n\t/** Persist token bytes for the given provider + account pair. */\n\tset(key: KeychainAccountKey, token: string, opts?: { expiresAt?: number | null }): Promise<void>;\n\n\t/** Fetch the token bytes; returns `null` when none is stored. */\n\tget(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null>;\n\n\t/** Remove the token entry. Idempotent — removing a missing entry is not an error. */\n\tdelete(key: KeychainAccountKey): Promise<void>;\n\n\t/**\n\t * List all stored token keys (metadata only — never the token bytes).\n\t * Useful for multi-account enumeration and bridge `auth.status`.\n\t */\n\tlist(): Promise<KeychainAccountKey[]>;\n\n\t/**\n\t * Health probe — returns `false` when the keychain is unreachable\n\t * (e.g. Linux without libsecret). Callers SHOULD probe on first\n\t * use and surface `AUTH_KEYRING_UNAVAILABLE` rather than degrade.\n\t */\n\tisAvailable(): Promise<boolean>;\n}\n\n/**\n * Sentinel error thrown by `KeychainAuthTokenStore.get()` / `.set()`\n * when the underlying keychain is unavailable. Callers map this to\n * `AUTH_KEYRING_UNAVAILABLE` (Phase 5.3 error code).\n */\nexport class KeychainUnavailableError extends Error {\n\tconstructor(message: string, cause?: unknown) {\n\t\t// Use the native ES2022 `Error` two-arg form instead of redeclaring\n\t\t// `cause` as a class field — a field redeclaration shadows the\n\t\t// inherited member and requires an `override` modifier under\n\t\t// `noImplicitOverride` (downstream consumers like the Extension),\n\t\t// which in turn fails THIS package's own build if its `lib` doesn't\n\t\t// also include `cause` on `Error`. Relying on the built-in\n\t\t// constructor option avoids the mismatch entirely (this package's\n\t\t// `tsconfig.json` targets `lib: [\"ES2022\", \"DOM\"]`, so `cause` is\n\t\t// recognized here too).\n\t\tsuper(message, cause !== undefined ? { cause } : undefined);\n\t\tthis.name = \"KeychainUnavailableError\";\n\t}\n}\n\n/**\n * In-memory `KeychainAuthTokenStore` — test/dev fallback. NEVER use\n * in production: tokens live in process memory and disappear on exit.\n * Production wiring is `KeyringTokenStore` (CLI) / `SecretStorageTokenStore`\n * (Extension).\n */\nexport class InMemoryKeychainAuthTokenStore implements KeychainAuthTokenStore {\n\tprivate readonly entries = new Map<\n\t\tstring,\n\t\t{ token: string; expiresAt: number | null; storedAt: number }\n\t>();\n\n\tprivate compositeKey(key: KeychainAccountKey): string {\n\t\treturn `${key.provider}:${key.accountId}`;\n\t}\n\n\tasync set(\n\t\tkey: KeychainAccountKey,\n\t\ttoken: string,\n\t\topts?: { expiresAt?: number | null }\n\t): Promise<void> {\n\t\tthis.entries.set(this.compositeKey(key), {\n\t\t\ttoken,\n\t\t\texpiresAt: opts?.expiresAt ?? null,\n\t\t\tstoredAt: Date.now(),\n\t\t});\n\t}\n\n\tasync get(key: KeychainAccountKey): Promise<KeychainTokenEnvelope | null> {\n\t\tconst entry = this.entries.get(this.compositeKey(key));\n\t\tif (!entry) return null;\n\t\treturn {\n\t\t\ttoken: entry.token,\n\t\t\tmetadata: {\n\t\t\t\tprovider: key.provider,\n\t\t\t\taccountId: key.accountId,\n\t\t\t\texpiresAt: entry.expiresAt,\n\t\t\t\tstoredAt: entry.storedAt,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(key: KeychainAccountKey): Promise<void> {\n\t\tthis.entries.delete(this.compositeKey(key));\n\t}\n\n\tasync list(): Promise<KeychainAccountKey[]> {\n\t\tconst out: KeychainAccountKey[] = [];\n\t\tfor (const composite of this.entries.keys()) {\n\t\t\tconst sepIdx = composite.indexOf(\":\");\n\t\t\tif (sepIdx <= 0) continue;\n\t\t\tout.push({\n\t\t\t\tprovider: composite.slice(0, sepIdx),\n\t\t\t\taccountId: composite.slice(sepIdx + 1),\n\t\t\t});\n\t\t}\n\t\treturn out;\n\t}\n\n\tasync isAvailable(): Promise<boolean> {\n\t\treturn true;\n\t}\n}\n","/**\n * AuthCore — Pure local utilities for GitHub local email handling.\n *\n * Copied verbatim from `@serviceme/shared/src/github-user-email.ts` (15 LOC)\n * because ADL-003 forbids Core from depending on `@serviceme/shared`. These\n * helpers are pure functions with zero side effects, so the duplication is\n * trivial to keep in sync.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — \"utils/githubUserEmail.ts 纯函数,直接搬\"\n * - ADL-003 — `@serviceme/shared` boundary decision\n */\n\n/**\n * Returns true when the supplied address is a synthetic GitHub \"local\" email\n * (suffixed with `@github.local`). Real GitHub OAuth clients often return\n * `null` for `email` (user kept it private) and the application substitutes\n * `<login>@github.local` to keep the field non-null.\n */\nexport function isGitHubLocalEmail(email: string | null | undefined): boolean {\n\treturn typeof email === \"string\" && email.trim().toLowerCase().endsWith(\"@github.local\");\n}\n\n/** Build a synthetic `<login>@github.local` address. */\nexport function buildGitHubLocalEmail(login: string): string {\n\treturn `${login}@github.local`;\n}\n\n/**\n * Resolve the user's primary email address — falling back to the synthetic\n * `<login>@github.local` form when the upstream email is missing or blank.\n */\nexport function resolvePrimaryEmail(login: string, email: string | null | undefined): string {\n\tconst normalizedEmail = email?.trim();\n\treturn normalizedEmail || buildGitHubLocalEmail(login);\n}\n","/**\n * GitHubAuthProvider — Device Flow OAuth implementation.\n *\n * Implements the GitHub OAuth Device Flow per the official spec\n * (https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow).\n *\n * Per ADL-002 the device flow is the locked authentication strategy for\n * SERVICEME — no localhost callback server, no PAT. This class is a\n * pure HTTP client (uses native `fetch`, no vscode dependency) that\n * returns the token bytes for the caller's `KeychainAuthTokenStore` to\n * persist immediately.\n *\n * Token bytes NEVER enter logs / errors / debug output. The provider\n * exposes only the user-visible code + verification URL during the\n * device-flow handshake.\n *\n * Refs:\n * - ADL-002 — Device Flow OAuth (locked)\n * - 4.功能规划.md §2.1 — `providers/GitHubAuthProvider.ts`\n */\n\nimport type {\n\tAuthAccountMeta,\n\tAuthLoginResult,\n\tAuthProvider,\n} from \"@serviceme/devtools-protocol\";\nimport { buildGitHubLocalEmail } from \"../utils/githubUserEmail\";\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\nconst DEFAULT_DEVICE_CODE_URL = \"https://github.com/login/device/code\";\nconst DEFAULT_TOKEN_URL = \"https://github.com/login/oauth/access_token\";\nconst DEFAULT_USER_URL = \"https://api.github.com/user\";\nconst DEFAULT_SCOPE = \"read:user user:email\";\n\n/** Attempts for the one-shot device-code request before surfacing a network error. */\nconst DEVICE_CODE_MAX_ATTEMPTS = 3;\nconst DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS = 500;\n\n/**\n * `fetch` (both browser and Node's undici) rejects with a `TypeError`\n * (e.g. \"fetch failed\") when the underlying connection never completes —\n * DNS failure, TLS handshake reset, ECONNRESET, etc. These are transient\n * network hiccups, distinct from a well-formed non-2xx HTTP response, and\n * should be retried rather than immediately failing the whole login.\n */\nfunction isTransientNetworkError(error: unknown): boolean {\n\treturn error instanceof TypeError;\n}\n\n/** Tunable provider config — exposed for tests + forks. */\nexport interface GitHubAuthProviderConfig {\n\tclientId: string;\n\tdeviceCodeUrl?: string;\n\ttokenUrl?: string;\n\tuserUrl?: string;\n\tscope?: string;\n\t/** Polling interval (ms) floor. The device-flow response's `interval` wins when larger. */\n\tminPollIntervalMs?: number;\n\t/** Maximum poll interval after exponential back-off. */\n\tmaxPollIntervalMs?: number;\n\t/** Fetch override (for tests + DI). */\n\tfetchImpl?: typeof fetch;\n\t/** Maximum wall-clock time to wait for the user before giving up (ms). Defaults to `expires_in * 1000`. */\n\tmaxWaitMs?: number;\n\t/** Base delay (ms) before retrying the device-code request after a transient network error. */\n\tdeviceCodeRetryBaseDelayMs?: number;\n}\n\ninterface DeviceCodeResponse {\n\tdevice_code: string;\n\tuser_code: string;\n\tverification_uri: string;\n\texpires_in: number;\n\tinterval: number;\n}\n\ninterface TokenPollResponse {\n\taccess_token?: string;\n\trefresh_token?: string;\n\texpires_in?: number;\n\ttoken_type?: string;\n\tscope?: string;\n\terror?: string;\n\terror_description?: string;\n\terror_uri?: string;\n}\n\ninterface GitHubUserResponse {\n\tid: number;\n\tlogin: string;\n\tname?: string | null;\n\temail?: string | null;\n\tavatar_url?: string | null;\n}\n\nexport class GitHubAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"github\";\n\tprivate readonly cfg: Required<\n\t\tOmit<GitHubAuthProviderConfig, \"maxWaitMs\" | \"fetchImpl\">\n\t> & {\n\t\tmaxWaitMs?: number;\n\t\tfetchImpl: typeof fetch;\n\t};\n\n\tconstructor(config: GitHubAuthProviderConfig) {\n\t\tif (!config.clientId || config.clientId.length === 0) {\n\t\t\tthrow new Error(\"GitHubAuthProvider requires a non-empty `clientId`\");\n\t\t}\n\t\tthis.cfg = {\n\t\t\tclientId: config.clientId,\n\t\t\tdeviceCodeUrl: config.deviceCodeUrl ?? DEFAULT_DEVICE_CODE_URL,\n\t\t\ttokenUrl: config.tokenUrl ?? DEFAULT_TOKEN_URL,\n\t\t\tuserUrl: config.userUrl ?? DEFAULT_USER_URL,\n\t\t\tscope: config.scope ?? DEFAULT_SCOPE,\n\t\t\tminPollIntervalMs: config.minPollIntervalMs ?? 1000,\n\t\t\tmaxPollIntervalMs: config.maxPollIntervalMs ?? 15000,\n\t\t\tmaxWaitMs: config.maxWaitMs,\n\t\t\tfetchImpl: config.fetchImpl ?? fetch,\n\t\t\tdeviceCodeRetryBaseDelayMs:\n\t\t\t\tconfig.deviceCodeRetryBaseDelayMs ??\n\t\t\t\tDEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS,\n\t\t};\n\t}\n\n\tasync requestDeviceFlow(opts?: { scope?: string }): Promise<AuthLoginResult> {\n\t\tconst body = JSON.stringify({\n\t\t\tclient_id: this.cfg.clientId,\n\t\t\tscope: opts?.scope ?? this.cfg.scope,\n\t\t});\n\n\t\tlet lastNetworkError: unknown;\n\t\tfor (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (\n\t\t\t\t\t!isTransientNetworkError(error) ||\n\t\t\t\t\tattempt === DEVICE_CODE_MAX_ATTEMPTS\n\t\t\t\t) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastNetworkError = error;\n\t\t\t\tawait sleep(this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`GitHub device-code request failed: HTTP ${resp.status}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as DeviceCodeResponse;\n\t\t\tif (!data.device_code || !data.user_code || !data.verification_uri) {\n\t\t\t\tthrow new Error(\"GitHub device-code response missing required fields\");\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tprovider: this.providerId,\n\t\t\t\tuserCode: data.user_code,\n\t\t\t\tverificationUrl: data.verification_uri,\n\t\t\t\texpiresAt: Date.now() + data.expires_in * 1000,\n\t\t\t\tmessage: `Open ${data.verification_uri} and enter ${data.user_code}`,\n\t\t\t};\n\t\t}\n\t\t// Unreachable in practice — the loop always returns or throws — but keeps\n\t\t// the type checker happy and surfaces the last transient error if hit.\n\t\tthrow lastNetworkError ?? new Error(\"GitHub device-code request failed\");\n\t}\n\n\tasync completeDeviceFlow(\n\t\tdeviceCode: string,\n\t\tshouldContinue?: () => boolean,\n\t): Promise<IAuthProviderSession> {\n\t\tconst start = Date.now();\n\t\tlet pollIntervalMs = this.cfg.minPollIntervalMs;\n\t\tlet consecutiveSlowDown = 0;\n\n\t\twhile (true) {\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\t\t\tconst elapsed = Date.now() - start;\n\t\t\tif (this.cfg.maxWaitMs !== undefined && elapsed > this.cfg.maxWaitMs) {\n\t\t\t\tthrow new Error(\"GitHub device flow exceeded max wait time\");\n\t\t\t}\n\t\t\tawait sleep(pollIntervalMs);\n\t\t\tif (shouldContinue && !shouldContinue()) {\n\t\t\t\tthrow new Error(\"GitHub device flow cancelled by caller\");\n\t\t\t}\n\n\t\t\tlet resp: Response;\n\t\t\ttry {\n\t\t\t\tresp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\t\t\tdevice_code: deviceCode,\n\t\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (!isTransientNetworkError(error)) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\t// Transient network failure (DNS/TLS/reset) — back off and retry\n\t\t\t\t// polling until the user acts, same as a non-2xx HTTP response.\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!resp.ok) {\n\t\t\t\t// Transient HTTP failure — back off and retry until the user acts.\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(pollIntervalMs * 1.5),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\t\tif (data.access_token) {\n\t\t\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\t\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\t\t\treturn {\n\t\t\t\t\ttoken: data.access_token,\n\t\t\t\t\trefreshToken: data.refresh_token,\n\t\t\t\t\texpiresIn: data.expires_in,\n\t\t\t\t\tuser: {\n\t\t\t\t\t\tid: String(rawUser.id),\n\t\t\t\t\t\tlogin: rawUser.login,\n\t\t\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\t\t\temail: email ?? null,\n\t\t\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (data.error === \"authorization_pending\") {\n\t\t\t\t// User hasn't acted yet — keep polling without back-off inflation.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"slow_down\") {\n\t\t\t\tconsecutiveSlowDown++;\n\t\t\t\tpollIntervalMs = Math.min(\n\t\t\t\t\tMath.round(\n\t\t\t\t\t\tpollIntervalMs * 1.5 + Math.min(consecutiveSlowDown * 500, 2000),\n\t\t\t\t\t),\n\t\t\t\t\tthis.cfg.maxPollIntervalMs,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (data.error === \"access_denied\") {\n\t\t\t\tthrow new Error(\"User denied authorization\");\n\t\t\t}\n\t\t\tif (data.error === \"expired_token\") {\n\t\t\t\tthrow new Error(\"GitHub device code expired — restart the flow\");\n\t\t\t}\n\t\t\tthrow new Error(`GitHub device flow error: ${data.error ?? \"unknown\"}`);\n\t\t}\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t): Promise<IAuthProviderSession> {\n\t\tif (!refreshToken) {\n\t\t\tthrow new Error(\"refreshAccessToken requires a non-empty refresh token\");\n\t\t}\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tclient_id: this.cfg.clientId,\n\t\t\t\tgrant_type: \"refresh_token\",\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t}),\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub refresh failed: HTTP ${resp.status}`);\n\t\t}\n\t\tconst data = (await resp.json()) as TokenPollResponse;\n\t\tif (!data.access_token) {\n\t\t\tthrow new Error(\n\t\t\t\t`GitHub refresh failed: ${data.error ?? \"no_access_token\"}`,\n\t\t\t);\n\t\t}\n\t\tconst rawUser = await this.fetchGitHubUser(data.access_token);\n\t\tconst email = await this.resolveEmail(data.access_token, rawUser);\n\t\treturn {\n\t\t\ttoken: data.access_token,\n\t\t\trefreshToken: data.refresh_token ?? refreshToken,\n\t\t\texpiresIn: data.expires_in,\n\t\t\tuser: {\n\t\t\t\tid: String(rawUser.id),\n\t\t\t\tlogin: rawUser.login,\n\t\t\t\tname: rawUser.name ?? undefined,\n\t\t\t\temail: email ?? null,\n\t\t\t\tavatarUrl: rawUser.avatar_url ?? undefined,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync validateToken(token: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tif (resp.status === 401) return false;\n\t\t\tif (!resp.ok) {\n\t\t\t\tthrow new Error(`GitHub /user returned HTTP ${resp.status}`);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\t// Network / parse errors are surfaced; only 401 means invalid.\n\t\t\tif (err instanceof Error && err.message.includes(\"401\")) return false;\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\tasync fetchAccountMeta(token: string): Promise<AuthAccountMeta> {\n\t\tconst user = await this.fetchGitHubUser(token);\n\t\tconst email = await this.resolveEmail(token, user);\n\t\treturn {\n\t\t\tid: String(user.id),\n\t\t\tprovider: this.providerId,\n\t\t\tdisplayName: user.name ?? user.login,\n\t\t\tlogin: user.login,\n\t\t\temail: email ?? buildGitHubLocalEmail(user.login),\n\t\t\tavatarUrl: user.avatar_url ?? undefined,\n\t\t\texpiresAt: null,\n\t\t};\n\t}\n\n\tprivate async fetchGitHubUser(token: string): Promise<GitHubUserResponse> {\n\t\tconst resp = await this.cfg.fetchImpl(this.cfg.userUrl, {\n\t\t\theaders: {\n\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t},\n\t\t});\n\t\tif (!resp.ok) {\n\t\t\tthrow new Error(`GitHub /user failed: HTTP ${resp.status}`);\n\t\t}\n\t\treturn (await resp.json()) as GitHubUserResponse;\n\t}\n\n\t/**\n\t * GitHub's `/user` endpoint returns `null` when the user kept their\n\t * email private. The `/user/emails` endpoint reveals verified emails;\n\t * we pick the primary one, falling back to the synthetic\n\t * `<login>@github.local` form so the account is never email-less.\n\t */\n\tprivate async resolveEmail(\n\t\ttoken: string,\n\t\tuser: GitHubUserResponse,\n\t): Promise<string | null> {\n\t\tif (user.email) return user.email;\n\t\tconst emailsResp = await this.cfg.fetchImpl(\n\t\t\t\"https://api.github.com/user/emails\",\n\t\t\t{\n\t\t\t\theaders: {\n\t\t\t\t\tAccept: \"application/vnd.github+json\",\n\t\t\t\t\tAuthorization: `Bearer ${token}`,\n\t\t\t\t\t\"User-Agent\": \"serviceme-core\",\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\tif (!emailsResp.ok) {\n\t\t\treturn null;\n\t\t}\n\t\tconst emails = (await emailsResp.json()) as Array<{\n\t\t\temail: string;\n\t\t\tprimary: boolean;\n\t\t\tvisibility: string | null;\n\t\t\tverified: boolean;\n\t\t}>;\n\t\tconst primary = emails.find((e) => e.primary && e.verified);\n\t\treturn primary?.email ?? null;\n\t}\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * MicrosoftAuthProvider — Microsoft Account (MSA) OAuth Device Flow stub.\n *\n * The Extension's existing `MicrosoftAuthProvider` (`apps/extension/src/services/auth/providers/MicrosoftAuthProvider.ts`)\n * delegates to VSCode's built-in `vscode.authentication.getSession()` API\n * — there's no direct Microsoft device-flow endpoint exposed for\n * first-party apps. To keep Core usable from the CLI without VSCode,\n * we expose a minimal stub that throws \"not implemented in Core\" so\n * callers can detect and route back to the Extension adapter.\n *\n * The reason this lives in Core at all (instead of being purely\n * Extension-side) is the cross-runtime registry: `AuthCore` needs to\n * know which providers it MIGHT support, even if only the Extension\n * process can actually fulfil the Microsoft login.\n *\n * Refs:\n * - 4.功能规划.md §2.1 — `providers/MicrosoftAuthProvider.ts 从 extension 整体迁移`\n * - ADL-002 — Device Flow OAuth (locked, GitHub-only)\n */\n\nimport type { AuthAccountMeta, AuthLoginResult, AuthProvider } from \"@serviceme/devtools-protocol\";\n\nimport type { IAuthProvider, IAuthProviderSession } from \"./IAuthProvider\";\n\n/**\n * Error thrown when callers invoke Microsoft auth from a non-Extension\n * runtime (CLI / Bridge). The CLI maps this to `AUTH_PROVIDER_REQUIRES_HOST`\n * so the user is prompted to retry inside VSCode.\n */\nexport class MicrosoftProviderNotHostedError extends Error {\n\tconstructor(\n\t\tmessage = \"Microsoft auth requires the VSCode host; CLI does not yet implement MSA device flow\"\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"MicrosoftProviderNotHostedError\";\n\t}\n}\n\nexport class MicrosoftAuthProvider implements IAuthProvider {\n\treadonly providerId: AuthProvider = \"microsoft\";\n\n\tasync requestDeviceFlow(): Promise<AuthLoginResult> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync completeDeviceFlow(\n\t\t_deviceCode: string,\n\t\t_shouldContinue?: () => boolean\n\t): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync refreshAccessToken(_refreshToken: string): Promise<IAuthProviderSession> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync validateToken(_token: string): Promise<boolean> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n\n\tasync fetchAccountMeta(_token: string): Promise<AuthAccountMeta> {\n\t\tthrow new MicrosoftProviderNotHostedError();\n\t}\n}\n"],"mappings":"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","names":[]}
|