1- import { readFile , readdir , stat } from 'fs/promises' ;
2- import { join } from 'path' ;
3- import { THREADS_DIR , isTextContent , type ThreadFile } from './threadTypes.js' ;
1+ import { isTextContent } from './threadTypes.js' ;
42import {
53 recordPrompt ,
64 searchPromptHistory ,
75 getPromptHistoryCount ,
86 type PromptHistoryRow ,
97} from './database.js' ;
8+ import { listAllThreads , readThreadFile } from './threadProvider.js' ;
109
1110let backfillPromise : Promise < void > | null = null ;
1211
@@ -20,38 +19,27 @@ async function backfillPromptHistory(): Promise<void> {
2019 // Skip if already backfilled (data persists across restarts)
2120 if ( getPromptHistoryCount ( ) > 0 ) return ;
2221
23- const files = await readdir ( THREADS_DIR ) ;
24- const threadFiles = files . filter ( ( f ) => f . startsWith ( 'T-' ) && f . endsWith ( '.json' ) ) ;
22+ const threads = await listAllThreads ( ) ;
2523
26- // Stat all files and sort oldest-first so duplicate prompts keep the most recent timestamp
27- const fileStats = (
28- await Promise . all (
29- threadFiles . map ( async ( file ) => {
30- try {
31- const fileStat = await stat ( join ( THREADS_DIR , file ) ) ;
32- return { file, mtimeMs : fileStat . mtimeMs } ;
33- } catch {
34- return null ;
35- }
36- } ) ,
37- )
38- )
39- . filter ( ( s ) : s is { file : string ; mtimeMs : number } => s !== null )
40- . sort ( ( a , b ) => a . mtimeMs - b . mtimeMs ) ;
24+ // Sort oldest-first so duplicate prompts keep the most recent timestamp
25+ const sorted = [ ...threads ] . sort ( ( a , b ) => {
26+ const aDate = a . lastUpdatedDate ? new Date ( a . lastUpdatedDate ) . getTime ( ) : 0 ;
27+ const bDate = b . lastUpdatedDate ? new Date ( b . lastUpdatedDate ) . getTime ( ) : 0 ;
28+ return aDate - bDate ;
29+ } ) ;
4130
4231 // Process in parallel batches to avoid overwhelming the filesystem
4332 const BATCH_SIZE = 20 ;
44- for ( let i = 0 ; i < fileStats . length ; i += BATCH_SIZE ) {
45- const batch = fileStats . slice ( i , i + BATCH_SIZE ) ;
33+ for ( let i = 0 ; i < sorted . length ; i += BATCH_SIZE ) {
34+ const batch = sorted . slice ( i , i + BATCH_SIZE ) ;
4635 await Promise . all (
47- batch . map ( async ( { file , mtimeMs } ) => {
36+ batch . map ( async ( thread ) => {
4837 try {
49- const filePath = join ( THREADS_DIR , file ) ;
50- const fileMtime = Math . floor ( mtimeMs / 1000 ) ;
51- const content = await readFile ( filePath , 'utf-8' ) ;
52- const data = JSON . parse ( content ) as ThreadFile ;
53- const threadId = file . replace ( '.json' , '' ) ;
38+ const data = await readThreadFile ( thread . id ) ;
5439 const messages = data . messages || [ ] ;
40+ const fileMtime = thread . lastUpdatedDate
41+ ? Math . floor ( new Date ( thread . lastUpdatedDate ) . getTime ( ) / 1000 )
42+ : Math . floor ( Date . now ( ) / 1000 ) ;
5543
5644 for ( const msg of messages ) {
5745 if ( msg . role !== 'user' ) continue ;
@@ -65,19 +53,19 @@ async function backfillPromptHistory(): Promise<void> {
6553 }
6654
6755 if ( text . trim ( ) ) {
68- // Use message sentAt if available, otherwise fall back to file mtime
56+ // Use message sentAt if available, otherwise fall back to thread lastUpdated
6957 const createdAt = msg . meta ?. sentAt ? Math . floor ( msg . meta . sentAt / 1000 ) : fileMtime ;
70- recordPrompt ( text , threadId , createdAt ) ;
58+ recordPrompt ( text , thread . id , createdAt ) ;
7159 }
7260 }
7361 } catch ( err ) {
74- console . warn ( `[prompt-history] Failed to parse ${ file } :` , err ) ;
62+ console . warn ( `[prompt-history] Failed to parse ${ thread . id } :` , err ) ;
7563 }
7664 } ) ,
7765 ) ;
7866 }
7967
80- console . warn ( `📋 Prompt history backfill complete (scanned ${ threadFiles . length } threads)` ) ;
68+ console . warn ( `📋 Prompt history backfill complete (scanned ${ sorted . length } threads)` ) ;
8169 } catch ( err ) {
8270 console . error ( '[prompt-history] Backfill failed:' , err ) ;
8371 }
@@ -132,25 +120,15 @@ export function addPromptToHistory(text: string, threadId: string): void {
132120 */
133121export async function getRecentThreadIds ( limit = 100 ) : Promise < string [ ] > {
134122 try {
135- const files = await readdir ( THREADS_DIR ) ;
136- const threadFiles = files . filter ( ( f ) => f . startsWith ( 'T-' ) && f . endsWith ( '.json' ) ) ;
137-
138- const stats = await Promise . all (
139- threadFiles . map ( async ( file ) => {
140- try {
141- const fileStat = await stat ( join ( THREADS_DIR , file ) ) ;
142- return { file, mtime : fileStat . mtime . getTime ( ) } ;
143- } catch {
144- return null ;
145- }
146- } ) ,
147- ) ;
148-
149- return stats
150- . filter ( ( s ) : s is { file : string ; mtime : number } => s !== null )
151- . sort ( ( a , b ) => b . mtime - a . mtime )
123+ const threads = await listAllThreads ( ) ;
124+ return threads
125+ . sort ( ( a , b ) => {
126+ const aDate = a . lastUpdatedDate ? new Date ( a . lastUpdatedDate ) . getTime ( ) : 0 ;
127+ const bDate = b . lastUpdatedDate ? new Date ( b . lastUpdatedDate ) . getTime ( ) : 0 ;
128+ return bDate - aDate ;
129+ } )
152130 . slice ( 0 , limit )
153- . map ( ( s ) => s . file . replace ( '.json' , '' ) ) ;
131+ . map ( ( t ) => t . id ) ;
154132 } catch {
155133 return [ ] ;
156134 }
0 commit comments