@@ -6,9 +6,13 @@ import {
66 resolveSessionFilePathOptions ,
77 resolveStorePath ,
88} from "../../config/sessions.js" ;
9+ import type { SessionEntry } from "../../config/sessions/types.js" ;
910import type { OpenClawConfig } from "../../config/types.openclaw.js" ;
1011import { callGateway } from "../../gateway/call.js" ;
11- import { deriveSessionTitle } from "../../gateway/session-utils.js" ;
12+ import {
13+ deriveSessionTitle ,
14+ readSessionTitleFieldsFromTranscriptAsync ,
15+ } from "../../gateway/session-utils.js" ;
1216import { resolveAgentIdFromSessionKey } from "../../routing/session-key.js" ;
1317import { normalizeOptionalLowercaseString , readStringValue } from "../../shared/string-coerce.js" ;
1418import {
@@ -45,6 +49,8 @@ const SessionsListToolSchema = Type.Object({
4549
4650type GatewayCaller = typeof callGateway ;
4751
52+ const SESSIONS_LIST_TRANSCRIPT_FIELD_ROWS = 100 ;
53+
4854function readSessionRunStatus ( value : unknown ) : SessionRunStatus | undefined {
4955 return value === "running" ||
5056 value === "done" ||
@@ -109,6 +115,8 @@ export function createSessionsListTool(opts?: {
109115 const includeDerivedTitles = params . includeDerivedTitles === true ;
110116 const includeLastMessage = params . includeLastMessage === true ;
111117 const gatewayCall = opts ?. callGateway ?? callGateway ;
118+ const a2aPolicy = createAgentToAgentPolicy ( cfg ) ;
119+ const hydrateTranscriptFieldsAfterFiltering = includeDerivedTitles || includeLastMessage ;
112120
113121 const list = await gatewayCall < { sessions : Array < SessionListRow > ; path : string } > ( {
114122 method : "sessions.list" ,
@@ -118,8 +126,8 @@ export function createSessionsListTool(opts?: {
118126 label,
119127 agentId,
120128 search,
121- includeDerivedTitles,
122- includeLastMessage,
129+ includeDerivedTitles : false ,
130+ includeLastMessage : false ,
123131 includeGlobal : ! restrictToSpawned ,
124132 includeUnknown : ! restrictToSpawned ,
125133 spawnedBy : restrictToSpawned ? effectiveRequesterKey : undefined ,
@@ -128,7 +136,6 @@ export function createSessionsListTool(opts?: {
128136
129137 const sessions = Array . isArray ( list ?. sessions ) ? list . sessions : [ ] ;
130138 const storePath = typeof list ?. path === "string" ? list . path : undefined ;
131- const a2aPolicy = createAgentToAgentPolicy ( cfg ) ;
132139 const visibilityGuard = await createSessionVisibilityGuard ( {
133140 action : "list" ,
134141 requesterSessionKey : effectiveRequesterKey ,
@@ -137,6 +144,13 @@ export function createSessionsListTool(opts?: {
137144 } ) ;
138145 const rows : SessionListRow [ ] = [ ] ;
139146 const historyTargets : Array < { row : SessionListRow ; resolvedKey : string } > = [ ] ;
147+ const titleTargets : Array < {
148+ row : SessionListRow ;
149+ titleEntry : SessionEntry ;
150+ sessionId : string ;
151+ sessionFile ?: string ;
152+ agentId : string ;
153+ } > = [ ] ;
140154
141155 for ( const entry of sessions ) {
142156 if ( ! entry || typeof entry !== "object" ) {
@@ -310,17 +324,24 @@ export function createSessionsListTool(opts?: {
310324 lastAccountId,
311325 transcriptPath,
312326 } ;
313- if ( sessionId && includeDerivedTitles && ! row . derivedTitle ) {
314- row . derivedTitle = deriveSessionTitle (
315- {
327+ if (
328+ sessionId &&
329+ hydrateTranscriptFieldsAfterFiltering &&
330+ titleTargets . length < SESSIONS_LIST_TRANSCRIPT_FIELD_ROWS
331+ ) {
332+ titleTargets . push ( {
333+ row,
334+ titleEntry : {
316335 sessionId,
317336 displayName : row . displayName ,
318337 label : row . label ,
319338 subject : readStringValue ( ( entry as { subject ?: unknown } ) . subject ) ,
320339 updatedAt : typeof row . updatedAt === "number" ? row . updatedAt : 0 ,
321340 } ,
322- undefined ,
323- ) ;
341+ sessionId,
342+ ...( sessionFile ? { sessionFile } : { } ) ,
343+ agentId : resolvedAgentId ,
344+ } ) ;
324345 }
325346 if ( messageLimit > 0 ) {
326347 const resolvedKey = resolveInternalSessionKey ( {
@@ -333,6 +354,37 @@ export function createSessionsListTool(opts?: {
333354 rows . push ( row ) ;
334355 }
335356
357+ if ( titleTargets . length > 0 ) {
358+ const maxConcurrent = Math . min ( 4 , titleTargets . length ) ;
359+ let index = 0 ;
360+ const worker = async ( ) => {
361+ while ( true ) {
362+ const next = index ;
363+ index += 1 ;
364+ if ( next >= titleTargets . length ) {
365+ return ;
366+ }
367+ const target = titleTargets [ next ] ;
368+ const fields = await readSessionTitleFieldsFromTranscriptAsync (
369+ target . sessionId ,
370+ storePath ,
371+ target . sessionFile ,
372+ target . agentId ,
373+ ) ;
374+ if ( includeDerivedTitles && ! target . row . derivedTitle ) {
375+ target . row . derivedTitle = deriveSessionTitle (
376+ target . titleEntry ,
377+ fields . firstUserMessage ,
378+ ) ;
379+ }
380+ if ( includeLastMessage && fields . lastMessagePreview ) {
381+ target . row . lastMessagePreview = fields . lastMessagePreview ;
382+ }
383+ }
384+ } ;
385+ await Promise . all ( Array . from ( { length : maxConcurrent } , ( ) => worker ( ) ) ) ;
386+ }
387+
336388 if ( messageLimit > 0 && historyTargets . length > 0 ) {
337389 const maxConcurrent = Math . min ( 4 , historyTargets . length ) ;
338390 let index = 0 ;
0 commit comments