@@ -11,6 +11,21 @@ export type BuiltinToolDef = {
1111}
1212
1313export const BUILTIN_TOOL_SCHEMAS : BuiltinToolDef [ ] = [
14+ {
15+ type : 'function' ,
16+ function : {
17+ name : 'ct_initiate_export' ,
18+ description : '初始化聊天记录导出工作流。在通过 ct_list_sessions 确认目标会话后调用此工具,系统将展示一个交互式导出确认卡片供用户选择格式并执行导出。每次导出流程只需调用一次。' ,
19+ parameters : {
20+ type : 'object' ,
21+ properties : {
22+ sessionId : { type : 'string' , description : '目标会话的 sessionId(从 ct_list_sessions 获取)' } ,
23+ sessionName : { type : 'string' , description : '目标会话的显示名称' }
24+ } ,
25+ required : [ 'sessionId' , 'sessionName' ]
26+ }
27+ }
28+ } ,
1429 {
1530 type : 'function' ,
1631 function : {
@@ -19,7 +34,7 @@ export const BUILTIN_TOOL_SCHEMAS: BuiltinToolDef[] = [
1934 parameters : {
2035 type : 'object' ,
2136 properties : {
22- limit : { type : 'number' , description : '返回数量,默认 20,最多 100 ' }
37+ limit : { type : 'number' , description : '返回数量,默认 20,上限由用户在输入框设定 ' }
2338 }
2439 }
2540 }
@@ -32,7 +47,7 @@ export const BUILTIN_TOOL_SCHEMAS: BuiltinToolDef[] = [
3247 parameters : {
3348 type : 'object' ,
3449 properties : {
35- limit : { type : 'number' , description : '返回数量,默认 30,最多 200 ' }
50+ limit : { type : 'number' , description : '返回数量,默认 30,上限由用户在输入框设定 ' }
3651 }
3752 }
3853 }
@@ -47,7 +62,7 @@ export const BUILTIN_TOOL_SCHEMAS: BuiltinToolDef[] = [
4762 properties : {
4863 keyword : { type : 'string' , description : '搜索关键词' } ,
4964 sessionId : { type : 'string' , description : '(可选)限定搜索的会话 sessionId,不填则跨多个最近会话搜索' } ,
50- limit : { type : 'number' , description : '返回结果数,默认 20,最多 50 ' }
65+ limit : { type : 'number' , description : '返回结果数,默认 20,上限由用户在输入框设定 ' }
5166 } ,
5267 required : [ 'keyword' ]
5368 }
@@ -62,12 +77,30 @@ export const BUILTIN_TOOL_SCHEMAS: BuiltinToolDef[] = [
6277 type : 'object' ,
6378 properties : {
6479 sessionId : { type : 'string' , description : '会话的 sessionId(从 ct_list_sessions 获取)' } ,
65- limit : { type : 'number' , description : '返回消息数量,默认 15,最多 50 ' }
80+ limit : { type : 'number' , description : '返回消息数量,默认 15,上限由用户在输入框设定 ' }
6681 } ,
6782 required : [ 'sessionId' ]
6883 }
6984 }
7085 } ,
86+ {
87+ type : 'function' ,
88+ function : {
89+ name : 'ct_get_moments' ,
90+ description : '获取微信朋友圈时间线。可按时间范围、关键词、发布者过滤。返回动态列表,含作者、时间、内容文字、点赞数和评论摘要。' ,
91+ parameters : {
92+ type : 'object' ,
93+ properties : {
94+ limit : { type : 'number' , description : '返回条数,默认 20,上限由读取条数设定' } ,
95+ offset : { type : 'number' , description : '分页偏移量,默认 0' } ,
96+ keyword : { type : 'string' , description : '(可选)按内容关键词过滤' } ,
97+ usernames : { type : 'array' , items : { type : 'string' } , description : '(可选)按发布者微信 ID 过滤' } ,
98+ startTime : { type : 'number' , description : '(可选)起始时间戳(Unix 秒)' } ,
99+ endTime : { type : 'number' , description : '(可选)结束时间戳(Unix 秒)' }
100+ }
101+ }
102+ }
103+ } ,
71104 {
72105 type : 'function' ,
73106 function : {
@@ -81,7 +114,7 @@ export const BUILTIN_TOOL_SCHEMAS: BuiltinToolDef[] = [
81114 sender : { type : 'string' , description : '(可选)按发送人过滤,模糊匹配用户名,填 "我" 则只看自己发的消息' } ,
82115 startTime : { type : 'number' , description : '(可选)起始时间戳(Unix 秒),配合 endTime 限定时间范围,可查任意历史区间' } ,
83116 endTime : { type : 'number' , description : '(可选)结束时间戳(Unix 秒),不填则到最新消息' } ,
84- limit : { type : 'number' , description : '返回结果数,默认 20,最多 50 ' } ,
117+ limit : { type : 'number' , description : '返回结果数,默认 20,上限由用户在输入框设定 ' } ,
85118 caseInsensitive : { type : 'boolean' , description : '是否忽略大小写,默认 true' }
86119 } ,
87120 required : [ 'pattern' ]
@@ -136,11 +169,20 @@ async function searchOneSession(
136169 } ) )
137170}
138171
139- export async function executeBuiltinTool ( toolName : string , args : Record < string , unknown > ) : Promise < unknown > {
172+ export async function executeBuiltinTool ( toolName : string , args : Record < string , unknown > , context ?: { readLimit ?: number } ) : Promise < unknown > {
140173 const { chatService } = await import ( './chatService' )
174+ const userReadLimit = context ?. readLimit ?? 50
175+
176+ if ( toolName === 'ct_initiate_export' ) {
177+ const sessionId = String ( args . sessionId || '' )
178+ const sessionName = String ( args . sessionName || '' )
179+ if ( ! sessionId ) return { error : '缺少 sessionId 参数' }
180+ // 返回结构化标记,useAgentChat 的 onDone 检测到后注入 CardBlock
181+ return { __ct_card : 'export-wizard' , sessionId, sessionName, message : `已准备好「${ sessionName } 」的导出确认卡片。` }
182+ }
141183
142184 if ( toolName === 'ct_list_sessions' ) {
143- const limit = Math . min ( 100 , Math . max ( 1 , Number ( args . limit ) || 10 ) )
185+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 20 , userReadLimit ) ) )
144186 const result = await chatService . getSessions ( 0 , limit )
145187 if ( ! result . success ) return { error : result . error }
146188 return ( result . sessions || [ ] ) . map ( s => ( {
@@ -152,7 +194,7 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
152194 }
153195
154196 if ( toolName === 'ct_list_contacts' ) {
155- const limit = Math . min ( 200 , Math . max ( 1 , Number ( args . limit ) || 30 ) )
197+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 30 , userReadLimit ) ) )
156198 const result = await chatService . getContacts ( )
157199 if ( ! result . success ) return { error : result . error }
158200 return ( result . contacts || [ ] ) . slice ( 0 , limit ) . map ( c => ( {
@@ -166,7 +208,7 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
166208 if ( toolName === 'ct_search_messages' ) {
167209 const keyword = String ( args . keyword || '' )
168210 if ( ! keyword ) return { error : '缺少 keyword 参数' }
169- const limit = Math . min ( 50 , Math . max ( 1 , Number ( args . limit ) || 20 ) )
211+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 20 , userReadLimit ) ) )
170212 const { agentRetriever } = await import ( './ai-agent/qa/data/retriever' )
171213 const { chatSearchIndexService } = await import ( './search/chatSearchIndexService' )
172214 const sessionId = args . sessionId ? String ( args . sessionId ) : undefined
@@ -175,17 +217,43 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
175217 return searchOneSession ( agentRetriever , chatSearchIndexService , sessionId , undefined , keyword , limit )
176218 }
177219
178- // 跨会话搜索:取前 10 个最近会话,每个最多 3 条,按 score 全局排序后取 top limit
179- const sessionsResult = await chatService . getSessions ( 0 , 10 )
220+ // 全量跨会话搜索:取全部会话(上限 1000),分批并行,每批 20 个
221+ const sessionsResult = await chatService . getSessions ( 0 , 1000 )
180222 const sessions = sessionsResult . sessions || [ ]
181223 const allHits : SearchHitRow [ ] = [ ]
182- for ( const s of sessions ) {
183- const hits = await searchOneSession (
184- agentRetriever , chatSearchIndexService ,
185- s . username , s . displayName || s . username ,
186- keyword , 3
224+ const BATCH = 20
225+ const PER_SESSION = 3
226+ const kw = keyword . toLowerCase ( )
227+ for ( let i = 0 ; i < sessions . length ; i += BATCH ) {
228+ const batch = sessions . slice ( i , i + BATCH )
229+ const batchHits = await Promise . all (
230+ batch . map ( async s => {
231+ const state = chatSearchIndexService . getSessionVectorIndexState ( s . username )
232+ if ( state . indexedCount === 0 ) {
233+ // 未建 FTS 索引:降级为扫描最近 200 条消息做关键词匹配
234+ const r = await chatService . getMessages ( s . username , 0 , 200 )
235+ if ( ! r . success ) return [ ]
236+ return ( r . messages || [ ] )
237+ . filter ( ( m : any ) => ( m . parsedContent || '' ) . toLowerCase ( ) . includes ( kw ) )
238+ . slice ( 0 , PER_SESSION )
239+ . map ( ( m : any ) : SearchHitRow => ( {
240+ sessionId : s . username ,
241+ sessionName : s . displayName || s . username ,
242+ text : m . parsedContent || '' ,
243+ sender : m . isSend === 1 ? '我' : ( m . senderUsername || '对方' ) ,
244+ time : m . createTime ,
245+ score : 0.5 ,
246+ source : 'scan'
247+ } ) )
248+ }
249+ return searchOneSession (
250+ agentRetriever , chatSearchIndexService ,
251+ s . username , s . displayName || s . username ,
252+ keyword , PER_SESSION
253+ )
254+ } )
187255 )
188- allHits . push ( ...hits )
256+ allHits . push ( ...batchHits . flat ( ) )
189257 }
190258 allHits . sort ( ( a , b ) => b . score - a . score )
191259 return allHits . slice ( 0 , limit )
@@ -194,7 +262,7 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
194262 if ( toolName === 'ct_get_recent_messages' ) {
195263 const sessionId = String ( args . sessionId || '' )
196264 if ( ! sessionId ) return { error : '缺少 sessionId 参数' }
197- const limit = Math . min ( 50 , Math . max ( 1 , Number ( args . limit ) || 15 ) )
265+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 15 , userReadLimit ) ) )
198266 const result = await chatService . getMessages ( sessionId , 0 , limit )
199267 if ( ! result . success ) return { error : result . error }
200268 return ( result . messages || [ ] ) . map ( m => {
@@ -207,6 +275,32 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
207275 } )
208276 }
209277
278+ if ( toolName === 'ct_get_moments' ) {
279+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 20 , userReadLimit ) ) )
280+ const offset = Math . max ( 0 , Number ( args . offset ) || 0 )
281+ const keyword = args . keyword ? String ( args . keyword ) : undefined
282+ const usernames = Array . isArray ( args . usernames ) ? ( args . usernames as unknown [ ] ) . map ( String ) : undefined
283+ const startTime = args . startTime ? Number ( args . startTime ) : undefined
284+ const endTime = args . endTime ? Number ( args . endTime ) : undefined
285+
286+ const { snsService } = await import ( './snsService' )
287+ const result = await snsService . getTimeline ( limit , offset , usernames , keyword , startTime , endTime )
288+ if ( ! result . success ) return { error : result . error || '获取朋友圈失败,请确认朋友圈数据库已加载' }
289+
290+ return ( result . timeline || [ ] ) . map ( post => ( {
291+ id : post . id ,
292+ author : post . nickname || post . username ,
293+ time : post . createTime ,
294+ text : ( post . contentDesc || '' ) . slice ( 0 , 500 ) ,
295+ mediaCount : post . media ?. length || 0 ,
296+ likeCount : post . likes ?. length || 0 ,
297+ comments : ( post . comments || [ ] ) . slice ( 0 , 5 ) . map ( c => ( {
298+ author : c . nickname ,
299+ text : c . content ,
300+ } ) ) ,
301+ } ) )
302+ }
303+
210304 if ( toolName === 'ct_grep_messages' ) {
211305 const pattern = String ( args . pattern || '' )
212306 if ( ! pattern ) return { error : '缺少 pattern 参数' }
@@ -218,7 +312,7 @@ export async function executeBuiltinTool(toolName: string, args: Record<string,
218312 return { error : `无效的正则表达式: ${ e instanceof Error ? e . message : String ( e ) } ` }
219313 }
220314
221- const limit = Math . min ( 50 , Math . max ( 1 , Number ( args . limit ) || 20 ) )
315+ const limit = Math . min ( userReadLimit , Math . max ( 1 , Number ( args . limit ) || Math . min ( 20 , userReadLimit ) ) )
222316 const sessionId = args . sessionId ? String ( args . sessionId ) : undefined
223317 const senderFilter = args . sender ? String ( args . sender ) . toLowerCase ( ) : undefined
224318 const startTime = args . startTime ? Number ( args . startTime ) : undefined
0 commit comments