openmeld 0.3.135 → 0.3.137
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-me-membership-9xY_Cbqw.js → add-me-membership-CZUHkjnP.js} +2 -2
- package/dist/{add-me-membership-9xY_Cbqw.js.map → add-me-membership-CZUHkjnP.js.map} +1 -1
- package/dist/{api-client-foundation--Nr2FS4d.js → api-client-foundation-BPmXnsr9.js} +4 -7
- package/dist/api-client-foundation-BPmXnsr9.js.map +1 -0
- package/dist/{auth-session-CNmIonA7.js → auth-session-B06nfwnD.js} +224 -88
- package/dist/auth-session-B06nfwnD.js.map +1 -0
- package/dist/{base-url-BtqW4aLS.js → base-url-CY_j4bLH.js} +13 -6
- package/dist/base-url-CY_j4bLH.js.map +1 -0
- package/dist/{command-wO4TFiYh.js → command-CU_3raCS.js} +783 -210
- package/dist/{command-wO4TFiYh.js.map → command-CU_3raCS.js.map} +1 -1
- package/dist/{daemon-runtime-lease-D82uDZ5s.js → daemon-runtime-lease-6DIprlJy.js} +2 -2
- package/dist/{daemon-runtime-lease-D82uDZ5s.js.map → daemon-runtime-lease-6DIprlJy.js.map} +1 -1
- package/dist/{dist-DoFr77hZ.js → dist-NJH-sDDB.js} +177 -42
- package/dist/dist-NJH-sDDB.js.map +1 -0
- package/dist/openmeld-dev.js +1 -1
- package/dist/openmeld.js +408 -222
- package/dist/openmeld.js.map +1 -1
- package/dist/{runtime-transport-Ciax1_jb.js → runtime-transport-DYzU1lHC.js} +78 -14
- package/dist/runtime-transport-DYzU1lHC.js.map +1 -0
- package/package.json +2 -2
- package/skills/README.md +5 -2
- package/skills/openmeld-cli/SKILL.md +109 -1151
- package/skills/openmeld-cli/playbooks/space-ops.md +34 -16
- package/skills/openmeld-cli/playbooks/space-reads.md +123 -0
- package/skills/openmeld-cli/references/commands.md +14 -9
- package/skills/openmeld-cli/references/runtime-resolution.md +21 -2
- package/dist/api-client-foundation--Nr2FS4d.js.map +0 -1
- package/dist/auth-session-CNmIonA7.js.map +0 -1
- package/dist/base-url-BtqW4aLS.js.map +0 -1
- package/dist/dist-DoFr77hZ.js.map +0 -1
- package/dist/runtime-transport-Ciax1_jb.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { r as runSpaceAddMembers } from "./command-
|
|
3
|
+
import { r as runSpaceAddMembers } from "./command-CU_3raCS.js";
|
|
4
4
|
//#region src/space/add-me-membership.ts
|
|
5
5
|
async function runSpaceAddMeMembership(input) {
|
|
6
6
|
return await runSpaceAddMembers(input);
|
|
@@ -8,4 +8,4 @@ async function runSpaceAddMeMembership(input) {
|
|
|
8
8
|
//#endregion
|
|
9
9
|
export { runSpaceAddMeMembership };
|
|
10
10
|
|
|
11
|
-
//# sourceMappingURL=add-me-membership-
|
|
11
|
+
//# sourceMappingURL=add-me-membership-CZUHkjnP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-me-membership-
|
|
1
|
+
{"version":3,"file":"add-me-membership-CZUHkjnP.js","names":[],"sources":["../src/space/add-me-membership.ts"],"sourcesContent":["import { runSpaceAddMembers } from \"./command\";\nimport type { SpaceCommandNavigationStatus } from \"./navigation-status\";\n\nexport type SpaceAddMeMembershipInput = Parameters<\n typeof runSpaceAddMembers\n>[0];\nexport type SpaceAddMeMembershipRunner = (\n input: SpaceAddMeMembershipInput\n) => Promise<SpaceCommandNavigationStatus>;\n\nexport async function runSpaceAddMeMembership(\n input: SpaceAddMeMembershipInput\n): Promise<SpaceCommandNavigationStatus> {\n return await runSpaceAddMembers(input);\n}\n"],"mappings":";;;;AAUA,eAAsB,wBACpB,OACuC;CACvC,OAAO,MAAM,mBAAmB,KAAK;AACvC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { D as normalizeBaseUrl,
|
|
4
|
-
import { I as OPENMELD_HEADER_REQUESTED_ORGANIZATION_ID,
|
|
5
|
-
import { M as MAX_SPACE_REQUEST_TIMEOUT_MS, P as MIN_SPACE_REQUEST_TIMEOUT_MS, b as resolveAuthSessionExpiryEligibility, k as DEFAULT_SPACE_REQUEST_TIMEOUT_MS, l as getAuthSessionState } from "./auth-session-
|
|
3
|
+
import { D as normalizeBaseUrl, Sn as __exportAll, W as isLoopbackHostname } from "./base-url-CY_j4bLH.js";
|
|
4
|
+
import { I as OPENMELD_HEADER_REQUESTED_ORGANIZATION_ID, h as createOpenMeldApiClient, z as buildOpenMeldRequestContextHeaders } from "./dist-NJH-sDDB.js";
|
|
5
|
+
import { M as MAX_SPACE_REQUEST_TIMEOUT_MS, P as MIN_SPACE_REQUEST_TIMEOUT_MS, b as resolveAuthSessionExpiryEligibility, k as DEFAULT_SPACE_REQUEST_TIMEOUT_MS, l as getAuthSessionState } from "./auth-session-B06nfwnD.js";
|
|
6
6
|
import crypto from "node:crypto";
|
|
7
7
|
//#region src/config/auth-request-compatibility.ts
|
|
8
8
|
function resolveAuthRequestCompatibility(input) {
|
|
@@ -86,9 +86,6 @@ function parseUrlOrNull(value) {
|
|
|
86
86
|
return null;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
function isLoopbackHostname(hostname) {
|
|
90
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
91
|
-
}
|
|
92
89
|
function isSecureTransportProtocol(protocol) {
|
|
93
90
|
return protocol === "https:" || protocol === "wss:";
|
|
94
91
|
}
|
|
@@ -440,4 +437,4 @@ function createCliApiClientFoundation(baseUrl, options = {}) {
|
|
|
440
437
|
//#endregion
|
|
441
438
|
export { AuthRequestCredentialError as a, buildOpenMeldRequestContextHeadersWithAuth as c, buildOpenMeldRequestContextFromProfile as d, buildOpenMeldRequestContextFromProfileIdentity as f, resolveSpaceRequestTimeoutMsForDiagnostics as i, buildRequestAuthHeaders as l, resolveAuthRequestCompatibility as m, createCliApiClientFoundation as n, buildOpenMeldProfileIdentityRequestHeadersWithAuth as o, deriveAuthBaseUrlFromGatewayUrl as p, fetchSpaceRequestWithTimeout as r, buildOpenMeldProfileRequestHeadersWithAuth as s, api_client_foundation_exports as t, canAttachAuthTokenToRequest as u };
|
|
442
439
|
|
|
443
|
-
//# sourceMappingURL=api-client-foundation
|
|
440
|
+
//# sourceMappingURL=api-client-foundation-BPmXnsr9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client-foundation-BPmXnsr9.js","names":["resolveSpaceRequestTimeoutMsForDiagnostics","resolveSpaceRequestTimeoutMsForCliDiagnostics"],"sources":["../src/config/auth-request-compatibility.ts","../src/config/openmeld-request-context.ts","../src/config/request-headers.ts","../src/space/request-timeout.ts","../src/space/fetch-with-timeout.ts","../src/space/api-client-foundation.ts"],"sourcesContent":["import { normalizeBaseUrl } from \"../protocol/urls\";\nimport type { StoredAuthSession } from \"./auth-session\";\nimport { isLoopbackHostname } from \"./loopback-hostname\";\n\nexport type AuthRequestCompatibilityReason =\n | \"invalid_request_url\"\n | \"invalid_auth_url\"\n | \"insecure_transport\"\n | \"host_mismatch\"\n | \"loopback\"\n | \"same_host\"\n | \"gateway_sibling\";\n\nexport type AuthRequestCompatibility = {\n compatible: boolean;\n reason: AuthRequestCompatibilityReason;\n requestHost: string | null;\n authHost: string | null;\n expectedGatewayHost: string | null;\n};\n\nexport function resolveAuthRequestCompatibility(input: {\n session: StoredAuthSession;\n requestUrl: string;\n}): AuthRequestCompatibility {\n const requestUrl = parseUrlOrNull(input.requestUrl);\n if (!requestUrl) {\n return {\n compatible: false,\n reason: \"invalid_request_url\",\n requestHost: null,\n authHost: null,\n expectedGatewayHost: null,\n };\n }\n\n const authBaseUrl = parseUrlOrNull(input.session.authBaseUrl);\n if (!authBaseUrl) {\n return {\n compatible: false,\n reason: \"invalid_auth_url\",\n requestHost: requestUrl.host,\n authHost: null,\n expectedGatewayHost: null,\n };\n }\n\n const expectedGatewayHost = deriveGatewayHost(authBaseUrl);\n const requestHost = requestUrl.host;\n const authHost = authBaseUrl.host;\n\n if (\n isLoopbackHostname(requestUrl.hostname) &&\n isLoopbackHostname(authBaseUrl.hostname)\n ) {\n return {\n compatible: true,\n reason: \"loopback\",\n requestHost,\n authHost,\n expectedGatewayHost,\n };\n }\n\n if (!isSecureTransportProtocol(requestUrl.protocol)) {\n return {\n compatible: false,\n reason: \"insecure_transport\",\n requestHost,\n authHost,\n expectedGatewayHost,\n };\n }\n\n if (requestHost === authHost) {\n return {\n compatible: true,\n reason: \"same_host\",\n requestHost,\n authHost,\n expectedGatewayHost,\n };\n }\n\n if (expectedGatewayHost && requestHost === expectedGatewayHost) {\n return {\n compatible: true,\n reason: \"gateway_sibling\",\n requestHost,\n authHost,\n expectedGatewayHost,\n };\n }\n\n return {\n compatible: false,\n reason: \"host_mismatch\",\n requestHost,\n authHost,\n expectedGatewayHost,\n };\n}\n\nexport function deriveAuthBaseUrlFromGatewayUrl(\n gatewayUrl: string\n): string | null {\n const parsed = parseUrlOrNull(gatewayUrl);\n if (!parsed) {\n return null;\n }\n if (!isHttpTransportProtocol(parsed.protocol)) {\n return null;\n }\n if (!parsed.hostname.startsWith(\"gateway.\")) {\n return null;\n }\n\n const authUrl = new URL(parsed.toString());\n authUrl.hostname = `auth.${parsed.hostname.slice(\"gateway.\".length)}`;\n authUrl.pathname = \"/\";\n authUrl.search = \"\";\n authUrl.hash = \"\";\n return normalizeBaseUrl(authUrl.toString());\n}\n\nfunction deriveGatewayHost(authBaseUrl: URL): string | null {\n if (!authBaseUrl.hostname.startsWith(\"auth.\")) {\n return null;\n }\n\n const gatewayUrl = new URL(authBaseUrl.toString());\n gatewayUrl.hostname = `gateway.${authBaseUrl.hostname.slice(\"auth.\".length)}`;\n return gatewayUrl.host;\n}\n\nfunction parseUrlOrNull(value: string): URL | null {\n try {\n return new URL(value);\n } catch {\n return null;\n }\n}\n\nfunction isSecureTransportProtocol(protocol: string): boolean {\n return protocol === \"https:\" || protocol === \"wss:\";\n}\n\nfunction isHttpTransportProtocol(protocol: string): boolean {\n return protocol === \"http:\" || protocol === \"https:\";\n}\n","import crypto from \"node:crypto\";\nimport type { OpenMeldRequestContext } from \"@openmeld/schemas/types\";\nimport type { OpenMeldProfile } from \"./openmeld-profiles-api\";\n\nexport type OpenMeldRequestContextProfileIdentity = {\n openMeldProfileId: string;\n profileKind?: \"human\" | \"agent\" | \"system\";\n kind?: \"human\" | \"agent\";\n profileHandle?: string;\n profileName: string;\n ownerUserId?: string;\n ownerName?: string;\n agentControllerConversationId?: string;\n};\n\nexport function buildOpenMeldRequestContextFromProfile(input: {\n openMeldProfile: OpenMeldProfile;\n requestId?: string;\n agentControllerConversationId?: string;\n}): OpenMeldRequestContext {\n const requestId =\n normalizeOptionalText(input.requestId) ?? crypto.randomUUID();\n const agentControllerConversationId = normalizeOptionalText(\n input.agentControllerConversationId\n );\n const ownerName = normalizeOptionalText(input.openMeldProfile.ownerName);\n\n return {\n actorContext: {\n requestId,\n openMeldProfileId: input.openMeldProfile.openMeldProfileId,\n profileKind: input.openMeldProfile.profileKind,\n profileHandle: input.openMeldProfile.profileHandle,\n profileName: input.openMeldProfile.profileName,\n ownerUserId: input.openMeldProfile.ownerUserId,\n ...(ownerName ? { ownerName } : {}),\n },\n ...(agentControllerConversationId ? { agentControllerConversationId } : {}),\n };\n}\n\nexport function buildOpenMeldRequestContextFromProfileIdentity(input: {\n profileIdentity: OpenMeldRequestContextProfileIdentity;\n requestId?: string;\n}): OpenMeldRequestContext {\n const requestId =\n normalizeOptionalText(input.requestId) ?? crypto.randomUUID();\n const profileHandle = normalizeOptionalText(\n input.profileIdentity.profileHandle\n );\n const ownerUserId = normalizeOptionalText(input.profileIdentity.ownerUserId);\n const ownerName = normalizeOptionalText(input.profileIdentity.ownerName);\n const agentControllerConversationId = normalizeOptionalText(\n input.profileIdentity.agentControllerConversationId\n );\n const profileKind =\n input.profileIdentity.profileKind ??\n (input.profileIdentity.kind === \"human\" ||\n input.profileIdentity.kind === \"agent\"\n ? input.profileIdentity.kind\n : \"system\");\n\n return {\n actorContext: {\n requestId,\n openMeldProfileId: input.profileIdentity.openMeldProfileId,\n profileKind,\n ...(profileHandle ? { profileHandle } : {}),\n profileName: input.profileIdentity.profileName,\n ...(ownerUserId ? { ownerUserId } : {}),\n ...(ownerName ? { ownerName } : {}),\n },\n ...(agentControllerConversationId ? { agentControllerConversationId } : {}),\n };\n}\n\nfunction normalizeOptionalText(value: unknown): string | null {\n const normalized = String(value ?? \"\").trim();\n return normalized.length > 0 ? normalized : null;\n}\n","import {\n buildOpenMeldRequestContextHeaders,\n type OpenMeldRequestContextHeadersInput,\n} from \"@openmeld/gateway-contract\";\nimport type { OpenMeldRequestContext } from \"@openmeld/schemas/types\";\nimport {\n type AuthRequestCompatibilityReason,\n resolveAuthRequestCompatibility,\n} from \"./auth-request-compatibility\";\nimport {\n type AuthSessionStateReasonCode,\n getAuthSessionState,\n type StoredAuthSession,\n} from \"./auth-session\";\nimport { resolveAuthSessionExpiryEligibility } from \"./auth-session-expiry\";\nimport type { OpenMeldProfile } from \"./openmeld-profiles-api\";\nimport {\n buildOpenMeldRequestContextFromProfile,\n buildOpenMeldRequestContextFromProfileIdentity,\n type OpenMeldRequestContextProfileIdentity,\n} from \"./openmeld-request-context\";\n\nconst DEFAULT_TOKEN_TYPE = \"Bearer\";\nconst AUTHORIZATION_HEADER = \"Authorization\";\n\ntype RequestAuthHeadersInput = {\n authSession?: StoredAuthSession | null;\n requestUrl: string;\n};\n\ntype OpenMeldRequestContextHeadersWithAuthInput =\n OpenMeldRequestContextHeadersInput & {\n authSession?: StoredAuthSession | null;\n requestUrl: string;\n };\n\nexport type AuthTokenAttachment =\n | {\n status: \"attachable\";\n token: string;\n tokenType: string;\n }\n | {\n status: \"dropped\";\n reason:\n | \"empty_token\"\n | \"session_expired\"\n | \"session_expiry_invalid\"\n | AuthRequestCompatibilityReason;\n requestHost: string | null;\n authHost: string | null;\n expectedGatewayHost: string | null;\n };\n\nexport type DroppedAuthTokenAttachment = Extract<\n AuthTokenAttachment,\n { status: \"dropped\" }\n>;\n\nexport type AuthRequestCredentialFailureReason =\n | DroppedAuthTokenAttachment[\"reason\"]\n | AuthSessionStateReasonCode\n | \"session_invalid\"\n | \"session_revoked\";\n\nexport class AuthRequestCredentialError extends Error {\n readonly authHost: string | null;\n readonly expectedGatewayHost: string | null;\n readonly reason: AuthRequestCredentialFailureReason;\n readonly requestHost: string | null;\n readonly droppedAttachment?: DroppedAuthTokenAttachment;\n\n constructor(input: {\n authHost: string | null;\n expectedGatewayHost: string | null;\n reason: AuthRequestCredentialFailureReason;\n requestHost: string | null;\n droppedAttachment?: DroppedAuthTokenAttachment;\n }) {\n super(\n `OpenMeld refused to send a request with unusable stored credentials (${input.reason}).`\n );\n this.name = \"AuthRequestCredentialError\";\n this.authHost = input.authHost;\n this.expectedGatewayHost = input.expectedGatewayHost;\n this.reason = input.reason;\n this.requestHost = input.requestHost;\n this.droppedAttachment = input.droppedAttachment;\n }\n}\n\nexport async function buildRequestAuthHeaders(\n input: RequestAuthHeadersInput\n): Promise<Record<string, string>> {\n const headers: Record<string, string> = {};\n await attachAuthHeader(headers, input);\n return headers;\n}\n\nexport async function buildOpenMeldRequestContextHeadersWithAuth(\n input: OpenMeldRequestContextHeadersWithAuthInput\n): Promise<Record<string, string>> {\n const headers = buildOpenMeldRequestContextHeaders(input);\n await attachAuthHeader(headers, input);\n return headers;\n}\n\nexport async function buildOpenMeldProfileRequestHeadersWithAuth(input: {\n openMeldProfile: OpenMeldProfile;\n requestUrl: string;\n authSession?: StoredAuthSession | null;\n agentControllerConversationId?: string;\n spacePassword?: string;\n}): Promise<Record<string, string>> {\n return await buildOpenMeldRequestContextHeadersWithAuth({\n requestContext: buildOpenMeldRequestContextFromProfile({\n openMeldProfile: input.openMeldProfile,\n agentControllerConversationId: input.agentControllerConversationId,\n }),\n requestUrl: input.requestUrl,\n ...(input.authSession === undefined\n ? {}\n : { authSession: input.authSession }),\n ...(input.spacePassword === undefined\n ? {}\n : { spacePassword: input.spacePassword }),\n });\n}\n\nexport async function buildOpenMeldProfileIdentityRequestHeadersWithAuth(input: {\n profileIdentity: OpenMeldRequestContextProfileIdentity;\n requestUrl: string;\n authSession?: StoredAuthSession | null;\n spacePassword?: string;\n}): Promise<Record<string, string>> {\n return await buildOpenMeldRequestContextHeadersWithAuth({\n requestContext: buildOpenMeldRequestContextFromProfileIdentity({\n profileIdentity: input.profileIdentity,\n }),\n requestUrl: input.requestUrl,\n ...(input.authSession === undefined\n ? {}\n : { authSession: input.authSession }),\n ...(input.spacePassword === undefined\n ? {}\n : { spacePassword: input.spacePassword }),\n });\n}\n\nexport function withSpacePassword(\n requestContext: OpenMeldRequestContext,\n spacePassword?: string\n): OpenMeldRequestContextHeadersInput {\n return {\n requestContext,\n ...(spacePassword === undefined ? {} : { spacePassword }),\n };\n}\n\n// One decision for \"can this stored session authenticate this request\", with\n// the drop reason preserved. Header building and the daemon stream connect\n// gate must share this so they cannot drift apart.\nexport function resolveAuthTokenAttachment(input: {\n session: StoredAuthSession;\n requestUrl: string;\n}): AuthTokenAttachment {\n const compatibility = resolveAuthRequestCompatibility({\n session: input.session,\n requestUrl: input.requestUrl,\n });\n const token = normalizeAccessToken(input.session);\n if (!token) {\n return {\n status: \"dropped\",\n reason: \"empty_token\",\n requestHost: compatibility.requestHost,\n authHost: compatibility.authHost,\n expectedGatewayHost: compatibility.expectedGatewayHost,\n };\n }\n if (!compatibility.compatible) {\n return {\n status: \"dropped\",\n reason: compatibility.reason,\n requestHost: compatibility.requestHost,\n authHost: compatibility.authHost,\n expectedGatewayHost: compatibility.expectedGatewayHost,\n };\n }\n const expiryReason = resolveSessionExpiryReason(input.session);\n if (expiryReason) {\n return {\n status: \"dropped\",\n reason: expiryReason,\n requestHost: compatibility.requestHost,\n authHost: compatibility.authHost,\n expectedGatewayHost: compatibility.expectedGatewayHost,\n };\n }\n return {\n status: \"attachable\",\n token,\n tokenType: normalizeTokenType(input.session.tokenType),\n };\n}\n\nexport function requireAuthTokenAttachment(input: {\n session: StoredAuthSession;\n requestUrl: string;\n}): Extract<AuthTokenAttachment, { status: \"attachable\" }> {\n const attachment = resolveAuthTokenAttachment(input);\n if (attachment.status === \"dropped\") {\n throw createAuthRequestCredentialError(attachment);\n }\n return attachment;\n}\n\nasync function attachAuthHeader(\n headers: Record<string, string>,\n input: RequestAuthHeadersInput\n): Promise<void> {\n const session = await resolveAuthSessionForHeaders(input);\n if (session === null) {\n // No stored credentials exist, so there is nothing to strip: the caller\n // is knowingly sending an unauthenticated request.\n return;\n }\n\n let attachment: Extract<AuthTokenAttachment, { status: \"attachable\" }>;\n try {\n attachment = requireAuthTokenAttachment({\n session,\n requestUrl: input.requestUrl,\n });\n } catch (error) {\n if (error instanceof AuthRequestCredentialError) {\n logRejectedAuthRequestCredential(error);\n }\n throw error;\n }\n headers[AUTHORIZATION_HEADER] = `${attachment.tokenType} ${attachment.token}`;\n}\n\nfunction logRejectedAuthRequestCredential(\n error: AuthRequestCredentialError\n): void {\n console.error(\"[openmeld.auth.request_credential_rejected]\", {\n authHost: error.authHost,\n expectedGatewayHost: error.expectedGatewayHost,\n reason: error.reason,\n requestHost: error.requestHost,\n });\n}\n\nasync function resolveAuthSessionForHeaders(\n input: RequestAuthHeadersInput\n): Promise<StoredAuthSession | null> {\n if (input.authSession !== undefined) {\n return input.authSession;\n }\n\n const sessionState = await getAuthSessionState({ refreshExpired: true });\n if (sessionState.state === \"missing\") {\n return null;\n }\n if (sessionState.state !== \"valid\" || sessionState.session === null) {\n const error = new AuthRequestCredentialError({\n authHost: null,\n expectedGatewayHost: null,\n reason:\n sessionState.reasonCode ??\n (sessionState.state === \"revoked\"\n ? \"session_revoked\"\n : \"session_invalid\"),\n requestHost: parseRequestHost(input.requestUrl),\n });\n logRejectedAuthRequestCredential(error);\n throw error;\n }\n\n return sessionState.session;\n}\n\nfunction createAuthRequestCredentialError(\n attachment: DroppedAuthTokenAttachment\n): AuthRequestCredentialError {\n return new AuthRequestCredentialError({\n authHost: attachment.authHost,\n expectedGatewayHost: attachment.expectedGatewayHost,\n reason: attachment.reason,\n requestHost: attachment.requestHost,\n droppedAttachment: attachment,\n });\n}\n\nfunction parseRequestHost(requestUrl: string): string | null {\n try {\n return new URL(requestUrl).host || null;\n } catch {\n return null;\n }\n}\n\nfunction normalizeAccessToken(session: StoredAuthSession): string {\n return String(session.accessToken ?? \"\").trim();\n}\n\nfunction normalizeTokenType(tokenType: string | undefined): string {\n const normalized = String(tokenType ?? \"\").trim();\n return normalized.length > 0 ? normalized : DEFAULT_TOKEN_TYPE;\n}\n\nfunction resolveSessionExpiryReason(\n session: StoredAuthSession\n): \"session_expired\" | \"session_expiry_invalid\" | null {\n const eligibility = resolveAuthSessionExpiryEligibility(session.expiresAt);\n if (eligibility.status === \"invalid\") {\n return \"session_expiry_invalid\";\n }\n return eligibility.status === \"expired\" ? \"session_expired\" : null;\n}\n\nexport function canAttachAuthTokenToRequest(\n session: StoredAuthSession,\n requestUrl: string\n): boolean {\n return (\n resolveAuthTokenAttachment({\n session,\n requestUrl,\n }).status === \"attachable\"\n );\n}\n","import {\n DEFAULT_SPACE_CONVERSATION_VALIDATE_TIMEOUT_MS,\n DEFAULT_SPACE_REQUEST_TIMEOUT_MS,\n MAX_SPACE_REQUEST_TIMEOUT_MS,\n MIN_SPACE_REQUEST_TIMEOUT_MS,\n} from \"../strategy-constants\";\n\nconst SPACE_REQUEST_TIMEOUT_ENV_KEY = \"OPENMELD_SPACE_REQUEST_TIMEOUT_MS\";\n\nexport function resolveSpaceRequestTimeoutMs(override?: number): number {\n if (isFinitePositiveNumber(override)) {\n return clampSpaceRequestTimeoutMs(override);\n }\n\n const fromEnv = Number.parseInt(\n String(process.env[SPACE_REQUEST_TIMEOUT_ENV_KEY] ?? \"\"),\n 10\n );\n if (isFinitePositiveNumber(fromEnv)) {\n return clampSpaceRequestTimeoutMs(fromEnv);\n }\n\n return DEFAULT_SPACE_REQUEST_TIMEOUT_MS;\n}\n\nexport function resolveSpaceRequestTimeoutMsForDiagnostics(): number {\n return resolveSpaceRequestTimeoutMs(undefined);\n}\n\nexport function resolveSpaceConversationValidateTimeoutMs(): number {\n return Math.max(\n resolveSpaceRequestTimeoutMs(undefined),\n DEFAULT_SPACE_CONVERSATION_VALIDATE_TIMEOUT_MS\n );\n}\n\nfunction clampSpaceRequestTimeoutMs(value: number): number {\n const normalized = Math.trunc(value);\n return Math.max(\n MIN_SPACE_REQUEST_TIMEOUT_MS,\n Math.min(MAX_SPACE_REQUEST_TIMEOUT_MS, normalized)\n );\n}\n\nfunction isFinitePositiveNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n","import {\n resolveSpaceRequestTimeoutMs,\n resolveSpaceRequestTimeoutMsForDiagnostics as resolveSpaceRequestTimeoutMsForCliDiagnostics,\n} from \"./request-timeout\";\n\nexport async function fetchSpaceRequestWithTimeout(input: {\n url: string;\n operation: string;\n init?: RequestInit;\n timeoutMs?: number;\n}): Promise<Response> {\n const timeoutMs = resolveSpaceRequestTimeoutMs(input.timeoutMs);\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const composed = composeAbortSignals(input.init?.signal, timeoutSignal);\n\n try {\n try {\n return await fetch(input.url, {\n ...input.init,\n signal: composed.signal,\n });\n } catch (error) {\n // A keep-alive connection that a proxy silently dropped fails exactly\n // once and undici evicts it; the retry rides a fresh connection. This\n // is what keeps a long-lived service working behind flaky proxies\n // without asking users to touch their network config.\n if (\n timeoutSignal.aborted ||\n composed.signal.aborted ||\n !shouldWrapNetworkRequestError(error)\n ) {\n throw error;\n }\n return await fetch(input.url, {\n ...input.init,\n signal: composed.signal,\n });\n }\n } catch (error) {\n if (timeoutSignal.aborted) {\n throw createSpaceRequestTimeoutError({\n operation: input.operation,\n timeoutMs,\n cause: error,\n });\n }\n if (shouldWrapNetworkRequestError(error)) {\n throw createSpaceRequestNetworkError({\n operation: input.operation,\n url: input.url,\n cause: error,\n });\n }\n throw error;\n } finally {\n composed.cleanup();\n }\n}\n\nexport function resolveSpaceRequestTimeoutMsForDiagnostics(): number {\n return resolveSpaceRequestTimeoutMsForCliDiagnostics();\n}\n\nfunction createSpaceRequestTimeoutError(input: {\n operation: string;\n timeoutMs: number;\n cause: unknown;\n}): Error {\n const error = new Error(\n `${input.operation} request timed out after ${input.timeoutMs}ms`,\n { cause: input.cause }\n ) as Error & { code?: string };\n error.code = \"network.timeout\";\n return error;\n}\n\nfunction createSpaceRequestNetworkError(input: {\n operation: string;\n url: string;\n cause: unknown;\n}): Error {\n const error = new Error(\n `${input.operation} request could not reach ${describeRequestTarget(input.url)} (${toErrorMessage(input.cause)})`,\n { cause: input.cause }\n ) as Error & { code?: string };\n error.code = \"network.request_failed\";\n return error;\n}\n\nfunction composeAbortSignals(\n parent: AbortSignal | null | undefined,\n timeout: AbortSignal\n): { signal: AbortSignal; cleanup: () => void } {\n if (!parent) {\n return {\n signal: timeout,\n cleanup: () => undefined,\n };\n }\n\n if (parent.aborted || timeout.aborted) {\n const controller = new AbortController();\n controller.abort();\n return {\n signal: controller.signal,\n cleanup: () => undefined,\n };\n }\n\n const controller = new AbortController();\n const abort = () => {\n controller.abort();\n };\n\n parent.addEventListener(\"abort\", abort, { once: true });\n timeout.addEventListener(\"abort\", abort, { once: true });\n\n return {\n signal: controller.signal,\n cleanup: () => {\n parent.removeEventListener(\"abort\", abort);\n timeout.removeEventListener(\"abort\", abort);\n },\n };\n}\n\nfunction describeRequestTarget(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return url;\n }\n}\n\nfunction toErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n const message = error.message.trim();\n return message.length > 0 ? message : \"request failed\";\n }\n const message = String(error ?? \"\").trim();\n return message.length > 0 ? message : \"request failed\";\n}\n\nfunction shouldWrapNetworkRequestError(error: unknown): boolean {\n if (error instanceof TypeError) {\n return true;\n }\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n typeof error.code === \"string\" &&\n error.code.startsWith(\"ERR_SSL_\")\n ) {\n return true;\n }\n const message = toErrorMessage(error).toLowerCase();\n return (\n message.includes(\"fetch failed\") ||\n message.includes(\"failed to fetch\") ||\n message.includes(\"network request failed\") ||\n message.includes(\"ssl_decryption_failed_or_bad_record_mac\")\n );\n}\n","import {\n createOpenMeldApiClient,\n type OpenMeldApiClientFoundation,\n} from \"@openmeld/api-client\";\nimport {\n buildOpenMeldRequestContextHeaders,\n OPENMELD_HEADER_REQUESTED_ORGANIZATION_ID,\n} from \"@openmeld/gateway-contract\";\nimport { buildRequestAuthHeaders } from \"../config/request-headers\";\nimport { fetchSpaceRequestWithTimeout } from \"./fetch-with-timeout\";\n\nexport function createCliApiClientFoundation(\n baseUrl: string,\n options: {\n onAuthHeadersResolved?: (headers: Record<string, string>) => void;\n organizationId?: string;\n } = {}\n): OpenMeldApiClientFoundation {\n return createOpenMeldApiClient({\n baseUrl,\n transport: async (request) =>\n await fetchSpaceRequestWithTimeout({\n url: request.url,\n operation: request.operation,\n init: request.init,\n timeoutMs: request.timeoutMs,\n }),\n getAuthHeaders: async ({ requestUrl }) => {\n const headers = await buildRequestAuthHeaders({\n requestUrl,\n });\n if (options.organizationId) {\n headers[OPENMELD_HEADER_REQUESTED_ORGANIZATION_ID] =\n options.organizationId;\n }\n options.onAuthHeadersResolved?.(headers);\n return headers;\n },\n getRequestContextHeaders: async ({ requestContext, spacePassword }) =>\n buildOpenMeldRequestContextHeaders({\n requestContext,\n ...(spacePassword === undefined ? {} : { spacePassword }),\n }),\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { t as CURRENT_DAEMON_EXECUTION_CONTRACT_EPOCH } from "./runtime-transport-
|
|
3
|
+
import { G as acquireLock, Rt as openMeldRootDir, Sn as __exportAll, W as isLoopbackHostname, et as runWithHeldLock, q as isOpenMeldLockError, vt as CURRENT_PROCESS_START_IDENTITY, yt as isProcessIdentityAlive } from "./base-url-CY_j4bLH.js";
|
|
4
|
+
import { t as CURRENT_DAEMON_EXECUTION_CONTRACT_EPOCH } from "./runtime-transport-DYzU1lHC.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import * as z$1 from "zod/v4";
|
|
7
7
|
import { join, resolve } from "node:path";
|
|
@@ -110,25 +110,40 @@ function resolveAuthSessionExpiryEligibility(expiresAt, nowMs = Date.now()) {
|
|
|
110
110
|
//#endregion
|
|
111
111
|
//#region src/config/auth-session-transition-lock.ts
|
|
112
112
|
const AUTH_SESSION_LOCK_KEY = "session-transition-mutation";
|
|
113
|
+
const AUTH_SESSION_CANDIDATE_REFRESH_LOCK_KEY = "candidate-session-refresh";
|
|
113
114
|
const AUTH_SESSION_LOCK_PROFILE = "auth-session";
|
|
114
115
|
const AUTH_SESSION_LOCK_RETRY_DELAY_MS = 20;
|
|
115
116
|
const AUTH_SESSION_LOCK_WAIT_MS = 1e4;
|
|
116
117
|
async function withAuthSessionTransitionMutationLock(operation) {
|
|
118
|
+
return await withAuthSessionLock({
|
|
119
|
+
component: "auth_session_transition",
|
|
120
|
+
key: AUTH_SESSION_LOCK_KEY,
|
|
121
|
+
operation
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function withAuthSessionCandidateRefreshLock(operation) {
|
|
125
|
+
return await withAuthSessionLock({
|
|
126
|
+
component: "auth_session_candidate_refresh",
|
|
127
|
+
key: AUTH_SESSION_CANDIDATE_REFRESH_LOCK_KEY,
|
|
128
|
+
operation
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async function withAuthSessionLock(input) {
|
|
117
132
|
return await runWithHeldLock({
|
|
118
|
-
lock: await
|
|
119
|
-
run: operation,
|
|
133
|
+
lock: await acquireAuthSessionLock(input),
|
|
134
|
+
run: input.operation,
|
|
120
135
|
releaseContext: {
|
|
121
|
-
component:
|
|
122
|
-
lockKey:
|
|
136
|
+
component: input.component,
|
|
137
|
+
lockKey: input.key
|
|
123
138
|
}
|
|
124
139
|
});
|
|
125
140
|
}
|
|
126
|
-
async function
|
|
141
|
+
async function acquireAuthSessionLock(input) {
|
|
127
142
|
const deadline = Date.now() + AUTH_SESSION_LOCK_WAIT_MS;
|
|
128
143
|
while (true) try {
|
|
129
144
|
return await acquireLock({
|
|
130
|
-
key:
|
|
131
|
-
meta: { component:
|
|
145
|
+
key: input.key,
|
|
146
|
+
meta: { component: input.component },
|
|
132
147
|
openMeldProfileId: AUTH_SESSION_LOCK_PROFILE
|
|
133
148
|
});
|
|
134
149
|
} catch (error) {
|
|
@@ -731,6 +746,8 @@ var auth_session_exports = /* @__PURE__ */ __exportAll({
|
|
|
731
746
|
setAuthSession: () => setAuthSession,
|
|
732
747
|
updateAuthSessionActiveOrganization: () => updateAuthSessionActiveOrganization
|
|
733
748
|
});
|
|
749
|
+
const MAX_CANDIDATE_SERVICE_PROCESSES = 16;
|
|
750
|
+
const MAX_CANDIDATE_TOKEN_REFS = 64;
|
|
734
751
|
var AuthSessionStorageError = class extends Error {
|
|
735
752
|
reason;
|
|
736
753
|
constructor(input) {
|
|
@@ -772,6 +789,9 @@ const TRAILING_SLASHES_RE = /\/+$/u;
|
|
|
772
789
|
let cachedAuthSession = null;
|
|
773
790
|
const activeAuthSessionTransitionIds = /* @__PURE__ */ new Set();
|
|
774
791
|
async function getAuthSessionState(options = {}) {
|
|
792
|
+
return await getAuthSessionStateWithCandidateRefreshFence(options, false);
|
|
793
|
+
}
|
|
794
|
+
async function getAuthSessionStateWithCandidateRefreshFence(options, candidateRefreshFenceHeld) {
|
|
775
795
|
const localState = await getLocalAuthSessionState();
|
|
776
796
|
const localPayloadKey = cachedAuthSession?.payloadKey ?? null;
|
|
777
797
|
const shouldValidateRemote = options.validateRemote === true || options.refreshExpired === true && localState.state === "expired";
|
|
@@ -785,6 +805,7 @@ async function getAuthSessionState(options = {}) {
|
|
|
785
805
|
default: return localState;
|
|
786
806
|
}
|
|
787
807
|
if (!shouldValidateRemote) return localState;
|
|
808
|
+
if (!candidateRefreshFenceHeld && await shouldFencePendingCandidateRemoteValidation()) return await withAuthSessionCandidateRefreshLock(async () => await getAuthSessionStateWithCandidateRefreshFence(options, true));
|
|
788
809
|
const remoteValidation = await getRemoteAuthSessionState(localState.session);
|
|
789
810
|
if (remoteValidation.state === "valid") return await resolveRemotelyValidatedAuthSession({
|
|
790
811
|
localPayloadKey,
|
|
@@ -804,6 +825,10 @@ async function getAuthSessionState(options = {}) {
|
|
|
804
825
|
session: null
|
|
805
826
|
};
|
|
806
827
|
}
|
|
828
|
+
async function shouldFencePendingCandidateRemoteValidation() {
|
|
829
|
+
const journal = await readAuthSessionTransitionJournal();
|
|
830
|
+
return Boolean(journal?.state === "pending" && journal.candidateCredentialRefreshVersion === 1 && isAuthSessionTransitionCandidateVisibleToCurrentProcess(journal));
|
|
831
|
+
}
|
|
807
832
|
async function resolveRemotelyValidatedAuthSession(input) {
|
|
808
833
|
if (!input.remoteValidation.expiresAt || isExpired(input.remoteValidation.expiresAt)) return input.localState;
|
|
809
834
|
let refreshedSession;
|
|
@@ -816,7 +841,14 @@ async function resolveRemotelyValidatedAuthSession(input) {
|
|
|
816
841
|
});
|
|
817
842
|
} catch (error) {
|
|
818
843
|
if (!(error instanceof AuthSessionRecoveryInProgressError)) throw error;
|
|
819
|
-
return
|
|
844
|
+
return {
|
|
845
|
+
state: "valid",
|
|
846
|
+
session: {
|
|
847
|
+
...input.localState.session,
|
|
848
|
+
accessToken: input.remoteValidation.accessToken ?? input.localState.session.accessToken,
|
|
849
|
+
expiresAt: input.remoteValidation.expiresAt
|
|
850
|
+
}
|
|
851
|
+
};
|
|
820
852
|
}
|
|
821
853
|
if (!refreshedSession) return await getLocalAuthSessionState();
|
|
822
854
|
return {
|
|
@@ -922,6 +954,8 @@ async function beginAuthSessionTransition(input, context = {}) {
|
|
|
922
954
|
ownerProcessStartedAt: CURRENT_PROCESS_START_IDENTITY,
|
|
923
955
|
previousPayload: previousPayload?.v === 1 ? previousPayload : null,
|
|
924
956
|
candidatePayload: nextPayload,
|
|
957
|
+
candidateCredentialRefreshVersion: 1,
|
|
958
|
+
candidateTokenRefs: [tokenRef],
|
|
925
959
|
...context.computerHandoff ? { computerHandoff: { gatewayUrl: context.computerHandoff.gatewayUrl } } : {}
|
|
926
960
|
};
|
|
927
961
|
let journalClaimed = false;
|
|
@@ -957,14 +991,14 @@ async function beginAuthSessionTransition(input, context = {}) {
|
|
|
957
991
|
if (!journal.previousPayload) return currentPayload === null;
|
|
958
992
|
return currentPayload?.v === 1 && buildAuthSessionPayloadKey(currentPayload) === buildAuthSessionPayloadKey(journal.previousPayload);
|
|
959
993
|
};
|
|
960
|
-
const isCandidateSessionCurrent = (currentPayload) => currentPayload?.v === 1 && buildAuthSessionPayloadKey(currentPayload) === buildAuthSessionPayloadKey(
|
|
994
|
+
const isCandidateSessionCurrent = (currentPayload, candidatePayload = nextPayload) => currentPayload?.v === 1 && buildAuthSessionPayloadKey(currentPayload) === buildAuthSessionPayloadKey(candidatePayload);
|
|
961
995
|
const assertPreviousSessionCurrent = async () => {
|
|
962
996
|
const currentPayload = await readSessionPayloadFile();
|
|
963
997
|
if (!isPreviousSessionCurrent(currentPayload)) throw new Error("auth session changed before the setup transition completed");
|
|
964
998
|
};
|
|
965
|
-
const assertTransitionSessionCurrent = async () => {
|
|
999
|
+
const assertTransitionSessionCurrent = async (candidatePayload = nextPayload) => {
|
|
966
1000
|
const currentPayload = await readSessionPayloadFile();
|
|
967
|
-
if (!(isPreviousSessionCurrent(currentPayload) || isCandidateSessionCurrent(currentPayload))) throw new Error("auth session changed before the setup transition completed");
|
|
1001
|
+
if (!(isPreviousSessionCurrent(currentPayload) || isCandidateSessionCurrent(currentPayload, candidatePayload))) throw new Error("auth session changed before the setup transition completed");
|
|
968
1002
|
};
|
|
969
1003
|
const restoreMissingJournalTransition = async () => {
|
|
970
1004
|
const currentPayload = await readSessionPayloadFile();
|
|
@@ -1016,14 +1050,13 @@ async function beginAuthSessionTransition(input, context = {}) {
|
|
|
1016
1050
|
reason: currentJournal.status
|
|
1017
1051
|
});
|
|
1018
1052
|
}
|
|
1053
|
+
const committedPayload = currentJournal.journal.candidatePayload;
|
|
1054
|
+
const committedSession = await readStoredAuthSessionFromPayload(committedPayload);
|
|
1019
1055
|
await assertPreviousSessionCurrent();
|
|
1020
|
-
await writeSessionPayloadAtomically(authSessionPath(),
|
|
1056
|
+
await writeSessionPayloadAtomically(authSessionPath(), committedPayload);
|
|
1021
1057
|
cacheAuthSession({
|
|
1022
|
-
payload:
|
|
1023
|
-
session:
|
|
1024
|
-
accessToken,
|
|
1025
|
-
payload: nextPayload
|
|
1026
|
-
})
|
|
1058
|
+
payload: committedPayload,
|
|
1059
|
+
session: committedSession
|
|
1027
1060
|
});
|
|
1028
1061
|
const committedJournal = {
|
|
1029
1062
|
...currentJournal.journal,
|
|
@@ -1054,7 +1087,7 @@ async function beginAuthSessionTransition(input, context = {}) {
|
|
|
1054
1087
|
reason: currentJournal.status
|
|
1055
1088
|
});
|
|
1056
1089
|
}
|
|
1057
|
-
await assertTransitionSessionCurrent();
|
|
1090
|
+
await assertTransitionSessionCurrent(currentJournal.journal.candidatePayload);
|
|
1058
1091
|
await restoreTransitionPreviousPayload(currentJournal.journal, previousSession);
|
|
1059
1092
|
settlement = "rolled_back";
|
|
1060
1093
|
activeAuthSessionTransitionIds.delete(journal.id);
|
|
@@ -1066,7 +1099,7 @@ async function beginAuthSessionTransition(input, context = {}) {
|
|
|
1066
1099
|
});
|
|
1067
1100
|
return;
|
|
1068
1101
|
}
|
|
1069
|
-
await
|
|
1102
|
+
await cleanupCandidateTokenRefs(currentJournal.journal);
|
|
1070
1103
|
await removeAuthSessionTransitionJournalIfOwned(currentJournal.journal);
|
|
1071
1104
|
})
|
|
1072
1105
|
};
|
|
@@ -1164,7 +1197,7 @@ async function completeInterruptedComputerAuthSessionRecovery(id) {
|
|
|
1164
1197
|
const journal = await readRequiredComputerAuthRecoveryJournal(id);
|
|
1165
1198
|
if (!(journal.computerHandoff.serviceReconciledAt && journal.computerHandoff.remoteSessionRevokedAt)) throw new Error("computer auth recovery cannot complete before service and remote-session cleanup");
|
|
1166
1199
|
try {
|
|
1167
|
-
await
|
|
1200
|
+
await cleanupCandidateTokenRefs(journal);
|
|
1168
1201
|
await removeAuthSessionTransitionJournalIfOwned(journal);
|
|
1169
1202
|
return true;
|
|
1170
1203
|
} catch (error) {
|
|
@@ -1436,7 +1469,7 @@ function buildStoredAuthSessionIdentity(session) {
|
|
|
1436
1469
|
async function cleanupTransitionTokensNotReferencedByCurrentSession(input) {
|
|
1437
1470
|
const currentTokenRef = normalizeTokenRef(input.currentPayload?.tokenRef);
|
|
1438
1471
|
const previousTokenRef = normalizeTokenRef(input.journal.previousPayload?.tokenRef);
|
|
1439
|
-
const transitionTokenRefs = [...previousTokenRef ? [previousTokenRef] : [], input.journal
|
|
1472
|
+
const transitionTokenRefs = [...previousTokenRef ? [previousTokenRef] : [], ...getCandidateTokenRefs(input.journal)];
|
|
1440
1473
|
for (const [index, tokenRef] of transitionTokenRefs.entries()) {
|
|
1441
1474
|
if (transitionTokenRefs.slice(0, index).some((previousRef) => areStoredTokenRefsEqual(previousRef, tokenRef)) || currentTokenRef && areStoredTokenRefsEqual(currentTokenRef, tokenRef)) continue;
|
|
1442
1475
|
await deleteAccessTokenOrRetainForCleanup(tokenRef);
|
|
@@ -1458,57 +1491,134 @@ function buildAuthSessionPayloadKey(payload) {
|
|
|
1458
1491
|
function areStoredTokenRefsEqual(left, right) {
|
|
1459
1492
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
1460
1493
|
}
|
|
1494
|
+
function getUniqueTokenRefs(tokenRefs) {
|
|
1495
|
+
const uniqueTokenRefs = [];
|
|
1496
|
+
for (const tokenRef of tokenRefs) if (!uniqueTokenRefs.some((existing) => areStoredTokenRefsEqual(existing, tokenRef))) uniqueTokenRefs.push(tokenRef);
|
|
1497
|
+
return uniqueTokenRefs;
|
|
1498
|
+
}
|
|
1499
|
+
function getCandidateTokenRefs(journal) {
|
|
1500
|
+
return getUniqueTokenRefs([...journal.candidateTokenRefs ?? [], journal.candidatePayload.tokenRef]);
|
|
1501
|
+
}
|
|
1502
|
+
async function cleanupCandidateTokenRefs(journal, keepTokenRef) {
|
|
1503
|
+
for (const tokenRef of getCandidateTokenRefs(journal)) {
|
|
1504
|
+
if (keepTokenRef && areStoredTokenRefsEqual(keepTokenRef, tokenRef)) continue;
|
|
1505
|
+
await deleteAccessTokenOrRetainForCleanup(tokenRef);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1461
1508
|
async function persistRemoteSessionRefresh(input) {
|
|
1462
1509
|
return await withAuthSessionTransitionMutationLock(async () => {
|
|
1463
|
-
await
|
|
1464
|
-
const
|
|
1465
|
-
if (
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
const clientId = normalizeOptionalString(payload.clientId);
|
|
1470
|
-
const createdAt = normalizeOptionalString(payload.createdAt);
|
|
1471
|
-
if (!(tokenRef && tokenType && authBaseUrl && clientId && createdAt)) return null;
|
|
1472
|
-
const nextAccessToken = normalizeOptionalString(input.accessToken);
|
|
1473
|
-
if (!nextAccessToken) return null;
|
|
1474
|
-
const nextTokenRef = nextAccessToken === input.previousAccessToken ? tokenRef : await storeAccessToken({
|
|
1475
|
-
accessToken: nextAccessToken,
|
|
1476
|
-
authBaseUrl,
|
|
1477
|
-
clientId
|
|
1478
|
-
});
|
|
1479
|
-
const nextPayload = {
|
|
1480
|
-
v: 1,
|
|
1481
|
-
tokenRef: nextTokenRef,
|
|
1482
|
-
tokenType,
|
|
1483
|
-
authBaseUrl,
|
|
1484
|
-
clientId,
|
|
1485
|
-
createdAt,
|
|
1486
|
-
expiresAt: input.expiresAt,
|
|
1487
|
-
...typeof payload.scope === "string" && payload.scope.trim() ? { scope: payload.scope.trim() } : {},
|
|
1488
|
-
...buildAuthUserPayload(payload.user),
|
|
1489
|
-
...buildActiveOrganizationPayload(payload.activeOrganization)
|
|
1490
|
-
};
|
|
1491
|
-
const nextSession = buildStoredAuthSession({
|
|
1492
|
-
accessToken: nextAccessToken,
|
|
1493
|
-
payload: nextPayload
|
|
1494
|
-
});
|
|
1495
|
-
try {
|
|
1496
|
-
await assertStoredAccessTokenReloads({
|
|
1497
|
-
accessToken: nextAccessToken,
|
|
1498
|
-
tokenRef: nextTokenRef
|
|
1510
|
+
await recoverInterruptedAuthSessionTransitionWithOwnership();
|
|
1511
|
+
const transitionJournal = await readAuthSessionTransitionJournal();
|
|
1512
|
+
if (transitionJournal) {
|
|
1513
|
+
if (transitionJournal.state === "pending" && transitionJournal.candidateCredentialRefreshVersion === 1 && isAuthSessionTransitionCandidateVisibleToCurrentProcess(transitionJournal) && input.expectedPayloadKey !== null && buildAuthSessionPayloadKey(transitionJournal.candidatePayload) === input.expectedPayloadKey) return await persistPendingCandidateRemoteSessionRefresh({
|
|
1514
|
+
...input,
|
|
1515
|
+
journal: transitionJournal
|
|
1499
1516
|
});
|
|
1500
|
-
|
|
1501
|
-
} catch (error) {
|
|
1502
|
-
if (!areStoredTokenRefsEqual(tokenRef, nextTokenRef)) await deleteAccessTokenOrRetainForCleanup(nextTokenRef);
|
|
1503
|
-
throw error;
|
|
1517
|
+
throw new AuthSessionRecoveryInProgressError();
|
|
1504
1518
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1519
|
+
return await persistCommittedRemoteSessionRefresh(input);
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
async function persistCommittedRemoteSessionRefresh(input) {
|
|
1523
|
+
const payload = await readSessionPayloadFile();
|
|
1524
|
+
if (payload?.v !== 1 || input.expectedPayloadKey === null || buildAuthSessionPayloadKey(payload) !== input.expectedPayloadKey) return null;
|
|
1525
|
+
const refresh = await prepareRemoteSessionRefresh({
|
|
1526
|
+
...input,
|
|
1527
|
+
payload,
|
|
1528
|
+
uniqueTokenRef: false
|
|
1529
|
+
});
|
|
1530
|
+
if (!refresh) return null;
|
|
1531
|
+
try {
|
|
1532
|
+
await assertStoredAccessTokenReloads({
|
|
1533
|
+
accessToken: refresh.nextSession.accessToken,
|
|
1534
|
+
tokenRef: refresh.nextTokenRef
|
|
1508
1535
|
});
|
|
1509
|
-
|
|
1510
|
-
|
|
1536
|
+
await writeSessionPayloadAtomically(authSessionPath(), refresh.nextPayload);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
await cleanupFailedRemoteSessionRefresh(refresh);
|
|
1539
|
+
throw error;
|
|
1540
|
+
}
|
|
1541
|
+
cacheAuthSession({
|
|
1542
|
+
payload: refresh.nextPayload,
|
|
1543
|
+
session: refresh.nextSession
|
|
1544
|
+
});
|
|
1545
|
+
if (!areStoredTokenRefsEqual(refresh.currentTokenRef, refresh.nextTokenRef)) await deleteAccessTokenOrRetainForCleanup(refresh.currentTokenRef);
|
|
1546
|
+
return refresh.nextSession;
|
|
1547
|
+
}
|
|
1548
|
+
async function persistPendingCandidateRemoteSessionRefresh(input) {
|
|
1549
|
+
const refresh = await prepareRemoteSessionRefresh({
|
|
1550
|
+
...input,
|
|
1551
|
+
payload: input.journal.candidatePayload,
|
|
1552
|
+
uniqueTokenRef: true
|
|
1511
1553
|
});
|
|
1554
|
+
if (!refresh) return null;
|
|
1555
|
+
const candidateTokenRefs = getUniqueTokenRefs([...getCandidateTokenRefs(input.journal), refresh.nextTokenRef]);
|
|
1556
|
+
if (candidateTokenRefs.length > MAX_CANDIDATE_TOKEN_REFS) {
|
|
1557
|
+
await cleanupFailedRemoteSessionRefresh(refresh);
|
|
1558
|
+
throw new AuthSessionStorageError({
|
|
1559
|
+
message: `auth session transition has more than ${String(MAX_CANDIDATE_TOKEN_REFS)} candidate token rotations`,
|
|
1560
|
+
reason: "unreadable"
|
|
1561
|
+
});
|
|
1562
|
+
}
|
|
1563
|
+
const nextJournal = {
|
|
1564
|
+
...input.journal,
|
|
1565
|
+
candidatePayload: refresh.nextPayload,
|
|
1566
|
+
candidateTokenRefs
|
|
1567
|
+
};
|
|
1568
|
+
try {
|
|
1569
|
+
await assertStoredAccessTokenReloads({
|
|
1570
|
+
accessToken: refresh.nextSession.accessToken,
|
|
1571
|
+
tokenRef: refresh.nextTokenRef
|
|
1572
|
+
});
|
|
1573
|
+
await writeAuthSessionTransitionJournalIfOwned(input.journal, nextJournal);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
await cleanupFailedRemoteSessionRefresh(refresh);
|
|
1576
|
+
throw error;
|
|
1577
|
+
}
|
|
1578
|
+
cacheAuthSession({
|
|
1579
|
+
payload: refresh.nextPayload,
|
|
1580
|
+
session: refresh.nextSession
|
|
1581
|
+
});
|
|
1582
|
+
return refresh.nextSession;
|
|
1583
|
+
}
|
|
1584
|
+
async function prepareRemoteSessionRefresh(input) {
|
|
1585
|
+
const currentTokenRef = normalizeTokenRef(input.payload.tokenRef);
|
|
1586
|
+
const tokenType = normalizeOptionalString(input.payload.tokenType);
|
|
1587
|
+
const authBaseUrl = normalizeOptionalString(input.payload.authBaseUrl);
|
|
1588
|
+
const clientId = normalizeOptionalString(input.payload.clientId);
|
|
1589
|
+
const createdAt = normalizeOptionalString(input.payload.createdAt);
|
|
1590
|
+
const nextAccessToken = normalizeOptionalString(input.accessToken);
|
|
1591
|
+
if (!(currentTokenRef && tokenType && authBaseUrl && clientId && createdAt && nextAccessToken)) return null;
|
|
1592
|
+
const nextTokenRef = nextAccessToken === input.previousAccessToken ? currentTokenRef : await storeAccessToken({
|
|
1593
|
+
accessToken: nextAccessToken,
|
|
1594
|
+
authBaseUrl,
|
|
1595
|
+
clientId,
|
|
1596
|
+
unique: input.uniqueTokenRef
|
|
1597
|
+
});
|
|
1598
|
+
const nextPayload = {
|
|
1599
|
+
v: 1,
|
|
1600
|
+
tokenRef: nextTokenRef,
|
|
1601
|
+
tokenType,
|
|
1602
|
+
authBaseUrl,
|
|
1603
|
+
clientId,
|
|
1604
|
+
createdAt,
|
|
1605
|
+
expiresAt: input.expiresAt,
|
|
1606
|
+
...typeof input.payload.scope === "string" && input.payload.scope.trim() ? { scope: input.payload.scope.trim() } : {},
|
|
1607
|
+
...buildAuthUserPayload(input.payload.user),
|
|
1608
|
+
...buildActiveOrganizationPayload(input.payload.activeOrganization)
|
|
1609
|
+
};
|
|
1610
|
+
return {
|
|
1611
|
+
currentTokenRef,
|
|
1612
|
+
nextPayload,
|
|
1613
|
+
nextSession: buildStoredAuthSession({
|
|
1614
|
+
accessToken: nextAccessToken,
|
|
1615
|
+
payload: nextPayload
|
|
1616
|
+
}),
|
|
1617
|
+
nextTokenRef
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
async function cleanupFailedRemoteSessionRefresh(refresh) {
|
|
1621
|
+
if (!areStoredTokenRefsEqual(refresh.currentTokenRef, refresh.nextTokenRef)) await deleteAccessTokenOrRetainForCleanup(refresh.nextTokenRef);
|
|
1512
1622
|
}
|
|
1513
1623
|
function normalizeTokenRef(input) {
|
|
1514
1624
|
if (!isStoredTokenRef(input)) return null;
|
|
@@ -1567,9 +1677,6 @@ function parseUrlOrNull(value) {
|
|
|
1567
1677
|
return null;
|
|
1568
1678
|
}
|
|
1569
1679
|
}
|
|
1570
|
-
function isLoopbackHostname(hostname) {
|
|
1571
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
1572
|
-
}
|
|
1573
1680
|
function hasActiveSessionPayload(payload) {
|
|
1574
1681
|
if (!isRecord(payload)) return false;
|
|
1575
1682
|
return isRecord(payload.session) && isRecord(payload.user);
|
|
@@ -1587,12 +1694,17 @@ function resolveRemoteAuthSessionReasonCode(state) {
|
|
|
1587
1694
|
async function readSessionPayload() {
|
|
1588
1695
|
await recoverInterruptedAuthSessionTransition();
|
|
1589
1696
|
const journal = await readAuthSessionTransitionJournal();
|
|
1590
|
-
if (journal?.state === "pending"
|
|
1697
|
+
if (journal?.state === "pending") {
|
|
1698
|
+
if (isAuthSessionTransitionCandidateVisibleToCurrentProcess(journal)) return journal.candidatePayload;
|
|
1699
|
+
}
|
|
1591
1700
|
return await readSessionPayloadFile();
|
|
1592
1701
|
}
|
|
1593
1702
|
function isAuthSessionTransitionCandidateVisibleToCurrentProcess(journal) {
|
|
1594
1703
|
if (activeAuthSessionTransitionIds.has(journal.id)) return true;
|
|
1595
|
-
return journal.candidateServiceProcess
|
|
1704
|
+
return getRegisteredCandidateServiceProcesses(journal).some((candidateServiceProcess) => isSameProcessIdentity(candidateServiceProcess, {
|
|
1705
|
+
ownerPid: process.pid,
|
|
1706
|
+
ownerProcessStartedAt: CURRENT_PROCESS_START_IDENTITY
|
|
1707
|
+
}));
|
|
1596
1708
|
}
|
|
1597
1709
|
async function readSessionPayloadFile() {
|
|
1598
1710
|
const path = authSessionPath();
|
|
@@ -1639,7 +1751,7 @@ async function recoverInterruptedAuthSessionTransitionWithOwnership() {
|
|
|
1639
1751
|
});
|
|
1640
1752
|
return;
|
|
1641
1753
|
}
|
|
1642
|
-
await
|
|
1754
|
+
await cleanupCandidateTokenRefs(journal);
|
|
1643
1755
|
await removeAuthSessionTransitionJournalIfOwned(journal);
|
|
1644
1756
|
return;
|
|
1645
1757
|
}
|
|
@@ -1664,6 +1776,7 @@ async function recoverInterruptedAuthSessionTransitionWithOwnership() {
|
|
|
1664
1776
|
async function finalizeCommittedAuthSessionTransition(journal) {
|
|
1665
1777
|
const previousTokenRef = normalizeTokenRef(journal.previousPayload?.tokenRef);
|
|
1666
1778
|
if (previousTokenRef && !areStoredTokenRefsEqual(previousTokenRef, journal.candidatePayload.tokenRef)) await deleteAccessTokenOrRetainForCleanup(previousTokenRef);
|
|
1779
|
+
await cleanupCandidateTokenRefs(journal, journal.candidatePayload.tokenRef);
|
|
1667
1780
|
await removeAuthSessionTransitionJournalIfOwned(journal);
|
|
1668
1781
|
}
|
|
1669
1782
|
async function registerCurrentCandidateServiceProcess(journal) {
|
|
@@ -1672,29 +1785,43 @@ async function registerCurrentCandidateServiceProcess(journal) {
|
|
|
1672
1785
|
ownerPid: process.pid,
|
|
1673
1786
|
ownerProcessStartedAt: CURRENT_PROCESS_START_IDENTITY
|
|
1674
1787
|
};
|
|
1675
|
-
if (journal.candidateServiceProcess
|
|
1788
|
+
if (getRegisteredCandidateServiceProcesses(journal).some((candidateServiceProcess) => isSameProcessIdentity(candidateServiceProcess, currentProcess))) return journal;
|
|
1676
1789
|
const currentJournal = await readAuthSessionTransitionJournal();
|
|
1677
1790
|
if (currentJournal?.id !== journal.id || currentJournal.state !== "pending") return currentJournal ?? journal;
|
|
1791
|
+
const candidateServiceProcesses = getRegisteredCandidateServiceProcesses(currentJournal).filter(isProcessIdentityAlive);
|
|
1792
|
+
candidateServiceProcesses.push(currentProcess);
|
|
1793
|
+
if (candidateServiceProcesses.length > MAX_CANDIDATE_SERVICE_PROCESSES) throw new Error(`auth session transition has more than ${String(MAX_CANDIDATE_SERVICE_PROCESSES)} live candidate OpenMeld Service processes`);
|
|
1678
1794
|
const updatedJournal = {
|
|
1679
1795
|
...currentJournal,
|
|
1796
|
+
candidateServiceProcesses,
|
|
1680
1797
|
candidateServiceProcess: currentProcess
|
|
1681
1798
|
};
|
|
1682
1799
|
await writeAuthSessionTransitionJournalIfOwned(currentJournal, updatedJournal);
|
|
1683
1800
|
return updatedJournal;
|
|
1684
1801
|
}
|
|
1685
1802
|
function stopRegisteredCandidateService(journal) {
|
|
1686
|
-
const candidateServiceProcess
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1803
|
+
for (const candidateServiceProcess of getRegisteredCandidateServiceProcesses(journal)) {
|
|
1804
|
+
if (!isProcessIdentityAlive(candidateServiceProcess)) continue;
|
|
1805
|
+
try {
|
|
1806
|
+
process.kill(candidateServiceProcess.ownerPid, "SIGTERM");
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
if (!isFileNotFoundErrorCode(error, "ESRCH")) throw new AuthSessionStorageError({
|
|
1809
|
+
message: `candidate OpenMeld Service process ${String(candidateServiceProcess.ownerPid)} could not be stopped: ${toErrorMessage(error)}`,
|
|
1810
|
+
reason: "unreadable",
|
|
1811
|
+
cause: error
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1696
1814
|
}
|
|
1697
1815
|
}
|
|
1816
|
+
function getRegisteredCandidateServiceProcesses(journal) {
|
|
1817
|
+
const candidates = [...journal.candidateServiceProcesses ?? [], ...journal.candidateServiceProcess ? [journal.candidateServiceProcess] : []];
|
|
1818
|
+
const uniqueCandidates = [];
|
|
1819
|
+
for (const candidate of candidates) if (!uniqueCandidates.some((existing) => isSameProcessIdentity(existing, candidate))) uniqueCandidates.push(candidate);
|
|
1820
|
+
return uniqueCandidates;
|
|
1821
|
+
}
|
|
1822
|
+
function isSameProcessIdentity(left, right) {
|
|
1823
|
+
return left.ownerPid === right.ownerPid && left.ownerProcessStartedAt === right.ownerProcessStartedAt;
|
|
1824
|
+
}
|
|
1698
1825
|
function isCurrentProcessCandidateService() {
|
|
1699
1826
|
const runtimeMode = String(process.env.OPENMELD_DAEMON_RUNTIME_MODE ?? "").trim();
|
|
1700
1827
|
return runtimeMode === "background" || runtimeMode === "foreground";
|
|
@@ -1771,10 +1898,19 @@ async function readAuthSessionTransitionJournal() {
|
|
|
1771
1898
|
}
|
|
1772
1899
|
function isAuthSessionTransitionJournal(value) {
|
|
1773
1900
|
if (!isRecord(value)) return false;
|
|
1774
|
-
return value.v === 1 && typeof value.id === "string" && value.id.length > 0 && (value.state === "pending" || value.state === "recovery_required" || value.state === "committed") && Number.isSafeInteger(value.ownerPid) && Number(value.ownerPid) > 0 && typeof value.ownerProcessStartedAt === "string" && value.ownerProcessStartedAt.length > 0 && (value.previousPayload === null || isAuthSessionPayload(value.previousPayload)) && isAuthSessionPayload(value.candidatePayload) && isOptionalProcessIdentity(value.candidateServiceProcess) && isOptionalComputerHandoffRecovery(value.computerHandoff);
|
|
1901
|
+
return value.v === 1 && typeof value.id === "string" && value.id.length > 0 && (value.state === "pending" || value.state === "recovery_required" || value.state === "committed") && Number.isSafeInteger(value.ownerPid) && Number(value.ownerPid) > 0 && typeof value.ownerProcessStartedAt === "string" && value.ownerProcessStartedAt.length > 0 && (value.previousPayload === null || isAuthSessionPayload(value.previousPayload)) && isAuthSessionPayload(value.candidatePayload) && (value.candidateCredentialRefreshVersion === void 0 || value.candidateCredentialRefreshVersion === 1) && isOptionalStoredTokenRefList(value.candidateTokenRefs) && isOptionalProcessIdentityList(value.candidateServiceProcesses) && isOptionalProcessIdentity(value.candidateServiceProcess) && isOptionalComputerHandoffRecovery(value.computerHandoff);
|
|
1902
|
+
}
|
|
1903
|
+
function isOptionalStoredTokenRefList(value) {
|
|
1904
|
+
return value === void 0 || Array.isArray(value) && value.length <= MAX_CANDIDATE_TOKEN_REFS && value.every((tokenRef) => isStoredTokenRef(tokenRef));
|
|
1905
|
+
}
|
|
1906
|
+
function isOptionalProcessIdentityList(value) {
|
|
1907
|
+
return value === void 0 || Array.isArray(value) && value.length <= MAX_CANDIDATE_SERVICE_PROCESSES && value.every((candidate) => isProcessIdentity(candidate));
|
|
1775
1908
|
}
|
|
1776
1909
|
function isOptionalProcessIdentity(value) {
|
|
1777
|
-
return value === void 0 ||
|
|
1910
|
+
return value === void 0 || isProcessIdentity(value);
|
|
1911
|
+
}
|
|
1912
|
+
function isProcessIdentity(value) {
|
|
1913
|
+
return isRecord(value) && Number.isSafeInteger(value.ownerPid) && Number(value.ownerPid) > 0 && typeof value.ownerProcessStartedAt === "string" && value.ownerProcessStartedAt.length > 0;
|
|
1778
1914
|
}
|
|
1779
1915
|
function isOptionalComputerHandoffRecovery(value) {
|
|
1780
1916
|
if (value === void 0) return true;
|
|
@@ -1958,4 +2094,4 @@ async function removeSessionFile() {
|
|
|
1958
2094
|
//#endregion
|
|
1959
2095
|
export { HEARTBEAT_RESPONSE_TIMEOUT_MS as A, RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_INTERVAL_MS as B, DAEMON_RUNTIME_CONTRACT_EPOCH as C, DEFAULT_GATEWAY_CHAIN_TIMEOUT_MS as D, DAEMON_STREAM_KEEPALIVE_PONG_TIMEOUT_MS as E, OPENCLAW_CLI_TIMEOUT_MS as F, RUNTIME_STATE_PERSIST_MIN_INTERVAL_MS as H, PROVIDER_CONVERSATION_PROOF_SYNC_INTERVAL_MS as I, PROVIDER_CONVERSATION_PROOF_SYNC_RETRY_INTERVAL_MS as L, MAX_SPACE_REQUEST_TIMEOUT_MS as M, MIN_HEARTBEAT_INTERVAL_MS as N, DEFAULT_HEARTBEAT_INTERVAL_MS as O, MIN_SPACE_REQUEST_TIMEOUT_MS as P, REGISTER_RESPONSE_TIMEOUT_MS as R, CATALOG_SYNC_SIGNAL_POLL_INTERVAL_MS as S, DAEMON_STREAM_KEEPALIVE_INTERVAL_MS as T, RUNTIME_AGENT_CONTROLLER_REPORT_SYNC_RETRY_INTERVAL_MS as V, runMacOsNativeKeychainHelperIfRequested as _, beginAuthSessionTransition as a, resolveAuthSessionExpiryEligibility as b, getAuthSessionMetadata as c, getInterruptedComputerAuthSessionRecovery as d, markInterruptedComputerAuthSessionRecovery as f, updateAuthSessionActiveOrganization as g, setAuthSession as h, auth_session_exports as i, MAX_HEARTBEAT_INTERVAL_MS as j, DEFAULT_SPACE_REQUEST_TIMEOUT_MS as k, getAuthSessionState as l, recordInterruptedComputerAuthSessionRecoveryFailure as m, AuthSessionRecoveryInProgressError as n, clearAuthSession as o, normalizeStoredActiveOrganization as p, assertAuthSessionMutationAvailable as r, completeInterruptedComputerAuthSessionRecovery as s, AuthSessionChangedBeforeTransitionError as t, getCurrentAuthOwnerUserId as u, isBinaryDistribution as v, DAEMON_STREAM_CONNECT_TIMEOUT_MS as w, CATALOG_SYNC_RETRY_INTERVAL_MS as x, resolveOpenMeldDistribution as y, ROUTE_CATALOG_LOAD_TIMEOUT_MS as z };
|
|
1960
2096
|
|
|
1961
|
-
//# sourceMappingURL=auth-session-
|
|
2097
|
+
//# sourceMappingURL=auth-session-B06nfwnD.js.map
|