UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

454 lines (453 loc) 18.1 kB
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 };