Files

187 lines
6.8 KiB
Python

import os
import json
import base64
import sys
from google.cloud import firestore
from triage_orchestrator import process_issue_triage
from utils.validator import validate_triage_result
from utils.egress import send_label_action, send_comment_action
from utils.events import publish_issue_ready_for_code
from db.issues_store import IssuesStore, ClaimAction, ReleaseAction
FEATURE_CLOSED_COMMENT = (
"Thank you for bringing this to our attention. Right now, our "
"engineering team is focusing all resources on critical system "
"maintenance and core stability. Because of this, we don't have "
"immediate plans to address this specific issue. If you believe "
"this issue was misclassified, feel free to reopen it."
)
QUALITY_CLOSED_COMMENT = (
"Thank you for reaching out. We are closing this issue as it does "
"not contain a discernible description or actionable bug report for "
"our team to investigate. If you believe this was closed in error, "
"please feel free to open a new issue with complete reproduction details."
)
NEEDS_INFO_FOOTER = (
"\n\nPlease reply with the requested details and mention `@caretaker-agent`."
)
def main() -> None:
"""
Orchestrates the Cloud Run Job execution loop for Caretaker Triage.
Assumptions:
- Assumes ISSUE_DETAILS env var contains base64-encoded JSON payload.
- Assumes WORKFLOW_EXECUTION_ID contains unique lock holder ID.
"""
# Cloud Run Jobs inject data via environment variables
encoded_data = os.environ.get("ISSUE_DETAILS")
if not encoded_data:
print("[PROD] Error: No data provided in ISSUE_DETAILS.")
sys.exit(1)
try:
payload = json.loads(base64.b64decode(encoded_data))
except Exception as e:
print(f"[PROD] Error decoding payload: {e}")
sys.exit(1)
try:
issue_number = int(payload.get("issue_number"))
except (TypeError, ValueError):
print("[PROD] Error: issue_number is not a valid number. Exiting.")
sys.exit(1)
try:
owner, repo = payload.get("repository", "").split("/")
if not owner or not repo:
raise ValueError
except (TypeError, ValueError):
print("[PROD] Error: Malformed repository format (expected 'owner/repo'). Exiting.")
sys.exit(1)
lock_holder = os.environ.get("WORKFLOW_EXECUTION_ID", "local-exec")
# Initialize Firestore Client & IssuesStore
project_id = os.environ.get("PROJECT_ID")
db_id = os.environ.get("FIRESTORE_DATABASE")
collection_name = os.environ.get("FIRESTORE_COLLECTION", "issues")
db_client = firestore.Client(project=project_id, database=db_id)
store = IssuesStore(db_client, collection_name)
# Claim the lock
claim_action = store.acquire_lock(owner, repo, issue_number, lock_holder)
if claim_action == ClaimAction.SKIP:
print(
f"[WORKER] Issue #{issue_number} already handled or active lock "
"present. Exiting."
)
sys.exit(0)
elif claim_action == ClaimAction.NEEDS_HUMAN:
print(f"[WORKER] Issue #{issue_number} requires human review. Exiting.")
sys.exit(0)
print(f"[WORKER] Starting triage for issue #{issue_number}...")
target_cwd = os.environ.get("TARGET_CWD", "/opt/gemini-cli")
try:
success, raw_output = process_issue_triage(payload, target_cwd)
except Exception as e:
print(f"[WORKER] Triage process failed with exception: {e}")
success, raw_output = False, f"Exception during triage execution: {e}"
error_message = None
if success:
try:
triage_result = json.loads(raw_output)
validate_triage_result(triage_result)
quality = triage_result.get("triage_metadata", {}).get("quality")
workable_spec = triage_result.get("workable_spec", {})
if quality in ["SPAM", "EMPTY", "FEATURE"]:
print(f"[WORKER] Quality: {quality}. Leaving comment and applying auto-close label.")
if quality == "FEATURE":
comment = FEATURE_CLOSED_COMMENT
else: # SPAM or EMPTY
comment = QUALITY_CLOSED_COMMENT
send_comment_action(owner, repo, issue_number, comment)
send_label_action(owner, repo, issue_number, ["auto-close"])
store.release_lock(
owner,
repo,
issue_number,
lock_holder,
success=True,
status="AUTO_CLOSE",
)
sys.exit(0)
elif quality == "NEEDS_INFO":
print(f"[WORKER] Quality: NEEDS_INFO. Leaving comment.")
comment_body = (
triage_result.get("triage_metadata", {})
.get("comment", "")
.strip()
+ NEEDS_INFO_FOOTER
)
send_comment_action(owner, repo, issue_number, comment_body)
store.release_lock(
owner,
repo,
issue_number,
lock_holder,
success=True,
status="NEEDS_INFO",
)
sys.exit(0)
else:
effort = triage_result.get("triage_metadata", {}).get(
"effort_estimate"
)
print(
f"[WORKER] Quality: OK. Effort: {effort}. Applying "
"effort label."
)
send_label_action(
owner, repo, issue_number, [f"effort/{effort.lower()}"]
)
publish_issue_ready_for_code(
owner, repo, issue_number, workable_spec
)
store.release_lock(
owner,
repo,
issue_number,
lock_holder,
success=True,
status="TRIAGED",
workable_spec=workable_spec,
)
print(f"[WORKER] Triage success.")
sys.exit(0)
except Exception as e:
print(f"[WORKER] Validation failed: {e}")
success, error_message = False, f"Validation Error: {e}"
else:
error_message = raw_output
# If an exception happens in json.loads or validate_triage_result
# If LLM inference itself fails inside process_issue_triage
if not success:
release_action = store.release_lock(
owner, repo, issue_number, lock_holder, success=False, error=error_message
)
sys.exit(1 if release_action == ReleaseAction.RETRY else 0)
if __name__ == "__main__":
main()