openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
236 lines (235 loc) • 9.67 kB
JavaScript
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 };