Download src/gateway/server-methods/agent-session-patch.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 11.7 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server-methods/agent-session-patch.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/gateway/server-methods/agent-session-patch.ts
-
curl -L -o agent-session-patch.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/gateway/server-methods/agent-session-patch.ts
11.7 kB
| import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; | |
| import { resolveTrustedGroupId } from "../../agents/agent-tools.policy.js"; | |
| import { clearAllCliSessions } from "../../agents/cli-session.js"; | |
| import { buildMainSessionRecoveryClearPatch } from "../../agents/main-session-recovery/main-session-recovery-clear.js"; | |
| import { | |
| evaluateSessionFreshness, | |
| hasTerminalMainSessionTranscriptNewerThanRegistrySync, | |
| resolveSessionLifecycleTimestamps, | |
| type SessionFreshness, | |
| } from "../../config/sessions.js"; | |
| import { hasProviderOwnedSession } from "../../config/sessions/entry-freshness.js"; | |
| import { resolveSessionEntryAccessTarget } from "../../config/sessions/session-accessor.js"; | |
| import { isRecoverableTerminalSessionStatus } from "../../config/sessions/terminal-status.js"; | |
| import type { InternalSessionEntry as SessionEntry } from "../../config/sessions/types.js"; | |
| import type { OpenClawConfig } from "../../config/types.openclaw.js"; | |
| import { | |
| isAcpSessionKey, | |
| isCronSessionKey, | |
| isSubagentSessionKey, | |
| parseAgentSessionKey, | |
| } from "../../routing/session-key.js"; | |
| import { | |
| deliveryContextFromSession, | |
| mergeDeliveryContext, | |
| normalizeSessionDeliveryState, | |
| sessionDeliveryOrigin, | |
| sessionDeliveryRoute, | |
| type DeliveryContext, | |
| } from "../../utils/delivery-context.shared.js"; | |
| import { resolveSessionStoreKey } from "../session-store-key.js"; | |
| import { | |
| normalizeTrustedGroupMetadata, | |
| requestGroupMatchesTrusted, | |
| resolveTrustedGroupMetadata, | |
| type TrustedGroupMetadata, | |
| } from "./agent-task-tracking.js"; | |
| export type AgentSessionPatchBuild = { | |
| patch: Partial<SessionEntry>; | |
| spawnedBy: string | undefined; | |
| groupId: string | undefined; | |
| groupChannel: string | undefined; | |
| groupSpace: string | undefined; | |
| freshSessionRotatedSinceLoad: boolean; | |
| isNewSession: boolean; | |
| rotatedSessionId: boolean; | |
| usableRequestedSessionId: string | undefined; | |
| freshness: SessionFreshness | undefined; | |
| }; | |
| type AgentSessionReuseInput = { | |
| freshEntry: SessionEntry | undefined; | |
| cfg: OpenClawConfig; | |
| sessionAgentId: string; | |
| canonicalSessionKey: string; | |
| storePath: string; | |
| expectedExistingSessionId?: string; | |
| hasRestoredCronContinuation: boolean; | |
| resetPolicy: ReturnType<typeof import("../../config/sessions.js").resolveSessionResetPolicy>; | |
| now: number; | |
| requestedSessionId?: string; | |
| isSystemGatewayRun: boolean; | |
| visibleRequest: boolean; | |
| failedSessionTranscriptMissing: (entry: SessionEntry | undefined) => boolean; | |
| }; | |
| /** Re-evaluate the entry from each read; callers retain admission and concurrent-rotation fencing. */ | |
| export function evaluateAgentSessionReuse(params: AgentSessionReuseInput) { | |
| const lifecycleTimestamps = params.freshEntry | |
| ? resolveSessionLifecycleTimestamps({ | |
| entry: params.freshEntry, | |
| storePath: params.storePath, | |
| agentId: params.sessionAgentId, | |
| sessionKey: params.canonicalSessionKey, | |
| }) | |
| : undefined; | |
| const skipImplicitExpiry = | |
| params.expectedExistingSessionId !== undefined || | |
| params.hasRestoredCronContinuation || | |
| params.freshEntry?.modelSelectionLocked === true || | |
| (params.resetPolicy.configured !== true && hasProviderOwnedSession(params.freshEntry)); | |
| const freshness = params.freshEntry | |
| ? skipImplicitExpiry | |
| ? ({ fresh: true } satisfies SessionFreshness) | |
| : evaluateSessionFreshness({ | |
| updatedAt: params.freshEntry.updatedAt, | |
| ...lifecycleTimestamps, | |
| now: params.now, | |
| policy: params.resetPolicy, | |
| }) | |
| : undefined; | |
| const requestedSessionMatchesEntry = Boolean( | |
| params.requestedSessionId && params.freshEntry?.sessionId?.trim() === params.requestedSessionId, | |
| ); | |
| const terminalMainTranscriptNewerThanRegistry = | |
| params.isSystemGatewayRun || requestedSessionMatchesEntry | |
| ? false | |
| : hasTerminalMainSessionTranscriptNewerThanRegistrySync({ | |
| entry: params.freshEntry, | |
| sessionScope: params.cfg.session?.scope, | |
| sessionKey: params.canonicalSessionKey, | |
| agentId: params.sessionAgentId, | |
| mainKey: params.cfg.session?.mainKey, | |
| storePath: params.storePath, | |
| }); | |
| const recoverableTerminalSession = | |
| Boolean(params.freshEntry?.sessionId) && | |
| params.visibleRequest && | |
| isRecoverableTerminalSessionStatus(params.freshEntry?.status); | |
| const canReuseSession = | |
| Boolean(params.freshEntry?.sessionId) && | |
| ((freshness?.fresh ?? false) || recoverableTerminalSession) && | |
| !params.failedSessionTranscriptMissing(params.freshEntry) && | |
| !terminalMainTranscriptNewerThanRegistry; | |
| const usableRequestedSessionId = | |
| params.requestedSessionId && (!params.freshEntry?.sessionId || canReuseSession) | |
| ? params.requestedSessionId | |
| : undefined; | |
| const sessionId = | |
| usableRequestedSessionId ?? (canReuseSession ? params.freshEntry?.sessionId : undefined); | |
| const isNewSession = | |
| !params.freshEntry || | |
| (!canReuseSession && !usableRequestedSessionId) || | |
| Boolean(usableRequestedSessionId && params.freshEntry?.sessionId !== usableRequestedSessionId); | |
| return { | |
| freshness, | |
| recoverableTerminalSession, | |
| canReuseSession, | |
| usableRequestedSessionId, | |
| sessionId, | |
| isNewSession, | |
| }; | |
| } | |
| export function buildAgentSessionPatch( | |
| params: AgentSessionReuseInput & { | |
| initialEntry: SessionEntry | undefined; | |
| normalizedSpawned: { groupId?: string; groupChannel?: string; groupSpace?: string }; | |
| requestDeliveryHint: DeliveryContext | undefined; | |
| requestLabel?: string; | |
| explicitSessionKey?: string; | |
| pluginOwnerId?: string; | |
| fallbackSessionId: string; | |
| touchInteraction: boolean; | |
| }, | |
| ): AgentSessionPatchBuild { | |
| const storedSpawnedBy = normalizeOptionalString(params.freshEntry?.spawnedBy); | |
| const freshSpawnedBy = storedSpawnedBy | |
| ? resolveSessionStoreKey({ | |
| cfg: params.cfg, | |
| sessionKey: storedSpawnedBy, | |
| storeAgentId: params.sessionAgentId, | |
| }) | |
| : undefined; | |
| const storedGroup = normalizeTrustedGroupMetadata(params.freshEntry); | |
| let inheritedGroup: TrustedGroupMetadata | undefined; | |
| if ( | |
| freshSpawnedBy && | |
| (!storedGroup.groupId || !storedGroup.groupChannel || !storedGroup.groupSpace) | |
| ) { | |
| try { | |
| const parentEntry = resolveSessionEntryAccessTarget({ | |
| cfg: params.cfg, | |
| sessionKey: freshSpawnedBy, | |
| }).entry; | |
| inheritedGroup = normalizeTrustedGroupMetadata({ | |
| groupId: parentEntry?.groupId, | |
| groupChannel: parentEntry?.groupChannel, | |
| groupSpace: parentEntry?.space, | |
| }); | |
| } catch (error) { | |
| if ((error as { code?: unknown })?.code === "SESSION_CANONICAL_KEY_MIGRATION_REQUIRED") { | |
| throw error; | |
| } | |
| inheritedGroup = undefined; | |
| } | |
| } | |
| const trustedGroup = resolveTrustedGroupMetadata({ | |
| sessionKey: params.canonicalSessionKey, | |
| spawnedBy: freshSpawnedBy, | |
| stored: storedGroup, | |
| inherited: inheritedGroup, | |
| }); | |
| const validatedGroup = trustedGroup.groupId | |
| ? resolveTrustedGroupId({ | |
| groupId: trustedGroup.groupId, | |
| sessionKey: params.canonicalSessionKey, | |
| spawnedBy: freshSpawnedBy, | |
| }) | |
| : undefined; | |
| const trustRequestSelectors = | |
| Boolean(trustedGroup.groupId) && | |
| requestGroupMatchesTrusted({ | |
| requestGroupId: params.normalizedSpawned.groupId, | |
| trustedGroupId: trustedGroup.groupId, | |
| }); | |
| const nextGroup = validatedGroup?.dropped | |
| ? { groupId: undefined, groupChannel: undefined, groupSpace: undefined } | |
| : { | |
| groupId: trustedGroup.groupId, | |
| groupChannel: | |
| trustedGroup.groupChannel ?? | |
| (trustRequestSelectors ? params.normalizedSpawned.groupChannel : undefined), | |
| groupSpace: | |
| trustedGroup.groupSpace ?? | |
| (trustRequestSelectors ? params.normalizedSpawned.groupSpace : undefined), | |
| }; | |
| const effectiveDelivery = mergeDeliveryContext( | |
| deliveryContextFromSession(params.freshEntry), | |
| params.requestDeliveryHint, | |
| ); | |
| const delivery = normalizeSessionDeliveryState({ | |
| route: sessionDeliveryRoute(params.freshEntry), | |
| context: effectiveDelivery, | |
| origin: sessionDeliveryOrigin(params.freshEntry), | |
| }); | |
| const labelValue = normalizeOptionalString(params.requestLabel) || params.freshEntry?.label; | |
| const explicitSessionDisplayName = | |
| params.freshEntry === undefined && | |
| params.visibleRequest && | |
| normalizeOptionalString(params.explicitSessionKey) && | |
| !labelValue && | |
| !isCronSessionKey(params.canonicalSessionKey) && | |
| !isSubagentSessionKey(params.canonicalSessionKey) && | |
| !isAcpSessionKey(params.canonicalSessionKey) | |
| ? parseAgentSessionKey(params.canonicalSessionKey)?.rest.trim() | |
| : undefined; | |
| const freshSessionRotatedSinceLoad = Boolean( | |
| params.initialEntry?.sessionId && | |
| params.freshEntry?.sessionId && | |
| params.freshEntry.sessionId !== params.initialEntry.sessionId, | |
| ); | |
| const reuse = evaluateAgentSessionReuse(params); | |
| const freshSessionId = reuse.sessionId ?? params.fallbackSessionId; | |
| const freshRotatedSessionId = Boolean( | |
| params.freshEntry?.sessionId && params.freshEntry.sessionId !== freshSessionId, | |
| ); | |
| const patchSessionId = freshSessionRotatedSinceLoad | |
| ? params.freshEntry?.sessionId | |
| : freshSessionId; | |
| const shouldClearRotatedState = freshRotatedSessionId && !freshSessionRotatedSinceLoad; | |
| const shouldClearTerminalState = | |
| reuse.canReuseSession && | |
| reuse.recoverableTerminalSession && | |
| !freshSessionRotatedSinceLoad && | |
| patchSessionId === params.freshEntry?.sessionId; | |
| const automaticRecoveryClearPatch = shouldClearRotatedState | |
| ? buildMainSessionRecoveryClearPatch(params.freshEntry) | |
| : {}; | |
| const patch: Partial<SessionEntry> = { | |
| sessionId: patchSessionId, | |
| updatedAt: params.now, | |
| ...(reuse.isNewSession && !freshSessionRotatedSinceLoad | |
| ? { sessionStartedAt: params.now } | |
| : {}), | |
| ...(params.touchInteraction | |
| ? { | |
| lastInteractionAt: params.now, | |
| // Clear at human-turn admission, before the model may declare a new | |
| // status. Later lifecycle writes must not erase a same-turn declaration. | |
| agentStatus: undefined, | |
| } | |
| : {}), | |
| ...automaticRecoveryClearPatch, | |
| delivery, | |
| ...(labelValue ? { label: labelValue } : {}), | |
| // An operator-supplied key is an explicit name: keep it instead of generating | |
| // a dashboard title later, matching the semantics of a Control UI rename. | |
| ...(explicitSessionDisplayName ? { displayName: explicitSessionDisplayName } : {}), | |
| ...(freshSpawnedBy ? { spawnedBy: freshSpawnedBy } : {}), | |
| groupId: nextGroup.groupId, | |
| groupChannel: nextGroup.groupChannel, | |
| space: nextGroup.groupSpace, | |
| // Plugin ownership is creation-only; existing sessions keep their original owner. | |
| ...(params.freshEntry === undefined && params.pluginOwnerId | |
| ? { pluginOwnerId: params.pluginOwnerId } | |
| : {}), | |
| ...(shouldClearRotatedState || shouldClearTerminalState | |
| ? { | |
| status: undefined, | |
| lifecycleRunId: undefined, | |
| lastRunId: undefined, | |
| startedAt: undefined, | |
| endedAt: undefined, | |
| runtimeMs: undefined, | |
| abortedLastRun: undefined, | |
| } | |
| : {}), | |
| }; | |
| if (shouldClearRotatedState) { | |
| clearAllCliSessions(patch); | |
| } | |
| return { | |
| patch, | |
| spawnedBy: freshSpawnedBy, | |
| groupId: nextGroup.groupId, | |
| groupChannel: nextGroup.groupChannel, | |
| groupSpace: nextGroup.groupSpace, | |
| freshSessionRotatedSinceLoad, | |
| isNewSession: reuse.isNewSession, | |
| rotatedSessionId: freshRotatedSessionId, | |
| usableRequestedSessionId: reuse.usableRequestedSessionId, | |
| freshness: reuse.freshness, | |
| }; | |
| } | |