UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

236 lines (235 loc) 9.67 kB
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { v as resolveSessionAgentId } from "./agent-scope-MrLta7Pq.js"; import { f as resolveAgentIdFromSessionKey } from "./session-key-B_NoIfpX.js"; import { r as logVerbose } from "./globals-GTrXU4s9.js"; import { t as getGlobalHookRunner } from "./hook-runner-global-BnyQREr6.js"; import { a as resolveSessionFilePathOptions, i as resolveSessionFilePath } from "./paths-NEwU8m3X.js"; import { d as updateSessionStore } from "./store-Qsgtu-0y.js"; import { f as canonicalizeAbsoluteSessionFilePath, p as rewriteSessionFileForNewSessionId } from "./types-D8S_uNvu.js"; import "./sessions-D6zZvoxX.js"; import { o as resolveStableSessionEndTranscript } from "./session-transcript-files.fs-CM11t-8l.js"; import { t as canExecRequestNode } from "./exec-defaults-BenGbA5f.js"; import { a as buildSessionStartHookPayload, i as buildSessionEndHookPayload, r as noteActiveSessionForShutdown, t as forgetActiveSessionForShutdown } from "./active-sessions-shutdown-tracker-D9lhBvd4.js"; import { t as getRemoteSkillEligibility } from "./remote-DoOmoEi1.js"; import "./session-system-events-C9gxjYmo.js"; import { n as resolveReusableWorkspaceSkillSnapshot } from "./session-snapshot-CO_70mfK.js"; import path from "node:path"; import crypto from "node:crypto"; //#region src/auto-reply/reply/session-updates.ts /** Session update helpers for skill snapshots, compaction, and lifecycle hooks. */ async function persistSessionEntryUpdate(params) { if (!params.sessionStore || !params.sessionKey) return; params.sessionStore[params.sessionKey] = { ...params.sessionStore[params.sessionKey], ...params.nextEntry }; if (!params.storePath) return; await updateSessionStore(params.storePath, (store) => { const next = { ...store[params.sessionKey], ...params.nextEntry }; store[params.sessionKey] = next; return next; }, { resolveSingleEntryPersistence: (entry) => entry && params.sessionKey ? { sessionKey: params.sessionKey, entry } : null }); } function emitCompactionSessionLifecycleHooks(params) { if (params.previousEntry.sessionId) forgetActiveSessionForShutdown(params.previousEntry.sessionId); if (params.nextEntry.sessionId && params.storePath) noteActiveSessionForShutdown({ cfg: params.cfg, sessionKey: params.sessionKey, sessionId: params.nextEntry.sessionId, storePath: params.storePath, sessionFile: params.nextEntry.sessionFile, agentId: resolveAgentIdFromSessionKey(params.sessionKey) }); const hookRunner = getGlobalHookRunner(); if (!hookRunner) return; if (hookRunner.hasHooks("session_end")) { const transcript = resolveStableSessionEndTranscript({ sessionId: params.previousEntry.sessionId, storePath: params.storePath, sessionFile: params.previousEntry.sessionFile, agentId: resolveAgentIdFromSessionKey(params.sessionKey) }); const payload = buildSessionEndHookPayload({ sessionId: params.previousEntry.sessionId, sessionKey: params.sessionKey, cfg: params.cfg, reason: "compaction", sessionFile: transcript.sessionFile, transcriptArchived: transcript.transcriptArchived, nextSessionId: params.nextEntry.sessionId }); hookRunner.runSessionEnd(payload.event, payload.context).catch((err) => { logVerbose(`session_end hook failed: ${String(err)}`); }); } if (hookRunner.hasHooks("session_start")) { const payload = buildSessionStartHookPayload({ sessionId: params.nextEntry.sessionId, sessionKey: params.sessionKey, cfg: params.cfg, resumedFrom: params.previousEntry.sessionId }); hookRunner.runSessionStart(payload.event, payload.context).catch((err) => { logVerbose(`session_start hook failed: ${String(err)}`); }); } } function resolveNonNegativeTokenCount(value) { return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0; } /** Ensures a session entry has the reusable skill snapshot needed for reply runs. */ async function ensureSkillSnapshot(params) { if (process.env.OPENCLAW_TEST_FAST === "1") return { sessionEntry: params.sessionEntry, skillsSnapshot: params.sessionEntry?.skillsSnapshot, systemSent: params.sessionEntry?.systemSent ?? false }; const { sessionEntry, sessionStore, sessionKey, storePath, sessionId, isFirstTurnInSession, workspaceDir, cfg, skillFilter } = params; let nextEntry = sessionEntry; let systemSent = sessionEntry?.systemSent ?? false; const sessionAgentId = resolveSessionAgentId({ sessionKey, config: cfg }); const remoteEligibility = getRemoteSkillEligibility({ advertiseExecNode: canExecRequestNode({ cfg, sessionEntry, sessionKey, agentId: sessionAgentId }) }); const existingSnapshot = nextEntry?.skillsSnapshot; const resolveSnapshot = (snapshot) => resolveReusableWorkspaceSkillSnapshot({ workspaceDir, config: cfg, agentId: sessionAgentId, skillFilter, eligibility: { remote: remoteEligibility }, existingSnapshot: snapshot }); const initialSnapshotState = resolveSnapshot(existingSnapshot); const shouldRefreshSnapshot = initialSnapshotState.shouldRefresh; if (isFirstTurnInSession && sessionStore && sessionKey) { const current = nextEntry ?? sessionStore[sessionKey] ?? { sessionId: sessionId ?? crypto.randomUUID(), updatedAt: Date.now() }; const skillSnapshot = !current.skillsSnapshot || shouldRefreshSnapshot ? initialSnapshotState.snapshot : resolveSnapshot(current.skillsSnapshot).snapshot; nextEntry = { ...current, sessionId: sessionId ?? current.sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), systemSent: true, skillsSnapshot: skillSnapshot }; await persistSessionEntryUpdate({ sessionStore, sessionKey, storePath, nextEntry }); systemSent = true; } const skillsSnapshot = Boolean(nextEntry?.skillsSnapshot) && (nextEntry?.skillsSnapshot !== existingSnapshot || !shouldRefreshSnapshot) && nextEntry?.skillsSnapshot ? resolveSnapshot(nextEntry.skillsSnapshot).snapshot : shouldRefreshSnapshot || !nextEntry?.skillsSnapshot ? initialSnapshotState.snapshot : resolveSnapshot(nextEntry.skillsSnapshot).snapshot; if (skillsSnapshot && sessionStore && sessionKey && !isFirstTurnInSession && (!nextEntry?.skillsSnapshot || shouldRefreshSnapshot)) { const current = nextEntry ?? { sessionId: sessionId ?? crypto.randomUUID(), updatedAt: Date.now() }; nextEntry = { ...current, sessionId: sessionId ?? current.sessionId ?? crypto.randomUUID(), updatedAt: Date.now(), skillsSnapshot }; await persistSessionEntryUpdate({ sessionStore, sessionKey, storePath, nextEntry }); } return { sessionEntry: nextEntry, skillsSnapshot, systemSent }; } /** Increments compaction count and persists the updated session entry. */ async function incrementCompactionCount(params) { const { sessionEntry, sessionStore, sessionKey, storePath, cfg, now = Date.now(), amount = 1, tokensAfter, newSessionId, newSessionFile } = params; if (!sessionStore || !sessionKey) return; const entry = sessionStore[sessionKey] ?? sessionEntry; if (!entry) return; const incrementBy = Math.max(0, amount); const nextCount = (entry.compactionCount ?? 0) + incrementBy; const updates = { compactionCount: nextCount, updatedAt: now }; const explicitNewSessionFile = normalizeOptionalString(newSessionFile); const sessionIdChanged = Boolean(newSessionId && newSessionId !== entry.sessionId); const sessionFileChanged = Boolean(explicitNewSessionFile && explicitNewSessionFile !== entry.sessionFile); if (sessionIdChanged && newSessionId) { updates.sessionId = newSessionId; updates.sessionFile = explicitNewSessionFile ?? resolveCompactionSessionFile({ entry, sessionKey, storePath, newSessionId }); updates.usageFamilyKey = entry.usageFamilyKey ?? sessionKey; updates.usageFamilySessionIds = Array.from(new Set([ ...entry.usageFamilySessionIds ?? [], entry.sessionId, newSessionId ])); } else if (sessionFileChanged && explicitNewSessionFile) updates.sessionFile = explicitNewSessionFile; const tokensAfterCompaction = resolveNonNegativeTokenCount(tokensAfter); if (tokensAfterCompaction !== void 0) { updates.totalTokens = tokensAfterCompaction; updates.totalTokensFresh = true; updates.inputTokens = void 0; updates.outputTokens = void 0; updates.cacheRead = void 0; updates.cacheWrite = void 0; } else if (incrementBy > 0) updates.totalTokensFresh = false; sessionStore[sessionKey] = { ...entry, ...updates }; if (storePath) await updateSessionStore(storePath, (store) => { store[sessionKey] = { ...store[sessionKey], ...updates }; }); if ((sessionIdChanged || sessionFileChanged) && cfg) emitCompactionSessionLifecycleHooks({ cfg, sessionKey, storePath, previousEntry: entry, nextEntry: sessionStore[sessionKey] }); return nextCount; } function resolveCompactionSessionFile(params) { const pathOpts = resolveSessionFilePathOptions({ agentId: resolveAgentIdFromSessionKey(params.sessionKey), storePath: params.storePath }); const rewrittenSessionFile = rewriteSessionFileForNewSessionId({ sessionFile: params.entry.sessionFile, previousSessionId: params.entry.sessionId, nextSessionId: params.newSessionId }); const normalizedRewrittenSessionFile = rewrittenSessionFile && path.isAbsolute(rewrittenSessionFile) ? canonicalizeAbsoluteSessionFilePath(rewrittenSessionFile) : rewrittenSessionFile; return resolveSessionFilePath(params.newSessionId, normalizedRewrittenSessionFile ? { sessionFile: normalizedRewrittenSessionFile } : void 0, pathOpts); } //#endregion export { incrementCompactionCount as n, ensureSkillSnapshot as t };