openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
692 lines (691 loc) • 26.9 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import "./agent-scope-MrLta7Pq.js";
import { a as isSubagentSessionKey, c as parseAgentSessionKey } from "./session-key-utils-Bx3apsJ3.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId, n as listAgentIds, o as resolveAgentWorkspaceDir } from "./agent-scope-config-CgCYpZfK.js";
import { r as logVerbose } from "./globals-GTrXU4s9.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import { t as getGlobalHookRunner } from "./hook-runner-global-BnyQREr6.js";
import { o as getActivePluginRegistry } from "./runtime-B2ROiq5l.js";
import { m as triggerInternalHook, n as createInternalHookEvent } from "./internal-hooks-Bq6_9FFu.js";
import { i as resolveSessionStoreKey } from "./session-store-key-BsydjA1h.js";
import { a as resolveSessionFilePathOptions, i as resolveSessionFilePath } from "./paths-NEwU8m3X.js";
import { d as updateSessionStore, v as snapshotSessionOrigin } from "./store-Qsgtu-0y.js";
import { f as canonicalizeAbsoluteSessionFilePath, p as rewriteSessionFileForNewSessionId } from "./types-D8S_uNvu.js";
import { t as loadCombinedSessionStoreForGateway } from "./combined-store-gateway-Drfj1Kad.js";
import "./sessions-D6zZvoxX.js";
import "./transcript-jsonl-CD0KPCG0.js";
import { r as runPluginHostCleanup } from "./host-hook-cleanup-i6AmnTe2.js";
import { t as getAcpSessionManager } from "./manager-D3LiuKaT.js";
import { t as getAcpRuntimeBackend } from "./registry-3G15-2Jg.js";
import { o as upsertAcpSessionMeta, r as readAcpSessionMeta, s as writeAcpSessionMetaForMigration } from "./session-meta-BuCj-TpW.js";
import { C as waitForEmbeddedAgentRunEnd, n as abortEmbeddedAgentRun } from "./runs-B4dP_Q30.js";
import { d as readSessionMessagesAsync } from "./session-utils.fs-D_8-X4jl.js";
import { o as resolveStableSessionEndTranscript, r as archiveSessionTranscriptsDetailed } from "./session-transcript-files.fs-CM11t-8l.js";
import { b as resolveSessionModelRef, d as migrateAndPruneGatewaySessionStoreKey, g as resolveGatewaySessionStoreTarget, u as loadSessionEntry } from "./session-utils-CHNCuY8a.js";
import { n as getSessionBindingService } from "./session-binding-service-cfUL2BWM.js";
import { u as retireSessionMcpRuntime } from "./agent-bundle-mcp-runtime-Bf36JLIh.js";
import "./agent-bundle-mcp-tools-tGjdbeEf.js";
import { t as clearBootstrapSnapshot } from "./bootstrap-cache-CGyy0R7Z.js";
import { t as clearAllCliSessions } from "./cli-session-DY8dQzGa.js";
import { in as errorShape, rn as ErrorCodes } from "./schema-BwaBORnA.js";
import "./src-oj0IwW6K.js";
import "./embedded-agent-L9tQiaO-.js";
import { i as stopSubagentsForRequester } from "./abort-7kAeg6fa.js";
import { a as buildSessionStartHookPayload, i as buildSessionEndHookPayload, n as listActiveSessionsForShutdown, r as noteActiveSessionForShutdown, t as forgetActiveSessionForShutdown } from "./active-sessions-shutdown-tracker-D9lhBvd4.js";
import { n as clearSessionResetRuntimeState, t as resolveResetPreservedSelection } from "./reset-preserved-selection-JDCEUHic.js";
import { t as cleanupBrowserSessionsForLifecycleEnd } from "./browser-lifecycle-cleanup-D3Nh2jnN.js";
import fs from "node:fs";
import path from "node:path";
import { randomUUID } from "node:crypto";
//#region src/gateway/session-child-sessions.ts
/** Returns true when a session store row is a direct child of the parent key. */
function isDirectChildSessionEntry(params) {
const parentKey = normalizeOptionalString(params.parentKey);
if (!parentKey || params.sessionKey === parentKey || !params.entry) return false;
return normalizeOptionalString(params.entry.spawnedBy) === parentKey || normalizeOptionalString(params.entry.parentSessionKey) === parentKey;
}
/** Finds direct child sessions for a parent session across the combined gateway store. */
function findDirectChildSessionsForParent(params) {
const { store } = loadCombinedSessionStoreForGateway(params.cfg);
return Object.entries(store).filter(([sessionKey, entry]) => isDirectChildSessionEntry({
sessionKey,
entry,
parentKey: params.parentKey
})).map(([sessionKey, entry]) => ({
sessionKey,
entry
}));
}
//#endregion
//#region src/gateway/session-reset-service.ts
const ACP_RUNTIME_CLEANUP_TIMEOUT_MS = 15e3;
function resolveResetSessionFile(params) {
const currentEntry = params.currentEntry;
const rewrittenSessionFile = currentEntry?.sessionId ? rewriteSessionFileForNewSessionId({
sessionFile: currentEntry.sessionFile,
previousSessionId: currentEntry.sessionId,
nextSessionId: params.nextSessionId
}) : void 0;
const preservedSessionFile = (rewrittenSessionFile && path.isAbsolute(rewrittenSessionFile) ? canonicalizeAbsoluteSessionFilePath(rewrittenSessionFile) : rewrittenSessionFile) ?? currentEntry?.sessionFile;
return resolveSessionFilePath(params.nextSessionId, preservedSessionFile ? { sessionFile: preservedSessionFile } : void 0, resolveSessionFilePathOptions({
storePath: params.storePath,
agentId: params.agentId
}));
}
function stripRuntimeModelState(entry) {
if (!entry) return entry;
return {
...entry,
model: void 0,
modelProvider: void 0,
contextTokens: void 0,
contextBudgetStatus: void 0,
systemPromptReport: void 0
};
}
function archiveSessionTranscriptsForSessionDetailed(params) {
if (!params.sessionId) return [];
return archiveSessionTranscriptsDetailed({
sessionId: params.sessionId,
storePath: params.storePath,
sessionFile: params.sessionFile,
agentId: params.agentId,
reason: params.reason,
onArchiveError: params.onArchiveError
});
}
function emitGatewaySessionEndPluginHook(params) {
if (!params.sessionId) return;
forgetActiveSessionForShutdown(params.sessionId);
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("session_end")) return;
const transcript = resolveStableSessionEndTranscript({
sessionId: params.sessionId,
storePath: params.storePath,
sessionFile: params.sessionFile,
agentId: params.agentId,
archivedTranscripts: params.archivedTranscripts
});
const payload = buildSessionEndHookPayload({
sessionId: params.sessionId,
sessionKey: params.sessionKey,
cfg: params.cfg,
reason: params.reason,
sessionFile: transcript.sessionFile,
transcriptArchived: transcript.transcriptArchived,
nextSessionId: params.nextSessionId,
nextSessionKey: params.nextSessionKey
});
hookRunner.runSessionEnd(payload.event, payload.context).catch((err) => {
logVerbose(`session_end hook failed: ${String(err)}`);
});
}
function emitGatewaySessionStartPluginHook(params) {
if (!params.sessionId) return;
if (params.storePath) noteActiveSessionForShutdown({
cfg: params.cfg,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
storePath: params.storePath,
sessionFile: params.sessionFile,
agentId: params.agentId
});
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("session_start")) return;
const payload = buildSessionStartHookPayload({
sessionId: params.sessionId,
sessionKey: params.sessionKey,
cfg: params.cfg,
resumedFrom: params.resumedFrom
});
hookRunner.runSessionStart(payload.event, payload.context).catch((err) => {
logVerbose(`session_start hook failed: ${String(err)}`);
});
}
const SHUTDOWN_DRAIN_DEFAULT_TOTAL_TIMEOUT_MS = 2e3;
/**
* Emit a typed `session_end` for every session that received `session_start`
* but did not yet receive a paired `session_end`. The bounded total timeout
* mirrors the gateway lifecycle hook timeout so a slow plugin cannot block
* SIGTERM/SIGINT past the runtime's overall shutdown grace window.
*
* Sessions that have already been finalized through replace / reset / delete /
* compaction are forgotten from the tracker by `emitGatewaySessionEndPluginHook`
* before this drain runs, so they will not be double-fired here.
*/
async function drainActiveSessionsForShutdown(params) {
const tracked = listActiveSessionsForShutdown();
if (tracked.length === 0) return {
emittedSessionIds: [],
timedOut: false
};
const totalTimeoutMs = Math.max(100, Math.floor(params.totalTimeoutMs ?? SHUTDOWN_DRAIN_DEFAULT_TOTAL_TIMEOUT_MS));
const emittedSessionIds = [];
const hookRunner = getGlobalHookRunner();
let settledEmissions = 0;
const drain = Promise.allSettled(tracked.map(async (entry) => {
try {
forgetActiveSessionForShutdown(entry.sessionId);
emittedSessionIds.push(entry.sessionId);
if (!hookRunner?.hasHooks("session_end")) return;
const transcript = resolveStableSessionEndTranscript({
sessionId: entry.sessionId,
storePath: entry.storePath,
sessionFile: entry.sessionFile,
agentId: entry.agentId
});
const payload = buildSessionEndHookPayload({
sessionId: entry.sessionId,
sessionKey: entry.sessionKey,
cfg: entry.cfg,
reason: params.reason,
sessionFile: transcript.sessionFile,
transcriptArchived: transcript.transcriptArchived
});
await hookRunner.runSessionEnd(payload.event, payload.context);
} catch (err) {
logVerbose(`session_end hook failed during shutdown drain: ${String(err)}`);
} finally {
settledEmissions++;
}
}));
let timer;
const timeout = new Promise((resolve) => {
timer = setTimeout(() => resolve("timeout"), totalTimeoutMs);
timer.unref?.();
});
try {
if (await Promise.race([drain.then(() => "ok"), timeout]) === "timeout") {
logVerbose(`shutdown session-end drain timed out after ${totalTimeoutMs}ms with ${tracked.length - settledEmissions} session_end handler(s) still pending`);
return {
emittedSessionIds,
timedOut: true
};
}
return {
emittedSessionIds,
timedOut: false
};
} finally {
if (timer) clearTimeout(timer);
}
}
async function emitSessionUnboundLifecycleEvent(params) {
const targetKind = isSubagentSessionKey(params.targetSessionKey) ? "subagent" : "acp";
await getSessionBindingService().unbind({
targetSessionKey: params.targetSessionKey,
reason: params.reason
});
if (params.emitHooks === false) return;
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("subagent_ended")) return;
await hookRunner.runSubagentEnded({
targetSessionKey: params.targetSessionKey,
targetKind,
reason: params.reason,
sendFarewell: true,
outcome: params.reason === "session-reset" ? "reset" : "deleted"
}, { childSessionKey: params.targetSessionKey });
}
async function ensureSessionRuntimeCleanup(params) {
const closeTrackedBrowserTabs = async () => {
const closeKeys = new Set([
params.key,
params.target.canonicalKey,
...params.target.storeKeys,
params.sessionId ?? ""
]);
await cleanupBrowserSessionsForLifecycleEnd({
cfg: params.cfg,
sessionKeys: [...closeKeys],
onWarn: (message) => logVerbose(message)
});
};
const queueKeys = new Set(params.target.storeKeys);
queueKeys.add(params.target.canonicalKey);
if (params.sessionId) queueKeys.add(params.sessionId);
clearSessionResetRuntimeState([...queueKeys]);
stopSubagentsForRequester({
cfg: params.cfg,
requesterSessionKey: params.target.canonicalKey
});
if (!params.sessionId) {
clearBootstrapSnapshot(params.target.canonicalKey);
await closeTrackedBrowserTabs();
return;
}
abortEmbeddedAgentRun(params.sessionId);
const ended = await waitForEmbeddedAgentRunEnd(params.sessionId, 15e3);
clearBootstrapSnapshot(params.target.canonicalKey);
if (ended) {
await retireSessionMcpRuntime({
sessionId: params.sessionId,
reason: "gateway-session-cleanup",
onError: (error, sessionId) => {
logVerbose(`sessions cleanup: failed to dispose bundle MCP runtime for ${sessionId}: ${String(error)}`);
}
});
await closeTrackedBrowserTabs();
return;
}
return errorShape(ErrorCodes.UNAVAILABLE, `Session ${params.key} is still active; try again in a moment.`);
}
async function runAcpCleanupStep(params) {
let timer;
const timeoutPromise = new Promise((resolve) => {
timer = setTimeout(() => resolve({ status: "timeout" }), ACP_RUNTIME_CLEANUP_TIMEOUT_MS);
});
const opPromise = params.op().then(() => ({ status: "ok" })).catch((error) => ({
status: "error",
error
}));
const outcome = await Promise.race([opPromise, timeoutPromise]);
if (timer) clearTimeout(timer);
return outcome;
}
async function closeAcpRuntimeForSession(params) {
const sessionKeys = Array.from(new Set([params.sessionKey, ...params.fallbackSessionKeys ?? []].map((key) => typeof key === "string" ? key.trim() : "").filter(Boolean)));
let acpMeta;
let acpSessionKey = params.sessionKey;
for (const sessionKey of sessionKeys) {
acpMeta = readAcpSessionMeta({ sessionKey });
if (acpMeta) {
acpSessionKey = sessionKey;
break;
}
}
if (!acpMeta) return;
const acpManager = getAcpSessionManager();
const cancelOutcome = await runAcpCleanupStep({ op: async () => {
await acpManager.cancelSession({
cfg: params.cfg,
sessionKey: acpSessionKey,
reason: params.reason
});
} });
if (cancelOutcome.status === "timeout") return errorShape(ErrorCodes.UNAVAILABLE, `Session ${params.sessionKey} is still active; try again in a moment.`);
if (cancelOutcome.status === "error") logVerbose(`sessions.${params.reason}: ACP cancel failed for ${params.sessionKey}: ${String(cancelOutcome.error)}`);
const closeOutcome = await runAcpCleanupStep({ op: async () => {
await acpManager.closeSession({
cfg: params.cfg,
sessionKey: acpSessionKey,
reason: params.reason,
discardPersistentState: true,
requireAcpSession: false,
allowBackendUnavailable: true
});
} });
if (closeOutcome.status === "timeout") return errorShape(ErrorCodes.UNAVAILABLE, `Session ${params.sessionKey} is still active; try again in a moment.`);
if (closeOutcome.status === "error") logVerbose(`sessions.${params.reason}: ACP runtime close failed for ${params.sessionKey}: ${String(closeOutcome.error)}`);
if (params.reason === "session-delete") await upsertAcpSessionMeta({
cfg: params.cfg,
sessionKey: acpSessionKey,
mutate: () => null
});
else {
const resetMeta = await ensureFreshAcpResetState({
cfg: params.cfg,
sessionKey: acpSessionKey,
reason: params.reason,
acpMeta
});
if (resetMeta) params.onResetMeta?.({
sessionKey: acpSessionKey,
meta: resetMeta
});
}
}
function buildPendingAcpMeta(base, now) {
const currentIdentity = base.identity;
const nextIdentity = currentIdentity ? {
state: "pending",
...currentIdentity.acpxRecordId ? { acpxRecordId: currentIdentity.acpxRecordId } : {},
source: currentIdentity.source,
lastUpdatedAt: now
} : void 0;
return {
backend: base.backend,
agent: base.agent,
runtimeSessionName: base.runtimeSessionName,
...nextIdentity ? { identity: nextIdentity } : {},
mode: base.mode,
...base.runtimeOptions ? { runtimeOptions: base.runtimeOptions } : {},
...base.cwd ? { cwd: base.cwd } : {},
state: "idle",
lastActivityAt: now
};
}
async function ensureFreshAcpResetState(params) {
if (params.reason !== "session-reset") return;
const latestMeta = readAcpSessionMeta({ sessionKey: params.sessionKey }) ?? params.acpMeta;
if (!latestMeta?.identity || latestMeta.identity.state !== "resolved" || !latestMeta.identity.acpxSessionId && !latestMeta.identity.agentSessionId) return;
const backendId = (latestMeta.backend || params.cfg.acp?.backend || "").trim() || void 0;
try {
await getAcpRuntimeBackend(backendId)?.runtime.prepareFreshSession?.({ sessionKey: params.sessionKey });
} catch (error) {
logVerbose(`sessions.${params.reason}: ACP prepareFreshSession failed for ${params.sessionKey}: ${String(error)}`);
}
const now = Date.now();
let resetMeta;
await upsertAcpSessionMeta({
cfg: params.cfg,
sessionKey: params.sessionKey,
mutate: (current) => {
resetMeta = buildPendingAcpMeta(current ?? latestMeta, now);
return resetMeta;
}
});
return resetMeta;
}
async function closeChildAcpRuntimesForParent(params) {
let children;
try {
children = findDirectChildSessionsForParent({
cfg: params.cfg,
parentKey: params.parentKey
}).flatMap(({ sessionKey }) => {
return readAcpSessionMeta({ sessionKey }) ? [{ sessionKey }] : [];
});
} catch (error) {
logVerbose(`sessions.${params.reason}: failed to enumerate sessions for child ACP cleanup: ${String(error)}`);
return;
}
await Promise.allSettled(children.map(({ sessionKey }) => closeAcpRuntimeForSession({
cfg: params.cfg,
sessionKey,
reason: params.reason
}).then((childError) => {
if (childError) logVerbose(`sessions.${params.reason}: child ACP cleanup incomplete for ${sessionKey}`);
})));
}
async function cleanupSessionBeforeMutation(params) {
const cleanupError = await ensureSessionRuntimeCleanup({
cfg: params.cfg,
key: params.key,
target: params.target,
sessionId: params.entry?.sessionId
});
if (cleanupError) return cleanupError;
const pluginCleanup = await runPluginHostCleanup({
cfg: params.cfg,
registry: getActivePluginRegistry(),
reason: params.reason === "session-reset" ? "reset" : "delete",
sessionKey: params.target.canonicalKey ?? params.key
});
for (const failure of pluginCleanup.failures) logVerbose(`plugin host cleanup failed for ${failure.pluginId}/${failure.hookId}: ${String(failure.error)}`);
const parentSessionKey = params.target.canonicalKey ?? params.canonicalKey ?? params.key;
const parentAcpError = await closeAcpRuntimeForSession({
cfg: params.cfg,
sessionKey: parentSessionKey,
fallbackSessionKeys: [
params.canonicalKey,
params.legacyKey,
params.key
],
reason: params.reason,
onResetMeta: params.onAcpResetMeta
});
await closeChildAcpRuntimesForParent({
cfg: params.cfg,
parentKey: params.target.canonicalKey ?? params.canonicalKey ?? params.key,
reason: params.reason
});
return parentAcpError;
}
async function emitGatewayBeforeResetPluginHook(params) {
const hookRunner = getGlobalHookRunner();
if (!hookRunner?.hasHooks("before_reset")) return;
const sessionKey = params.target.canonicalKey ?? params.key;
const sessionId = params.entry?.sessionId;
const sessionFile = params.entry?.sessionFile;
const agentId = normalizeAgentId(params.target.agentId ?? resolveDefaultAgentId(params.cfg));
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, agentId);
let messages = [];
try {
if (typeof sessionId === "string" && sessionId.trim().length > 0) messages = await readSessionMessagesAsync(sessionId, params.storePath, sessionFile, {
mode: "full",
reason: "before_reset hook payload"
});
} catch (err) {
logVerbose(`before_reset: failed to read session messages for ${sessionId ?? "(none)"}; firing hook with empty messages (${String(err)})`);
}
hookRunner.runBeforeReset({
sessionFile,
messages,
reason: params.reason
}, {
agentId,
sessionKey,
sessionId,
workspaceDir
}).catch((err) => {
logVerbose(`before_reset hook failed: ${String(err)}`);
});
}
async function performGatewaySessionReset(params) {
const resetTarget = (() => {
const cfg = getRuntimeConfig();
const explicitAgentId = params.agentId ? normalizeAgentId(params.agentId) : void 0;
const parsedKey = parseAgentSessionKey(params.key);
const inferredGlobalAgentId = !explicitAgentId && parsedKey && resolveSessionStoreKey({
cfg,
sessionKey: params.key
}) === "global" ? normalizeAgentId(parsedKey.agentId) : void 0;
const requestedAgentId = explicitAgentId ?? inferredGlobalAgentId;
if (requestedAgentId && !listAgentIds(cfg).includes(requestedAgentId)) return {
ok: false,
error: errorShape(ErrorCodes.INVALID_REQUEST, `Unknown agent id: ${requestedAgentId}`)
};
if (explicitAgentId && parsedKey?.agentId && normalizeAgentId(parsedKey.agentId) !== explicitAgentId) return {
ok: false,
error: errorShape(ErrorCodes.INVALID_REQUEST, "session key agent does not match agentId")
};
const target = resolveGatewaySessionStoreTarget({
cfg,
key: params.key,
...requestedAgentId ? { agentId: requestedAgentId } : {}
});
return {
ok: true,
cfg,
target,
storePath: target.storePath,
requestedAgentId
};
})();
if (!resetTarget.ok) return resetTarget;
const { cfg, target, storePath, requestedAgentId } = resetTarget;
const { entry, legacyKey, canonicalKey } = loadSessionEntry(params.key, requestedAgentId ? { agentId: requestedAgentId } : void 0);
const hadExistingEntry = Boolean(entry);
const workspaceDir = resolveAgentWorkspaceDir(cfg, normalizeAgentId(target.agentId ?? resolveDefaultAgentId(cfg)));
let pendingAcpResetMeta;
await triggerInternalHook(createInternalHookEvent("command", params.reason, target.canonicalKey ?? params.key, {
sessionEntry: entry,
previousSessionEntry: entry,
commandSource: params.commandSource,
cfg,
workspaceDir
}));
const mutationCleanupError = await cleanupSessionBeforeMutation({
cfg,
key: params.key,
target,
entry,
legacyKey,
canonicalKey,
reason: "session-reset",
onAcpResetMeta: (meta) => {
pendingAcpResetMeta = meta;
}
});
if (mutationCleanupError) return {
ok: false,
error: mutationCleanupError
};
let oldSessionId;
let oldSessionFile;
let resetSourceEntry;
const next = await updateSessionStore(storePath, (store) => {
const { primaryKey } = migrateAndPruneGatewaySessionStoreKey({
cfg,
key: params.key,
store,
...requestedAgentId ? { agentId: requestedAgentId } : {}
});
const currentEntry = store[primaryKey];
resetSourceEntry = currentEntry ? { ...currentEntry } : void 0;
const sessionAgentId = normalizeAgentId(parseAgentSessionKey(primaryKey)?.agentId ?? target.agentId ?? requestedAgentId ?? resolveDefaultAgentId(cfg));
const resetPreservedSelection = resolveResetPreservedSelection({ entry: currentEntry });
const resetEntry = {
...stripRuntimeModelState(currentEntry),
providerOverride: void 0,
modelOverride: void 0,
modelOverrideSource: void 0,
authProfileOverride: void 0,
authProfileOverrideSource: void 0,
authProfileOverrideCompactionCount: void 0,
...resetPreservedSelection
};
const resolvedModel = resolveSessionModelRef(cfg, resetEntry, sessionAgentId);
oldSessionId = currentEntry?.sessionId;
oldSessionFile = currentEntry?.sessionFile;
const now = Date.now();
const nextSessionId = randomUUID();
const nextEntry = {
sessionId: nextSessionId,
sessionFile: resolveResetSessionFile({
nextSessionId,
currentEntry,
storePath,
agentId: sessionAgentId
}),
updatedAt: now,
systemSent: false,
abortedLastRun: false,
thinkingLevel: currentEntry?.thinkingLevel,
fastMode: currentEntry?.fastMode,
verboseLevel: currentEntry?.verboseLevel,
traceLevel: currentEntry?.traceLevel,
reasoningLevel: currentEntry?.reasoningLevel,
elevatedLevel: currentEntry?.elevatedLevel,
ttsAuto: currentEntry?.ttsAuto,
execHost: currentEntry?.execHost,
execSecurity: currentEntry?.execSecurity,
execAsk: currentEntry?.execAsk,
execNode: currentEntry?.execNode,
responseUsage: currentEntry?.responseUsage,
...resetPreservedSelection,
groupActivation: currentEntry?.groupActivation,
groupActivationNeedsSystemIntro: currentEntry?.groupActivationNeedsSystemIntro,
chatType: currentEntry?.chatType,
model: resolvedModel.model,
modelProvider: resolvedModel.provider,
contextTokens: resetEntry?.contextTokens,
compactionCount: currentEntry?.compactionCount,
compactionCheckpoints: currentEntry?.compactionCheckpoints,
sendPolicy: currentEntry?.sendPolicy,
queueMode: currentEntry?.queueMode,
queueDebounceMs: currentEntry?.queueDebounceMs,
queueCap: currentEntry?.queueCap,
queueDrop: currentEntry?.queueDrop,
spawnedBy: currentEntry?.spawnedBy,
spawnedWorkspaceDir: currentEntry?.spawnedWorkspaceDir,
spawnedCwd: currentEntry?.spawnedCwd,
parentSessionKey: currentEntry?.parentSessionKey,
forkedFromParent: currentEntry?.forkedFromParent,
spawnDepth: currentEntry?.spawnDepth,
subagentRole: currentEntry?.subagentRole,
subagentControlScope: currentEntry?.subagentControlScope,
label: currentEntry?.label,
displayName: currentEntry?.displayName,
channel: currentEntry?.channel,
groupId: currentEntry?.groupId,
subject: currentEntry?.subject,
groupChannel: currentEntry?.groupChannel,
space: currentEntry?.space,
origin: snapshotSessionOrigin(currentEntry),
deliveryContext: currentEntry?.deliveryContext,
cliSessionBindings: currentEntry?.cliSessionBindings,
cliSessionIds: currentEntry?.cliSessionIds,
claudeCliSessionId: currentEntry?.claudeCliSessionId,
lastChannel: currentEntry?.lastChannel,
lastTo: currentEntry?.lastTo,
lastAccountId: currentEntry?.lastAccountId,
lastThreadId: currentEntry?.lastThreadId,
inputTokens: 0,
outputTokens: 0,
totalTokens: 0,
totalTokensFresh: true
};
if (!isSubagentSessionKey(primaryKey)) clearAllCliSessions(nextEntry);
store[primaryKey] = nextEntry;
return nextEntry;
});
if (pendingAcpResetMeta) writeAcpSessionMetaForMigration({
sessionKey: pendingAcpResetMeta.sessionKey,
sessionId: next.sessionId,
meta: pendingAcpResetMeta.meta
});
await emitGatewayBeforeResetPluginHook({
cfg,
key: params.key,
target,
storePath,
entry: resetSourceEntry,
reason: params.reason
});
const archivedTranscripts = archiveSessionTranscriptsForSessionDetailed({
sessionId: oldSessionId,
storePath,
sessionFile: oldSessionFile,
agentId: target.agentId,
reason: "reset"
});
fs.mkdirSync(path.dirname(next.sessionFile), { recursive: true });
if (!fs.existsSync(next.sessionFile)) {
const header = {
type: "session",
version: 3,
id: next.sessionId,
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
cwd: process.cwd()
};
fs.writeFileSync(next.sessionFile, `${JSON.stringify(header)}\n`, {
encoding: "utf-8",
mode: 384
});
}
emitGatewaySessionEndPluginHook({
cfg,
sessionKey: target.canonicalKey ?? params.key,
sessionId: oldSessionId,
storePath,
sessionFile: oldSessionFile,
agentId: target.agentId,
reason: params.reason,
archivedTranscripts,
nextSessionId: next.sessionId
});
emitGatewaySessionStartPluginHook({
cfg,
sessionKey: target.canonicalKey ?? params.key,
sessionId: next.sessionId,
resumedFrom: oldSessionId,
storePath,
sessionFile: next.sessionFile,
agentId: target.agentId
});
if (hadExistingEntry) await emitSessionUnboundLifecycleEvent({
targetSessionKey: target.canonicalKey ?? params.key,
reason: "session-reset"
});
return {
ok: true,
key: target.canonicalKey,
entry: next,
agentId: target.agentId
};
}
//#endregion
export { emitGatewaySessionEndPluginHook as a, performGatewaySessionReset as c, emitGatewayBeforeResetPluginHook as i, cleanupSessionBeforeMutation as n, emitGatewaySessionStartPluginHook as o, drainActiveSessionsForShutdown as r, emitSessionUnboundLifecycleEvent as s, archiveSessionTranscriptsForSessionDetailed as t };