Skip to content

Commit c87c156

Browse files
committed
docs: document session cleanup helpers
1 parent d00d10f commit c87c156

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

src/config/sessions/artifacts.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Session artifact filename classifiers and archive timestamp helpers.
2+
// Cleanup, disk-budget, and usage accounting use these predicates to avoid deleting live transcripts.
3+
14
import { timestampMsToIsoFileStamp } from "@openclaw/normalization-core/number-coercion";
25
import { escapeRegExp } from "../../shared/regexp.js";
36

@@ -18,6 +21,7 @@ function hasArchiveSuffix(fileName: string, reason: SessionArchiveReason): boole
1821
return ARCHIVE_TIMESTAMP_RE.test(raw);
1922
}
2023

24+
/** Returns true for archived session artifacts and legacy store backup names. */
2125
export function isSessionArchiveArtifactName(fileName: string): boolean {
2226
if (LEGACY_STORE_BACKUP_RE.test(fileName)) {
2327
return true;
@@ -59,6 +63,7 @@ export function isSessionStoreTempArtifactName(fileName: string, storeBasename:
5963
return sessionStoreTempPattern(storeBasename).test(fileName);
6064
}
6165

66+
/** Parses a compaction checkpoint transcript filename into session/checkpoint ids. */
6267
export function parseCompactionCheckpointTranscriptFileName(fileName: string): {
6368
sessionId: string;
6469
checkpointId: string;
@@ -69,22 +74,27 @@ export function parseCompactionCheckpointTranscriptFileName(fileName: string): {
6974
return sessionId && checkpointId ? { sessionId, checkpointId } : null;
7075
}
7176

77+
/** Returns true when a filename is a compaction checkpoint transcript. */
7278
export function isCompactionCheckpointTranscriptFileName(fileName: string): boolean {
7379
return parseCompactionCheckpointTranscriptFileName(fileName) !== null;
7480
}
7581

82+
/** Returns true for trajectory runtime jsonl artifacts. */
7683
export function isTrajectoryRuntimeArtifactName(fileName: string): boolean {
7784
return fileName.endsWith(".trajectory.jsonl");
7885
}
7986

87+
/** Returns true for trajectory pointer artifacts. */
8088
export function isTrajectoryPointerArtifactName(fileName: string): boolean {
8189
return fileName.endsWith(".trajectory-path.json");
8290
}
8391

92+
/** Returns true for any trajectory-related session artifact. */
8493
export function isTrajectorySessionArtifactName(fileName: string): boolean {
8594
return isTrajectoryRuntimeArtifactName(fileName) || isTrajectoryPointerArtifactName(fileName);
8695
}
8796

97+
/** Returns true for primary session transcript files that represent live session history. */
8898
export function isPrimarySessionTranscriptFileName(fileName: string): boolean {
8999
if (fileName === "sessions.json") {
90100
return false;
@@ -101,13 +111,15 @@ export function isPrimarySessionTranscriptFileName(fileName: string): boolean {
101111
return !isSessionArchiveArtifactName(fileName);
102112
}
103113

114+
/** Returns true for transcript files counted in usage, including reset/deleted archives. */
104115
export function isUsageCountedSessionTranscriptFileName(fileName: string): boolean {
105116
if (isPrimarySessionTranscriptFileName(fileName)) {
106117
return true;
107118
}
108119
return hasArchiveSuffix(fileName, "reset") || hasArchiveSuffix(fileName, "deleted");
109120
}
110121

122+
/** Extracts the session id from a usage-counted transcript filename. */
111123
export function parseUsageCountedSessionIdFromFileName(fileName: string): string | null {
112124
if (isPrimarySessionTranscriptFileName(fileName)) {
113125
return fileName.slice(0, -".jsonl".length);
@@ -122,6 +134,7 @@ export function parseUsageCountedSessionIdFromFileName(fileName: string): string
122134
return null;
123135
}
124136

137+
/** Formats an archive timestamp that is safe for filenames. */
125138
export function formatSessionArchiveTimestamp(nowMs = Date.now()): string {
126139
return timestampMsToIsoFileStamp(nowMs);
127140
}

src/config/sessions/cleanup-service.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Session cleanup service for store entries and transcript/artifact files.
2+
// Supports dry-run/apply modes, stale pruning, missing transcript fixes, DM-scope retirement, and disk budgets.
3+
14
import fs from "node:fs";
25
import path from "node:path";
36
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
@@ -134,6 +137,7 @@ function transcriptHasNoMessageRecords(transcriptPath: string): boolean {
134137
return false;
135138
}
136139
if (!stat.isFile() || stat.size > EMPTY_TRANSCRIPT_MAX_BYTES) {
140+
// Only inspect small transcript files; larger files are assumed to contain real history.
137141
return false;
138142
}
139143

@@ -162,6 +166,7 @@ function transcriptHasNoMessageRecords(transcriptPath: string): boolean {
162166
return true;
163167
}
164168

169+
/** Resolves the action label for one session key from cleanup key sets. */
165170
export function resolveSessionCleanupAction(params: {
166171
key: string;
167172
missingKeys: Set<string>;
@@ -274,6 +279,7 @@ function pruneMissingTranscriptEntries(params: {
274279
for (const [key, entry] of Object.entries(params.store)) {
275280
if (!entry?.sessionId) {
276281
if (parseAgentSessionKey(key)) {
282+
// Agent-scoped keys without session ids are valid routing entries; keep them.
277283
continue;
278284
}
279285
delete params.store[key];
@@ -332,6 +338,7 @@ async function previewStoreCleanup(params: {
332338
fixDmScope?: boolean;
333339
}) {
334340
const beforeStore = loadSessionStore(params.target.storePath, { skipCache: true });
341+
// Preview always mutates a clone so dry-run output can report exact counts without touching disk.
335342
const previewStore = cloneSessionStoreRecord(beforeStore);
336343
const staleKeys = new Set<string>();
337344
const cappedKeys = new Set<string>();
@@ -458,6 +465,7 @@ async function previewStoreCleanup(params: {
458465
};
459466
}
460467

468+
/** Runs session cleanup preview/apply for the selected store targets. */
461469
export async function runSessionsCleanup(params: {
462470
cfg: OpenClawConfig;
463471
opts: SessionsCleanupOptions;
@@ -510,6 +518,7 @@ export async function runSessionsCleanup(params: {
510518
removed += missingApplied;
511519
}
512520
if (opts.fixDmScope) {
521+
// DM-scope retirement removes stale main-scope direct entries during apply.
513522
dmScopeRetiredApplied = retireMainScopeDirectSessionEntries({
514523
cfg,
515524
store,
@@ -534,6 +543,7 @@ export async function runSessionsCleanup(params: {
534543
},
535544
);
536545
if (dmScopeRemovedSessionFiles.size > 0) {
546+
// Archive removed direct-session transcripts unless still referenced by surviving entries.
537547
const storeAfterDmScopeRetire = loadSessionStore(target.storePath, { skipCache: true });
538548
await archiveRemovedSessionTranscripts({
539549
removedSessionFiles: dmScopeRemovedSessionFiles,

src/config/sessions/combined-store-gateway.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
// Builds the gateway-visible combined session store across agent-specific stores.
2+
// Gateway callers need canonical per-agent keys even when stores are split by `{agentId}`.
3+
14
import { resolveDefaultAgentId } from "../../agents/agent-scope.js";
25
import {
36
canonicalizeSpawnedByForAgent,
@@ -30,6 +33,7 @@ function mergeSessionEntryIntoCombined(params: {
3033
const existing = combined[canonicalKey];
3134

3235
if (existing && (existing.updatedAt ?? 0) > (entry.updatedAt ?? 0)) {
36+
// Preserve the freshest entry while still canonicalizing spawnedBy for this agent store.
3337
const spawnedBy = canonicalizeSpawnedByForAgent(
3438
cfg,
3539
agentId,
@@ -59,6 +63,7 @@ function mergeSessionEntryIntoCombined(params: {
5963
}
6064
}
6165

66+
/** Loads and canonicalizes session entries for gateway views across one or more agent stores. */
6267
export function loadCombinedSessionStoreForGateway(
6368
cfg: OpenClawConfig,
6469
opts: { agentId?: string; configuredAgentsOnly?: boolean } = {},
@@ -68,6 +73,7 @@ export function loadCombinedSessionStoreForGateway(
6873
} {
6974
const storeConfig = cfg.session?.store;
7075
if (storeConfig && !isStorePathTemplate(storeConfig)) {
76+
// A single shared store still needs keys canonicalized as if owned by the default agent.
7177
const storePath = resolveStorePath(storeConfig);
7278
const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg));
7379
const store = loadSessionStore(storePath, { clone: false });

0 commit comments

Comments
 (0)