@@ -11,8 +11,13 @@ import { generateId } from '@sim/utils/id'
1111import { task } from '@trigger.dev/sdk'
1212import { Cron } from 'croner'
1313import { and , eq , isNull } from 'drizzle-orm'
14+ import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
1415import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types'
15- import { createTimeoutAbortController , getTimeoutErrorMessage } from '@/lib/core/execution-limits'
16+ import {
17+ createTimeoutAbortController ,
18+ getExecutionTimeout ,
19+ getTimeoutErrorMessage ,
20+ } from '@/lib/core/execution-limits'
1621import { preprocessExecution } from '@/lib/execution/preprocessing'
1722import { LoggingSession } from '@/lib/logs/execution/logging-session'
1823import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans'
@@ -1007,6 +1012,8 @@ export async function executeJobInline(payload: JobExecutionPayload) {
10071012 const promptText = buildJobPrompt ( jobRecord )
10081013
10091014 try {
1015+ const userSubscription = await getHighestPrioritySubscription ( jobRecord . sourceUserId )
1016+ const mothershipJobTimeoutMs = getExecutionTimeout ( userSubscription ?. plan , 'sync' )
10101017 const url = buildAPIUrl ( '/api/mothership/execute' )
10111018 const headers = await buildAuthHeaders ( jobRecord . sourceUserId )
10121019
@@ -1018,16 +1025,52 @@ export async function executeJobInline(payload: JobExecutionPayload) {
10181025 }
10191026
10201027 const startTime = new Date ( )
1021- const response = await fetch ( url . toString ( ) , {
1022- method : 'POST' ,
1023- headers,
1024- body : JSON . stringify ( body ) ,
1025- } )
1026- const endTime = new Date ( )
1027- const durationMs = endTime . getTime ( ) - startTime . getTime ( )
1028+ const timeoutController = createTimeoutAbortController ( mothershipJobTimeoutMs )
1029+ try {
1030+ const response = await fetch ( url . toString ( ) , {
1031+ method : 'POST' ,
1032+ headers,
1033+ body : JSON . stringify ( body ) ,
1034+ signal : timeoutController . signal ,
1035+ } )
1036+
1037+ if ( ! response . ok ) {
1038+ const errorText = await response . text ( ) . catch ( ( ) => {
1039+ if ( timeoutController . isTimedOut ( ) ) {
1040+ throw new Error ( getTimeoutErrorMessage ( null , timeoutController . timeoutMs ) )
1041+ }
1042+ return 'Unknown error'
1043+ } )
1044+ const endTime = new Date ( )
1045+ const durationMs = endTime . getTime ( ) - startTime . getTime ( )
10281046
1029- if ( ! response . ok ) {
1030- const errorText = await response . text ( ) . catch ( ( ) => 'Unknown error' )
1047+ await createJobLogEntry ( {
1048+ scheduleId : payload . scheduleId ,
1049+ workspaceId : jobRecord . sourceWorkspaceId ,
1050+ jobTitle : jobRecord . jobTitle ,
1051+ startTime,
1052+ endTime,
1053+ durationMs,
1054+ success : false ,
1055+ errorMessage : errorText ,
1056+ } )
1057+
1058+ throw new Error ( `Mothership execution failed (${ response . status } ): ${ errorText } ` )
1059+ }
1060+
1061+ let responseBody : Record < string , any > = { }
1062+ let wasCompletedByTool = false
1063+ try {
1064+ responseBody = await response . json ( )
1065+ const toolCalls = responseBody ?. toolCalls as Array < { name ?: string } > | undefined
1066+ wasCompletedByTool = toolCalls ?. some ( ( tc ) => tc . name === 'complete_job' ) ?? false
1067+ } catch {
1068+ if ( timeoutController . isTimedOut ( ) ) {
1069+ throw new Error ( getTimeoutErrorMessage ( null , timeoutController . timeoutMs ) )
1070+ }
1071+ }
1072+ const endTime = new Date ( )
1073+ const durationMs = endTime . getTime ( ) - startTime . getTime ( )
10311074
10321075 await createJobLogEntry ( {
10331076 scheduleId : payload . scheduleId ,
@@ -1036,92 +1079,71 @@ export async function executeJobInline(payload: JobExecutionPayload) {
10361079 startTime,
10371080 endTime,
10381081 durationMs,
1039- success : false ,
1040- errorMessage : errorText ,
1082+ success : true ,
1083+ responseBody ,
10411084 } )
10421085
1043- throw new Error ( `Mothership execution failed (${ response . status } ): ${ errorText } ` )
1044- }
1086+ const newRunCount = ( jobRecord . runCount || 0 ) + 1
10451087
1046- let responseBody : Record < string , any > = { }
1047- let wasCompletedByTool = false
1048- try {
1049- responseBody = await response . json ( )
1050- const toolCalls = responseBody ?. toolCalls as Array < { name ?: string } > | undefined
1051- wasCompletedByTool = toolCalls ?. some ( ( tc ) => tc . name === 'complete_job' ) ?? false
1052- } catch {
1053- // Response may not be JSON; proceed with normal flow
1054- }
1088+ logger . info ( `[${ requestId } ] Job executed successfully` , {
1089+ scheduleId : payload . scheduleId ,
1090+ runCount : newRunCount ,
1091+ wasCompletedByTool,
1092+ } )
10551093
1056- await createJobLogEntry ( {
1057- scheduleId : payload . scheduleId ,
1058- workspaceId : jobRecord . sourceWorkspaceId ,
1059- jobTitle : jobRecord . jobTitle ,
1060- startTime,
1061- endTime,
1062- durationMs,
1063- success : true ,
1064- responseBody,
1065- } )
1094+ if ( wasCompletedByTool ) {
1095+ await applyScheduleUpdate (
1096+ payload . scheduleId ,
1097+ {
1098+ lastRanAt : now ,
1099+ updatedAt : now ,
1100+ runCount : newRunCount ,
1101+ failedCount : 0 ,
1102+ lastQueuedAt : null ,
1103+ } ,
1104+ requestId ,
1105+ `Error updating job ${ payload . scheduleId } after completion`
1106+ )
1107+ return
1108+ }
10661109
1067- const newRunCount = ( jobRecord . runCount || 0 ) + 1
1110+ const isOneTime = ! jobRecord . cronExpression
1111+ let nextRunAt : Date | null = null
10681112
1069- logger . info ( `[${ requestId } ] Job executed successfully` , {
1070- scheduleId : payload . scheduleId ,
1071- runCount : newRunCount ,
1072- wasCompletedByTool,
1073- } )
1113+ if ( ! isOneTime && jobRecord . cronExpression ) {
1114+ const validation = validateCronExpression (
1115+ jobRecord . cronExpression ,
1116+ jobRecord . timezone || 'UTC'
1117+ )
1118+ nextRunAt = validation . nextRun || null
1119+ }
1120+
1121+ const maxRunsReached = jobRecord . maxRuns && newRunCount >= jobRecord . maxRuns
1122+ if ( maxRunsReached ) {
1123+ logger . info ( `[${ requestId } ] Job hit maxRuns limit` , {
1124+ scheduleId : payload . scheduleId ,
1125+ maxRuns : jobRecord . maxRuns ,
1126+ runCount : newRunCount ,
1127+ } )
1128+ }
10741129
1075- if ( wasCompletedByTool ) {
10761130 await applyScheduleUpdate (
10771131 payload . scheduleId ,
10781132 {
10791133 lastRanAt : now ,
10801134 updatedAt : now ,
1081- runCount : newRunCount ,
1135+ nextRunAt : isOneTime || maxRunsReached ? null : nextRunAt ,
10821136 failedCount : 0 ,
10831137 lastQueuedAt : null ,
1138+ runCount : newRunCount ,
1139+ status : isOneTime || maxRunsReached ? 'completed' : 'active' ,
10841140 } ,
10851141 requestId ,
1086- `Error updating job ${ payload . scheduleId } after completion`
1087- )
1088- return
1089- }
1090-
1091- const isOneTime = ! jobRecord . cronExpression
1092- let nextRunAt : Date | null = null
1093-
1094- if ( ! isOneTime && jobRecord . cronExpression ) {
1095- const validation = validateCronExpression (
1096- jobRecord . cronExpression ,
1097- jobRecord . timezone || 'UTC'
1142+ `Error updating job ${ payload . scheduleId } after success`
10981143 )
1099- nextRunAt = validation . nextRun || null
1100- }
1101-
1102- const maxRunsReached = jobRecord . maxRuns && newRunCount >= jobRecord . maxRuns
1103- if ( maxRunsReached ) {
1104- logger . info ( `[${ requestId } ] Job hit maxRuns limit` , {
1105- scheduleId : payload . scheduleId ,
1106- maxRuns : jobRecord . maxRuns ,
1107- runCount : newRunCount ,
1108- } )
1144+ } finally {
1145+ timeoutController . cleanup ( )
11091146 }
1110-
1111- await applyScheduleUpdate (
1112- payload . scheduleId ,
1113- {
1114- lastRanAt : now ,
1115- updatedAt : now ,
1116- nextRunAt : isOneTime || maxRunsReached ? null : nextRunAt ,
1117- failedCount : 0 ,
1118- lastQueuedAt : null ,
1119- runCount : newRunCount ,
1120- status : isOneTime || maxRunsReached ? 'completed' : 'active' ,
1121- } ,
1122- requestId ,
1123- `Error updating job ${ payload . scheduleId } after success`
1124- )
11251147 } catch ( error ) {
11261148 const errorMessage = toError ( error ) . message
11271149 logger . error ( `[${ requestId } ] Job execution failed` , {
0 commit comments