mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: clarify session accessor contracts
This commit is contained in:
@@ -32,32 +32,55 @@ import { streamSessionTranscriptLines } from "./transcript-stream.js";
|
||||
import { resolveSessionTranscriptFile } from "./transcript.js";
|
||||
import type { SessionEntry } from "./types.js";
|
||||
|
||||
/**
|
||||
* Session access API for callers that need entries or transcripts without
|
||||
* depending on the persisted store layout. Callers provide stable session
|
||||
* identity, and this module resolves the current entry/transcript target while
|
||||
* preserving canonical-key, transcript-linking, and update-notification rules.
|
||||
*/
|
||||
export type SessionAccessScope = {
|
||||
/** Agent owner used when the session key does not already encode one. */
|
||||
agentId?: string;
|
||||
/**
|
||||
* Set false only for internal read-only hot paths that will not retain or
|
||||
* mutate the returned entry.
|
||||
*/
|
||||
clone?: boolean;
|
||||
/** Environment override used when resolving agent-scoped store paths in tests/tools. */
|
||||
env?: NodeJS.ProcessEnv;
|
||||
/** Set false for metadata-only reads that do not need hydrated prompt refs. */
|
||||
hydrateSkillPromptRefs?: boolean;
|
||||
/** Canonical or alias session key for the entry being read or written. */
|
||||
sessionKey: string;
|
||||
/** Explicit store path for callers that already resolved the owning store. */
|
||||
storePath?: string;
|
||||
};
|
||||
|
||||
export type SessionTranscriptReadScope = Omit<SessionAccessScope, "sessionKey"> & {
|
||||
/** Explicit transcript file path; bypasses store lookup when already known. */
|
||||
sessionFile?: string;
|
||||
/** Runtime session id used to derive a transcript file when no explicit file is provided. */
|
||||
sessionId: string;
|
||||
/** Optional key for read callers that can resolve via the session entry. */
|
||||
sessionKey?: string;
|
||||
/** Channel thread suffix used when deriving topic transcript paths. */
|
||||
threadId?: string | number;
|
||||
};
|
||||
|
||||
export type SessionTranscriptAccessScope = SessionTranscriptReadScope & {
|
||||
/** Required for writes because write paths may update entry metadata. */
|
||||
sessionKey: string;
|
||||
};
|
||||
|
||||
export type SessionTranscriptWriteScope = Omit<SessionTranscriptAccessScope, "sessionId"> & {
|
||||
/** Optional for appenders that can operate on an existing explicit transcript target. */
|
||||
sessionId?: string;
|
||||
};
|
||||
|
||||
export type SessionEntrySummary = {
|
||||
/** Persisted key for the entry. */
|
||||
sessionKey: string;
|
||||
/** Entry value cloned from the backing store unless the caller requested borrowed reads. */
|
||||
entry: SessionEntry;
|
||||
};
|
||||
|
||||
@@ -67,44 +90,62 @@ export type ExactSessionEntry = {
|
||||
entry: SessionEntry;
|
||||
};
|
||||
|
||||
/** Raw transcript record for non-message events; message records use appendTranscriptMessage. */
|
||||
export type TranscriptEvent = unknown;
|
||||
|
||||
export type TranscriptMessageAppendOptions<TMessage> = {
|
||||
/** Runtime config used for message redaction and transcript header metadata. */
|
||||
config?: OpenClawConfig;
|
||||
/** Working directory recorded in a newly created transcript header. */
|
||||
cwd?: string;
|
||||
/** How duplicate message idempotency keys are detected before append. */
|
||||
idempotencyLookup?: "scan" | "caller-checked";
|
||||
/** Provider/channel message payload to persist. */
|
||||
message: TMessage;
|
||||
/** Testable timestamp override for the generated transcript entry. */
|
||||
now?: number;
|
||||
/** Optional finalizer that runs after duplicate detection but before persistence. */
|
||||
prepareMessageAfterIdempotencyCheck?: (message: TMessage) => TMessage | undefined;
|
||||
/** Allow append without parent-link migration for large legacy linear transcripts. */
|
||||
useRawWhenLinear?: boolean;
|
||||
};
|
||||
|
||||
export type TranscriptMessageAppendResult<TMessage> = {
|
||||
/** False when idempotency lookup found an existing transcript message. */
|
||||
appended: boolean;
|
||||
/** Redacted message payload as persisted or replayed from the transcript. */
|
||||
message: TMessage;
|
||||
/** Existing or newly generated transcript message id. */
|
||||
messageId: string;
|
||||
};
|
||||
|
||||
/** Transcript update fields supplied by callers; sessionFile is resolved here. */
|
||||
export type TranscriptUpdatePayload = Omit<SessionTranscriptUpdate, "sessionFile">;
|
||||
|
||||
export type SessionEntryUpdateOptions = {
|
||||
/** Skip prune/cap/rotation maintenance for specialized internal updates. */
|
||||
skipMaintenance?: boolean;
|
||||
/** Let the writer cache retain the updated object without cloning. */
|
||||
takeCacheOwnership?: boolean;
|
||||
};
|
||||
|
||||
export type SessionEntryPatchOptions = {
|
||||
/** Entry to synthesize when a patch operation is allowed to create. */
|
||||
fallbackEntry?: SessionEntry;
|
||||
/** Keep the previous updatedAt value when the patch should not count as activity. */
|
||||
preserveActivity?: boolean;
|
||||
/** Replace the whole entry instead of merging the returned patch. */
|
||||
replaceEntry?: boolean;
|
||||
};
|
||||
|
||||
export type SessionEntryPatchContext = {
|
||||
/** Present when the patched entry already existed before fallback synthesis. */
|
||||
existingEntry?: SessionEntry;
|
||||
};
|
||||
|
||||
export type { SessionLifecycleArtifactCleanupParams, SessionLifecycleArtifactCleanupResult };
|
||||
|
||||
/** Loads one session entry through the storage-neutral accessor seam. */
|
||||
/** Returns the entry for a canonical or alias session key, if one exists. */
|
||||
export function loadSessionEntry(scope: SessionAccessScope): SessionEntry | undefined {
|
||||
if (scope.clone === false) {
|
||||
const store = loadSessionStore(resolveAccessStorePath(scope), {
|
||||
@@ -117,8 +158,9 @@ export function loadSessionEntry(scope: SessionAccessScope): SessionEntry | unde
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads one entry only when the persisted key exactly matches the requested key.
|
||||
* Approval routing uses this to avoid canonical alias lookup crossing accounts.
|
||||
* Returns only the row persisted under the exact key provided.
|
||||
* Use this for authorization-sensitive routing where alias canonicalization
|
||||
* could cross an account or agent boundary.
|
||||
*/
|
||||
export function loadExactSessionEntry(scope: SessionAccessScope): ExactSessionEntry | undefined {
|
||||
const sessionKey = scope.sessionKey.trim();
|
||||
@@ -133,7 +175,7 @@ export function loadExactSessionEntry(scope: SessionAccessScope): ExactSessionEn
|
||||
return entry ? { sessionKey, entry } : undefined;
|
||||
}
|
||||
|
||||
/** Lists session entries through the storage-neutral accessor seam. */
|
||||
/** Lists entries from the resolved store, preserving the persisted key for each row. */
|
||||
export function listSessionEntries(
|
||||
scope: Partial<Omit<SessionAccessScope, "sessionKey">> = {},
|
||||
): SessionEntrySummary[] {
|
||||
@@ -148,7 +190,7 @@ export function listSessionEntries(
|
||||
return listFileSessionEntries(scope);
|
||||
}
|
||||
|
||||
/** Reads a session activity timestamp through the storage-neutral accessor seam. */
|
||||
/** Reads the last activity timestamp for one session entry, or undefined when absent. */
|
||||
export function readSessionUpdatedAt(scope: SessionAccessScope): number | undefined {
|
||||
if (scope.storePath) {
|
||||
return readFileSessionUpdatedAt({
|
||||
@@ -159,7 +201,7 @@ export function readSessionUpdatedAt(scope: SessionAccessScope): number | undefi
|
||||
return loadSessionEntry(scope)?.updatedAt;
|
||||
}
|
||||
|
||||
/** Applies a partial entry update through the storage-neutral accessor seam. */
|
||||
/** Creates or updates one entry from a partial patch and returns the persisted entry. */
|
||||
export async function upsertSessionEntry(
|
||||
scope: SessionAccessScope,
|
||||
patch: Partial<SessionEntry>,
|
||||
@@ -171,7 +213,7 @@ export async function upsertSessionEntry(
|
||||
});
|
||||
}
|
||||
|
||||
/** Replaces one entry through the storage-neutral accessor seam. */
|
||||
/** Replaces one entry with the supplied value and returns the persisted entry. */
|
||||
export async function replaceSessionEntry(
|
||||
scope: SessionAccessScope,
|
||||
entry: SessionEntry,
|
||||
@@ -184,7 +226,11 @@ export async function replaceSessionEntry(
|
||||
});
|
||||
}
|
||||
|
||||
/** Patches one entry atomically through the storage-neutral accessor seam. */
|
||||
/**
|
||||
* Applies an atomic patch to one entry.
|
||||
* The updater sees the current entry plus whether it was synthesized from a
|
||||
* fallback; returning null skips persistence.
|
||||
*/
|
||||
export async function patchSessionEntry(
|
||||
scope: SessionAccessScope,
|
||||
update: (
|
||||
@@ -202,7 +248,7 @@ export async function patchSessionEntry(
|
||||
});
|
||||
}
|
||||
|
||||
/** Updates an existing session entry through the storage-neutral accessor seam. */
|
||||
/** Updates an existing entry only; returns null when the session is absent. */
|
||||
export async function updateSessionEntry(
|
||||
scope: SessionAccessScope,
|
||||
update: (
|
||||
@@ -219,14 +265,14 @@ export async function updateSessionEntry(
|
||||
});
|
||||
}
|
||||
|
||||
/** Cleans scoped session lifecycle entries and transcript artifacts through the accessor seam. */
|
||||
/** Removes entries and orphan transcript artifacts owned by a named session lifecycle. */
|
||||
export async function cleanupSessionLifecycleArtifacts(
|
||||
params: SessionLifecycleArtifactCleanupParams,
|
||||
): Promise<SessionLifecycleArtifactCleanupResult> {
|
||||
return await cleanupFileSessionLifecycleArtifacts(params);
|
||||
}
|
||||
|
||||
/** Loads raw transcript events through the storage-neutral accessor seam. */
|
||||
/** Reads parsed transcript records from an explicit or derived transcript target. */
|
||||
export async function loadTranscriptEvents(
|
||||
scope: SessionTranscriptReadScope,
|
||||
): Promise<TranscriptEvent[]> {
|
||||
@@ -238,7 +284,11 @@ export async function loadTranscriptEvents(
|
||||
return events;
|
||||
}
|
||||
|
||||
/** Appends one raw transcript event through the storage-neutral accessor seam. */
|
||||
/**
|
||||
* Appends a non-message transcript record such as session or metadata events.
|
||||
* Message records must use appendTranscriptMessage so parent links, idempotency,
|
||||
* and redaction are preserved.
|
||||
*/
|
||||
export async function appendTranscriptEvent(
|
||||
scope: SessionTranscriptAccessScope,
|
||||
event: TranscriptEvent,
|
||||
@@ -264,7 +314,10 @@ function assertNonMessageTranscriptEvent(event: TranscriptEvent): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** Appends one transcript message through the storage-neutral writer seam. */
|
||||
/**
|
||||
* Appends one transcript message with message-id generation and optional
|
||||
* idempotency lookup. The returned message is the redacted persisted value.
|
||||
*/
|
||||
export async function appendTranscriptMessage<TMessage>(
|
||||
scope: SessionTranscriptWriteScope,
|
||||
options: TranscriptMessageAppendOptions<TMessage> & {
|
||||
@@ -297,7 +350,7 @@ export async function appendTranscriptMessage<TMessage>(
|
||||
});
|
||||
}
|
||||
|
||||
/** Publishes a transcript update after resolving the current storage target. */
|
||||
/** Emits a transcript update after resolving the current transcript target. */
|
||||
export async function publishTranscriptUpdate(
|
||||
scope: SessionTranscriptWriteScope,
|
||||
update: TranscriptUpdatePayload = {},
|
||||
|
||||
Reference in New Issue
Block a user