@@ -445,6 +445,8 @@ type SessionListRowContext = {
445445 modelCostConfigByModelRef : Map < string , ModelCostConfig | undefined > ;
446446} ;
447447
448+ type SessionListRowContextProvider = ( ) => SessionListRowContext ;
449+
448450type SingleRowChildSessionCandidateCacheEntry = {
449451 store : Record < string , SessionEntry > ;
450452 storeVersion : number ;
@@ -671,16 +673,33 @@ function buildSessionListRowContext(params: {
671673 now : number ;
672674} ) : SessionListRowContext {
673675 const subagentRuns = buildSubagentRunReadIndex ( params . now ) ;
674- return {
676+ return buildSessionListRowContextFromParts ( {
675677 subagentRuns,
676678 storeChildSessionsByKey : buildStoreChildSessionIndex ( params . store , params . now , subagentRuns ) ,
679+ } ) ;
680+ }
681+
682+ function buildSessionListRowContextFromParts ( params : {
683+ subagentRuns : ReturnType < typeof buildSubagentRunReadIndex > ;
684+ storeChildSessionsByKey : Map < string , string [ ] > ;
685+ } ) : SessionListRowContext {
686+ return {
687+ subagentRuns : params . subagentRuns ,
688+ storeChildSessionsByKey : params . storeChildSessionsByKey ,
677689 selectedModelByOverrideRef : new Map ( ) ,
678690 thinkingMetadataByModelRef : new Map ( ) ,
679691 displayModelIdentityByKey : new Map ( ) ,
680692 modelCostConfigByModelRef : new Map ( ) ,
681693 } ;
682694}
683695
696+ function buildSessionListRowMetadataContext ( params : { now : number } ) : SessionListRowContext {
697+ return buildSessionListRowContextFromParts ( {
698+ subagentRuns : buildSubagentRunReadIndex ( params . now ) ,
699+ storeChildSessionsByKey : new Map ( ) ,
700+ } ) ;
701+ }
702+
684703function buildSingleRowStoreChildSessionsByKey ( params : {
685704 store : Record < string , SessionEntry > ;
686705 storePath : string ;
@@ -2180,6 +2199,37 @@ function addSessionListSearchModelFields(
21802199 }
21812200}
21822201
2202+ function matchesSessionListSearch ( fields : Array < string | undefined > , search : string ) : boolean {
2203+ return fields . some (
2204+ ( field ) => typeof field === "string" && normalizeLowercaseStringOrEmpty ( field ) . includes ( search ) ,
2205+ ) ;
2206+ }
2207+
2208+ function appendStoredSessionModelSearchFields (
2209+ fields : Array < string | undefined > ,
2210+ entry ?: SessionEntry ,
2211+ ) {
2212+ const provider = normalizeOptionalString ( entry ?. modelProvider ) ;
2213+ const model = normalizeOptionalString ( entry ?. model ) ;
2214+ fields . push ( provider , model ) ;
2215+ if ( provider && model ) {
2216+ fields . push ( `${ provider } /${ model } ` ) ;
2217+ }
2218+ }
2219+
2220+ function shouldResolveDerivedSessionModelSearchFields ( search : string ) : boolean {
2221+ // Agent session-key searches are already covered by cheap key fields; do not
2222+ // hydrate model metadata for every non-matching row on hot TUI lookups.
2223+ return ! search . startsWith ( "agent:" ) ;
2224+ }
2225+
2226+ function resolveSessionListRowContext ( params : {
2227+ rowContext ?: SessionListRowContext ;
2228+ getRowContext ?: SessionListRowContextProvider ;
2229+ } ) : SessionListRowContext | undefined {
2230+ return params . rowContext ?? params . getRowContext ?.( ) ;
2231+ }
2232+
21832233function resolveSessionListSearchModelFields ( params : {
21842234 cfg : OpenClawConfig ;
21852235 key : string ;
@@ -2356,9 +2406,9 @@ function filterSessionEntries(params: {
23562406 opts : SessionsListParams ;
23572407 now : number ;
23582408 rowContext ?: SessionListRowContext ;
2409+ getRowContext ?: SessionListRowContextProvider ;
23592410} ) : SessionEntryPair [ ] {
23602411 const { cfg, store, opts, now } = params ;
2361- const rowContext = params . rowContext ;
23622412 const includeGlobal = opts . includeGlobal === true ;
23632413 const includeUnknown = opts . includeUnknown === true ;
23642414 const spawnedBy = typeof opts . spawnedBy === "string" ? opts . spawnedBy : "" ;
@@ -2403,8 +2453,9 @@ function filterSessionEntries(params: {
24032453 if ( key === "unknown" || key === "global" ) {
24042454 return false ;
24052455 }
2406- const latest = rowContext
2407- ? rowContext . subagentRuns . getDisplaySubagentRun ( key )
2456+ const filterRowContext = resolveSessionListRowContext ( params ) ;
2457+ const latest = filterRowContext
2458+ ? filterRowContext . subagentRuns . getDisplaySubagentRun ( key )
24082459 : getSessionDisplaySubagentRunByChildSessionKey ( key ) ;
24092460 if ( latest ) {
24102461 const latestControllerSessionKey =
@@ -2413,8 +2464,8 @@ function filterSessionEntries(params: {
24132464 return (
24142465 latestControllerSessionKey === spawnedBy &&
24152466 shouldKeepSubagentRunChildLink ( latest , {
2416- activeDescendants : rowContext
2417- ? rowContext . subagentRuns . countActiveDescendantRuns ( key )
2467+ activeDescendants : filterRowContext
2468+ ? filterRowContext . subagentRuns . countActiveDescendantRuns ( key )
24182469 : countActiveDescendantRuns ( key ) ,
24192470 now,
24202471 } )
@@ -2434,21 +2485,29 @@ function filterSessionEntries(params: {
24342485
24352486 if ( search ) {
24362487 entries = entries . filter ( ( [ key , entry ] ) => {
2437- const fields = [
2488+ const cheapFields = [
24382489 resolveSessionListSearchDisplayName ( key , entry ) ,
24392490 entry ?. label ,
24402491 entry ?. subject ,
24412492 entry ?. sessionId ,
24422493 key ,
2443- ...resolveSessionListSearchModelFields ( {
2494+ ] ;
2495+ appendStoredSessionModelSearchFields ( cheapFields , entry ) ;
2496+ if ( matchesSessionListSearch ( cheapFields , search ) ) {
2497+ return true ;
2498+ }
2499+ if ( ! shouldResolveDerivedSessionModelSearchFields ( search ) ) {
2500+ return false ;
2501+ }
2502+ const searchRowContext = resolveSessionListRowContext ( params ) ;
2503+ return matchesSessionListSearch (
2504+ resolveSessionListSearchModelFields ( {
24442505 cfg,
24452506 key,
24462507 entry,
2447- rowContext,
2508+ rowContext : searchRowContext ,
24482509 } ) ,
2449- ] ;
2450- return fields . some (
2451- ( f ) => typeof f === "string" && normalizeLowercaseStringOrEmpty ( f ) . includes ( search ) ,
2510+ search ,
24522511 ) ;
24532512 } ) ;
24542513 }
@@ -2467,6 +2526,7 @@ function selectSessionEntries(params: {
24672526 opts : SessionsListParams ;
24682527 now : number ;
24692528 rowContext ?: SessionListRowContext ;
2529+ getRowContext ?: SessionListRowContextProvider ;
24702530 defaultLimit ?: number ;
24712531} ) : SessionEntrySelection {
24722532 const filtered = filterSessionEntries ( params ) ;
@@ -2494,6 +2554,7 @@ export function filterAndSortSessionEntries(params: {
24942554 opts : SessionsListParams ;
24952555 now : number ;
24962556 rowContext ?: SessionListRowContext ;
2557+ getRowContext ?: SessionListRowContextProvider ;
24972558} ) : [ string , SessionEntry ] [ ] {
24982559 return selectSessionEntries ( params ) . entries ;
24992560}
@@ -2523,20 +2584,30 @@ export function listSessionsFromStore(params: {
25232584 store,
25242585 opts,
25252586 now,
2526- rowContext :
2587+ getRowContext :
25272588 hasSpawnedByFilter || Boolean ( normalizeOptionalString ( opts . search ) )
2528- ? getRowContext ( )
2589+ ? getRowContext
25292590 : undefined ,
25302591 defaultLimit : SESSIONS_LIST_DEFAULT_LIMIT ,
25312592 } ) ;
25322593 const { entries, totalCount, limitApplied, offset, nextOffset, hasMore } = selection ;
2594+ const fullRowContext =
2595+ rowContext || hasSpawnedByFilter || entries . length > SESSIONS_LIST_YIELD_BATCH_SIZE
2596+ ? getRowContext ( )
2597+ : undefined ;
2598+ const sharedRowContext =
2599+ fullRowContext ??
2600+ ( entries . length > 1 ? buildSessionListRowMetadataContext ( { now } ) : undefined ) ;
25332601
25342602 const sessions = entries . map ( ( [ key , entry ] , index ) => {
25352603 const includeTranscriptFields = index < sessionListTranscriptFieldRows ;
25362604 const rowAgentId =
25372605 key === "global" && typeof opts . agentId === "string"
25382606 ? normalizeAgentId ( opts . agentId )
25392607 : undefined ;
2608+ const storeChildSessionsByKey =
2609+ fullRowContext ?. storeChildSessionsByKey ??
2610+ buildSingleRowStoreChildSessionsByKey ( { store, storePath, key, now } ) ;
25402611 return buildGatewaySessionRow ( {
25412612 cfg,
25422613 storePath,
@@ -2549,8 +2620,8 @@ export function listSessionsFromStore(params: {
25492620 includeDerivedTitles : includeTranscriptFields && includeDerivedTitles ,
25502621 includeLastMessage : includeTranscriptFields && includeLastMessage ,
25512622 transcriptUsageMaxBytes : sessionListTranscriptUsageMaxBytes ,
2552- storeChildSessionsByKey : getRowContext ( ) . storeChildSessionsByKey ,
2553- rowContext : getRowContext ( ) ,
2623+ storeChildSessionsByKey,
2624+ rowContext : sharedRowContext ,
25542625 } ) ;
25552626 } ) ;
25562627
@@ -2603,13 +2674,20 @@ export async function listSessionsFromStoreAsync(params: {
26032674 store,
26042675 opts,
26052676 now,
2606- rowContext :
2677+ getRowContext :
26072678 hasSpawnedByFilter || Boolean ( normalizeOptionalString ( opts . search ) )
2608- ? getRowContext ( )
2679+ ? getRowContext
26092680 : undefined ,
26102681 defaultLimit : SESSIONS_LIST_DEFAULT_LIMIT ,
26112682 } ) ;
26122683 const { entries, totalCount, limitApplied, offset, nextOffset, hasMore } = selection ;
2684+ const fullRowContext =
2685+ rowContext || hasSpawnedByFilter || entries . length > SESSIONS_LIST_YIELD_BATCH_SIZE
2686+ ? getRowContext ( )
2687+ : undefined ;
2688+ const sharedRowContext =
2689+ fullRowContext ??
2690+ ( entries . length > 1 ? buildSessionListRowMetadataContext ( { now } ) : undefined ) ;
26132691
26142692 const sessions : GatewaySessionRow [ ] = [ ] ;
26152693 for ( let i = 0 ; i < entries . length ; i ++ ) {
@@ -2619,6 +2697,9 @@ export async function listSessionsFromStoreAsync(params: {
26192697 key === "global" && typeof opts . agentId === "string"
26202698 ? normalizeAgentId ( opts . agentId )
26212699 : undefined ;
2700+ const storeChildSessionsByKey =
2701+ fullRowContext ?. storeChildSessionsByKey ??
2702+ buildSingleRowStoreChildSessionsByKey ( { store, storePath, key, now } ) ;
26222703 const row = buildGatewaySessionRow ( {
26232704 cfg,
26242705 storePath,
@@ -2631,8 +2712,8 @@ export async function listSessionsFromStoreAsync(params: {
26312712 includeDerivedTitles : false ,
26322713 includeLastMessage : false ,
26332714 transcriptUsageMaxBytes : sessionListTranscriptUsageMaxBytes ,
2634- storeChildSessionsByKey : getRowContext ( ) . storeChildSessionsByKey ,
2635- rowContext : getRowContext ( ) ,
2715+ storeChildSessionsByKey,
2716+ rowContext : sharedRowContext ,
26362717 skipTranscriptUsageFallback : true ,
26372718 lightweightListRow : true ,
26382719 } ) ;
0 commit comments