






















@@ -117,34 +117,39 @@ export type SessionAccessScope = {
117117storePath?: string;
118118};
119119120-export type SessionTranscriptReadScope = Omit<SessionAccessScope, "sessionKey"> & {
120+export type SessionTranscriptAccessScope = Omit<SessionAccessScope, "sessionKey"> & {
121121/** Explicit transcript file path; bypasses store lookup when already known. */
122122sessionFile?: string;
123123/** Runtime session id used to derive a transcript file when no explicit file is provided. */
124124sessionId: string;
125-/** Optional key for read callers that can resolve via the session entry. */
125+/** Required when resolving through session metadata; optional for explicit transcript artifacts. */
126126sessionKey?: string;
127127/** Channel thread suffix used when deriving topic transcript paths. */
128128threadId?: string | number;
129129};
130130131-export type SessionTranscriptAccessScope = SessionTranscriptReadScope & {
132-/**
133- * Identifies the owning entry when the transcript target must be resolved
134- * (and possibly persisted) through the session store. May be omitted only
135- * when an explicit sessionFile binds the operation to a concrete artifact;
136- * such writes never read or update entry metadata.
137- */
138-sessionKey?: string;
139-};
140-141131export type SessionTranscriptRuntimeScope = SessionAccessScope & {
142132/** Resolved file-backed artifact for the current runtime target. */
143133sessionFile?: string;
144134sessionId: string;
145135threadId?: string | number;
146136};
147137138+export type SessionTranscriptReadScope = Omit<SessionTranscriptRuntimeScope, "sessionKey"> & {
139+/** Canonical key when the caller has a session-store identity for this read. */
140+sessionKey?: string;
141+/** Entry already loaded by hot callers; avoids rereading the session store. */
142+sessionEntry?: Pick<SessionEntry, "sessionFile"> & Partial<Pick<SessionEntry, "sessionId">>;
143+};
144+145+export type SessionTranscriptReadTarget = Omit<
146+SessionTranscriptRuntimeTarget,
147+"agentId" | "sessionKey"
148+> & {
149+agentId?: string;
150+sessionKey?: string;
151+};
152+148153export type SessionTranscriptWriteScope = Omit<SessionTranscriptAccessScope, "sessionId"> & {
149154/** Optional for appenders that can operate on an existing explicit transcript target. */
150155sessionId?: string;
@@ -846,7 +851,7 @@ export async function persistSessionRolloverLifecycle(params: {
846851847852/** Reads parsed transcript records from an explicit or derived transcript target. */
848853export async function loadTranscriptEvents(
849-scope: SessionTranscriptReadScope,
854+scope: SessionTranscriptAccessScope,
850855): Promise<TranscriptEvent[]> {
851856const transcript = await resolveTranscriptReadAccess(scope);
852857const events: TranscriptEvent[] = [];
@@ -1479,6 +1484,82 @@ export async function resolveSessionTranscriptRuntimeReadTarget(
14791484};
14801485}
148114861487+/**
1488+ * Resolves the current file-backed target for read-only transcript callers.
1489+ * Unlike writer/runtime resolution, this does not persist missing sessionFile
1490+ * metadata; reader projections must not mutate session metadata.
1491+ */
1492+export function resolveSessionTranscriptReadTarget(
1493+scope: SessionTranscriptReadScope,
1494+): SessionTranscriptReadTarget {
1495+const explicitSessionFile = scope.sessionFile?.trim();
1496+if (explicitSessionFile) {
1497+return {
1498+sessionFile: explicitSessionFile,
1499+sessionId: scope.sessionId,
1500+ ...(scope.agentId ? { agentId: scope.agentId } : {}),
1501+ ...(scope.sessionKey ? { sessionKey: scope.sessionKey } : {}),
1502+};
1503+}
1504+const agentId = scope.agentId ?? resolveAgentIdFromSessionKey(scope.sessionKey);
1505+if (!agentId) {
1506+throw new Error(`Cannot resolve transcript scope without an agent id: ${scope.sessionKey}`);
1507+}
1508+const storePath = resolveConcreteReadStorePath(scope.storePath);
1509+const resolvedStoreEntry =
1510+scope.sessionEntry || !scope.sessionKey
1511+ ? undefined
1512+ : storePath
1513+ ? resolveSessionStoreEntry({
1514+store: loadSessionStore(storePath, { skipCache: true }),
1515+sessionKey: scope.sessionKey,
1516+})
1517+ : undefined;
1518+const sessionEntry =
1519+scope.sessionEntry ??
1520+resolvedStoreEntry?.existing ??
1521+(scope.sessionKey ? loadSessionEntry({ ...scope, sessionKey: scope.sessionKey }) : undefined);
1522+const sessionKey = resolvedStoreEntry?.normalizedKey ?? scope.sessionKey;
1523+const matchingSessionEntry =
1524+sessionEntry?.sessionId === undefined || sessionEntry.sessionId === scope.sessionId
1525+ ? sessionEntry
1526+ : undefined;
1527+const threadId =
1528+scope.threadId ?? (sessionKey ? parseSessionThreadInfo(sessionKey).threadId : undefined);
1529+const sessionFile = matchingSessionEntry?.sessionFile
1530+ ? resolveSessionFilePath(
1531+scope.sessionId,
1532+matchingSessionEntry,
1533+resolveSessionFilePathOptions({
1534+ agentId,
1535+ ...(storePath ? { storePath } : {}),
1536+}),
1537+)
1538+ : storePath
1539+ ? resolveSessionTranscriptPathInDir(
1540+// File-backed readers derive beside sessions.json only for the JSON-store
1541+// deprecation window; the SQLite flip resolves from canonical metadata.
1542+scope.sessionId,
1543+path.dirname(path.resolve(storePath)),
1544+threadId,
1545+)
1546+ : resolveSessionTranscriptPath(scope.sessionId, agentId, threadId);
1547+return {
1548+ agentId,
1549+ sessionFile,
1550+sessionId: scope.sessionId,
1551+ ...(sessionKey ? { sessionKey } : {}),
1552+};
1553+}
1554+1555+function resolveConcreteReadStorePath(storePath: string | undefined): string | undefined {
1556+const trimmed = storePath?.trim();
1557+if (!trimmed || trimmed === "(multiple)" || trimmed.includes("{agentId}")) {
1558+return undefined;
1559+}
1560+return trimmed;
1561+}
1562+14821563function createFallbackSessionEntry(patch: Partial<SessionEntry>): SessionEntry {
14831564const now = Date.now();
14841565return {
@@ -1555,7 +1636,7 @@ function resolveAccessStorePath(scope: SessionAccessScope): string {
15551636});
15561637}
155716381558-async function resolveTranscriptReadAccess(scope: SessionTranscriptReadScope): Promise<{
1639+async function resolveTranscriptReadAccess(scope: SessionTranscriptAccessScope): Promise<{
15591640sessionFile: string;
15601641}> {
15611642if (scope.sessionFile?.trim()) {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。