Skip to content

Commit 058e993

Browse files
authored
Merge pull request #114 from MLAI-AUS-Inc/codex/domain-scan-routing-fix
bugfix
2 parents 4d13453 + 4d974ff commit 058e993

File tree

4 files changed

+113
-20
lines changed

4 files changed

+113
-20
lines changed

roo-standalone/roo/content_factory_progress.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,48 @@
22

33
CONTENT_FACTORY_ARTICLE_COST_POINTS = 6
44
CONTENT_FACTORY_REQUEST_SOURCE = "roo_slackbot"
5+
FREE_CONTENT_FACTORY_DOMAINS = {"mlai.au"}
6+
7+
8+
def normalize_content_factory_domain(domain: str | None) -> str:
9+
if not domain:
10+
return ""
11+
12+
normalized = domain.strip().lower()
13+
if normalized.startswith("https://"):
14+
normalized = normalized[8:]
15+
elif normalized.startswith("http://"):
16+
normalized = normalized[7:]
17+
18+
if normalized.startswith("www."):
19+
normalized = normalized[4:]
20+
21+
if "/" in normalized:
22+
normalized = normalized.split("/", 1)[0]
23+
24+
return normalized
25+
26+
27+
def get_content_factory_article_cost_points(domain: str | None) -> int:
28+
normalized_domain = normalize_content_factory_domain(domain)
29+
if normalized_domain in FREE_CONTENT_FACTORY_DOMAINS:
30+
return 0
31+
return CONTENT_FACTORY_ARTICLE_COST_POINTS
32+
33+
34+
def is_free_content_factory_domain(domain: str | None) -> bool:
35+
return get_content_factory_article_cost_points(domain) == 0
36+
37+
38+
def build_content_factory_live_cost_text(domain: str | None) -> str:
39+
if is_free_content_factory_domain(domain):
40+
normalized_domain = normalize_content_factory_domain(domain) or "this domain"
41+
return f"💳 Articles for {normalized_domain} are free. No Roo points will be deducted for this run."
42+
43+
return (
44+
f"💳 This paid run costs {CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points. "
45+
"They were deducted when the run started."
46+
)
547

648

749
def build_live_status_blocks(
@@ -56,10 +98,7 @@ def build_live_status_blocks(
5698
"elements": [
5799
{
58100
"type": "mrkdwn",
59-
"text": (
60-
f"💳 This paid run costs {CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points. "
61-
"They were deducted when the run started."
62-
),
101+
"text": build_content_factory_live_cost_text(domain),
63102
}
64103
],
65104
},

roo-standalone/roo/main.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919

2020
from .config import get_settings, Settings
2121
from .agent import RooAgent, get_agent
22-
from .content_factory_progress import CONTENT_FACTORY_REQUEST_SOURCE, build_live_status_blocks
22+
from .content_factory_progress import (
23+
CONTENT_FACTORY_REQUEST_SOURCE,
24+
build_live_status_blocks,
25+
get_content_factory_article_cost_points,
26+
normalize_content_factory_domain,
27+
)
2328
from .slack_client import post_message, send_dm
2429

2530
# Pending intents for auto-continue after prerequisite steps complete.
@@ -2397,14 +2402,21 @@ async def slack_actions(request: Request):
23972402
reply_channel = value_channel_id or msg_channel
23982403
reply_thread_ts = value_thread_ts or payload.get("message", {}).get("thread_ts") or msg_ts
23992404
is_dm = bool(reply_channel and reply_channel.startswith("D"))
2405+
normalized_domain = normalize_content_factory_domain(domain) or domain or "this domain"
2406+
article_cost_points = get_content_factory_article_cost_points(domain)
2407+
cost_guidance = (
2408+
f"Articles for {normalized_domain} are free."
2409+
if article_cost_points == 0
2410+
else f"Starting the article run deducts {article_cost_points} Roo points."
2411+
)
24002412
guidance_text = (
24012413
"✅ Reply here with the topic you want me to write about. "
24022414
"I'll still research the best keywords, title, and talking points so it has the best chance to rank. "
2403-
"Starting the article run deducts 6 Roo points."
2415+
f"{cost_guidance}"
24042416
if is_dm else
24052417
"✅ Reply in this thread with something like `@Roo write about AI for clinic workflows`. "
24062418
"I'll still research the best keywords, title, and talking points so it has the best chance to rank. "
2407-
"Starting the article run deducts 6 Roo points."
2419+
f"{cost_guidance}"
24082420
)
24092421

24102422
print(f"📝 User {user_id} will provide the article topic for {domain}")

roo-standalone/roo/skills/executor.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
CONTENT_FACTORY_ARTICLE_COST_POINTS,
2121
CONTENT_FACTORY_REQUEST_SOURCE,
2222
build_live_status_blocks,
23+
get_content_factory_article_cost_points,
24+
is_free_content_factory_domain,
25+
normalize_content_factory_domain,
2326
)
2427
from ..content_intent import is_explicit_scan_request
2528
from ..llm import chat, embed, get_llm_client
@@ -221,6 +224,13 @@ def _build_article_direction_blocks(
221224
client_request_id: str,
222225
) -> list[dict]:
223226
"""Build the preflight prompt for generic article requests."""
227+
normalized_domain = normalize_content_factory_domain(domain) or domain
228+
cost_points = get_content_factory_article_cost_points(domain)
229+
cost_text = (
230+
f"*Cost:* Articles for *{normalized_domain}* are free. No Roo points will be deducted for this run."
231+
if cost_points == 0
232+
else f"*Cost:* Starting the article run deducts {cost_points} Roo points."
233+
)
224234
button_value = json.dumps(
225235
{
226236
"domain": domain,
@@ -241,7 +251,7 @@ def _build_article_direction_blocks(
241251
"If you do, send it through and I'll still research the best keywords, title, "
242252
"and talking points so the article has the best chance to rank.\n\n"
243253
"If you don't, I can research the strongest article opportunity for you.\n\n"
244-
f"*Cost:* Starting the article run deducts {CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points."
254+
f"{cost_text}"
245255
),
246256
},
247257
},
@@ -1129,15 +1139,23 @@ async def _validate_content_factory_paid_access(
11291139
self,
11301140
api_client: Any,
11311141
user_id: str,
1142+
domain: Optional[str],
11321143
) -> Optional[str]:
11331144
from ..slack_client import get_user_info
11341145

1146+
normalized_domain = normalize_content_factory_domain(domain) or "this domain"
1147+
cost_points = get_content_factory_article_cost_points(domain)
11351148
slack_info = get_user_info(user_id)
11361149
email = str(slack_info.get("email") or "").strip().lower()
11371150
if not email:
1151+
if cost_points == 0:
1152+
return (
1153+
"I need access to your real Slack email before I can start Content Factory for you. "
1154+
f"Once that's available, articles for {normalized_domain} are free."
1155+
)
11381156
return (
11391157
"I need access to your real Slack email before I can start Content Factory for you. "
1140-
f"Once that's available, creating an article costs {CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points."
1158+
f"Once that's available, creating an article costs {cost_points} Roo points."
11411159
)
11421160

11431161
real_name = str(slack_info.get("real_name") or "").strip()
@@ -1161,6 +1179,9 @@ async def _validate_content_factory_paid_access(
11611179
"Please try again in a moment."
11621180
)
11631181

1182+
if cost_points == 0:
1183+
return None
1184+
11641185
try:
11651186
balance_data = await api_client.get_balance(user_id)
11661187
except Exception as exc:
@@ -1171,9 +1192,9 @@ async def _validate_content_factory_paid_access(
11711192
)
11721193

11731194
balance = int(balance_data.get("balance") or 0)
1174-
if balance < CONTENT_FACTORY_ARTICLE_COST_POINTS:
1195+
if balance < cost_points:
11751196
return (
1176-
f"Creating an article costs {CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points, and you currently have "
1197+
f"Creating an article costs {cost_points} Roo points, and you currently have "
11771198
f"{balance}. Earn a few more Roo points first, then ask me again."
11781199
)
11791200

@@ -1292,6 +1313,16 @@ async def _execute_content_factory(
12921313
"thread_ts": thread_ts,
12931314
}
12941315
)
1316+
disclaimer_cost_text = (
1317+
f"*Cost:* Articles for **{normalize_content_factory_domain(domain) or domain or 'this domain'}** are free. "
1318+
"No Roo points will be deducted for this run.\n\n"
1319+
if is_free_content_factory_domain(domain)
1320+
else (
1321+
f"*Cost:* Creating an article costs **{CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points**. "
1322+
"Those points are deducted when article research/generation starts. "
1323+
"If something goes wrong, message Dr Sam on Slack and he can help sort out a refund.\n\n"
1324+
)
1325+
)
12951326

12961327
blocks = [
12971328
{
@@ -1309,9 +1340,7 @@ async def _execute_content_factory(
13091340
"text": (
13101341
"Before we start, a quick heads-up! This skill works best with **Next.js & Tailwind CSS** projects "
13111342
"and requires a connected **GitHub repository**.\n\n"
1312-
f"*Cost:* Creating an article costs **{CONTENT_FACTORY_ARTICLE_COST_POINTS} Roo points**. "
1313-
"Those points are deducted when article research/generation starts. "
1314-
"If something goes wrong, message Dr Sam on Slack and he can help sort out a refund.\n\n"
1343+
f"{disclaimer_cost_text}"
13151344
"*How it works:*\n"
13161345
"1. 🏗️ **Scans** your repo for blog structure (creates it if missing)\n"
13171346
"2. 🧩 **Checks** for reusable components (Hero, CTAs) or creates them\n"
@@ -1996,7 +2025,7 @@ async def _execute_content_factory(
19962025
history_str = "\n".join([f"{msg.get('user')}: {msg.get('text')}" for msg in thread_history[:-1]])
19972026
full_context = f"Context from Thread:\n{history_str}\n\nCurrent Request: {text}"
19982027

1999-
access_error = await self._validate_content_factory_paid_access(api_client, user_id)
2028+
access_error = await self._validate_content_factory_paid_access(api_client, user_id, domain)
20002029
if access_error:
20012030
return access_error
20022031

roo-standalone/roo/tests/test_content_factory_article_flow.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,19 @@ def _patch_content_factory(monkeypatch):
192192
],
193193
}
194194
FakeContentFactoryClient.generic_integration = default_integration
195-
FakeContentFactoryClient.domain_integrations = {"mlai.au": default_integration}
195+
FakeContentFactoryClient.domain_integrations = {
196+
"mlai.au": default_integration,
197+
"woofya.com.au": {
198+
**default_integration,
199+
"connected_domains": [
200+
{
201+
"domain": "woofya.com.au",
202+
"github_repo": "MLAI-AUS-Inc/mlai-au",
203+
"scanned": True,
204+
}
205+
],
206+
},
207+
}
196208
FakeContentFactoryClient.auth_urls = {"default": "https://github.test/auth"}
197209
FakeContentFactoryClient.saved_intents = []
198210
FakeContentFactoryClient.balance_by_user = {}
@@ -616,6 +628,7 @@ async def test_explicit_research_request_starts_generation(monkeypatch):
616628
assert trigger_call["request_source"] == "roo_slackbot"
617629
assert trigger_call["client_request_id"].startswith("content-factory-")
618630
assert trigger_call["user_email"] == "sam@example.com"
631+
assert FakeContentFactoryClient.last_instance.balance_checks == []
619632

620633

621634
@pytest.mark.asyncio
@@ -627,8 +640,8 @@ async def test_content_factory_blocks_when_user_has_insufficient_points(monkeypa
627640

628641
result = await executor._execute_content_factory(
629642
skill=None,
630-
text="research the best article for mlai.au",
631-
params={"domain": "mlai.au"},
643+
text="research the best article for woofya.com.au",
644+
params={"domain": "woofya.com.au"},
632645
user_id="U999FREE",
633646
channel_id="C123",
634647
thread_ts="111.222",
@@ -662,7 +675,7 @@ async def test_content_factory_blocks_when_slack_email_missing(monkeypatch):
662675
)
663676

664677
assert "real Slack email" in result
665-
assert "6 Roo points" in result
678+
assert "articles for mlai.au are free" in result.lower()
666679
assert FakeContentFactoryClient.last_instance.trigger_calls == []
667680
assert FakeContentFactoryClient.last_instance.user_registration_calls == []
668681

@@ -851,7 +864,7 @@ async def form(self):
851864
updated_blocks = updated_messages[0]["blocks"]
852865
assert all(block.get("type") != "actions" for block in updated_blocks)
853866
assert "@Roo write about AI for clinic workflows" in updated_blocks[-1]["elements"][0]["text"]
854-
assert "6 Roo points" in updated_blocks[-1]["elements"][0]["text"]
867+
assert "free" in updated_blocks[-1]["elements"][0]["text"].lower()
855868

856869

857870
def test_prerequisite_scan_action_triggers_backend_from_repeat_scan_prompt(monkeypatch):

0 commit comments

Comments
 (0)