@@ -215,10 +215,19 @@ const layer = Layer.effect(
215215
216216 const ag = yield * agents . get ( "title" )
217217 if ( ! ag ) return
218- const mdl = ag . model
219- ? yield * provider . getModel ( ag . model . providerID , ag . model . modelID )
220- : ( ( yield * provider . getSmallModel ( input . providerID ) ) ??
221- ( yield * provider . getModel ( input . providerID , input . modelID ) ) )
218+ const mdlOpt = yield * Effect . fnUntraced ( function * ( ) {
219+ if ( ag . model ) return yield * provider . getModel ( ag . model . providerID , ag . model . modelID )
220+ return ( yield * provider . getSmallModel ( input . providerID ) ) ??
221+ ( yield * provider . getModel ( input . providerID , input . modelID ) )
222+ } ) ( ) . pipe (
223+ Effect . tapError ( ( err ) => Effect . logWarning ( "title model resolution failed" , { error : err } ) ) ,
224+ Effect . option ,
225+ )
226+ if ( Option . isNone ( mdlOpt ) ) {
227+ yield * Effect . logWarning ( "title skipped: no model available" )
228+ return
229+ }
230+ const mdl = mdlOpt . value
222231 const msgs = onlySubtasks
223232 ? [ { role : "user" as const , content : subtasks . map ( ( p ) => p . prompt ) . join ( "\n" ) } ]
224233 : yield * MessageV2 . toModelMessagesEffect ( context , mdl )
@@ -227,7 +236,6 @@ const layer = Layer.effect(
227236 agent : ag ,
228237 user : firstInfo ,
229238 system : [ ] ,
230- small : true ,
231239 tools : { } ,
232240 model : mdl ,
233241 sessionID : input . session . id ,
@@ -238,18 +246,61 @@ const layer = Layer.effect(
238246 Stream . filter ( LLMEvent . is . textDelta ) ,
239247 Stream . map ( ( e ) => e . text ) ,
240248 Stream . mkString ,
241- Effect . orDie ,
249+ Effect . catchAll ( ( err ) =>
250+ Effect . logWarning ( "title generation LLM call failed" , { error : err } ) . pipe ( Effect . as ( "" ) )
251+ ) ,
242252 )
243- const cleaned = text
244- . replace ( / < t h i n k > [ \s \S ] * ?< \/ t h i n k > \s * / g, "" )
245- . split ( "\n" )
246- . map ( ( line ) => line . trim ( ) )
247- . find ( ( line ) => line . length > 0 )
248- if ( ! cleaned ) return
249- const t = cleaned . length > 100 ? cleaned . substring ( 0 , 97 ) + "..." : cleaned
250- yield * sessions
251- . setTitle ( { sessionID : input . session . id , title : t } )
252- . pipe ( Effect . catchCause ( ( cause ) => Effect . logError ( "failed to generate title" , { error : Cause . squash ( cause ) } ) ) )
253+ const extractTitle = ( raw : string ) => {
254+ const t = raw
255+ . replace ( / < t h i n k > [ \s \S ] * ?< \/ t h i n k > \s * / g, "" )
256+ . split ( "\n" )
257+ . map ( ( line ) => line . trim ( ) )
258+ . find ( ( line ) => line . length > 0 )
259+ if ( ! t ) return
260+ return t . length > 100 ? t . substring ( 0 , 97 ) + "..." : t
261+ }
262+ const setTitle = ( title : string ) =>
263+ sessions
264+ . setTitle ( { sessionID : input . session . id , title } )
265+ . pipe ( Effect . catchCause ( ( cause ) => Effect . logError ( "failed to persist title" , { error : Cause . squash ( cause ) } ) ) )
266+ const first = extractTitle ( text )
267+ if ( first ) {
268+ yield * setTitle ( first )
269+ return
270+ }
271+ yield * Effect . logInfo ( "title generation deferred, will retry after delay" )
272+ yield * Effect . sleep ( "10 seconds" )
273+ const fresh = yield * sessions . get ( input . session . id ) . pipe ( Effect . option )
274+ if ( Option . isNone ( fresh ) || ! Session . isDefaultTitle ( fresh . value . title ) ) return
275+ const retryMsgs = yield * sessions . messages ( { sessionID : input . session . id } ) . pipe (
276+ Effect . catchAll ( ( err ) =>
277+ Effect . logWarning ( "title retry: failed to fetch messages" , { error : err } ) . pipe ( Effect . as ( [ ] as SessionV1 . WithParts [ ] ) )
278+ ) ,
279+ )
280+ if ( retryMsgs . length === 0 ) return
281+ const retryModelMsgs = yield * MessageV2 . toModelMessagesEffect ( retryMsgs , mdl )
282+ const retryText = yield * llm
283+ . stream ( {
284+ agent : ag ,
285+ user : firstInfo ,
286+ system : [ ] ,
287+ tools : { } ,
288+ model : mdl ,
289+ sessionID : input . session . id ,
290+ retries : 2 ,
291+ messages : [ { role : "user" , content : "Generate a title for this conversation:\n" } , ...retryModelMsgs ] ,
292+ } )
293+ . pipe (
294+ Stream . filter ( LLMEvent . is . textDelta ) ,
295+ Stream . map ( ( e ) => e . text ) ,
296+ Stream . mkString ,
297+ Effect . catchAll ( ( err ) =>
298+ Effect . logWarning ( "title generation retry LLM call failed" , { error : err } ) . pipe ( Effect . as ( "" ) )
299+ ) ,
300+ )
301+ const second = extractTitle ( retryText )
302+ if ( ! second ) return
303+ yield * setTitle ( second )
253304 } )
254305
255306 const handleSubtask = Effect . fn ( "SessionPrompt.handleSubtask" ) ( function * ( input : {
@@ -1136,7 +1187,14 @@ const layer = Layer.effect(
11361187 modelID : lastUser . model . modelID ,
11371188 providerID : lastUser . model . providerID ,
11381189 history : msgs ,
1139- } ) . pipe ( Effect . ignore , Effect . forkIn ( scope ) )
1190+ } ) . pipe (
1191+ Effect . catchCause ( ( cause ) =>
1192+ Effect . logWarning ( "title generation background task failed" , {
1193+ error : Cause . squash ( cause ) ,
1194+ } ) ,
1195+ ) ,
1196+ Effect . forkIn ( scope ) ,
1197+ )
11401198
11411199 const model = yield * getModel ( lastUser . model . providerID , lastUser . model . modelID , sessionID )
11421200 const task = tasks . pop ( )
0 commit comments