Skip to content

Commit 971a11a

Browse files
authored
Merge pull request #110 from MLAI-AUS-Inc/codex/domain-scan-routing-fix
bugfix
2 parents ae8b39a + 77acb93 commit 971a11a

File tree

8 files changed

+796
-23
lines changed

8 files changed

+796
-23
lines changed

roo-standalone/roo/agent.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ async def handle_mention(
5252
user_id: str,
5353
channel_id: Optional[str] = None,
5454
thread_ts: Optional[str] = None,
55+
param_overrides: Optional[Dict[str, Any]] = None,
5556
**kwargs
5657
) -> Dict[str, Any]:
5758
"""
@@ -104,6 +105,7 @@ async def handle_mention(
104105
channel_id=channel_id,
105106
thread_ts=thread_ts,
106107
thread_history=thread_history,
108+
param_overrides=param_overrides,
107109
**kwargs
108110
)
109111
return {

roo-standalone/roo/clients/mlai_backend.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,61 @@ async def get_admin_allowance(self, slack_user_id: str) -> dict:
866866
print(f"❌ Failed to fetch admin allowance: {e}")
867867
return {'error': str(e)}
868868

869+
async def promote_points_admin(
870+
self,
871+
requester_slack_id: str,
872+
target_slack_id: str,
873+
) -> dict:
874+
"""Promote a user to Points Admin using the privileged admin API."""
875+
cleaned_target = self._clean_slack_id(target_slack_id)
876+
payload = {
877+
"requester_slack_id": requester_slack_id,
878+
"target_slack_id": cleaned_target,
879+
}
880+
881+
async with httpx.AsyncClient() as client:
882+
response = await client.post(
883+
f"{self._points_base}/admins/",
884+
json=payload,
885+
headers=self.admin_headers,
886+
timeout=10.0,
887+
)
888+
889+
if response.status_code == 409:
890+
return response.json()
891+
892+
response.raise_for_status()
893+
result = response.json()
894+
self._admin_cache[cleaned_target] = True
895+
return result
896+
897+
async def set_points_admin_weekly_allowance(
898+
self,
899+
requester_slack_id: str,
900+
target_slack_id: str,
901+
weekly_allowance: int,
902+
) -> dict:
903+
"""Update a specific Points Admin's weekly allowance."""
904+
cleaned_target = self._clean_slack_id(target_slack_id)
905+
payload = {
906+
"requester_slack_id": requester_slack_id,
907+
"weekly_allowance": weekly_allowance,
908+
}
909+
910+
async with httpx.AsyncClient() as client:
911+
response = await client.patch(
912+
f"{self._points_base}/admins/{cleaned_target}/",
913+
json=payload,
914+
headers=self.admin_headers,
915+
timeout=10.0,
916+
)
917+
918+
if response.status_code == 404:
919+
return response.json()
920+
921+
response.raise_for_status()
922+
return response.json()
923+
869924
async def create_task(
870925
self,
871926
admin_slack_id: str,

roo-standalone/roo/main.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,24 +1436,39 @@ async def slack_actions(request: Request):
14361436

14371437
if action_id == "confirm_content_factory":
14381438
print(f"✅ User {user_id} confirmed content factory requirements")
1439-
1440-
# Trigger re-entry into the skill, but this time imply confirmation
1439+
1440+
value = actions[0].get("value", "")
1441+
try:
1442+
value_data = json.loads(value) if value else {}
1443+
except json.JSONDecodeError:
1444+
value_data = {}
1445+
1446+
original_text = value_data.get("text")
1447+
original_channel_id = value_data.get("channel_id") or channel_id
1448+
original_thread_ts = value_data.get("thread_ts") or thread_ts
1449+
param_overrides = value_data.get("params") or {}
1450+
if not isinstance(param_overrides, dict):
1451+
param_overrides = {}
1452+
1453+
if not original_text:
1454+
if channel_id:
1455+
post_message(
1456+
channel_id,
1457+
"I couldn't recover your original content request. Please resend the article request so I can continue.",
1458+
thread_ts=thread_ts,
1459+
)
1460+
return JSONResponse(status_code=200, content={})
1461+
14411462
import asyncio
14421463
agent = get_agent()
1443-
1444-
# We act as if the user said "Proceed with content factory confirmed=true"
1445-
# The LLM extraction in executor.py will pick up 'confirmed': True (or we hope so)
1446-
# Actually, to be safer, we should rely on the saved pending intent or just
1447-
# send a text that explicitly sets the param if possible, OR
1448-
# better yet, since we use LLM extraction, adding "confirmed=true" to text might work
1449-
# providing the LLM understands it.
1450-
# Let's try sending a clear directive.
1451-
1464+
resumed_params = {**param_overrides, "confirmed": True}
1465+
14521466
asyncio.create_task(agent.handle_mention(
1453-
text="Proceed with content factory. confirmed=True",
1467+
text=original_text,
14541468
user_id=user_id,
1455-
channel_id=channel_id,
1456-
thread_ts=thread_ts
1469+
channel_id=original_channel_id,
1470+
thread_ts=original_thread_ts,
1471+
param_overrides=resumed_params,
14571472
))
14581473
return JSONResponse(status_code=200, content={})
14591474

roo-standalone/roo/skills/executor.py

Lines changed: 201 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
from ..config import get_settings
2121

2222

23+
POINTS_SUPER_ADMIN_SLACK_ID = "U05QPB483K9"
24+
25+
2326
@dataclass
2427
class SkillResult:
2528
"""Result from skill execution."""
@@ -49,6 +52,7 @@ async def execute(
4952
channel_id: Optional[str] = None,
5053
thread_ts: Optional[str] = None,
5154
thread_history: Optional[List[dict]] = None,
55+
param_overrides: Optional[dict] = None,
5256
**kwargs
5357
) -> SkillResult:
5458
"""
@@ -70,6 +74,9 @@ async def execute(
7074
try:
7175
# Extract parameters using LLM
7276
params = await self._extract_parameters(skill, text, user_id, thread_history)
77+
if param_overrides:
78+
params = {**params, **param_overrides}
79+
print(f" Applied param overrides: {param_overrides}")
7380
print(f" Extracted params: {params}")
7481

7582
# Check for skill-specific implementation
@@ -1092,15 +1099,23 @@ async def _execute_content_factory(
10921099
# 1. New User Disclaimer & Education
10931100
# If user has no integration AND hasn't confirmed the disclaimer yet
10941101
if not integration and not params.get("confirmed"):
1095-
# Save pending intent so we don't lose the original params (domain/topic)
1096-
intent_data = json.dumps({
1097-
"skill": "content-factory",
1098-
"params": params,
1099-
"text": text,
1100-
"channel": channel_id,
1101-
"ts": thread_ts
1102-
})
1103-
await api_client.save_pending_intent(user_id, intent_data)
1102+
await self._save_content_factory_pending_intent(
1103+
api_client,
1104+
user_id,
1105+
params,
1106+
text,
1107+
channel_id,
1108+
thread_ts,
1109+
)
1110+
1111+
confirm_value = json.dumps(
1112+
{
1113+
"text": text,
1114+
"params": params,
1115+
"channel_id": channel_id,
1116+
"thread_ts": thread_ts,
1117+
}
1118+
)
11041119

11051120
blocks = [
11061121
{
@@ -1137,6 +1152,7 @@ async def _execute_content_factory(
11371152
"emoji": True
11381153
},
11391154
"action_id": "confirm_content_factory",
1155+
"value": confirm_value,
11401156
"style": "primary"
11411157
}
11421158
]
@@ -2254,6 +2270,15 @@ def _resolve_points_action(self, params: dict, text: str) -> str:
22542270
text_lower = text.lower()
22552271
explicit_points_request = "request" in text_lower and "point" in text_lower and "reward" not in text_lower
22562272

2273+
if action in ["promote_points_admin", "set_points_admin_allowance"]:
2274+
return action
2275+
2276+
if self._is_points_admin_promotion_command(text):
2277+
return "promote_points_admin"
2278+
2279+
if self._is_points_allowance_change_command(text):
2280+
return "set_points_admin_allowance"
2281+
22572282
if explicit_points_request:
22582283
return "request_points"
22592284

@@ -2302,6 +2327,86 @@ def _resolve_points_action(self, params: dict, text: str) -> str:
23022327
return "deduct_points"
23032328
return action
23042329

2330+
def _is_points_super_admin(self, user_id: str) -> bool:
2331+
"""Return true when the requester can manage points admins and allowances."""
2332+
return user_id == POINTS_SUPER_ADMIN_SLACK_ID
2333+
2334+
def _points_super_admin_denial(self) -> str:
2335+
"""Fixed denial response for restricted points super-admin actions."""
2336+
return (
2337+
f"Sorry mate, only <@{POINTS_SUPER_ADMIN_SLACK_ID}> can manage "
2338+
"Points Admin access and weekly allowances. 🔒"
2339+
)
2340+
2341+
def _is_points_admin_promotion_command(self, text: str) -> bool:
2342+
"""Detect commands that promote a tagged user to Points Admin."""
2343+
return bool(
2344+
re.search(r"\b(?:promote|make)\b", text, re.IGNORECASE)
2345+
and re.search(r"<@[A-Z0-9]+>", text)
2346+
and re.search(r"\b(?:roo\s+)?points\s+admin\b", text, re.IGNORECASE)
2347+
)
2348+
2349+
def _is_points_allowance_change_command(self, text: str) -> bool:
2350+
"""Detect commands that change a tagged admin's weekly allowance."""
2351+
return bool(
2352+
re.search(r"\b(?:set|change)\b", text, re.IGNORECASE)
2353+
and re.search(r"<@[A-Z0-9]+>", text)
2354+
and re.search(r"\bweekly\s+(?:points\s+)?allowance\b", text, re.IGNORECASE)
2355+
)
2356+
2357+
def _extract_non_roo_mentions(self, text: str, bot_id: Optional[str] = None) -> list[str]:
2358+
"""Extract unique tagged Slack user IDs, excluding Roo when present."""
2359+
target_ids: list[str] = []
2360+
seen: set[str] = set()
2361+
2362+
for target_id in re.findall(r"<@([A-Z0-9]+)>", text):
2363+
if target_id == bot_id or target_id in seen:
2364+
continue
2365+
seen.add(target_id)
2366+
target_ids.append(target_id)
2367+
2368+
return target_ids
2369+
2370+
def _extract_single_admin_target(
2371+
self,
2372+
text: str,
2373+
*,
2374+
bot_id: Optional[str],
2375+
action_label: str,
2376+
) -> tuple[Optional[str], Optional[str]]:
2377+
"""Extract exactly one tagged target user for privileged admin actions."""
2378+
target_ids = self._extract_non_roo_mentions(text, bot_id=bot_id)
2379+
2380+
if not target_ids:
2381+
return None, f"Tag exactly one user to {action_label}."
2382+
if len(target_ids) > 1:
2383+
return None, f"Tag exactly one user to {action_label}. I found multiple mentions there."
2384+
2385+
return target_ids[0], None
2386+
2387+
def _extract_weekly_allowance(self, text: str, fallback_allowance: Any = None) -> Optional[int]:
2388+
"""Extract the requested weekly allowance from params or text."""
2389+
if fallback_allowance not in (None, "", "0"):
2390+
try:
2391+
return int(fallback_allowance)
2392+
except (TypeError, ValueError):
2393+
pass
2394+
2395+
patterns = (
2396+
r"weekly\s+(?:points\s+)?allowance\s+(?:to|of)\s+(-?\d+)\b",
2397+
r"allowance\s+(?:to|of)\s+(-?\d+)\b",
2398+
r"(?<![A-Z0-9])(-?\d+)\b",
2399+
)
2400+
for pattern in patterns:
2401+
match = re.search(pattern, text, re.IGNORECASE)
2402+
if match:
2403+
try:
2404+
return int(match.group(1))
2405+
except ValueError:
2406+
return None
2407+
2408+
return None
2409+
23052410
def _extract_points_request_reason(self, text: str, fallback_reason: str = "") -> str:
23062411
"""Extract the reason from a natural-language points request."""
23072412
if fallback_reason:
@@ -2689,6 +2794,93 @@ async def _handle_points_action(
26892794
# =====================================================================
26902795
# Admin Actions
26912796
# =====================================================================
2797+
2798+
elif action == "promote_points_admin":
2799+
from ..slack_client import get_bot_user_id
2800+
2801+
if not self._is_points_super_admin(user_id):
2802+
return self._points_super_admin_denial()
2803+
2804+
try:
2805+
bot_id = get_bot_user_id()
2806+
except Exception:
2807+
bot_id = None
2808+
2809+
target_slack_id, target_error = self._extract_single_admin_target(
2810+
text,
2811+
bot_id=bot_id,
2812+
action_label="promote them to Roo Points Admin",
2813+
)
2814+
if target_error:
2815+
return target_error
2816+
2817+
await self._ensure_user_exists(target_slack_id)
2818+
result = await client.promote_points_admin(user_id, target_slack_id)
2819+
2820+
error_message = str(result.get("error") or result.get("detail") or "").strip()
2821+
promoted_target = client._clean_slack_id(
2822+
result.get("target_slack_id") or target_slack_id
2823+
)
2824+
2825+
if result.get("already_admin") or (
2826+
"already" in error_message.lower() and "admin" in error_message.lower()
2827+
):
2828+
return f"<@{promoted_target}> is already a Roo Points Admin."
2829+
if error_message:
2830+
return f"Couldn't promote <@{promoted_target}> to Roo Points Admin: {error_message}"
2831+
2832+
return f"✅ <@{promoted_target}> is now a Roo Points Admin."
2833+
2834+
elif action == "set_points_admin_allowance":
2835+
from ..slack_client import get_bot_user_id
2836+
2837+
if not self._is_points_super_admin(user_id):
2838+
return self._points_super_admin_denial()
2839+
2840+
try:
2841+
bot_id = get_bot_user_id()
2842+
except Exception:
2843+
bot_id = None
2844+
2845+
target_slack_id, target_error = self._extract_single_admin_target(
2846+
text,
2847+
bot_id=bot_id,
2848+
action_label="change their weekly points allowance",
2849+
)
2850+
if target_error:
2851+
return target_error
2852+
2853+
weekly_allowance = self._extract_weekly_allowance(
2854+
text,
2855+
params.get("weekly_allowance"),
2856+
)
2857+
if weekly_allowance is None:
2858+
return (
2859+
"What weekly allowance should I set? Give me a positive number of points, "
2860+
"like `set <@user> weekly points allowance to 150`."
2861+
)
2862+
if weekly_allowance <= 0:
2863+
return "Weekly points allowance has to be a positive number."
2864+
2865+
result = await client.set_points_admin_weekly_allowance(
2866+
user_id,
2867+
target_slack_id,
2868+
weekly_allowance,
2869+
)
2870+
2871+
error_message = str(result.get("error") or result.get("detail") or "").strip()
2872+
if error_message:
2873+
if "not a points admin" in error_message.lower():
2874+
return f"<@{target_slack_id}> isn't a Points Admin yet, so I can't update their allowance."
2875+
return f"Couldn't update <@{target_slack_id}>'s weekly allowance: {error_message}"
2876+
2877+
effective_allowance = result.get("weekly_allowance", result.get("allowance", weekly_allowance))
2878+
updated_target = client._clean_slack_id(result.get("target_slack_id") or target_slack_id)
2879+
2880+
return (
2881+
f"✅ Set <@{updated_target}>'s weekly points allowance to "
2882+
f"{effective_allowance} points."
2883+
)
26922884

26932885
elif action == "create_task":
26942886
# 1. Parameter Aliases

0 commit comments

Comments
 (0)