@gitkraken/core-gitlens 0.7.0 → 0.7.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/CHANGELOG.md +21 -2
- package/dist/git/remotes/azure-devops.js +3 -3
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git-cli/exec/git.js +1 -1
- package/dist/git-cli/providers/config.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js +2 -2
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/staging.js +1 -1
- package/dist/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/integrations/authentication/configuredIntegrationService.js +9 -2
- package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +34 -0
- package/dist/plus/integrations/integrationService.js +90 -43
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/lite.js +5 -4
- package/dist/plus/integrations/lite.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +68 -23
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +30 -14
- package/dist/plus/integrations/models/gitHostIntegration.js +59 -21
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +3 -3
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -0
- package/dist/plus/integrations/models/pullRequestReads.d.ts +26 -0
- package/dist/plus/integrations/providers/azure/azure.d.ts +66 -1
- package/dist/plus/integrations/providers/azure/azure.js +192 -12
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/search.d.ts +144 -0
- package/dist/plus/integrations/providers/azure/search.js +250 -0
- package/dist/plus/integrations/providers/azure/search.js.map +1 -0
- package/dist/plus/integrations/providers/azureDevOps.d.ts +54 -35
- package/dist/plus/integrations/providers/azureDevOps.js +154 -121
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +176 -0
- package/dist/plus/integrations/providers/azureDevOpsServer.js +560 -0
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.d.ts +12 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.js +128 -0
- package/dist/plus/integrations/providers/bitbucket-server/discovery.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +10 -1
- package/dist/plus/integrations/providers/bitbucket-server/models.js +3 -2
- package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.d.ts +58 -0
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +485 -0
- package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -0
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +52 -3
- package/dist/plus/integrations/providers/bitbucket-server.js +96 -20
- package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
- package/dist/plus/integrations/providers/bitbucket.js +1 -1
- package/dist/plus/integrations/providers/github.d.ts +5 -5
- package/dist/plus/integrations/providers/github.js +34 -33
- package/dist/plus/integrations/providers/github.js.map +1 -1
- package/dist/plus/integrations/providers/gitlab.d.ts +5 -3
- package/dist/plus/integrations/providers/gitlab.js +31 -27
- package/dist/plus/integrations/providers/gitlab.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/jira-server.d.ts +9 -0
- package/dist/plus/integrations/providers/jira-server.js +13 -2
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.js +1 -1
- package/dist/plus/integrations/providers/linear.js +1 -1
- package/dist/plus/integrations/providers/models.d.ts +9 -0
- package/dist/plus/integrations/providers/models.js +86 -0
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +10 -0
- package/dist/plus/integrations/providers/providersApi.js +16 -1
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils.d.ts +12 -1
- package/dist/plus/integrations/providers/utils.js +36 -6
- package/dist/plus/integrations/providers/utils.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.js +5 -5
- package/dist/plus/integrations/reads/broaden.js.map +1 -1
- package/dist/plus/integrations/reads/counts.d.ts +18 -4
- package/dist/plus/integrations/reads/counts.js +66 -15
- package/dist/plus/integrations/reads/counts.js.map +1 -1
- package/dist/plus/integrations/reads/cursors.js +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +1 -0
- package/dist/plus/integrations/reads/filters.d.ts +1 -1
- package/dist/plus/integrations/reads/filters.js +17 -12
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +1 -1
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +1 -1
- package/dist/plus/integrations/reads/issues.js +1 -1
- package/dist/plus/integrations/reads/paging.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.d.ts +12 -11
- package/dist/plus/integrations/reads/resolveRepository.js +109 -18
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/searchIssues.js +4 -2
- package/dist/plus/integrations/reads/searchIssues.js.map +1 -1
- package/dist/plus/integrations/reads/searchPullRequests.js +9 -3
- package/dist/plus/integrations/reads/searchPullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/trackerIssue.d.ts +9 -5
- package/dist/plus/integrations/reads/trackerIssue.js +15 -9
- package/dist/plus/integrations/reads/trackerIssue.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +6 -2
- package/dist/plus/integrations/reads/warnings.js +15 -19
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/utils/domain.utils.d.ts +19 -0
- package/dist/plus/integrations/utils/domain.utils.js +36 -1
- package/dist/plus/integrations/utils/domain.utils.js.map +1 -1
- package/dist/plus/integrations/utils/integration.utils.d.ts +1 -0
- package/dist/plus/integrations/utils/integration.utils.js +12 -1
- package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
- package/dist/utils/env/node/exec.js +2 -2
- package/docs/integrations.md +176 -73
- package/docs/kepler-read-api-parity.md +19 -6
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":[],"sources":["../../../../../plus/integrations/src/models/integration.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { PullRequest } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug, trace } from '@gitlens/utils/decorators/log.js';\nimport type { Disposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type {\n\tIntegrationAuthenticationProviderDescriptor,\n\tIntegrationAuthenticationSessionDescriptor,\n} from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { RejectedTokenTracker } from '../authentication/rejectedTokenTracker.js';\nimport type { IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { AuthenticationError, RequestClientError, toError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProvidersApi } from '../providers/providersApi.js';\nimport type { Sources } from '../telemetry.js';\nimport { areDomainsOnSameHost } from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { GitHostIntegration } from './gitHostIntegration.js';\nimport { getCachedIssue } from './issueCache.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from './issueReads.js';\nimport type { IssuesIntegration } from './issuesIntegration.js';\n\nexport type Integration = GitHostIntegration | IssuesIntegration;\nexport type IntegrationById<T extends IntegrationIds> = T extends IssuesHostIntegrationIds\n\t? IssuesIntegration\n\t: GitHostIntegration;\nexport type IntegrationType = 'git' | 'issues';\n\n// The issue-read contracts live in their own module (pure data, and their relationship to each other is the\n// point of reading them together); re-exported here so the providers that implement these reads keep one import.\nexport type { AccountWideIssuesResult, ProviderIssueSearchPage, SearchMyIssuesOptions } from './issueReads.js';\nexport type { ProviderPullRequestSearchPage } from './pullRequestReads.js';\n\n// Keep this in step with `isSelfManagedHostIntegrationId`: it is the type-level twin of that predicate, and a\n// disagreement compiles cleanly while the runtime writes a domain-keyed key the type says is unkeyed (so the\n// local-disconnect flag is written under one key and read under another).\nexport type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extends\n\t| GitCloudHostIntegrationId\n\t| IssuesCloudHostIntegrationId\n\t? `${T}`\n\t: `${T}:${string}`;\n\nexport type IntegrationConnectedKey<T extends IntegrationIds = IntegrationIds> = `connected:${IntegrationKey<T>}`;\n\nexport type IntegrationResult<T> =\n\t| { value: T; duration?: number; error?: Error }\n\t| { error: Error; duration?: number; value?: never }\n\t| undefined;\n\ntype SyncReqUsecase = Exclude<\n\t| 'getAccountForCommit'\n\t| 'getAccountForEmail'\n\t| 'getAccountForResource'\n\t| 'getCurrentAccount'\n\t| 'getDefaultBranch'\n\t| 'getIssue'\n\t| 'getIssueOrPullRequest'\n\t| 'getIssuesBatch'\n\t| 'getIssuesForProject'\n\t| 'getIssuesForRepos'\n\t| 'getMyPullRequestsForUser'\n\t| 'getOrganizationsForUser'\n\t| 'getProjectsForOrg'\n\t| 'getProjectsForResources'\n\t| 'getPullRequest'\n\t| 'getRepositoriesForOrg'\n\t| 'getRepositoriesForUser'\n\t| 'getPullRequestForBranch'\n\t| 'getPullRequestForCommit'\n\t| 'getPullRequestsForRepos'\n\t| 'getRepositoryMetadata'\n\t| 'getResourcesForUser'\n\t| 'getSshSigningKeysForEmails'\n\t| 'countIssues'\n\t| 'countPullRequests'\n\t| 'mergePullRequest'\n\t| 'searchIssuesPage'\n\t| 'searchPullRequestsPage'\n\t| 'searchMyIssues'\n\t| 'searchMyPullRequests'\n\t| 'searchPullRequests',\n\t// excluding to show explicitly that we don't want to add 'all' key occasionally\n\t'all'\n>;\n\nexport abstract class IntegrationBase<\n\tID extends IntegrationIds = IntegrationIds,\n\tT extends ResourceDescriptor = ResourceDescriptor,\n> implements Disposable {\n\tabstract readonly type: IntegrationType;\n\n\tprivate readonly _onDidChange = new Emitter<void>();\n\tget onDidChange(): Event<void> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(\n\t\tprotected readonly ctx: IntegrationServiceContext,\n\t\tprotected readonly authenticationService: IntegrationAuthenticationService,\n\t\tprotected readonly getProvidersApi: () => Promise<ProvidersApi>,\n\t\tprivate readonly didChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tabstract get authProvider(): IntegrationAuthenticationProviderDescriptor;\n\tabstract get id(): ID;\n\tprotected abstract get key(): IntegrationKey<ID>;\n\tabstract get name(): string;\n\tabstract get domain(): string;\n\n\tget authProviderDescriptor(): IntegrationAuthenticationSessionDescriptor {\n\t\treturn { domain: this.domain, scopes: this.authProvider.scopes };\n\t}\n\n\t/**\n\t * The `gl-provider-<key>` glicon key for this provider, which is NOT always its id.\n\t *\n\t * `providersMetadata.iconKey` is the field that says which glyph a provider draws with, and a\n\t * self-managed variant reuses its cloud family's — Jira Data Center draws with Jira's, because it is the\n\t * same product and the id only selects an icon. Returning the id instead named a glyph the registry has\n\t * no entry for, so a consumer rendering a `gl-provider-` glicon from `issue.provider.icon` requested a\n\t * glyph that does not exist.\n\t */\n\tget icon(): string {\n\t\treturn providersMetadata[this.id]?.iconKey ?? this.id;\n\t}\n\n\taccess(): Promise<boolean> {\n\t\treturn this.ctx.account.isTrialOrPaid();\n\t}\n\n\tautolinks():\n\t\t| (AutolinkReference | DynamicAutolinkReference)[]\n\t\t| Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\treturn [];\n\t}\n\n\tprivate get connectedKey(): IntegrationConnectedKey<ID> {\n\t\treturn `connected:${this.key}`;\n\t}\n\n\tget maybeConnected(): boolean | undefined {\n\t\treturn this._session === undefined ? undefined : this._session !== null;\n\t}\n\n\t/** Hash of the current session's access token. Changes on any token change (account switch or refresh). */\n\tprivate _sessionFingerprint: { session: ProviderAuthenticationSession; hash: string } | undefined;\n\tget sessionFingerprint(): string | undefined {\n\t\tif (this._session == null) return undefined;\n\n\t\tif (this._sessionFingerprint?.session !== this._session) {\n\t\t\tthis._sessionFingerprint = { session: this._session, hash: this.getSessionFingerprint(this._session) };\n\t\t}\n\t\treturn this._sessionFingerprint.hash;\n\t}\n\n\tprotected getSessionFingerprint(session: ProviderAuthenticationSession): string {\n\t\treturn fnv1aHash64(session.accessToken);\n\t}\n\n\tget connectionExpired(): boolean | undefined {\n\t\tif (this._session?.expiresAt == null) return undefined;\n\t\treturn new Date(this._session.expiresAt) < new Date();\n\t}\n\n\tprotected _session: ProviderAuthenticationSession | null | undefined;\n\tgetSession(\n\t\tsource: Sources,\n\t): ProviderAuthenticationSession | Promise<ProviderAuthenticationSession | undefined> | undefined {\n\t\tif (this._session === undefined) {\n\t\t\treturn this.ensureSession({ createIfNeeded: false, source: source });\n\t\t}\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\tprivate isSessionForIntegrationHost(session: ProviderAuthenticationSession): boolean {\n\t\tif (!isSelfManagedHostIntegrationId(this.id)) return true;\n\n\t\treturn areDomainsOnSameHost(this.domain, session.domain);\n\t}\n\n\t/**\n\t * Resolves the session to read as, for a per-connection (multi-account) read. When `connectionId` is\n\t * omitted this is the integration's primary session, resolved exactly like the existing read flow\n\t * (ensure-connected + refresh-if-expired). When set, it resolves THAT connection's session directly\n\t * from the auth provider — refreshing it if expired — WITHOUT disturbing the cached primary\n\t * `_session`. Returns undefined when the requested session can't be resolved (e.g. the connection is\n\t * gone or the provider isn't connected), so callers degrade to \"no results\".\n\t */\n\tprotected async resolveReadSession(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t\tsource?: Sources,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tif (\n\t\t\tthis.ctx.config.isIntegrationsEnabled?.() === false ||\n\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// A truthy connectionId targets a specific account; an empty string is not a real target, so it falls\n\t\t// through to the primary path below.\n\t\tif (connectionId) {\n\t\t\t// A read of this connection previously failed with an AuthenticationError, so the token in\n\t\t\t// storage is known-refused. Ask for a refresh through the GK cloud before reading again, which\n\t\t\t// is this branch's equivalent of the primary path's `refreshSessionIfExpired`. Claimed here so\n\t\t\t// the forced refresh runs at most once per rejection.\n\t\t\tconst refreshRejectedToken = this._rejectedTokens.claimRefresh(connectionId);\n\t\t\t// Degrade to \"no results\" on failure, matching the primary path (whose ensureSession/\n\t\t\t// refreshSessionIfExpired swallow errors) so read methods keep their never-throws contract.\n\t\t\ttry {\n\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\tconst session = await authProvider.getSession(\n\t\t\t\t\t{ ...this.authProviderDescriptor, connectionId: connectionId, cloud: true },\n\t\t\t\t\t{ source: source, refreshRejectedToken: refreshRejectedToken },\n\t\t\t\t);\n\t\t\t\treturn session != null && this.isSessionForIntegrationHost(session) ? session : undefined;\n\t\t\t} catch (ex) {\n\t\t\t\tscope?.error(ex);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\t@debug()\n\tasync connect(source: Sources): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Boolean(await this.ensureSession({ createIfNeeded: true, source: source }));\n\t\t} catch (_ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected providerOnConnect?(): void | Promise<void>;\n\n\t@gate()\n\t@debug()\n\tasync disconnect(options?: { silent?: boolean; currentSessionOnly?: boolean }): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tconst connected = this._session != null;\n\n\t\tlet signOut = !options?.currentSessionOnly;\n\n\t\tif (connected && !options?.currentSessionOnly && !options?.silent) {\n\t\t\tconst decision = await this.ctx.hooks?.onConfirmDisconnect?.({\n\t\t\t\tintegrationName: this.name,\n\t\t\t\tofferSignOut: this.authenticationService.supports(this.authProvider.id),\n\t\t\t});\n\t\t\tif (decision == null) return;\n\n\t\t\tsignOut = decision.signOut;\n\t\t}\n\n\t\tif (signOut) {\n\t\t\t// Disconnecting a provider signs out of ALL its connected accounts (multi-account), not just the\n\t\t\t// primary — otherwise secondary connections' secrets/config would be orphaned. Removing a single\n\t\t\t// account is done via IntegrationService.deleteConnection instead. Pass this instance's descriptor\n\t\t\t// so self-managed disconnects stay scoped to this host: those group every host under one provider\n\t\t\t// id, so an unscoped clear would sign the user out of unrelated hosts. deleteAllSessions derives an\n\t\t\t// undefined domain for cloud providers, so they still clear every account as intended.\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t// Awaited, not fire-and-forget: a caller that awaits `disconnect()` has to be able to rely on the\n\t\t\t// secrets and descriptors actually being gone when it resumes. Left floating, the only thing that\n\t\t\t// ever made this land in time was incidental scheduling slack — `syncCloudIntegrations` used to\n\t\t\t// await each provider in turn, so a later iteration's suspension let the previous provider's delete\n\t\t\t// finish. Syncing providers concurrently removes that slack and the clear was observably still\n\t\t\t// pending when the sync returned.\n\t\t\tawait authProvider.deleteAllSessions(this.authProviderDescriptor);\n\t\t}\n\n\t\tthis.resetRequestExceptionCount('all');\n\t\tthis._session = null;\n\n\t\tif (connected) {\n\t\t\t// Don't store the disconnected flag if silently disconnecting or disconnecting this only for\n\t\t\t// this current VS Code session (will be re-connected on next restart)\n\t\t\tif (!options?.currentSessionOnly && !options?.silent) {\n\t\t\t\tvoid this.ctx.storage.storeWorkspace(this.connectedKey, false).catch();\n\t\t\t}\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tif (!options?.currentSessionOnly) {\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\t}\n\t\t}\n\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprotected providerOnDisconnect?(): void | Promise<void>;\n\n\t@debug()\n\tasync reauthenticate(): Promise<void> {\n\t\t// `forceNewSession` below deletes the stored secrets to reconnect. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tif (this._session === undefined) return;\n\n\t\tthis._session = undefined;\n\t\tvoid (await this.ensureSession({ createIfNeeded: true, forceNewSession: true }));\n\t}\n\n\trefresh(): void {\n\t\tvoid this.ensureSession({ createIfNeeded: false });\n\t}\n\n\tprivate _syncRequestsPerFailedUsecase = new Set<SyncReqUsecase>();\n\thasSessionSyncRequests(): boolean {\n\t\treturn this._syncRequestsPerFailedUsecase.size > 0;\n\t}\n\trequestSessionSyncForUsecase(syncReqUsecase: SyncReqUsecase): void {\n\t\tthis._syncRequestsPerFailedUsecase.add(syncReqUsecase);\n\t}\n\n\t/**\n\t * The per-connection counterpart of the primary session's expire-and-resync recovery: a token the\n\t * provider refused is refreshed once through the GK cloud, which exchanges the refresh token server-side\n\t * (the client never holds one). Armed by {@link handleProviderException} and consumed by\n\t * {@link resolveReadSession}; a rejection it declines belongs to {@link trackRequestException} instead.\n\t */\n\tprivate readonly _rejectedTokens = new RejectedTokenTracker();\n\n\t/**\n\t * Called by every path that replaces or removes the stored tokens — a disconnect, a forced re-sync, a\n\t * reauthentication, or the connection set changing. A rejection names a specific credential, so once that\n\t * credential is gone the rejection describes nothing and must not force a refresh (or, for a deleted\n\t * connection, outlive it).\n\t *\n\t * Deliberately NOT conditioned on `_session`. A per-connection read resolves through the auth provider and\n\t * never populates the cached primary session, so the connections this recovery exists for are exactly the\n\t * ones with no `_session` to inspect — the same blind spot the recovery itself was added to fix. Callers\n\t * that guard on `_session` therefore invoke this ahead of that guard.\n\t */\n\tprotected onStoredTokensReplaced(): void {\n\t\tthis._rejectedTokens.clear();\n\t}\n\n\tprivate static readonly requestExceptionLimit = 5;\n\tprivate requestExceptionCount = 0;\n\n\tresetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void {\n\t\tthis.requestExceptionCount = 0;\n\t\tif (syncReqUsecase === 'all') {\n\t\t\tthis._syncRequestsPerFailedUsecase.clear();\n\t\t\t// 'all' is the whole-integration reset: a disconnect, or a re-sync that produced a new access token.\n\t\t\tthis.onStoredTokensReplaced();\n\t\t} else {\n\t\t\tthis._syncRequestsPerFailedUsecase.delete(syncReqUsecase);\n\t\t}\n\t}\n\n\t/**\n\t * Resets request exceptions without resetting the amount of syncs\n\t */\n\tsmoothifyRequestExceptionCount(): void {\n\t\t// On resync we reset exception count only to avoid infinitive syncs on failure\n\t\tthis.requestExceptionCount = 0;\n\t}\n\n\tasync reset(): Promise<void> {\n\t\tawait this.disconnect({ silent: true });\n\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t}\n\n\t/**\n\t * Drops the in-memory session so the next access re-resolves it from storage. Used when the primary\n\t * connection changed underneath a warm integration (e.g. after `setPrimaryConnection`/\n\t * `deleteConnection`). Unlike {@link reset}/{@link disconnect}, it deletes nothing from storage.\n\t */\n\tswitchConnection(): void {\n\t\t// A connection was deleted, or a different one became primary. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tif (this._session === undefined) return;\n\n\t\tconst wasConnected = this._session != null;\n\t\tthis._session = undefined;\n\t\tthis._onDidChange.fire();\n\t\tvoid this.refreshAfterSwitch(wasConnected);\n\t}\n\n\tprivate async refreshAfterSwitch(wasConnected: boolean): Promise<void> {\n\t\tconst session = await this.ensureSession({ createIfNeeded: false });\n\t\tif (session != null || !wasConnected) return;\n\n\t\tthis._onDidChange.fire();\n\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprivate skippedNonCloudReported = false;\n\t@debug()\n\tasync syncCloudConnection(state: 'connected' | 'disconnected', forceSync: boolean): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\t\t// Initially the condition on `this._session.cloud` has been added here: https://github.com/gitkraken/vscode-gitlens/commit/e95e70c430bd162924cc3bd5c1e8ab90e6293449#diff-4213141a45cccaab7aa2e40028b155a87eb913b07388485831403e60ce5555e4R237\n\t\t// I'm not sure about reasons, but it seems we want to replace it with the cloud session if it's connected.\n\t\t// Gradually we'll stop having non-cloud sessions.\n\t\t// However this is needed to be tested with PATs, e.g. with a GitLab PAT.\n\t\tif (this._session?.cloud === false && state !== 'connected') {\n\t\t\tif (this.id !== GitCloudHostIntegrationId.GitHub && !this.skippedNonCloudReported) {\n\t\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\t\tid: this.id,\n\t\t\t\t\treason: 'skip-non-cloud',\n\t\t\t\t\tcloud: false,\n\t\t\t\t});\n\t\t\t\tthis.skippedNonCloudReported = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (state) {\n\t\t\tcase 'connected': {\n\t\t\t\tconst oldSession = this._session;\n\t\t\t\tlet resyncing = false;\n\t\t\t\tif (forceSync) {\n\t\t\t\t\t// Reset our stored session so that we get a new one from the cloud\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor);\n\t\t\t\t\t// The stored token was just deleted. Not left to the token-changed check below, which needs\n\t\t\t\t\t// an `oldSession` — see `onStoredTokensReplaced`.\n\t\t\t\t\tthis.onStoredTokensReplaced();\n\t\t\t\t\t// Reset the session and clear our \"stay disconnected\" flag\n\t\t\t\t\tthis._session = undefined;\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t\t\t\tresyncing = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Only sync if we're not connected and not disabled and don't have pending errors\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis._session != null ||\n\t\t\t\t\t\tthis.requestExceptionCount > 0 ||\n\t\t\t\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tforceSync = true;\n\t\t\t\t}\n\n\t\t\t\t// sync option, rather than createIfNeeded, makes sure we don't call connectCloudIntegrations and open a gkdev window\n\t\t\t\t// if there was no session or some problem fetching/refreshing the existing session from the cloud api\n\t\t\t\tlet newSession: ProviderAuthenticationSession | undefined;\n\t\t\t\tlet refetchFailed = false;\n\t\t\t\ttry {\n\t\t\t\t\tnewSession = await this.ensureSession({ sync: forceSync });\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Not evidence the connection is gone (#5569). Also leaves `_session` as-is rather than\n\t\t\t\t\t// latching null, so the next access can re-resolve it.\n\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\t}\n\t\t\t\t\trefetchFailed = true;\n\t\t\t\t}\n\n\t\t\t\tif (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) {\n\t\t\t\t\tthis.resetRequestExceptionCount('all');\n\t\t\t\t}\n\n\t\t\t\t// The forced re-sync above deleted the cloud secret but kept the descriptor to avoid UI churn.\n\t\t\t\t// Drop it only when the replacement fetch came back definitively empty, so a connection whose\n\t\t\t\t// token is really gone is cleanly disconnected (#5497) while a healthy one survives a blip (#5569).\n\t\t\t\tif (resyncing && newSession == null && !refetchFailed) {\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor, { preserveConfigured: false });\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'disconnected':\n\t\t\t\tawait this.disconnect({ silent: true });\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprotected handleProviderException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions?: { scope?: ScopedLogger | undefined; silent?: boolean; connectionId?: string },\n\t): void {\n\t\tif (isCancellationError(ex)) return;\n\n\t\tif (options?.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\n\t\t// A per-connection (multi-account) read resolved its session through `resolveReadSession`'s\n\t\t// `connectionId` branch, which deliberately never touches the cached primary `_session`. So the\n\t\t// primary-session recovery below cannot apply to it: expiring `_session` would mark a session this\n\t\t// read never used, while the rejected connection kept its stored token and re-sent it on every\n\t\t// later read — a token the provider has already refused (expired scopes, a revoked grant, an\n\t\t// uninstalled app) is not self-healing, so the read failed identically until the user reconnected\n\t\t// by hand. Record the rejection against the connection instead; `resolveReadSession` consumes it\n\t\t// and forces the cloud `/refresh` on the next read of that connection. When there is nothing to\n\t\t// recover, fall through to the shared failure budget, which disconnects after\n\t\t// `requestExceptionLimit` and surfaces the reconnect prompt.\n\t\tif (ex instanceof AuthenticationError && options?.connectionId) {\n\t\t\tif (!this._rejectedTokens.recordRejection(options.connectionId)) {\n\t\t\t\tthis.trackRequestException(options);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (ex instanceof AuthenticationError && this._session?.cloud) {\n\t\t\tif (!this.hasSessionSyncRequests()) {\n\t\t\t\tthis.requestSessionSyncForUsecase(syncReqUsecase);\n\t\t\t\tthis._session = {\n\t\t\t\t\t...this._session,\n\t\t\t\t\texpiresAt: new Date(Date.now() - 1),\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tthis.trackRequestException(options);\n\t\t\t}\n\t\t} else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) {\n\t\t\tthis.trackRequestException(options);\n\t\t}\n\t}\n\n\tprivate missingExpirityReported = false;\n\t@gate()\n\tprotected async refreshSessionIfExpired(scope?: ScopedLogger): Promise<void> {\n\t\tif (this._session?.expiresAt != null && this._session.expiresAt < new Date()) {\n\t\t\t// The current session is expired, so get the latest from the cloud and refresh if needed\n\t\t\ttry {\n\t\t\t\tawait this.syncCloudConnection('connected', true);\n\t\t\t} catch (ex) {\n\t\t\t\tscope?.error(ex);\n\t\t\t}\n\t\t} else if (\n\t\t\tthis._session?.expiresAt == null &&\n\t\t\tthis.id !== GitCloudHostIntegrationId.GitHub &&\n\t\t\t!this.missingExpirityReported\n\t\t) {\n\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\tid: this.id,\n\t\t\t\treason: 'missing-expiry',\n\t\t\t\tcloud: this._session?.cloud,\n\t\t\t});\n\t\t\tthis.missingExpirityReported = true;\n\t\t}\n\t}\n\n\t@trace()\n\ttrackRequestException(options?: { silent?: boolean }): void {\n\t\tthis.requestExceptionCount++;\n\n\t\tif (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) {\n\t\t\tif (!options?.silent) {\n\t\t\t\tthis.ctx.hooks?.ui?.onDisconnectedAfterTooManyFailures?.(this.name);\n\t\t\t}\n\t\t\tvoid this.disconnect({ currentSessionOnly: true });\n\t\t}\n\t}\n\n\t@gate()\n\t@trace({ exit: true })\n\tasync isConnected(): Promise<boolean> {\n\t\treturn (await this.getSession('integrations')) != null;\n\t}\n\n\t@gate()\n\tprivate async ensureSession(\n\t\toptions:\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: boolean;\n\t\t\t\t\tforceNewSession?: boolean;\n\t\t\t\t\tsync?: never;\n\t\t\t\t\tsource?: Sources;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: never;\n\t\t\t\t\tforceNewSession?: never;\n\t\t\t\t\tsync: boolean;\n\t\t\t\t\tsource?: Sources;\n\t\t\t },\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst { createIfNeeded, forceNewSession, source, sync } = options;\n\t\tif (this._session != null) {\n\t\t\tif (this.isSessionForIntegrationHost(this._session)) return this._session;\n\n\t\t\tthis._session = null;\n\t\t}\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return undefined;\n\n\t\tif (createIfNeeded || sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t} else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet session: ProviderAuthenticationSession | undefined | null;\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\tsession = await authProvider.getSession(\n\t\t\t\tthis.authProviderDescriptor,\n\t\t\t\tsync\n\t\t\t\t\t? { sync: sync, source: source }\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tcreateIfNeeded: createIfNeeded,\n\t\t\t\t\t\t\tforceNewSession: forceNewSession,\n\t\t\t\t\t\t\tsource: source,\n\t\t\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (session?.expiresAt != null && session.expiresAt < new Date()) {\n\t\t\t\tsession = null;\n\t\t\t}\n\t\t\tif (session != null && !this.isSessionForIntegrationHost(session)) {\n\t\t\t\tsession = undefined;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\n\t\t\t// Only interactive paths can prompt for consent, so a sync failure must reach the rethrow below.\n\t\t\tif (!sync && ex instanceof Error && ex.message.includes('User did not consent')) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// On a forced re-sync, propagate so syncCloudConnection can tell a failure from a definitive empty\n\t\t\t// result (#5569); other callers keep swallowing to null so reads never throw.\n\t\t\tif (sync) {\n\t\t\t\t// Throwing skips the reset below, and a lingering count blocks the next non-forced sync.\n\t\t\t\tthis.smoothifyRequestExceptionCount();\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tsession = null;\n\t\t}\n\n\t\tif (session === undefined && !createIfNeeded && !sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t}\n\n\t\tthis._session = session ?? null;\n\t\tthis.smoothifyRequestExceptionCount();\n\n\t\tif (session != null) {\n\t\t\tawait this.ctx.storage.storeWorkspace(this.connectedKey, true);\n\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tthis._onDidChange.fire();\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'connected' });\n\t\t\t\t// Fired detached, so there is no caller left to catch anything: every implementor is async, and\n\t\t\t\t// a rejection would surface as a process-level unhandled rejection in the host. It is a\n\t\t\t\t// best-effort warm-up, so swallow the failure with a warning instead.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.providerOnConnect?.();\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t`Failed to run providerOnConnect for ${this.key}: ${ex instanceof Error ? ex.message : String(ex)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t}\n\n\t\treturn session ?? undefined;\n\t}\n\n\tgetIgnoreSSLErrors(): boolean | 'force' {\n\t\treturn this.authenticationService.ignoreSSLErrors(this);\n\t}\n\n\tasync searchMyIssues(\n\t\tresource?: ResourceDescriptor,\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\t@trace()\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchMyIssuesResult(resources, cancellation, connectionId))?.value;\n\t}\n\n\t/**\n\t * Result-returning core of {@link searchMyIssues}. Recovers thrown errors into `{ error }` so callers\n\t * (e.g. the ProviderBackend account-wide issues read) can surface a per-provider warning instead of a\n\t * silent empty result. Returns the normalized {@link IssueShape} (there is no raw account-wide issue read).\n\t */\n\tasync searchMyIssuesResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IntegrationResult<IssueShape[] | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\tconst issues = await this.searchProviderMyIssues(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: issues, duration: performance.now() - start };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex), duration: performance.now() - start };\n\t\t}\n\t}\n\n\tprotected abstract searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined>;\n\n\t/**\n\t * Paging/truncation-aware variant of {@link searchProviderMyIssues}. The default wraps the normalized read\n\t * as a complete single page; providers with native cursors or fan-out metadata override it.\n\t */\n\tprotected async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\t_options?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\t// The default read has no assignee scoping to broaden, so `_options` is inert here; a provider whose\n\t\t// account-wide read is user-scoped (GitHub/GitLab/Azure) overrides this and honors `includeAllAssignees`.\n\t\tconst values = await this.searchProviderMyIssues(session, resources, cancellation);\n\t\tif (values == null) return undefined;\n\t\treturn { values: values, truncated: false };\n\t}\n\n\t/**\n\t * Result-returning, truncation-aware account-wide issue read. Recovers thrown errors into `{ error }` and\n\t * carries the `truncated` flag so the ProviderBackend facade can report an incomplete read honestly.\n\t */\n\tasync searchMyIssuesWithTruncationResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<IntegrationResult<AccountWideIssuesResult | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\ttry {\n\t\t\tconst result = await this.searchProviderMyIssuesWithTruncation(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: result };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\t@trace()\n\tasync getLinkedIssueOrPullRequest(\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\toptions?: { expiryOverride?: boolean | number; type?: IssueOrPullRequestType },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\n\t\tconst issueOrPR = this.ctx.cache.getIssueOrPullRequest(\n\t\t\tlink.key,\n\t\t\toptions?.type,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\t() => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderLinkedIssueOrPullRequest(\n\t\t\t\t\t\t\tthis._session!,\n\t\t\t\t\t\t\tresource,\n\t\t\t\t\t\t\tlink,\n\t\t\t\t\t\t\toptions?.type,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssueOrPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tthis.handleProviderException('getIssueOrPullRequest', ex, { scope: scope });\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\toptions,\n\t\t);\n\t\treturn issueOrPR;\n\t}\n\n\tprotected abstract getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\ttype: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined>;\n\n\tasync getIssue(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.getIssueResult(resource, id, options))?.value;\n\t}\n\n\tgetIssueResult(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\treturn this.getIssueResultCore(resource, id, options, session => this.getProviderIssue(session, resource, id));\n\t}\n\n\t@trace()\n\tprotected async getIssueResultCore(\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t\toptions: { connectionId?: string; expiryOverride?: boolean | number } | undefined,\n\t\tgetProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue | undefined>,\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\ttry {\n\t\t\tconst issue = await getCachedIssue({\n\t\t\t\tcache: this.ctx.cache,\n\t\t\t\tid: id,\n\t\t\t\tresource: resource,\n\t\t\t\tintegration: this,\n\t\t\t\tload: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst issue = await getProviderIssue(session);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssue');\n\t\t\t\t\t\treturn issue;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\t\tthis.handleProviderException('getIssue', toError(ex), {\n\t\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcacheOptions: {\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${this.getSessionFingerprint(session)}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn { value: issue };\n\t\t} catch (ex) {\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\tprotected abstract getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<Issue | undefined>;\n\n\tasync getCurrentAccount(options?: {\n\t\tavatarSize?: number;\n\t\tconnectionId?: string;\n\t\texpiryOverride?: boolean | number;\n\t}): Promise<Account | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride, ...opts } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst sessionFingerprint = this.getSessionFingerprint(session);\n\n\t\tconst currentAccount = await this.ctx.cache.getCurrentAccount(\n\t\t\tthis,\n\n\t\t\t(cacheable: any) => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst account = await this.getProviderCurrentAccount?.(session, opts);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getCurrentAccount');\n\t\t\t\t\t\treturn account;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (isCancellationError(ex)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.handleProviderException('getCurrentAccount', ex, {\n\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Invalidate the cache on error, except for auth errors\n\t\t\t\t\t\tif (!(ex instanceof AuthenticationError)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Re-throw to the caller\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\texpireOnError: false,\n\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${sessionFingerprint}`,\n\t\t\t},\n\t\t);\n\t\treturn currentAccount;\n\t}\n\n\tprotected getProviderCurrentAccount?(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined>;\n\n\t/**\n\t * Resolves the account for a specific session/token — including connections other than the current\n\t * primary (multi-account) — using this integration's provider API base URL and auth type. Returns\n\t * undefined when the provider doesn't support account lookup. Uncached (callers cache per connection).\n\t */\n\tgetProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account | undefined> {\n\t\treturn this.getProviderCurrentAccount?.(session) ?? Promise.resolve(undefined);\n\t}\n\n\t@trace()\n\tasync getPullRequest(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { expiryOverride?: boolean | number; throwOnError?: boolean },\n\t): Promise<PullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\n\t\tconst pr = await this.ctx.cache.getPullRequest(\n\t\t\tid,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderPullRequest?.(this._session!, resource, id);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and the by-id bucket never expires a miss.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (options?.throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{ expiryOverride: options?.expiryOverride },\n\t\t);\n\t\treturn pr;\n\t}\n\n\tprotected getProviderPullRequest?(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<PullRequest | undefined>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAqGA,IAAsB,kBAAtB,MAAsB,gBAGE;CASH;CACA;CACA;CACF;CATlB,eAAgC,IAAI,QAAc;CAClD,IAAI,cAA2B;EAC9B,OAAO,KAAK,aAAa;CAC1B;CAEA,YACC,KACA,uBACA,iBACA,qBACC;EAJkB,KAAA,MAAA;EACA,KAAA,wBAAA;EACA,KAAA,kBAAA;EACF,KAAA,sBAAA;CACf;CAEH,UAAgB;EACf,KAAK,aAAa,QAAQ;CAC3B;CAQA,IAAI,yBAAqE;EACxE,OAAO;GAAE,QAAQ,KAAK;GAAQ,QAAQ,KAAK,aAAa;EAAO;CAChE;;;;;;;;;;CAWA,IAAI,OAAe;EAClB,OAAO,kBAAkB,KAAK,GAAG,EAAE,WAAW,KAAK;CACpD;CAEA,SAA2B;EAC1B,OAAO,KAAK,IAAI,QAAQ,cAAc;CACvC;CAEA,YAE6D;EAC5D,OAAO,CAAC;CACT;CAEA,IAAY,eAA4C;EACvD,OAAO,aAAa,KAAK;CAC1B;CAEA,IAAI,iBAAsC;EACzC,OAAO,KAAK,aAAa,KAAA,IAAY,KAAA,IAAY,KAAK,aAAa;CACpE;;CAGA;CACA,IAAI,qBAAyC;EAC5C,IAAI,KAAK,YAAY,MAAM,OAAO,KAAA;EAElC,IAAI,KAAK,qBAAqB,YAAY,KAAK,UAC9C,KAAK,sBAAsB;GAAE,SAAS,KAAK;GAAU,MAAM,KAAK,sBAAsB,KAAK,QAAQ;EAAE;EAEtG,OAAO,KAAK,oBAAoB;CACjC;CAEA,sBAAgC,SAAgD;EAC/E,OAAO,YAAY,QAAQ,WAAW;CACvC;CAEA,IAAI,oBAAyC;EAC5C,IAAI,KAAK,UAAU,aAAa,MAAM,OAAO,KAAA;EAC7C,OAAO,IAAI,KAAK,KAAK,SAAS,SAAS,oBAAI,IAAI,KAAK;CACrD;CAEA;CACA,WACC,QACiG;EACjG,IAAI,KAAK,aAAa,KAAA,GACrB,OAAO,KAAK,cAAc;GAAE,gBAAgB;GAAe;EAAO,CAAC;EAEpE,OAAO,KAAK,YAAY,QAAQ,KAAK,4BAA4B,KAAK,QAAQ,IAAI,KAAK,WAAW,KAAA;CACnG;CAEA,4BAAoC,SAAiD;EACpF,IAAI,CAAC,+BAA+B,KAAK,EAAE,GAAG,OAAO;EAErD,OAAO,qBAAqB,KAAK,QAAQ,QAAQ,MAAM;CACxD;;;;;;;;;CAUA,MAAgB,mBACf,cACA,OACA,QACqD;EACrD,IACC,KAAK,IAAI,OAAO,wBAAwB,MAAM,SAC9C,KAAK,IAAI,QAAQ,aAAa,KAAK,YAAY,MAAM,OAErD;EAKD,IAAI,cAAc;GAKjB,MAAM,uBAAuB,KAAK,gBAAgB,aAAa,YAAY;GAG3E,IAAI;IAEH,MAAM,UAAU,OAAM,MADK,KAAK,sBAAsB,IAAI,KAAK,aAAa,EAAE,EAAA,CAC3C,WAClC;KAAE,GAAG,KAAK;KAAsC;KAAc,OAAO;IAAK,GAC1E;KAAU;KAA8B;IAAqB,CAC9D;IACA,OAAO,WAAW,QAAQ,KAAK,4BAA4B,OAAO,IAAI,UAAU,KAAA;GACjF,SAAS,IAAI;IACZ,OAAO,MAAM,EAAE;IACf;GACD;EACD;EAGA,IAAI,EADc,KAAK,kBAAmB,MAAM,KAAK,YAAY,IACjD,OAAO,KAAA;EAEvB,MAAM,KAAK,wBAAwB,KAAK;EACxC,OAAO,KAAK,YAAY,QAAQ,KAAK,4BAA4B,KAAK,QAAQ,IAAI,KAAK,WAAW,KAAA;CACnG;CAEA,MACM,QAAQ,QAAmC;EAChD,IAAI;GACH,OAAO,QAAQ,MAAM,KAAK,cAAc;IAAE,gBAAgB;IAAc;GAAO,CAAC,CAAC;EAClF,SAAS,KAAK;GACb,OAAO;EACR;CACD;CAIA,MAEM,WAAW,SAA6E;EAC7F,IAAI,SAAS,sBAAsB,KAAK,aAAa,MAAM;EAE3D,MAAM,YAAY,KAAK,YAAY;EAEnC,IAAI,UAAU,CAAC,SAAS;EAExB,IAAI,aAAa,CAAC,SAAS,sBAAsB,CAAC,SAAS,QAAQ;GAClE,MAAM,WAAW,MAAM,KAAK,IAAI,OAAO,sBAAsB;IAC5D,iBAAiB,KAAK;IACtB,cAAc,KAAK,sBAAsB,SAAS,KAAK,aAAa,EAAE;GACvE,CAAC;GACD,IAAI,YAAY,MAAM;GAEtB,UAAU,SAAS;EACpB;EAEA,IAAI,SAcH,OAAM,MAPqB,KAAK,sBAAsB,IAAI,KAAK,aAAa,EAAE,EAAA,CAO3D,kBAAkB,KAAK,sBAAsB;EAGjE,KAAK,2BAA2B,KAAK;EACrC,KAAK,WAAW;EAEhB,IAAI,WAAW;GAGd,IAAI,CAAC,SAAS,sBAAsB,CAAC,SAAS,QAC7C,KAAU,IAAI,QAAQ,eAAe,KAAK,cAAc,KAAK,CAAC,CAAC,MAAM;GAGtE,KAAK,aAAa,KAAK;GACvB,IAAI,CAAC,SAAS,oBACb,KAAK,qBAAqB,KAAK;IAAE,aAAa;IAAM,KAAK,KAAK;IAAK,QAAQ;GAAe,CAAC;EAE7F;EAEA,MAAM,KAAK,uBAAuB;CACnC;CAIA,MACM,iBAAgC;EAGrC,KAAK,uBAAuB;EAE5B,IAAI,KAAK,aAAa,KAAA,GAAW;EAEjC,KAAK,WAAW,KAAA;EAChB,MAAY,KAAK,cAAc;GAAE,gBAAgB;GAAM,iBAAiB;EAAK,CAAC;CAC/E;CAEA,UAAgB;EACf,KAAU,cAAc,EAAE,gBAAgB,MAAM,CAAC;CAClD;CAEA,gDAAwC,IAAI,IAAoB;CAChE,yBAAkC;EACjC,OAAO,KAAK,8BAA8B,OAAO;CAClD;CACA,6BAA6B,gBAAsC;EAClE,KAAK,8BAA8B,IAAI,cAAc;CACtD;;;;;;;CAQA,kBAAmC,IAAI,qBAAqB;;;;;;;;;;;;CAa5D,yBAAyC;EACxC,KAAK,gBAAgB,MAAM;CAC5B;CAEA,OAAwB,wBAAwB;CAChD,wBAAgC;CAEhC,2BAA2B,gBAA8C;EACxE,KAAK,wBAAwB;EAC7B,IAAI,mBAAmB,OAAO;GAC7B,KAAK,8BAA8B,MAAM;GAEzC,KAAK,uBAAuB;EAC7B,OACC,KAAK,8BAA8B,OAAO,cAAc;CAE1D;;;;CAKA,iCAAuC;EAEtC,KAAK,wBAAwB;CAC9B;CAEA,MAAM,QAAuB;EAC5B,MAAM,KAAK,WAAW,EAAE,QAAQ,KAAK,CAAC;EACtC,MAAM,KAAK,IAAI,QAAQ,gBAAgB,KAAK,YAAY;CACzD;;;;;;CAOA,mBAAyB;EAGxB,KAAK,uBAAuB;EAE5B,IAAI,KAAK,aAAa,KAAA,GAAW;EAEjC,MAAM,eAAe,KAAK,YAAY;EACtC,KAAK,WAAW,KAAA;EAChB,KAAK,aAAa,KAAK;EACvB,KAAU,mBAAmB,YAAY;CAC1C;CAEA,MAAc,mBAAmB,cAAsC;EAEtE,IAAI,MADkB,KAAK,cAAc,EAAE,gBAAgB,MAAM,CAAC,KACnD,QAAQ,CAAC,cAAc;EAEtC,KAAK,aAAa,KAAK;EACvB,KAAK,qBAAqB,KAAK;GAAE,aAAa;GAAM,KAAK,KAAK;GAAK,QAAQ;EAAe,CAAC;EAC3F,MAAM,KAAK,uBAAuB;CACnC;CAEA,0BAAkC;CAClC,MACM,oBAAoB,OAAqC,WAAmC;EACjG,MAAM,QAAQ,gBAAgB;EAK9B,IAAI,KAAK,UAAU,UAAU,SAAS,UAAU,aAAa;GAC5D,IAAI,KAAK,OAAA,YAA2C,CAAC,KAAK,yBAAyB;IAClF,KAAK,IAAI,OAAO,SAAS,mBAAmB;KAC3C,IAAI,KAAK;KACT,QAAQ;KACR,OAAO;IACR,CAAC;IACD,KAAK,0BAA0B;GAChC;GACA;EACD;EAEA,QAAQ,OAAR;GACC,KAAK,aAAa;IACjB,MAAM,aAAa,KAAK;IACxB,IAAI,YAAY;IAChB,IAAI,WAAW;KAGd,OAAM,MADqB,KAAK,sBAAsB,IAAI,KAAK,aAAa,EAAE,EAAA,CAC3D,cAAc,KAAK,sBAAsB;KAG5D,KAAK,uBAAuB;KAE5B,KAAK,WAAW,KAAA;KAChB,MAAM,KAAK,IAAI,QAAQ,gBAAgB,KAAK,YAAY;KACxD,YAAY;IACb,OAAO;KAEN,IACC,KAAK,YAAY,QACjB,KAAK,wBAAwB,KAC7B,KAAK,IAAI,QAAQ,aAAa,KAAK,YAAY,MAAM,OAErD;KAGD,YAAY;IACb;IAIA,IAAI;IACJ,IAAI,gBAAgB;IACpB,IAAI;KACH,aAAa,MAAM,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;IAC1D,SAAS,IAAI;KAGZ,IAAI,CAAC,oBAAoB,EAAE,GAC1B,OAAO,MAAM,EAAE;KAEhB,gBAAgB;IACjB;IAEA,IAAI,cAAc,cAAc,WAAW,gBAAgB,WAAW,aACrE,KAAK,2BAA2B,KAAK;IAMtC,IAAI,aAAa,cAAc,QAAQ,CAAC,eAEvC,OAAM,MADqB,KAAK,sBAAsB,IAAI,KAAK,aAAa,EAAE,EAAA,CAC3D,cAAc,KAAK,wBAAwB,EAAE,oBAAoB,MAAM,CAAC;IAG5F;GACD;GACA,KAAK,gBACJ,MAAM,KAAK,WAAW,EAAE,QAAQ,KAAK,CAAC;EAExC;CACD;CAEA,wBACC,gBACA,IACA,SACO;EACP,IAAI,oBAAoB,EAAE,GAAG;EAE7B,IAAI,SAAS,SAAS,MACrB,QAAQ,MAAM,MAAM,EAAE;OAEtB,QAAO,MAAM,EAAE;EAahB,IAAI,cAAc,uBAAuB,SAAS,cAAc;GAC/D,IAAI,CAAC,KAAK,gBAAgB,gBAAgB,QAAQ,YAAY,GAC7D,KAAK,sBAAsB,OAAO;GAEnC;EACD;EAEA,IAAI,cAAc,uBAAuB,KAAK,UAAU,OAAO;GAC9D,IAAI,CAAC,KAAK,uBAAuB,GAAG;IACnC,KAAK,6BAA6B,cAAc;IAChD,KAAK,WAAW;KACf,GAAG,KAAK;KACR,2BAAW,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC;IACnC;GACD,OACC,KAAK,sBAAsB,OAAO;EAEpC,OAAO,IAAI,cAAc,uBAAuB,cAAc,oBAC7D,KAAK,sBAAsB,OAAO;CAEpC;CAEA,0BAAkC;CAClC,MACgB,wBAAwB,OAAqC;EAC5E,IAAI,KAAK,UAAU,aAAa,QAAQ,KAAK,SAAS,4BAAY,IAAI,KAAK,GAE1E,IAAI;GACH,MAAM,KAAK,oBAAoB,aAAa,IAAI;EACjD,SAAS,IAAI;GACZ,OAAO,MAAM,EAAE;EAChB;OACM,IACN,KAAK,UAAU,aAAa,QAC5B,KAAK,OAAA,YACL,CAAC,KAAK,yBACL;GACD,KAAK,IAAI,OAAO,SAAS,mBAAmB;IAC3C,IAAI,KAAK;IACT,QAAQ;IACR,OAAO,KAAK,UAAU;GACvB,CAAC;GACD,KAAK,0BAA0B;EAChC;CACD;CAEA,sBACsB,SAAsC;EAC3D,KAAK;EAEL,IAAI,KAAK,yBAAyB,gBAAgB,yBAAyB,KAAK,aAAa,MAAM;GAClG,IAAI,CAAC,SAAS,QACb,KAAK,IAAI,OAAO,IAAI,qCAAqC,KAAK,IAAI;GAEnE,KAAU,WAAW,EAAE,oBAAoB,KAAK,CAAC;EAClD;CACD;CAEA,MAEM,cAAgC;EACrC,OAAQ,MAAM,KAAK,WAAW,cAAc,KAAM;CACnD;CAEA,MACc,cACb,SAaqD;EACrD,MAAM,QAAQ,gBAAgB;EAE9B,MAAM,EAAE,gBAAgB,iBAAiB,QAAQ,SAAS;EAC1D,IAAI,KAAK,YAAY,MAAM;GAC1B,IAAI,KAAK,4BAA4B,KAAK,QAAQ,GAAG,OAAO,KAAK;GAEjE,KAAK,WAAW;EACjB;EACA,IAAI,KAAK,IAAI,OAAO,wBAAwB,MAAM,OAAO,OAAO,KAAA;EAEhE,IAAI,kBAAkB,MACrB,MAAM,KAAK,IAAI,QAAQ,gBAAgB,KAAK,YAAY;OAClD,IAAI,KAAK,IAAI,QAAQ,aAAa,KAAK,YAAY,MAAM,OAC/D;EAGD,IAAI;EACJ,IAAI;GAEH,UAAU,OAAM,MADW,KAAK,sBAAsB,IAAI,KAAK,aAAa,EAAE,EAAA,CACjD,WAC5B,KAAK,wBACL,OACG;IAAQ;IAAc;GAAO,IAC7B;IACgB;IACC;IACT;GACT,CACH;GAEA,IAAI,SAAS,aAAa,QAAQ,QAAQ,4BAAY,IAAI,KAAK,GAC9D,UAAU;GAEX,IAAI,WAAW,QAAQ,CAAC,KAAK,4BAA4B,OAAO,GAC/D,UAAU,KAAA;EAEZ,SAAS,IAAI;GACZ,MAAM,KAAK,IAAI,QAAQ,gBAAgB,KAAK,YAAY;GAGxD,IAAI,CAAC,QAAQ,cAAc,SAAS,GAAG,QAAQ,SAAS,sBAAsB,GAC7E;GAKD,IAAI,MAAM;IAET,KAAK,+BAA+B;IACpC,MAAM;GACP;GAEA,UAAU;EACX;EAEA,IAAI,YAAY,KAAA,KAAa,CAAC,kBAAkB,CAAC,MAChD,MAAM,KAAK,IAAI,QAAQ,gBAAgB,KAAK,YAAY;EAGzD,KAAK,WAAW,WAAW;EAC3B,KAAK,+BAA+B;EAEpC,IAAI,WAAW,MAAM;GACpB,MAAM,KAAK,IAAI,QAAQ,eAAe,KAAK,cAAc,IAAI;GAE7D,qBAAqB;IACpB,KAAK,aAAa,KAAK;IACvB,KAAK,qBAAqB,KAAK;KAAE,aAAa;KAAM,KAAK,KAAK;KAAK,QAAQ;IAAY,CAAC;IAIxF,CAAM,YAAY;KACjB,IAAI;MACH,MAAM,KAAK,oBAAoB;KAChC,SAAS,IAAI;MACZ,OAAO,KACN,uCAAuC,KAAK,IAAI,IAAI,cAAc,QAAQ,GAAG,UAAU,OAAO,EAAE,GACjG;KACD;IACD,EAAA,CAAG;GACJ,CAAC;EACF;EAEA,OAAO,WAAW,KAAA;CACnB;CAEA,qBAAwC;EACvC,OAAO,KAAK,sBAAsB,gBAAgB,IAAI;CACvD;CAYA,MACM,eACL,WACA,cACA,cACoC;EACpC,QAAQ,MAAM,KAAK,qBAAqB,WAAW,cAAc,YAAY,EAAA,EAAI;CAClF;;;;;;CAOA,MAAM,qBACL,WACA,cACA,cACuD;EACvD,MAAM,QAAQ,gBAAgB;EAE9B,MAAM,UAAU,MAAM,KAAK,mBAAmB,cAAc,KAAK;EACjE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,MAAM,QAAQ,YAAY,IAAI;EAC9B,IAAI;GACH,MAAM,SAAS,MAAM,KAAK,uBACzB,SACA,aAAa,OAAQ,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS,IAAK,KAAA,GAC3E,YACD;GACA,KAAK,2BAA2B,gBAAgB;GAChD,OAAO;IAAE,OAAO;IAAQ,UAAU,YAAY,IAAI,IAAI;GAAM;EAC7D,SAAS,IAAI;GACZ,KAAK,wBAAwB,kBAAkB,IAAI;IAAS;IAAqB;GAAa,CAAC;GAC/F,OAAO;IAAE,OAAO,QAAQ,EAAE;IAAG,UAAU,YAAY,IAAI,IAAI;GAAM;EAClE;CACD;;;;;CAYA,MAAgB,qCACf,SACA,WACA,cACA,UAC+C;EAG/C,MAAM,SAAS,MAAM,KAAK,uBAAuB,SAAS,WAAW,YAAY;EACjF,IAAI,UAAU,MAAM,OAAO,KAAA;EAC3B,OAAO;GAAU;GAAQ,WAAW;EAAM;CAC3C;;;;;CAMA,MAAM,mCACL,WACA,cACA,cACA,SACkE;EAClE,MAAM,QAAQ,gBAAgB;EAC9B,MAAM,UAAU,MAAM,KAAK,mBAAmB,cAAc,KAAK;EACjE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,IAAI;GACH,MAAM,SAAS,MAAM,KAAK,qCACzB,SACA,aAAa,OAAQ,MAAM,QAAQ,SAAS,IAAI,YAAY,CAAC,SAAS,IAAK,KAAA,GAC3E,cACA,OACD;GACA,KAAK,2BAA2B,gBAAgB;GAChD,OAAO,EAAE,OAAO,OAAO;EACxB,SAAS,IAAI;GACZ,KAAK,wBAAwB,kBAAkB,IAAI;IAAS;IAAqB;GAAa,CAAC;GAC/F,OAAO,EAAE,OAAO,QAAQ,EAAE,EAAE;EAC7B;CACD;CAEA,MACM,4BACL,UACA,MACA,SAC0C;EAC1C,MAAM,QAAQ,gBAAgB;EAG9B,IAAI,EADc,KAAK,kBAAmB,MAAM,KAAK,YAAY,IACjD,OAAO,KAAA;EAEvB,MAAM,KAAK,wBAAwB,KAAK;EA0BxC,OAxBkB,KAAK,IAAI,MAAM,sBAChC,KAAK,KACL,SAAS,MACT,UACA,aACO,EACN,QAAQ,YAAY;GACnB,IAAI;IACH,MAAM,SAAS,MAAM,KAAK,oCACzB,KAAK,UACL,UACA,MACA,SAAS,IACV;IACA,KAAK,2BAA2B,uBAAuB;IACvD,OAAO;GACR,SAAS,IAAI;IACZ,KAAK,wBAAwB,yBAAyB,IAAI,EAAS,MAAM,CAAC;IAC1E;GACD;EACD,EAAA,CAAG,EACJ,IACA,OAEc;CAChB;CASA,MAAM,SACL,UACA,IACA,SAC6B;EAC7B,QAAQ,MAAM,KAAK,eAAe,UAAU,IAAI,OAAO,EAAA,EAAI;CAC5D;CAEA,eACC,UACA,IACA,SACgD;EAChD,OAAO,KAAK,mBAAmB,UAAU,IAAI,UAAS,YAAW,KAAK,iBAAiB,SAAS,UAAU,EAAE,CAAC;CAC9G;CAEA,MACgB,mBACf,UACA,IACA,SACA,kBACgD;EAChD,MAAM,QAAQ,gBAAgB;EAC9B,MAAM,EAAE,cAAc,uBAAuB,mBAAmB,WAAW,CAAC;EAC5E,MAAM,eAAe,yBAAyB,KAAA;EAC9C,MAAM,UAAU,MAAM,KAAK,mBAAmB,cAAc,KAAK;EACjE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,IAAI;GA2BH,OAAO,EAAE,OAAO,MA1BI,eAAe;IAClC,OAAO,KAAK,IAAI;IACZ;IACM;IACV,aAAa;IACb,MAAM,YAAY;KACjB,IAAI;MACH,MAAM,QAAQ,MAAM,iBAAiB,OAAO;MAC5C,KAAK,2BAA2B,UAAU;MAC1C,OAAO;KACR,SAAS,IAAI;MACZ,IAAI,CAAC,oBAAoB,EAAE,GAC1B,KAAK,wBAAwB,YAAY,QAAQ,EAAE,GAAG;OAC9C;OACO;MACf,CAAC;MAEF,MAAM;KACP;IACD;IACA,cAAc;KACC;KACE;KAChB,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,kBAAkB,MAAM,GAAG,KAAK,sBAAsB,OAAO;IACvF;GACD,CAAC,EACqB;EACvB,SAAS,IAAI;GACZ,OAAO,EAAE,OAAO,QAAQ,EAAE,EAAE;EAC7B;CACD;CAQA,MAAM,kBAAkB,SAIS;EAChC,MAAM,QAAQ,gBAAgB;EAC9B,MAAM,EAAE,cAAc,uBAAuB,gBAAgB,GAAG,SAAS,WAAW,CAAC;EACrF,MAAM,eAAe,yBAAyB,KAAA;EAC9C,MAAM,UAAU,MAAM,KAAK,mBAAmB,cAAc,KAAK;EACjE,IAAI,WAAW,MAAM,OAAO,KAAA;EAE5B,MAAM,qBAAqB,KAAK,sBAAsB,OAAO;EAuC7D,OAAO,MArCsB,KAAK,IAAI,MAAM,kBAC3C,OAEC,eAAoB,EACpB,QAAQ,YAAY;GACnB,IAAI;IACH,MAAM,UAAU,MAAM,KAAK,4BAA4B,SAAS,IAAI;IACpE,KAAK,2BAA2B,mBAAmB;IACnD,OAAO;GACR,SAAS,IAAI;IACZ,IAAI,oBAAoB,EAAE,GAAG;KAC5B,UAAU,WAAW;KACrB;IACD;IAEA,KAAK,wBAAwB,qBAAqB,IAAI;KAC9C;KACO;IACf,CAAC;IAGD,IAAI,EAAE,cAAc,sBACnB,UAAU,WAAW;IAItB,MAAM;GACP;EACD,EAAA,CAAG,EACJ,IACA;GACe;GACE;GAChB,eAAe;GACf,MAAM,GAAG,KAAK,GAAG,GAAG,KAAK,kBAAkB,MAAM,GAAG;EACrD,CACD;CAED;;;;;;CAYA,6BAA6B,SAAsE;EAClG,OAAO,KAAK,4BAA4B,OAAO,KAAK,QAAQ,QAAQ,KAAA,CAAS;CAC9E;CAEA,MACM,eACL,UACA,IACA,SACmC;EACnC,MAAM,QAAQ,gBAAgB;EAG9B,IAAI,EADc,KAAK,kBAAmB,MAAM,KAAK,YAAY,IACjD,OAAO,KAAA;EAEvB,MAAM,KAAK,wBAAwB,KAAK;EAwBxC,OAAO,MAtBU,KAAK,IAAI,MAAM,eAC/B,IACA,UACA,OACA,eAAc,EACb,QAAQ,YAAY;GACnB,IAAI;IACH,MAAM,SAAS,MAAM,KAAK,yBAAyB,KAAK,UAAW,UAAU,EAAE;IAC/E,KAAK,2BAA2B,gBAAgB;IAChD,OAAO;GACR,SAAS,IAAI;IAEZ,UAAU,WAAW;IACrB,KAAK,wBAAwB,kBAAkB,IAAI,EAAS,MAAM,CAAC;IACnE,IAAI,SAAS,cAAc,MAAM;IAEjC;GACD;EACD,EAAA,CAAG,EACJ,IACA,EAAE,gBAAgB,SAAS,eAAe,CAC3C;CAED;AAOD;AAnwBE,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,WAAA,IAAA;AAWN,WAAA,CAAA,KAAK,GACL,MAAM,CAAA,GAAA,gBAAA,WAAA,cAAA,IAAA;AAwDN,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,kBAAA,IAAA;AAsGN,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,uBAAA,IAAA;AAgIN,WAAA,CAAA,KAAK,CAAA,GAAA,gBAAA,WAAA,2BAAA,IAAA;AAuBL,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,yBAAA,IAAA;AAYN,WAAA,CAAA,KAAK,GACL,MAAM,EAAE,MAAM,KAAK,CAAC,CAAA,GAAA,gBAAA,WAAA,eAAA,IAAA;AAKpB,WAAA,CAAA,KAAK,CAAA,GAAA,gBAAA,WAAA,iBAAA,IAAA;AAoHL,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,kBAAA,IAAA;AA2FN,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,+BAAA,IAAA;AA+DN,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,sBAAA,IAAA;AAuHN,WAAA,CAAA,MAAM,CAAA,GAAA,gBAAA,WAAA,kBAAA,IAAA"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../../../../../plus/integrations/src/models/integration.ts"],"sourcesContent":["import type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type { IssueOrPullRequest, IssueOrPullRequestType } from '@gitlens/git/models/issueOrPullRequest.js';\nimport type { PullRequest } from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug, trace } from '@gitlens/utils/decorators/log.js';\nimport type { Disposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport type {\n\tIntegrationAuthenticationProviderDescriptor,\n\tIntegrationAuthenticationSessionDescriptor,\n} from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ProviderAuthenticationSession } from '../authentication/models.js';\nimport { RejectedTokenTracker } from '../authentication/rejectedTokenTracker.js';\nimport type { IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { AuthenticationError, RequestClientError, toError } from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport { providersMetadata } from '../providers/models.js';\nimport type { ProvidersApi } from '../providers/providersApi.js';\nimport type { Sources } from '../telemetry.js';\nimport { areDomainsOnSameHost } from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { GitHostIntegration } from './gitHostIntegration.js';\nimport { getCachedIssue } from './issueCache.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from './issueReads.js';\nimport type { IssuesIntegration } from './issuesIntegration.js';\n\nexport type Integration = GitHostIntegration | IssuesIntegration;\nexport type IntegrationById<T extends IntegrationIds> = T extends IssuesHostIntegrationIds\n\t? IssuesIntegration\n\t: GitHostIntegration;\nexport type IntegrationType = 'git' | 'issues';\n\n// The issue-read contracts live in their own module (pure data, and their relationship to each other is the\n// point of reading them together); re-exported here so the providers that implement these reads keep one import.\nexport type {\n\tAccountWideIssuesResult,\n\tProviderIssueSearchPage,\n\tProviderSearchCount,\n\tSearchMyIssuesOptions,\n} from './issueReads.js';\nexport type { ProviderPullRequestCount, ProviderPullRequestSearchPage } from './pullRequestReads.js';\n\n// Keep this in step with `isSelfManagedHostIntegrationId`: it is the type-level twin of that predicate, and a\n// disagreement compiles cleanly while the runtime writes a domain-keyed key the type says is unkeyed (so the\n// local-disconnect flag is written under one key and read under another).\nexport type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extends\n\t| GitCloudHostIntegrationId\n\t| IssuesCloudHostIntegrationId\n\t? `${T}`\n\t: `${T}:${string}`;\n\nexport type IntegrationConnectedKey<T extends IntegrationIds = IntegrationIds> = `connected:${IntegrationKey<T>}`;\n\nexport type IntegrationResult<T> =\n\t| { value: T; duration?: number; error?: Error }\n\t| { error: Error; duration?: number; value?: never }\n\t| undefined;\n\ntype SyncReqUsecase = Exclude<\n\t| 'getAccountForCommit'\n\t| 'getAccountForEmail'\n\t| 'getAccountForResource'\n\t| 'getCurrentAccount'\n\t| 'getDefaultBranch'\n\t| 'getIssue'\n\t| 'getIssueOrPullRequest'\n\t| 'getIssuesBatch'\n\t| 'getIssuesForProject'\n\t| 'getIssuesForRepos'\n\t| 'getMyPullRequestsForUser'\n\t| 'getOrganizationsForUser'\n\t| 'getProjectsForOrg'\n\t| 'getProjectsForResources'\n\t| 'getPullRequest'\n\t| 'getRepositoriesForOrg'\n\t| 'getRepositoriesForUser'\n\t| 'getPullRequestForBranch'\n\t| 'getPullRequestForCommit'\n\t| 'getPullRequestsForRepos'\n\t| 'getRepositoryMetadata'\n\t| 'getResourcesForUser'\n\t| 'getSshSigningKeysForEmails'\n\t| 'countIssues'\n\t| 'countPullRequests'\n\t| 'mergePullRequest'\n\t| 'searchIssuesPage'\n\t| 'searchPullRequestsPage'\n\t| 'searchMyIssues'\n\t| 'searchMyPullRequests'\n\t| 'searchPullRequests',\n\t// excluding to show explicitly that we don't want to add 'all' key occasionally\n\t'all'\n>;\n\nexport abstract class IntegrationBase<\n\tID extends IntegrationIds = IntegrationIds,\n\tT extends ResourceDescriptor = ResourceDescriptor,\n> implements Disposable {\n\tabstract readonly type: IntegrationType;\n\n\tprivate readonly _onDidChange = new Emitter<void>();\n\tget onDidChange(): Event<void> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(\n\t\tprotected readonly ctx: IntegrationServiceContext,\n\t\tprotected readonly authenticationService: IntegrationAuthenticationService,\n\t\tprotected readonly getProvidersApi: () => Promise<ProvidersApi>,\n\t\tprivate readonly didChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tabstract get authProvider(): IntegrationAuthenticationProviderDescriptor;\n\tabstract get id(): ID;\n\tprotected abstract get key(): IntegrationKey<ID>;\n\tabstract get name(): string;\n\tabstract get domain(): string;\n\n\tget authProviderDescriptor(): IntegrationAuthenticationSessionDescriptor {\n\t\treturn { domain: this.domain, scopes: this.authProvider.scopes };\n\t}\n\n\t/**\n\t * The `gl-provider-<key>` glicon key for this provider, which is NOT always its id.\n\t *\n\t * `providersMetadata.iconKey` is the field that says which glyph a provider draws with, and a\n\t * self-managed variant reuses its cloud family's — Jira Data Center draws with Jira's, because it is the\n\t * same product and the id only selects an icon. Returning the id instead named a glyph the registry has\n\t * no entry for, so a consumer rendering a `gl-provider-` glicon from `issue.provider.icon` requested a\n\t * glyph that does not exist.\n\t */\n\tget icon(): string {\n\t\treturn providersMetadata[this.id]?.iconKey ?? this.id;\n\t}\n\n\taccess(): Promise<boolean> {\n\t\treturn this.ctx.account.isTrialOrPaid();\n\t}\n\n\tautolinks():\n\t\t| (AutolinkReference | DynamicAutolinkReference)[]\n\t\t| Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\treturn [];\n\t}\n\n\tprivate get connectedKey(): IntegrationConnectedKey<ID> {\n\t\treturn `connected:${this.key}`;\n\t}\n\n\tget maybeConnected(): boolean | undefined {\n\t\treturn this._session === undefined ? undefined : this._session !== null;\n\t}\n\n\t/** Hash of the current session's access token. Changes on any token change (account switch or refresh). */\n\tprivate _sessionFingerprint: { session: ProviderAuthenticationSession; hash: string } | undefined;\n\tget sessionFingerprint(): string | undefined {\n\t\tif (this._session == null) return undefined;\n\n\t\tif (this._sessionFingerprint?.session !== this._session) {\n\t\t\tthis._sessionFingerprint = { session: this._session, hash: this.getSessionFingerprint(this._session) };\n\t\t}\n\t\treturn this._sessionFingerprint.hash;\n\t}\n\n\tprotected getSessionFingerprint(session: ProviderAuthenticationSession): string {\n\t\treturn fnv1aHash64(session.accessToken);\n\t}\n\n\tget connectionExpired(): boolean | undefined {\n\t\tif (this._session?.expiresAt == null) return undefined;\n\t\treturn new Date(this._session.expiresAt) < new Date();\n\t}\n\n\tprotected _session: ProviderAuthenticationSession | null | undefined;\n\tgetSession(\n\t\tsource: Sources,\n\t): ProviderAuthenticationSession | Promise<ProviderAuthenticationSession | undefined> | undefined {\n\t\tif (this._session === undefined) {\n\t\t\treturn this.ensureSession({ createIfNeeded: false, source: source });\n\t\t}\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\tprivate isSessionForIntegrationHost(session: ProviderAuthenticationSession): boolean {\n\t\tif (!isSelfManagedHostIntegrationId(this.id)) return true;\n\n\t\treturn areDomainsOnSameHost(this.domain, session.domain);\n\t}\n\n\t/**\n\t * Resolves the session to read as, for a per-connection (multi-account) read. When `connectionId` is\n\t * omitted this is the integration's primary session, resolved exactly like the existing read flow\n\t * (ensure-connected + refresh-if-expired). When set, it resolves THAT connection's session directly\n\t * from the auth provider — refreshing it if expired — WITHOUT disturbing the cached primary\n\t * `_session`. Returns undefined when the requested session can't be resolved (e.g. the connection is\n\t * gone or the provider isn't connected), so callers degrade to \"no results\".\n\t */\n\tprotected async resolveReadSession(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t\tsource?: Sources,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tif (\n\t\t\tthis.ctx.config.isIntegrationsEnabled?.() === false ||\n\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// A truthy connectionId targets a specific account; an empty string is not a real target, so it falls\n\t\t// through to the primary path below.\n\t\tif (connectionId) {\n\t\t\t// A read of this connection previously failed with an AuthenticationError, so the token in\n\t\t\t// storage is known-refused. Ask for a refresh through the GK cloud before reading again, which\n\t\t\t// is this branch's equivalent of the primary path's `refreshSessionIfExpired`. Claimed here so\n\t\t\t// the forced refresh runs at most once per rejection.\n\t\t\tconst refreshRejectedToken = this._rejectedTokens.claimRefresh(connectionId);\n\t\t\t// Degrade to \"no results\" on failure, matching the primary path (whose ensureSession/\n\t\t\t// refreshSessionIfExpired swallow errors) so read methods keep their never-throws contract.\n\t\t\ttry {\n\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\tconst session = await authProvider.getSession(\n\t\t\t\t\t{ ...this.authProviderDescriptor, connectionId: connectionId, cloud: true },\n\t\t\t\t\t{ source: source, refreshRejectedToken: refreshRejectedToken },\n\t\t\t\t);\n\t\t\t\treturn session != null && this.isSessionForIntegrationHost(session) ? session : undefined;\n\t\t\t} catch (ex) {\n\t\t\t\tscope?.error(ex);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\t@debug()\n\tasync connect(source: Sources): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Boolean(await this.ensureSession({ createIfNeeded: true, source: source }));\n\t\t} catch (_ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected providerOnConnect?(): void | Promise<void>;\n\n\t@gate()\n\t@debug()\n\tasync disconnect(options?: { silent?: boolean; currentSessionOnly?: boolean }): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tconst connected = this._session != null;\n\n\t\tlet signOut = !options?.currentSessionOnly;\n\n\t\tif (connected && !options?.currentSessionOnly && !options?.silent) {\n\t\t\tconst decision = await this.ctx.hooks?.onConfirmDisconnect?.({\n\t\t\t\tintegrationName: this.name,\n\t\t\t\tofferSignOut: this.authenticationService.supports(this.authProvider.id),\n\t\t\t});\n\t\t\tif (decision == null) return;\n\n\t\t\tsignOut = decision.signOut;\n\t\t}\n\n\t\tif (signOut) {\n\t\t\t// Disconnecting a provider signs out of ALL its connected accounts (multi-account), not just the\n\t\t\t// primary — otherwise secondary connections' secrets/config would be orphaned. Removing a single\n\t\t\t// account is done via IntegrationService.deleteConnection instead. Pass this instance's descriptor\n\t\t\t// so self-managed disconnects stay scoped to this host: those group every host under one provider\n\t\t\t// id, so an unscoped clear would sign the user out of unrelated hosts. deleteAllSessions derives an\n\t\t\t// undefined domain for cloud providers, so they still clear every account as intended.\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t// Awaited, not fire-and-forget: a caller that awaits `disconnect()` has to be able to rely on the\n\t\t\t// secrets and descriptors actually being gone when it resumes. Left floating, the only thing that\n\t\t\t// ever made this land in time was incidental scheduling slack — `syncCloudIntegrations` used to\n\t\t\t// await each provider in turn, so a later iteration's suspension let the previous provider's delete\n\t\t\t// finish. Syncing providers concurrently removes that slack and the clear was observably still\n\t\t\t// pending when the sync returned.\n\t\t\tawait authProvider.deleteAllSessions(this.authProviderDescriptor);\n\t\t}\n\n\t\tthis.resetRequestExceptionCount('all');\n\t\tthis._session = null;\n\n\t\tif (connected) {\n\t\t\t// Don't store the disconnected flag if silently disconnecting or disconnecting this only for\n\t\t\t// this current VS Code session (will be re-connected on next restart)\n\t\t\tif (!options?.currentSessionOnly && !options?.silent) {\n\t\t\t\tvoid this.ctx.storage.storeWorkspace(this.connectedKey, false).catch();\n\t\t\t}\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tif (!options?.currentSessionOnly) {\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\t}\n\t\t}\n\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprotected providerOnDisconnect?(): void | Promise<void>;\n\n\t@debug()\n\tasync reauthenticate(): Promise<void> {\n\t\t// `forceNewSession` below deletes the stored secrets to reconnect. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tif (this._session === undefined) return;\n\n\t\tthis._session = undefined;\n\t\tvoid (await this.ensureSession({ createIfNeeded: true, forceNewSession: true }));\n\t}\n\n\trefresh(): void {\n\t\tvoid this.ensureSession({ createIfNeeded: false });\n\t}\n\n\tprivate _syncRequestsPerFailedUsecase = new Set<SyncReqUsecase>();\n\thasSessionSyncRequests(): boolean {\n\t\treturn this._syncRequestsPerFailedUsecase.size > 0;\n\t}\n\trequestSessionSyncForUsecase(syncReqUsecase: SyncReqUsecase): void {\n\t\tthis._syncRequestsPerFailedUsecase.add(syncReqUsecase);\n\t}\n\n\t/**\n\t * The per-connection counterpart of the primary session's expire-and-resync recovery: a token the\n\t * provider refused is refreshed once through the GK cloud, which exchanges the refresh token server-side\n\t * (the client never holds one). Armed by {@link handleProviderException} and consumed by\n\t * {@link resolveReadSession}; a rejection it declines belongs to {@link trackRequestException} instead.\n\t */\n\tprivate readonly _rejectedTokens = new RejectedTokenTracker();\n\n\t/**\n\t * Called by every path that replaces or removes the stored tokens — a disconnect, a forced re-sync, a\n\t * reauthentication, or the connection set changing. A rejection names a specific credential, so once that\n\t * credential is gone the rejection describes nothing and must not force a refresh (or, for a deleted\n\t * connection, outlive it).\n\t *\n\t * Deliberately NOT conditioned on `_session`. A per-connection read resolves through the auth provider and\n\t * never populates the cached primary session, so the connections this recovery exists for are exactly the\n\t * ones with no `_session` to inspect — the same blind spot the recovery itself was added to fix. Callers\n\t * that guard on `_session` therefore invoke this ahead of that guard.\n\t */\n\tprotected onStoredTokensReplaced(): void {\n\t\tthis._rejectedTokens.clear();\n\t}\n\n\tprivate static readonly requestExceptionLimit = 5;\n\tprivate requestExceptionCount = 0;\n\n\tresetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void {\n\t\tthis.requestExceptionCount = 0;\n\t\tif (syncReqUsecase === 'all') {\n\t\t\tthis._syncRequestsPerFailedUsecase.clear();\n\t\t\t// 'all' is the whole-integration reset: a disconnect, or a re-sync that produced a new access token.\n\t\t\tthis.onStoredTokensReplaced();\n\t\t} else {\n\t\t\tthis._syncRequestsPerFailedUsecase.delete(syncReqUsecase);\n\t\t}\n\t}\n\n\t/**\n\t * Resets request exceptions without resetting the amount of syncs\n\t */\n\tsmoothifyRequestExceptionCount(): void {\n\t\t// On resync we reset exception count only to avoid infinitive syncs on failure\n\t\tthis.requestExceptionCount = 0;\n\t}\n\n\tasync reset(): Promise<void> {\n\t\tawait this.disconnect({ silent: true });\n\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t}\n\n\t/**\n\t * Drops the in-memory session so the next access re-resolves it from storage. Used when the primary\n\t * connection changed underneath a warm integration (e.g. after `setPrimaryConnection`/\n\t * `deleteConnection`). Unlike {@link reset}/{@link disconnect}, it deletes nothing from storage.\n\t */\n\tswitchConnection(): void {\n\t\t// A connection was deleted, or a different one became primary. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tif (this._session === undefined) return;\n\n\t\tconst wasConnected = this._session != null;\n\t\tthis._session = undefined;\n\t\tthis._onDidChange.fire();\n\t\tvoid this.refreshAfterSwitch(wasConnected);\n\t}\n\n\tprivate async refreshAfterSwitch(wasConnected: boolean): Promise<void> {\n\t\tconst session = await this.ensureSession({ createIfNeeded: false });\n\t\tif (session != null || !wasConnected) return;\n\n\t\tthis._onDidChange.fire();\n\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprivate skippedNonCloudReported = false;\n\t@debug()\n\tasync syncCloudConnection(state: 'connected' | 'disconnected', forceSync: boolean): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\t\t// Initially the condition on `this._session.cloud` has been added here: https://github.com/gitkraken/vscode-gitlens/commit/e95e70c430bd162924cc3bd5c1e8ab90e6293449#diff-4213141a45cccaab7aa2e40028b155a87eb913b07388485831403e60ce5555e4R237\n\t\t// I'm not sure about reasons, but it seems we want to replace it with the cloud session if it's connected.\n\t\t// Gradually we'll stop having non-cloud sessions.\n\t\t// However this is needed to be tested with PATs, e.g. with a GitLab PAT.\n\t\tif (this._session?.cloud === false && state !== 'connected') {\n\t\t\tif (this.id !== GitCloudHostIntegrationId.GitHub && !this.skippedNonCloudReported) {\n\t\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\t\tid: this.id,\n\t\t\t\t\treason: 'skip-non-cloud',\n\t\t\t\t\tcloud: false,\n\t\t\t\t});\n\t\t\t\tthis.skippedNonCloudReported = true;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tswitch (state) {\n\t\t\tcase 'connected': {\n\t\t\t\tconst oldSession = this._session;\n\t\t\t\tlet resyncing = false;\n\t\t\t\tif (forceSync) {\n\t\t\t\t\t// Reset our stored session so that we get a new one from the cloud\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor);\n\t\t\t\t\t// The stored token was just deleted. Not left to the token-changed check below, which needs\n\t\t\t\t\t// an `oldSession` — see `onStoredTokensReplaced`.\n\t\t\t\t\tthis.onStoredTokensReplaced();\n\t\t\t\t\t// Reset the session and clear our \"stay disconnected\" flag\n\t\t\t\t\tthis._session = undefined;\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t\t\t\tresyncing = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Only sync if we're not connected and not disabled and don't have pending errors\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis._session != null ||\n\t\t\t\t\t\tthis.requestExceptionCount > 0 ||\n\t\t\t\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tforceSync = true;\n\t\t\t\t}\n\n\t\t\t\t// sync option, rather than createIfNeeded, makes sure we don't call connectCloudIntegrations and open a gkdev window\n\t\t\t\t// if there was no session or some problem fetching/refreshing the existing session from the cloud api\n\t\t\t\tlet newSession: ProviderAuthenticationSession | undefined;\n\t\t\t\tlet refetchFailed = false;\n\t\t\t\ttry {\n\t\t\t\t\tnewSession = await this.ensureSession({ sync: forceSync });\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Not evidence the connection is gone (#5569). Also leaves `_session` as-is rather than\n\t\t\t\t\t// latching null, so the next access can re-resolve it.\n\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\t}\n\t\t\t\t\trefetchFailed = true;\n\t\t\t\t}\n\n\t\t\t\tif (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) {\n\t\t\t\t\tthis.resetRequestExceptionCount('all');\n\t\t\t\t}\n\n\t\t\t\t// The forced re-sync above deleted the cloud secret but kept the descriptor to avoid UI churn.\n\t\t\t\t// Drop it only when the replacement fetch came back definitively empty, so a connection whose\n\t\t\t\t// token is really gone is cleanly disconnected (#5497) while a healthy one survives a blip (#5569).\n\t\t\t\tif (resyncing && newSession == null && !refetchFailed) {\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor, { preserveConfigured: false });\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'disconnected':\n\t\t\t\tawait this.disconnect({ silent: true });\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprotected handleProviderException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions?: { scope?: ScopedLogger | undefined; silent?: boolean; connectionId?: string },\n\t): void {\n\t\tif (isCancellationError(ex)) return;\n\n\t\tif (options?.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\n\t\t// A per-connection (multi-account) read resolved its session through `resolveReadSession`'s\n\t\t// `connectionId` branch, which deliberately never touches the cached primary `_session`. So the\n\t\t// primary-session recovery below cannot apply to it: expiring `_session` would mark a session this\n\t\t// read never used, while the rejected connection kept its stored token and re-sent it on every\n\t\t// later read — a token the provider has already refused (expired scopes, a revoked grant, an\n\t\t// uninstalled app) is not self-healing, so the read failed identically until the user reconnected\n\t\t// by hand. Record the rejection against the connection instead; `resolveReadSession` consumes it\n\t\t// and forces the cloud `/refresh` on the next read of that connection. When there is nothing to\n\t\t// recover, fall through to the shared failure budget, which disconnects after\n\t\t// `requestExceptionLimit` and surfaces the reconnect prompt.\n\t\tif (ex instanceof AuthenticationError && options?.connectionId) {\n\t\t\tif (!this._rejectedTokens.recordRejection(options.connectionId)) {\n\t\t\t\tthis.trackRequestException(options);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (ex instanceof AuthenticationError && this._session?.cloud) {\n\t\t\tif (!this.hasSessionSyncRequests()) {\n\t\t\t\tthis.requestSessionSyncForUsecase(syncReqUsecase);\n\t\t\t\tthis._session = {\n\t\t\t\t\t...this._session,\n\t\t\t\t\texpiresAt: new Date(Date.now() - 1),\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tthis.trackRequestException(options);\n\t\t\t}\n\t\t} else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) {\n\t\t\tthis.trackRequestException(options);\n\t\t}\n\t}\n\n\tprivate missingExpirityReported = false;\n\t@gate()\n\tprotected async refreshSessionIfExpired(scope?: ScopedLogger): Promise<void> {\n\t\tif (this._session?.expiresAt != null && this._session.expiresAt < new Date()) {\n\t\t\t// The current session is expired, so get the latest from the cloud and refresh if needed\n\t\t\ttry {\n\t\t\t\tawait this.syncCloudConnection('connected', true);\n\t\t\t} catch (ex) {\n\t\t\t\tscope?.error(ex);\n\t\t\t}\n\t\t} else if (\n\t\t\tthis._session?.expiresAt == null &&\n\t\t\tthis.id !== GitCloudHostIntegrationId.GitHub &&\n\t\t\t!this.missingExpirityReported\n\t\t) {\n\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\tid: this.id,\n\t\t\t\treason: 'missing-expiry',\n\t\t\t\tcloud: this._session?.cloud,\n\t\t\t});\n\t\t\tthis.missingExpirityReported = true;\n\t\t}\n\t}\n\n\t@trace()\n\ttrackRequestException(options?: { silent?: boolean }): void {\n\t\tthis.requestExceptionCount++;\n\n\t\tif (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) {\n\t\t\tif (!options?.silent) {\n\t\t\t\tthis.ctx.hooks?.ui?.onDisconnectedAfterTooManyFailures?.(this.name);\n\t\t\t}\n\t\t\tvoid this.disconnect({ currentSessionOnly: true });\n\t\t}\n\t}\n\n\t@gate()\n\t@trace({ exit: true })\n\tasync isConnected(): Promise<boolean> {\n\t\treturn (await this.getSession('integrations')) != null;\n\t}\n\n\t@gate()\n\tprivate async ensureSession(\n\t\toptions:\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: boolean;\n\t\t\t\t\tforceNewSession?: boolean;\n\t\t\t\t\tsync?: never;\n\t\t\t\t\tsource?: Sources;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: never;\n\t\t\t\t\tforceNewSession?: never;\n\t\t\t\t\tsync: boolean;\n\t\t\t\t\tsource?: Sources;\n\t\t\t },\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst { createIfNeeded, forceNewSession, source, sync } = options;\n\t\tif (this._session != null) {\n\t\t\tif (this.isSessionForIntegrationHost(this._session)) return this._session;\n\n\t\t\tthis._session = null;\n\t\t}\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return undefined;\n\n\t\tif (createIfNeeded || sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t} else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet session: ProviderAuthenticationSession | undefined | null;\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\tsession = await authProvider.getSession(\n\t\t\t\tthis.authProviderDescriptor,\n\t\t\t\tsync\n\t\t\t\t\t? { sync: sync, source: source }\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tcreateIfNeeded: createIfNeeded,\n\t\t\t\t\t\t\tforceNewSession: forceNewSession,\n\t\t\t\t\t\t\tsource: source,\n\t\t\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (session?.expiresAt != null && session.expiresAt < new Date()) {\n\t\t\t\tsession = null;\n\t\t\t}\n\t\t\tif (session != null && !this.isSessionForIntegrationHost(session)) {\n\t\t\t\tsession = undefined;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\n\t\t\t// Only interactive paths can prompt for consent, so a sync failure must reach the rethrow below.\n\t\t\tif (!sync && ex instanceof Error && ex.message.includes('User did not consent')) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// On a forced re-sync, propagate so syncCloudConnection can tell a failure from a definitive empty\n\t\t\t// result (#5569); other callers keep swallowing to null so reads never throw.\n\t\t\tif (sync) {\n\t\t\t\t// Throwing skips the reset below, and a lingering count blocks the next non-forced sync.\n\t\t\t\tthis.smoothifyRequestExceptionCount();\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tsession = null;\n\t\t}\n\n\t\tif (session === undefined && !createIfNeeded && !sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t}\n\n\t\tthis._session = session ?? null;\n\t\tthis.smoothifyRequestExceptionCount();\n\n\t\tif (session != null) {\n\t\t\tawait this.ctx.storage.storeWorkspace(this.connectedKey, true);\n\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tthis._onDidChange.fire();\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'connected' });\n\t\t\t\t// Fired detached, so there is no caller left to catch anything: every implementor is async, and\n\t\t\t\t// a rejection would surface as a process-level unhandled rejection in the host. It is a\n\t\t\t\t// best-effort warm-up, so swallow the failure with a warning instead.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.providerOnConnect?.();\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t`Failed to run providerOnConnect for ${this.key}: ${ex instanceof Error ? ex.message : String(ex)}`,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t}\n\n\t\treturn session ?? undefined;\n\t}\n\n\tgetIgnoreSSLErrors(): boolean | 'force' {\n\t\treturn this.authenticationService.ignoreSSLErrors(this);\n\t}\n\n\tasync searchMyIssues(\n\t\tresource?: ResourceDescriptor,\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\t@trace()\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchMyIssuesResult(resources, cancellation, connectionId))?.value;\n\t}\n\n\t/**\n\t * Result-returning core of {@link searchMyIssues}. Recovers thrown errors into `{ error }` so callers\n\t * (e.g. the ProviderBackend account-wide issues read) can surface a per-provider warning instead of a\n\t * silent empty result. Returns the normalized {@link IssueShape} (there is no raw account-wide issue read).\n\t */\n\tasync searchMyIssuesResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IntegrationResult<IssueShape[] | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\tconst issues = await this.searchProviderMyIssues(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: issues, duration: performance.now() - start };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex), duration: performance.now() - start };\n\t\t}\n\t}\n\n\tprotected abstract searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined>;\n\n\t/**\n\t * Paging/truncation-aware variant of {@link searchProviderMyIssues}. The default wraps the normalized read\n\t * as a complete single page; providers with native cursors or fan-out metadata override it.\n\t */\n\tprotected async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\t_options?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\t// The default read has no assignee scoping to broaden, so `_options` is inert here; a provider whose\n\t\t// account-wide read is user-scoped (GitHub/GitLab/Azure) overrides this and honors `includeAllAssignees`.\n\t\tconst values = await this.searchProviderMyIssues(session, resources, cancellation);\n\t\tif (values == null) return undefined;\n\t\treturn { values: values, truncated: false };\n\t}\n\n\t/**\n\t * Result-returning, truncation-aware account-wide issue read. Recovers thrown errors into `{ error }` and\n\t * carries the `truncated` flag so the ProviderBackend facade can report an incomplete read honestly.\n\t */\n\tasync searchMyIssuesWithTruncationResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<IntegrationResult<AccountWideIssuesResult | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\ttry {\n\t\t\tconst result = await this.searchProviderMyIssuesWithTruncation(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: result };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\t@trace()\n\tasync getLinkedIssueOrPullRequest(\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\toptions?: { expiryOverride?: boolean | number; type?: IssueOrPullRequestType },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\n\t\tconst issueOrPR = this.ctx.cache.getIssueOrPullRequest(\n\t\t\tlink.key,\n\t\t\toptions?.type,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\t() => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderLinkedIssueOrPullRequest(\n\t\t\t\t\t\t\tthis._session!,\n\t\t\t\t\t\t\tresource,\n\t\t\t\t\t\t\tlink,\n\t\t\t\t\t\t\toptions?.type,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssueOrPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tthis.handleProviderException('getIssueOrPullRequest', ex, { scope: scope });\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\toptions,\n\t\t);\n\t\treturn issueOrPR;\n\t}\n\n\tprotected abstract getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\ttype: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined>;\n\n\tasync getIssue(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.getIssueResult(resource, id, options))?.value;\n\t}\n\n\tgetIssueResult(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\treturn this.getIssueResultCore(resource, id, options, session => this.getProviderIssue(session, resource, id));\n\t}\n\n\t@trace()\n\tprotected async getIssueResultCore(\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t\toptions: { connectionId?: string; expiryOverride?: boolean | number } | undefined,\n\t\tgetProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue | undefined>,\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\ttry {\n\t\t\tconst issue = await getCachedIssue({\n\t\t\t\tcache: this.ctx.cache,\n\t\t\t\tid: id,\n\t\t\t\tresource: resource,\n\t\t\t\tintegration: this,\n\t\t\t\tload: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst issue = await getProviderIssue(session);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssue');\n\t\t\t\t\t\treturn issue;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\t\tthis.handleProviderException('getIssue', toError(ex), {\n\t\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcacheOptions: {\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${this.getSessionFingerprint(session)}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn { value: issue };\n\t\t} catch (ex) {\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\tprotected abstract getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<Issue | undefined>;\n\n\tasync getCurrentAccount(options?: {\n\t\tavatarSize?: number;\n\t\tconnectionId?: string;\n\t\texpiryOverride?: boolean | number;\n\t}): Promise<Account | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride, ...opts } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst sessionFingerprint = this.getSessionFingerprint(session);\n\n\t\tconst currentAccount = await this.ctx.cache.getCurrentAccount(\n\t\t\tthis,\n\n\t\t\t(cacheable: any) => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst account = await this.getProviderCurrentAccount?.(session, opts);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getCurrentAccount');\n\t\t\t\t\t\treturn account;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (isCancellationError(ex)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.handleProviderException('getCurrentAccount', ex, {\n\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Invalidate the cache on error, except for auth errors\n\t\t\t\t\t\tif (!(ex instanceof AuthenticationError)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Re-throw to the caller\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\texpireOnError: false,\n\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${sessionFingerprint}`,\n\t\t\t},\n\t\t);\n\t\treturn currentAccount;\n\t}\n\n\tprotected getProviderCurrentAccount?(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined>;\n\n\t/**\n\t * Resolves the account for a specific session/token — including connections other than the current\n\t * primary (multi-account) — using this integration's provider API base URL and auth type. Returns\n\t * undefined when the provider doesn't support account lookup. Uncached (callers cache per connection).\n\t */\n\tgetProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account | undefined> {\n\t\treturn this.getProviderCurrentAccount?.(session) ?? Promise.resolve(undefined);\n\t}\n\n\t@trace()\n\tasync getPullRequest(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { expiryOverride?: boolean | number; throwOnError?: boolean },\n\t): Promise<PullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\tawait this.refreshSessionIfExpired(scope);\n\n\t\tconst pr = await this.ctx.cache.getPullRequest(\n\t\t\tid,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderPullRequest?.(this._session!, resource, id);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and the by-id bucket never expires a miss.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (options?.throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{ expiryOverride: options?.expiryOverride },\n\t\t);\n\t\treturn pr;\n\t}\n\n\tprotected getProviderPullRequest?(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<PullRequest | undefined>;\n}\n"],"mappings":"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"}
|
|
@@ -38,7 +38,24 @@ export type ProviderIssueSearchPage = {
|
|
|
38
38
|
hasMore: boolean;
|
|
39
39
|
page: number;
|
|
40
40
|
totalCount?: number;
|
|
41
|
+
/**
|
|
42
|
+
* The provider's declared result ceiling was reached, whether or not it reported how many matched. Lets the
|
|
43
|
+
* facade report the ceiling as a `provider-limit` omission for a provider that can prove more matched than it
|
|
44
|
+
* serves but can't say how many (Azure DevOps Server's WIQL), instead of a generic "was truncated".
|
|
45
|
+
*/
|
|
46
|
+
limitReached?: boolean;
|
|
41
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* One scope's answer from the issue count probe (`countIssuesResult`), positionally. The pull-request probe's slots
|
|
50
|
+
* are `ProviderPullRequestCount | Error`, with the same meaning for an `Error`.
|
|
51
|
+
*
|
|
52
|
+
* - a number is the match count;
|
|
53
|
+
* - `'exceeds-limit'` means more matched than the provider's declared result ceiling, and it can't say how many;
|
|
54
|
+
* - an `Error` refuses that ONE scope for its own reasons (the facade warns and drops only it, as it does for a
|
|
55
|
+
* scope it refuses itself);
|
|
56
|
+
* - `undefined` means the provider reported nothing for it — never zero matches.
|
|
57
|
+
*/
|
|
58
|
+
export type ProviderSearchCount = number | 'exceeds-limit' | Error | undefined;
|
|
42
59
|
/**
|
|
43
60
|
* Options for an issue tracker's PROJECT-scoped read (`IssuesIntegration.getIssuesForProject*`), which is the
|
|
44
61
|
* only issue surface a tracker has — its issues live under resource -> project, so there is no account-wide read
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PullRequestShape } from "../../../git/models/pullRequest.js";
|
|
2
|
+
import { CollectionMetadata } from "@gitkraken/provider-apis";
|
|
2
3
|
//#region ../plus/integrations/src/models/pullRequestReads.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* One provider page of the filtered pull-request search, before the facade turns it into a neutral
|
|
@@ -7,6 +8,14 @@ import { PullRequestShape } from "../../../git/models/pullRequest.js";
|
|
|
7
8
|
* `totalCount` is the provider-reported pre-ceiling facet count, not the number of rows that remain reachable
|
|
8
9
|
* after a provider limit. It stays optional because absence means "not reported", never zero. Together with
|
|
9
10
|
* `truncated` it lets the facade distinguish GitHub's quantified ceiling from another unusable continuation.
|
|
11
|
+
*
|
|
12
|
+
* `metadata` carries the facets whose rows are missing from the walk so far — Bitbucket Data Center reads every
|
|
13
|
+
* facet as its own request, so one can be refused (a repository the token can't see) without the others. It
|
|
14
|
+
* describes the whole walk rather than one page: a facet that failed on this page is reported here and retried on
|
|
15
|
+
* the next, and one whose retry failed too is dropped and reported on every later page, since its hole is still in
|
|
16
|
+
* the result. A retry that succeeds recovers the rows, so it drops out. The last page read is therefore the one to
|
|
17
|
+
* assess; the facade reports each failure as a warning with `fetchFailed`, and a page where EVERY facet failed
|
|
18
|
+
* throws instead.
|
|
10
19
|
*/
|
|
11
20
|
export type ProviderPullRequestSearchPage = {
|
|
12
21
|
values: PullRequestShape[];
|
|
@@ -15,6 +24,23 @@ export type ProviderPullRequestSearchPage = {
|
|
|
15
24
|
hasMore: boolean;
|
|
16
25
|
page: number;
|
|
17
26
|
totalCount?: number;
|
|
27
|
+
metadata?: CollectionMetadata;
|
|
28
|
+
/** See `ProviderIssueSearchPage.limitReached`. */
|
|
29
|
+
limitReached?: boolean;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One scope's pull-request count, as the provider reports it.
|
|
33
|
+
*
|
|
34
|
+
* `lowerBound` marks a count that stopped before the scope was exhausted: a provider with no count query
|
|
35
|
+
* (Bitbucket Data Center) counts by reading, within a budget, so past it `count` is how many matched in what was
|
|
36
|
+
* read — a floor, never the total.
|
|
37
|
+
*
|
|
38
|
+
* An `Error` in its place refuses that ONE scope for its own reasons (Azure DevOps Server refusing a scope it can't
|
|
39
|
+
* search): the facade warns and drops only it, as it does for a scope it refuses itself.
|
|
40
|
+
*/
|
|
41
|
+
export type ProviderPullRequestCount = {
|
|
42
|
+
count: number | undefined;
|
|
43
|
+
lowerBound?: boolean;
|
|
18
44
|
};
|
|
19
45
|
//#endregion
|
|
20
46
|
//# sourceMappingURL=pullRequestReads.d.ts.map
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Disposable } from "../../../../utils/disposable.js";
|
|
2
2
|
import { Provider } from "../../../../git/models/remoteProvider.js";
|
|
3
3
|
import { IssueOrPullRequest, IssueOrPullRequestType } from "../../../../git/models/issueOrPullRequest.js";
|
|
4
|
-
import { Issue } from "../../../../git/models/issue.js";
|
|
4
|
+
import { Issue, IssueSearchCriteria } from "../../../../git/models/issue.js";
|
|
5
5
|
import { PullRequest } from "../../../../git/models/pullRequest.js";
|
|
6
6
|
import { UnidentifiedAuthor } from "../../../../git/models/author.js";
|
|
7
7
|
import { DefaultBranch } from "../../../../git/models/defaultBranch.js";
|
|
8
8
|
import { RepositoryMetadata } from "../../../../git/models/repositoryMetadata.js";
|
|
9
9
|
import { TokenWithInfo } from "../../authentication/models.js";
|
|
10
|
+
import { ProviderIssueSearchPage } from "../../models/issueReads.js";
|
|
10
11
|
import { ProviderApiConfig } from "../apiConfig.js";
|
|
11
12
|
import { AzureProjectDescriptor, AzureWorkItemStateCategory } from "./models.js";
|
|
12
13
|
import { IntegrationServiceContext } from "../../context.js";
|
|
@@ -16,6 +17,12 @@ export declare class AzureDevOpsApi implements Disposable {
|
|
|
16
17
|
private readonly _disposable;
|
|
17
18
|
private _workItemStates;
|
|
18
19
|
private readonly _forkRepositoryUrls;
|
|
20
|
+
/**
|
|
21
|
+
* The ordered ids each filtered work-item search's first page queried, paged through by its continuations. Kept
|
|
22
|
+
* alive while a pagination keeps reading them (see `workItemSearchSnapshotTtl`). Every first page adds one, so
|
|
23
|
+
* the capacity leaves room for repeated first pages before an idle pagination's snapshot is the one evicted.
|
|
24
|
+
*/
|
|
25
|
+
private readonly _workItemSearches;
|
|
19
26
|
constructor(config: ProviderApiConfig);
|
|
20
27
|
dispose(): void;
|
|
21
28
|
private resetCaches;
|
|
@@ -35,6 +42,19 @@ export declare class AzureDevOpsApi implements Disposable {
|
|
|
35
42
|
getAccountForCommit(provider: Provider, token: TokenWithInfo, owner: string, repo: string, rev: string, baseUrl: string, _options?: {
|
|
36
43
|
avatarSize?: number;
|
|
37
44
|
}): Promise<UnidentifiedAuthor | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* The collection an Azure DevOps Server address names, or `undefined` when it names the installation.
|
|
47
|
+
*
|
|
48
|
+
* Below a collection, `connectionData` describes that collection: its `instanceId` is the collection's id, and its
|
|
49
|
+
* `webApplicationRelativeDirectory` ends in the collection's path segment (`DefaultCollection/`); it is empty or
|
|
50
|
+
* absent at the server level. As measured against Azure DevOps Server 2020. The directory may also carry the
|
|
51
|
+
* installation's own virtual directory before the collection (`tfs/DefaultCollection/`), so only its LAST segment
|
|
52
|
+
* names the collection, and only when it is the last segment of the address asked about.
|
|
53
|
+
*/
|
|
54
|
+
getAddressedCollection(provider: Provider, token: TokenWithInfo, baseUrl: string): Promise<{
|
|
55
|
+
id: string;
|
|
56
|
+
name: string;
|
|
57
|
+
} | undefined>;
|
|
38
58
|
getCurrentUserOnServer(provider: Provider, token: TokenWithInfo, baseUrl: string): Promise<{
|
|
39
59
|
id: string;
|
|
40
60
|
name?: string;
|
|
@@ -52,6 +72,51 @@ export declare class AzureDevOpsApi implements Disposable {
|
|
|
52
72
|
getDefaultBranch(provider: Provider, token: TokenWithInfo, owner: string, repo: string, options: {
|
|
53
73
|
baseUrl: string;
|
|
54
74
|
}, cancellation?: AbortSignal): Promise<DefaultBranch | undefined>;
|
|
75
|
+
/**
|
|
76
|
+
* One page of a filtered work-item search over a collection: a WIQL query for the ordered ids, then one detail
|
|
77
|
+
* read for the page's slice of them.
|
|
78
|
+
*
|
|
79
|
+
* The ids a first page queried are kept as the snapshot its continuations page through, for as long as the
|
|
80
|
+
* pagination keeps reading it, so it reads a consistent set rather than re-running a query whose rows move under
|
|
81
|
+
* an `updated` order. Each first page queries its own snapshot, so paginations of one query never share one, and
|
|
82
|
+
* a continuation whose snapshot is gone is refused (see {@link AzureWorkItemSearchCursor}).
|
|
83
|
+
*
|
|
84
|
+
* The query asks for one id more than {@link azureWorkItemSearchResultLimit}, bounded by `$top`: the extra id is
|
|
85
|
+
* what tells "exactly the limit" from "past it". Past it, the page is `truncated` with `limitReached` and no total,
|
|
86
|
+
* since the true count is unknowable here. A server that refuses the match set even so fails the page with a
|
|
87
|
+
* "narrow the search" error: no bounded query could serve that order's first window.
|
|
88
|
+
*
|
|
89
|
+
* `resolveProject` maps each result's project name to its descriptor. A work item in a project it doesn't know
|
|
90
|
+
* fails the page rather than being dropped or mis-attributed, since either would leave the page disagreeing with
|
|
91
|
+
* the count of the same query; the caller refreshes its project discovery before a search so that means a
|
|
92
|
+
* project created during the pagination.
|
|
93
|
+
*/
|
|
94
|
+
searchWorkItemsPage(provider: Provider, token: TokenWithInfo, collection: string, options: {
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
criteria?: IssueSearchCriteria;
|
|
97
|
+
projectNames?: readonly string[];
|
|
98
|
+
resolveProject: (name: string) => AzureProjectDescriptor | undefined;
|
|
99
|
+
cursor?: string;
|
|
100
|
+
pageSize?: number;
|
|
101
|
+
}, cancellation?: AbortSignal): Promise<ProviderIssueSearchPage>;
|
|
102
|
+
/**
|
|
103
|
+
* How many work items the same WIQL {@link searchWorkItemsPage} would page through match, reading no details.
|
|
104
|
+
*
|
|
105
|
+
* Asks for one id more than the result ceiling, so the count is exact up to it and `'exceeds-limit'` past it —
|
|
106
|
+
* never the ceiling itself, which would understate the match set.
|
|
107
|
+
*/
|
|
108
|
+
countWorkItems(provider: Provider, token: TokenWithInfo, collection: string, options: {
|
|
109
|
+
baseUrl: string;
|
|
110
|
+
criteria?: IssueSearchCriteria;
|
|
111
|
+
projectNames?: readonly string[];
|
|
112
|
+
}, cancellation?: AbortSignal): Promise<number | 'exceeds-limit'>;
|
|
113
|
+
/**
|
|
114
|
+
* The ids a collection-level WIQL query matches, in its order.
|
|
115
|
+
*
|
|
116
|
+
* `api-version=5.0` is the oldest version every Azure DevOps Server release this search supports (2019 and later)
|
|
117
|
+
* answers; `timePrecision=true` is what lets the criteria's dates compare as instants — see `toWiqlDate`.
|
|
118
|
+
*/
|
|
119
|
+
private queryWorkItemIds;
|
|
55
120
|
private getRepository;
|
|
56
121
|
private toPullRequest;
|
|
57
122
|
/**
|