Skip to content

Commit f2ad875

Browse files
committed
docs: document session accessor contract
1 parent b39023f commit f2ad875

1 file changed

Lines changed: 145 additions & 13 deletions

File tree

src/config/sessions/session-accessor.ts

Lines changed: 145 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,79 +32,153 @@ import { streamSessionTranscriptLines } from "./transcript-stream.js";
3232
import { resolveSessionTranscriptFile } from "./transcript.js";
3333
import type { SessionEntry } from "./types.js";
3434

35+
/**
36+
* Identifies a session entry within one agent's session store.
37+
*
38+
* `sessionKey` is the caller-facing routing key. Accessors may normalize aliases
39+
* when reading or writing unless the exact-key variant is used. `agentId` and
40+
* `storePath` are explicit owner hints for callers that already know the target
41+
* store; otherwise the owner is derived from the session key and runtime config.
42+
*/
3543
export type SessionAccessScope = {
44+
/** Agent that owns the session store, when the caller already knows it. */
3645
agentId?: string;
46+
/** Return the stored object by reference when false; default reads are cloned. */
3747
clone?: boolean;
48+
/** Environment used only while resolving the configured session store path. */
3849
env?: NodeJS.ProcessEnv;
50+
/** Disable skill prompt hydration for callers that need raw stored fields. */
3951
hydrateSkillPromptRefs?: boolean;
52+
/** Session routing key requested by the caller. */
4053
sessionKey: string;
54+
/** Explicit store path for scoped tools, tests, or already-resolved callers. */
4155
storePath?: string;
4256
};
4357

58+
/**
59+
* Identifies transcript content for read-only access.
60+
*
61+
* A read can target a named transcript artifact directly with `sessionFile`, or
62+
* resolve the artifact from `sessionId` plus store/agent/thread identity. Reads
63+
* do not require `sessionKey` because historical projections often already have
64+
* the persisted session id but not the original routing key.
65+
*/
4466
export type SessionTranscriptReadScope = Omit<SessionAccessScope, "sessionKey"> & {
67+
/** Explicit transcript artifact path for compatibility and import/export callers. */
4568
sessionFile?: string;
69+
/** Stable session identifier stored in the session entry and transcript header. */
4670
sessionId: string;
71+
/** Routing key when available; used to preserve scoped agent/thread semantics. */
4772
sessionKey?: string;
73+
/** Optional thread discriminator for thread-scoped transcript artifacts. */
4874
threadId?: string | number;
4975
};
5076

77+
/**
78+
* Identifies transcript content for operations that must also know the session key.
79+
*/
5180
export type SessionTranscriptAccessScope = SessionTranscriptReadScope & {
5281
sessionKey: string;
5382
};
5483

84+
/**
85+
* Identifies transcript content for append/update operations.
86+
*
87+
* Writers may create or persist the transcript target, so they require
88+
* `sessionKey` and either an existing `sessionId` or enough entry context for the
89+
* caller to supply one later. Direct `sessionFile` remains an explicit artifact
90+
* target, not the normal identity for runtime callers.
91+
*/
5592
export type SessionTranscriptWriteScope = Omit<SessionTranscriptAccessScope, "sessionId"> & {
5693
sessionId?: string;
5794
};
5895

96+
/** One listed session entry paired with the routing key used to find it. */
5997
export type SessionEntrySummary = {
6098
sessionKey: string;
6199
entry: SessionEntry;
62100
};
63101

64-
/** Session entry read by the exact persisted session key, without alias resolution. */
102+
/**
103+
* Session entry read by exact persisted key.
104+
*
105+
* Use this when the caller must not cross aliases or normalized account/session
106+
* keys, such as approval routing and account binding decisions.
107+
*/
65108
export type ExactSessionEntry = {
66109
sessionKey: string;
67110
entry: SessionEntry;
68111
};
69112

113+
/** Raw transcript record as stored by the transcript append pipeline. */
70114
export type TranscriptEvent = unknown;
71115

116+
/** Options for appending one projected message to a transcript. */
72117
export type TranscriptMessageAppendOptions<TMessage> = {
118+
/** Runtime config used by message normalization and transcript append helpers. */
73119
config?: OpenClawConfig;
120+
/** Working directory for resolving relative media paths in appended messages. */
74121
cwd?: string;
122+
/** Idempotency strategy for callers that already performed duplicate checks. */
75123
idempotencyLookup?: "scan" | "caller-checked";
124+
/** Message payload to append after optional idempotency preparation. */
76125
message: TMessage;
126+
/** Timestamp override for deterministic tests or replayed events. */
77127
now?: number;
128+
/** Last chance to transform or drop a message after idempotency checks pass. */
78129
prepareMessageAfterIdempotencyCheck?: (message: TMessage) => TMessage | undefined;
130+
/** Preserve raw message bytes when the existing transcript is linear. */
79131
useRawWhenLinear?: boolean;
80132
};
81133

134+
/** Result returned when a message append either writes or reuses an idempotent hit. */
82135
export type TranscriptMessageAppendResult<TMessage> = {
83136
appended: boolean;
84137
message: TMessage;
85138
messageId: string;
86139
};
87140

141+
/**
142+
* Transcript update payload supplied by callers.
143+
*
144+
* The accessor resolves the concrete transcript artifact before publishing, so
145+
* callers provide session identity fields and optional update metadata only.
146+
*/
88147
export type TranscriptUpdatePayload = Omit<SessionTranscriptUpdate, "sessionFile">;
89148

149+
/** Options for updating an existing entry without creating a fallback entry. */
90150
export type SessionEntryUpdateOptions = {
151+
/** Skip pruning or other maintenance work owned by the backing store. */
91152
skipMaintenance?: boolean;
153+
/** Let the update take ownership of any store cache invalidation. */
92154
takeCacheOwnership?: boolean;
93155
};
94156

157+
/** Options for patching or creating one session entry. */
95158
export type SessionEntryPatchOptions = {
159+
/** Entry to create when the target key does not currently exist. */
96160
fallbackEntry?: SessionEntry;
161+
/** Keep the existing activity timestamp unless the patch changes it explicitly. */
97162
preserveActivity?: boolean;
163+
/** Replace the entry with the update result instead of merging fields. */
98164
replaceEntry?: boolean;
99165
};
100166

167+
/** Existing entry snapshot supplied to patch callbacks. */
101168
export type SessionEntryPatchContext = {
102169
existingEntry?: SessionEntry;
103170
};
104171

105172
export type { SessionLifecycleArtifactCleanupParams, SessionLifecycleArtifactCleanupResult };
106173

107-
/** Loads one session entry through the storage-neutral accessor seam. */
174+
/**
175+
* Loads one session entry.
176+
*
177+
* The returned entry follows normal session-key aliasing rules. Pass
178+
* `clone: false` only for tightly scoped callers that intentionally mutate or
179+
* compare the live store object; most callers should use the default cloned
180+
* value.
181+
*/
108182
export function loadSessionEntry(scope: SessionAccessScope): SessionEntry | undefined {
109183
if (scope.clone === false) {
110184
const store = loadSessionStore(resolveAccessStorePath(scope), {
@@ -133,7 +207,12 @@ export function loadExactSessionEntry(scope: SessionAccessScope): ExactSessionEn
133207
return entry ? { sessionKey, entry } : undefined;
134208
}
135209

136-
/** Lists session entries through the storage-neutral accessor seam. */
210+
/**
211+
* Lists all session entries visible in the resolved store.
212+
*
213+
* Each result includes the persisted routing key so callers can preserve the
214+
* exact key in UI projections, maintenance tasks, and export surfaces.
215+
*/
137216
export function listSessionEntries(
138217
scope: Partial<Omit<SessionAccessScope, "sessionKey">> = {},
139218
): SessionEntrySummary[] {
@@ -148,7 +227,9 @@ export function listSessionEntries(
148227
return listFileSessionEntries(scope);
149228
}
150229

151-
/** Reads a session activity timestamp through the storage-neutral accessor seam. */
230+
/**
231+
* Reads the stored session activity timestamp without requiring a full entry projection.
232+
*/
152233
export function readSessionUpdatedAt(scope: SessionAccessScope): number | undefined {
153234
if (scope.storePath) {
154235
return readFileSessionUpdatedAt({
@@ -159,7 +240,12 @@ export function readSessionUpdatedAt(scope: SessionAccessScope): number | undefi
159240
return loadSessionEntry(scope)?.updatedAt;
160241
}
161242

162-
/** Applies a partial entry update through the storage-neutral accessor seam. */
243+
/**
244+
* Creates or patches one session entry by merging a partial update.
245+
*
246+
* Missing entries are created from the supplied patch, including a generated
247+
* `sessionId` and current `updatedAt` when the patch does not provide them.
248+
*/
163249
export async function upsertSessionEntry(
164250
scope: SessionAccessScope,
165251
patch: Partial<SessionEntry>,
@@ -171,7 +257,9 @@ export async function upsertSessionEntry(
171257
});
172258
}
173259

174-
/** Replaces one entry through the storage-neutral accessor seam. */
260+
/**
261+
* Creates or replaces one session entry with the supplied entry shape.
262+
*/
175263
export async function replaceSessionEntry(
176264
scope: SessionAccessScope,
177265
entry: SessionEntry,
@@ -184,7 +272,14 @@ export async function replaceSessionEntry(
184272
});
185273
}
186274

187-
/** Patches one entry atomically through the storage-neutral accessor seam. */
275+
/**
276+
* Applies a callback-driven patch to one session entry.
277+
*
278+
* The callback receives the current entry and may return a partial update, a
279+
* replacement entry when `replaceEntry` is true, or `null` to leave the entry
280+
* unchanged. Store-level locking and timestamp handling are owned by the
281+
* backing implementation.
282+
*/
188283
export async function patchSessionEntry(
189284
scope: SessionAccessScope,
190285
update: (
@@ -202,7 +297,13 @@ export async function patchSessionEntry(
202297
});
203298
}
204299

205-
/** Updates an existing session entry through the storage-neutral accessor seam. */
300+
/**
301+
* Updates an existing session entry without creating a fallback entry.
302+
*
303+
* Use this when absence is meaningful and the caller should not materialize a
304+
* new session row. The update callback returns `null` to leave the entry
305+
* unchanged.
306+
*/
206307
export async function updateSessionEntry(
207308
scope: SessionAccessScope,
208309
update: (
@@ -219,14 +320,27 @@ export async function updateSessionEntry(
219320
});
220321
}
221322

222-
/** Cleans scoped session lifecycle entries and transcript artifacts through the accessor seam. */
323+
/**
324+
* Removes lifecycle-owned session records and transcript artifacts for one scoped target.
325+
*
326+
* This operation is for owner-managed cleanup such as reset, archive, delete,
327+
* or temporary run/session reclamation. It must stay scoped to the requested
328+
* session identity and report what was removed so callers can make cleanup
329+
* idempotent.
330+
*/
223331
export async function cleanupSessionLifecycleArtifacts(
224332
params: SessionLifecycleArtifactCleanupParams,
225333
): Promise<SessionLifecycleArtifactCleanupResult> {
226334
return await cleanupFileSessionLifecycleArtifacts(params);
227335
}
228336

229-
/** Loads raw transcript events through the storage-neutral accessor seam. */
337+
/**
338+
* Loads raw transcript events for a resolved transcript target.
339+
*
340+
* This is intentionally an event-level API: callers that need projected chat
341+
* messages should use the transcript reader helpers that preserve projection,
342+
* bounding, and visibility rules.
343+
*/
230344
export async function loadTranscriptEvents(
231345
scope: SessionTranscriptReadScope,
232346
): Promise<TranscriptEvent[]> {
@@ -238,7 +352,12 @@ export async function loadTranscriptEvents(
238352
return events;
239353
}
240354

241-
/** Appends one raw transcript event through the storage-neutral accessor seam. */
355+
/**
356+
* Appends one raw transcript event to the resolved transcript target.
357+
*
358+
* Prefer `appendTranscriptMessage` for normal assistant/user message writes so
359+
* idempotency and message normalization remain centralized.
360+
*/
242361
export async function appendTranscriptEvent(
243362
scope: SessionTranscriptAccessScope,
244363
event: TranscriptEvent,
@@ -250,7 +369,14 @@ export async function appendTranscriptEvent(
250369
});
251370
}
252371

253-
/** Appends one transcript message through the storage-neutral writer seam. */
372+
/**
373+
* Appends one projected transcript message to the resolved transcript target.
374+
*
375+
* The overload with `prepareMessageAfterIdempotencyCheck` may return
376+
* `undefined` when the preparation callback drops the message. Without that
377+
* callback, successful calls always return the appended or idempotently reused
378+
* message result.
379+
*/
254380
export async function appendTranscriptMessage<TMessage>(
255381
scope: SessionTranscriptWriteScope,
256382
options: TranscriptMessageAppendOptions<TMessage> & {
@@ -283,7 +409,13 @@ export async function appendTranscriptMessage<TMessage>(
283409
});
284410
}
285411

286-
/** Publishes a transcript update after resolving the current storage target. */
412+
/**
413+
* Publishes a transcript update for subscribers of the resolved transcript target.
414+
*
415+
* Callers provide storage-neutral identity and update metadata. The accessor
416+
* resolves the concrete transcript artifact needed by current subscribers and
417+
* includes it in the emitted event.
418+
*/
287419
export async function publishTranscriptUpdate(
288420
scope: SessionTranscriptWriteScope,
289421
update: TranscriptUpdatePayload = {},

0 commit comments

Comments
 (0)