steerdev 1.1.6__tar.gz → 1.1.7__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 (117) hide show
  1. {steerdev-1.1.6 → steerdev-1.1.7}/.github/workflows/publish.yml +3 -1
  2. {steerdev-1.1.6 → steerdev-1.1.7}/PKG-INFO +1 -1
  3. {steerdev-1.1.6 → steerdev-1.1.7}/pyproject.toml +1 -1
  4. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/tasks.py +9 -0
  5. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/cli.py +13 -0
  6. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/runner.py +12 -1
  7. steerdev-1.1.7/tests/test_wave_scoped_fetch.py +60 -0
  8. {steerdev-1.1.6 → steerdev-1.1.7}/.github/workflows/pre-commit.yml +0 -0
  9. {steerdev-1.1.6 → steerdev-1.1.7}/.gitignore +0 -0
  10. {steerdev-1.1.6 → steerdev-1.1.7}/.pre-commit-config.yaml +0 -0
  11. {steerdev-1.1.6 → steerdev-1.1.7}/AGENTS.md +0 -0
  12. {steerdev-1.1.6 → steerdev-1.1.7}/CLAUDE.md +0 -0
  13. {steerdev-1.1.6 → steerdev-1.1.7}/README.md +0 -0
  14. {steerdev-1.1.6 → steerdev-1.1.7}/scripts/pre-commit-version-bump.sh +0 -0
  15. {steerdev-1.1.6 → steerdev-1.1.7}/snapshots/steerdev-agent-v1/Dockerfile +0 -0
  16. {steerdev-1.1.6 → steerdev-1.1.7}/snapshots/steerdev-agent-v1/start-agent.sh +0 -0
  17. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/__init__.py +0 -0
  18. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/agent_loop.py +0 -0
  19. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/__init__.py +0 -0
  20. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/activity.py +0 -0
  21. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/agents.py +0 -0
  22. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/canals.py +0 -0
  23. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/client.py +0 -0
  24. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/commands.py +0 -0
  25. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/configs.py +0 -0
  26. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/context.py +0 -0
  27. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/events.py +0 -0
  28. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/hooks.py +0 -0
  29. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/implementation_plan.py +0 -0
  30. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/merger.py +0 -0
  31. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/messages.py +0 -0
  32. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/prd.py +0 -0
  33. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/reports.py +0 -0
  34. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/runs.py +0 -0
  35. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/sessions.py +0 -0
  36. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/specs.py +0 -0
  37. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/workflow_runs.py +0 -0
  38. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/api/workflows.py +0 -0
  39. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/config/__init__.py +0 -0
  40. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/config/models.py +0 -0
  41. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/config/platform.py +0 -0
  42. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/config/settings.py +0 -0
  43. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/evidence.py +0 -0
  44. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/evidence_assets.py +0 -0
  45. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/executor/__init__.py +0 -0
  46. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/executor/base.py +0 -0
  47. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/executor/claude.py +0 -0
  48. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/executor/stream.py +0 -0
  49. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/handlers/__init__.py +0 -0
  50. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/handlers/prd.py +0 -0
  51. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/integration.py +0 -0
  52. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/prompt/__init__.py +0 -0
  53. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/prompt/builder.py +0 -0
  54. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/prompt/templates.py +0 -0
  55. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/prompt/workflow_template.py +0 -0
  56. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/py.typed +0 -0
  57. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/retry.py +0 -0
  58. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/__init__.py +0 -0
  59. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/claude_setup.py +0 -0
  60. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/repo_setup.py +0 -0
  61. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/ci/canal-integration.yml +0 -0
  62. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/claude_md_section.md +0 -0
  63. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/settings.json +0 -0
  64. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-activity-skill/SKILL.md +0 -0
  65. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-canal-workflow-skill/SKILL.md +0 -0
  66. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-context-skill/SKILL.md +0 -0
  67. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-git-workflow-skill/SKILL.md +0 -0
  68. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-merge-into-canal-skill/SKILL.md +0 -0
  69. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-progress-logging-skill/SKILL.md +0 -0
  70. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-single-task-merge-skill/SKILL.md +0 -0
  71. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-specs-management-skill/SKILL.md +0 -0
  72. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-task-management-skill/SKILL.md +0 -0
  73. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/skills/steerdev-wave-tasks-merge-skill/SKILL.md +0 -0
  74. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/steerdev.yaml +0 -0
  75. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/setup/templates/worktrunk.config.toml +0 -0
  76. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/update_check.py +0 -0
  77. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/version.py +0 -0
  78. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workflow/__init__.py +0 -0
  79. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workflow/context.py +0 -0
  80. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workflow/executor.py +0 -0
  81. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workflow/memory.py +0 -0
  82. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workspace/__init__.py +0 -0
  83. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workspace/preparation.py +0 -0
  84. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workspace/project_manager.py +0 -0
  85. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/workspace/tool_detection.py +0 -0
  86. {steerdev-1.1.6 → steerdev-1.1.7}/src/steerdev_agent/worktree.py +0 -0
  87. {steerdev-1.1.6 → steerdev-1.1.7}/tests/__init__.py +0 -0
  88. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_agent_loop.py +0 -0
  89. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_agent_loop_extended.py +0 -0
  90. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_agents_api.py +0 -0
  91. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_api_client.py +0 -0
  92. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_claude_executor.py +0 -0
  93. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_claude_setup.py +0 -0
  94. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_client_methods.py +0 -0
  95. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_commands_api.py +0 -0
  96. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_config.py +0 -0
  97. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_config_extended.py +0 -0
  98. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_conflict_mitigation.py +0 -0
  99. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_context_search.py +0 -0
  100. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_executor.py +0 -0
  101. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_platform_config.py +0 -0
  102. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_preparation.py +0 -0
  103. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_prompt.py +0 -0
  104. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_reports_client.py +0 -0
  105. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_retry.py +0 -0
  106. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_runner_merge_modes.py +0 -0
  107. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_runner_worktrees.py +0 -0
  108. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_stream_parser.py +0 -0
  109. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_tasks.py +0 -0
  110. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_version.py +0 -0
  111. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workflow_context.py +0 -0
  112. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workflow_memory.py +0 -0
  113. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workflow_prompt_template.py +0 -0
  114. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workflow_runs_api.py +0 -0
  115. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workspace.py +0 -0
  116. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_workspace_extended.py +0 -0
  117. {steerdev-1.1.6 → steerdev-1.1.7}/tests/test_worktree.py +0 -0
@@ -53,7 +53,9 @@ jobs:
53
53
  run: uv build
54
54
 
55
55
  - name: Publish to PyPI
56
- run: uv publish --trusted-publishing always
56
+ env:
57
+ UV_PUBLISH_TOKEN: ${{ secrets.UV_PUBLISH_TOKEN }}
58
+ run: uv publish
57
59
 
58
60
  - name: Create git tag and GitHub release
59
61
  env:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: steerdev
3
- Version: 1.1.6
3
+ Version: 1.1.7
4
4
  Summary: Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting
5
5
  Project-URL: Homepage, https://github.com/pentoai/steerdev-agent
6
6
  Project-URL: Repository, https://github.com/pentoai/steerdev-agent
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "steerdev"
3
- version = "1.1.6"
3
+ version = "1.1.7"
4
4
  description = "Backend task runner for steerdev.com - orchestrates CLI coding agents with activity reporting"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -108,6 +108,7 @@ class TasksClient(SteerDevClient):
108
108
  self,
109
109
  project_id: str | None = None,
110
110
  agent_id: str | None = None,
111
+ wave_id: str | None = None,
111
112
  ) -> dict[str, Any] | None:
112
113
  """Fetch the next task with wave-aware scheduling.
113
114
 
@@ -117,6 +118,11 @@ class TasksClient(SteerDevClient):
117
118
  When agent_id is provided, the server uses conflict-aware scheduling
118
119
  to avoid assigning tasks that overlap files with in-flight tasks.
119
120
 
121
+ When wave_id is provided, scheduling is scoped to that wave: the server
122
+ returns tasks only from that wave and a 404 (→ None) once it is
123
+ exhausted, so the caller's loop stops instead of bleeding into another
124
+ wave or an unrelated task.
125
+
120
126
  Returns either:
121
127
  - A wave response dict (has "wave" and "context" keys) with full wave context
122
128
  - A plain task dict (no wave keys) when no waves exist for the project
@@ -125,6 +131,7 @@ class TasksClient(SteerDevClient):
125
131
  Args:
126
132
  project_id: Filter by project ID. Falls back to STEERDEV_PROJECT_ID env var.
127
133
  agent_id: Agent ID for conflict-aware scheduling.
134
+ wave_id: Scope scheduling to a single wave (sandbox wave dispatch).
128
135
 
129
136
  Returns:
130
137
  Wave response, plain task, or None.
@@ -136,6 +143,8 @@ class TasksClient(SteerDevClient):
136
143
  params["project_id"] = effective_project_id
137
144
  if agent_id:
138
145
  params["agent_id"] = agent_id
146
+ if wave_id:
147
+ params["wave_id"] = wave_id
139
148
 
140
149
  console.print(f"Fetching next task from {self.api_base}/tasks/next")
141
150
  response = self.get("/tasks/next", params=params)
@@ -1745,6 +1745,16 @@ def run(
1745
1745
  help="Run a specific task by ID (bypasses queue/wave scheduling)",
1746
1746
  ),
1747
1747
  ] = None,
1748
+ wave_id: Annotated[
1749
+ str | None,
1750
+ typer.Option(
1751
+ "--wave-id",
1752
+ help=(
1753
+ "Process every task of a specific wave, then stop. Scopes wave "
1754
+ "scheduling to this wave (used by sandbox wave dispatch)."
1755
+ ),
1756
+ ),
1757
+ ] = None,
1748
1758
  ) -> None:
1749
1759
  """Run the agent for a project!
1750
1760
 
@@ -1814,6 +1824,7 @@ def run(
1814
1824
  max_tasks_display = "unlimited" if max_tasks == 0 else str(max_tasks)
1815
1825
  workflow_status = resolved_workflow_id or "auto (per-task)"
1816
1826
  agent_name_display = agent_name or "(auto from env)"
1827
+ wave_scope_display = wave_id or "(all waves)"
1817
1828
 
1818
1829
  console.print(
1819
1830
  Panel(
@@ -1826,6 +1837,7 @@ def run(
1826
1837
  f"Timeout: {resolved_timeout}s\n"
1827
1838
  f"Workflow: {workflow_status}\n"
1828
1839
  f"Waves: {waves_status}\n"
1840
+ f"Wave Scope: {wave_scope_display}\n"
1829
1841
  f"Canals: {canals_status}\n"
1830
1842
  f"Worktrees: {worktree_status}\n"
1831
1843
  f"Evidence: {evidence_status}\n"
@@ -1872,6 +1884,7 @@ def run(
1872
1884
  branch_config=resolved_branch_config,
1873
1885
  task_id=task_id,
1874
1886
  agent_id=agent_id,
1887
+ wave_id=wave_id,
1875
1888
  )
1876
1889
  )
1877
1890
 
@@ -288,6 +288,7 @@ class Runner:
288
288
  shutdown_event: asyncio.Event | None = None,
289
289
  agent_id: str | None = None,
290
290
  task_id: str | None = None,
291
+ wave_id: str | None = None,
291
292
  ) -> None:
292
293
  """Initialize the runner.
293
294
 
@@ -310,6 +311,9 @@ class Runner:
310
311
  dry_run: If True, print the command without executing it.
311
312
  retry_config: Retry configuration for failed tasks.
312
313
  agent_id: Database agent ID for conflict-aware scheduling.
314
+ task_id: Run a single specific task (bypasses queue/wave scheduling).
315
+ wave_id: Scope scheduling to a single wave; the loop processes every
316
+ task of that wave and stops when it is exhausted.
313
317
  """
314
318
  self.project_id = project_id
315
319
  self.working_directory = Path(working_directory or Path.cwd())
@@ -336,6 +340,7 @@ class Runner:
336
340
  self._enable_canals = enable_canals
337
341
  self._agent_id = agent_id
338
342
  self._target_task_id = task_id
343
+ self._target_wave_id = wave_id
339
344
 
340
345
  # State
341
346
  self._state = RunState.STOPPED
@@ -1144,6 +1149,7 @@ class Runner:
1144
1149
  client.get_next_wave(
1145
1150
  project_id=self.project_id,
1146
1151
  agent_id=self._agent_id,
1152
+ wave_id=self._target_wave_id,
1147
1153
  )
1148
1154
  )
1149
1155
  )
@@ -1153,7 +1159,10 @@ class Runner:
1153
1159
  exc_info=True,
1154
1160
  )
1155
1161
 
1156
- if not task:
1162
+ # When scoped to a wave, never fall back to single-task
1163
+ # mode — an empty wave response means the wave is done, so
1164
+ # the loop should stop rather than pull an unrelated task.
1165
+ if not task and not self._target_wave_id:
1157
1166
  task = client.get_next_task(
1158
1167
  project_id=self.project_id,
1159
1168
  agent_id=self._agent_id,
@@ -1412,6 +1421,7 @@ async def run_agent(
1412
1421
  branch_config: BranchConfig | None = None,
1413
1422
  task_id: str | None = None,
1414
1423
  agent_id: str | None = None,
1424
+ wave_id: str | None = None,
1415
1425
  ) -> dict[str, Any]:
1416
1426
  """Run the steerdev agent.
1417
1427
 
@@ -1461,6 +1471,7 @@ async def run_agent(
1461
1471
  branch_config=branch_config,
1462
1472
  task_id=task_id,
1463
1473
  agent_id=agent_id,
1474
+ wave_id=wave_id,
1464
1475
  )
1465
1476
 
1466
1477
  # Install signal handler that kills the subprocess immediately on Ctrl+C
@@ -0,0 +1,60 @@
1
+ """Tests for wave-scoped task fetching (sandbox wave dispatch).
2
+
3
+ `steerdev run --wave-id <id>` scopes scheduling to a single wave: the client
4
+ passes `wave_id` to GET /tasks/next, and the server returns tasks only from that
5
+ wave (404 → None once exhausted) so the run loop stops instead of bleeding into
6
+ another wave or an unrelated task.
7
+ """
8
+
9
+ from typing import Any
10
+ from unittest.mock import MagicMock, patch
11
+
12
+ import httpx
13
+
14
+ from steerdev_agent.api.tasks import TasksClient
15
+
16
+
17
+ def _make_response(
18
+ status_code: int,
19
+ json_body: dict[str, Any] | None = None,
20
+ text: str = "",
21
+ ) -> MagicMock:
22
+ resp = MagicMock(spec=httpx.Response)
23
+ resp.status_code = status_code
24
+ resp.text = text or (str(json_body) if json_body else "")
25
+ if json_body is not None:
26
+ resp.json.return_value = json_body
27
+ return resp
28
+
29
+
30
+ class TestGetNextWaveScoping:
31
+ def test_wave_id_is_forwarded_as_param(self):
32
+ with TasksClient(api_key="test-key") as client:
33
+ mock_resp = _make_response(200, {"wave": {"id": "wave-1"}, "context": {}})
34
+ with patch.object(client, "get", return_value=mock_resp) as mock_get:
35
+ client.get_next_wave(
36
+ project_id="proj-1",
37
+ agent_id="agent-1",
38
+ wave_id="wave-1",
39
+ )
40
+ params = mock_get.call_args.kwargs["params"]
41
+ assert params["wave_id"] == "wave-1"
42
+ assert params["waves"] == "true"
43
+ assert params["project_id"] == "proj-1"
44
+ assert params["agent_id"] == "agent-1"
45
+
46
+ def test_wave_id_omitted_when_not_scoped(self):
47
+ with TasksClient(api_key="test-key") as client:
48
+ mock_resp = _make_response(200, {"id": "task-1"})
49
+ with patch.object(client, "get", return_value=mock_resp) as mock_get:
50
+ client.get_next_wave(project_id="proj-1")
51
+ params = mock_get.call_args.kwargs["params"]
52
+ assert "wave_id" not in params
53
+
54
+ def test_exhausted_wave_returns_none_on_404(self):
55
+ # A wave-scoped request 404s once the wave is done — the loop stops.
56
+ with TasksClient(api_key="test-key") as client:
57
+ mock_resp = _make_response(404, text="No tasks available")
58
+ with patch.object(client, "get", return_value=mock_resp):
59
+ result = client.get_next_wave(project_id="proj-1", wave_id="wave-1")
60
+ assert result is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes