openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
513 lines (512 loc) • 25.1 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { r as racePromiseWithAbortSignal } from "./abort-signal-D2k14JsD.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { t as isDiagnosticFlagEnabled } from "./diagnostic-flags-qVPAqdVk.js";
import { c as getAgentEventLifecycleGeneration } from "./agent-events-CoxiItUi.js";
import { a as getPluginRuntimeGatewayRequestScope, c as withPluginRuntimeGatewayContextResolver, r as getGatewayContextResolver, t as bindGatewayContextResolver } from "./gateway-request-scope-BCMYlsDI.js";
import "./session-accessor-YsytfDtG.js";
import { l as loadSessionEntry } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { c as getSessionWorkAdmissionOwnerRelease, n as beginSessionWorkAdmission } from "./session-lifecycle-admission-CS8v45tk.js";
import { C as waitForReplyRunSuccessorAdmission, F as isReplyRunRecoveryBlocked, G as REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS, H as runAfterReplyOperationClear, I as isReplyRunSuccessorAdmissionBlocked, J as ReplyRunSuccessorAdmissionBlockedError, K as ReplyRunAlreadyActiveError, P as isReplyRunEvidenceStale, S as waitForReplyRunFollowupAdmission, V as retainReplyOperationUntilComplete, W as REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, h as replyRunRegistry, k as expireStaleReplyOperation, q as ReplyRunFollowupAdmissionBlockedError, w as createReplyOperation, z as registerReplyOperationSuccessorBarrier } from "./reply-run-registry.registry-BMo0d96L.js";
import { o as getDiagnosticSessionActivitySnapshot, v as resolveRunStaleThresholdMs } from "./diagnostic-run-activity-CJrEba4R.js";
import "./reply-run-registry-BlmBKV23.js";
import { f as resolveSessionWorkStartError, i as SessionWorkStartChangedError, l as isRestartRecoveryTombstone, n as SESSION_RESTART_RECOVERY_TOMBSTONE_ERROR_CODE, r as SessionRestartRecoveryTombstoneError } from "./lifecycle-BaroCBMc.js";
import { i as isMainRestartRecoveryCandidate } from "./main-session-recovery-state-DD1DvBOq.js";
import { t as MAIN_SESSION_RECOVERY_WORK_ADMISSION_OWNER } from "./main-session-recovery-admission-BnYEQypz.js";
import { t as scheduleMainSessionRecoveryPendingTarget } from "./main-session-recovery-owner-release-CNuMN7iH.js";
import { a as releaseMainSessionRecoveryOwner, t as claimMainSessionRecoveryOwner } from "./main-session-recovery-store-jMegHmAf.js";
//#region src/shared/silent-reply-policy.ts
const DEFAULT_SILENT_REPLY_POLICY = {
direct: "disallow",
group: "allow",
internal: "allow"
};
/** Classifies a reply context for silent-reply policy from explicit type, session key, or surface. */
function classifySilentReplyConversationType(params) {
if (params.conversationType) return params.conversationType;
const normalizedSessionKey = normalizeLowercaseStringOrEmpty(params.sessionKey);
if (normalizedSessionKey.includes(":group:") || normalizedSessionKey.includes(":channel:")) return "group";
if (normalizedSessionKey.includes(":direct:") || normalizedSessionKey.includes(":dm:")) return "direct";
if (normalizeLowercaseStringOrEmpty(params.surface) === "webchat") return "direct";
return "internal";
}
/** Resolves silent-reply policy with surface overrides while keeping direct replies audible. */
function resolveSilentReplyPolicyFromPolicies(params) {
if (params.conversationType === "direct") return "disallow";
return params.surfacePolicy?.[params.conversationType] ?? params.defaultPolicy?.[params.conversationType] ?? DEFAULT_SILENT_REPLY_POLICY[params.conversationType];
}
//#endregion
//#region src/config/silent-reply.ts
function resolveSilentReplyConversationContext(params) {
const conversationType = classifySilentReplyConversationType({
sessionKey: params.sessionKey,
surface: params.surface,
conversationType: params.conversationType
});
const normalizedSurface = normalizeLowercaseStringOrEmpty(params.surface);
const surface = normalizedSurface ? params.cfg?.surfaces?.[normalizedSurface] : void 0;
return {
conversationType,
defaultPolicy: params.cfg?.agents?.defaults?.silentReply,
surfacePolicy: surface?.silentReply
};
}
/** Resolves the effective silent-reply settings for a routed conversation. */
function resolveSilentReplySettings(params) {
return { policy: resolveSilentReplyPolicyFromPolicies(resolveSilentReplyConversationContext(params)) };
}
/** Returns just the effective silent-reply policy for callers that do not need metadata. */
function resolveSilentReplyPolicy(params) {
return resolveSilentReplySettings(params).policy;
}
//#endregion
//#region src/auto-reply/reply/reply-turn-admission.ts
var QueuedFollowupLifecycleInvalidatedError = class extends Error {};
const log = createSubsystemLogger("auto-reply/reply-turn-admission");
const lifecycleAdmissionByOperation = /* @__PURE__ */ new WeakMap();
async function releaseReplyRecoveryOwner(lease) {
if (!lease) return;
try {
return await releaseMainSessionRecoveryOwner(lease);
} catch (error) {
log.warn(`failed to release main-session recovery reply owner: ${formatErrorMessage(error)}`);
return;
}
}
/** Runs owner work with its admission marked as the initiating lifecycle context. */
async function runWithReplyOperationLifecycleAdmission(operation, run) {
const admission = lifecycleAdmissionByOperation.get(operation);
if (admission) return await admission.run(run);
const resolver = getGatewayContextResolver(operation);
return await withPluginRuntimeGatewayContextResolver(resolver, run);
}
function rejectLifecycleInvalidatedWork(params) {
if (params.kind === "queued_followup") {
const error = new QueuedFollowupLifecycleInvalidatedError(params.message);
if (params.restartRecoveryTombstone === true) Object.assign(error, { code: SESSION_RESTART_RECOVERY_TOMBSTONE_ERROR_CODE });
throw error;
}
if (params.restartRecoveryTombstone === true) throw new SessionRestartRecoveryTombstoneError(params.message);
if (params.kind === "visible" && params.transientSessionChange === true) throw new SessionWorkStartChangedError(params.message);
throw new Error(params.message);
}
function isAbortSignalAborted(signal) {
return signal?.aborted === true;
}
function expireVisibleStaleOperation(operation) {
if (!operation) return false;
const idleMs = Date.now() - operation.lastActivityAtMs;
if (operation.result) return idleMs >= 6e4 && expireStaleReplyOperation(operation, "terminal_unreleased");
return isReplyRunEvidenceStale(operation) && expireStaleReplyOperation(operation, "no_activity");
}
function resolveVisibleActiveWaitMs(operation) {
if (!operation || isReplyRunRecoveryBlocked(operation)) return REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS;
const ageMs = Date.now() - operation.lastActivityAtMs;
const activity = getDiagnosticSessionActivitySnapshot({
sessionId: operation.sessionId,
sessionKey: operation.key
});
const remainingMs = operation.result ? REPLY_RUN_TERMINAL_SETTLE_TIMEOUT_MS - ageMs : resolveRunStaleThresholdMs(activity, ageMs) - ageMs;
return Math.min(REPLY_RUN_IDLE_SETTLE_TIMEOUT_MS, Math.max(1, remainingMs));
}
/** Waits for or claims the per-session reply run slot. */
async function admitReplyTurn(params) {
let sessionId = params.sessionId;
const resolveGatewayContext = params.adoptOperation ? getGatewayContextResolver(params.adoptOperation) : Object.hasOwn(params, "resolveGatewayContext") ? params.resolveGatewayContext : getPluginRuntimeGatewayRequestScope()?.resolveGatewayContext;
let expectedSessionId = params.expectedSessionId;
const waitTimeoutMs = params.waitTimeoutMs ?? (params.kind === "queued_followup" ? 15e3 : void 0);
while (true) {
if (isAbortSignalAborted(params.upstreamAbortSignal)) return {
status: "skipped",
reason: "aborted"
};
if (isReplyRunSuccessorAdmissionBlocked(params.sessionKey)) {
if (params.kind === "heartbeat") return {
status: "skipped",
reason: "active-run"
};
const successorAdmission = await waitForReplyRunSuccessorAdmission(params.sessionKey, params.kind === "visible" ? null : waitTimeoutMs, { signal: params.upstreamAbortSignal });
if (!successorAdmission.settled) return {
status: "skipped",
reason: isAbortSignalAborted(params.upstreamAbortSignal) ? "aborted" : "active-run"
};
sessionId = successorAdmission.sessionId ?? sessionId;
if (expectedSessionId && successorAdmission.sessionId) expectedSessionId = successorAdmission.sessionId;
continue;
}
try {
const storePath = params.storePath;
let operation;
let admittedSessionEntry;
let recoveryOwnerLease;
let interruptedBeforeOperation = false;
const admission = storePath ? await beginSessionWorkAdmission({
scope: storePath,
resolveGatewayContext,
identities: [params.sessionKey],
signal: params.upstreamAbortSignal,
onInterrupt: () => {
interruptedBeforeOperation = true;
operation?.abortForRestart();
params.onLifecycleInterrupt?.();
},
assertAllowed: () => {
const currentEntry = loadSessionEntry({
storePath,
sessionKey: params.sessionKey,
readConsistency: "latest"
});
admittedSessionEntry = currentEntry;
if (expectedSessionId && !currentEntry) rejectLifecycleInvalidatedWork({
kind: params.kind,
message: `Session "${params.sessionKey}" was deleted while starting work. Retry.`,
transientSessionChange: true
});
const registeredOperation = replyRunRegistry.get(params.sessionKey);
const rotationOperation = [registeredOperation, params.expectedActiveOperation].find((candidate) => {
if (!candidate || !expectedSessionId || currentEntry?.sessionId !== candidate.sessionId || !candidate.hasOwnedSessionId(expectedSessionId)) return false;
if (candidate.result?.kind === "aborted" && candidate.result.code === "aborted_for_restart") return false;
return candidate === registeredOperation || candidate.result !== null;
});
const activeOperationRotatedExpectedSession = Boolean(rotationOperation && currentEntry?.sessionId === rotationOperation.sessionId);
if (expectedSessionId && currentEntry?.sessionId !== expectedSessionId && !activeOperationRotatedExpectedSession) rejectLifecycleInvalidatedWork({
kind: params.kind,
message: `Session "${params.sessionKey}" changed while starting work. Retry.`,
transientSessionChange: true
});
if (activeOperationRotatedExpectedSession) expectedSessionId = currentEntry?.sessionId;
const archivedSessionError = resolveSessionWorkStartError(params.sessionKey || sessionId, currentEntry, { allowRestartTombstoneReplacement: params.resetTriggered && params.allowRestartTombstoneReset === true || params.allowRestartTombstoneParentFork === true });
if (archivedSessionError) rejectLifecycleInvalidatedWork({
kind: params.kind,
message: archivedSessionError,
restartRecoveryTombstone: isRestartRecoveryTombstone(currentEntry)
});
sessionId = currentEntry?.sessionId ?? sessionId;
}
}) : void 0;
try {
if (isReplyRunSuccessorAdmissionBlocked(params.sessionKey)) throw new ReplyRunSuccessorAdmissionBlockedError(params.sessionKey);
const mayWaitForRecoveryOwner = storePath && !params.resetTriggered && params.allowRestartTombstoneParentFork !== true;
const recoveryOwnerRelease = mayWaitForRecoveryOwner ? getSessionWorkAdmissionOwnerRelease({
scope: storePath,
identities: [params.sessionKey, sessionId],
owner: MAIN_SESSION_RECOVERY_WORK_ADMISSION_OWNER
}) : void 0;
if (mayWaitForRecoveryOwner && admittedSessionEntry && (admittedSessionEntry.status === "running" && (admittedSessionEntry.abortedLastRun === true || admittedSessionEntry.restartRecoveryRuns !== void 0) || admittedSessionEntry.mainRestartRecovery?.tombstone !== void 0) && isMainRestartRecoveryCandidate(admittedSessionEntry, params.sessionKey) && recoveryOwnerRelease === void 0) {
const ownerClaim = await claimMainSessionRecoveryOwner({
lifecycleGeneration: getAgentEventLifecycleGeneration(),
sessionId,
target: {
sessionKey: params.sessionKey,
storePath
}
});
if (ownerClaim.kind === "invalidated") rejectLifecycleInvalidatedWork({
kind: params.kind,
message: `Session "${params.sessionKey}" changed while starting work. Retry.`,
transientSessionChange: true
});
recoveryOwnerLease = ownerClaim.kind === "claimed" ? ownerClaim.lease : void 0;
}
if (params.kind === "queued_followup" && recoveryOwnerRelease) {
admission?.release();
await racePromiseWithAbortSignal(recoveryOwnerRelease, params.upstreamAbortSignal);
continue;
}
if (interruptedBeforeOperation || isAbortSignalAborted(params.upstreamAbortSignal)) rejectLifecycleInvalidatedWork({
kind: params.kind,
message: `Session "${params.sessionKey}" changed while starting work. Retry.`,
transientSessionChange: true
});
if (params.adoptOperation) {
params.adoptOperation.updateSessionKey(params.sessionKey);
operation = params.adoptOperation;
} else {
operation = createReplyOperation({
sessionKey: params.sessionKey,
sessionId,
turnKind: params.kind,
resetTriggered: params.resetTriggered,
routeThreadId: params.routeThreadId,
originatingLeafEntryId: params.originatingLeafEntryId,
upstreamAbortSignal: params.upstreamAbortSignal,
respectFollowupAdmissionBarrier: params.kind === "queued_followup" || params.kind === "heartbeat"
});
bindGatewayContextResolver(operation, resolveGatewayContext);
}
} catch (error) {
const pendingRecovery = recoveryOwnerLease ? await releaseReplyRecoveryOwner(recoveryOwnerLease) : void 0;
if (error instanceof ReplyRunAlreadyActiveError && admission && params.retainLifecycleAdmissionOnActive) {
admission.released.then(() => {
scheduleMainSessionRecoveryPendingTarget(pendingRecovery);
});
return {
status: "skipped",
reason: "active-run",
activeOperation: replyRunRegistry.get(params.sessionKey),
lifecycleAdmission: admission
};
}
admission?.release();
scheduleMainSessionRecoveryPendingTarget(pendingRecovery);
throw error;
}
if (admission) {
retainReplyOperationUntilComplete(operation);
lifecycleAdmissionByOperation.set(operation, admission);
let recoveryOwnerRelease;
const releaseRecoveryOwner = () => recoveryOwnerRelease ??= releaseReplyRecoveryOwner(recoveryOwnerLease);
if (recoveryOwnerLease) registerReplyOperationSuccessorBarrier({
operation,
sessionId: recoveryOwnerLease.sessionId,
sessionKeys: [params.sessionKey, recoveryOwnerLease.sessionKey],
start: releaseRecoveryOwner
});
runAfterReplyOperationClear(operation, () => {
lifecycleAdmissionByOperation.delete(operation);
releaseRecoveryOwner().then((pendingTarget) => {
admission.release();
scheduleMainSessionRecoveryPendingTarget(pendingTarget);
});
});
}
return {
status: "owned",
operation,
...admittedSessionEntry ? { sessionEntry: admittedSessionEntry } : {}
};
} catch (error) {
if (isAbortSignalAborted(params.upstreamAbortSignal)) return {
status: "skipped",
reason: "aborted"
};
if (error instanceof QueuedFollowupLifecycleInvalidatedError) return {
status: "skipped",
reason: "lifecycle-invalidated"
};
if (error instanceof ReplyRunSuccessorAdmissionBlockedError) {
if (params.kind === "heartbeat") return {
status: "skipped",
reason: "active-run"
};
continue;
}
if (error instanceof ReplyRunFollowupAdmissionBlockedError) {
if (params.kind === "heartbeat") return {
status: "skipped",
reason: "active-run"
};
const followupAdmission = await waitForReplyRunFollowupAdmission(params.sessionKey, waitTimeoutMs ?? 15e3, { signal: params.upstreamAbortSignal });
if (!followupAdmission.settled) return {
status: "skipped",
reason: isAbortSignalAborted(params.upstreamAbortSignal) ? "aborted" : "active-run"
};
sessionId = followupAdmission.sessionId ?? sessionId;
if (expectedSessionId && followupAdmission.sessionId) expectedSessionId = followupAdmission.sessionId;
continue;
}
if (!(error instanceof ReplyRunAlreadyActiveError)) throw error;
const activeOperation = replyRunRegistry.get(params.sessionKey);
if (params.kind === "visible" && activeOperation?.turnKind === "heartbeat") activeOperation.supersede();
if (params.kind === "visible" && expireVisibleStaleOperation(activeOperation)) continue;
if (params.kind === "heartbeat") return {
status: "skipped",
reason: "active-run",
activeOperation
};
if (params.waitForActive === false) return {
status: "skipped",
reason: "active-run",
activeOperation
};
const activeWaitTimeoutMs = params.kind === "visible" ? resolveVisibleActiveWaitMs(activeOperation) : waitTimeoutMs;
if (!await replyRunRegistry.waitForIdle(params.sessionKey, activeWaitTimeoutMs, { signal: params.upstreamAbortSignal })) {
if (params.kind === "visible" && !isAbortSignalAborted(params.upstreamAbortSignal)) {
expireVisibleStaleOperation(replyRunRegistry.get(params.sessionKey) ?? activeOperation);
continue;
}
return {
status: "skipped",
reason: isAbortSignalAborted(params.upstreamAbortSignal) ? "aborted" : "active-run",
activeOperation
};
}
if (activeOperation) {
sessionId = activeOperation.sessionId;
if (expectedSessionId && !(activeOperation.result?.kind === "aborted" && activeOperation.result.code === "aborted_for_restart")) expectedSessionId = activeOperation.sessionId;
}
}
}
}
/** Resolves the default turn kind from reply options. */
function resolveReplyTurnKind(opts) {
return opts?.isHeartbeat === true ? "heartbeat" : "visible";
}
//#endregion
//#region src/auto-reply/reply/reply-timing-tracker.ts
/** Checks config/env diagnostic flags for reply profiling. */
function isReplyProfilerEnabled(params) {
const cfg = params?.config;
const env = params?.env ?? process.env;
return isDiagnosticFlagEnabled("profiler", cfg, env) || isDiagnosticFlagEnabled("reply.profiler", cfg, env);
}
/** Keeps slow replies diagnosable; profiling lowers the warning thresholds. */
function createReplyTimingTracker(params) {
const profilerEnabled = params.enabled ?? isReplyProfilerEnabled({
config: params.config,
env: params.env
});
const startedAt = Date.now();
const spans = [];
let didLog = false;
const totalWarnMs = params.totalWarnMs ?? (profilerEnabled ? 1e3 : 1e4);
const stageWarnMs = params.stageWarnMs ?? (profilerEnabled ? 500 : 5e3);
const toMs = (value) => Math.max(0, Math.round(value));
const record = (name, spanStartedAt) => {
const currentAt = Date.now();
spans.push({
name,
durationMs: toMs(currentAt - spanStartedAt),
elapsedMs: toMs(currentAt - startedAt)
});
};
return {
async measure(name, run) {
const spanStartedAt = Date.now();
try {
return await run();
} finally {
record(name, spanStartedAt);
}
},
measureSync(name, run) {
const spanStartedAt = Date.now();
try {
return run();
} finally {
record(name, spanStartedAt);
}
},
logIfSlow(logParams, options) {
if (didLog && !options?.repeat) return;
const summary = {
totalMs: toMs(Date.now() - startedAt),
spans: spans.slice()
};
if (summary.totalMs < totalWarnMs && !summary.spans.some((span) => span.durationMs >= stageWarnMs)) return;
if (!options?.repeat) didLog = true;
const formattedSpans = summary.spans.length > 0 ? summary.spans.map((span) => `${span.name}:${span.durationMs}ms@${span.elapsedMs}ms`).join(",") : "none";
if (params.formatMessage) {
const detailParams = logParams;
const details = Object.fromEntries((params.detailKeys?.(logParams) ?? []).map((key) => [key, detailParams[key]]));
params.log.warn(params.formatMessage(logParams, summary, formattedSpans), {
...details,
totalMs: summary.totalMs,
spans: summary.spans
});
return;
}
const defaults = logParams;
const suffix = [
`totalMs=${summary.totalMs}`,
`stages=${formattedSpans}`,
defaults.outcome ? `outcome=${defaults.outcome}` : void 0,
defaults.reason ? `reason=${defaults.reason}` : void 0,
defaults.error ? `error="${defaults.error}"` : void 0
].filter(Boolean).join(" ");
params.log.warn(`${defaults.message} ${suffix}`, {
...defaults.details,
outcome: defaults.outcome,
reason: defaults.reason,
error: defaults.error,
totalMs: summary.totalMs,
spans: summary.spans
});
}
};
}
//#endregion
//#region src/auto-reply/reply/session-entry-handle.ts
var ReplySessionGenerationInvalidatedError = class extends Error {};
function createReplySessionEntryHandle(params) {
const { generationFence, sessionKey, sessionStore } = params;
const entries = sessionStore ?? {};
let ownedSessionId = generationFence?.sessionId;
let ownedLifecycleRevision = params.sessionEntry && params.sessionEntry.sessionId === ownedSessionId ? params.sessionEntry.lifecycleRevision : void 0;
const matchesGeneration = (entry) => entry !== void 0 && (!generationFence || entry.sessionId === ownedSessionId && entry.lifecycleRevision === ownedLifecycleRevision);
let currentEntry = matchesGeneration(params.sessionEntry) ? params.sessionEntry : void 0;
if (sessionKey && currentEntry) {
const storedEntry = entries[sessionKey];
if (!generationFence || !sessionStore || storedEntry && (storedEntry === generationFence.expectedStoreEntry && !matchesGeneration(storedEntry) || matchesGeneration(storedEntry) && currentEntry.updatedAt >= storedEntry.updatedAt)) entries[sessionKey] = currentEntry;
}
const current = () => {
const storedEntry = sessionKey ? entries[sessionKey] : void 0;
if (generationFence && matchesGeneration(storedEntry) && (!currentEntry || storedEntry.updatedAt >= currentEntry.updatedAt)) currentEntry = storedEntry;
return currentEntry;
};
const replaceCurrent = (entry, adopt = false) => {
if (!generationFence) {
currentEntry = entry;
if (sessionKey) entries[sessionKey] = entry;
return;
}
const storedEntry = sessionKey ? entries[sessionKey] : void 0;
const storedMatchesOwned = matchesGeneration(storedEntry);
let nextEntry = entry;
if (adopt) {
const storedMatchesAdopted = Boolean(storedEntry && storedEntry.sessionId === entry.sessionId && storedEntry.lifecycleRevision === entry.lifecycleRevision);
if (sessionStore && sessionKey && !storedEntry && generationFence.expectedStoreEntry || storedEntry && !storedMatchesOwned && !storedMatchesAdopted) throw new ReplySessionGenerationInvalidatedError("Follow-up session generation was replaced during admission");
if (storedMatchesAdopted && storedEntry && storedEntry.updatedAt >= entry.updatedAt) nextEntry = storedEntry;
ownedSessionId = nextEntry.sessionId;
ownedLifecycleRevision = nextEntry.lifecycleRevision;
} else if (!matchesGeneration(nextEntry)) return;
if (adopt || !currentEntry || nextEntry.updatedAt >= currentEntry.updatedAt) currentEntry = nextEntry;
if (sessionKey && (adopt ? !storedEntry || storedMatchesOwned || nextEntry !== storedEntry : !storedEntry && !generationFence.expectedStoreEntry || storedMatchesOwned && storedEntry && nextEntry.updatedAt >= storedEntry.updatedAt)) entries[sessionKey] = nextEntry;
};
const handle = {
adoptCurrent: (entry) => replaceCurrent(entry, true),
clearCurrent: () => {
currentEntry = void 0;
if (sessionKey && (!generationFence || matchesGeneration(entries[sessionKey]))) delete entries[sessionKey];
},
get: (key) => entries[key],
getCurrent: current,
patchCurrent: (patch) => {
if (currentEntry) replaceCurrent({
...currentEntry,
...patch
});
return currentEntry;
},
replaceCurrent,
set: (key, entry) => {
if (key === sessionKey) replaceCurrent(entry);
else entries[key] = entry;
},
toCompatSessionStore: () => {
if (!generationFence || !sessionKey) return entries;
const view = {
...entries,
...currentEntry ? { [sessionKey]: currentEntry } : {}
};
return new Proxy(view, {
get: (target, key) => key === sessionKey ? current() : Reflect.get(target, key),
set(target, key, entry) {
if (key !== sessionKey) return Reflect.set(target, key, entry);
if (!entry) handle.clearCurrent();
else replaceCurrent(entry, !matchesGeneration(entry));
return true;
},
deleteProperty(target, key) {
if (key !== sessionKey) return Reflect.deleteProperty(target, key);
handle.clearCurrent();
return true;
}
});
}
};
return handle;
}
//#endregion
export { admitReplyTurn as a, resolveSilentReplyPolicy as c, isReplyProfilerEnabled as i, resolveSilentReplySettings as l, createReplySessionEntryHandle as n, resolveReplyTurnKind as o, createReplyTimingTracker as r, runWithReplyOperationLifecycleAdmission as s, ReplySessionGenerationInvalidatedError as t, resolveSilentReplyPolicyFromPolicies as u };