openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
744 lines (743 loc) • 27.4 kB
JavaScript
import { o as asDateTimestampMs } from "./number-coercion-CJQ8TR--.js";
import "./number-coercion-Z7n6tXLk.js";
import { i as getLogger } from "./logger-Brhy0cvC.js";
import "./agent-scope-MrLta7Pq.js";
import { c as parseAgentSessionKey } from "./session-key-utils-Bx3apsJ3.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { c as resolveDefaultAgentId } from "./agent-scope-config-CgCYpZfK.js";
import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js";
import { i as resolveMainSessionKey, t as canonicalizeMainSessionAlias } from "./main-session-Eahn-btj.js";
import { o as resolveStoredSessionOwnerAgentId } from "./session-store-key-BsydjA1h.js";
import { a as resolveSessionFilePathOptions, i as resolveSessionFilePath, u as resolveStorePath } from "./paths-NEwU8m3X.js";
import { C as capEntryCount, E as pruneStaleEntries, d as cloneSessionStoreRecord, o as resolveMaintenanceConfig, s as collectSessionMaintenancePreserveKeys, t as loadSessionStore } from "./store-load-C4uq6Lrq.js";
import { C as resolveSessionArtifactCanonicalPathsForEntry, S as pruneUnreferencedSessionArtifacts, a as patchSessionEntry, d as updateSessionStore, n as archiveRemovedSessionTranscripts, r as getSessionEntry, x as enforceSessionDiskBudget } from "./store-Qsgtu-0y.js";
import { n as isTerminalSessionStatus, s as resolveFreshSessionTotalTokens } from "./types-D8S_uNvu.js";
import { i as resolveSessionStoreTargets } from "./targets-BrMDn2yj.js";
import "./delivery-info-DYbymE-x.js";
import "./combined-store-gateway-Drfj1Kad.js";
import "./reset-D9vnBMp0.js";
import "./session-key-DNlvhlw9.js";
import "./transcript-Cw-EfeYD.js";
import fs from "node:fs";
import path from "node:path";
import fs$1 from "node:fs/promises";
import crypto from "node:crypto";
//#region src/config/sessions/goals.ts
const MODEL_UPDATABLE_SESSION_GOAL_STATUSES = ["complete", "blocked"];
const TERMINAL_GOAL_STATUSES = new Set(["complete"]);
function nowMs(value) {
return typeof value === "number" && Number.isFinite(value) ? value : Date.now();
}
function normalizeTokenCount(value) {
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
}
function resolveEntryFreshTotalTokens(entry) {
return normalizeTokenCount(resolveFreshSessionTotalTokens(entry));
}
function resolveEntryGoalStartTokens(entry) {
return resolveEntryFreshTotalTokens(entry) ?? 0;
}
function normalizeTokenBudget(value) {
const normalized = normalizeTokenCount(value);
return normalized && normalized > 0 ? normalized : void 0;
}
function cloneGoal(goal) {
return { ...goal };
}
function formatGoalTokenCount(value) {
if (value === void 0 || !Number.isFinite(value)) return "0";
const safe = Math.max(0, value);
if (safe >= 1e6) return `${(safe / 1e6).toFixed(1)}m`;
if (safe >= 1e3) {
const precision = safe >= 1e4 ? 0 : 1;
const formattedThousands = (safe / 1e3).toFixed(precision);
if (Number(formattedThousands) >= 1e3) return `${(safe / 1e6).toFixed(1)}m`;
return `${formattedThousands}k`;
}
return String(Math.round(safe));
}
function resolveSessionGoalDisplayState(entry, now, options) {
return accountGoalUsage(entry, nowMs(now), options);
}
function accountGoalUsage(entry, now, options) {
const goal = entry.goal;
if (!goal) return;
const totalTokens = resolveEntryFreshTotalTokens(entry);
const hasFreshStart = goal.tokenStartFresh !== false;
const shouldHoldStaleStart = !hasFreshStart && options?.adoptFreshBaseline === false;
const shouldAdoptFreshStart = !shouldHoldStaleStart && totalTokens !== void 0 && !hasFreshStart;
const tokenStart = shouldAdoptFreshStart ? totalTokens : normalizeTokenCount(goal.tokenStart) ?? totalTokens ?? 0;
const tokensUsed = totalTokens === void 0 || shouldAdoptFreshStart || shouldHoldStaleStart ? goal.tokensUsed : Math.max(goal.tokensUsed, Math.max(0, totalTokens - tokenStart));
const next = {
...goal,
tokenStart,
tokenStartFresh: hasFreshStart || shouldAdoptFreshStart,
tokensUsed
};
if (next.status === "active" && next.tokenBudget !== void 0 && tokensUsed >= next.tokenBudget) {
next.status = "budget_limited";
next.budgetLimitedAt = now;
next.updatedAt = now;
}
return next;
}
function goalsEqual(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
function formatSessionGoalStatus(goal) {
if (!goal) return "No goal for this session.\nStart one with /goal start <objective>.";
const budget = goal.tokenBudget === void 0 ? "" : `\nToken budget: ${formatGoalTokenCount(goal.tokensUsed)}/${formatGoalTokenCount(goal.tokenBudget)}`;
const note = goal.lastStatusNote ? `\nNote: ${goal.lastStatusNote}` : "";
const commands = resolveGoalCommandHint(goal.status);
return [
"Goal",
`Status: ${goal.status}`,
`Objective: ${goal.objective}`,
`Tokens used: ${formatGoalTokenCount(goal.tokensUsed)}`,
...budget ? [budget.slice(1)] : [],
...note ? [note.slice(1)] : [],
"",
`Commands: ${commands}`
].join("\n");
}
function resolveGoalCommandHint(status) {
switch (status) {
case "active": return "/goal pause, /goal complete, /goal clear";
case "paused":
case "blocked":
case "usage_limited":
case "budget_limited": return "/goal resume, /goal clear";
case "complete": return "/goal clear";
}
return "/goal";
}
async function getSessionGoal(options) {
const now = nowMs(options.now);
if (options.persist === false) {
const entry = getSessionEntry({
sessionKey: options.sessionKey,
storePath: options.storePath
}) ?? options.fallbackEntry;
const projected = entry ? resolveSessionGoalDisplayState(entry, now, { adoptFreshBaseline: false }) : void 0;
return projected ? {
status: "found",
goal: projected
} : { status: "missing" };
}
let goal;
if (!await patchSessionEntry({
sessionKey: options.sessionKey,
storePath: options.storePath,
fallbackEntry: options.fallbackEntry,
update: (entry) => {
const accounted = accountGoalUsage(entry, now);
goal = accounted ? cloneGoal(accounted) : void 0;
if (!accounted || goalsEqual(accounted, entry.goal)) return null;
return { goal: accounted };
}
}) || !goal) return { status: "missing" };
return {
status: "found",
goal
};
}
async function createSessionGoal(options) {
const objective = options.objective.trim();
if (!objective) throw new Error("objective required");
const now = nowMs(options.now);
let created;
if (!await patchSessionEntry({
sessionKey: options.sessionKey,
storePath: options.storePath,
fallbackEntry: options.fallbackEntry,
update: (entry) => {
if (entry.goal) throw new Error("goal already exists");
const tokenBudget = normalizeTokenBudget(options.tokenBudget);
const tokenStartFresh = resolveEntryFreshTotalTokens(entry) !== void 0;
created = {
schemaVersion: 1,
id: crypto.randomUUID(),
objective,
status: "active",
createdAt: now,
updatedAt: now,
tokenStart: resolveEntryGoalStartTokens(entry),
tokenStartFresh,
tokensUsed: 0,
...tokenBudget ? { tokenBudget } : {},
continuationTurns: 0
};
return { goal: created };
}
}) || !created) throw new Error("session not found");
return cloneGoal(created);
}
async function updateSessionGoalStatus(options) {
const now = nowMs(options.now);
let updated;
let foundSession = false;
if (!await patchSessionEntry({
sessionKey: options.sessionKey,
storePath: options.storePath,
update: (entry) => {
foundSession = true;
const accounted = accountGoalUsage(entry, now);
if (!accounted) throw new Error("goal not found");
if (TERMINAL_GOAL_STATUSES.has(accounted.status) && accounted.status !== options.status) throw new Error(`goal is already ${accounted.status}`);
const resetsBudgetWindow = options.status === "active" && (accounted.status === "budget_limited" || accounted.status === "usage_limited" || accounted.tokenBudget !== void 0 && accounted.tokensUsed >= accounted.tokenBudget);
const freshTokenStart = resetsBudgetWindow ? resolveEntryFreshTotalTokens(entry) : void 0;
const next = {
...accounted,
status: options.status,
updatedAt: now,
...options.note ? { lastStatusNote: options.note } : {},
...options.status === "paused" ? { pausedAt: now } : {},
...options.status === "blocked" ? { blockedAt: now } : {},
...options.status === "complete" ? { completedAt: now } : {}
};
if (resetsBudgetWindow) {
next.tokenStart = freshTokenStart ?? 0;
next.tokenStartFresh = freshTokenStart !== void 0;
next.tokensUsed = 0;
delete next.budgetLimitedAt;
delete next.usageLimitedAt;
}
if (next.status === "active" && next.tokenBudget !== void 0 && next.tokensUsed >= next.tokenBudget) {
next.status = "budget_limited";
next.budgetLimitedAt = now;
}
updated = next;
return { goal: updated };
}
}) || !updated) throw new Error(foundSession ? "goal not found" : "session not found");
return cloneGoal(updated);
}
async function clearSessionGoal(options) {
let removed = false;
const result = await patchSessionEntry({
sessionKey: options.sessionKey,
storePath: options.storePath,
update: (entry) => {
if (!entry.goal) return null;
removed = true;
return { goal: void 0 };
}
});
return Boolean(result && removed);
}
//#endregion
//#region src/config/sessions/main-session.runtime.ts
/** Resolves the main session key from the active runtime config. */
function resolveMainSessionKeyFromConfig() {
return resolveMainSessionKey(getRuntimeConfig());
}
//#endregion
//#region src/config/sessions/lifecycle.ts
function resolveTimestamp(value) {
const timestampMs = asDateTimestampMs(value);
return timestampMs !== void 0 && timestampMs >= 0 ? timestampMs : void 0;
}
function resolvePositiveTimestamp(value) {
const timestampMs = resolveTimestamp(value);
return timestampMs !== void 0 && timestampMs > 0 ? timestampMs : void 0;
}
function parseTimestampMs(value) {
if (typeof value === "number") return resolveTimestamp(value);
if (typeof value !== "string" || !value.trim()) return;
return resolveTimestamp(Date.parse(value));
}
function readFirstLine(filePath) {
try {
const fd = fs.openSync(filePath, "r");
try {
const buffer = Buffer.alloc(8192);
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
if (bytesRead <= 0) return;
const chunk = buffer.subarray(0, bytesRead).toString("utf8");
const newline = chunk.indexOf("\n");
return newline >= 0 ? chunk.slice(0, newline) : chunk;
} finally {
fs.closeSync(fd);
}
} catch {
return;
}
}
/** Reads session start time from a transcript header when store metadata is missing. */
function readSessionHeaderStartedAtMs(params) {
const sessionId = params.entry?.sessionId?.trim();
if (!sessionId) return;
const pathOptions = params.pathOptions ?? resolveSessionFilePathOptions({
agentId: params.agentId,
storePath: params.storePath
});
let sessionFile;
try {
sessionFile = resolveSessionFilePath(sessionId, params.entry, pathOptions);
} catch {
return;
}
const firstLine = readFirstLine(sessionFile);
if (!firstLine) return;
try {
const header = JSON.parse(firstLine);
if (header.type !== "session") return;
if (typeof header.id === "string" && header.id.trim() && header.id !== sessionId) return;
return parseTimestampMs(header.timestamp);
} catch {
return;
}
}
function resolveSessionLifecycleTimestamps(params) {
const entry = params.entry;
if (!entry) return {};
return {
sessionStartedAt: resolveTimestamp(entry.sessionStartedAt) ?? readSessionHeaderStartedAtMs({
entry,
agentId: params.agentId,
storePath: params.storePath,
pathOptions: params.pathOptions
}),
lastInteractionAt: resolveTimestamp(entry.lastInteractionAt)
};
}
function resolveTerminalMainSessionTranscriptRegistryCheck(params) {
if (!params.entry || !params.sessionKey) return;
const configuredMainSessionKey = canonicalizeMainSessionAlias({
cfg: { session: {
scope: params.sessionScope,
mainKey: params.mainKey
} },
agentId: params.agentId,
sessionKey: params.mainKey ?? "main"
});
if (canonicalizeMainSessionAlias({
cfg: { session: {
scope: params.sessionScope,
mainKey: params.mainKey
} },
agentId: params.agentId,
sessionKey: params.sessionKey
}) !== configuredMainSessionKey) return;
if (!(isTerminalSessionStatus(params.entry.status) || resolvePositiveTimestamp(params.entry.endedAt) !== void 0)) return;
if (params.entry.status === "failed") return;
const registryTimestampMs = resolvePositiveTimestamp(params.entry.updatedAt);
if (registryTimestampMs === void 0) return;
const sessionId = typeof params.entry.sessionId === "string" ? params.entry.sessionId.trim() : "";
if (!sessionId) return;
return {
sessionId,
registryTimestampMs
};
}
function isTranscriptMtimeNewerThanRegistry(params) {
const transcriptMtimeMs = Math.floor(params.transcriptMtimeMs);
const registryTimestampMs = Math.floor(params.registryTimestampMs);
return Number.isFinite(transcriptMtimeMs) && transcriptMtimeMs > registryTimestampMs;
}
function hasTerminalMainSessionTranscriptNewerThanRegistrySync(params) {
const check = resolveTerminalMainSessionTranscriptRegistryCheck(params);
if (!check) return false;
const pathOptions = resolveSessionFilePathOptions({
agentId: params.agentId,
storePath: params.storePath
});
try {
const sessionFile = resolveSessionFilePath(check.sessionId, params.entry, pathOptions);
return isTranscriptMtimeNewerThanRegistry({
transcriptMtimeMs: fs.statSync(sessionFile).mtimeMs,
registryTimestampMs: check.registryTimestampMs
});
} catch {
return false;
}
}
async function hasTerminalMainSessionTranscriptNewerThanRegistry(params) {
const check = resolveTerminalMainSessionTranscriptRegistryCheck(params);
if (!check) return false;
const pathOptions = resolveSessionFilePathOptions({
agentId: params.agentId,
storePath: params.storePath
});
try {
const sessionFile = resolveSessionFilePath(check.sessionId, params.entry, pathOptions);
return isTranscriptMtimeNewerThanRegistry({
transcriptMtimeMs: (await fs$1.stat(sessionFile)).mtimeMs,
registryTimestampMs: check.registryTimestampMs
});
} catch {
return false;
}
}
//#endregion
//#region src/config/sessions/cleanup-service.ts
const EMPTY_TRANSCRIPT_MAX_BYTES = 4096;
function isTranscriptMessageRole(role) {
return role === "user" || role === "assistant" || role === "tool" || role === "toolResult" || role === "system";
}
function isTranscriptMessageRecord(entry) {
if (!entry || typeof entry !== "object") return false;
const record = entry;
if (record.type === "message") return true;
if (record.type === void 0 && record.message && typeof record.message === "object" && isTranscriptMessageRole(record.message.role)) return true;
return record.type === void 0 && isTranscriptMessageRole(record.role);
}
function transcriptHasNoMessageRecords(transcriptPath) {
let stat;
try {
stat = fs.statSync(transcriptPath);
} catch {
return false;
}
if (!stat.isFile() || stat.size > EMPTY_TRANSCRIPT_MAX_BYTES) return false;
let raw;
try {
raw = fs.readFileSync(transcriptPath, "utf-8");
} catch {
return false;
}
const lines = raw.split(/\r?\n/u).filter((line) => line.trim().length > 0);
if (lines.length === 0) return true;
for (const line of lines) {
let entry;
try {
entry = JSON.parse(line);
} catch {
return false;
}
if (isTranscriptMessageRecord(entry)) return false;
}
return true;
}
/** Resolves the action label for one session key from cleanup key sets. */
function resolveSessionCleanupAction(params) {
if (params.dmScopeRetiredKeys.has(params.key)) return "retire-dm-scope";
if (params.missingKeys.has(params.key)) return "prune-missing";
if (params.staleKeys.has(params.key)) return "prune-stale";
if (params.cappedKeys.has(params.key)) return "cap-overflow";
if (params.budgetEvictedKeys.has(params.key)) return "evict-budget";
return "keep";
}
function isMainScopeStaleDirectSessionKey(params) {
if ((params.cfg.session?.dmScope ?? "main") !== "main") return false;
if (params.activeKey && params.key === params.activeKey) return false;
const parsed = parseAgentSessionKey(params.key);
if (!parsed || normalizeAgentId(parsed.agentId) !== normalizeAgentId(params.targetAgentId)) return false;
const parts = parsed.rest.split(":").filter(Boolean);
return parts.length === 2 && parts[0] === "direct" || parts.length === 3 && parts[1] === "direct" || parts.length === 4 && parts[2] === "direct";
}
function rememberRemovedSessionFile(removedSessionFiles, entry) {
if (entry?.sessionId) removedSessionFiles.set(entry.sessionId, entry.sessionFile);
}
function retireMainScopeDirectSessionEntries(params) {
let retired = 0;
for (const [key, entry] of Object.entries(params.store)) if (isMainScopeStaleDirectSessionKey({
cfg: params.cfg,
targetAgentId: params.targetAgentId,
key,
activeKey: params.activeKey
})) {
params.onRetired?.(key, entry);
delete params.store[key];
retired += 1;
}
return retired;
}
function serializeSessionCleanupResult(params) {
if (params.summaries.length === 1) return params.summaries[0] ?? {};
return {
allAgents: true,
mode: params.mode,
dryRun: params.dryRun,
stores: params.summaries
};
}
function pruneMissingTranscriptEntries(params) {
const sessionPathOpts = resolveSessionFilePathOptions({ storePath: params.storePath });
let removed = 0;
for (const [key, entry] of Object.entries(params.store)) {
if (!entry?.sessionId) {
if (parseAgentSessionKey(key)) continue;
delete params.store[key];
removed += 1;
params.onPruned?.(key);
continue;
}
let transcriptPath;
try {
transcriptPath = resolveSessionFilePath(entry.sessionId, entry, sessionPathOpts);
} catch {}
if (!transcriptPath || !fs.existsSync(transcriptPath) || transcriptHasNoMessageRecords(transcriptPath)) {
delete params.store[key];
removed += 1;
params.onPruned?.(key);
}
}
return removed;
}
function addEntryArtifactPathsToSet(params) {
const sessionsDir = path.dirname(params.storePath);
for (const key of params.keys) {
const entry = params.store[key];
if (!entry) continue;
for (const artifactPath of resolveSessionArtifactCanonicalPathsForEntry({
sessionsDir,
entry
})) params.paths.add(artifactPath);
}
}
async function previewStoreCleanup(params) {
const beforeStore = loadSessionStore(params.target.storePath, { skipCache: true });
const previewStore = cloneSessionStoreRecord(beforeStore);
const staleKeys = /* @__PURE__ */ new Set();
const cappedKeys = /* @__PURE__ */ new Set();
const missingKeys = /* @__PURE__ */ new Set();
const dmScopeRetiredKeys = /* @__PURE__ */ new Set();
const missing = params.fixMissing === true ? pruneMissingTranscriptEntries({
store: previewStore,
storePath: params.target.storePath,
onPruned: (key) => {
missingKeys.add(key);
}
}) : 0;
const dmScopeRetired = params.fixDmScope === true ? retireMainScopeDirectSessionEntries({
cfg: params.cfg,
store: previewStore,
targetAgentId: params.target.agentId,
activeKey: params.activeKey,
onRetired: (key) => {
dmScopeRetiredKeys.add(key);
}
}) : 0;
const preserveSessionKeys = collectSessionMaintenancePreserveKeys([params.activeKey]);
const pruned = pruneStaleEntries(previewStore, params.maintenance.pruneAfterMs, {
log: false,
preserveKeys: preserveSessionKeys,
onPruned: ({ key }) => {
staleKeys.add(key);
}
});
const capped = capEntryCount(previewStore, params.maintenance.maxEntries, {
log: false,
preserveKeys: preserveSessionKeys,
onCapped: ({ key }) => {
cappedKeys.add(key);
}
});
const entryCleanupArtifactPaths = /* @__PURE__ */ new Set();
addEntryArtifactPathsToSet({
paths: entryCleanupArtifactPaths,
store: beforeStore,
storePath: params.target.storePath,
keys: staleKeys
});
addEntryArtifactPathsToSet({
paths: entryCleanupArtifactPaths,
store: beforeStore,
storePath: params.target.storePath,
keys: cappedKeys
});
addEntryArtifactPathsToSet({
paths: entryCleanupArtifactPaths,
store: beforeStore,
storePath: params.target.storePath,
keys: dmScopeRetiredKeys
});
const beforeBudgetStore = cloneSessionStoreRecord(previewStore);
const budgetRemovedFilePaths = /* @__PURE__ */ new Set();
const diskBudget = await enforceSessionDiskBudget({
store: previewStore,
storePath: params.target.storePath,
activeSessionKey: params.activeKey,
preserveKeys: preserveSessionKeys,
maintenance: params.maintenance,
warnOnly: false,
dryRun: true,
onRemoveFile: (canonicalPath) => {
budgetRemovedFilePaths.add(canonicalPath);
}
});
const unreferencedArtifacts = await pruneUnreferencedSessionArtifacts({
store: previewStore,
storePath: params.target.storePath,
olderThanMs: params.maintenance.pruneAfterMs,
dryRun: true,
excludeCanonicalPaths: new Set([...budgetRemovedFilePaths, ...entryCleanupArtifactPaths])
});
const budgetEvictedKeys = /* @__PURE__ */ new Set();
for (const key of Object.keys(beforeBudgetStore)) if (!Object.hasOwn(previewStore, key)) budgetEvictedKeys.add(key);
const beforeCount = Object.keys(beforeStore).length;
const afterPreviewCount = Object.keys(previewStore).length;
const wouldMutate = missing > 0 || dmScopeRetired > 0 || pruned > 0 || capped > 0 || unreferencedArtifacts.removedFiles > 0 || (diskBudget?.removedEntries ?? 0) > 0 || (diskBudget?.removedFiles ?? 0) > 0;
return {
summary: {
agentId: params.target.agentId,
storePath: params.target.storePath,
mode: params.mode,
dryRun: params.dryRun,
beforeCount,
afterCount: afterPreviewCount,
missing,
dmScopeRetired,
pruned,
capped,
unreferencedArtifacts,
diskBudget,
wouldMutate
},
beforeStore,
missingKeys,
staleKeys,
cappedKeys,
budgetEvictedKeys,
dmScopeRetiredKeys
};
}
/** Runs session cleanup preview/apply for the selected store targets. */
async function runSessionsCleanup(params) {
const { cfg, opts } = params;
const maintenance = resolveMaintenanceConfig();
const mode = opts.enforce ? "enforce" : maintenance.mode;
const targets = params.targets ?? resolveSessionStoreTargets(cfg, {
store: opts.store,
agent: opts.agent,
allAgents: opts.allAgents
});
const previewResults = [];
for (const target of targets) {
const result = await previewStoreCleanup({
cfg,
target,
maintenance,
mode,
dryRun: Boolean(opts.dryRun),
activeKey: opts.activeKey,
fixMissing: Boolean(opts.fixMissing),
fixDmScope: Boolean(opts.fixDmScope)
});
previewResults.push(result);
}
const appliedSummaries = [];
if (!opts.dryRun) for (const target of targets) {
const appliedReportRef = { current: null };
const dmScopeRemovedSessionFiles = /* @__PURE__ */ new Map();
let missingApplied = 0;
let dmScopeRetiredApplied = 0;
await updateSessionStore(target.storePath, async (store) => {
let removed = 0;
if (opts.fixMissing) {
missingApplied = pruneMissingTranscriptEntries({
store,
storePath: target.storePath
});
removed += missingApplied;
}
if (opts.fixDmScope) {
dmScopeRetiredApplied = retireMainScopeDirectSessionEntries({
cfg,
store,
targetAgentId: target.agentId,
activeKey: opts.activeKey,
onRetired: (_key, entry) => {
rememberRemovedSessionFile(dmScopeRemovedSessionFiles, entry);
}
});
removed += dmScopeRetiredApplied;
}
return removed;
}, {
activeSessionKey: opts.activeKey,
maintenanceOverride: { mode },
onMaintenanceApplied: (report) => {
appliedReportRef.current = report;
}
});
if (dmScopeRemovedSessionFiles.size > 0) {
const storeAfterDmScopeRetire = loadSessionStore(target.storePath, { skipCache: true });
await archiveRemovedSessionTranscripts({
removedSessionFiles: dmScopeRemovedSessionFiles,
referencedSessionIds: new Set(Object.values(storeAfterDmScopeRetire).map((entry) => entry?.sessionId).filter((id) => Boolean(id))),
storePath: target.storePath,
reason: "deleted",
restrictToStoreDir: true
});
}
const afterStore = loadSessionStore(target.storePath, { skipCache: true });
const unreferencedArtifacts = mode === "warn" ? {
scannedFiles: 0,
removedFiles: 0,
freedBytes: 0,
olderThanMs: maintenance.pruneAfterMs
} : await pruneUnreferencedSessionArtifacts({
store: afterStore,
storePath: target.storePath,
olderThanMs: maintenance.pruneAfterMs,
dryRun: false
});
const preview = previewResults.find((result) => result.summary.storePath === target.storePath);
const appliedReport = appliedReportRef.current;
const summary = appliedReport === null ? {
...preview?.summary ?? {
agentId: target.agentId,
storePath: target.storePath,
mode,
dryRun: false,
beforeCount: 0,
afterCount: 0,
missing: 0,
dmScopeRetired: 0,
pruned: 0,
capped: 0,
unreferencedArtifacts,
diskBudget: null,
wouldMutate: false
},
dryRun: false,
unreferencedArtifacts,
wouldMutate: (preview?.summary.wouldMutate ?? false) || unreferencedArtifacts.removedFiles > 0,
applied: true,
appliedCount: Object.keys(afterStore).length
} : {
agentId: target.agentId,
storePath: target.storePath,
mode: appliedReport.mode,
dryRun: false,
beforeCount: appliedReport.beforeCount,
afterCount: appliedReport.afterCount,
missing: missingApplied,
dmScopeRetired: dmScopeRetiredApplied,
pruned: appliedReport.pruned,
capped: appliedReport.capped,
unreferencedArtifacts,
diskBudget: appliedReport.diskBudget,
wouldMutate: missingApplied > 0 || dmScopeRetiredApplied > 0 || appliedReport.pruned > 0 || appliedReport.capped > 0 || unreferencedArtifacts.removedFiles > 0 || (appliedReport.diskBudget?.removedEntries ?? 0) > 0 || (appliedReport.diskBudget?.removedFiles ?? 0) > 0,
applied: true,
appliedCount: Object.keys(afterStore).length
};
appliedSummaries.push(summary);
}
return {
mode,
previewResults,
appliedSummaries
};
}
/** Purge session store entries for a deleted agent (#65524). Best-effort. */
async function purgeAgentSessionStoreEntries(cfg, agentId) {
try {
const normalizedAgentId = normalizeAgentId(agentId);
const storeConfig = cfg.session?.store;
const storeAgentId = typeof storeConfig === "string" && storeConfig.includes("{agentId}") ? normalizedAgentId : normalizeAgentId(resolveDefaultAgentId(cfg));
await updateSessionStore(resolveStorePath(cfg.session?.store, { agentId: normalizedAgentId }), (store) => {
for (const key of Object.keys(store)) if (resolveStoredSessionOwnerAgentId({
cfg,
agentId: storeAgentId,
sessionKey: key
}) === normalizedAgentId) delete store[key];
});
} catch (err) {
getLogger().debug("session store purge skipped during agent delete", err);
}
}
//#endregion
export { updateSessionGoalStatus as _, hasTerminalMainSessionTranscriptNewerThanRegistry as a, resolveSessionLifecycleTimestamps as c, MODEL_UPDATABLE_SESSION_GOAL_STATUSES as d, clearSessionGoal as f, resolveSessionGoalDisplayState as g, getSessionGoal as h, serializeSessionCleanupResult as i, resolveTerminalMainSessionTranscriptRegistryCheck as l, formatSessionGoalStatus as m, resolveSessionCleanupAction as n, hasTerminalMainSessionTranscriptNewerThanRegistrySync as o, createSessionGoal as p, runSessionsCleanup as r, readSessionHeaderStartedAtMs as s, purgeAgentSessionStoreEntries as t, resolveMainSessionKeyFromConfig as u };