Skip to content

Commit 2c91617

Browse files
authored
Merge pull request #122 from MLAI-AUS-Inc/codex/domain-scan-routing-fix
bugfix publish to PR
2 parents a57b2a0 + f28716f commit 2c91617

File tree

5 files changed

+420
-3
lines changed

5 files changed

+420
-3
lines changed

roo-standalone/roo/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .slack_client import get_thread_messages
1818

1919

20-
THREAD_CONTEXT_TTL = timedelta(minutes=20)
20+
THREAD_CONTEXT_TTL = timedelta(hours=2)
2121

2222

2323
class RooAgent:

roo-standalone/roo/clients/mlai_backend.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,38 @@ async def publish_article_as_pr(self, job_id: str, slack_user_id: str) -> dict:
962962
response.raise_for_status()
963963
return response.json()
964964

965+
async def resolve_content_thread(
966+
self,
967+
*,
968+
slack_user_id: str,
969+
slack_channel_id: str,
970+
slack_thread_ts: str,
971+
requested_action: str,
972+
domain: Optional[str] = None,
973+
) -> dict:
974+
"""Resolve the active content-factory job for a Slack thread."""
975+
if not self.base_url:
976+
raise ValueError("MLAI_BACKEND_URL not configured")
977+
978+
payload = {
979+
"slack_user_id": self._clean_slack_id(slack_user_id),
980+
"slack_channel_id": slack_channel_id,
981+
"slack_thread_ts": slack_thread_ts,
982+
"requested_action": requested_action,
983+
}
984+
if domain:
985+
payload["domain"] = domain
986+
987+
async with httpx.AsyncClient() as client:
988+
response = await client.post(
989+
f"{self.base_url}/api/v1/content/jobs/resolve-thread",
990+
json=payload,
991+
headers=self.headers,
992+
timeout=30.0,
993+
)
994+
response.raise_for_status()
995+
return response.json()
996+
965997
# =========================================================================
966998
# Missing Admin / Points Methods
967999
# =========================================================================

roo-standalone/roo/main.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,6 +2072,93 @@ async def slack_actions(request: Request):
20722072
))
20732073
return JSONResponse(status_code=200, content={})
20742074

2075+
if action_id == "publish_content_pr":
2076+
value = actions[0].get("value", "")
2077+
try:
2078+
value_data = json.loads(value) if value else {}
2079+
except json.JSONDecodeError:
2080+
value_data = {}
2081+
2082+
job_id = str(value_data.get("job_id") or "").strip()
2083+
domain = value_data.get("domain")
2084+
original_user_id = value_data.get("slack_user_id")
2085+
value_channel_id = value_data.get("channel_id")
2086+
value_thread_ts = value_data.get("thread_ts")
2087+
2088+
msg_channel = payload.get("channel", {}).get("id")
2089+
msg_ts = payload.get("message", {}).get("ts")
2090+
reply_channel = value_channel_id or channel_id or msg_channel
2091+
reply_thread_ts = value_thread_ts or payload.get("message", {}).get("thread_ts") or msg_ts
2092+
2093+
if user_id != original_user_id:
2094+
return JSONResponse(status_code=200, content={
2095+
"response_type": "ephemeral",
2096+
"text": "⚠️ Only the user who requested this article can publish it as a PR."
2097+
})
2098+
2099+
if not job_id:
2100+
if reply_channel:
2101+
post_message(
2102+
channel=reply_channel,
2103+
thread_ts=reply_thread_ts,
2104+
text="⚠️ I couldn't determine which completed article bundle to publish from this button. Please use the original content-ready thread."
2105+
)
2106+
return JSONResponse(status_code=200, content={})
2107+
2108+
_remember_content_thread_context(
2109+
reply_channel,
2110+
reply_thread_ts,
2111+
domain,
2112+
"publish_pr",
2113+
active_job_id=job_id,
2114+
)
2115+
2116+
try:
2117+
from .slack_client import get_slack_client
2118+
slack_client = get_slack_client()
2119+
original_blocks = payload.get("message", {}).get("blocks", [])
2120+
updated_blocks = []
2121+
for block in original_blocks:
2122+
if block.get("block_id") == "content_ready_publish_actions":
2123+
continue
2124+
if block.get("type") == "actions":
2125+
elements = [
2126+
element for element in block.get("elements", [])
2127+
if element.get("action_id") != "publish_content_pr"
2128+
]
2129+
if not elements:
2130+
continue
2131+
updated_blocks.append({**block, "elements": elements})
2132+
continue
2133+
updated_blocks.append(block)
2134+
updated_blocks.append({
2135+
"type": "context",
2136+
"elements": [{"type": "mrkdwn", "text": "⏳ Publishing this article as a draft PR..."}]
2137+
})
2138+
slack_client.chat_update(
2139+
channel=msg_channel,
2140+
ts=msg_ts,
2141+
text=payload.get("message", {}).get("text", ""),
2142+
blocks=updated_blocks
2143+
)
2144+
except Exception as e:
2145+
print(f"⚠️ Failed to update publish-content-pr message: {e}")
2146+
2147+
asyncio.create_task(_handle_mention(
2148+
{
2149+
"user": user_id,
2150+
"text": "publish this article as a PR",
2151+
"channel": reply_channel,
2152+
"thread_ts": reply_thread_ts,
2153+
"param_overrides": {
2154+
"action": "publish_pr",
2155+
"job_id": job_id,
2156+
"domain": domain,
2157+
},
2158+
}
2159+
))
2160+
return JSONResponse(status_code=200, content={})
2161+
20752162
# Handler for confirm_topic (new format)
20762163
# Value format: "confirm:{keyword}:{job_id}"
20772164
if action_id == "confirm_topic":

roo-standalone/roo/skills/executor.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
is_free_content_factory_domain,
2525
normalize_content_factory_domain,
2626
)
27-
from ..content_intent import is_explicit_scan_request
27+
from ..content_intent import detect_content_action, is_explicit_scan_request
2828
from ..llm import chat, embed, get_llm_client
2929
from ..slack_client import post_message
3030
from ..config import get_settings
@@ -1372,6 +1372,76 @@ def _build_publish_pr_start_response(
13721372
},
13731373
}
13741374

1375+
async def _resolve_publish_pr_follow_up(
1376+
self,
1377+
api_client,
1378+
*,
1379+
text: str,
1380+
params: dict,
1381+
user_id: str,
1382+
channel_id: Optional[str],
1383+
thread_ts: Optional[str],
1384+
) -> tuple[dict, Optional[Any]]:
1385+
requested_action = detect_content_action(text)
1386+
if requested_action != "publish_pr":
1387+
return params, None
1388+
1389+
existing_job_id = str(params.get("job_id") or "").strip()
1390+
if existing_job_id:
1391+
params["action"] = "publish_pr"
1392+
return params, None
1393+
1394+
if not channel_id or not thread_ts:
1395+
return params, (
1396+
"I couldn't identify a completed article bundle for this publish request because the Slack thread context was missing."
1397+
)
1398+
1399+
try:
1400+
resolution = await api_client.resolve_content_thread(
1401+
slack_user_id=user_id,
1402+
slack_channel_id=channel_id,
1403+
slack_thread_ts=thread_ts,
1404+
requested_action="publish_pr",
1405+
domain=params.get("domain"),
1406+
)
1407+
except httpx.HTTPStatusError as exc:
1408+
if exc.response.status_code == 404:
1409+
return params, (
1410+
"I couldn't identify a completed article bundle to publish from this thread. "
1411+
"Please use the original content-ready thread for the article you want to publish."
1412+
)
1413+
return params, f"❌ I couldn't resolve which completed article to publish from this thread: {exc}"
1414+
except Exception as exc:
1415+
return params, f"❌ I couldn't resolve which completed article to publish from this thread: {exc}"
1416+
1417+
resolution_type = str(resolution.get("resolution") or "").strip()
1418+
resolved_domain = (
1419+
normalize_content_factory_domain(resolution.get("domain"))
1420+
or params.get("domain")
1421+
or resolution.get("domain")
1422+
)
1423+
if resolution_type == "ready":
1424+
params["action"] = "publish_pr"
1425+
params["job_id"] = str(resolution.get("job_id") or "").strip()
1426+
if resolved_domain:
1427+
params["domain"] = resolved_domain
1428+
return params, None
1429+
1430+
if resolution_type == "in_progress":
1431+
publish_job_id = str(resolution.get("promoted_publish_job_id") or "").strip()
1432+
if not publish_job_id:
1433+
return params, (
1434+
"I found an article in this thread that is already being promoted, but I couldn't determine the active publish run."
1435+
)
1436+
return params, self._build_publish_pr_start_response(
1437+
domain=resolved_domain,
1438+
job_id=publish_job_id,
1439+
)
1440+
1441+
return params, (
1442+
"I couldn't identify a completed article bundle to publish from this thread."
1443+
)
1444+
13751445
def _get_connected_domain_info(
13761446
self,
13771447
connected_domains: List[dict],
@@ -1479,6 +1549,18 @@ async def _execute_content_factory(
14791549
domain = params.get("domain")
14801550
org_config_cached = None
14811551
action = params.get("action")
1552+
params, publish_resolution = await self._resolve_publish_pr_follow_up(
1553+
api_client,
1554+
text=text,
1555+
params=params,
1556+
user_id=user_id,
1557+
channel_id=channel_id,
1558+
thread_ts=thread_ts,
1559+
)
1560+
if publish_resolution is not None:
1561+
return publish_resolution
1562+
action = params.get("action")
1563+
domain = params.get("domain")
14821564
if action == "publish_pr":
14831565
return await self._publish_content_bundle_as_pr(
14841566
api_client,

0 commit comments

Comments
 (0)