diagram-to-iac 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diagram_to_iac/actions/supervisor_entry.py +165 -3
- diagram_to_iac/agents/git_langgraph/agent.py +60 -28
- diagram_to_iac/agents/supervisor_langgraph/agent.py +362 -33
- diagram_to_iac/agents/supervisor_langgraph/github_listener.py +433 -0
- diagram_to_iac/core/registry.py +674 -0
- diagram_to_iac/services/commenter.py +589 -0
- diagram_to_iac/tools/llm_utils/__init__.py +3 -1
- diagram_to_iac/tools/llm_utils/grok_driver.py +71 -0
- diagram_to_iac/tools/llm_utils/router.py +220 -30
- {diagram_to_iac-1.0.2.dist-info → diagram_to_iac-1.0.4.dist-info}/METADATA +5 -4
- {diagram_to_iac-1.0.2.dist-info → diagram_to_iac-1.0.4.dist-info}/RECORD +14 -10
- {diagram_to_iac-1.0.2.dist-info → diagram_to_iac-1.0.4.dist-info}/WHEEL +0 -0
- {diagram_to_iac-1.0.2.dist-info → diagram_to_iac-1.0.4.dist-info}/entry_points.txt +0 -0
- {diagram_to_iac-1.0.2.dist-info → diagram_to_iac-1.0.4.dist-info}/top_level.txt +0 -0
@@ -23,15 +23,20 @@ Architecture:
|
|
23
23
|
|
24
24
|
from __future__ import annotations
|
25
25
|
|
26
|
+
import fnmatch
|
27
|
+
import json
|
26
28
|
import logging
|
27
29
|
import os
|
28
|
-
import fnmatch
|
29
30
|
import uuid
|
30
|
-
from datetime import datetime
|
31
|
-
from
|
32
|
-
|
31
|
+
from datetime import datetime, timedelta
|
32
|
+
from pathlib import Path
|
33
|
+
from typing import Dict, Any, Optional, List, Annotated
|
34
|
+
from typing_extensions import TypedDict
|
35
|
+
from dataclasses import dataclass, asdict, field
|
36
|
+
from enum import Enum
|
33
37
|
import yaml
|
34
|
-
|
38
|
+
|
39
|
+
from pydantic import BaseModel, Field, validator
|
35
40
|
from langchain_core.messages import HumanMessage, BaseMessage
|
36
41
|
from langgraph.graph import StateGraph, END
|
37
42
|
from langgraph.checkpoint.memory import MemorySaver
|
@@ -134,9 +139,8 @@ class SupervisorAgent(AgentBase):
|
|
134
139
|
git_agent: Optional[GitAgent] = None,
|
135
140
|
shell_agent: Optional[ShellAgent] = None,
|
136
141
|
terraform_agent: Optional[TerraformAgent] = None,
|
137
|
-
|
142
|
+
registry: Optional["RunRegistry"] = None,
|
138
143
|
demonstrator: Optional[DryRunDemonstrator] = None,
|
139
|
-
|
140
144
|
issue_tracker: Optional[IssueTracker] = None,
|
141
145
|
|
142
146
|
) -> None:
|
@@ -207,6 +211,16 @@ class SupervisorAgent(AgentBase):
|
|
207
211
|
self.checkpointer = MemorySaver()
|
208
212
|
self.logger.info("MemorySaver checkpointer initialized")
|
209
213
|
|
214
|
+
# Initialize run registry for issue linking and metadata tracking
|
215
|
+
from diagram_to_iac.core.registry import get_default_registry
|
216
|
+
self.run_registry = registry or get_default_registry()
|
217
|
+
self.logger.info("Run registry initialized for issue tracking")
|
218
|
+
|
219
|
+
# Check for PR merge context
|
220
|
+
self.pr_merge_context = self._detect_pr_merge_context()
|
221
|
+
if self.pr_merge_context:
|
222
|
+
self.logger.info(f"PR merge context detected: PR #{self.pr_merge_context.get('pr_number')} -> SHA {self.pr_merge_context.get('merged_sha')}")
|
223
|
+
|
210
224
|
# Issue tracker for deduplicating issues
|
211
225
|
self.issue_tracker = issue_tracker or IssueTracker()
|
212
226
|
|
@@ -612,22 +626,24 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
612
626
|
)
|
613
627
|
)
|
614
628
|
|
615
|
-
if git_result.error_message:
|
629
|
+
if git_result.artifacts and git_result.artifacts.get('error_message'):
|
630
|
+
error_message = git_result.artifacts.get('error_message')
|
616
631
|
self.logger.error(
|
617
|
-
f"Repository cloning failed: {
|
632
|
+
f"Repository cloning failed: {error_message}"
|
618
633
|
)
|
619
634
|
return {
|
620
|
-
"final_result": f"Repository cloning failed: {
|
621
|
-
"error_message":
|
635
|
+
"final_result": f"Repository cloning failed: {error_message}",
|
636
|
+
"error_message": error_message,
|
622
637
|
"operation_type": "clone_error",
|
623
638
|
}
|
624
639
|
|
625
640
|
# Update state with repo path and continue to stack detection
|
641
|
+
repo_path = git_result.artifacts.get('repo_path') if git_result.artifacts else git_result.summary
|
626
642
|
self.logger.info(
|
627
|
-
f"Repository cloned successfully to: {
|
643
|
+
f"Repository cloned successfully to: {repo_path}"
|
628
644
|
)
|
629
645
|
return {
|
630
|
-
"repo_path":
|
646
|
+
"repo_path": repo_path,
|
631
647
|
"final_result": "route_to_stack_detect", # Continue workflow
|
632
648
|
"operation_type": "clone_success",
|
633
649
|
"error_message": None,
|
@@ -679,12 +695,12 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
679
695
|
issues_opened = 0
|
680
696
|
error_message = None
|
681
697
|
final_result = f"Unsupported stack detected: {stack}"
|
682
|
-
if issue_result.error_message:
|
683
|
-
error_message = issue_result.error_message
|
684
|
-
final_result += f" - Issue creation failed: {issue_result.error_message}"
|
698
|
+
if issue_result.artifacts.get('error_message'):
|
699
|
+
error_message = issue_result.artifacts.get('error_message')
|
700
|
+
final_result += f" - Issue creation failed: {issue_result.artifacts.get('error_message')}"
|
685
701
|
else:
|
686
702
|
issues_opened = 1
|
687
|
-
final_result += f" - Issue created: {issue_result.
|
703
|
+
final_result += f" - Issue created: {issue_result.summary}"
|
688
704
|
|
689
705
|
return {
|
690
706
|
"stack_detected": stack_detected,
|
@@ -929,25 +945,71 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
929
945
|
)
|
930
946
|
)
|
931
947
|
|
932
|
-
if issue_result.
|
948
|
+
issue_error_msg = issue_result.artifacts.get('error_message') if issue_result.artifacts else None
|
949
|
+
if issue_error_msg:
|
933
950
|
self.logger.error(
|
934
|
-
f"Issue creation failed: {
|
951
|
+
f"Issue creation failed: {issue_error_msg}"
|
935
952
|
)
|
936
953
|
return {
|
937
|
-
"final_result": f"Issue creation failed: {
|
954
|
+
"final_result": f"Issue creation failed: {issue_error_msg}",
|
938
955
|
"issues_opened": 0,
|
939
956
|
"operation_type": "issue_error",
|
940
957
|
}
|
941
958
|
|
942
959
|
|
943
960
|
if existing_id is None:
|
944
|
-
new_id = self._parse_issue_number(issue_result.
|
961
|
+
new_id = self._parse_issue_number(issue_result.summary)
|
945
962
|
if new_id is not None:
|
946
963
|
self._record_issue_id(repo_url, error_type, new_id)
|
964
|
+
|
965
|
+
# Update the run registry with the new issue ID
|
966
|
+
try:
|
967
|
+
# Find the current run by repo URL and update with issue ID
|
968
|
+
current_runs = self.run_registry.find_by_commit_and_repo(repo_url, "manual")
|
969
|
+
if current_runs:
|
970
|
+
latest_run = current_runs[0] # Get most recent
|
971
|
+
updated = self.run_registry.update(latest_run.run_key, {
|
972
|
+
'umbrella_issue_id': new_id,
|
973
|
+
'status': 'FAILED' # Issue created means workflow failed
|
974
|
+
})
|
975
|
+
if updated:
|
976
|
+
self.logger.info(f"Updated run {latest_run.run_key} with issue ID {new_id}")
|
977
|
+
except Exception as e:
|
978
|
+
self.logger.warning(f"Failed to update run registry with issue ID: {e}")
|
979
|
+
|
980
|
+
# If this is a PR merge workflow, update the previous issue with the new issue ID
|
981
|
+
try:
|
982
|
+
if self.pr_merge_context:
|
983
|
+
# Find the previous umbrella issue
|
984
|
+
previous_issue_id = self.run_registry.find_previous_umbrella_issue(
|
985
|
+
repo_url, exclude_sha=self.pr_merge_context.get('merged_sha', '')
|
986
|
+
)
|
987
|
+
if previous_issue_id:
|
988
|
+
# Create updated comment linking to the new issue
|
989
|
+
updated_comment = self._create_issue_link_comment(
|
990
|
+
previous_issue_id,
|
991
|
+
self.pr_merge_context.get('merged_sha', 'unknown')[:7],
|
992
|
+
new_issue_id=new_id
|
993
|
+
)
|
994
|
+
# Comment on the previous issue with the updated link
|
995
|
+
link_result = self.git_agent.run(
|
996
|
+
GitAgentInput(
|
997
|
+
query=f"comment on issue {previous_issue_id}: {updated_comment}",
|
998
|
+
issue_id=previous_issue_id,
|
999
|
+
thread_id=state.get("thread_id")
|
1000
|
+
)
|
1001
|
+
)
|
1002
|
+
link_error_msg = link_result.artifacts.get('error_message') if link_result.artifacts else None
|
1003
|
+
if link_error_msg:
|
1004
|
+
self.logger.warning(f"Failed to update previous issue #{previous_issue_id} with new issue link: {link_error_msg}")
|
1005
|
+
else:
|
1006
|
+
self.logger.info(f"Successfully updated previous issue #{previous_issue_id} with link to new issue #{new_id}")
|
1007
|
+
except Exception as e:
|
1008
|
+
self.logger.warning(f"Error updating previous issue with new issue link: {e}")
|
947
1009
|
|
948
1010
|
self.logger.info("GitHub issue created successfully")
|
949
1011
|
return {
|
950
|
-
"final_result": f"R2D workflow failed, issue created: {issue_result.
|
1012
|
+
"final_result": f"R2D workflow failed, issue created: {issue_result.summary}",
|
951
1013
|
"issues_opened": 1,
|
952
1014
|
"operation_type": "issue_success",
|
953
1015
|
"error_message": None,
|
@@ -1095,22 +1157,22 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1095
1157
|
)
|
1096
1158
|
)
|
1097
1159
|
|
1098
|
-
if issue_result.error_message:
|
1099
|
-
self.logger.error(f"Issue creation failed: {issue_result.error_message}")
|
1160
|
+
if issue_result.artifacts.get('error_message'):
|
1161
|
+
self.logger.error(f"Issue creation failed: {issue_result.artifacts.get('error_message')}")
|
1100
1162
|
return {
|
1101
|
-
"final_result": f"Issue creation failed: {issue_result.error_message}",
|
1163
|
+
"final_result": f"Issue creation failed: {issue_result.artifacts.get('error_message')}",
|
1102
1164
|
"issues_opened": 0,
|
1103
1165
|
"operation_type": "issue_error",
|
1104
1166
|
}
|
1105
1167
|
|
1106
1168
|
# Track new issue ID for deduplication
|
1107
1169
|
if existing_id is None:
|
1108
|
-
new_id = self._parse_issue_number(issue_result.
|
1170
|
+
new_id = self._parse_issue_number(issue_result.summary)
|
1109
1171
|
if new_id is not None:
|
1110
1172
|
self._record_issue_id(repo_url, error_type, new_id)
|
1111
1173
|
|
1112
1174
|
self.logger.info("GitHub issue created successfully")
|
1113
|
-
print(f"\n✅ Success! GitHub issue created: {issue_result.
|
1175
|
+
print(f"\n✅ Success! GitHub issue created: {issue_result.summary}")
|
1114
1176
|
|
1115
1177
|
return {
|
1116
1178
|
"final_result": f"R2D workflow failed, issue created: {issue_result.result}",
|
@@ -1370,6 +1432,137 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1370
1432
|
"error_message": str(e),
|
1371
1433
|
}
|
1372
1434
|
|
1435
|
+
# --- PR Merge Handling Methods ---
|
1436
|
+
|
1437
|
+
def _generate_dynamic_branch_name(self) -> str:
|
1438
|
+
"""Generate timestamp-based branch name."""
|
1439
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
1440
|
+
return f"r2d-{timestamp}"
|
1441
|
+
|
1442
|
+
def _detect_pr_merge_context(self) -> Optional[Dict[str, Any]]:
|
1443
|
+
"""
|
1444
|
+
Detect PR merge context from environment variables.
|
1445
|
+
|
1446
|
+
Returns:
|
1447
|
+
PR merge context dictionary if detected, None otherwise
|
1448
|
+
"""
|
1449
|
+
try:
|
1450
|
+
pr_context_env = os.environ.get('PR_MERGE_CONTEXT')
|
1451
|
+
if pr_context_env:
|
1452
|
+
import json
|
1453
|
+
pr_context = json.loads(pr_context_env)
|
1454
|
+
self.logger.info(f"PR merge context detected: {pr_context}")
|
1455
|
+
return pr_context
|
1456
|
+
except Exception as e:
|
1457
|
+
self.logger.warning(f"Failed to parse PR merge context: {e}")
|
1458
|
+
|
1459
|
+
return None
|
1460
|
+
|
1461
|
+
def _handle_pr_merge_workflow(self, repo_url: str, commit_sha: str) -> Optional[Dict[str, Any]]:
|
1462
|
+
"""
|
1463
|
+
Handle PR merge workflow by detecting previous umbrella issues and linking them.
|
1464
|
+
|
1465
|
+
Args:
|
1466
|
+
repo_url: Repository URL
|
1467
|
+
commit_sha: New commit SHA from PR merge
|
1468
|
+
|
1469
|
+
Returns:
|
1470
|
+
Dictionary with previous issue information if found, None otherwise
|
1471
|
+
"""
|
1472
|
+
try:
|
1473
|
+
# Find previous umbrella issue for this repository (excluding current SHA)
|
1474
|
+
previous_issue_id = self.run_registry.find_previous_umbrella_issue(
|
1475
|
+
repo_url, exclude_sha=commit_sha
|
1476
|
+
)
|
1477
|
+
|
1478
|
+
if previous_issue_id:
|
1479
|
+
self.logger.info(f"Found previous umbrella issue #{previous_issue_id} for {repo_url}")
|
1480
|
+
|
1481
|
+
# Create run for new commit and link to previous issue
|
1482
|
+
job_name = f"pr-merge-{self.pr_merge_context.get('pr_number', 'unknown')}"
|
1483
|
+
new_run_key = self.run_registry.create_run(
|
1484
|
+
repo_url=repo_url,
|
1485
|
+
commit_sha=commit_sha,
|
1486
|
+
job_name=job_name,
|
1487
|
+
thread_id=str(self.pr_merge_context.get('pr_number', 'unknown'))
|
1488
|
+
)
|
1489
|
+
|
1490
|
+
# Find the previous run to link them
|
1491
|
+
previous_run = self.run_registry.find_latest_run_with_issue(repo_url)
|
1492
|
+
if previous_run:
|
1493
|
+
self.run_registry.link_predecessor_run(new_run_key, previous_run.run_key)
|
1494
|
+
self.run_registry.close_old_umbrella_issue(
|
1495
|
+
previous_run.run_key,
|
1496
|
+
new_issue_id=0, # Will be updated when new issue is created
|
1497
|
+
new_commit_sha=commit_sha
|
1498
|
+
)
|
1499
|
+
|
1500
|
+
# Comment on the previous issue to link it to the new workflow
|
1501
|
+
try:
|
1502
|
+
link_comment = self._create_issue_link_comment(
|
1503
|
+
previous_issue_id, commit_sha, new_issue_id=None
|
1504
|
+
)
|
1505
|
+
# Use the standard issue format but with issue_id to trigger commenting
|
1506
|
+
comment_result = self.git_agent.run(
|
1507
|
+
GitAgentInput(
|
1508
|
+
query=f"open issue PR Merge Update for repository {repo_url}: {link_comment}",
|
1509
|
+
issue_id=previous_issue_id,
|
1510
|
+
thread_id=str(self.pr_merge_context.get('pr_number', 'unknown'))
|
1511
|
+
)
|
1512
|
+
)
|
1513
|
+
error_message = comment_result.artifacts.get('error_message') if comment_result.artifacts else None
|
1514
|
+
if error_message:
|
1515
|
+
self.logger.warning(f"Failed to comment on previous issue #{previous_issue_id}: {error_message}")
|
1516
|
+
else:
|
1517
|
+
self.logger.info(f"Successfully commented on previous issue #{previous_issue_id}")
|
1518
|
+
except Exception as e:
|
1519
|
+
self.logger.warning(f"Error commenting on previous issue #{previous_issue_id}: {e}")
|
1520
|
+
|
1521
|
+
return {
|
1522
|
+
"previous_issue_id": previous_issue_id,
|
1523
|
+
"new_run_key": new_run_key,
|
1524
|
+
"previous_run": previous_run,
|
1525
|
+
"commit_sha": commit_sha[:7]
|
1526
|
+
}
|
1527
|
+
except Exception as e:
|
1528
|
+
self.logger.error(f"Error handling PR merge workflow: {e}")
|
1529
|
+
|
1530
|
+
return None
|
1531
|
+
|
1532
|
+
def _create_issue_link_comment(self, previous_issue_id: int, new_commit_sha: str, new_issue_id: Optional[int] = None) -> str:
|
1533
|
+
"""
|
1534
|
+
Create a comment to link old issue to new issue for PR merge workflow.
|
1535
|
+
|
1536
|
+
Args:
|
1537
|
+
previous_issue_id: ID of the previous umbrella issue
|
1538
|
+
new_commit_sha: New commit SHA that triggered the workflow
|
1539
|
+
new_issue_id: New issue ID if created
|
1540
|
+
|
1541
|
+
Returns:
|
1542
|
+
Comment text for linking issues
|
1543
|
+
"""
|
1544
|
+
short_sha = new_commit_sha[:7]
|
1545
|
+
|
1546
|
+
if new_issue_id:
|
1547
|
+
comment = f"""🔄 **New commit detected: `{short_sha}`**
|
1548
|
+
|
1549
|
+
A new commit has been merged, opening fresh pipeline in Issue #{new_issue_id}.
|
1550
|
+
|
1551
|
+
**Previous Issue Status:** Resolved/Follow-up - superseded by new commit
|
1552
|
+
**New Pipeline:** Issue #{new_issue_id} tracks the latest deployment
|
1553
|
+
|
1554
|
+
This issue is now closed and linked forward to the new deployment pipeline."""
|
1555
|
+
else:
|
1556
|
+
comment = f"""🔄 **New commit detected: `{short_sha}`**
|
1557
|
+
|
1558
|
+
A new commit has been merged, attempting to open fresh pipeline.
|
1559
|
+
|
1560
|
+
**Previous Issue Status:** Resolved/Follow-up - superseded by new commit
|
1561
|
+
|
1562
|
+
This issue is now closed. A new issue will be created for the latest deployment pipeline."""
|
1563
|
+
|
1564
|
+
return comment
|
1565
|
+
|
1373
1566
|
# --- Utility Methods (preserved from original implementation) ---
|
1374
1567
|
|
1375
1568
|
def _generate_dynamic_branch_name(self) -> str:
|
@@ -1721,7 +1914,8 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1721
1914
|
)
|
1722
1915
|
)
|
1723
1916
|
)
|
1724
|
-
|
1917
|
+
startup_error_msg = issue_result.artifacts.get('error_message') if issue_result.artifacts else None
|
1918
|
+
if not startup_error_msg:
|
1725
1919
|
issues_opened = 1
|
1726
1920
|
except Exception as e:
|
1727
1921
|
self.logger.error(
|
@@ -1757,6 +1951,55 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1757
1951
|
thread_id=thread_id,
|
1758
1952
|
)
|
1759
1953
|
|
1954
|
+
# Handle PR merge context and old issue linking
|
1955
|
+
pr_merge_results = None
|
1956
|
+
current_commit_sha = None
|
1957
|
+
run_key = None
|
1958
|
+
|
1959
|
+
# Extract commit SHA from PR merge context or generate from current state
|
1960
|
+
if self.pr_merge_context:
|
1961
|
+
current_commit_sha = self.pr_merge_context.get('merged_sha')
|
1962
|
+
self.logger.info(f"Using PR merge SHA: {current_commit_sha}")
|
1963
|
+
|
1964
|
+
# Handle previous issue linking for PR merges
|
1965
|
+
pr_merge_results = self._handle_pr_merge_workflow(repo_url, current_commit_sha)
|
1966
|
+
if pr_merge_results:
|
1967
|
+
self.logger.info(f"PR merge workflow handled: {pr_merge_results}")
|
1968
|
+
else:
|
1969
|
+
# Generate a placeholder SHA for non-PR runs (this would be replaced by actual git SHA in real deployment)
|
1970
|
+
current_commit_sha = f"manual-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
1971
|
+
|
1972
|
+
# Create registry entry for this run
|
1973
|
+
try:
|
1974
|
+
job_name = self.pr_merge_context.get('pr_title', 'R2D Workflow') if self.pr_merge_context else 'Manual R2D Workflow'
|
1975
|
+
run_key = self.run_registry.create_run(
|
1976
|
+
repo_url=repo_url,
|
1977
|
+
commit_sha=current_commit_sha,
|
1978
|
+
job_name=job_name,
|
1979
|
+
thread_id=thread_id,
|
1980
|
+
branch_name=branch_name
|
1981
|
+
)
|
1982
|
+
self.logger.info(f"Created registry entry with run key: {run_key}")
|
1983
|
+
|
1984
|
+
# Link to predecessor run if this is a PR merge
|
1985
|
+
if pr_merge_results and pr_merge_results.get('previous_run_key'):
|
1986
|
+
linked = self.run_registry.link_predecessor_run(
|
1987
|
+
run_key, pr_merge_results['previous_run_key']
|
1988
|
+
)
|
1989
|
+
if linked:
|
1990
|
+
self.logger.info(f"Linked to predecessor run: {pr_merge_results['previous_run_key']}")
|
1991
|
+
|
1992
|
+
except Exception as e:
|
1993
|
+
self.logger.warning(f"Failed to create registry entry: {e}")
|
1994
|
+
|
1995
|
+
# Handle PR merge workflow context if detected
|
1996
|
+
pr_merge_info = None
|
1997
|
+
if self.pr_merge_context:
|
1998
|
+
commit_sha = self.pr_merge_context.get('merged_sha', 'unknown')
|
1999
|
+
pr_merge_info = self._handle_pr_merge_workflow(repo_url, commit_sha)
|
2000
|
+
if pr_merge_info:
|
2001
|
+
self.logger.info(f"PR merge workflow initiated: {pr_merge_info['commit_sha']} follows {pr_merge_info['previous_issue_id']}")
|
2002
|
+
|
1760
2003
|
# Create initial state
|
1761
2004
|
initial_state: SupervisorAgentState = {
|
1762
2005
|
"input_message": HumanMessage(
|
@@ -1850,7 +2093,7 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1850
2093
|
"network" in error_message.lower()
|
1851
2094
|
or "connection" in error_message.lower()
|
1852
2095
|
):
|
1853
|
-
|
2096
|
+
error_type = "network_error"
|
1854
2097
|
elif "timeout" in error_message.lower():
|
1855
2098
|
error_type = "timeout_error"
|
1856
2099
|
elif (
|
@@ -1894,17 +2137,19 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1894
2137
|
issue_id=existing_id,
|
1895
2138
|
)
|
1896
2139
|
issue_result = self.git_agent.run(git_input)
|
1897
|
-
|
2140
|
+
error_msg_from_artifacts = issue_result.artifacts.get('error_message') if issue_result.artifacts else None
|
2141
|
+
if not error_msg_from_artifacts:
|
1898
2142
|
issues_opened = 1
|
1899
2143
|
# Record new issue id if created
|
1900
2144
|
if existing_id is None:
|
1901
|
-
new_id = self._parse_issue_number(issue_result.
|
2145
|
+
new_id = self._parse_issue_number(issue_result.summary)
|
1902
2146
|
if new_id is not None:
|
1903
2147
|
self._record_issue_id(repo_url, error_type, new_id)
|
1904
2148
|
|
1905
|
-
self.logger.info(f"Successfully created GitHub issue for workflow failure: {issue_result.
|
2149
|
+
self.logger.info(f"Successfully created GitHub issue for workflow failure: {issue_result.summary}")
|
1906
2150
|
else:
|
1907
|
-
|
2151
|
+
issue_error_msg = issue_result.artifacts.get('error_message') if issue_result.artifacts else "Unknown error"
|
2152
|
+
self.logger.warning(f"Failed to create GitHub issue for workflow failure: {issue_error_msg}")
|
1908
2153
|
|
1909
2154
|
|
1910
2155
|
except Exception as issue_error:
|
@@ -1937,6 +2182,90 @@ Important: Only use routing tokens if the input contains actionable R2D workflow
|
|
1937
2182
|
)
|
1938
2183
|
return output
|
1939
2184
|
|
2185
|
+
def resume_workflow(self, run_key: str, commit_sha: str) -> SupervisorAgentOutput:
|
2186
|
+
"""
|
2187
|
+
Resume a workflow from a previous state using the run registry.
|
2188
|
+
|
2189
|
+
Args:
|
2190
|
+
run_key: The unique identifier for the workflow run to resume
|
2191
|
+
commit_sha: The commit SHA to use for the resumed workflow
|
2192
|
+
|
2193
|
+
Returns:
|
2194
|
+
SupervisorAgentOutput: Result of the resumed workflow
|
2195
|
+
"""
|
2196
|
+
self.logger.info(f"Resuming workflow for run_key: {run_key}, commit_sha: {commit_sha}")
|
2197
|
+
|
2198
|
+
try:
|
2199
|
+
# Get the existing run from registry
|
2200
|
+
existing_run = self.run_registry.get_run(run_key)
|
2201
|
+
if not existing_run:
|
2202
|
+
self.logger.error(f"No run found for key: {run_key}")
|
2203
|
+
return SupervisorAgentOutput(
|
2204
|
+
repo_url="unknown",
|
2205
|
+
branch_created=False,
|
2206
|
+
branch_name="unknown",
|
2207
|
+
stack_detected={},
|
2208
|
+
terraform_summary=None,
|
2209
|
+
unsupported=False,
|
2210
|
+
issues_opened=0,
|
2211
|
+
success=False,
|
2212
|
+
message=f"No existing run found for key: {run_key}"
|
2213
|
+
)
|
2214
|
+
|
2215
|
+
# Update run status to IN_PROGRESS
|
2216
|
+
self.run_registry.update(run_key, {
|
2217
|
+
'status': 'IN_PROGRESS',
|
2218
|
+
'commit_sha': commit_sha,
|
2219
|
+
'wait_reason': None
|
2220
|
+
})
|
2221
|
+
|
2222
|
+
# Create a new SupervisorAgentInput based on the existing run data
|
2223
|
+
resume_input = SupervisorAgentInput(
|
2224
|
+
repo_url=existing_run.repo_url,
|
2225
|
+
branch_name=existing_run.branch_name,
|
2226
|
+
thread_id=existing_run.thread_id,
|
2227
|
+
dry_run=False # Assume production run for resume
|
2228
|
+
)
|
2229
|
+
|
2230
|
+
self.logger.info(f"Resuming workflow for repo: {existing_run.repo_url}")
|
2231
|
+
|
2232
|
+
# Run the workflow with the restored input
|
2233
|
+
# The workflow will naturally continue from where it left off
|
2234
|
+
# based on the state preserved in memory and registry
|
2235
|
+
result = self.run(resume_input)
|
2236
|
+
|
2237
|
+
# Update registry with final status
|
2238
|
+
final_status = 'COMPLETED' if result.success else 'FAILED'
|
2239
|
+
self.run_registry.update(run_key, {
|
2240
|
+
'status': final_status,
|
2241
|
+
'issues_opened': result.issues_opened,
|
2242
|
+
'terraform_summary': result.terraform_summary
|
2243
|
+
})
|
2244
|
+
|
2245
|
+
self.logger.info(f"Workflow resumption {'successful' if result.success else 'failed'} for run_key: {run_key}")
|
2246
|
+
return result
|
2247
|
+
|
2248
|
+
except Exception as e:
|
2249
|
+
self.logger.error(f"Error resuming workflow for run_key {run_key}: {e}")
|
2250
|
+
|
2251
|
+
# Update registry with error status
|
2252
|
+
self.run_registry.update(run_key, {
|
2253
|
+
'status': 'FAILED',
|
2254
|
+
'wait_reason': f"Resume error: {str(e)}"
|
2255
|
+
})
|
2256
|
+
|
2257
|
+
return SupervisorAgentOutput(
|
2258
|
+
repo_url=existing_run.repo_url if 'existing_run' in locals() else "unknown",
|
2259
|
+
branch_created=False,
|
2260
|
+
branch_name=existing_run.branch_name if 'existing_run' in locals() else "unknown",
|
2261
|
+
stack_detected={},
|
2262
|
+
terraform_summary=None,
|
2263
|
+
unsupported=False,
|
2264
|
+
issues_opened=0,
|
2265
|
+
success=False,
|
2266
|
+
message=f"Workflow resumption failed: {str(e)}"
|
2267
|
+
)
|
2268
|
+
|
1940
2269
|
# --- Memory and Conversation Management ---
|
1941
2270
|
|
1942
2271
|
def get_conversation_history(self) -> List[Dict[str, any]]:
|