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.
Files changed (31) hide show
  1. package/dist/{add-me-membership-9xY_Cbqw.js → add-me-membership-CZUHkjnP.js} +2 -2
  2. package/dist/{add-me-membership-9xY_Cbqw.js.map → add-me-membership-CZUHkjnP.js.map} +1 -1
  3. package/dist/{api-client-foundation--Nr2FS4d.js → api-client-foundation-BPmXnsr9.js} +4 -7
  4. package/dist/api-client-foundation-BPmXnsr9.js.map +1 -0
  5. package/dist/{auth-session-CNmIonA7.js → auth-session-B06nfwnD.js} +224 -88
  6. package/dist/auth-session-B06nfwnD.js.map +1 -0
  7. package/dist/{base-url-BtqW4aLS.js → base-url-CY_j4bLH.js} +13 -6
  8. package/dist/base-url-CY_j4bLH.js.map +1 -0
  9. package/dist/{command-wO4TFiYh.js → command-CU_3raCS.js} +783 -210
  10. package/dist/{command-wO4TFiYh.js.map → command-CU_3raCS.js.map} +1 -1
  11. package/dist/{daemon-runtime-lease-D82uDZ5s.js → daemon-runtime-lease-6DIprlJy.js} +2 -2
  12. package/dist/{daemon-runtime-lease-D82uDZ5s.js.map → daemon-runtime-lease-6DIprlJy.js.map} +1 -1
  13. package/dist/{dist-DoFr77hZ.js → dist-NJH-sDDB.js} +177 -42
  14. package/dist/dist-NJH-sDDB.js.map +1 -0
  15. package/dist/openmeld-dev.js +1 -1
  16. package/dist/openmeld.js +408 -222
  17. package/dist/openmeld.js.map +1 -1
  18. package/dist/{runtime-transport-Ciax1_jb.js → runtime-transport-DYzU1lHC.js} +78 -14
  19. package/dist/runtime-transport-DYzU1lHC.js.map +1 -0
  20. package/package.json +2 -2
  21. package/skills/README.md +5 -2
  22. package/skills/openmeld-cli/SKILL.md +109 -1151
  23. package/skills/openmeld-cli/playbooks/space-ops.md +34 -16
  24. package/skills/openmeld-cli/playbooks/space-reads.md +123 -0
  25. package/skills/openmeld-cli/references/commands.md +14 -9
  26. package/skills/openmeld-cli/references/runtime-resolution.md +21 -2
  27. package/dist/api-client-foundation--Nr2FS4d.js.map +0 -1
  28. package/dist/auth-session-CNmIonA7.js.map +0 -1
  29. package/dist/base-url-BtqW4aLS.js.map +0 -1
  30. package/dist/dist-DoFr77hZ.js.map +0 -1
  31. 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-wO4TFiYh.js";
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-9xY_Cbqw.js.map
11
+ //# sourceMappingURL=add-me-membership-CZUHkjnP.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"add-me-membership-9xY_Cbqw.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
+ {"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, xn as __exportAll } from "./base-url-BtqW4aLS.js";
4
- import { I as OPENMELD_HEADER_REQUESTED_ORGANIZATION_ID, R as buildOpenMeldRequestContextHeaders, h as createOpenMeldApiClient } from "./dist-DoFr77hZ.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-CNmIonA7.js";
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--Nr2FS4d.js.map
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 { $ as runWithHeldLock, K as isOpenMeldLockError, Lt as openMeldRootDir, W as acquireLock, _t as CURRENT_PROCESS_START_IDENTITY, vt as isProcessIdentityAlive, xn as __exportAll } from "./base-url-BtqW4aLS.js";
4
- import { t as CURRENT_DAEMON_EXECUTION_CONTRACT_EPOCH } from "./runtime-transport-Ciax1_jb.js";
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 acquireAuthSessionTransitionMutationLock(),
119
- run: operation,
133
+ lock: await acquireAuthSessionLock(input),
134
+ run: input.operation,
120
135
  releaseContext: {
121
- component: "auth_session_transition",
122
- lockKey: AUTH_SESSION_LOCK_KEY
136
+ component: input.component,
137
+ lockKey: input.key
123
138
  }
124
139
  });
125
140
  }
126
- async function acquireAuthSessionTransitionMutationLock() {
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: AUTH_SESSION_LOCK_KEY,
131
- meta: { component: "auth_session_transition" },
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 await getLocalAuthSessionState({ recover: false });
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(nextPayload);
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(), nextPayload);
1056
+ await writeSessionPayloadAtomically(authSessionPath(), committedPayload);
1021
1057
  cacheAuthSession({
1022
- payload: nextPayload,
1023
- session: buildStoredAuthSession({
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 deleteAccessTokenOrRetainForCleanup(tokenRef);
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 deleteAccessTokenOrRetainForCleanup(journal.candidatePayload.tokenRef);
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.candidatePayload.tokenRef];
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 assertAuthSessionMutationAvailableWithOwnership();
1464
- const payload = await readSessionPayloadFile();
1465
- if (payload?.v !== 1 || input.expectedPayloadKey === null || buildAuthSessionPayloadKey(payload) !== input.expectedPayloadKey) return null;
1466
- const tokenRef = normalizeTokenRef(payload.tokenRef);
1467
- const tokenType = normalizeOptionalString(payload.tokenType);
1468
- const authBaseUrl = normalizeOptionalString(payload.authBaseUrl);
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
- await writeSessionPayloadAtomically(authSessionPath(), nextPayload);
1501
- } catch (error) {
1502
- if (!areStoredTokenRefsEqual(tokenRef, nextTokenRef)) await deleteAccessTokenOrRetainForCleanup(nextTokenRef);
1503
- throw error;
1517
+ throw new AuthSessionRecoveryInProgressError();
1504
1518
  }
1505
- cacheAuthSession({
1506
- payload: nextPayload,
1507
- session: nextSession
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
- if (!areStoredTokenRefsEqual(tokenRef, nextTokenRef)) await deleteAccessTokenOrRetainForCleanup(tokenRef);
1510
- return nextSession;
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" && isAuthSessionTransitionCandidateVisibleToCurrentProcess(journal)) return journal.candidatePayload;
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?.ownerPid === process.pid && journal.candidateServiceProcess.ownerProcessStartedAt === CURRENT_PROCESS_START_IDENTITY;
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 deleteAccessTokenOrRetainForCleanup(journal.candidatePayload.tokenRef);
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?.ownerPid === currentProcess.ownerPid && journal.candidateServiceProcess.ownerProcessStartedAt === currentProcess.ownerProcessStartedAt) return journal;
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 = journal.candidateServiceProcess;
1687
- if (!(candidateServiceProcess && isProcessIdentityAlive(candidateServiceProcess))) return;
1688
- try {
1689
- process.kill(candidateServiceProcess.ownerPid, "SIGTERM");
1690
- } catch (error) {
1691
- if (!isFileNotFoundErrorCode(error, "ESRCH")) throw new AuthSessionStorageError({
1692
- message: `candidate OpenMeld Service process ${String(candidateServiceProcess.ownerPid)} could not be stopped: ${toErrorMessage(error)}`,
1693
- reason: "unreadable",
1694
- cause: error
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 || isRecord(value) && Number.isSafeInteger(value.ownerPid) && Number(value.ownerPid) > 0 && typeof value.ownerProcessStartedAt === "string" && value.ownerProcessStartedAt.length > 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-CNmIonA7.js.map
2097
+ //# sourceMappingURL=auth-session-B06nfwnD.js.map