openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
454 lines (453 loc) • 18.1 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { m as resolveAgentWorkspaceDir } from "./agent-scope-config-DcbEhP0R.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { A as resolveCollapsedSessionAuthPinSource } from "./agent-scope-DbtJyKUL.js";
import { o as normalizeReasoningLevel, s as normalizeThinkLevel } from "./thinking.shared-bHYuuc1L.js";
import { t as ErrorCodes } from "./gateway-error-details-w0nAGBBp.js";
import { qr as validateSessionsCompactParams } from "./src-BiL5aQto.js";
import { d as errorShape } from "./error-codes-Bo8q2D1o.js";
import { c as preflightManualSessionCompaction } from "./sessions-BdNAJTEP.js";
import "./session-manager-codec-DIXSw5vP.js";
import { B as resolveSessionTranscriptRuntimeTarget, Lt as applySessionPatchProjection, X as trimSessionTranscriptForManualCompact, Y as preflightSessionTranscriptForManualCompact } from "./session-accessor-YsytfDtG.js";
import { M as readTranscriptStatsSync, b as loadTranscriptEvents } from "./session-accessor.sqlite-transcript-store-B0zw3fAU.js";
import { f as isCompetingSessionWorkAdmissionActive, h as runExclusiveSessionLifecycleMutation } from "./session-lifecycle-admission-CS8v45tk.js";
import { c as scanSessionTranscriptTree, d as selectSessionTranscriptTreePathNodes } from "./transcript-tree-BH69pMSi.js";
import { t as clearAllCliSessions } from "./cli-session-binding-Aa6nlDa8.js";
import { i as isIndexedSessionEntry } from "./session-entry-codec-6GPwHDjQ.js";
import "./thinking-DterdXhr.js";
import { n as resolveManualCompactionCliTarget } from "./session-runtime-compat-DNAICzi6.js";
import { l as recordSessionCompacted } from "./session-state-events-DNCKmH78.js";
import { a as getCommandLaneSnapshot } from "./command-queue-C3Fv2rcU.js";
import "./sessions-9nxpeTwt.js";
import { f as resolveSessionWorkStartError, t as SESSION_LIFECYCLE_CHANGED_ERROR_REASON } from "./lifecycle-BaroCBMc.js";
import "./cli-session-pAa_-y_N.js";
import { n as resolveSessionModelRef } from "./session-model-ref-CPZiclLt.js";
import { i as tryResolveSessionCompatibilityOwnerAgentId, n as resolveRequestedSessionAgentId } from "./session-request-agent-CCRSEGCB.js";
import { f as resolveGatewaySessionStoreTargetWithStore, o as resolveCanonicalGatewaySessionStoreKey } from "./session-utils-store-CInT2loy.js";
import { s as hasPendingFollowupQueueWork } from "./settings-jmDsm5RI.js";
import "./session-utils-Cai0_C6U.js";
import { t as resolveEmbeddedSessionLane } from "./lanes-CVttd5qX.js";
import { r as resolveIngressWorkspaceOverrideForSessionRun } from "./spawned-context-Dt3YQx79.js";
import { n as compactEmbeddedAgentSession } from "./embedded-agent-D9hkv0XX.js";
import { o as resolveCurrentSessionPrimaryConversation } from "./conversation-registry-By0ZasAK.js";
import { t as assertValidParams } from "./validation-pzrlzFvo.js";
import { i as resolveVisibleActiveSessionRunState } from "./session-active-runs-CCNuJW7B.js";
import { n as emitSessionsChanged } from "./session-change-event-DzmH4zlz.js";
import { t as asWorkerInferenceControl } from "./inference-control-CDvM08Nt.js";
import { i as loadAccessorSessionEntryForGatewayTarget, s as requireSessionKey, t as emitSessionOperation } from "./sessions-shared-D_lV07q2.js";
import { randomUUID } from "node:crypto";
//#region src/gateway/server-methods/sessions-compaction-runner.ts
function usesLegacyOpenClawCompaction(params) {
const resolvedModel = resolveSessionModelRef(params.cfg, params.entry, params.agentId);
const persistedRuntime = resolveManualCompactionCliTarget({
provider: resolvedModel.provider,
entry: params.entry,
cfg: params.cfg
}).agentHarnessId;
const contextEngine = params.cfg.plugins?.slots?.contextEngine?.trim();
return (!persistedRuntime || persistedRuntime === "openclaw") && (!contextEngine || contextEngine === "legacy");
}
async function resolveGatewayCompactionTranscriptTarget(params) {
return await resolveSessionTranscriptRuntimeTarget({
agentId: params.agentId,
sessionId: params.sessionId,
sessionKey: params.sessionStoreKey,
storePath: params.storePath
});
}
/** Returns only definitive legacy-runtime no-op verdicts; other runtimes decide for themselves. */
async function preflightGatewaySessionCompaction(params) {
if (!usesLegacyOpenClawCompaction(params)) return;
try {
const transcriptEvents = await loadTranscriptEvents({
agentId: params.agentId,
sessionId: params.sessionId,
sessionKey: params.sessionStoreKey,
storePath: params.storePath
});
const tree = scanSessionTranscriptTree(transcriptEvents);
const branch = selectSessionTranscriptTreePathNodes(tree, tree.leafId).map((node) => node.entry).filter(isIndexedSessionEntry);
const preflight = preflightManualSessionCompaction(branch, {
enabled: true,
reserveTokens: 0,
keepRecentTokens: 0
});
return preflight.compactable ? void 0 : { reason: preflight.reason };
} catch {
return;
}
}
async function runGatewaySessionCompaction(params, host) {
const transcriptTarget = await resolveGatewayCompactionTranscriptTarget(params);
const resolvedModel = resolveSessionModelRef(params.cfg, params.entry, params.agentId);
const workspaceDir = resolveIngressWorkspaceOverrideForSessionRun({
spawnedBy: params.entry.spawnedBy,
workspaceDir: params.entry.spawnedWorkspaceDir,
cwd: params.entry.spawnedCwd
}) ?? resolveAgentWorkspaceDir(params.cfg, params.agentId);
const compactionCliTarget = resolveManualCompactionCliTarget({
provider: resolvedModel.provider,
entry: params.entry,
cfg: params.cfg
});
const primaryConversation = resolveCurrentSessionPrimaryConversation(transcriptTarget);
return await compactEmbeddedAgentSession({
contextEngineAgentId: params.agentId,
runId: params.runId,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
agentId: params.agentId,
sessionTarget: {
agentId: params.agentId,
sessionId: params.sessionId,
sessionKey: params.sessionKey,
storePath: params.storePath
},
allowGatewaySubagentBinding: true,
sessionFile: transcriptTarget.sessionKey,
workspaceDir,
cwd: normalizeOptionalString(params.entry.spawnedCwd),
config: params.cfg,
agentAccountId: params.entry.delivery?.kind === "external" ? params.entry.delivery.context?.accountId : void 0,
conversationRoutePeerId: primaryConversation?.routeContext?.peerId,
chatType: primaryConversation?.kind,
provider: resolvedModel.provider,
model: resolvedModel.model,
authProfileId: compactionCliTarget.cliSessionBinding?.authProfileId ?? params.entry.authProfileOverride,
authProfileIdSource: resolveCollapsedSessionAuthPinSource(params.entry),
agentHarnessId: compactionCliTarget.agentHarnessId,
cliSessionId: compactionCliTarget.cliSessionId,
cliSessionBinding: compactionCliTarget.cliSessionBinding,
sessionEntry: params.entry,
modelSelectionLocked: params.entry.modelSelectionLocked === true,
thinkLevel: normalizeThinkLevel(params.entry.thinkingLevel),
reasoningLevel: normalizeReasoningLevel(params.entry.reasoningLevel),
bashElevated: {
enabled: false,
allowed: false,
defaultLevel: "off"
},
trigger: "manual"
}, host);
}
//#endregion
//#region src/gateway/server-methods/sessions-compact.ts
const sessionCompactHandlers = { "sessions.compact": async ({ params, respond, context }) => {
if (!assertValidParams(params, validateSessionsCompactParams, "sessions.compact", respond)) return;
const p = params;
const key = requireSessionKey(p.key, respond);
if (!key) return;
const maxLines = typeof p.maxLines === "number" && Number.isFinite(p.maxLines) ? Math.max(1, Math.floor(p.maxLines)) : void 0;
const cfg = context.getRuntimeConfig();
const requestedAgent = resolveRequestedSessionAgentId(cfg, key, p.agentId);
if (!requestedAgent.ok) {
respond(false, void 0, requestedAgent.error);
return;
}
const requestedAgentId = requestedAgent.agentId;
const compatibilityDefaultAgentId = tryResolveSessionCompatibilityOwnerAgentId(cfg, key);
const target = resolveGatewaySessionStoreTargetWithStore({
cfg,
key,
exactRead: true,
...requestedAgentId ? { agentId: requestedAgentId } : {}
});
const storePath = target.storePath;
let compactPrimaryKey = target.canonicalKey;
const compactRead = await applySessionPatchProjection({
agentId: target.agentId,
sessionKeys: target.storeKeys,
storePath,
resolveTarget: ({ store }) => {
const { target: migratedTarget, primaryKey } = resolveCanonicalGatewaySessionStoreKey({
cfg,
key,
store,
agentId: requestedAgentId
});
compactPrimaryKey = primaryKey;
return {
primaryKey,
candidateKeys: migratedTarget.storeKeys
};
},
project: ({ existingEntry }) => existingEntry ? {
ok: true,
entry: existingEntry
} : { ok: false }
});
const compactTarget = {
entry: compactRead.ok ? compactRead.entry : void 0,
primaryKey: compactPrimaryKey
};
const entry = compactTarget.entry;
const sessionId = entry?.sessionId;
if (!sessionId) {
respond(true, {
ok: true,
key: target.canonicalKey,
compacted: false,
reason: "no sessionId"
}, void 0);
return;
}
if (maxLines !== void 0) {
const trimPreflight = await preflightSessionTranscriptForManualCompact({
sessionId,
storePath,
sessionKey: compactTarget.primaryKey,
agentId: target.agentId
}, { maxLines });
if (!trimPreflight.compacted) {
respond(true, {
ok: true,
key: target.canonicalKey,
compacted: false,
..."kept" in trimPreflight ? { kept: trimPreflight.kept } : { reason: "no transcript" }
}, void 0);
return;
}
} else if (readTranscriptStatsSync({
agentId: target.agentId,
sessionId,
sessionKey: compactTarget.primaryKey,
storePath
}).eventCount === 0) {
respond(true, {
ok: true,
key: target.canonicalKey,
compacted: false,
reason: "no transcript"
}, void 0);
return;
}
const lifecycleRevision = entry.lifecycleRevision;
const queueIdentities = [
key,
target.canonicalKey,
compactTarget.primaryKey,
sessionId
];
const lifecycleIdentities = [...queueIdentities, lifecycleRevision];
let sessionStillCurrent = true;
let compactionNoopReason;
let blockedByActiveRun = false;
let blockedByQueuedWork = false;
try {
await runExclusiveSessionLifecycleMutation({
scope: storePath,
identities: lifecycleIdentities,
kind: "compaction",
prepare: async () => {
const latestEntry = loadAccessorSessionEntryForGatewayTarget({
key,
cfg,
agentId: requestedAgentId
}).entry;
if (!latestEntry || latestEntry.sessionId !== sessionId || latestEntry.lifecycleRevision !== lifecycleRevision || resolveSessionWorkStartError(target.canonicalKey, latestEntry)) {
sessionStillCurrent = false;
return;
}
if (maxLines === void 0) {
compactionNoopReason = (await preflightGatewaySessionCompaction({
cfg,
entry: latestEntry,
agentId: target.agentId,
sessionId,
sessionKey: target.canonicalKey,
sessionStoreKey: compactTarget.primaryKey,
storePath
}))?.reason;
if (compactionNoopReason) return;
}
blockedByActiveRun = isCompetingSessionWorkAdmissionActive(storePath, lifecycleIdentities) || (asWorkerInferenceControl(context.workerEnvironmentService)?.hasInferenceForSession(sessionId) ?? false) || resolveVisibleActiveSessionRunState({
context,
requestedKey: key,
canonicalKey: target.canonicalKey,
sessionId,
agentId: requestedAgentId,
defaultAgentId: compatibilityDefaultAgentId
}).active;
blockedByQueuedWork = hasPendingFollowupQueueWork(queueIdentities) || queueIdentities.some((identity) => getCommandLaneSnapshot(resolveEmbeddedSessionLane(identity)).queuedCount > 0);
},
run: async () => {
if (!sessionStillCurrent) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `Session ${key} changed before compaction. Retry.`, { details: { reason: SESSION_LIFECYCLE_CHANGED_ERROR_REASON } }));
return;
}
if (compactionNoopReason) {
respond(true, {
ok: false,
key: target.canonicalKey,
compacted: false,
reason: compactionNoopReason
}, void 0);
return;
}
if (blockedByQueuedWork) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `Session ${key} has queued work; retry after it finishes.`));
return;
}
if (blockedByActiveRun) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `Session ${key} has an active run; retry after it finishes.`));
return;
}
const latestEntry = loadAccessorSessionEntryForGatewayTarget({
key,
cfg,
agentId: requestedAgentId
}).entry;
if (!latestEntry || latestEntry.sessionId !== sessionId || latestEntry.lifecycleRevision !== lifecycleRevision || resolveSessionWorkStartError(target.canonicalKey, latestEntry)) {
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, `Session ${key} changed before compaction. Retry.`, { details: { reason: SESSION_LIFECYCLE_CHANGED_ERROR_REASON } }));
return;
}
const operationId = randomUUID();
if (maxLines !== void 0) {
const trimResult = await trimSessionTranscriptForManualCompact({
sessionId,
storePath,
sessionKey: compactTarget.primaryKey,
agentId: target.agentId
}, { maxLines });
respond(true, {
ok: true,
key: target.canonicalKey,
compacted: trimResult.compacted,
...trimResult.compacted ? { kept: trimResult.kept } : "kept" in trimResult ? { kept: trimResult.kept } : { reason: "no transcript" }
}, void 0);
if (trimResult.compacted) {
recordSessionCompacted({
sessionKey: target.canonicalKey,
operationId,
sessionId,
agentId: target.agentId ?? requestedAgentId
});
emitSessionsChanged(context, {
sessionKey: target.canonicalKey,
agentId: target.agentId,
reason: "compact",
compacted: true
});
}
return;
}
if (readTranscriptStatsSync({
agentId: target.agentId,
sessionId,
sessionKey: compactTarget.primaryKey,
storePath
}).eventCount === 0) {
respond(true, {
ok: true,
key: target.canonicalKey,
compacted: false,
reason: "no transcript"
}, void 0);
return;
}
emitSessionOperation(context, {
operationId,
operation: "compact",
phase: "start",
sessionKey: target.canonicalKey,
agentId: target.agentId
});
const emitCompactionEnd = (completed, reason) => emitSessionOperation(context, {
operationId,
operation: "compact",
phase: "end",
sessionKey: target.canonicalKey,
agentId: target.agentId,
completed,
reason
});
let result;
let expectedEntry = latestEntry;
try {
result = await runGatewaySessionCompaction({
cfg,
entry: latestEntry,
runId: operationId,
agentId: target.agentId,
sessionId,
sessionKey: target.canonicalKey,
sessionStoreKey: compactTarget.primaryKey,
storePath
}, { onCommitted: (accepted) => {
expectedEntry = accepted.entry;
} });
} catch (err) {
emitCompactionEnd(false, formatErrorMessage(err));
throw err;
}
if (result.ok && result.compacted) {
let persisted;
try {
persisted = (await applySessionPatchProjection({
agentId: target.agentId,
sessionKeys: [compactTarget.primaryKey],
storePath,
resolveTarget: () => ({ primaryKey: compactTarget.primaryKey }),
project: ({ existingEntry }) => {
if (!existingEntry || existingEntry.sessionId !== expectedEntry.sessionId || existingEntry.lifecycleRevision !== expectedEntry.lifecycleRevision || existingEntry.activeWriterRunId !== expectedEntry.activeWriterRunId || resolveSessionWorkStartError(target.canonicalKey, existingEntry)) return { ok: false };
const entryToUpdate = existingEntry;
entryToUpdate.updatedAt = Date.now();
entryToUpdate.compactionCount = Math.max(0, entryToUpdate.compactionCount ?? 0) + 1;
if (result.compactionKind === "context-engine") clearAllCliSessions(entryToUpdate);
delete entryToUpdate.inputTokens;
delete entryToUpdate.outputTokens;
delete entryToUpdate.contextBudgetStatus;
if (typeof result.result?.tokensAfter === "number" && Number.isFinite(result.result.tokensAfter)) {
entryToUpdate.totalTokens = result.result.tokensAfter;
entryToUpdate.totalTokensFresh = true;
entryToUpdate.totalTokensVersion = 1;
} else {
delete entryToUpdate.totalTokens;
delete entryToUpdate.totalTokensFresh;
delete entryToUpdate.totalTokensVersion;
}
return {
ok: true,
entry: entryToUpdate
};
}
})).ok;
} catch (err) {
emitCompactionEnd(false, formatErrorMessage(err));
throw err;
}
if (!persisted) {
const reason = `Session ${key} changed before compaction completed. Retry.`;
emitCompactionEnd(false, reason);
respond(false, void 0, errorShape(ErrorCodes.INVALID_REQUEST, reason, { details: { reason: SESSION_LIFECYCLE_CHANGED_ERROR_REASON } }));
return;
}
recordSessionCompacted({
sessionKey: target.canonicalKey,
operationId,
sessionId: expectedEntry.sessionId,
agentId: target.agentId ?? requestedAgentId
});
}
emitCompactionEnd(result.ok && result.compacted, result.reason);
respond(true, {
ok: result.ok,
key: target.canonicalKey,
compacted: result.compacted,
reason: result.reason,
result: result.result
}, void 0);
if (result.ok) emitSessionsChanged(context, {
sessionKey: target.canonicalKey,
agentId: target.agentId,
reason: "compact",
compacted: result.compacted
});
}
});
} catch (err) {
respond(false, void 0, errorShape(ErrorCodes.UNAVAILABLE, formatErrorMessage(err)));
}
} };
//#endregion
export { sessionCompactHandlers };