@@ -1003,6 +1003,60 @@ async def _execute_connect_users(
10031003 # Note: Vector search is disabled until API endpoint is implemented
10041004 # For now, fall back to LLM-based execution
10051005 return await self ._execute_with_llm (skill , text , params , user_id )
1006+
1007+ async def _save_content_factory_pending_intent (
1008+ self ,
1009+ api_client : Any ,
1010+ user_id : str ,
1011+ params : dict ,
1012+ text : str ,
1013+ channel_id : Optional [str ],
1014+ thread_ts : Optional [str ],
1015+ ) -> None :
1016+ """Persist the original content request so GitHub auth/repo selection can resume it."""
1017+ if params .get ("confirmed" ):
1018+ return
1019+
1020+ intent_data = json .dumps ({
1021+ "skill" : "content-factory" ,
1022+ "params" : params ,
1023+ "text" : text ,
1024+ "channel" : channel_id ,
1025+ "ts" : thread_ts ,
1026+ })
1027+ await api_client .save_pending_intent (user_id , intent_data )
1028+
1029+ def _get_connected_domain_info (
1030+ self ,
1031+ connected_domains : List [dict ],
1032+ domain : Optional [str ],
1033+ ) -> Optional [dict ]:
1034+ """Return the connected domain record for the requested domain."""
1035+ if not domain or not connected_domains :
1036+ return None
1037+
1038+ return next (
1039+ (domain_info for domain_info in connected_domains if domain_info .get ("domain" ) == domain ),
1040+ None ,
1041+ )
1042+
1043+ def _resolve_content_factory_repo_name (
1044+ self ,
1045+ integration : dict ,
1046+ connected_domains : List [dict ],
1047+ domain : Optional [str ],
1048+ ) -> tuple [Optional [str ], Optional [dict ]]:
1049+ """Resolve the repo Roo should use for content-factory flows."""
1050+ domain_info = self ._get_connected_domain_info (connected_domains , domain )
1051+
1052+ if domain :
1053+ return (
1054+ integration .get ("domain_github_repo" )
1055+ or (domain_info or {}).get ("github_repo" ),
1056+ domain_info ,
1057+ )
1058+
1059+ return integration .get ("github_repo" ), domain_info
10061060
10071061 async def _execute_content_factory (
10081062 self ,
@@ -1182,15 +1236,14 @@ async def _execute_content_factory(
11821236
11831237 # Save pending intent before asking for auth
11841238 # Critically: Do NOT overwrite the intent if we are just confirming requirements (which has no domain/topic)
1185- if not params .get ("confirmed" ):
1186- intent_data = json .dumps ({
1187- "skill" : "content-factory" ,
1188- "params" : params ,
1189- "text" : text ,
1190- "channel" : channel_id ,
1191- "ts" : thread_ts
1192- })
1193- await api_client .save_pending_intent (user_id , intent_data )
1239+ await self ._save_content_factory_pending_intent (
1240+ api_client ,
1241+ user_id ,
1242+ params ,
1243+ text ,
1244+ channel_id ,
1245+ thread_ts ,
1246+ )
11941247
11951248 if channel_id :
11961249 post_message (channel_id , "Please connect GitHub" , thread_ts = thread_ts , blocks = blocks )
@@ -1232,19 +1285,60 @@ async def _execute_content_factory(
12321285 integration = domain_integration
12331286 connected_domains = integration .get ("connected_domains" , connected_domains )
12341287
1235- # Look up repo from connected_domains
1236- repo_name = None
1237- domain_info = None
1238- if domain and connected_domains :
1239- domain_info = next (
1240- (d for d in connected_domains if d .get ("domain" ) == domain ), None
1288+ repo_name , domain_info = self ._resolve_content_factory_repo_name (
1289+ integration ,
1290+ connected_domains ,
1291+ domain ,
1292+ )
1293+
1294+ if domain and integration .get ("needs_github_auth" ):
1295+ auth_url = integration .get ("oauth_url" )
1296+ if not auth_url :
1297+ auth_response = await api_client .get_github_auth_url (user_id , domain = domain )
1298+ auth_url = auth_response .get ("auth_url" )
1299+
1300+ if not auth_url :
1301+ return "Sorry mate, I couldn't get the authorization URL. Try again?"
1302+
1303+ await self ._save_content_factory_pending_intent (
1304+ api_client ,
1305+ user_id ,
1306+ params ,
1307+ text ,
1308+ channel_id ,
1309+ thread_ts ,
12411310 )
1242- if domain_info :
1243- repo_name = domain_info .get ("github_repo" )
12441311
1245- # Fallback to top-level github_repo if domain not in connected_domains
1246- if not repo_name :
1247- repo_name = integration .get ("github_repo" )
1312+ blocks = [
1313+ {
1314+ "type" : "section" ,
1315+ "text" : {
1316+ "type" : "mrkdwn" ,
1317+ "text" : f"⚠️ GitHub isn't connected for *{ domain } *.\n Click below to connect your GitHub repo for this domain."
1318+ }
1319+ },
1320+ {
1321+ "type" : "actions" ,
1322+ "elements" : [
1323+ {
1324+ "type" : "button" ,
1325+ "text" : {
1326+ "type" : "plain_text" ,
1327+ "text" : "Connect GitHub for " + domain ,
1328+ "emoji" : True
1329+ },
1330+ "url" : auth_url ,
1331+ "action_id" : "connect_github" ,
1332+ "style" : "primary"
1333+ }
1334+ ]
1335+ }
1336+ ]
1337+
1338+ if channel_id :
1339+ post_message (channel_id , f"GitHub not connected for { domain } " , thread_ts = thread_ts , blocks = blocks )
1340+ return f"GitHub isn't connected for { domain } . Click the button above to connect, then try again! 🔌"
1341+ return f"GitHub isn't connected for { domain } . Connect here: { auth_url } "
12481342
12491343 # No repo at all — prompt to connect
12501344 if not repo_name :
@@ -1254,12 +1348,26 @@ async def _execute_content_factory(
12541348 if not auth_url :
12551349 return "Sorry mate, I couldn't get the authorization URL. Try again?"
12561350
1351+ await self ._save_content_factory_pending_intent (
1352+ api_client ,
1353+ user_id ,
1354+ params ,
1355+ text ,
1356+ channel_id ,
1357+ thread_ts ,
1358+ )
1359+
12571360 blocks = [
12581361 {
12591362 "type" : "section" ,
12601363 "text" : {
12611364 "type" : "mrkdwn" ,
1262- "text" : "I see you're connected, but no repository is selected. Click below to choose one."
1365+ "text" : (
1366+ f"I see you're connected for *{ domain } *, but no repository is selected for that domain. "
1367+ "Click below to choose one."
1368+ if domain
1369+ else "I see you're connected, but no repository is selected. Click below to choose one."
1370+ )
12631371 }
12641372 },
12651373 {
@@ -2146,7 +2254,7 @@ def _resolve_points_action(self, params: dict, text: str) -> str:
21462254 text_lower = text .lower ()
21472255 explicit_points_request = "request" in text_lower and "point" in text_lower and "reward" not in text_lower
21482256
2149- if explicit_points_request and action in ( "" , "task" , "award" , "award_points" ) :
2257+ if explicit_points_request :
21502258 return "request_points"
21512259
21522260 if action == "book" :
@@ -2201,6 +2309,7 @@ def _extract_points_request_reason(self, text: str, fallback_reason: str = "") -
22012309
22022310 patterns = (
22032311 r"request\s+\d+\s*(?:points?|pts?)\s+for\s+(.+)" ,
2312+ r"(?:i\s*(?:am|'m)\s+)?requesting\s+\d+\s*(?:points?|pts?)\s+for\s+(.+)" ,
22042313 r"(?:can i|get me|i(?:'d| would)? like)\s+\d+\s*(?:points?|pts?)\s+for\s+(.+)" ,
22052314 )
22062315 for pattern in patterns :
0 commit comments