UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

744 lines (743 loc) 27.4 kB
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 };