codee-agent 0.7.0__tar.gz → 0.7.1__tar.gz

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.
Files changed (95) hide show
  1. {codee_agent-0.7.0 → codee_agent-0.7.1}/PKG-INFO +1 -1
  2. {codee_agent-0.7.0 → codee_agent-0.7.1}/pyproject.toml +1 -1
  3. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/admin.py +8 -3
  4. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/admin_service.py +38 -6
  5. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_admin_service.py +89 -44
  6. {codee_agent-0.7.0 → codee_agent-0.7.1}/LICENSE +0 -0
  7. {codee_agent-0.7.0 → codee_agent-0.7.1}/README.md +0 -0
  8. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/.gitignore +0 -0
  9. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/__init__.py +0 -0
  10. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/admin_api.py +0 -0
  11. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/admin_cli.py +0 -0
  12. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/agent_cli.py +0 -0
  13. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/coding_agents.py +0 -0
  14. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/executor.py +0 -0
  15. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/init_cli.py +0 -0
  16. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/__init__.py +0 -0
  17. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/claude_key_rotation.py +0 -0
  18. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/cron_describe.py +0 -0
  19. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/mcp_config.py +0 -0
  20. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/runs_db.py +0 -0
  21. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/test_claude_key_rotation.py +0 -0
  22. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/test_mcp_config.py +0 -0
  23. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/test_runs_db.py +0 -0
  24. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/test_trigger_cron_skills.py +0 -0
  25. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/test_trigger_issue_skills.py +0 -0
  26. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/trigger_aws_sqs_skills.py +0 -0
  27. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/trigger_cron_skills.py +0 -0
  28. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/trigger_email_skills.py +0 -0
  29. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/lib/trigger_issue_skills.py +0 -0
  30. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/mail_server.py +0 -0
  31. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/setup_wizard.py +0 -0
  32. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/start_cli.py +0 -0
  33. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/tasks_providers.py +0 -0
  34. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/AGENTS.md +0 -0
  35. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/CLAUDE.md +0 -0
  36. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/aws-sqs-alarm-response/SKILL.md +0 -0
  37. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/cron-research-5xx-errors/SKILL.md +0 -0
  38. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-code-reviewer/SKILL.md +0 -0
  39. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-developer/SKILL.md +0 -0
  40. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-planner/SKILL.md +0 -0
  41. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-planner/assets/readme-template.md +0 -0
  42. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-qa/SKILL.md +0 -0
  43. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/story-security-reviewer/SKILL.md +0 -0
  44. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/task-code-reviewer/SKILL.md +0 -0
  45. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/task-developer/SKILL.md +0 -0
  46. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/task-qa/SKILL.md +0 -0
  47. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/templates/skills/task-security-reviewer/SKILL.md +0 -0
  48. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_admin_api.py +0 -0
  49. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_admin_cli.py +0 -0
  50. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_agent_cli.py +0 -0
  51. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_coding_agents.py +0 -0
  52. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_executor.py +0 -0
  53. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_init_cli.py +0 -0
  54. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_memory_index.py +0 -0
  55. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_setup_wizard.py +0 -0
  56. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/test_start_cli.py +0 -0
  57. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee/workflow_graph.py +0 -0
  58. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_admin/__init__.py +0 -0
  59. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_admin/codee_admin.py +0 -0
  60. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_abstract/__init__.py +0 -0
  61. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_abstract/provider.py +0 -0
  62. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/__init__.py +0 -0
  63. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/account.py +0 -0
  64. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/credentials.py +0 -0
  65. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/oauth.py +0 -0
  66. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/provider.py +0 -0
  67. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/test.py +0 -0
  68. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_claude_code/usage.py +0 -0
  69. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_codex/__init__.py +0 -0
  70. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_codex/provider.py +0 -0
  71. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_codex/test.py +0 -0
  72. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_github_copilot/__init__.py +0 -0
  73. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_github_copilot/provider.py +0 -0
  74. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_github_copilot/test.py +0 -0
  75. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_opencode/__init__.py +0 -0
  76. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_opencode/provider.py +0 -0
  77. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_agent_opencode/test.py +0 -0
  78. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_database/__init__.py +0 -0
  79. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_database/claude_code_accounts.py +0 -0
  80. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_database/database.py +0 -0
  81. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_database/oauth_tokens.py +0 -0
  82. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_main_context/__init__.py +0 -0
  83. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_main_context/context.py +0 -0
  84. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_main_context/logging.py +0 -0
  85. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_main_context/test_context.py +0 -0
  86. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_main_context/test_logging.py +0 -0
  87. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_abstract/__init__.py +0 -0
  88. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_abstract/provider.py +0 -0
  89. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_azure_devops/__init__.py +0 -0
  90. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_azure_devops/oauth.py +0 -0
  91. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_azure_devops/provider.py +0 -0
  92. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_azure_devops/test.py +0 -0
  93. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_jira/__init__.py +0 -0
  94. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_jira/provider.py +0 -0
  95. {codee_agent-0.7.0 → codee_agent-0.7.1}/src/codee_tasks_jira/test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codee-agent
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents.
5
5
  Keywords: ai,agent,jira,azure-devops,claude-code,github-copilot,codex,opencode,automation
6
6
  Author: Denis Kibalko
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "codee-agent"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  description = "A virtual co-worker that picks up Jira and Azure DevOps tasks and solves them with coding agents."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -272,6 +272,7 @@ class RunRecord(BaseModel):
272
272
  started_at: str
273
273
  ended_at: str
274
274
  duration_label: str
275
+ relative_age_label: str
275
276
  session_id: str
276
277
  message: str
277
278
  user_message: str
@@ -863,6 +864,7 @@ class AdminState(rx.State):
863
864
  started_at=run["started_at"],
864
865
  ended_at=run["ended_at"],
865
866
  duration_label=run["duration_label"],
867
+ relative_age_label=run["relative_age_label"],
866
868
  session_id=run.get("session_id") or "",
867
869
  message=message,
868
870
  user_message=(run.get("user_message") or message).strip(),
@@ -2572,9 +2574,12 @@ def run_row(run: RunRecord) -> rx.Component:
2572
2574
  rx.flex(
2573
2575
  rx.vstack(rx.hstack(rx.text(run.skill_name, font_weight="600"),
2574
2576
  rx.badge(run.status, color_scheme=rx.cond(run.status == "succeeded", "green", "red"))),
2575
- rx.text(local_datetime(run.ended_at), color=MUTED,
2576
- font_size="0.8rem",
2577
- font_family="IBM Plex Mono, monospace"),
2577
+ rx.hstack(
2578
+ rx.text(local_datetime(run.ended_at)),
2579
+ rx.text("(", run.relative_age_label, ")"),
2580
+ color=MUTED, font_size="0.8rem",
2581
+ font_family="IBM Plex Mono, monospace",
2582
+ spacing="2"),
2578
2583
  rx.text("Duration: ", run.duration_label, color=MUTED,
2579
2584
  font_size="0.8rem",
2580
2585
  font_family="IBM Plex Mono, monospace"),
@@ -59,6 +59,26 @@ from codee_main_context.context import (
59
59
  work_item_mappings,
60
60
  )
61
61
 
62
+
63
+ def relative_age_label(value: str, now: datetime | None = None) -> str:
64
+ """Format an ISO timestamp as a compact minute, hour, or day age."""
65
+ try:
66
+ timestamp = datetime.fromisoformat(value)
67
+ except (TypeError, ValueError):
68
+ return ""
69
+ if timestamp.tzinfo is None:
70
+ timestamp = timestamp.replace(tzinfo=timezone.utc)
71
+ current = now or datetime.now(timezone.utc)
72
+ elapsed = max(int((current - timestamp).total_seconds()), 0)
73
+ if elapsed < 3600:
74
+ amount, unit = max(elapsed // 60, 1), "minute"
75
+ elif elapsed < 86400:
76
+ amount, unit = elapsed // 3600, "hour"
77
+ else:
78
+ amount, unit = elapsed // 86400, "day"
79
+ return f"{amount} {unit}{'' if amount == 1 else 's'} ago"
80
+
81
+
62
82
  load_dotenv()
63
83
 
64
84
  MANAGED = {
@@ -1300,16 +1320,25 @@ class AdminService:
1300
1320
  f"Generating workflow for work item {issue_type.capitalize()} "
1301
1321
  f"from {_count(len(skills), 'issue-trigger skill')}..."
1302
1322
  )
1323
+ session_id = str(uuid.uuid4())
1324
+
1325
+ def opened(agent_session_id: str) -> None:
1326
+ nonlocal session_id
1327
+ session_id = agent_session_id
1328
+
1303
1329
  validation_error = ""
1304
1330
  for attempt in range(2):
1305
- request = prompt
1306
1331
  if validation_error:
1307
- request += (
1308
- "\n\nYour previous response was invalid: "
1309
- f"{validation_error}. Return corrected JSON only."
1332
+ response = agent.continue_conversation(
1333
+ "Your previous response was invalid: "
1334
+ f"{validation_error}. Return corrected JSON only.",
1335
+ session_id,
1336
+ agent.best_model(),
1337
+ opened,
1310
1338
  )
1311
- response = agent.run(
1312
- request, str(uuid.uuid4()), agent.best_model())
1339
+ else:
1340
+ response = agent.run(
1341
+ prompt, session_id, agent.best_model(), opened)
1313
1342
  payload_text = _strip_code_fence(response)
1314
1343
  try:
1315
1344
  payload = json.loads(payload_text)
@@ -2050,6 +2079,7 @@ class AdminService:
2050
2079
  search: str = "") -> list[dict[str, Any]]:
2051
2080
  rows = runs_db.recent_runs(
2052
2081
  limit, offset, search=search, main_context=self.context)
2082
+ now = datetime.now(timezone.utc)
2053
2083
  for row in rows:
2054
2084
  try:
2055
2085
  elapsed = (datetime.fromisoformat(row["ended_at"])
@@ -2057,6 +2087,8 @@ class AdminService:
2057
2087
  except (TypeError, ValueError):
2058
2088
  elapsed = 0
2059
2089
  row["duration_label"] = runs_db.fmt_elapsed(max(int(elapsed), 0))
2090
+ row["relative_age_label"] = relative_age_label(
2091
+ row["ended_at"], now)
2060
2092
  return rows
2061
2093
 
2062
2094
  def load_settings(self) -> Settings:
@@ -5,6 +5,7 @@ import tempfile
5
5
  import threading
6
6
  import time
7
7
  import unittest
8
+ from datetime import datetime, timezone
8
9
  from pathlib import Path
9
10
  from unittest.mock import Mock, patch
10
11
  from urllib.parse import parse_qs, urlparse
@@ -19,7 +20,7 @@ from codee.admin_service import (
19
20
  USAGE_RATE_LIMIT_BACKOFF_SECONDS, WORKFLOW_CACHE_VERSION,
20
21
  WORKFLOW_HUMAN_EDGE_COLOR, AdminService, WorkflowGeneration,
21
22
  _remove_redundant_skill_transitions, azure_oauth, issue_prompt_task,
22
- normalize_work_items, parse_skill, repository_name)
23
+ normalize_work_items, parse_skill, relative_age_label, repository_name)
23
24
  from codee.lib import runs_db
24
25
  from codee_agent_claude_code import oauth as claude_oauth
25
26
  from codee_agent_claude_code.account import AccountUnavailable
@@ -61,6 +62,19 @@ def _write_issue_skill(root: Path) -> Path:
61
62
 
62
63
 
63
64
  class RecentRunsTest(unittest.TestCase):
65
+ def test_formats_relative_age_in_minutes_hours_and_days(self) -> None:
66
+ now = datetime(2026, 9, 24, 12, 0, tzinfo=timezone.utc)
67
+ cases = [
68
+ ("2026-09-24T11:59:30+00:00", "1 minute ago"),
69
+ ("2026-09-24T11:58:00+00:00", "2 minutes ago"),
70
+ ("2026-09-24T10:00:00+00:00", "2 hours ago"),
71
+ ("2026-09-21T12:00:00+00:00", "3 days ago"),
72
+ ]
73
+
74
+ for timestamp, expected in cases:
75
+ with self.subTest(timestamp=timestamp):
76
+ self.assertEqual(relative_age_label(timestamp, now), expected)
77
+
64
78
  def test_formats_duration_from_start_and_end_times(self) -> None:
65
79
  with tempfile.TemporaryDirectory() as directory:
66
80
  context = CodeeMainContext(data_dir=Path(directory))
@@ -1281,18 +1295,25 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1281
1295
  "Move the story to CR Needed when every subtask is implemented.\n"
1282
1296
  )
1283
1297
  agent = Mock()
1284
- agent.run.side_effect = [
1285
- '{"statuses":["Ready","Subtask Review","CR Needed"],'
1286
- '"transitions":[{"source":"Ready","target":"Subtask Review",'
1287
- '"label":"story-developer","evidence":"Move each subtask to '
1288
- 'Subtask Review once its pull request is open."}],'
1289
- '"final_statuses":["CR Needed"]}',
1298
+
1299
+ def invalid_workflow(message, session_id, model, on_session_id):
1300
+ on_session_id("agent-thread")
1301
+ return (
1302
+ '{"statuses":["Ready","Subtask Review","CR Needed"],'
1303
+ '"transitions":[{"source":"Ready","target":"Subtask Review",'
1304
+ '"label":"story-developer","evidence":"Move each subtask to '
1305
+ 'Subtask Review once its pull request is open."}],'
1306
+ '"final_statuses":["CR Needed"]}'
1307
+ )
1308
+
1309
+ agent.run.side_effect = invalid_workflow
1310
+ agent.continue_conversation.return_value = (
1290
1311
  '{"statuses":["Ready","CR Needed"],'
1291
1312
  '"transitions":[{"source":"Ready","target":"CR Needed",'
1292
1313
  '"label":"story-developer","evidence":"Move the story to CR '
1293
1314
  'Needed when every subtask is implemented."}],'
1294
- '"final_statuses":["CR Needed"]}',
1295
- ]
1315
+ '"final_statuses":["CR Needed"]}'
1316
+ )
1296
1317
  service = AdminService.__new__(AdminService)
1297
1318
  service.root = root
1298
1319
  service.skills_dir = skills_dir
@@ -1305,14 +1326,23 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1305
1326
  }):
1306
1327
  workflow = service.generate_workflow()["story"]
1307
1328
 
1308
- self.assertEqual(agent.run.call_count, 2)
1329
+ self.assertEqual(agent.run.call_count, 1)
1330
+ self.assertEqual(agent.continue_conversation.call_count, 1)
1309
1331
  self.assertIn(
1310
1332
  "moves the subtask rather than the story",
1311
- agent.run.call_args.args[0],
1333
+ agent.continue_conversation.call_args.args[0],
1312
1334
  )
1313
1335
  self.assertIn(
1314
1336
  "The graph is the lifecycle of the story work item alone",
1315
- agent.run.call_args_list[0].args[0],
1337
+ agent.run.call_args.args[0],
1338
+ )
1339
+ self.assertNotIn(
1340
+ "The graph is the lifecycle of the story work item alone",
1341
+ agent.continue_conversation.call_args.args[0],
1342
+ )
1343
+ self.assertEqual(
1344
+ agent.continue_conversation.call_args.args[1],
1345
+ "agent-thread",
1316
1346
  )
1317
1347
  self.assertEqual(
1318
1348
  [node["data"]["label"] for node in workflow["nodes"]],
@@ -1382,12 +1412,14 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1382
1412
  "After planning, move the story to [AI] Ready for human review.\n"
1383
1413
  )
1384
1414
  agent = Mock()
1385
- agent.run.side_effect = [
1415
+ agent.run.return_value = (
1386
1416
  '{"statuses":["[AI] Decomposition needed",'
1387
1417
  '"[AI] Ready for development","[AI] Ready for human review"],'
1388
1418
  '"transitions":[{"source":"[AI] Decomposition needed",'
1389
1419
  '"target":"[AI] Ready for development","label":"",'
1390
- '"evidence":""}],"final_statuses":[]}',
1420
+ '"evidence":""}],"final_statuses":[]}'
1421
+ )
1422
+ agent.continue_conversation.return_value = (
1391
1423
  '{"statuses":["[AI] Decomposition needed",'
1392
1424
  '"[AI] Ready for development","[AI] Ready for human review"],'
1393
1425
  '"transitions":[{'
@@ -1395,8 +1427,8 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1395
1427
  '"target":"[AI] Ready for human review",'
1396
1428
  '"label":"story-planner",'
1397
1429
  '"evidence":"After planning, move the story to [AI] Ready for human review."}],'
1398
- '"final_statuses":["[AI] Ready for human review"]}',
1399
- ]
1430
+ '"final_statuses":["[AI] Ready for human review"]}'
1431
+ )
1400
1432
  service = AdminService.__new__(AdminService)
1401
1433
  service.root = root
1402
1434
  service.skills_dir = skills_dir
@@ -1409,8 +1441,9 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1409
1441
  }):
1410
1442
  workflow = service.generate_workflow()["story"]
1411
1443
 
1412
- self.assertEqual(agent.run.call_count, 2)
1413
- retry_prompt = agent.run.call_args.args[0]
1444
+ self.assertEqual(agent.run.call_count, 1)
1445
+ self.assertEqual(agent.continue_conversation.call_count, 1)
1446
+ retry_prompt = agent.continue_conversation.call_args.args[0]
1414
1447
  self.assertIn(
1415
1448
  'transition 1 ("[AI] Decomposition needed" -> '
1416
1449
  '"[AI] Ready for development", label "") is labelled with a '
@@ -1501,16 +1534,18 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1501
1534
  "Move the issue to Review when work is done.\n"
1502
1535
  )
1503
1536
  agent = Mock()
1504
- agent.run.side_effect = [
1537
+ agent.run.return_value = (
1505
1538
  '{"statuses":["Ready","Review"],"transitions":['
1506
1539
  '{"source":"Ready","target":"Review","label":"develop",'
1507
1540
  '"evidence":"Work the issue until it is done."}],'
1508
- '"final_statuses":["Review"]}',
1541
+ '"final_statuses":["Review"]}'
1542
+ )
1543
+ agent.continue_conversation.return_value = (
1509
1544
  '{"statuses":["Ready","Review"],"transitions":['
1510
1545
  '{"source":"Ready","target":"Review","label":"develop",'
1511
1546
  '"evidence":"Move the issue to Review when work is done."}],'
1512
- '"final_statuses":["Review"]}',
1513
- ]
1547
+ '"final_statuses":["Review"]}'
1548
+ )
1514
1549
  service = AdminService.__new__(AdminService)
1515
1550
  service.root = root
1516
1551
  service.skills_dir = skills_dir
@@ -1554,18 +1589,20 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1554
1589
  "Move the issue to Review when work is done.\n"
1555
1590
  )
1556
1591
  agent = Mock()
1557
- agent.run.side_effect = [
1592
+ agent.run.return_value = (
1558
1593
  '{"statuses":["Ready","Review"],"transitions":['
1559
1594
  '{"source":"Blocked","target":"Review","label":"develop",'
1560
1595
  '"evidence":"Move the issue to Review when work is done."},'
1561
1596
  '{"source":"Ready","target":"Review","label":"develop",'
1562
1597
  '"evidence":"Work the issue until it is done."}],'
1563
- '"final_statuses":["Done"]}',
1598
+ '"final_statuses":["Done"]}'
1599
+ )
1600
+ agent.continue_conversation.return_value = (
1564
1601
  '{"statuses":["Ready","Review"],"transitions":['
1565
1602
  '{"source":"Ready","target":"Review","label":"develop",'
1566
1603
  '"evidence":"Move the issue to Review when work is done."}],'
1567
- '"final_statuses":["Review"]}',
1568
- ]
1604
+ '"final_statuses":["Review"]}'
1605
+ )
1569
1606
  service = AdminService.__new__(AdminService)
1570
1607
  service.root = root
1571
1608
  service.skills_dir = skills_dir
@@ -1578,7 +1615,7 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1578
1615
  }):
1579
1616
  service.generate_workflow()
1580
1617
 
1581
- retry_prompt = agent.run.call_args.args[0]
1618
+ retry_prompt = agent.continue_conversation.call_args.args[0]
1582
1619
  self.assertIn('transition 1 ("Blocked" -> "Review", label '
1583
1620
  '"develop") uses "Blocked", which is missing from '
1584
1621
  'statuses "Ready", "Review"', retry_prompt)
@@ -1601,16 +1638,18 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1601
1638
  "Move the issue to Done after approval.\n"
1602
1639
  )
1603
1640
  agent = Mock()
1604
- agent.run.side_effect = [
1641
+ agent.run.return_value = (
1605
1642
  '{"statuses":["Ready","Review","Done"],"transitions":['
1606
1643
  '{"source":"Ready","target":"Done","label":"review",'
1607
1644
  '"evidence":"Move the issue to Done after approval."}],'
1608
- '"final_statuses":["Done"]}',
1645
+ '"final_statuses":["Done"]}'
1646
+ )
1647
+ agent.continue_conversation.return_value = (
1609
1648
  '{"statuses":["Ready","Review","Done"],"transitions":['
1610
1649
  '{"source":"Review","target":"Done","label":"review",'
1611
1650
  '"evidence":"Move the issue to Done after approval."}],'
1612
- '"final_statuses":["Done"]}',
1613
- ]
1651
+ '"final_statuses":["Done"]}'
1652
+ )
1614
1653
  service = AdminService.__new__(AdminService)
1615
1654
  service.root = root
1616
1655
  service.skills_dir = skills_dir
@@ -1628,7 +1667,7 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1628
1667
  'a status "review" never has the issue in: its entry statuses '
1629
1668
  'are "Review" and no other transition of that skill moves the '
1630
1669
  'issue to "Ready".',
1631
- agent.run.call_args.args[0],
1670
+ agent.continue_conversation.call_args.args[0],
1632
1671
  )
1633
1672
 
1634
1673
  def test_generate_workflow_warns_when_statuses_have_no_edges(self) -> None:
@@ -1840,15 +1879,17 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1840
1879
  "Move the issue to In Progress when work starts.\n"
1841
1880
  )
1842
1881
  agent = Mock()
1843
- agent.run.side_effect = [
1882
+ agent.run.return_value = (
1844
1883
  '{"statuses":["Ready","In Progress"],"transitions":[],'
1845
1884
  '"final_statuses":[],"human_actions":[],'
1846
1885
  '"human_transitions":[{"source":"Ready","target":"In Progress",'
1847
- '"evidence":"Move the issue to In Progress when work starts."}]}',
1886
+ '"evidence":"Move the issue to In Progress when work starts."}]}'
1887
+ )
1888
+ agent.continue_conversation.return_value = (
1848
1889
  '{"statuses":["Ready","In Progress"],"transitions":[],'
1849
1890
  '"final_statuses":[],"human_actions":[],'
1850
- '"human_transitions":[]}',
1851
- ]
1891
+ '"human_transitions":[]}'
1892
+ )
1852
1893
  service = AdminService.__new__(AdminService)
1853
1894
  service.root = root
1854
1895
  service.skills_dir = skills_dir
@@ -1861,10 +1902,11 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1861
1902
  }):
1862
1903
  service.generate_workflow()
1863
1904
 
1864
- self.assertEqual(agent.run.call_count, 2)
1905
+ self.assertEqual(agent.run.call_count, 1)
1906
+ self.assertEqual(agent.continue_conversation.call_count, 1)
1865
1907
  self.assertIn(
1866
1908
  "human transition source Ready is an entry status of a skill",
1867
- agent.run.call_args.args[0],
1909
+ agent.continue_conversation.call_args.args[0],
1868
1910
  )
1869
1911
 
1870
1912
  def test_generate_workflow_rejects_a_human_action_for_an_unknown_status(
@@ -1882,13 +1924,15 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1882
1924
  "Move the issue to In Progress when work starts.\n"
1883
1925
  )
1884
1926
  agent = Mock()
1885
- agent.run.side_effect = [
1927
+ agent.run.return_value = (
1886
1928
  '{"statuses":["Ready","In Progress"],"transitions":[],'
1887
1929
  '"final_statuses":[],"human_actions":[{"status":"Blocked",'
1888
- '"action":"Unblock the story."}]}',
1930
+ '"action":"Unblock the story."}]}'
1931
+ )
1932
+ agent.continue_conversation.return_value = (
1889
1933
  '{"statuses":["Ready","In Progress"],"transitions":[],'
1890
- '"final_statuses":[],"human_actions":[]}',
1891
- ]
1934
+ '"final_statuses":[],"human_actions":[]}'
1935
+ )
1892
1936
  service = AdminService.__new__(AdminService)
1893
1937
  service.root = root
1894
1938
  service.skills_dir = skills_dir
@@ -1901,10 +1945,11 @@ class AdminServiceIssueTriggerTest(unittest.TestCase):
1901
1945
  }):
1902
1946
  service.generate_workflow()
1903
1947
 
1904
- self.assertEqual(agent.run.call_count, 2)
1948
+ self.assertEqual(agent.run.call_count, 1)
1949
+ self.assertEqual(agent.continue_conversation.call_count, 1)
1905
1950
  self.assertIn(
1906
1951
  "each human action must name a declared status",
1907
- agent.run.call_args.args[0],
1952
+ agent.continue_conversation.call_args.args[0],
1908
1953
  )
1909
1954
 
1910
1955
  def test_generate_workflow_returns_empty_without_issue_skills(self) -> None:
File without changes
File without changes