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.
@@ -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 typing import Optional, Dict, List, Set, TypedDict, Annotated
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
- from pydantic import BaseModel, Field
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: {git_result.error_message}"
632
+ f"Repository cloning failed: {error_message}"
618
633
  )
619
634
  return {
620
- "final_result": f"Repository cloning failed: {git_result.error_message}",
621
- "error_message": git_result.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: {git_result.repo_path}"
643
+ f"Repository cloned successfully to: {repo_path}"
628
644
  )
629
645
  return {
630
- "repo_path": git_result.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.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.error_message:
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: {issue_result.error_message}"
951
+ f"Issue creation failed: {issue_error_msg}"
935
952
  )
936
953
  return {
937
- "final_result": f"Issue creation failed: {issue_result.error_message}",
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.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.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.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.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
- if not issue_result.error_message:
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
- error_type = "network_error"
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
- if not issue_result.error_message:
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.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.result}")
2149
+ self.logger.info(f"Successfully created GitHub issue for workflow failure: {issue_result.summary}")
1906
2150
  else:
1907
- self.logger.warning(f"Failed to create GitHub issue for workflow failure: {issue_result.error_message}")
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]]: