@@ -267,9 +267,13 @@ export const aiChat = chat
267267
268268 // --- Lifecycle hooks ---
269269
270- // #region onPreload — eagerly initialize before the user's first message
271- onPreload : async ( { chatId, runId, chatAccessToken, clientData } ) => {
272- if ( ! clientData ) return ;
270+ // #region onBoot — per-process setup that runs on EVERY fresh worker
271+ //
272+ // Fires for the initial run, preloaded runs, AND reactive continuation
273+ // runs (post-cancel/crash/endRun/upgrade). The single place to initialize
274+ // `chat.local` and per-process resources so they're ready in `run()`
275+ // regardless of how the run was triggered.
276+ onBoot : async ( { clientData } ) => {
273277 const user = await prisma . user . upsert ( {
274278 where : { id : clientData . userId } ,
275279 create : { id : clientData . userId , name : "User" } ,
@@ -289,13 +293,18 @@ export const aiChat = chat
289293 } ) ;
290294 chat . prompt . set ( resolved ) ;
291295 chat . skills . set ( [ await timeUtilsSkill . local ( ) ] ) ;
296+ } ,
297+ // #endregion
292298
299+ // #region onPreload — eagerly create chat/session DB rows before the first message
300+ onPreload : async ( { chatId, chatAccessToken, clientData } ) => {
301+ if ( ! clientData ) return ;
293302 await prisma . chat . upsert ( {
294303 where : { id : chatId } ,
295304 create : {
296305 id : chatId ,
297306 title : "New chat" ,
298- userId : user . id ,
307+ userId : clientData . userId ,
299308 model : clientData ?. model ?? DEFAULT_MODEL ,
300309 } ,
301310 update : { } ,
@@ -308,50 +317,24 @@ export const aiChat = chat
308317 } ,
309318 // #endregion
310319
311- // #region onChatStart — fallback init when not preloaded
312- onChatStart : async ( {
313- chatId,
314- runId,
315- chatAccessToken,
316- clientData,
317- continuation,
318- preloaded,
319- } ) => {
320+ // #region onChatStart — first-message chat/session DB rows when not preloaded
321+ //
322+ // Fires once per chat (on the very first message of the chat's lifetime).
323+ // Per-process state initialization lives in `onBoot`; this hook is only
324+ // for chat-scoped DB work that's a no-op on continuation runs.
325+ onChatStart : async ( { chatId, chatAccessToken, clientData, preloaded } ) => {
320326 if ( preloaded ) return ;
321327
322- const user = await prisma . user . upsert ( {
323- where : { id : clientData . userId } ,
324- create : { id : clientData . userId , name : "User" } ,
328+ await prisma . chat . upsert ( {
329+ where : { id : chatId } ,
330+ create : {
331+ id : chatId ,
332+ title : "New chat" ,
333+ userId : clientData . userId ,
334+ model : clientData . model ?? DEFAULT_MODEL ,
335+ } ,
325336 update : { } ,
326337 } ) ;
327- userContext . init ( {
328- userId : user . id ,
329- name : user . name ,
330- plan : user . plan as "free" | "pro" ,
331- preferredModel : user . preferredModel ,
332- messageCount : user . messageCount ,
333- } ) ;
334-
335- const resolved = await systemPrompt . resolve ( {
336- name : user . name ,
337- plan : user . plan as string ,
338- } ) ;
339- chat . prompt . set ( resolved ) ;
340- chat . skills . set ( [ await timeUtilsSkill . local ( ) ] ) ;
341-
342- if ( ! continuation ) {
343- await prisma . chat . upsert ( {
344- where : { id : chatId } ,
345- create : {
346- id : chatId ,
347- title : "New chat" ,
348- userId : user . id ,
349- model : clientData . model ?? DEFAULT_MODEL ,
350- } ,
351- update : { } ,
352- } ) ;
353- }
354-
355338 await prisma . chatSession . upsert ( {
356339 where : { id : chatId } ,
357340 create : { id : chatId , publicAccessToken : chatAccessToken } ,
0 commit comments