openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
390 lines (389 loc) • 20.2 kB
JavaScript
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { D as tryResolveLegacyCompatibilityAgentId, o as listAgentIds, t as AgentSelectionRequiredError, y as resolveDefaultAgentId } from "./agent-scope-config-DcbEhP0R.js";
import { O as parseAgentSessionKey, c as classifySessionKeyShape, f as resolveAgentIdFromSessionKey, l as isUnscopedSessionKeySentinel, u as normalizeMainKey } from "./session-key-BnWWjqNc.js";
import "./legacy.default-agent-owner-BGwEdQRe.js";
import { o as resolveSessionStorePathCore } from "./paths-CXdaYWF_.js";
import "./agent-scope-DbtJyKUL.js";
import { n as resolvePersistedSessionStoreOwnerForKey, t as resolvePersistedSessionStoreOwner } from "./session-store-owner-CR2Ag3xK.js";
import { s as normalizeThinkLevel, u as normalizeVerboseLevel } from "./thinking.shared-bHYuuc1L.js";
import { d as sessionDeliveryChannel } from "./delivery-context.shared-CXmRgetN.js";
import "./session-accessor-YsytfDtG.js";
import { A as isInternalSessionEffectsKey, a as listSessionEntriesReadOnly, w as loadExactSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { i as resolveExplicitAgentSessionKey, n as canonicalizeMainSessionAlias } from "./main-session-Br0F9dzh.js";
import { s as isModelSelectionLocked } from "./model-overrides-Bm7jo7Di.js";
import { t as clearAllCliSessions } from "./cli-session-binding-Aa6nlDa8.js";
import "./thinking-DterdXhr.js";
import { c as hasTerminalMainSessionTranscriptNewerThanRegistrySync, d as resolveSessionLifecycleTimestamps } from "./lifecycle-BaroCBMc.js";
import { n as resolveSessionResetPolicy, t as evaluateSessionFreshness } from "./reset-policy-Bcf937ne.js";
import { n as resolveSessionResetType, t as resolveChannelResetConfig } from "./reset-DkY1cXo4.js";
import { n as resolveSessionKey } from "./session-key-CwhWkrL5.js";
import { t as hasProviderOwnedSession } from "./entry-freshness-C81RWf2e.js";
import { n as resolveSessionIdMatchSelection } from "./session-id-resolution-BbcM7Och.js";
import { r as clearBootstrapSnapshotOnSessionRollover } from "./bootstrap-cache-Bpsy6pH_.js";
import "./cli-session-pAa_-y_N.js";
import { s as transitionMainSessionRecovery } from "./main-session-recovery-state-DD1DvBOq.js";
import path from "node:path";
import crypto from "node:crypto";
//#region src/agents/command/session.ts
/**
* Resolves command session ids, keys, stores, and persisted thinking state.
*/
function clearRotatedSessionMetadata(entry) {
const next = {
...entry,
sessionFile: void 0,
status: void 0,
lifecycleRunId: void 0,
lastRunId: void 0,
startedAt: void 0,
endedAt: void 0,
runtimeMs: void 0,
abortedLastRun: void 0,
restartRecoveryForceSafeTools: void 0,
restartRecoveryDeliveryContext: void 0,
restartRecoveryDeliveryMediaUrls: void 0,
restartRecoveryDisableMessageTool: void 0,
restartRecoverySuppressTextDelivery: void 0,
restartRecoveryDeliveryRequestFingerprint: void 0,
restartRecoveryDeliveryRunId: void 0,
restartRecoveryDeliverySourceRunId: void 0,
restartRecoveryBeforeAgentReplyState: void 0,
restartRecoveryDeliveryReceiptState: void 0,
restartRecoveryDeliveryToolCallId: void 0,
restartRecoveryRequesterAccountId: void 0,
restartRecoveryRequesterSenderId: void 0,
restartRecoverySameChannelThreadRequired: void 0,
restartRecoverySourceIngress: void 0,
restartRecoverySourceReplyDeliveryMode: void 0,
restartRecoveryTerminalDeliveryEvidence: void 0,
restartRecoveryTerminalRunIds: void 0,
sessionStartedAt: void 0,
sessionDiffBaseline: void 0,
sessionDiffBaselineCapture: void 0,
lastInteractionAt: void 0,
pendingTranscriptRepair: void 0
};
transitionMainSessionRecovery(next, { kind: "clear" });
clearAllCliSessions(next);
return next;
}
function selectSessionIdMatchCandidate(candidates, sessionId) {
const selection = resolveSessionIdMatchSelection(candidates.map((candidate) => [candidate.sessionKey, candidate.entry]), sessionId);
if (selection.kind !== "selected") return;
return candidates.filter((candidate) => candidate.sessionKey === selection.sessionKey).toSorted((left, right) => {
const updatedAt = (right.entry.updatedAt ?? 0) - (left.entry.updatedAt ?? 0);
if (updatedAt !== 0) return updatedAt;
if (left.primary !== right.primary) return left.primary ? -1 : 1;
return (left.resolution.agentId ?? "").localeCompare(right.resolution.agentId ?? "");
})[0];
}
function loadCommandSessionEntries(params) {
return listSessionEntriesReadOnly({
storePath: params.storePath,
...params.agentId ? { agentId: params.agentId } : {},
clone: false
});
}
/** Builds the synthetic session key used for explicit session-id runs. */
function buildExplicitSessionIdSessionKey(params) {
return `agent:${normalizeAgentId(params.agentId)}:explicit:${params.sessionId.trim()}`;
}
function collectSessionIdMatchesForRequest(opts) {
const candidates = [];
let ownerConflict = false;
const configuredAgentIds = listAgentIds(opts.cfg).map(normalizeAgentId);
const compatibilityAgentId = tryResolveLegacyCompatibilityAgentId(opts.cfg);
const persistedStoreOwner = resolvePersistedSessionStoreOwner(opts.cfg);
const configuredStoreOwners = /* @__PURE__ */ new Map();
for (const agentId of configuredAgentIds) {
const configuredStorePath = path.resolve(resolveSessionStorePathCore(opts.cfg.session?.store, { agentId }));
const owners = configuredStoreOwners.get(configuredStorePath) ?? /* @__PURE__ */ new Set();
owners.add(agentId);
configuredStoreOwners.set(configuredStorePath, owners);
}
const addMatches = (candidateEntries, candidateStorePath, candidateAgentId, options) => {
for (const { sessionKey: candidateKey, entry: candidateEntry } of candidateEntries) {
if (candidateEntry?.sessionId !== opts.sessionId) continue;
const normalizedCandidateAgentId = candidateAgentId ? normalizeAgentId(candidateAgentId) : void 0;
const scopedCandidateAgentId = normalizedCandidateAgentId && configuredAgentIds.includes(normalizedCandidateAgentId) ? normalizedCandidateAgentId : void 0;
const pathOwners = configuredStoreOwners.get(path.resolve(candidateStorePath));
const pathOwnedAgentId = pathOwners?.size === 1 ? pathOwners.values().next().value : void 0;
const parsedAgentId = parseAgentSessionKey(candidateKey)?.agentId;
const normalizedParsedAgentId = parsedAgentId ? normalizeAgentId(parsedAgentId) : void 0;
if (normalizedParsedAgentId && !configuredAgentIds.includes(normalizedParsedAgentId)) continue;
const isLegacyUnscopedKey = classifySessionKeyShape(candidateKey) === "legacy_or_alias";
const legacyUnscopedOwner = isLegacyUnscopedKey ? persistedStoreOwner.kind === "configured" ? persistedStoreOwner.agentId : persistedStoreOwner.kind === "retired" ? void 0 : pathOwnedAgentId ?? (opts.searchOtherAgentStores ? void 0 : scopedCandidateAgentId) ?? compatibilityAgentId : void 0;
const matchedAgentId = normalizedParsedAgentId ?? (isLegacyUnscopedKey ? legacyUnscopedOwner : scopedCandidateAgentId ?? compatibilityAgentId);
if (isLegacyUnscopedKey && persistedStoreOwner.kind === "retired") {
ownerConflict = true;
continue;
}
if (!opts.searchOtherAgentStores && scopedCandidateAgentId && matchedAgentId && normalizeAgentId(matchedAgentId) !== scopedCandidateAgentId) {
ownerConflict = true;
continue;
}
candidates.push({
sessionKey: candidateKey,
entry: candidateEntry,
primary: options?.primary === true,
resolution: {
...matchedAgentId ? { agentId: normalizeAgentId(matchedAgentId) } : {},
sessionKey: candidateKey,
storePath: candidateStorePath
}
});
}
};
addMatches(opts.sessionEntries, opts.storePath, opts.storeAgentId, { primary: true });
if (!opts.searchOtherAgentStores) return {
candidates,
ownerConflict
};
for (const agentId of configuredAgentIds) {
if (agentId === opts.storeAgentId) continue;
const candidateStorePath = resolveSessionStorePathCore(opts.cfg.session?.store, { agentId });
addMatches(loadCommandSessionEntries({
agentId,
storePath: candidateStorePath
}), candidateStorePath, agentId);
}
return {
candidates,
ownerConflict
};
}
/**
* Resolve an existing stored session key for a session id from a specific agent store.
* This scopes the lookup to the target store without implicitly converting `agentId`
* into that agent's main session key.
*/
function resolveStoredSessionKeyForSessionId(opts) {
const sessionId = opts.sessionId.trim();
const requestedAgentId = opts.agentId?.trim() ? normalizeAgentId(opts.agentId) : void 0;
const persistedStoreOwner = resolvePersistedSessionStoreOwner(opts.cfg);
const storeAgentId = requestedAgentId ?? (persistedStoreOwner.kind === "configured" ? persistedStoreOwner.agentId : void 0) ?? tryResolveLegacyCompatibilityAgentId(opts.cfg) ?? resolveDefaultAgentId(opts.cfg, {
surface: "stored session lookup",
hint: "Pass an explicit agent id when looking up a session by id."
});
const storePath = resolveSessionStorePathCore(opts.cfg.session?.store, { agentId: storeAgentId });
const sessionEntries = loadCommandSessionEntries({
storePath,
agentId: storeAgentId
});
if (!sessionId) return {
sessionKey: void 0,
storePath
};
const resolveMatchedAgentId = (sessionKey) => {
const scopedAgentId = parseAgentSessionKey(sessionKey)?.agentId;
if (scopedAgentId) return normalizeAgentId(scopedAgentId);
const persistedRowOwner = resolvePersistedSessionStoreOwnerForKey(opts.cfg, sessionKey);
return persistedRowOwner.kind === "configured" ? persistedRowOwner.agentId : persistedRowOwner.kind === "retired" ? void 0 : requestedAgentId ?? tryResolveLegacyCompatibilityAgentId(opts.cfg);
};
const sessionIdMatches = sessionEntries.filter(({ entry }) => entry.sessionId === sessionId);
const selectionMatches = requestedAgentId ? sessionIdMatches.filter(({ sessionKey }) => resolveMatchedAgentId(sessionKey) === requestedAgentId) : sessionIdMatches;
if (requestedAgentId && selectionMatches.length === 0 && sessionIdMatches.length > 0) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `stored session id "${sessionId}"`,
hint: `The matching rows belong to a different agent than agent "${requestedAgentId}".`
});
const selection = resolveSessionIdMatchSelection(selectionMatches.map(({ sessionKey, entry }) => [sessionKey, entry]), sessionId);
if (selection.kind !== "selected") return {
agentId: requestedAgentId,
sessionKey: void 0,
storePath
};
const sessionKey = selection.sessionKey;
const persistedRowOwner = resolvePersistedSessionStoreOwnerForKey(opts.cfg, sessionKey);
const resolvedAgentId = resolveMatchedAgentId(sessionKey);
if (!resolvedAgentId) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `stored session key "${sessionKey}"`,
hint: persistedRowOwner.kind === "retired" ? `The shared fixed-store row belongs to retired agent "${persistedRowOwner.agentId}".` : "Pass an explicit agent id when looking up an unscoped session by id."
});
if (requestedAgentId && requestedAgentId !== resolvedAgentId) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `stored session key "${sessionKey}"`,
hint: `The matching row belongs to agent "${resolvedAgentId}", not agent "${requestedAgentId}".`
});
return {
agentId: resolvedAgentId,
sessionKey,
sessionEntry: structuredClone(selectionMatches.find((match) => match.sessionKey === sessionKey)?.entry),
storePath
};
}
function resolveSessionKeyForRequestInternal(opts) {
const sessionCfg = opts.cfg.session;
const scope = sessionCfg?.scope ?? "per-sender";
const mainKey = normalizeMainKey(sessionCfg?.mainKey);
const requestedAgentId = opts.agentId?.trim() ? normalizeAgentId(opts.agentId) : void 0;
const requestedSessionId = opts.sessionId?.trim() || void 0;
const requestedSessionKey = opts.sessionKey?.trim() || void 0;
const toSessionKey = !requestedSessionKey && !requestedSessionId && classifySessionKeyShape(opts.to) === "agent" ? opts.to?.trim() : void 0;
const explicitSessionKey = requestedSessionKey || toSessionKey || (!requestedSessionId ? resolveExplicitAgentSessionKey({
cfg: opts.cfg,
agentId: requestedAgentId
}) : void 0);
const scopedSessionAgentId = parseAgentSessionKey(explicitSessionKey)?.agentId;
const explicitKeyStoreOwner = resolvePersistedSessionStoreOwnerForKey(opts.cfg, explicitSessionKey);
if (explicitKeyStoreOwner.kind === "configured" && requestedAgentId && requestedAgentId !== explicitKeyStoreOwner.agentId) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `session key "${explicitSessionKey}"`,
hint: `The shared fixed-store row belongs to agent "${explicitKeyStoreOwner.agentId}", not --agent "${requestedAgentId}".`
});
if (explicitKeyStoreOwner.kind === "retired") throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `session key "${explicitSessionKey}"`,
hint: `The shared fixed-store row belongs to retired agent "${explicitKeyStoreOwner.agentId}".`
});
const knownAgentId = requestedAgentId ?? scopedSessionAgentId ?? (explicitKeyStoreOwner.kind === "configured" ? explicitKeyStoreOwner.agentId : void 0) ?? tryResolveLegacyCompatibilityAgentId(opts.cfg);
const unownedBareSessionKey = Boolean(requestedSessionId && explicitSessionKey && classifySessionKeyShape(explicitSessionKey) === "legacy_or_alias" && !knownAgentId);
const sessionIdScanAnchor = requestedSessionId ? knownAgentId ?? listAgentIds(opts.cfg)[0] : void 0;
const defaultAgentId = knownAgentId ? normalizeAgentId(knownAgentId) : requestedSessionId ? void 0 : normalizeAgentId(resolveDefaultAgentId(opts.cfg, {
surface: "agent command session routing",
hint: "Pass --agent <id> or an agent-prefixed --session-key."
}));
const storeAgentId = explicitSessionKey ? unownedBareSessionKey ? sessionIdScanAnchor : isUnscopedSessionKeySentinel(explicitSessionKey) ? requestedAgentId ?? defaultAgentId : resolveAgentIdFromSessionKey(explicitSessionKey, defaultAgentId) : requestedAgentId ?? defaultAgentId ?? sessionIdScanAnchor;
if (!storeAgentId) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: "agent command session routing",
hint: "Pass --agent <id> or an agent-prefixed --session-key."
});
const storePath = resolveSessionStorePathCore(sessionCfg?.store, { agentId: storeAgentId });
const ctx = opts.to?.trim() ? { From: opts.to } : void 0;
let sessionKey = (!unownedBareSessionKey && explicitSessionKey ? canonicalizeMainSessionAlias({
cfg: opts.cfg,
agentId: storeAgentId,
sessionKey: explicitSessionKey
}) : void 0) ?? (!unownedBareSessionKey && ctx ? resolveSessionKey(scope, ctx, mainKey, storeAgentId) : void 0);
const sessionEntry = sessionKey && !isInternalSessionEffectsKey(sessionKey) ? loadExactSessionEntryReadOnly({
agentId: storeAgentId,
storePath,
sessionKey
})?.entry : void 0;
if (requestedSessionId && (!explicitSessionKey || unownedBareSessionKey) && (!sessionKey || sessionEntry?.sessionId !== requestedSessionId)) {
const { candidates, ownerConflict } = collectSessionIdMatchesForRequest({
cfg: opts.cfg,
sessionEntries: loadCommandSessionEntries({
storePath,
agentId: storeAgentId
}),
storePath,
storeAgentId,
sessionId: requestedSessionId,
searchOtherAgentStores: requestedAgentId === void 0
});
const selectedMatch = selectSessionIdMatchCandidate(candidates.filter((candidate) => candidate.resolution.agentId !== void 0), requestedSessionId);
if (selectedMatch) return {
...selectedMatch.resolution,
sessionEntry: structuredClone(selectedMatch.entry)
};
if (ownerConflict) throw new AgentSelectionRequiredError(listAgentIds(opts.cfg), {
surface: `session id "${requestedSessionId}"`,
hint: requestedAgentId ? `The matching session belongs to a different agent than --agent "${requestedAgentId}".` : "The matching unscoped session belongs to a retired fixed-store owner."
});
}
if (requestedSessionId && !sessionKey && opts.createMissingSessionId) {
const explicitSessionAgentId = requestedAgentId ?? tryResolveLegacyCompatibilityAgentId(opts.cfg) ?? resolveDefaultAgentId(opts.cfg, {
surface: "agent command session creation",
hint: "Pass --agent <id> when creating a session from --session-id."
});
sessionKey = buildExplicitSessionIdSessionKey({
sessionId: requestedSessionId,
agentId: explicitSessionAgentId
});
return {
agentId: explicitSessionAgentId,
sessionKey,
storePath
};
}
return {
agentId: storeAgentId,
sessionKey,
sessionEntry,
storePath
};
}
/** Resolves an existing session-id row across agent stores without creating a fallback key. */
function resolveExistingSessionKeyForRequest(opts) {
return resolveSessionKeyForRequestInternal({
...opts,
createMissingSessionId: false
});
}
/** Resolves the session key/store targeted by one command request. */
function resolveSessionKeyForRequest(opts) {
return resolveSessionKeyForRequestInternal({
...opts,
createMissingSessionId: true
});
}
/** Core alias retained for runtime owners that bypass the public library facade. */
function resolveSessionKeyForRequestCore(opts) {
return resolveSessionKeyForRequest(opts);
}
/** Resolves or creates the session used by one agent command request. */
function resolveSession(opts) {
const sessionCfg = opts.cfg.session;
const { agentId: resolvedAgentId, sessionKey, sessionEntry, storePath } = resolveSessionKeyForRequestCore({
cfg: opts.cfg,
to: opts.to,
sessionId: opts.sessionId,
sessionKey: opts.sessionKey,
agentId: opts.agentId
});
const now = Date.now();
const sessionAgentId = (opts.agentId?.trim() ? normalizeAgentId(opts.agentId) : void 0) ?? resolvedAgentId ?? parseAgentSessionKey(sessionKey)?.agentId ?? tryResolveLegacyCompatibilityAgentId(opts.cfg) ?? resolveDefaultAgentId(opts.cfg, {
surface: "agent command session ownership",
hint: "Pass --agent <id> or an agent-prefixed --session-key."
});
const resetType = resolveSessionResetType({ sessionKey });
const channelReset = resolveChannelResetConfig({
sessionCfg,
channel: sessionDeliveryChannel(sessionEntry)
});
const resetPolicy = resolveSessionResetPolicy({
sessionCfg,
resetType,
resetOverride: channelReset
});
const requestedSessionId = opts.sessionId?.trim() || void 0;
const terminalMainTranscriptNewerThanRegistry = sessionEntry && !requestedSessionId ? hasTerminalMainSessionTranscriptNewerThanRegistrySync({
entry: sessionEntry,
sessionScope: sessionCfg?.scope,
sessionKey,
agentId: sessionAgentId,
mainKey: sessionCfg?.mainKey,
storePath
}) : false;
const lockedModelSelection = isModelSelectionLocked(sessionEntry);
const skipImplicitExpiry = resetPolicy.configured !== true && hasProviderOwnedSession(sessionEntry);
const fresh = sessionEntry ? lockedModelSelection || !terminalMainTranscriptNewerThanRegistry && (skipImplicitExpiry || evaluateSessionFreshness({
updatedAt: sessionEntry.updatedAt,
...resolveSessionLifecycleTimestamps({
entry: sessionEntry,
agentId: sessionAgentId,
sessionKey,
storePath
}),
now,
policy: resetPolicy
}).fresh) : false;
const sessionId = requestedSessionId || (fresh ? sessionEntry?.sessionId : void 0) || crypto.randomUUID();
const isNewSession = !fresh && !requestedSessionId;
const resolvedSessionEntry = isNewSession && sessionEntry ? clearRotatedSessionMetadata(sessionEntry) : sessionEntry;
clearBootstrapSnapshotOnSessionRollover({
sessionKey,
previousSessionId: isNewSession ? sessionEntry?.sessionId : void 0
});
const persistedThinking = sessionEntry?.thinkingLevel ? normalizeThinkLevel(sessionEntry.thinkingLevel) : void 0;
const persistedVerbose = sessionEntry?.verboseLevel ? normalizeVerboseLevel(sessionEntry.verboseLevel) : void 0;
return {
sessionId,
sessionKey,
sessionEntry: resolvedSessionEntry,
storePath,
isNewSession,
previousSessionId: isNewSession ? sessionEntry?.sessionId : void 0,
persistedThinking,
persistedVerbose
};
}
//#endregion
export { resolveSessionKeyForRequestCore as a, resolveSession as i, clearRotatedSessionMetadata as n, resolveStoredSessionKeyForSessionId as o, resolveExistingSessionKeyForRequest as r, buildExplicitSessionIdSessionKey as t };